mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
187980de50 | ||
|
|
b9f100e5dc | ||
|
|
46529ee092 | ||
|
|
4c4572121d | ||
|
|
c5cdc81667 |
@@ -6,13 +6,12 @@ It is highly recommended to read the '''[[Standard Operating Procedure|Standard
|
||||
* '''[[Standard Operating Procedure#Controlled Items|Controlled Items]]:''' If a member of station crew requires access to a Controlled Item for use in their job, or for regular function, they must obtain the signed and stamped approval of a relevant member of Station Command or the CO. The latter is able to override the decisions of Station Command in this regard, however.
|
||||
* '''[[Standard Operating Procedure#Entity Definitions|Entity]]:''' Any thing with distinct, independent existence.
|
||||
* '''[[Standard Operating Procedure#Entity Definitions|Employee]]:''' An entity who has an employment contract with a Company.
|
||||
* '''[[Standard Operating Procedure#Entity Definitions|Pet]]:''' Any Entity that is provided to Company's Space Stations - either through the station's Logistics Department, or brought on board by the crew - for the purposes of emotional support.
|
||||
* '''[[Standard Operating Procedure#Security Jurisdiction|Security Jurisdiction]]:''' Station law enforcement officers are bound by Space Law; they cannot operate outside of standard accessible areas, or perform searches, unless such action is allowed by either Alert Procedure or a valid warrant.
|
||||
* '''[[Standard Operating Procedure#Arrest|Arrest]]:'''Any entity may be arrested, during which they are restrained, informed of their charges, and taken to the brig for search and processing under '''[[Standard Operating Procedure#Brig Procedure|Brig Procedure]]'''.
|
||||
* '''[[Standard Operating Procedure#Detainment and Arrest|Detainment & Arrest]]:''' Members of law enforcement may detain entities under '''[[Standard Operating Procedure#Probable Cause|Probable Cause]]''' of criminal activity. These detainees must be informed of the crime they are suspected of committing, and may be moved to a secure location and/or restrained while law enforcement determine if a crime was committed. Such detention should last no longer than five minutes. Any entity may be arrested, during which they are restrained, informed of their charges, and taken to the brig for search and processing under '''[[Standard Operating Procedure#Brig Procedure|Brig Procedure]]'''.
|
||||
* '''[[Standard Operating Procedure#Sentencing|Sentencing]]:''' Any member of law enforcement is allowed to pass sentence on Misdemeanor, Felony, and Grand Felony charges. The Chief Justice, or in their absence, the Head of Security are allowed to pass sentence on all Charges.
|
||||
* '''[[Standard Operating Procedure#Treatment of Prisoners|Treatment of Prisoners]]:''' Prisoners must be provided with food, water, clothing, free movement within the brig, adequate medical care, access to the Common and Prison radio channels, and moral, spiritual, or legal counseling if requested and available. If the brig becomes uninhabitable, prisoners must be securely and safely relocated to another area until the brig is functional once more. Prisoners sentenced to Extended Confinement have additional rights and firmer restrictions that must be upheld by law enforcement officials.
|
||||
* '''[[Standard Operating Procedure#Paroles and Pardons|Paroles & Pardons]]:''' Parole may be offered under the discretion of the Warden, Head of Security, or CO. A parole hearing requires only a Judge, as discussed within Trial Procedure. Pardons may be issued by the arresting officer, the Judge of a trial, by the Warden (to those within the permanent brig), or by the CO, when deemed in the best interest of the crew or vessel, or when the circumstances of the crime warrant suspension of sentence.
|
||||
* '''[[Standard Operating Procedure#Pressing Charges|Pressing Charges]]:''' Any Employee may press charges against any other entity. Individuals may only be charged with a crime if it can be proven beyond reasonable doubt that they have committed the crime.
|
||||
* '''[[Standard Operating Procedure#Pressing Charges|Pressing Charges]]:''' Any Employee may press charges against any other entity. Individuals may only be charged with a crime if it can be proveny beyond reasonable doubt that they have committed the crime.
|
||||
* '''[[Standard Operating Procedure#Trial Procedure|Trial Procedure]]:''' A criminal trial’s Judge must be, in sequential order: The Chief Justice, the Court Clerk, an impartial member of Justice or Station Command, or the Warden. The Prosecution should ideally be the Prosecutor, or an available member of Station Command or Station Security in that order. The Defense should ideally be a member of the Justice department assigned to the defendant, though they are allowed to represent themselves if they wish. If insufficient crew are available, the Judge is fully authorized and strongly encouraged to pass a sentence of Extended Confinement until a proper trial can be held.
|
||||
* '''[[Alert Procedure]]:''' Codes Blue, Red, Gamma, and Delta authorize Security to carry progressively more advanced equipment, and loosen '''[[Standard Operating Procedure#Rules of Engagement|Rules of Engagement]]''' regarding their use. The former two codes disallow openly carrying firearms by non-Security personnel; the latter two suspend many rights to privacy, and grant Security the power to do whatever neccessary to retake or scuttle the station.
|
||||
|
||||
@@ -211,7 +210,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|
||||
|style= "border:1px solid black;"| [[File:SL_Kidnapping.png]]
|
||||
|style= "border:1px solid black;"| Kidnapping
|
||||
|style= "border:1px solid black;"| 16 minutes
|
||||
|style= "border:1px solid black;"| To unlawfully confine or restrict the free movement of an Employee against its will.
|
||||
|style= "border:1px solid black;"| To unlawfully confine or restrict the free movement of an entity against its will.
|
||||
|-
|
||||
| style="border:1px solid black;" | 303
|
||||
| style="border:1px solid black;" | WIP
|
||||
@@ -311,7 +310,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|
||||
|style= "border:1px solid black;"| WIP
|
||||
!style= "border:1px solid black;"| Identity Theft
|
||||
|style= "border:1px solid black;"| 5 minutes
|
||||
|style= "border:1px solid black;"| To maliciously assume the identity of an Employee, such as through writing, attire, and/or speech.
|
||||
|style= "border:1px solid black;"| To maliciously assume the identity of an entity, such as through writing, attire, and/or speech.
|
||||
|}
|
||||
|
||||
|
||||
@@ -330,7 +329,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|
||||
| style="border: 1px solid black;" | [[File:SL_AnimalCruelty.png]]
|
||||
! style="border: 1px solid black;" | {{anchor|Animal Cruelty}}Animal Cruelty
|
||||
| style="border: 1px solid black;" | 5 minutes
|
||||
| style="border: 1px solid black;" | To inflict unnecessary suffering on a Pet with malicious intent.
|
||||
| style="border: 1px solid black;" | To inflict unnecessary suffering on a sapient being with malicious intent.
|
||||
|-
|
||||
|style= "border:1px solid black;"| 102
|
||||
|style= "border:1px solid black;"| WIP
|
||||
|
||||
@@ -8,6 +8,7 @@ The terms specified below are defined for further use within the procedures.
|
||||
:* Command Staff/Station Command: Members of the Command department who lead a department aboard the station.
|
||||
:* Crew: All Employees employed by Station Command, or by NT to reside on the station and perform an established function therein. This includes any Employee listed on the Crew Manifest or issued a functional NT identification and access card. Individuals imprisoned by NT, along with those imprisoned during the course of a shift for violations of Space Law, are considered Crew.
|
||||
:* Judge: An individual legally appointed to preside over a hearing, as specified within Hearing Procedure.
|
||||
:* Detainee: Any Employee whose free movement has been curtailed by law enforcement on suspicion of criminal activity. They have not, however, been officially charged with a crime.
|
||||
:* Arrestee: Any Employee who has been formally charged with a crime.
|
||||
:* Prisoner: Any Employee who has been sentenced for the commission of a crime, or awaiting a hearing for a capital offense.
|
||||
:* Permanent Brig/Permabrig/Brig: A secure location for the long-term confinement of Prisoners. Typically outfitted to sustain the basic survival needs of the Prisoners within.
|
||||
@@ -21,7 +22,7 @@ The following definitions are provided to clarify the legal protections- and obl
|
||||
== Entity ==
|
||||
Any individual with a distinct existence. Entities may also encompass a plurality of identities contained within a single physical form. NanoTrasen does not require Entities be offered the protections of Standard Operating Procedure, Space Law, or Rules of Engagement unless further defined below. Security Officers are encouraged to be reasonable with their use of force regarding circumstances, especially if Entity's termination would leave lawsuits or tarnish reputation for NanoTrasen.
|
||||
== Pet ==
|
||||
Any Entity that is provided to NanoTrasen Space Stations - either through the station's Logistics Department, or brought on board by the crew - for the purposes of emotional support. Any harm against such a Pet may render the offender liable to the crime of Animal Cruelty.
|
||||
Any Entity that is provided to NanoTrasen Space Stations- either through the station's Logistics Department, or brought on board by the crew- for the purposes of emotional support. Any harm against such a Pet may render the offender liable to the crime of Animal Cruelty.
|
||||
Examples of Pets include, but are not limited to:
|
||||
:* Renault the Fox
|
||||
:* Smile the Rainbow Slime
|
||||
@@ -100,27 +101,27 @@ A list of such Substances follow:
|
||||
== Controlled Armaments ==
|
||||
|
||||
|
||||
Controlled Armaments are grouped into categories of increasing severity. If the offender possesses several categories of contraband, only the highest severity of said items should be charged.
|
||||
Controlled Armaments are grouped into categories of increasing severity.
|
||||
|
||||
'''Category A:''' ''Illegal possession of Cat. A controlled armaments may incur a sentence of up to 5min brig and further legal action as the situation demands.''
|
||||
'''Category A:''' ''Illegal possession of Cat. A controlled armaments may incur a sentence of up to 5min brig for each separate and distinct item, confiscation of the offending item(s), and further legal action as the situation demands.''
|
||||
|
||||
* Any object or instrument specifically designed, adapted, or used to cause physical harm in close combat
|
||||
* Any object or equipment specifically designed or adapted to protect against physical harm caused by weapons, projectiles, or other forms of direct assault
|
||||
* Any object or instrument specifically designed or adapted to incapacitate or disorient by using light or sound without causing permanent injury
|
||||
* Any object or equipment specifically designed or adapted for explicitly training purposes that would otherwise fall under another category
|
||||
|
||||
'''Category B:''' ''Illegal possession of Cat. B controlled armaments may incur a sentence of up to 10min brig and further legal action as the situation demands.''
|
||||
'''Category B:''' ''Illegal possession of Cat. B controlled armaments may incur a sentence of up to 10min brig for each separate and distinct item, confiscation of the offending item(s), and further legal action as the situation demands.''
|
||||
|
||||
* Any object or instrument specifically designed or adapted to cause physical harm in close combat, which employs a powered system to enhance performance
|
||||
* Any manually-operated ballistic arms that possess an internal magazine or feeding system, or lack an internal magazine or feeding system altogether
|
||||
|
||||
'''Category C:''' ''Illegal possession of Cat. C controlled armaments may incur a sentence of up to 15min brig and further legal action as the situation demands.''
|
||||
'''Category C:''' ''Illegal possession of Cat. C controlled armaments may incur a sentence of up to 15min brig for each separate and distinct item, confiscation of the offending item(s), and further legal action as the situation demands.''
|
||||
|
||||
* Any manually-operated ballistic arms that use an external magazine or feeding system
|
||||
* Any semi-automatic ballistic arms not easily concealable within a pocket or coat
|
||||
* Any object or instrument specifically designed or adapted for explicitly less-lethal purposes that would otherwise fall under another category
|
||||
|
||||
'''Category D:''' ''Illegal possession of Cat. D controlled armaments may incur a sentence of up to 20min brig and further legal action as the situation demands.''
|
||||
'''Category D:''' ''Illegal possession of Cat. D controlled armaments may incur a sentence of up to 20min brig for each separate and distinct item, confiscation of the offending item(s), and further legal action as the situation demands.''
|
||||
|
||||
* Any semi-automatic ballistic arms that are easily concealable
|
||||
* Any fully-automatic ballistic arms
|
||||
@@ -128,7 +129,7 @@ Controlled Armaments are grouped into categories of increasing severity. If the
|
||||
* Any object or device specifically designed or adapted to cause physical harm through detonation of an explosive charge
|
||||
* Any object or equipment specifically designed or adapted to protect against both a space environment and physical harm caused by weapons, projectiles, or other forms of direct assault
|
||||
|
||||
'''Category E:''' ''Illegal possession of Cat. E controlled armaments may incur a sentence of up to 25min brig and further legal action as the situation demands.''
|
||||
'''Category E:''' ''Illegal possession of Cat. E controlled armaments may incur a sentence of up to 25min brig for each separate and distinct item, confiscation of the offending item(s), and further legal action as the situation demands.''
|
||||
|
||||
* Any crew-served or emplaced weapon
|
||||
* Any object or instrument specifically designed or adapted to launch large-calibre explosives or projectiles for offensive or defensive purposes
|
||||
@@ -217,7 +218,7 @@ Should the mutiny be unsuccessful, only the head officer(s) who approved the mut
|
||||
Should the mutiny be successful, the head officer(s) who approved the mutiny must select a new Commanding Officer per Line of Succession. The new CO will then sentence the previous heads of staff for their crimes per Space Law.
|
||||
|
||||
== Foreign Vessels ==
|
||||
Should civilian and military vessels receive a docking request from another neutral vessel, they may approve or deny such a request. Any vessel may, with consent from the docking entity, be searched by law enforcement upon arrival, and its crew granted free movement accompanied by an escort at the discretion of the CO or law enforcement officials. Crew of vessels legally docked on NanoTrasen Stations are considered to be in contract with NanoTrasen and as such are protected by Space Law, Standard operating Procedure and the Rules of Engagement.
|
||||
Should civilian and military vessels receive a docking request from another neutral vessel, they may approve or deny such a request. Any vessel may, with consent from the docking entity, be searched by law enforcement upon arrival, and its crew granted free movement accompanied by an escort at the discretion of the CO or law enforcement officials.
|
||||
|
||||
Crew of foreign vessels may be refused entry, at any time and for any reason, and must be returned to, or be escorted to, their own vessel where applicable. Failure to comply may constitute trespass. Docking vessels bearing a warrant or commission represent an exception, and may not be denied entry or be forced to submit to a search.
|
||||
|
||||
@@ -252,7 +253,7 @@ Martial Law may only be declared by Central Command. Under Martial Law Security
|
||||
* authorised to take whatever actions deemed necessary to neutralise or repel station threats.
|
||||
* authorized to summarily execute station threats.
|
||||
* not bound to the Rules of Engagement.
|
||||
* not obligated to provide any medical care to prisoners, including revival.
|
||||
* not obligated to provide any medical care to prisoners and detainees, including revival.
|
||||
|
||||
== Armory Procedure ==
|
||||
The armory and its usage are governed by the Warden. The Warden is responsible for stocking, distributing, and recording the collection of weapons used by the station during the shift. They are also afforded the authority to arm security at their discretion, so long as the weaponry distributed is in appropriate response to [[Alert Procedure]].
|
||||
|
||||
+24
-76
@@ -22,57 +22,39 @@ Examples:
|
||||
|
||||
# Changes to upstream files
|
||||
|
||||
Follow a few guidelines when modifying non-DeltaV files, to help us manage our project. (files that are not in `_DV` or `Nyano` folders)
|
||||
If you make a change to an upstream C# or YAML file **you must add comments on or around the changed lines**.
|
||||
The comments should clarify what changed, to make conflict resolution simpler when a file is changed upstream.
|
||||
|
||||
Primarily, **add comments on or around all new or changed lines** in upstream files. Explain what was changed to make resolving merge conflicts easier; we regularly merge new upstream changes into our project.
|
||||
For YAML specifically, if you add a new component to a prototype add the comment to the `type: ...` line.
|
||||
If you just modify some fields of a component, comment the fields instead.
|
||||
|
||||
### Changing Upstream YAML .yml files
|
||||
For C# files, if you are adding a lot of code try to put it in a partial class when it makes sense.
|
||||
|
||||
**Add comments on or around any changed lines.**
|
||||
The exception to this is early merging commits that are going to be cherry picked in the future regardless, there's no harm in leaving them as-is.
|
||||
|
||||
If you add a new component to a prototype, add an explanation to the `type: ...` line. Example:
|
||||
As an aside, fluent (.ftl) files **do not support comments on the same line** as a locale value, so be careful when changing them.
|
||||
|
||||
## Examples of comments in upstream files
|
||||
|
||||
A single line comment on a changed yml field:
|
||||
```yml
|
||||
- type: entity
|
||||
parent: MobSiliconBase
|
||||
id: MobSupplyBot
|
||||
components:
|
||||
- type: InteractionPopup # DeltaV - Make supplybots pettable
|
||||
interactSuccessString: petting-success-supplybot
|
||||
interactFailureString: petting-failure-supplybot
|
||||
interactSuccessSound:
|
||||
path: /Audio/Ambience/Objects/periodic_beep.ogg
|
||||
parent: BasePDA
|
||||
id: SciencePDA
|
||||
name: epistemics PDA # DeltaV - Epistemics Department replacing Science
|
||||
```
|
||||
|
||||
Whereas if you just modify some fields of a component, comment the fields instead, using inline or block comments. Examples:
|
||||
|
||||
A pair of comments enclosing a list of added items to starting gear:
|
||||
```yml
|
||||
- type: entityTable
|
||||
id: FillLockerWarden
|
||||
table: !type:AllSelector
|
||||
children:
|
||||
- id: ClothingHandsGlovesCombat
|
||||
- id: ClothingShoesBootsSecurityMagboots # DeltaV - Added security magboots.
|
||||
- id: ClothingShoesBootsJack
|
||||
#- id: ClothingOuterCoatWarden # DeltaV - removed for incongruence
|
||||
#- id: ClothingOuterWinterWarden # DeltaV - removed for incongruence
|
||||
- id: RubberStampWarden
|
||||
- id: DoorRemoteArmory
|
||||
- id: HoloprojectorSecurity
|
||||
storage:
|
||||
back:
|
||||
- EmergencyRollerBedSpawnFolded
|
||||
# Begin DeltaV additions
|
||||
- id: WeaponEnergyShotgun
|
||||
- id: BoxPDAPrisoner
|
||||
- id: LunchboxSecurityFilledRandom
|
||||
prob: 0.3
|
||||
- BodyBagFolded
|
||||
- Portafib
|
||||
# End DeltaV additions
|
||||
```
|
||||
|
||||
### Changing Upstream C# .cs files
|
||||
|
||||
If you are adding a lot of C# code, then take advantage of partial classes. Put the new code in its own file in the `_DV` folder, if it makes sense.
|
||||
|
||||
Otherwise, **add comments on or around any changed lines.**
|
||||
|
||||
A comment on a new imported namespace:
|
||||
```cs
|
||||
using Content.Server.Psionics.Glimmer; // DeltaV
|
||||
@@ -95,40 +77,6 @@ private EntityUid Slice(...)
|
||||
}
|
||||
```
|
||||
|
||||
### Changing Upstream Localization Fluent .ftl files
|
||||
|
||||
**Move all changed locale strings to a new DeltaV file** - use a `.ftl` file in the `_DV` folder. Comment out the old strings in the upstream file, and explain that they were moved.
|
||||
|
||||
Example:
|
||||
|
||||
Commented out old string in `Resources\Locale\en-US\xenoarchaeology\artifact-analyzer.ftl`
|
||||
```
|
||||
# DeltaV - moved to _DV file
|
||||
#analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
|
||||
# [true] {$info}
|
||||
# *[false] Unlock nodes to gain info
|
||||
#}[/color][/font]
|
||||
```
|
||||
|
||||
The new version of the string in `Resources\Locale\en-US\_DV\xenoarchaeology\artifact-analyzer.ftl`
|
||||
```
|
||||
analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
|
||||
[vagueandspecific] {$vagueInfo} ({$specificInfo})
|
||||
[vagueonly] {$vagueInfo} (unable to detect details)
|
||||
[simple] {$specificInfo}
|
||||
[hidden] Unable to detect (unlock to discover)
|
||||
*[noinfo] Unlock nodes to gain info
|
||||
}[/color][/font]
|
||||
```
|
||||
|
||||
Also keep in mind that fluent (.ftl) files **do not support comments on the same line** as a locale value, so be careful when commenting.
|
||||
|
||||
### Early merges
|
||||
|
||||
We mostly merge upstream changes in big chunks (e.g. a month of upstream PRs at a time), but urgent changes can be merged early, separately.
|
||||
|
||||
Early merges are an exception to the above rules - if cherry-picking a PR for an early merge, you don't need to add `#DeltaV` comments, since the code is coming directly from upstream without any changes.
|
||||
|
||||
# Mapping
|
||||
|
||||
If you want to make changes to a map, get in touch with its maintainer to make sure you don't both make changes at the same time.
|
||||
@@ -144,11 +92,11 @@ Please limit changelogs on map PRs to **significant** map alterations or additio
|
||||
Format for map PRs looks like:
|
||||
```
|
||||
:cl: Yourname
|
||||
MAPS:
|
||||
- add: Mapname: Added fun!
|
||||
- remove: Mapname: Removed fun!
|
||||
- tweak: Mapname: Changed fun!
|
||||
- fix: Mapname: Fixed fun!
|
||||
MAPS: Mapname
|
||||
- add: Added fun!
|
||||
- remove: Removed fun!
|
||||
- tweak: Changed fun!
|
||||
- fix: Fixed fun!
|
||||
```
|
||||
|
||||
# Before you submit
|
||||
|
||||
@@ -59,9 +59,6 @@ public abstract class CartridgeLoaderBoundUserInterface : BoundUserInterface
|
||||
SendCartridgeUiReadyEvent(_activeProgram.Value);
|
||||
}
|
||||
|
||||
if (_activeCartridgeUI is IDisposable disposableUi) // Funky: Handle cleanup for popups and such.
|
||||
disposableUi.Dispose();
|
||||
|
||||
_activeCartridgeUI = ui;
|
||||
_activeUiFragment?.Dispose();
|
||||
_activeUiFragment = control;
|
||||
@@ -124,13 +121,8 @@ public abstract class CartridgeLoaderBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing) // Funky: Handle cleanup for popups and such.
|
||||
{
|
||||
if (_activeCartridgeUI is IDisposable disposableUi)
|
||||
disposableUi.Dispose();
|
||||
|
||||
if (disposing)
|
||||
_activeUiFragment?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected CartridgeComponent? RetrieveCartridgeComponent(EntityUid? cartridgeUid)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
@@ -17,18 +14,6 @@ public sealed partial class NewsReaderUiFragment : BoxContainer
|
||||
|
||||
public event Action? OnNotificationSwithPressed;
|
||||
|
||||
// DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
private static readonly Type[] AllowedTags =
|
||||
[
|
||||
typeof(BoldItalicTag),
|
||||
typeof(BoldTag),
|
||||
typeof(BulletTag),
|
||||
typeof(ColorTag),
|
||||
typeof(HeadingTag),
|
||||
typeof(ItalicTag),
|
||||
typeof(MonoTag),
|
||||
];
|
||||
|
||||
public NewsReaderUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -46,17 +31,16 @@ public sealed partial class NewsReaderUiFragment : BoxContainer
|
||||
Author.Visible = true;
|
||||
|
||||
PageName.Text = article.Title;
|
||||
PageText.SetMessage(FormattedMessage.FromMarkupPermissive(article.Content), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
PageText.SetMarkupPermissive(article.Content);
|
||||
|
||||
PageNum.Text = $"{targetNum}/{totalNum}";
|
||||
|
||||
NotificationSwitch.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
|
||||
|
||||
var shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
|
||||
string shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
|
||||
ShareTime.SetMarkup(Loc.GetString("news-read-ui-time-prefix-text") + " " + shareTime);
|
||||
|
||||
var author = Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author ?? Loc.GetString("news-read-ui-no-author")); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
Author.SetMessage(FormattedMessage.FromMarkupPermissive(author), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
Author.SetMarkup(Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author != null ? article.Author : Loc.GetString("news-read-ui-no-author")));
|
||||
|
||||
Prev.Disabled = targetNum <= 1;
|
||||
Next.Disabled = targetNum >= totalNum;
|
||||
|
||||
@@ -58,13 +58,6 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
_menu?.UpdateState(targets, st.SelectedId);
|
||||
}
|
||||
|
||||
// Begin Delta-V: Override for the name of the chameleon stuff, so you can have items use the chameleon system without being illegal.
|
||||
if (_menu is { } menu && EntMan.TryGetComponent<ChameleonClothingComponent>(Owner, out var chameleon) && chameleon.WindowTitleOverride is { } titleOverride)
|
||||
{
|
||||
_menu.Title = Loc.GetString(titleOverride);
|
||||
}
|
||||
// End Delta-V
|
||||
}
|
||||
|
||||
private void OnIdSelected(string selectedId)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -20,18 +18,6 @@ public sealed partial class ArticleEditorPanel : Control
|
||||
|
||||
private bool _preview;
|
||||
|
||||
// DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
private static readonly Type[] AllowedTags =
|
||||
[
|
||||
typeof(BoldItalicTag),
|
||||
typeof(BoldTag),
|
||||
typeof(BulletTag),
|
||||
typeof(ColorTag),
|
||||
typeof(HeadingTag),
|
||||
typeof(ItalicTag),
|
||||
typeof(MonoTag),
|
||||
];
|
||||
|
||||
public ArticleEditorPanel()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -43,7 +29,7 @@ public sealed partial class ArticleEditorPanel : Control
|
||||
// Customize scrollbar width and margin. This is not possible in xaml
|
||||
var scrollbar = ContentField.GetChild(1);
|
||||
scrollbar.SetWidth = 6f;
|
||||
scrollbar.Margin = new Thickness(9, 0, 2, 0);
|
||||
scrollbar.Margin = new Thickness(9, 0, 2 , 0);
|
||||
|
||||
RichTextInfoLabel.TooltipSupplier = sender =>
|
||||
{
|
||||
@@ -95,9 +81,7 @@ public sealed partial class ArticleEditorPanel : Control
|
||||
|
||||
TextEditPanel.Visible = !_preview;
|
||||
PreviewPanel.Visible = _preview;
|
||||
|
||||
var articleBody = Rope.Collapse(ContentField.TextRope); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
PreviewLabel.SetMessage(FormattedMessage.FromMarkupPermissive(articleBody), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
||||
PreviewLabel.SetMarkupPermissive(Rope.Collapse(ContentField.TextRope));
|
||||
}
|
||||
|
||||
private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
|
||||
|
||||
@@ -19,9 +19,5 @@
|
||||
Align="Center"
|
||||
Access="Public"/>
|
||||
</BoxContainer>
|
||||
<Label Name='WarningLabel'
|
||||
Align="Center"
|
||||
StyleClasses="WindowFooterText"
|
||||
Text="{Loc 'war-declarator-standards-warning'}" /> <!-- DeltaV - warops LRP announcement warning -->
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Content.Client.PDA
|
||||
|
||||
if (state.PdaOwnerInfo.ActualOwnerName != null)
|
||||
{
|
||||
_pdaOwner = FormattedMessage.EscapeText(state.PdaOwnerInfo.ActualOwnerName); // DeltaV - Escape actual owner
|
||||
_pdaOwner = state.PdaOwnerInfo.ActualOwnerName;
|
||||
PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
|
||||
("actualOwnerName", _pdaOwner)));
|
||||
PdaOwnerLabel.Visible = true;
|
||||
@@ -159,8 +159,8 @@ namespace Content.Client.PDA
|
||||
|
||||
if (state.PdaOwnerInfo.IdOwner != null || state.PdaOwnerInfo.JobTitle != null)
|
||||
{
|
||||
_owner = FormattedMessage.EscapeText(state.PdaOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown")); // DeltaV - Escape owner and job
|
||||
_jobTitle = FormattedMessage.EscapeText(state.PdaOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned")); // DeltaV - Escape owner and job
|
||||
_owner = state.PdaOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown");
|
||||
_jobTitle = state.PdaOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned");
|
||||
IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui",
|
||||
("owner", _owner),
|
||||
("jobTitle", _jobTitle)));
|
||||
|
||||
@@ -714,13 +714,6 @@ public sealed partial class ChatUIController : UIController
|
||||
box.ChatInput.ChannelSelector.UpdateChannelSelectButton(box.SelectedChannel, null);
|
||||
else
|
||||
box.ChatInput.ChannelSelector.UpdateChannelSelectButton(prefixChannel, radioChannel);
|
||||
|
||||
// DeltaV begin - refresh indicator change
|
||||
if (CurrentChannel != prefixChannel)
|
||||
_typingIndicator?.ClientSubmittedChatText();
|
||||
|
||||
CurrentChannel = prefixChannel;
|
||||
// DeltaV end
|
||||
}
|
||||
|
||||
public (ChatSelectChannel chatChannel, string text, RadioChannelPrototype? radioChannel) SplitInputContents(string text)
|
||||
|
||||
@@ -238,7 +238,6 @@ public partial class ChatBox : UIWidget
|
||||
return;
|
||||
|
||||
ChatInput.ChannelSelector.Select(toSelect);
|
||||
_controller.CurrentChannel = toSelect; // DeltaV - Alt Chat Indicators
|
||||
}
|
||||
|
||||
private void OnInputKeyBindDown(GUIBoundKeyEventArgs args)
|
||||
@@ -271,14 +270,12 @@ public partial class ChatBox : UIWidget
|
||||
_controller.UpdateSelectedChannel(this);
|
||||
|
||||
// Warn typing indicator about change
|
||||
// _controller.NotifyChatTextChange(); // DeltaV
|
||||
_controller.NotifySpecificChatTextChange(SelectedChannel); // DeltaV - Alt Chat Indicators
|
||||
_controller.NotifyChatTextChange();
|
||||
}
|
||||
|
||||
private void OnFocusEnter(LineEditEventArgs args)
|
||||
{
|
||||
// Warn typing indicator about focus
|
||||
_controller.CurrentChannel = SelectedChannel; // DeltaV - Alt Chat Indicators
|
||||
_controller.NotifyChatFocus(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ using Content.Client.Ghost;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Client.UserInterface.Systems.Ghost.Widgets;
|
||||
using Content.Shared.Ghost;
|
||||
using Robust.Client.Console; // Frontier
|
||||
using Robust.Shared.Console; // Frontier
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Content.Shared._Corvax.Respawn; // Frontier
|
||||
using Content.Client._Corvax.Respawn; // Frontier
|
||||
using Content.Shared._NF.CCVar; // Frontier
|
||||
using Robust.Shared.Configuration; // Frontier
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Ghost;
|
||||
@@ -19,9 +21,7 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!; // Frontier
|
||||
|
||||
[UISystemDependency] private readonly GhostSystem? _system = default;
|
||||
|
||||
// Updated when get death time from the server.
|
||||
private TimeSpan? DeathTime;
|
||||
[UISystemDependency] private readonly RespawnSystem? _respawn = default; // Frontier
|
||||
|
||||
private GhostGui? Gui => UIManager.GetActiveUIWidgetOrNull<GhostGui>();
|
||||
|
||||
@@ -32,9 +32,6 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
|
||||
var gameplayStateLoad = UIManager.GetUIController<GameplayStateLoadController>();
|
||||
gameplayStateLoad.OnScreenLoad += OnScreenLoad;
|
||||
gameplayStateLoad.OnScreenUnload += OnScreenUnload;
|
||||
|
||||
// DeltaV
|
||||
SubscribeNetworkEvent<RespawnResetEvent>(OnRespawnReseted);
|
||||
}
|
||||
|
||||
private void OnScreenLoad()
|
||||
@@ -67,14 +64,23 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
|
||||
system.GhostRoleCountUpdated -= OnRoleCountUpdated;
|
||||
}
|
||||
|
||||
// Begin DeltaV
|
||||
private void OnRespawnReseted(RespawnResetEvent ev, EntitySessionEventArgs args)
|
||||
// Begin Frontier
|
||||
public void OnSystemLoaded(RespawnSystem system)
|
||||
{
|
||||
DeathTime = ev.Time;
|
||||
UpdateGui();
|
||||
UpdateRespawn();
|
||||
system.RespawnReseted += OnRespawnReseted;
|
||||
}
|
||||
// End DeltaV
|
||||
|
||||
public void OnSystemUnloaded(RespawnSystem system)
|
||||
{
|
||||
system.RespawnReseted -= OnRespawnReseted;
|
||||
}
|
||||
|
||||
private void OnRespawnReseted()
|
||||
{
|
||||
UpdateGui();
|
||||
UpdateRespawn(_respawn?.RespawnResetTime);
|
||||
}
|
||||
// End Frontier
|
||||
|
||||
public void UpdateGui()
|
||||
{
|
||||
@@ -88,9 +94,9 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
|
||||
}
|
||||
|
||||
// Begin Frontier
|
||||
private void UpdateRespawn()
|
||||
private void UpdateRespawn(TimeSpan? timeOfDeath)
|
||||
{
|
||||
Gui?.UpdateRespawn(DeathTime);
|
||||
Gui?.UpdateRespawn(timeOfDeath);
|
||||
}
|
||||
// End Frontier
|
||||
|
||||
@@ -110,7 +116,7 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
|
||||
return;
|
||||
|
||||
Gui.Visible = true;
|
||||
UpdateRespawn(); // Frontier, DeltaV
|
||||
UpdateRespawn(_respawn?.RespawnResetTime); // Frontier
|
||||
UpdateGui();
|
||||
}
|
||||
|
||||
@@ -197,4 +203,11 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
|
||||
{
|
||||
_system?.OpenGhostRoles();
|
||||
}
|
||||
|
||||
// Begin Frontier
|
||||
private void RespawnPressed()
|
||||
{
|
||||
IoCManager.Resolve<IClientConsoleHost>().RemoteExecuteCommand(null, "ghostrespawn");
|
||||
}
|
||||
// End Frontier
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using Content.Shared.Research.Components;
|
||||
using Content.Shared._DV.Xenoarchaeology.BUI;// DeltaV
|
||||
using Content.Shared.Xenoarchaeology.Equipment.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface; // DeltaV
|
||||
|
||||
namespace Content.Client.Xenoarchaeology.Ui;
|
||||
|
||||
@@ -55,15 +54,5 @@ public sealed class AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKe
|
||||
|
||||
_consoleMenu?.Dispose();
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
if (state is not AnalysisConsoleBoundUserInterfaceState consoleState)
|
||||
return;
|
||||
|
||||
_consoleMenu?.UpdateState(consoleState.Mult, consoleState.Ratio);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,7 @@
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<Control MinHeight="5"/>
|
||||
<RichTextLabel Name="GlimmerSumLabel" VerticalAlignment="Bottom" HorizontalAlignment="Left"/> <!-- DeltaV -->
|
||||
<RichTextLabel Name="ExtractionSumLabel" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
|
||||
<RichTextLabel Name="MultLabel" VerticalAlignment="Bottom" HorizontalAlignment="Left"/> <!-- DeltaV -->
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Numerics; // DeltaV
|
||||
using System.Text;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Resources;
|
||||
@@ -22,7 +21,7 @@ namespace Content.Client.Xenoarchaeology.Ui;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
{
|
||||
private static readonly TimeSpan ExtractInfoDisplayForDuration = TimeSpan.FromSeconds(5);// DeltaV
|
||||
private static readonly TimeSpan ExtractInfoDisplayForDuration = TimeSpan.FromSeconds(3);
|
||||
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IResourceCache _resCache = default!;
|
||||
@@ -37,14 +36,8 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
private Entity<XenoArtifactNodeComponent>? _currentNode;
|
||||
|
||||
private TimeSpan? _hideExtractInfoIn;
|
||||
|
||||
// Begin DeltaV - Variables for extraction calculation display
|
||||
private int _extractionSum;
|
||||
private int _glimmerSum;
|
||||
private float _multValue;
|
||||
private float _ratioValue;
|
||||
private bool _pressed;
|
||||
// End DeltaV
|
||||
|
||||
public event Action? OnServerSelectionButtonPressed;
|
||||
public event Action? OnExtractButtonPressed;
|
||||
|
||||
@@ -72,12 +65,7 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
OnServerSelectionButtonPressed?.Invoke();
|
||||
};
|
||||
|
||||
// DeltaV
|
||||
ExtractButton.OnPressed += _ =>
|
||||
{
|
||||
_pressed = true;
|
||||
OnExtractButtonPressed?.Invoke();
|
||||
};
|
||||
ExtractButton.OnPressed += StartExtract;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -96,20 +84,7 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
Update(_owner);
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
public void UpdateState(float mult, float ratio)
|
||||
{
|
||||
_multValue = mult;
|
||||
_ratioValue = ratio;
|
||||
if (_pressed)
|
||||
{
|
||||
_pressed = false;
|
||||
StartExtract();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void StartExtract() // DeltaV - args changed
|
||||
private void StartExtract(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if (!_artifactAnalyzer.TryGetArtifactFromConsole(_owner, out var artifact))
|
||||
return;
|
||||
@@ -117,8 +92,7 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
ExtractContainer.Visible = true;
|
||||
NodeViewContainer.Visible = false;
|
||||
|
||||
_extractionSum = 0;// DeltaV
|
||||
_glimmerSum = 0;// DeltaV
|
||||
_extractionSum = 0;
|
||||
var extractionMessage = new FormattedMessage();
|
||||
|
||||
var nodes = _xenoArtifact.GetAllNodes(artifact.Value);
|
||||
@@ -126,21 +100,16 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
var count = 0;
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var pointValue = _xenoArtifact.GetResearchValue(node) * _multValue;// DeltaV
|
||||
var glimmerValue = (pointValue / _ratioValue / _multValue);// DeltaV
|
||||
var pointValue = _xenoArtifact.GetResearchValue(node);
|
||||
if (pointValue <= 0)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
|
||||
var nodeId = _xenoArtifact.GetNodeId(node);
|
||||
_extractionSum += (int)pointValue;// DeltaV
|
||||
_glimmerSum += (int)(glimmerValue);// DeltaV
|
||||
var textPoints = Loc.GetString("analysis-console-extract-value", ("id", nodeId), ("value", (int)(pointValue)));
|
||||
var textGlimmer = Loc.GetString("analysis-console-glimmer-value", ("id", nodeId), ("value", (int)(glimmerValue)));// DeltaV
|
||||
extractionMessage.AddMarkupOrThrow(textPoints);
|
||||
extractionMessage.PushNewline();
|
||||
extractionMessage.AddMarkupOrThrow(textGlimmer);// DeltaV
|
||||
|
||||
var text = Loc.GetString("analysis-console-extract-value", ("id", nodeId), ("value", pointValue));
|
||||
extractionMessage.AddMarkupOrThrow(text);
|
||||
extractionMessage.PushNewline();
|
||||
}
|
||||
|
||||
@@ -151,12 +120,10 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
|
||||
ExtractionResearchLabel.SetMessage(extractionMessage);
|
||||
|
||||
ExtractionSumLabel.SetMarkup(Loc.GetString("analysis-console-extract-sum", ("value", _extractionSum)));// DeltaV
|
||||
GlimmerSumLabel.SetMarkup(Loc.GetString("analysis-console-glimmer-sum", ("value", _glimmerSum)));// DeltaV
|
||||
MultLabel.SetMarkup(Loc.GetString("analysis-console-glimmer-mult", ("value", _multValue.ToString("F2"))));// DeltaV
|
||||
ExtractionSumLabel.SetMarkup(Loc.GetString("analysis-console-extract-sum", ("value", _extractionSum)));
|
||||
|
||||
_audio.PlayGlobal(_owner.Comp.ScanFinishedSound, _owner, AudioParams.Default.WithVolume(1f));
|
||||
|
||||
OnExtractButtonPressed?.Invoke();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -235,34 +202,14 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
|
||||
var hasInfo = _xenoArtifact.HasUnlockedPredecessor(artifact.Value, node.Value);
|
||||
|
||||
// DeltaV - start of hide locked node effects
|
||||
var specificInfo = node.Value.Comp.EffectTipSpecific ?? "xenoarch-effect-tip-unknown";
|
||||
if (!hasInfo || (node.Value.Comp.LockedEffectTipHidden && node.Value.Comp.Locked))
|
||||
{
|
||||
EffectValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value",
|
||||
("state", hasInfo ? XenoArtifactEffectVisibility.Hidden : XenoArtifactEffectVisibility.NoInfo)));
|
||||
}
|
||||
else if (!node.Value.Comp.LockedEffectTipHidden && node.Value.Comp.LockedEffectTipVague && node.Value.Comp.EffectTipVague != null)
|
||||
{
|
||||
EffectValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value",
|
||||
("state", node.Value.Comp.Locked ? XenoArtifactEffectVisibility.VagueOnly : XenoArtifactEffectVisibility.VagueAndSpecific),
|
||||
("specificInfo", Loc.GetString(specificInfo)),
|
||||
("vagueInfo", Loc.GetString(node.Value.Comp.EffectTipVague))));
|
||||
}
|
||||
else
|
||||
{
|
||||
EffectValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value",
|
||||
("state", XenoArtifactEffectVisibility.Simple),
|
||||
("specificInfo", Loc.GetString(specificInfo))));
|
||||
}
|
||||
// DeltaV - end of hide locked node effects
|
||||
EffectValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value",
|
||||
("state", hasInfo),
|
||||
("info", _ent.GetComponentOrNull<MetaDataComponent>(node.Value)?.EntityDescription ?? string.Empty)));
|
||||
|
||||
var predecessorNodes = _xenoArtifact.GetPredecessorNodes(artifact.Value.Owner, node.Value);
|
||||
if (!hasInfo)
|
||||
{
|
||||
// DeltaV - start of hide locked node effects
|
||||
TriggerValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value", ("state", XenoArtifactEffectVisibility.NoInfo)));
|
||||
// DeltaV - end of hide locked node effects
|
||||
TriggerValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value", ("state", false)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -282,16 +229,5 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
ClassValueLabel.SetMarkup(Loc.GetString("analysis-console-info-class-value",
|
||||
("class", Loc.GetString($"artifact-node-class-{Math.Min(6, predecessorNodes.Count + 1)}"))));
|
||||
}
|
||||
|
||||
// DeltaV - start of hide locked node effects
|
||||
public enum XenoArtifactEffectVisibility : sbyte
|
||||
{
|
||||
NoInfo = 0,
|
||||
Hidden = 1,
|
||||
Simple = 2,
|
||||
VagueOnly = 3,
|
||||
VagueAndSpecific = 4,
|
||||
}
|
||||
// DeltaV - end of hide locked node effects
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Client._DV.Xenoarchaeology.Ui;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Xenoarchaeology.Ui;
|
||||
@@ -8,19 +7,15 @@ namespace Content.Client.Xenoarchaeology.Ui;
|
||||
/// </summary>
|
||||
public sealed class NodeScannerBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
// DeltaV - start of node scanner overhaul
|
||||
[ViewVariables]
|
||||
private DVNodeScannerDisplay? _scannerDisplay;
|
||||
// DeltaV - end of node scanner overhaul
|
||||
private NodeScannerDisplay? _scannerDisplay;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
// DeltaV - start of node scanner overhaul
|
||||
_scannerDisplay = this.CreateWindow<DVNodeScannerDisplay>();
|
||||
// DeltaV - end of node scanner overhaul
|
||||
_scannerDisplay = this.CreateWindow<NodeScannerDisplay>();
|
||||
_scannerDisplay.SetOwner(Owner);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'node-scan-display-title'}"
|
||||
MinSize="305 180"
|
||||
SetSize="305 180"
|
||||
Resizable="False"
|
||||
>
|
||||
<BoxContainer Orientation="Vertical" >
|
||||
<controls:StripeBack>
|
||||
<Label Name="NodeScannerState" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
|
||||
</controls:StripeBack>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="NoActiveNodeDataLabel" Text="{Loc 'node-scan-no-data'}" Margin="45 25 0 0" MinHeight="47" />
|
||||
<GridContainer Name="ActiveNodesList" Columns="4" Rows="2" Visible="True" MinHeight="72" />
|
||||
</BoxContainer>
|
||||
<controls:StripeBack>
|
||||
<Label Name="ArtifactStateLabel" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
|
||||
</controls:StripeBack>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -0,0 +1,150 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Xenoarchaeology.Artifact;
|
||||
using Content.Shared.Xenoarchaeology.Artifact.Components;
|
||||
using Content.Shared.Xenoarchaeology.Equipment.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Xenoarchaeology.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NodeScannerDisplay : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IGameTiming _timing= default!;
|
||||
|
||||
private readonly SharedXenoArtifactSystem _artifact;
|
||||
private TimeSpan? _nextUpdate;
|
||||
private EntityUid _owner;
|
||||
private TimeSpan _updateFromAttachedFrequency;
|
||||
private readonly HashSet<string> _triggeredNodeNames = new();
|
||||
|
||||
public NodeScannerDisplay()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_artifact = _ent.System<SharedXenoArtifactSystem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets entity that represents hand-held xeno artifact node scanner for which window is opened.
|
||||
/// Closes window if <see cref="NodeScannerComponent"/> is not present on entity.
|
||||
/// </summary>
|
||||
public void SetOwner(EntityUid scannerEntityUid)
|
||||
{
|
||||
if (!_ent.TryGetComponent<NodeScannerComponent>(scannerEntityUid, out var scannerComponent))
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
_updateFromAttachedFrequency = scannerComponent.DisplayDataUpdateInterval;
|
||||
_owner = scannerEntityUid;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if(_nextUpdate != null && _timing.CurTime < _nextUpdate)
|
||||
return;
|
||||
|
||||
_nextUpdate = _timing.CurTime + _updateFromAttachedFrequency;
|
||||
|
||||
if (!_ent.TryGetComponent(_owner, out NodeScannerConnectedComponent? connectedScanner))
|
||||
{
|
||||
Update(false, ArtifactState.None);
|
||||
return;
|
||||
}
|
||||
|
||||
var attachedArtifactEnt = connectedScanner.AttachedTo;
|
||||
if (!_ent.TryGetComponent(attachedArtifactEnt, out XenoArtifactComponent? artifactComponent))
|
||||
return;
|
||||
|
||||
_ent.TryGetComponent(attachedArtifactEnt, out XenoArtifactUnlockingComponent? unlockingComponent);
|
||||
|
||||
_triggeredNodeNames.Clear();
|
||||
ArtifactState artifactState;
|
||||
if (unlockingComponent == null)
|
||||
{
|
||||
var timeToUnlockAvailable = artifactComponent.NextUnlockTime - _timing.CurTime;
|
||||
artifactState = timeToUnlockAvailable > TimeSpan.Zero
|
||||
? ArtifactState.Cooldown
|
||||
: ArtifactState.Ready;
|
||||
}
|
||||
else
|
||||
{
|
||||
var triggeredIndexes = unlockingComponent.TriggeredNodeIndexes;
|
||||
|
||||
foreach (var triggeredIndex in triggeredIndexes)
|
||||
{
|
||||
var node = _artifact.GetNode((attachedArtifactEnt, artifactComponent), triggeredIndex);
|
||||
var triggeredNodeName = (_ent.GetComponentOrNull<NameIdentifierComponent>(node)?.Identifier ?? 0).ToString("D3");
|
||||
_triggeredNodeNames.Add(triggeredNodeName);
|
||||
}
|
||||
|
||||
artifactState = ArtifactState.Unlocking;
|
||||
}
|
||||
|
||||
Update(true, artifactState, _triggeredNodeNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates labels with scanned artifact data and list of triggered nodes from component.
|
||||
/// </summary>
|
||||
private void Update(bool isConnected, ArtifactState artifactState, HashSet<string>? triggeredNodeNames = null)
|
||||
{
|
||||
ArtifactStateLabel.Text = GetStateText(artifactState);
|
||||
NodeScannerState.Text = isConnected
|
||||
? Loc.GetString("node-scanner-artifact-connected")
|
||||
: Loc.GetString("node-scanner-artifact-non-connected");
|
||||
|
||||
ActiveNodesList.Children.Clear();
|
||||
|
||||
if (triggeredNodeNames == null)
|
||||
return;
|
||||
|
||||
if (triggeredNodeNames.Count > 0)
|
||||
{
|
||||
// show list of triggered nodes instead of 'no data' placeholder
|
||||
NoActiveNodeDataLabel.Visible = false;
|
||||
ActiveNodesList.Visible = true;
|
||||
|
||||
foreach (var nodeId in triggeredNodeNames)
|
||||
{
|
||||
var nodeLabel = new Button
|
||||
{
|
||||
Text = nodeId,
|
||||
Margin = new Thickness(15, 5, 0, 0),
|
||||
MaxHeight = 40,
|
||||
Disabled = true
|
||||
};
|
||||
ActiveNodesList.Children.Add(nodeLabel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// clear list of activated nodes (done previously), show 'no data' placeholder
|
||||
NoActiveNodeDataLabel.Visible = true;
|
||||
ActiveNodesList.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetStateText(ArtifactState state)
|
||||
{
|
||||
return state switch
|
||||
{
|
||||
ArtifactState.None => "\u2800", // placeholder for line to not be squeezed
|
||||
ArtifactState.Ready => Loc.GetString("node-scanner-artifact-state-ready"),
|
||||
ArtifactState.Unlocking => Loc.GetString("node-scanner-artifact-state-unlocking"),
|
||||
ArtifactState.Cooldown => Loc.GetString("node-scanner-artifact-state-cooldown"),
|
||||
_ => throw new ArgumentException("Invalid state"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Content.Shared._Corvax.Respawn;
|
||||
|
||||
namespace Content.Client._Corvax.Respawn;
|
||||
|
||||
public sealed class RespawnSystem : EntitySystem
|
||||
{
|
||||
public TimeSpan? RespawnResetTime { get; private set; }
|
||||
|
||||
public event Action? RespawnReseted;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeNetworkEvent<RespawnResetEvent>(OnRespawnReset);
|
||||
}
|
||||
|
||||
private void OnRespawnReset(RespawnResetEvent e)
|
||||
{
|
||||
RespawnResetTime = e.Time;
|
||||
|
||||
RespawnReseted?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ namespace Content.Client._DV.Biscuit;
|
||||
public sealed class BiscuitSystem : VisualizerSystem<BiscuitVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, BiscuitVisualsComponent component,
|
||||
ref AppearanceChangeEvent args)
|
||||
@@ -16,7 +15,7 @@ public sealed class BiscuitSystem : VisualizerSystem<BiscuitVisualsComponent>
|
||||
|
||||
_appearance.TryGetData(uid, BiscuitStatus.Cracked, out bool cracked);
|
||||
|
||||
_sprite.LayerSetVisible((uid, args.Sprite), BiscuitVisualLayers.Top, !cracked);
|
||||
args.Sprite.LayerSetVisible(BiscuitVisualLayers.Top, !cracked);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,16 +26,6 @@
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<!-- Group chat icon -->
|
||||
<TextureRect Name="GroupIcon"
|
||||
TexturePath="/Textures/_Funkystation/Interface/NanoChat/members.png"
|
||||
MinSize="16 16"
|
||||
MaxSize="16 16"
|
||||
Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center"
|
||||
Margin="4 0 6 0"
|
||||
Visible="False" />
|
||||
|
||||
<!-- Text container -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Shared.Access.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client._DV.CartridgeLoader.Cartridges;
|
||||
|
||||
@@ -39,12 +38,10 @@ public sealed partial class NanoChatEntry : BoxContainer
|
||||
_pressHandler = _ => OnPressed?.Invoke(_number);
|
||||
ChatButton.OnPressed += _pressHandler;
|
||||
|
||||
// FIXME: Truncate and Escape can lead to stray `\`, if the truncate cuts a markup tag in half
|
||||
NameLabel.Text = FormattedMessage.EscapeText(SharedNanoChatSystem.Truncate(recipient.Name, _maxNameLength));
|
||||
JobLabel.Text = FormattedMessage.EscapeText(SharedNanoChatSystem.Truncate(recipient.JobTitle ?? string.Empty, _maxIdJobLength));
|
||||
JobLabel.Visible = !string.IsNullOrWhiteSpace(JobLabel.Text);
|
||||
NameLabel.Text = SharedNanoChatSystem.Truncate(recipient.Name, _maxNameLength);
|
||||
JobLabel.Text = SharedNanoChatSystem.Truncate(recipient.JobTitle ?? "", _maxIdJobLength);
|
||||
JobLabel.Visible = !string.IsNullOrEmpty(recipient.JobTitle);
|
||||
UnreadIndicator.Visible = recipient.HasUnread;
|
||||
GroupIcon.Visible = recipient.IsGroup;
|
||||
|
||||
ChatButton.ModulateSelfOverride = isSelected ? NanoChatMessageBubble.OwnMessageColor : null;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
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._DV.CartridgeLoader.Cartridges;
|
||||
|
||||
@@ -29,67 +26,12 @@ public sealed partial class NanoChatLookupView : PanelContainer
|
||||
return;
|
||||
}
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var departmentGroups = new Dictionary<DepartmentPrototype, List<NanoChatRecipient>>();
|
||||
var noDepartmentContacts = new List<NanoChatRecipient>();
|
||||
|
||||
foreach (var contact in contacts)
|
||||
for (var idx = 0; idx < contacts.Count; idx++)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(contact.Department) &&
|
||||
prototypeManager.TryIndex<DepartmentPrototype>(contact.Department, out var dept))
|
||||
{
|
||||
departmentGroups.GetOrNew(dept).Add(contact);
|
||||
}
|
||||
else
|
||||
{
|
||||
noDepartmentContacts.Add(contact);
|
||||
}
|
||||
}
|
||||
|
||||
var rowIndex = 0;
|
||||
foreach (var (department, departmentContacts) in departmentGroups.OrderBy(entry => entry.Key, DepartmentUIComparer.Instance))
|
||||
{
|
||||
departmentContacts.Sort();
|
||||
|
||||
// Add department header
|
||||
ContactsList.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString(department.Name),
|
||||
StyleClasses = { "LabelBig" },
|
||||
Margin = new Thickness(0, rowIndex == 0 ? 0 : 8, 0, 2)
|
||||
});
|
||||
|
||||
foreach (var contact in departmentContacts)
|
||||
{
|
||||
var isEvenRow = rowIndex % 2 == 0;
|
||||
var contactControl = new ContactContainer(contact, state, isEvenRow, OnStartChat);
|
||||
ContactsList.AddChild(contactControl);
|
||||
rowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add contacts without departments at the end
|
||||
if (noDepartmentContacts.Count > 0)
|
||||
{
|
||||
noDepartmentContacts.Sort();
|
||||
|
||||
if (departmentGroups.Count > 0)
|
||||
{
|
||||
ContactsList.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("nano-chat-other-department"),
|
||||
StyleClasses = { "LabelBig" },
|
||||
Margin = new Thickness(0, 8, 0, 2)
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var contact in noDepartmentContacts)
|
||||
{
|
||||
var isEvenRow = rowIndex % 2 == 0;
|
||||
var contactControl = new ContactContainer(contact, state, isEvenRow, OnStartChat);
|
||||
ContactsList.AddChild(contactControl);
|
||||
rowIndex++;
|
||||
}
|
||||
var contact = contacts[idx];
|
||||
var isEvenRow = idx % 2 == 0;
|
||||
var contactControl = new ContactContainer(contact, state, isEvenRow, OnStartChat);
|
||||
ContactsList.AddChild(contactControl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,13 +42,9 @@ public sealed partial class NanoChatLookupView : PanelContainer
|
||||
HorizontalExpand = true;
|
||||
StyleClasses.Add(isEvenRow ? "PanelBackgroundBaseDark" : "PanelBackgroundLight");
|
||||
|
||||
var displayName = contact.Name;
|
||||
if (!string.IsNullOrWhiteSpace(contact.JobTitle))
|
||||
displayName = $"{contact.Name} ({contact.JobTitle})";
|
||||
|
||||
var nameLabel = new Label()
|
||||
{
|
||||
Text = displayName,
|
||||
Text = contact.Name,
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<cartridges:NanoChatMessageBubble
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:nanochatui="clr-namespace:Content.Client._DV.NanoChat.UI"
|
||||
xmlns:cartridges="clr-namespace:Content.Client._DV.CartridgeLoader.Cartridges"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
HorizontalExpand="True">
|
||||
@@ -8,34 +7,31 @@
|
||||
<BoxContainer Name="MessageContainer"
|
||||
Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<!-- Left spacer for other's messages -->
|
||||
<Control MinSize="12 0" Name="LeftSpacer" />
|
||||
<!-- Left spacer for other's messages -->
|
||||
<Control Name="LeftSpacer"
|
||||
MinSize="12 0" />
|
||||
|
||||
<!-- Message panel -->
|
||||
<BoxContainer Name="MessageBox"
|
||||
Orientation="Vertical"
|
||||
MaxWidth="320"
|
||||
HorizontalExpand="True">
|
||||
|
||||
<!-- Sender name -->
|
||||
<Label HorizontalAlignment="Left"
|
||||
Margin="10,0,10,2"
|
||||
Name="SenderNameLabel"
|
||||
StyleClasses="LabelSmall"
|
||||
Visible="False" />
|
||||
|
||||
<PanelContainer Name="MessagePanel"
|
||||
MaxWidth="320"
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BorderThickness="1">
|
||||
<graphics:StyleBoxFlat
|
||||
ContentMarginLeftOverride="10"
|
||||
ContentMarginRightOverride="10"
|
||||
ContentMarginTopOverride="6"
|
||||
ContentMarginBottomOverride="6"
|
||||
BorderThickness="1">
|
||||
<!-- Colors set in code based on message sender -->
|
||||
</graphics:StyleBoxFlat>
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<RichTextLabel Name="MessageText"
|
||||
HorizontalExpand="True"
|
||||
Margin="10,6,10,6" />
|
||||
HorizontalExpand="True" />
|
||||
</PanelContainer>
|
||||
|
||||
<!-- Delivery failed text -->
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Client._Funkystation.NanoChat;
|
||||
|
||||
namespace Content.Client._DV.CartridgeLoader.Cartridges;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoChatMessageBubble : BoxContainer
|
||||
{
|
||||
private static readonly Regex EmojiRegex = new(@":(\w+):", RegexOptions.Compiled);
|
||||
|
||||
public static readonly Color OwnMessageColor = Color.FromHex("#173717d9"); // Dark green
|
||||
public static readonly Color OtherMessageColor = Color.FromHex("#252525d9"); // Dark gray
|
||||
public static readonly Color BorderColor = Color.FromHex("#40404066"); // Subtle border
|
||||
@@ -25,7 +20,7 @@ public sealed partial class NanoChatMessageBubble : BoxContainer
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SetMessage(NanoChatMessage message, bool isOwnMessage, string? senderName = null, bool showSenderName = false)
|
||||
public void SetMessage(NanoChatMessage message, bool isOwnMessage)
|
||||
{
|
||||
if (MessagePanel.PanelOverride is not StyleBoxFlat)
|
||||
return;
|
||||
@@ -35,20 +30,8 @@ public sealed partial class NanoChatMessageBubble : BoxContainer
|
||||
style.BackgroundColor = isOwnMessage ? OwnMessageColor : OtherMessageColor;
|
||||
style.BorderColor = BorderColor;
|
||||
|
||||
var contentWithEmoji = EmojiRegex.Replace(FormattedMessage.EscapeText(message.Content), "[emoji=\"$1\"]");
|
||||
|
||||
MessageText.SetMessage(
|
||||
FormattedMessage.FromMarkupPermissive(contentWithEmoji),
|
||||
new[] { typeof(EmojiTag) }
|
||||
);
|
||||
|
||||
SenderNameLabel.Visible = showSenderName && !string.IsNullOrEmpty(senderName);
|
||||
if (SenderNameLabel.Visible)
|
||||
{
|
||||
SenderNameLabel.Text = senderName;
|
||||
SenderNameLabel.Modulate = TextColor;
|
||||
}
|
||||
|
||||
// Set message content
|
||||
MessageText.Text = message.Content;
|
||||
MessageText.Modulate = TextColor;
|
||||
|
||||
// Show delivery failed text if needed (only for own messages)
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client._DV.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed partial class NanoChatUi : UIFragment, IDisposable
|
||||
public sealed partial class NanoChatUi : UIFragment
|
||||
{
|
||||
private NanoChatUiFragment? _fragment;
|
||||
|
||||
@@ -30,12 +30,6 @@ public sealed partial class NanoChatUi : UIFragment, IDisposable
|
||||
_fragment?.UpdateState(cast);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Close all popups when switching apps or closing the PDA
|
||||
_fragment?.CloseAllPopups();
|
||||
}
|
||||
|
||||
private static void SendNanoChatUiMessage(NanoChatUiMessageType type,
|
||||
uint? number,
|
||||
string? content,
|
||||
|
||||
@@ -60,17 +60,6 @@
|
||||
Margin="0 0 4 0"
|
||||
StyleClasses="OpenBoth"
|
||||
ToolTip="{Loc nano-chat-new-chat}" />
|
||||
<Button Name="CreateGroupChatButton"
|
||||
MinSize="32 32"
|
||||
MaxSize="32 32"
|
||||
Margin="0 0 4 0"
|
||||
StyleClasses="OpenBoth"
|
||||
ToolTip="{Loc nano-chat-create-group}">
|
||||
<TextureRect StyleClasses="ButtonSquare"
|
||||
TexturePath="/Textures/_Funkystation/Interface/NanoChat/members.png"
|
||||
Stretch="KeepAspectCentered"
|
||||
MinSize="18 18" />
|
||||
</Button>
|
||||
<Button Name="LookupButton"
|
||||
MaxSize="32 32"
|
||||
StyleClasses="OpenBoth"
|
||||
@@ -95,20 +84,12 @@
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<Label Name="NoIdLabel"
|
||||
Text="{Loc 'nano-chat-no-id-card'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Visible="True" />
|
||||
<!-- Main content split -->
|
||||
<BoxContainer Name="ChatView"
|
||||
Orientation="Horizontal"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 5 0 0"
|
||||
Visible="False">
|
||||
Margin="0 5 0 0">
|
||||
<!-- Left panel: Chat list -->
|
||||
<PanelContainer StyleClasses="AngleRect"
|
||||
VerticalExpand="True"
|
||||
@@ -186,18 +167,6 @@
|
||||
Stretch="KeepAspectCentered"
|
||||
MinSize="18 18" />
|
||||
</Button>
|
||||
<!-- View Members should take place of edit button in group chats, to avoid layout shift when moving between normal and group -->
|
||||
<Button Name="ViewMembersButton"
|
||||
MaxSize="32 32"
|
||||
Visible="False"
|
||||
StyleClasses="OpenBoth"
|
||||
Margin="0 0 4 0"
|
||||
ToolTip="{Loc nano-chat-view-members}">
|
||||
<TextureRect StyleClasses="ButtonSquare"
|
||||
TexturePath="/Textures/_Funkystation/Interface/NanoChat/members.png"
|
||||
Stretch="KeepAspectCentered"
|
||||
MinSize="18 18" />
|
||||
</Button>
|
||||
<Button Name="DeleteChatButton"
|
||||
MaxSize="32 32"
|
||||
Visible="False"
|
||||
@@ -228,34 +197,11 @@
|
||||
MinSize="18 18" />
|
||||
</Control>
|
||||
</Button>
|
||||
<Button Name="InviteToGroupButton"
|
||||
MaxSize="32 32"
|
||||
Visible="False"
|
||||
StyleClasses="OpenBoth"
|
||||
Margin="0 0 4 0"
|
||||
ToolTip="{Loc nano-chat-invite-to-group}">
|
||||
<TextureRect StyleClasses="ButtonSquare"
|
||||
TexturePath="/Textures/_Funkystation/Interface/NanoChat/invite.png"
|
||||
Stretch="KeepAspectCentered"
|
||||
MinSize="18 18" />
|
||||
</Button>
|
||||
<!-- Input row -->
|
||||
<LineEdit Name="MessageInput"
|
||||
PlaceHolder="{Loc nano-chat-message-placeholder}"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenRight" />
|
||||
<!-- Emoji Picker -->
|
||||
<Button Name="EmojiButton"
|
||||
MinSize="32 32"
|
||||
MaxSize="32 32"
|
||||
StyleClasses="OpenBoth"
|
||||
Margin="4 0 0 0"
|
||||
ToolTip="{Loc nano-chat-emoji-picker}">
|
||||
<TextureRect StyleClasses="ButtonSquare"
|
||||
TexturePath="/Textures/Interface/Emotes/laugh.png"
|
||||
Stretch="KeepAspectCentered"
|
||||
MinSize="24 24" />
|
||||
</Button>
|
||||
<Button Name="SendButton"
|
||||
MinSize="32 32"
|
||||
Disabled="True"
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2024 Skubman <ba.fallaria@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2024 Tadeo <td12233a@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 Evaisa <evagiacosa1@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
// SPDX-FileCopyrightText: 2025 Icepick <122653407+Icepicked@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 corresp0nd <46357632+corresp0nd@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 taydeo <td12233a@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Client._Funkystation.NanoChat;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -29,14 +18,10 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private readonly int _maxNameLength;
|
||||
private readonly int _maxIdJobLength;
|
||||
private int _maxNameLength;
|
||||
private int _maxIdJobLength;
|
||||
private readonly NewChatPopup _newChatPopup;
|
||||
private readonly EditChatPopup _editChatPopup;
|
||||
private readonly CreateGroupChatPopup _createGroupChatPopup;
|
||||
private readonly InviteToGroupPopup _inviteToGroupPopup;
|
||||
private readonly GroupMembersPopup _groupMembersPopup;
|
||||
private readonly EmojiPickerPopup _emojiPickerPopup;
|
||||
private uint? _currentChat;
|
||||
private uint? _pendingChat;
|
||||
private uint _ownNumber;
|
||||
@@ -45,7 +30,6 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
private Dictionary<uint, NanoChatRecipient> _recipients = [];
|
||||
private Dictionary<uint, List<NanoChatMessage>> _messages = [];
|
||||
private HashSet<uint> _mutedChats = [];
|
||||
private List<NanoChatRecipient>? _contacts; // Used in order to show names for people not in recipients
|
||||
|
||||
public event Action<NanoChatUiMessageType, uint?, string?, string?>? OnMessageSent;
|
||||
|
||||
@@ -59,10 +43,6 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
|
||||
_newChatPopup = new(_maxNameLength, _maxIdJobLength);
|
||||
_editChatPopup = new(_maxNameLength, _maxIdJobLength);
|
||||
_createGroupChatPopup = new(_maxNameLength);
|
||||
_inviteToGroupPopup = new();
|
||||
_groupMembersPopup = new();
|
||||
_emojiPickerPopup = new();
|
||||
SetupEventHandlers();
|
||||
}
|
||||
|
||||
@@ -78,57 +58,12 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
OnMessageSent?.Invoke(NanoChatUiMessageType.EditChat, number, name, job);
|
||||
};
|
||||
|
||||
_createGroupChatPopup.OnGroupCreated += (name) =>
|
||||
{
|
||||
OnMessageSent?.Invoke(NanoChatUiMessageType.CreateGroupChat, null, name, null);
|
||||
};
|
||||
|
||||
_inviteToGroupPopup.OnInvite += (number) =>
|
||||
{
|
||||
if (_currentChat is not uint currentChat)
|
||||
return;
|
||||
|
||||
OnMessageSent?.Invoke(NanoChatUiMessageType.InviteToGroup, currentChat, number.ToString(), null);
|
||||
};
|
||||
|
||||
_groupMembersPopup.OnKick += (number) =>
|
||||
{
|
||||
if (_currentChat is not uint currentChat)
|
||||
return;
|
||||
|
||||
OnMessageSent?.Invoke(NanoChatUiMessageType.KickFromGroup, currentChat, number.ToString(), null);
|
||||
};
|
||||
|
||||
_groupMembersPopup.OnAdmin += (number) =>
|
||||
{
|
||||
if (_currentChat is not uint currentChat)
|
||||
return;
|
||||
|
||||
OnMessageSent?.Invoke(NanoChatUiMessageType.AdminUser, currentChat, number.ToString(), null);
|
||||
};
|
||||
|
||||
_groupMembersPopup.OnDeadmin += (number) =>
|
||||
{
|
||||
if (_currentChat is not uint currentChat)
|
||||
return;
|
||||
|
||||
OnMessageSent?.Invoke(NanoChatUiMessageType.DeadminUser, currentChat, number.ToString(), null);
|
||||
};
|
||||
|
||||
_emojiPickerPopup.OnEmojiSelected += InsertEmoji;
|
||||
|
||||
NewChatButton.OnPressed += _ =>
|
||||
{
|
||||
_newChatPopup.ClearInputs();
|
||||
_newChatPopup.OpenCentered();
|
||||
};
|
||||
|
||||
CreateGroupChatButton.OnPressed += _ =>
|
||||
{
|
||||
_createGroupChatPopup.ClearInputs();
|
||||
_createGroupChatPopup.OpenCentered();
|
||||
};
|
||||
|
||||
MuteChatButton.OnPressed += _ =>
|
||||
{
|
||||
if (_currentChat is not uint currentChat)
|
||||
@@ -199,39 +134,8 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
|
||||
MessageInput.OnTextEntered += _ => SendMessage();
|
||||
SendButton.OnPressed += _ => SendMessage();
|
||||
EmojiButton.OnPressed += _ => OpenEmojiPicker();
|
||||
EditChatButton.OnPressed += _ => BeginEditChat();
|
||||
DeleteChatButton.OnPressed += _ => DeleteCurrentChat();
|
||||
InviteToGroupButton.OnPressed += _ => OpenInvitePopup();
|
||||
ViewMembersButton.OnPressed += _ => OpenMembersPopup();
|
||||
}
|
||||
|
||||
private void OpenEmojiPicker()
|
||||
{
|
||||
// Close any existing emoji picker to prevent duplicates (shouldn't be happening anyway but it sure did)
|
||||
if (_emojiPickerPopup.IsOpen)
|
||||
_emojiPickerPopup.Close();
|
||||
|
||||
_emojiPickerPopup.OpenCentered();
|
||||
}
|
||||
|
||||
private void InsertEmoji(string emoji)
|
||||
{
|
||||
MessageInput.InsertAtCursor(emoji);
|
||||
MessageInput.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all open popups. Called when switching apps or closing the PDA.
|
||||
/// </summary>
|
||||
public void CloseAllPopups()
|
||||
{
|
||||
_emojiPickerPopup.Close();
|
||||
_inviteToGroupPopup.Close();
|
||||
_groupMembersPopup.Close();
|
||||
_newChatPopup.Close();
|
||||
_editChatPopup.Close();
|
||||
_createGroupChatPopup.Close();
|
||||
}
|
||||
|
||||
private void ToggleView()
|
||||
@@ -252,7 +156,7 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
if (_recipients.Count == 0)
|
||||
return;
|
||||
|
||||
var orderedRecipients = _recipients.OrderBy(r => r.Value).Select(r => r.Key).ToArray();
|
||||
var orderedRecipients = _recipients.OrderBy(r => r.Value.Name).Select(r => r.Key).ToArray();
|
||||
var currentChatIndex = (direction, _currentChat) switch
|
||||
{
|
||||
(CycleDirection.Up, null) => _recipients.Count,
|
||||
@@ -348,89 +252,6 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
OnMessageSent?.Invoke(NanoChatUiMessageType.DeleteChat, activeChat, null, null);
|
||||
}
|
||||
|
||||
private void OpenInvitePopup()
|
||||
{
|
||||
if (_currentChat is not uint currentChat ||
|
||||
!_recipients.TryGetValue(currentChat, out var recipient) ||
|
||||
!recipient.IsGroup)
|
||||
return;
|
||||
|
||||
var availableContacts = _contacts ?? new List<NanoChatRecipient>();
|
||||
var members = recipient.Members ?? new HashSet<uint>();
|
||||
_inviteToGroupPopup.SetContacts(availableContacts, members, _ownNumber);
|
||||
_inviteToGroupPopup.ClearSearch();
|
||||
_inviteToGroupPopup.OpenCentered();
|
||||
}
|
||||
|
||||
private void OpenMembersPopup()
|
||||
{
|
||||
if (_currentChat is not uint currentChat ||
|
||||
!_recipients.TryGetValue(currentChat, out var recipient) ||
|
||||
!recipient.IsGroup)
|
||||
return;
|
||||
|
||||
var availableContacts = _contacts ?? new List<NanoChatRecipient>();
|
||||
var members = recipient.Members ?? new HashSet<uint>();
|
||||
var admins = recipient.Admins ?? new HashSet<uint>();
|
||||
_groupMembersPopup.SetMembers(availableContacts, members, _ownNumber, recipient.CreatorId, admins);
|
||||
_groupMembersPopup.OpenCentered();
|
||||
}
|
||||
|
||||
private void RefreshOpenPopups()
|
||||
{
|
||||
// If popup is open, refresh it or close if no longer a member
|
||||
if (_inviteToGroupPopup.IsOpen)
|
||||
{
|
||||
if (_currentChat is uint currentChat &&
|
||||
_recipients.TryGetValue(currentChat, out var recipient) &&
|
||||
recipient.IsGroup)
|
||||
{
|
||||
var members = recipient.Members ?? new HashSet<uint>();
|
||||
if (!members.Contains(_ownNumber))
|
||||
{
|
||||
_inviteToGroupPopup.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the contacts list from the server (same as Look Up Numbers)
|
||||
// Don't fall back to recipients as they contain outdated cached information
|
||||
var availableContacts = _contacts ?? new List<NanoChatRecipient>();
|
||||
_inviteToGroupPopup.SetContacts(availableContacts, members, _ownNumber);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_inviteToGroupPopup.Close();
|
||||
}
|
||||
}
|
||||
|
||||
if (_groupMembersPopup.IsOpen)
|
||||
{
|
||||
if (_currentChat is uint currentChat &&
|
||||
_recipients.TryGetValue(currentChat, out var recipient) &&
|
||||
recipient.IsGroup)
|
||||
{
|
||||
var members = recipient.Members ?? new HashSet<uint>();
|
||||
if (!members.Contains(_ownNumber))
|
||||
{
|
||||
_groupMembersPopup.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the contacts list from the server (same as Look Up Numbers)
|
||||
// Don't fall back to recipients as they contain outdated cached information
|
||||
var availableContacts = _contacts ?? new List<NanoChatRecipient>();
|
||||
var admins = recipient.Admins ?? new HashSet<uint>();
|
||||
_groupMembersPopup.SetMembers(availableContacts, members, _ownNumber, recipient.CreatorId, admins);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_groupMembersPopup.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginEditChat()
|
||||
{
|
||||
if (_currentChat is not uint currentChat)
|
||||
@@ -473,40 +294,17 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
var activeChat = _pendingChat ?? _currentChat;
|
||||
var hasActiveChat = activeChat != null;
|
||||
|
||||
var isGroupChat = false;
|
||||
var canManageGroup = false;
|
||||
NanoChatRecipient recipient = default;
|
||||
var hasRecipient = false;
|
||||
|
||||
if (hasActiveChat && activeChat != null && _recipients.TryGetValue(activeChat.Value, out recipient))
|
||||
{
|
||||
hasRecipient = true;
|
||||
isGroupChat = recipient.IsGroup;
|
||||
canManageGroup = isGroupChat && (recipient.CreatorId == _ownNumber || recipient.Admins?.Contains(_ownNumber) == true);
|
||||
}
|
||||
|
||||
// Update UI state
|
||||
MessagesScroll.Visible = hasActiveChat;
|
||||
CurrentChatName.Visible = !hasActiveChat;
|
||||
MessageInputContainer.Visible = hasActiveChat;
|
||||
DeleteChatButton.Visible = hasActiveChat;
|
||||
EditChatButton.Visible = hasActiveChat && !isGroupChat;
|
||||
EditChatButton.Visible = hasActiveChat;
|
||||
DeleteChatButton.Disabled = !hasActiveChat;
|
||||
|
||||
|
||||
InviteToGroupButton.Visible = canManageGroup;
|
||||
ViewMembersButton.Visible = isGroupChat;
|
||||
|
||||
if (hasRecipient)
|
||||
if (activeChat != null && _recipients.TryGetValue(activeChat.Value, out var recipient))
|
||||
{
|
||||
if (isGroupChat)
|
||||
{
|
||||
CurrentChatName.Text = recipient.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentChatName.Text = recipient.Name + (string.IsNullOrEmpty(recipient.JobTitle) ? "" : $" ({recipient.JobTitle})");
|
||||
}
|
||||
CurrentChatName.Text = recipient.Name + (string.IsNullOrEmpty(recipient.JobTitle) ? "" : $" ({recipient.JobTitle})");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -523,44 +321,14 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
if (activeChat == null || !messages.TryGetValue(activeChat.Value, out var chatMessages))
|
||||
return;
|
||||
|
||||
var isGroupChat = _recipients.TryGetValue(activeChat.Value, out var recipient) && recipient.IsGroup;
|
||||
foreach (var message in chatMessages)
|
||||
{
|
||||
var messageBubble = new NanoChatMessageBubble();
|
||||
var isOwnMessage = message.SenderId == _ownNumber;
|
||||
|
||||
// For group chats, show sender name for other people's messages
|
||||
string? senderName = null;
|
||||
if (isGroupChat && !isOwnMessage)
|
||||
{
|
||||
// Try to get sender name from contacts first, then recipients
|
||||
if (_recipients.TryGetValue(message.SenderId, out var senderRecipient))
|
||||
{
|
||||
senderName = senderRecipient.Name;
|
||||
}
|
||||
|
||||
// If not in recipients, try contacts list (station-wide directory)
|
||||
if (senderName == null && _contacts != null)
|
||||
{
|
||||
var contact = _contacts.FirstOrDefault(c => c.Number == message.SenderId);
|
||||
if (contact.Number != 0)
|
||||
senderName = contact.Name;
|
||||
}
|
||||
|
||||
// Fallback to "Unknown" with number if name not found
|
||||
if (senderName == null)
|
||||
{
|
||||
senderName = $"{Loc.GetString("nano-chat-unknown-sender")}#{message.SenderId:D4}";
|
||||
}
|
||||
}
|
||||
|
||||
messageBubble.SetMessage(message, isOwnMessage, senderName, isGroupChat && !isOwnMessage);
|
||||
messageBubble.SetMessage(message, message.SenderId == _ownNumber);
|
||||
MessageList.AddChild(messageBubble);
|
||||
|
||||
// Add spacing between messages
|
||||
if (MessageList.ChildCount > 0)
|
||||
MessageList.AddChild(new Control { Name = "MessagePadding", MinSize = new Vector2(0, 4) });
|
||||
|
||||
MessageList.AddChild(messageBubble);
|
||||
MessageList.AddChild(new Control { MinSize = new Vector2(0, 4) });
|
||||
}
|
||||
|
||||
MessageList.InvalidateMeasure();
|
||||
@@ -589,41 +357,13 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
ListNumberButton.Pressed = _listNumber;
|
||||
}
|
||||
|
||||
private void CheckValidNumber()
|
||||
{
|
||||
if (_ownNumber == 0)
|
||||
{
|
||||
NoIdLabel.Visible = true;
|
||||
ChatView.Visible = false;
|
||||
LookupView.Visible = false;
|
||||
|
||||
MuteButton.Disabled = true;
|
||||
NewChatButton.Disabled = true;
|
||||
CreateGroupChatButton.Disabled = true;
|
||||
LookupButton.Disabled = true;
|
||||
ListNumberButton.Disabled = true;
|
||||
}
|
||||
else if (NoIdLabel.Visible)
|
||||
{
|
||||
NoIdLabel.Visible = false;
|
||||
ToggleView();
|
||||
|
||||
MuteButton.Disabled = false;
|
||||
NewChatButton.Disabled = false;
|
||||
CreateGroupChatButton.Disabled = false;
|
||||
LookupButton.Disabled = false;
|
||||
ListNumberButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(NanoChatUiState state)
|
||||
{
|
||||
_ownNumber = state.OwnNumber;
|
||||
_notificationsMuted = state.NotificationsMuted;
|
||||
_listNumber = state.ListNumber;
|
||||
_mutedChats = state.MutedChats;
|
||||
_contacts = state.Contacts;
|
||||
OwnNumberLabel.Text = $"#{_ownNumber:D4}";
|
||||
OwnNumberLabel.Text = $"#{state.OwnNumber:D4}";
|
||||
UpdateMuteButton();
|
||||
UpdateListNumber();
|
||||
|
||||
@@ -647,15 +387,10 @@ public sealed partial class NanoChatUiFragment : BoxContainer
|
||||
if (_pendingChat == null)
|
||||
_currentChat = state.CurrentChat;
|
||||
|
||||
// Update recipients first, then update UI that depends on it
|
||||
UpdateChatList(state.Recipients);
|
||||
UpdateMessages(state.Messages);
|
||||
UpdateCurrentChat();
|
||||
UpdateMuteChatButton();
|
||||
UpdateChatList(state.Recipients);
|
||||
UpdateMessages(state.Messages);
|
||||
LookupView.UpdateContactList(state);
|
||||
|
||||
CheckValidNumber();
|
||||
|
||||
RefreshOpenPopups();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client._DV.NanoChat.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Extremely specialized version of <see cref="Robust.Client.UserInterface.Controls.TextureRect" /> designed for drawing Textures at fixed size with an additional offset
|
||||
/// Circumvents the problem of not having "negative mergins" on TextureRect for manual alignment
|
||||
/// This is fucking evil, please do not use this :3
|
||||
/// </summary>
|
||||
public sealed class EmojiControl : Control
|
||||
{
|
||||
public const string StylePropertyTexture = "texture";
|
||||
public const string StylePropertyOffset = "offset";
|
||||
|
||||
private Texture? _texture;
|
||||
private Vector2? _offset;
|
||||
|
||||
private string? _texturePath;
|
||||
|
||||
public string TexturePath
|
||||
{
|
||||
set
|
||||
{
|
||||
Texture = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(value);
|
||||
_texturePath = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void OnThemeUpdated()
|
||||
{
|
||||
if (_texturePath != null) Texture = Theme.ResolveTexture(_texturePath);
|
||||
base.OnThemeUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The texture to draw.
|
||||
/// </summary>
|
||||
public Texture? Texture
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_texture is null)
|
||||
{
|
||||
if (TryGetStyleProperty(StylePropertyTexture, out Texture? texture))
|
||||
{
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
|
||||
return _texture;
|
||||
}
|
||||
set
|
||||
{
|
||||
var oldSize = _texture?.Size;
|
||||
_texture = value;
|
||||
|
||||
if (value?.Size != oldSize)
|
||||
{
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vertical offset of the texture
|
||||
/// </summary>
|
||||
public Vector2 Offset
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_offset is null)
|
||||
{
|
||||
if (TryGetStyleProperty(StylePropertyOffset, out Vector2 offset))
|
||||
return offset;
|
||||
}
|
||||
|
||||
return _offset ?? Vector2.Zero;
|
||||
}
|
||||
set
|
||||
{
|
||||
var oldOffset = _offset;
|
||||
_offset = value;
|
||||
|
||||
if (oldOffset != value)
|
||||
{
|
||||
InvalidateArrange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ArrangeCore(UIBox2 finalRect)
|
||||
{
|
||||
finalRect.Left += Offset.X;
|
||||
finalRect.Right += Offset.X;
|
||||
finalRect.Top += Offset.Y;
|
||||
finalRect.Bottom += Offset.Y;
|
||||
base.ArrangeCore(finalRect);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
var texture = Texture;
|
||||
|
||||
if (texture is null)
|
||||
return;
|
||||
|
||||
handle.DrawTextureRect(texture, GetDrawDimensions());
|
||||
}
|
||||
|
||||
private UIBox2 GetDrawDimensions()
|
||||
{
|
||||
var (texWidth, texHeight) = Texture?.Size ?? Vector2i.Zero;
|
||||
var width = texWidth * (PixelSize.Y / texHeight);
|
||||
var height = (float)PixelSize.Y;
|
||||
if (width > PixelSize.X)
|
||||
{
|
||||
width = PixelSize.X;
|
||||
height = texHeight * (PixelSize.X / texWidth);
|
||||
}
|
||||
|
||||
var size = new Vector2(width, height);
|
||||
var position = (PixelSize - size) / 2 + Offset;
|
||||
|
||||
return UIBox2.FromDimensions(position, size);
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stealth;
|
||||
using Content.Shared._DV.Overlays.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._Goobstation.Overlays;
|
||||
|
||||
public sealed class SharkVisionOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private readonly TransformSystem _transform;
|
||||
private readonly StealthSystem _stealth;
|
||||
private readonly ContainerSystem _container;
|
||||
private readonly SharedSolutionContainerSystem _solution;
|
||||
private readonly SpriteSystem _sprite;
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly List<SharkVisionRenderEntry> _entries = [];
|
||||
|
||||
private EntityUid? _lightEntity;
|
||||
|
||||
public float LightRadius;
|
||||
|
||||
public SharkVisionComponent? Comp;
|
||||
|
||||
public SharkVisionOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_container = _entity.System<ContainerSystem>();
|
||||
_transform = _entity.System<TransformSystem>();
|
||||
_stealth = _entity.System<StealthSystem>();
|
||||
_solution = _entity.System<SharedSolutionContainerSystem>();
|
||||
_sprite = _entity.System<SpriteSystem>();
|
||||
|
||||
ZIndex = -1;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture is null || Comp is null)
|
||||
return;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var eye = args.Viewport.Eye;
|
||||
|
||||
if (eye == null)
|
||||
return;
|
||||
|
||||
var player = _player.LocalEntity;
|
||||
|
||||
if (!_entity.TryGetComponent(player, out TransformComponent? playerXform))
|
||||
return;
|
||||
|
||||
var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime);
|
||||
var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime);
|
||||
|
||||
var mapId = eye.Position.MapId;
|
||||
var eyeRot = eye.Rotation;
|
||||
|
||||
GetVisionEntities(Comp.BloodPrototypes, mapId, eyeRot);
|
||||
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot, Comp.Color, alpha);
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void GetVisionEntities(ProtoId<ReagentPrototype>[] bloodPrototypes, MapId mapId, Angle eyeRot)
|
||||
{
|
||||
_entries.Clear();
|
||||
var entities = _entity.EntityQueryEnumerator<SolutionContainerManagerComponent, SpriteComponent, TransformComponent>();
|
||||
while (entities.MoveNext(out var uid, out var body, out var sprite, out var xform))
|
||||
{
|
||||
if (!CanSee(uid, sprite))
|
||||
continue;
|
||||
|
||||
// Luckily, players are also SolutionContainerManagerComponent, so let's check for a bloodstream and check for bleeds
|
||||
if (_entity.TryGetComponent<BloodstreamComponent>(uid, out var bloodstream) && bloodstream.BleedAmount <= 0)
|
||||
continue;
|
||||
|
||||
// Should always be true but I'm just simplifying a future if statement
|
||||
if (!_entity.TryGetComponent<SolutionContainerManagerComponent>(uid, out var solutionContainer))
|
||||
continue;
|
||||
|
||||
if (solutionContainer.Containers == null)
|
||||
continue;
|
||||
|
||||
var bloodFound = false;
|
||||
// Someone calculate the Big O notation of this lmao
|
||||
foreach (var individualContainer in solutionContainer.Containers)
|
||||
{
|
||||
if (_solution.TryGetSolution((uid, solutionContainer), individualContainer, out var _, out var solution))
|
||||
{
|
||||
var reagentsInContainer = solution.GetReagentPrototypes(_proto).ToDictionary();
|
||||
bloodFound = reagentsInContainer.Keys.Any(reagentKey => bloodPrototypes.Any(x => reagentKey == x));
|
||||
}
|
||||
|
||||
if (bloodFound)
|
||||
break; // We don't need to keep searching if the entity has blood in at least one container
|
||||
}
|
||||
|
||||
if (!bloodFound)
|
||||
continue;
|
||||
|
||||
var entity = uid;
|
||||
// Parent container check
|
||||
if (_container.TryGetOuterContainer(uid, xform, out var container))
|
||||
{
|
||||
var owner = container.Owner;
|
||||
|
||||
if (_entity.TryGetComponent<SpriteComponent>(owner, out var ownerSprite)
|
||||
&& _entity.TryGetComponent<TransformComponent>(owner, out var ownerXform))
|
||||
{
|
||||
entity = owner;
|
||||
sprite = ownerSprite;
|
||||
xform = ownerXform;
|
||||
}
|
||||
}
|
||||
|
||||
if (_entries.Any(e => e.Ent.Owner == entity))
|
||||
continue;
|
||||
|
||||
_entries.Add(new SharkVisionRenderEntry((entity, sprite, xform), mapId, eyeRot));
|
||||
}
|
||||
}
|
||||
|
||||
private void Render(Entity<SpriteComponent, TransformComponent> ent,
|
||||
MapId? map,
|
||||
DrawingHandleWorld handle,
|
||||
Angle eyeRot,
|
||||
Color color,
|
||||
float alpha)
|
||||
{
|
||||
var (uid, sprite, xform) = ent;
|
||||
if (xform.MapID != map || !CanSee(uid, sprite))
|
||||
return;
|
||||
|
||||
var position = _transform.GetWorldPosition(xform);
|
||||
var rotation = _transform.GetWorldRotation(xform);
|
||||
|
||||
var originalColor = sprite.Color;
|
||||
_sprite.SetColor((ent, ent.Comp1), color.WithAlpha(alpha));
|
||||
_sprite.RenderSprite((ent, ent.Comp1), handle, eyeRot, rotation, position);
|
||||
_sprite.SetColor((ent, ent.Comp1), originalColor);
|
||||
}
|
||||
|
||||
private bool CanSee(EntityUid uid, SpriteComponent sprite)
|
||||
{
|
||||
return sprite.Visible && (!_entity.TryGetComponent(uid, out StealthComponent? stealth) ||
|
||||
_stealth.GetVisibility(uid, stealth) > 0.5f);
|
||||
}
|
||||
|
||||
public void ResetLight(bool checkFirstTimePredicted = true)
|
||||
{
|
||||
if (_lightEntity == null || checkFirstTimePredicted && !_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
_entity.DeleteEntity(_lightEntity);
|
||||
_lightEntity = null;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct SharkVisionRenderEntry(
|
||||
Entity<SpriteComponent, TransformComponent> Ent,
|
||||
MapId? Map,
|
||||
Angle EyeRot);
|
||||
@@ -1,113 +0,0 @@
|
||||
using Content.Client.Overlays;
|
||||
using Content.Shared._DV.Overlays.Components;
|
||||
using Content.Shared._Goobstation.Overlays;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client._Goobstation.Overlays;
|
||||
|
||||
public sealed class SharkVisionSystem : EquipmentHudSystem<SharkVisionComponent>
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private SharkVisionOverlay _sharkOverlay = default!;
|
||||
private BaseSwitchableOverlay<SharkVisionComponent> _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharkVisionComponent, SwitchableOverlayToggledEvent>(OnToggle);
|
||||
|
||||
_sharkOverlay = new SharkVisionOverlay();
|
||||
_overlay = new BaseSwitchableOverlay<SharkVisionComponent>();
|
||||
}
|
||||
|
||||
protected override void OnRefreshComponentHud(Entity<SharkVisionComponent> ent,
|
||||
ref RefreshEquipmentHudEvent<SharkVisionComponent> args)
|
||||
{
|
||||
if (!ent.Comp.IsEquipment)
|
||||
base.OnRefreshComponentHud(ent, ref args);
|
||||
}
|
||||
|
||||
protected override void OnRefreshEquipmentHud(Entity<SharkVisionComponent> ent,
|
||||
ref InventoryRelayedEvent<RefreshEquipmentHudEvent<SharkVisionComponent>> args)
|
||||
{
|
||||
if (ent.Comp.IsEquipment)
|
||||
base.OnRefreshEquipmentHud(ent, ref args);
|
||||
}
|
||||
|
||||
private void OnToggle(Entity<SharkVisionComponent> ent, ref SwitchableOverlayToggledEvent args)
|
||||
{
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<SharkVisionComponent> args)
|
||||
{
|
||||
base.UpdateInternal(args);
|
||||
SharkVisionComponent? tvComp = null;
|
||||
var lightRadius = 0f;
|
||||
foreach (var comp in args.Components)
|
||||
{
|
||||
if (!comp.IsActive && (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime))
|
||||
continue;
|
||||
|
||||
if (tvComp == null)
|
||||
tvComp = comp;
|
||||
else if (!tvComp.DrawOverlay && comp.DrawOverlay)
|
||||
tvComp = comp;
|
||||
else if (tvComp.DrawOverlay == comp.DrawOverlay && tvComp.PulseTime > 0f && comp.PulseTime <= 0f)
|
||||
tvComp = comp;
|
||||
|
||||
lightRadius = MathF.Max(lightRadius, 1);
|
||||
}
|
||||
|
||||
UpdateSharkOverlay(tvComp, lightRadius);
|
||||
UpdateOverlay(tvComp);
|
||||
}
|
||||
|
||||
protected override void DeactivateInternal()
|
||||
{
|
||||
base.DeactivateInternal();
|
||||
|
||||
_sharkOverlay.ResetLight(false);
|
||||
UpdateOverlay(null);
|
||||
UpdateSharkOverlay(null, 0f);
|
||||
}
|
||||
|
||||
private void UpdateSharkOverlay(SharkVisionComponent? comp, float lightRadius)
|
||||
{
|
||||
_sharkOverlay.LightRadius = lightRadius;
|
||||
_sharkOverlay.Comp = comp;
|
||||
|
||||
switch (comp)
|
||||
{
|
||||
case not null when !_overlayMan.HasOverlay<SharkVisionOverlay>():
|
||||
_overlayMan.AddOverlay(_sharkOverlay);
|
||||
break;
|
||||
case null:
|
||||
_overlayMan.RemoveOverlay(_sharkOverlay);
|
||||
_sharkOverlay.ResetLight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOverlay(SharkVisionComponent? tvComp)
|
||||
{
|
||||
_overlay.Comp = tvComp;
|
||||
|
||||
switch (tvComp)
|
||||
{
|
||||
case { DrawOverlay: true } when !_overlayMan.HasOverlay<BaseSwitchableOverlay<SharkVisionComponent>>():
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
break;
|
||||
case null or { DrawOverlay: false }:
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
break;
|
||||
}
|
||||
|
||||
// Night vision overlay is prioritized
|
||||
_overlay.IsActive = !_overlayMan.HasOverlay<BaseSwitchableOverlay<NightVisionComponent>>();
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,6 @@ using Content.Client.CharacterInfo;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared._DV.CCVars;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Chat.TypingIndicator;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using static Content.Client.CharacterInfo.CharacterInfoSystem;
|
||||
@@ -15,12 +12,6 @@ namespace Content.Client.UserInterface.Systems.Chat;
|
||||
|
||||
public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSystem>
|
||||
{
|
||||
public ChatSelectChannel CurrentChannel = ChatSelectChannel.None;
|
||||
private static readonly ProtoId<TypingIndicatorPrototype> WhisperID = "whisper";
|
||||
private static readonly ProtoId<TypingIndicatorPrototype> EmoteID = "emote";
|
||||
private static readonly ProtoId<TypingIndicatorPrototype> OocID = "ooc";
|
||||
private static readonly ProtoId<TypingIndicatorPrototype> RadioID = "radio";
|
||||
|
||||
/// <summary>
|
||||
/// Gets Invoked whenever the autofilled highlights have changed.
|
||||
/// Used to populate the preview in the channel selector window.
|
||||
@@ -107,38 +98,4 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
||||
if (shouldSave)
|
||||
_config.SaveToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies and sets what type of typing indicator should be put.
|
||||
/// </summary>
|
||||
public void NotifySpecificChatTextChange(ChatSelectChannel selectedChannel)
|
||||
{
|
||||
var channel = CurrentChannel;
|
||||
if (CurrentChannel == ChatSelectChannel.None)
|
||||
channel = selectedChannel;
|
||||
|
||||
switch (channel)
|
||||
{
|
||||
case ChatSelectChannel.Whisper:
|
||||
_typingIndicator?.ClientAlternateTyping(WhisperID);
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Radio:
|
||||
_typingIndicator?.ClientAlternateTyping(RadioID);
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Emotes:
|
||||
_typingIndicator?.ClientAlternateTyping(EmoteID);
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.LOOC:
|
||||
case ChatSelectChannel.OOC:
|
||||
_typingIndicator?.ClientAlternateTyping(OocID);
|
||||
break;
|
||||
|
||||
default:
|
||||
_typingIndicator?.ClientChangedChatText();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'node-scan-display-title'}"
|
||||
MinSize="350 400"
|
||||
SetSize="350 400"
|
||||
Resizable="False"
|
||||
>
|
||||
<BoxContainer Orientation="Vertical" >
|
||||
<controls:StripeBack>
|
||||
<Label Name="NodeScannerState" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
|
||||
</controls:StripeBack>
|
||||
<BoxContainer Name="ProgressBarContainer" Orientation="Horizontal" Margin="0 5 0 0" HorizontalExpand="True" Visible="False" >
|
||||
<Label Name="ProgressBarLabel" Text="{Loc 'node-scan-timer'}" Margin="20 0 0 0" />
|
||||
<ProgressBar
|
||||
Name="ProgressBar"
|
||||
MinValue="0"
|
||||
MaxValue="1"
|
||||
SetHeight="20"
|
||||
Margin="10 0 20 0"
|
||||
HorizontalExpand="True" >
|
||||
</ProgressBar>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" >
|
||||
<Label Name="NoActiveNodeDataLabel" Text="{Loc 'node-scan-no-data'}" Margin="15 15 0 0" MinHeight="47" HorizontalExpand="True" HorizontalAlignment="Center" />
|
||||
<BoxContainer Name="ActiveNodesList" Orientation="Vertical" HorizontalExpand="True" Visible="False" MinHeight="72" />
|
||||
</BoxContainer>
|
||||
<PanelContainer Name="SystemWarningPanel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalExpand="True"
|
||||
Margin="10 0 10 5"
|
||||
Visible="False" >
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" >
|
||||
<RichTextLabel Name="SystemWarningLabel" Margin="5 8 5 0" HorizontalAlignment="Center" />
|
||||
<RichTextLabel Name="SystemWarningSublabel" Margin="5 3 5 8" HorizontalAlignment="Center" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -1,250 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Xenoarchaeology.Artifact;
|
||||
using Content.Shared.Xenoarchaeology.Artifact.Components;
|
||||
using Content.Shared.Xenoarchaeology.Equipment.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client._DV.Xenoarchaeology.Ui;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV replacement for NodeScannerDisplay, the UI of the artifact node scanner.
|
||||
/// Displays extra information about the artifact unlocking phase.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DVNodeScannerDisplay : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IGameTiming _timing= default!;
|
||||
|
||||
private readonly SharedXenoArtifactSystem _artifact;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private Texture? _blipTexture;
|
||||
private EntityUid _owner;
|
||||
|
||||
private float animatedTimeRemaining = 0;
|
||||
private float animatedTimeRemainingVelocity = 0;
|
||||
|
||||
public DVNodeScannerDisplay()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_artifact = _ent.System<SharedXenoArtifactSystem>();
|
||||
_spriteSystem = _ent.System<SpriteSystem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets entity that represents hand-held xeno artifact node scanner for which window is opened.
|
||||
/// Closes window if <see cref="NodeScannerComponent"/> is not present on entity.
|
||||
/// </summary>
|
||||
public void SetOwner(EntityUid scannerEntityUid)
|
||||
{
|
||||
if (!_ent.TryGetComponent<NodeScannerComponent>(scannerEntityUid, out var scannerComponent))
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
_owner = scannerEntityUid;
|
||||
|
||||
_blipTexture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
|
||||
// reset
|
||||
NoActiveNodeDataLabel.Visible = true;
|
||||
NodeScannerState.Text = Loc.GetString("node-scanner-artifact-non-connected");
|
||||
ProgressBarContainer.Visible = false;
|
||||
ActiveNodesList.Visible = false;
|
||||
SystemWarningPanel.Visible = false;
|
||||
|
||||
|
||||
if (!_ent.TryGetComponent(_owner, out NodeScannerConnectedComponent? connectedScanner))
|
||||
return;
|
||||
|
||||
var attachedArtifactEnt = connectedScanner.AttachedTo;
|
||||
if (!_ent.TryGetComponent(attachedArtifactEnt, out XenoArtifactComponent? artifactComponent))
|
||||
return;
|
||||
|
||||
_ent.TryGetComponent(attachedArtifactEnt, out XenoArtifactUnlockingComponent? unlockingComponent);
|
||||
|
||||
|
||||
// header banner
|
||||
NodeScannerState.Text =
|
||||
unlockingComponent != null
|
||||
? Loc.GetString("node-scanner-artifact-state-unlocking")
|
||||
: _timing.CurTime < artifactComponent.NextUnlockTime
|
||||
? Loc.GetString("node-scanner-artifact-state-cooldown")
|
||||
: Loc.GetString("node-scanner-artifact-state-ready");
|
||||
|
||||
|
||||
// time remaining progress bar
|
||||
var unlockTimeRemaining =
|
||||
unlockingComponent != null
|
||||
? MathF.Max(0f, (float)(unlockingComponent.EndTime - _timing.CurTime).TotalSeconds)
|
||||
: 0f;
|
||||
|
||||
// damped-oscillator-style system for animating sudden jumps in time remaining
|
||||
animatedTimeRemainingVelocity += 0.001f * (unlockTimeRemaining - animatedTimeRemaining) - 0.1f * animatedTimeRemainingVelocity;
|
||||
animatedTimeRemaining = MathF.Max(0f, animatedTimeRemaining + animatedTimeRemainingVelocity);
|
||||
|
||||
// sigmoid-style function to make sure progress bar never goes over 100%
|
||||
ProgressBar.Value = 2f / (1f + MathF.Exp(-0.08f * animatedTimeRemaining)) - 1f;
|
||||
|
||||
|
||||
// node data and warning panel
|
||||
(List<int> triggeredIndexesOrdered, HashSet<int> triggeredIndexesRelated)? unlockingDataToShow = null;
|
||||
if (unlockingComponent != null)
|
||||
{
|
||||
unlockingDataToShow = (
|
||||
unlockingComponent.TriggeredNodeIndexesOrdered,
|
||||
unlockingComponent.TriggeredNodeIndexesRelated
|
||||
);
|
||||
}
|
||||
else if (
|
||||
artifactComponent.LastUnlockingEndTime != null
|
||||
&& _timing.CurTime - artifactComponent.LastUnlockingEndTime < TimeSpan.FromSeconds(60)
|
||||
)
|
||||
{
|
||||
unlockingDataToShow = (
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesOrdered,
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesRelated
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
unlockingDataToShow is (List<int> triggeredIndexesOrdered, HashSet<int> triggeredIndexesRelated)
|
||||
&& triggeredIndexesOrdered.Count > 0
|
||||
)
|
||||
{
|
||||
// show triggered node data instead of 'no data' placeholder
|
||||
NoActiveNodeDataLabel.Visible = false;
|
||||
ProgressBarContainer.Visible = true;
|
||||
ActiveNodesList.Visible = true;
|
||||
|
||||
|
||||
// node list
|
||||
ActiveNodesList.Children.Clear();
|
||||
foreach (var triggeredIndex in triggeredIndexesOrdered)
|
||||
{
|
||||
var node = _artifact.GetNode((attachedArtifactEnt, artifactComponent), triggeredIndex);
|
||||
|
||||
var nodeControl = new Button
|
||||
{
|
||||
Margin = new Thickness(15, 5, 15, 0),
|
||||
MaxHeight = 40,
|
||||
Disabled = true,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
ActiveNodesList.AddChild(nodeControl);
|
||||
|
||||
var mainContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
nodeControl.AddChild(mainContainer);
|
||||
|
||||
// mimics suitCoordsIndicator in CrewMonitoringWindow
|
||||
var relatednessIndicator = new TextureRect()
|
||||
{
|
||||
Texture = _blipTexture,
|
||||
TextureScale = new Vector2(0.25f, 0.25f),
|
||||
Modulate = triggeredIndexesRelated.Contains(triggeredIndex) ? Color.LimeGreen : Color.DarkRed,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
};
|
||||
mainContainer.AddChild(relatednessIndicator);
|
||||
|
||||
var triggerTip = Loc.GetString(node.Comp.TriggerTip!);
|
||||
var triggeredNodeName = (_ent.GetComponentOrNull<NameIdentifierComponent>(node)?.Identifier ?? 0).ToString("D3");
|
||||
var descriptionLabel = new Label()
|
||||
{
|
||||
Text = $"{triggerTip} ({triggeredNodeName})",
|
||||
HorizontalExpand = true,
|
||||
ClipText = true,
|
||||
Margin = new Thickness(8, 0, 0, 0),
|
||||
};
|
||||
mainContainer.AddChild(descriptionLabel);
|
||||
}
|
||||
|
||||
|
||||
// warning panel (mimics that of PowerMonitoringWindow)
|
||||
(Color background, Color border, string message, string subtext)? warningDataToShow = null;
|
||||
if (unlockingComponent == null)
|
||||
{
|
||||
if (artifactComponent.LastUnlockingSuccessful)
|
||||
{
|
||||
warningDataToShow = (
|
||||
background: Color.Green,
|
||||
border: Color.DarkGreen,
|
||||
message: Loc.GetString("node-scan-warning-unlock-complete"),
|
||||
subtext: Loc.GetString("node-scan-warning-unlock-complete-subtext")
|
||||
);
|
||||
}
|
||||
else if (triggeredIndexesOrdered.Count == triggeredIndexesRelated.Count)
|
||||
{
|
||||
warningDataToShow = (
|
||||
background: Color.Orange,
|
||||
border: Color.DarkOrange,
|
||||
message: Loc.GetString("node-scan-warning-partial-complete"),
|
||||
subtext: Loc.GetString("node-scan-warning-partial-complete-subtext")
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
warningDataToShow = (
|
||||
background: Color.Red,
|
||||
border: Color.DarkRed,
|
||||
message: Loc.GetString("node-scan-warning-failure-complete"),
|
||||
subtext: Loc.GetString("node-scan-warning-failure-complete-subtext")
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (triggeredIndexesOrdered.Count != triggeredIndexesRelated.Count)
|
||||
{
|
||||
warningDataToShow = (
|
||||
background: Color.Red,
|
||||
border: Color.DarkRed,
|
||||
message: Loc.GetString("node-scan-warning-failure-imminent"),
|
||||
subtext: Loc.GetString("node-scan-warning-failure-imminent-subtext")
|
||||
);
|
||||
}
|
||||
|
||||
if (warningDataToShow is (Color background, Color border, string message, string subtext))
|
||||
{
|
||||
SystemWarningPanel.Visible = true;
|
||||
|
||||
SystemWarningPanel.PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = background,
|
||||
BorderColor = border,
|
||||
BorderThickness = new Thickness(2),
|
||||
};
|
||||
|
||||
// warning panel pulses during unlocking phase
|
||||
var lit = unlockingComponent != null && _timing.RealTime.TotalSeconds % 1f > 0.5f;
|
||||
SystemWarningPanel.Modulate = lit ? Color.White : new Color(178, 178, 178);
|
||||
|
||||
SystemWarningLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(message));
|
||||
SystemWarningSublabel.SetMessage(FormattedMessage.FromMarkupOrThrow(subtext));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client._EE.Flight.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class FlightVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The speed of the shader animation.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Speed;
|
||||
/// <summary>
|
||||
/// How far it goes in any direction.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Multiplier;
|
||||
|
||||
/// <summary>
|
||||
/// How much the limbs (if there are any) rotate.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Are we animating layers or the entire sprite?
|
||||
/// </summary>
|
||||
public bool AnimateLayer = false;
|
||||
public int? TargetLayer;
|
||||
|
||||
[DataField]
|
||||
public string AnimationKey = "default";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ShaderInstance Shader = default!;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Content.Shared._EE.Flight;
|
||||
using Content.Shared._EE.Flight.Events;
|
||||
using Content.Client._EE.Flight.Components;
|
||||
|
||||
namespace Content.Client._EE.Flight;
|
||||
|
||||
public sealed class FlightSystem : SharedFlightSystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<FlightEvent>(OnFlight);
|
||||
}
|
||||
|
||||
private void OnFlight(FlightEvent args)
|
||||
{
|
||||
var uid = GetEntity(args.Uid);
|
||||
if (!_entityManager.TryGetComponent(uid, out SpriteComponent? sprite)
|
||||
|| !args.IsAnimated
|
||||
|| !_entityManager.TryGetComponent(uid, out FlightComponent? flight))
|
||||
return;
|
||||
|
||||
int? targetLayer = null;
|
||||
if (flight.IsLayerAnimated && flight.Layer is not null)
|
||||
{
|
||||
targetLayer = GetAnimatedLayer(uid, flight.Layer, sprite);
|
||||
if (targetLayer == null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.IsFlying && args.IsAnimated && flight.AnimationKey != "default")
|
||||
{
|
||||
var comp = new FlightVisualsComponent
|
||||
{
|
||||
AnimateLayer = flight.IsLayerAnimated,
|
||||
AnimationKey = flight.AnimationKey,
|
||||
Multiplier = flight.ShaderMultiplier,
|
||||
Offset = flight.ShaderOffset,
|
||||
Speed = flight.ShaderSpeed,
|
||||
TargetLayer = targetLayer,
|
||||
};
|
||||
AddComp(uid, comp);
|
||||
}
|
||||
if (!args.IsFlying)
|
||||
RemComp<FlightVisualsComponent>(uid);
|
||||
}
|
||||
|
||||
public int? GetAnimatedLayer(EntityUid uid, string targetLayer, SpriteComponent? sprite = null)
|
||||
{
|
||||
if (!Resolve(uid, ref sprite))
|
||||
return null;
|
||||
|
||||
var index = 0;
|
||||
foreach (var layer in sprite.AllLayers)
|
||||
{
|
||||
// This feels like absolute shitcode, isn't there a better way to check for it?
|
||||
if (layer.Rsi?.Path.ToString() == targetLayer)
|
||||
return index;
|
||||
index++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using Content.Client._EE.Flight.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._EE.Flight;
|
||||
|
||||
/// <summary>
|
||||
/// Handles offsetting an entity while flying
|
||||
/// </summary>
|
||||
public sealed class FlyingVisualizerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<FlightVisualsComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<FlightVisualsComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<FlightVisualsComponent, BeforePostShaderRenderEvent>(OnBeforeShaderPost);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, FlightVisualsComponent comp, ComponentStartup args)
|
||||
{
|
||||
comp.Shader = _protoMan.Index<ShaderPrototype>(comp.AnimationKey).InstanceUnique();
|
||||
AddShader(uid, comp.Shader, comp.AnimateLayer, comp.TargetLayer);
|
||||
SetValues(comp, comp.Speed, comp.Offset, comp.Multiplier);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, FlightVisualsComponent comp, ComponentShutdown args)
|
||||
{
|
||||
AddShader(uid, null, comp.AnimateLayer, comp.TargetLayer);
|
||||
}
|
||||
|
||||
private void AddShader(Entity<SpriteComponent?> entity, ShaderInstance? shader, bool animateLayer, int? layer)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
entity.Comp.PostShader = shader;
|
||||
|
||||
if (animateLayer && layer is not null)
|
||||
entity.Comp.LayerSetShader(layer.Value, shader);
|
||||
|
||||
entity.Comp.GetScreenTexture = shader is not null;
|
||||
entity.Comp.RaiseShaderEvent = shader is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function can be used to modify the shader's values while its running.
|
||||
/// </summary>
|
||||
private void OnBeforeShaderPost(EntityUid uid, FlightVisualsComponent comp, ref BeforePostShaderRenderEvent args)
|
||||
{
|
||||
SetValues(comp, comp.Speed, comp.Offset, comp.Multiplier);
|
||||
}
|
||||
|
||||
private void SetValues(FlightVisualsComponent comp, float speed, float offset, float multiplier)
|
||||
{
|
||||
comp.Shader.SetParameter("Speed", speed);
|
||||
comp.Shader.SetParameter("Offset", offset);
|
||||
comp.Shader.SetParameter("Multiplier", multiplier);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
-->
|
||||
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc nano-chat-create-group-title}"
|
||||
MinSize="300 150">
|
||||
<PanelContainer StyleClasses="AngleRect">
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<!-- Group Name input -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||
<Label Text="{Loc nano-chat-group-name-label}"
|
||||
StyleClasses="LabelHeading" />
|
||||
<PanelContainer StyleClasses="ButtonSquare">
|
||||
<LineEdit Name="GroupNameInput"
|
||||
PlaceHolder="{Loc nano-chat-group-name-placeholder}" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0">
|
||||
<Button Name="CancelButton"
|
||||
Text="{Loc nano-chat-cancel}"
|
||||
StyleClasses="OpenRight"
|
||||
MinSize="80 0" />
|
||||
<Button Name="CreateButton"
|
||||
Text="{Loc nano-chat-create}"
|
||||
StyleClasses="OpenLeft"
|
||||
MinSize="80 0"
|
||||
Disabled="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</DefaultWindow>
|
||||
@@ -1,59 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Evaisa <evagiacosa1@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Funkystation.NanoChat;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CreateGroupChatPopup : DefaultWindow
|
||||
{
|
||||
public event Action<string>? OnGroupCreated;
|
||||
|
||||
public CreateGroupChatPopup(int maxFullNameLength)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ContentsContainer.Margin = new Thickness(3);
|
||||
|
||||
// Button handlers
|
||||
CancelButton.OnPressed += _ => Close();
|
||||
CreateButton.OnPressed += _ => CreateGroup();
|
||||
|
||||
// Input validation
|
||||
GroupNameInput.OnTextEntered += _ => CreateGroup();
|
||||
|
||||
GroupNameInput.OnTextChanged += args =>
|
||||
{
|
||||
if (args.Text.Length > maxFullNameLength)
|
||||
GroupNameInput.Text = args.Text[..maxFullNameLength];
|
||||
ValidateInputs();
|
||||
};
|
||||
}
|
||||
|
||||
private void ValidateInputs()
|
||||
{
|
||||
var isValid = !string.IsNullOrWhiteSpace(GroupNameInput.Text);
|
||||
CreateButton.Disabled = !isValid;
|
||||
}
|
||||
|
||||
private void CreateGroup()
|
||||
{
|
||||
if (CreateButton.Disabled)
|
||||
return;
|
||||
|
||||
var name = GroupNameInput.Text.Trim();
|
||||
OnGroupCreated?.Invoke(name);
|
||||
Close();
|
||||
}
|
||||
|
||||
public void ClearInputs()
|
||||
{
|
||||
GroupNameInput.Text = string.Empty;
|
||||
ValidateInputs();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
-->
|
||||
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc nano-chat-emoji-picker}"
|
||||
MinSize="300 400">
|
||||
<PanelContainer StyleClasses="AngleRect">
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<!-- Search box -->
|
||||
<LineEdit Name="SearchInput"
|
||||
PlaceHolder="{Loc nano-chat-emoji-search}"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 0 0 4" />
|
||||
|
||||
<!-- Emoji grid -->
|
||||
<ScrollContainer VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 4"
|
||||
HScroll="0"
|
||||
VScroll="1">
|
||||
<GridContainer Name="EmojiGrid"
|
||||
Columns="6"
|
||||
HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
|
||||
<!-- Close button -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0">
|
||||
<Button Name="EmojiCloseButton"
|
||||
Text="{Loc nano-chat-close}"
|
||||
MinSize="80 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</DefaultWindow>
|
||||
@@ -1,150 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Evaisa <evagiacosa1@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
|
||||
using System.Linq;
|
||||
using Content.Shared._Funkystation.NanoChat;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client._Funkystation.NanoChat;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class EmojiPickerPopup : DefaultWindow
|
||||
{
|
||||
private List<(string name, SpriteSpecifier specifier)> _filteredEmojis;
|
||||
private SpriteSystem? _spriteSystem;
|
||||
|
||||
public event Action<string>? OnEmojiSelected;
|
||||
|
||||
public EmojiPickerPopup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_filteredEmojis = NanoChatEmojis.EmojiSpecifiers.Select(kv => (kv.Key, kv.Value)).ToList();
|
||||
|
||||
ContentsContainer.Margin = new Thickness(3);
|
||||
|
||||
EmojiCloseButton.OnPressed += _ => Close();
|
||||
SearchInput.OnTextChanged += OnSearchChanged;
|
||||
|
||||
OnResized += UpdateGridColumns;
|
||||
|
||||
PopulateEmojiGrid();
|
||||
UpdateGridColumns();
|
||||
}
|
||||
|
||||
private void UpdateGridColumns()
|
||||
{
|
||||
const int buttonSize = 48;
|
||||
const int scrollbarWidth = 12;
|
||||
|
||||
var scrollContainer = EmojiGrid.Parent as ScrollContainer;
|
||||
if (scrollContainer == null)
|
||||
return;
|
||||
|
||||
var availableWidth = scrollContainer.PixelSize.X - scrollbarWidth;
|
||||
|
||||
if (availableWidth <= 0)
|
||||
return;
|
||||
|
||||
var columns = Math.Max(1, (int) (availableWidth / buttonSize));
|
||||
if (EmojiGrid.Columns != columns)
|
||||
{
|
||||
EmojiGrid.Columns = columns;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchChanged(LineEdit.LineEditEventArgs args)
|
||||
{
|
||||
var search = args.Text.ToLower().Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(search))
|
||||
{
|
||||
_filteredEmojis = NanoChatEmojis.EmojiSpecifiers.Select(kv => (kv.Key, kv.Value)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_filteredEmojis = NanoChatEmojis.EmojiSpecifiers
|
||||
.Where(kv => kv.Key.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(kv => (kv.Key, kv.Value))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
PopulateEmojiGrid();
|
||||
}
|
||||
|
||||
private void PopulateEmojiGrid()
|
||||
{
|
||||
EmojiGrid.RemoveAllChildren();
|
||||
|
||||
var resCache = IoCManager.Resolve<IResourceCache>();
|
||||
_spriteSystem ??= IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
|
||||
foreach (var (name, specifier) in _filteredEmojis)
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
MinSize = new Vector2(48, 48),
|
||||
MaxSize = new Vector2(48, 48),
|
||||
ToolTip = $":{name}:",
|
||||
};
|
||||
|
||||
Texture texture;
|
||||
switch (specifier)
|
||||
{
|
||||
case SpriteSpecifier.Texture texSpecifier:
|
||||
if (!resCache.TryGetResource<TextureResource>(texSpecifier.TexturePath, out var textureResource))
|
||||
continue;
|
||||
texture = textureResource.Texture;
|
||||
break;
|
||||
|
||||
case SpriteSpecifier.Rsi rsiSpecifier:
|
||||
var state = _spriteSystem.GetState(rsiSpecifier);
|
||||
texture = state.Frame0;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
var textureRect = new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
Stretch = TextureRect.StretchMode.KeepAspect,
|
||||
MinSize = new Vector2(32, 32),
|
||||
MaxSize = new Vector2(32, 32),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
};
|
||||
|
||||
button.AddChild(textureRect);
|
||||
|
||||
button.OnPressed += _ =>
|
||||
{
|
||||
OnEmojiSelected?.Invoke($":{name}:");
|
||||
Close();
|
||||
};
|
||||
|
||||
EmojiGrid.AddChild(button);
|
||||
}
|
||||
|
||||
// Recalculate columns after populating
|
||||
UpdateGridColumns();
|
||||
}
|
||||
|
||||
public static SpriteSpecifier? GetEmojiSpecifier(string emojiName)
|
||||
{
|
||||
return NanoChatEmojis.EmojiSpecifiers.TryGetValue(emojiName, out var specifier) ? specifier : null;
|
||||
}
|
||||
|
||||
public static Dictionary<string, SpriteSpecifier> GetEmojis() => new Dictionary<string, SpriteSpecifier>(NanoChatEmojis.EmojiSpecifiers);
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Evaisa <evagiacosa1@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
using Content.Shared._Funkystation.NanoChat;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Client._DV.NanoChat.UI; // DeltaV - Fix emoji alignment
|
||||
using Robust.Shared.Prototypes; // DeltaV - Emoji id for debugging
|
||||
|
||||
namespace Content.Client._Funkystation.NanoChat;
|
||||
|
||||
/// <summary>
|
||||
/// Markup tag for rendering emoji images inline in RichTextLabel.
|
||||
/// </summary>
|
||||
public sealed class EmojiTag : IMarkupTagHandler
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
private SpriteSystem? _spriteSystem;
|
||||
|
||||
public EmojiTag()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public string Name => "emoji";
|
||||
|
||||
// Begin DeltaV - Render invalid emoji as text
|
||||
|
||||
private static ProtoId<NanoChatEmojiPrototype>? GetEmoji(MarkupNode node, out string text, out SpriteSpecifier? emoji)
|
||||
{
|
||||
if (!node.Value.TryGetString(out var emojiName))
|
||||
{
|
||||
// If we don't have a value, then render nothing
|
||||
emoji = null;
|
||||
text = string.Empty;
|
||||
return null;
|
||||
}
|
||||
else if (NanoChatEmojis.EmojiSpecifiers.TryGetValue(emojiName, out emoji))
|
||||
{
|
||||
// If we have a valid emoji, render the emoji, but no text
|
||||
text = string.Empty;
|
||||
return emojiName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have an invalid emoji, render only text
|
||||
text = $":{emojiName}:";
|
||||
return emojiName;
|
||||
}
|
||||
}
|
||||
|
||||
string IMarkupTagHandler.TextBefore(MarkupNode node)
|
||||
{
|
||||
GetEmoji(node, out var text, out _);
|
||||
return text;
|
||||
}
|
||||
|
||||
// End DeltaV - Render invalid emoji as text
|
||||
|
||||
public bool TryCreateControl(MarkupNode node, out Control control)
|
||||
{
|
||||
control = default!;
|
||||
|
||||
var emojiId = GetEmoji(node, out _, out var maybeSpecifier);
|
||||
if (maybeSpecifier is not { } specifier)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_spriteSystem ??= _entitySystemManager.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
Texture texture;
|
||||
switch (specifier)
|
||||
{
|
||||
case SpriteSpecifier.Texture texSpecifier:
|
||||
if (!_resourceCache.TryGetResource<TextureResource>(texSpecifier.TexturePath, out var textureResource))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
texture = textureResource.Texture;
|
||||
break;
|
||||
|
||||
case SpriteSpecifier.Rsi rsiSpecifier:
|
||||
var state = _spriteSystem.GetState(rsiSpecifier);
|
||||
texture = state.Frame0;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Begin DeltaV - Fix emoji alignment, replace TextureRect with custom EmojiControl
|
||||
control = new EmojiControl
|
||||
{
|
||||
Texture = texture,
|
||||
SetWidth = 32f,
|
||||
SetHeight = 32f,
|
||||
Name = $"Emoji[{emojiId}]",
|
||||
// Evilly hard-coded Margin and Offset, don't ask me where they come from please
|
||||
Margin = new(5f, 0f, 2f, 0),
|
||||
Offset = new(5f, -5f),
|
||||
};
|
||||
// End DeltaV
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
-->
|
||||
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc nano-chat-group-members-title}"
|
||||
MinSize="400 300">
|
||||
<PanelContainer StyleClasses="AngleRect">
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<!-- Member list -->
|
||||
<ScrollContainer VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 4">
|
||||
<BoxContainer Name="MembersList"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0">
|
||||
<Button Name="MembersCloseButton"
|
||||
Text="{Loc nano-chat-close}"
|
||||
MinSize="80 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</DefaultWindow>
|
||||
@@ -1,201 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Evaisa <evagiacosa1@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Resources;
|
||||
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Client.ResourceManagement;
|
||||
|
||||
namespace Content.Client._Funkystation.NanoChat;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GroupMembersPopup : DefaultWindow
|
||||
{
|
||||
private List<NanoChatRecipient> _allContacts = new();
|
||||
private HashSet<uint> _members = new();
|
||||
private HashSet<uint> _admins = new();
|
||||
private uint _ownNumber;
|
||||
private uint? _creatorId;
|
||||
|
||||
public event Action<uint>? OnKick;
|
||||
public event Action<uint>? OnAdmin;
|
||||
public event Action<uint>? OnDeadmin;
|
||||
|
||||
public GroupMembersPopup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ContentsContainer.Margin = new Thickness(3);
|
||||
|
||||
MembersCloseButton.OnPressed += _ => Close();
|
||||
}
|
||||
|
||||
public void SetMembers(List<NanoChatRecipient> allContacts, HashSet<uint> members, uint ownNumber, uint? creatorId, HashSet<uint>? admins = null)
|
||||
{
|
||||
_allContacts = allContacts;
|
||||
_members = members;
|
||||
_ownNumber = ownNumber;
|
||||
_creatorId = creatorId;
|
||||
_admins = admins ?? new HashSet<uint>();
|
||||
UpdateMembersList();
|
||||
}
|
||||
|
||||
private void UpdateMembersList()
|
||||
{
|
||||
MembersList.RemoveAllChildren();
|
||||
|
||||
if (_members.Count == 0)
|
||||
{
|
||||
MembersList.AddChild(new Label { Text = Loc.GetString("nano-chat-no-members") });
|
||||
return;
|
||||
}
|
||||
|
||||
var isCreator = _creatorId == _ownNumber;
|
||||
var isAdmin = _admins.Contains(_ownNumber);
|
||||
var canManage = isCreator || isAdmin;
|
||||
|
||||
var memberContacts = _allContacts
|
||||
.Where(c => _members.Contains(c.Number))
|
||||
.OrderBy(c => c.Name)
|
||||
.ToList();
|
||||
|
||||
for (var idx = 0; idx < memberContacts.Count; idx++)
|
||||
{
|
||||
var contact = memberContacts[idx];
|
||||
var isEvenRow = idx % 2 == 0;
|
||||
var isMemberAdmin = _admins.Contains(contact.Number);
|
||||
var isMemberCreator = contact.Number == _creatorId;
|
||||
var isSelf = contact.Number == _ownNumber;
|
||||
|
||||
var canKick = false;
|
||||
if (isSelf) // can always leave yourself.
|
||||
{
|
||||
canKick = true;
|
||||
}
|
||||
else if (!isSelf && canManage && !isMemberCreator)
|
||||
{
|
||||
if (isCreator)
|
||||
canKick = true;
|
||||
else if (isAdmin && !isMemberAdmin)
|
||||
canKick = true;
|
||||
}
|
||||
|
||||
var canAdmin = canManage && !isSelf && !isMemberCreator && !isMemberAdmin;
|
||||
|
||||
var canDeadmin = canManage && !isSelf && !isMemberCreator && isMemberAdmin;
|
||||
if (!isCreator && canDeadmin)
|
||||
canDeadmin = false;
|
||||
|
||||
var contactControl = new MemberContainer(contact, isEvenRow, isMemberCreator, isMemberAdmin, isSelf, canKick, canAdmin, canDeadmin, OnKick, OnAdmin, OnDeadmin);
|
||||
MembersList.AddChild(contactControl);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MemberContainer : PanelContainer
|
||||
{
|
||||
public MemberContainer(NanoChatRecipient contact, bool isEvenRow, bool isCreator, bool isAdmin, bool isSelf, bool canKick, bool canMakeAdmin, bool canDeadmin, Action<uint>? onKick, Action<uint>? onAdmin, Action<uint>? onDeadmin)
|
||||
{
|
||||
HorizontalExpand = true;
|
||||
StyleClasses.Add(isEvenRow ? "PanelBackgroundBaseDark" : "PanelBackgroundLight");
|
||||
|
||||
var resCache = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
// Show name with job title if available
|
||||
var displayName = contact.Name;
|
||||
if (!string.IsNullOrWhiteSpace(contact.JobTitle))
|
||||
displayName = $"{contact.Name} ({contact.JobTitle})";
|
||||
|
||||
var nameLabel = new Label
|
||||
{
|
||||
Text = displayName + (isCreator ? $" {Loc.GetString("nano-chat-owner-suffix")}" : isAdmin ? $" {Loc.GetString("nano-chat-admin-suffix")}" : ""),
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
var numberLabel = new Label
|
||||
{
|
||||
Text = $"#{contact.Number:D4}",
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
Margin = new Thickness(0, 0, (canKick || canMakeAdmin || canDeadmin) ? 108 : 4, 0),
|
||||
};
|
||||
|
||||
AddChild(nameLabel);
|
||||
AddChild(numberLabel);
|
||||
|
||||
// Admin/Deadmin button
|
||||
if (canMakeAdmin)
|
||||
{
|
||||
var adminButton = new Button
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
MinSize = new Vector2(32, 32),
|
||||
MaxSize = new Vector2(32, 32),
|
||||
ToolTip = Loc.GetString("nano-chat-admin"),
|
||||
Margin = new Thickness(0, 0, canKick ? 72 : 36, 0),
|
||||
};
|
||||
|
||||
adminButton.AddStyleClass("OpenBoth");
|
||||
adminButton.AddChild(new TextureRect
|
||||
{
|
||||
Texture = resCache.GetTexture("/Textures/_Funkystation/Interface/NanoChat/admin.png"),
|
||||
Stretch = TextureRect.StretchMode.KeepAspectCentered,
|
||||
MinSize = new Vector2(28, 28),
|
||||
MaxSize = new Vector2(28, 28),
|
||||
});
|
||||
adminButton.OnPressed += _ => onAdmin?.Invoke(contact.Number);
|
||||
|
||||
AddChild(adminButton);
|
||||
}
|
||||
else if (canDeadmin)
|
||||
{
|
||||
var deadminButton = new Button
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
MinSize = new Vector2(32, 32),
|
||||
MaxSize = new Vector2(32, 32),
|
||||
ToolTip = Loc.GetString("nano-chat-deadmin"),
|
||||
Margin = new Thickness(0, 0, canKick ? 72 : 36, 0),
|
||||
};
|
||||
|
||||
deadminButton.AddStyleClass("OpenBoth");
|
||||
deadminButton.AddChild(new TextureRect
|
||||
{
|
||||
Texture = resCache.GetTexture("/Textures/_Funkystation/Interface/NanoChat/deadmin.png"),
|
||||
Stretch = TextureRect.StretchMode.KeepAspectCentered,
|
||||
MinSize = new Vector2(28, 28),
|
||||
MaxSize = new Vector2(28, 28),
|
||||
});
|
||||
deadminButton.OnPressed += _ => onDeadmin?.Invoke(contact.Number);
|
||||
|
||||
AddChild(deadminButton);
|
||||
}
|
||||
|
||||
// Kick/Leave button
|
||||
if (canKick)
|
||||
{
|
||||
var kickButton = new Button
|
||||
{
|
||||
Text = isSelf ? Loc.GetString("nano-chat-leave") : "X",
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
MinSize = new Vector2(isSelf ? 64 : 32, 32),
|
||||
MaxSize = new Vector2(isSelf ? 64 : 32, 32),
|
||||
ToolTip = isSelf ? Loc.GetString("nano-chat-leave") : Loc.GetString("nano-chat-kick"),
|
||||
Margin = new Thickness(0, 0, 36, 0),
|
||||
};
|
||||
|
||||
kickButton.AddStyleClass("OpenBoth");
|
||||
kickButton.OnPressed += _ => onKick?.Invoke(contact.Number);
|
||||
|
||||
AddChild(kickButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
-->
|
||||
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc nano-chat-invite-to-group-title}"
|
||||
MinSize="400 300">
|
||||
<PanelContainer StyleClasses="AngleRect">
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<!-- Search input -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||
<Label Text="{Loc nano-chat-search-label}"
|
||||
StyleClasses="LabelHeading" />
|
||||
<PanelContainer StyleClasses="ButtonSquare">
|
||||
<LineEdit Name="SearchInput"
|
||||
PlaceHolder="{Loc nano-chat-search-placeholder}" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Contact list -->
|
||||
<ScrollContainer VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 4">
|
||||
<BoxContainer Name="ContactsList"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0">
|
||||
<Button Name="InviteCloseButton"
|
||||
Text="{Loc nano-chat-close}"
|
||||
MinSize="80 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</DefaultWindow>
|
||||
@@ -1,191 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Evaisa <evagiacosa1@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 EvaisaDev <mail@evaisa.dev>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.Roles;
|
||||
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._Funkystation.NanoChat;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class InviteToGroupPopup : DefaultWindow
|
||||
{
|
||||
private List<NanoChatRecipient> _allContacts = new();
|
||||
private HashSet<uint> _existingMembers = new();
|
||||
private uint _ownNumber;
|
||||
|
||||
public event Action<uint>? OnInvite;
|
||||
|
||||
public InviteToGroupPopup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ContentsContainer.Margin = new Thickness(3);
|
||||
|
||||
InviteCloseButton.OnPressed += _ => Close();
|
||||
|
||||
SearchInput.OnTextChanged += _ => UpdateContactList();
|
||||
}
|
||||
|
||||
public void SetContacts(List<NanoChatRecipient> contacts, HashSet<uint> existingMembers, uint ownNumber)
|
||||
{
|
||||
_allContacts = contacts;
|
||||
_existingMembers = existingMembers;
|
||||
_ownNumber = ownNumber;
|
||||
UpdateContactList();
|
||||
}
|
||||
|
||||
private void UpdateContactList()
|
||||
{
|
||||
ContactsList.RemoveAllChildren();
|
||||
|
||||
if (_allContacts.Count == 0)
|
||||
{
|
||||
ContactsList.AddChild(new Label { Text = Loc.GetString("nano-chat-look-up-no-server") });
|
||||
return;
|
||||
}
|
||||
|
||||
var searchText = SearchInput.Text.ToLower();
|
||||
var filteredContacts = _allContacts
|
||||
.Where(c => c.Number != _ownNumber &&
|
||||
!_existingMembers.Contains(c.Number) &&
|
||||
(string.IsNullOrWhiteSpace(searchText) ||
|
||||
c.Name.ToLower().Contains(searchText) ||
|
||||
c.Number.ToString().Contains(searchText) ||
|
||||
(!string.IsNullOrWhiteSpace(c.JobTitle) && c.JobTitle.ToLower().Contains(searchText))))
|
||||
.ToList();
|
||||
|
||||
if (filteredContacts.Count == 0)
|
||||
{
|
||||
ContactsList.AddChild(new Label { Text = Loc.GetString("nano-chat-no-results") });
|
||||
return;
|
||||
}
|
||||
|
||||
// Group contacts by department like crew manifest
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var departmentGroups = new Dictionary<DepartmentPrototype, List<NanoChatRecipient>>();
|
||||
var noDepartmentContacts = new List<NanoChatRecipient>();
|
||||
|
||||
foreach (var contact in filteredContacts)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(contact.Department) &&
|
||||
prototypeManager.TryIndex<DepartmentPrototype>(contact.Department, out var dept))
|
||||
{
|
||||
departmentGroups.GetOrNew(dept).Add(contact);
|
||||
}
|
||||
else
|
||||
{
|
||||
noDepartmentContacts.Add(contact);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort departments by weight
|
||||
var sortedDepartments = departmentGroups.Keys.ToList();
|
||||
sortedDepartments.Sort(DepartmentUIComparer.Instance);
|
||||
|
||||
var rowIndex = 0;
|
||||
foreach (var department in sortedDepartments)
|
||||
{
|
||||
var departmentContacts = departmentGroups[department];
|
||||
departmentContacts.Sort((a, b) => string.CompareOrdinal(a.Name, b.Name));
|
||||
|
||||
// Add department header
|
||||
ContactsList.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString(department.Name),
|
||||
StyleClasses = { "LabelBig" },
|
||||
Margin = new Thickness(0, rowIndex == 0 ? 0 : 8, 0, 2)
|
||||
});
|
||||
|
||||
foreach (var contact in departmentContacts)
|
||||
{
|
||||
var isEvenRow = rowIndex % 2 == 0;
|
||||
var contactControl = new ContactContainer(contact, isEvenRow, OnInvite);
|
||||
ContactsList.AddChild(contactControl);
|
||||
rowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add contacts without departments at the end
|
||||
if (noDepartmentContacts.Count > 0)
|
||||
{
|
||||
noDepartmentContacts.Sort((a, b) => string.CompareOrdinal(a.Name, b.Name));
|
||||
|
||||
if (sortedDepartments.Count > 0)
|
||||
{
|
||||
ContactsList.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("nano-chat-other-department"),
|
||||
StyleClasses = { "LabelBig" },
|
||||
Margin = new Thickness(0, 8, 0, 2)
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var contact in noDepartmentContacts)
|
||||
{
|
||||
var isEvenRow = rowIndex % 2 == 0;
|
||||
var contactControl = new ContactContainer(contact, isEvenRow, OnInvite);
|
||||
ContactsList.AddChild(contactControl);
|
||||
rowIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSearch()
|
||||
{
|
||||
SearchInput.Text = string.Empty;
|
||||
}
|
||||
|
||||
private sealed class ContactContainer : PanelContainer
|
||||
{
|
||||
public ContactContainer(NanoChatRecipient contact, bool isEvenRow, Action<uint>? onInvite)
|
||||
{
|
||||
HorizontalExpand = true;
|
||||
StyleClasses.Add(isEvenRow ? "PanelBackgroundBaseDark" : "PanelBackgroundLight");
|
||||
|
||||
// Show name with job title if available
|
||||
var displayName = contact.Name;
|
||||
if (!string.IsNullOrWhiteSpace(contact.JobTitle))
|
||||
displayName = $"{contact.Name} ({contact.JobTitle})";
|
||||
|
||||
var nameLabel = new Label
|
||||
{
|
||||
Text = displayName,
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
var numberLabel = new Label
|
||||
{
|
||||
Text = $"#{contact.Number:D4}",
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
Margin = new Thickness(0, 0, 36, 0),
|
||||
};
|
||||
|
||||
var inviteButton = new Button
|
||||
{
|
||||
Text = "+",
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
MinSize = new Vector2(32, 32),
|
||||
MaxSize = new Vector2(32, 32),
|
||||
ToolTip = Loc.GetString("nano-chat-invite"),
|
||||
};
|
||||
|
||||
inviteButton.AddStyleClass("OpenBoth");
|
||||
inviteButton.OnPressed += _ => onInvite?.Invoke(contact.Number);
|
||||
|
||||
AddChild(nameLabel);
|
||||
AddChild(numberLabel);
|
||||
AddChild(inviteButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"MapGrid",
|
||||
"StationEvent",
|
||||
"TimedDespawn",
|
||||
"TimedDespawnDetailed", // DeltaV
|
||||
|
||||
// makes an announcement on mapInit.
|
||||
"AnnounceOnSpawn",
|
||||
};
|
||||
|
||||
@@ -76,7 +76,6 @@ namespace Content.IntegrationTests.Tests
|
||||
"Hammurabi", // DeltaV
|
||||
"Lighthouse", // DeltaV
|
||||
"Micro", // DeltaV
|
||||
"Ovni", //DeltaV
|
||||
"Pebble", // DeltaV
|
||||
"Shoukou", // DeltaV
|
||||
"Submarine", //DeltaV
|
||||
|
||||
@@ -9,7 +9,6 @@ using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Roles;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared._DV.Access.Components; // DeltaV
|
||||
using Content.Shared._DV.NanoChat; // DeltaV
|
||||
using Content.Server.Clothing.Systems;
|
||||
using Content.Server.Implants;
|
||||
@@ -102,16 +101,11 @@ namespace Content.Server.Access.Systems
|
||||
if (!TryComp<AccessComponent>(uid, out var access) || !HasComp<IdCardComponent>(uid))
|
||||
return;
|
||||
|
||||
// Begin DeltaV Additions
|
||||
// Prevent certain types of IDs from being copied (i.e. Borg ID chips)
|
||||
if (TryComp<PreventAgentIdComponent>(args.Target, out var preventable))
|
||||
{
|
||||
if (preventable.PopupText is { } popupText)
|
||||
_popupSystem.PopupEntity(Loc.GetString(popupText), args.Target.Value, args.User);
|
||||
return;
|
||||
}
|
||||
var beforeLength = access.Tags.Count;
|
||||
access.Tags.UnionWith(targetAccess.Tags);
|
||||
var addedLength = access.Tags.Count - beforeLength;
|
||||
|
||||
// Copy NanoChat data if available
|
||||
// Begin DeltaV Additions - Copy NanoChat data if available
|
||||
if (TryComp<NanoChatCardComponent>(args.Target, out var targetNanoChat) &&
|
||||
TryComp<NanoChatCardComponent>(uid, out var agentNanoChat))
|
||||
{
|
||||
@@ -138,10 +132,6 @@ namespace Content.Server.Access.Systems
|
||||
}
|
||||
}
|
||||
// End DeltaV Additions
|
||||
var beforeLength = access.Tags.Count;
|
||||
access.Tags.UnionWith(targetAccess.Tags);
|
||||
var addedLength = access.Tags.Count - beforeLength;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("agent-id-new", ("number", addedLength), ("card", args.Target)), args.Target.Value, args.User);
|
||||
if (addedLength > 0)
|
||||
Dirty(uid, access);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Components;
|
||||
|
||||
@@ -83,7 +82,6 @@ public sealed partial class DeltaPressureComponent : Component
|
||||
/// The minimum difference in pressure between any side required for the entity to start taking damage.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
public float MinPressureDelta = 7500;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -37,26 +37,4 @@ public sealed partial class DragonRiftComponent : SharedDragonRiftComponent
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("spawn", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string SpawnPrototype = "MobCarpDragon";
|
||||
|
||||
//Begin DeltaV - Elite spawns on dragon rifts
|
||||
|
||||
[DataField]
|
||||
public bool SpawnElites = true;
|
||||
|
||||
[DataField("spawnElite")]
|
||||
public EntProtoId? SpawnElitePrototype = "MobSharkminnowDragon";
|
||||
|
||||
/// <summary>
|
||||
/// Every N-th spawn is the elite where N is Elite frequency
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int SpawnEliteFrequency = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Accumulation of elite spawns
|
||||
/// Starts at the same value as Frequency to guarantee the first spawn to be elite
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int SpawnEliteAccumulator = 5;
|
||||
//End DeltaV - Elite spawns on dragon rifts
|
||||
}
|
||||
|
||||
@@ -90,16 +90,7 @@ public sealed class DragonRiftSystem : EntitySystem
|
||||
if (comp.SpawnAccumulator > comp.SpawnCooldown)
|
||||
{
|
||||
comp.SpawnAccumulator -= comp.SpawnCooldown;
|
||||
//Begin DeltaV - Elite spawns on dragon rifts
|
||||
comp.SpawnEliteAccumulator += 1;
|
||||
var entSpawnPrototype = comp.SpawnPrototype;
|
||||
if (comp.SpawnElites && comp.SpawnEliteAccumulator >= comp.SpawnEliteFrequency)
|
||||
{
|
||||
comp.SpawnEliteAccumulator -= comp.SpawnEliteFrequency;
|
||||
entSpawnPrototype = comp.SpawnElitePrototype;
|
||||
}
|
||||
//End DeltaV - Elite spawns on dragon rifts
|
||||
var ent = Spawn(entSpawnPrototype, xform.Coordinates); //Delta-v change: comp.SpawnPrototype -> entSpawnPrototype
|
||||
var ent = Spawn(comp.SpawnPrototype, xform.Coordinates);
|
||||
|
||||
// Update their look to match the leader.
|
||||
if (TryComp<RandomSpriteComponent>(comp.Dragon, out var randomSprite))
|
||||
|
||||
@@ -8,7 +8,6 @@ using Content.Server.Stack;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared._NF.Storage.Components; // Frontier
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
@@ -82,14 +81,6 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
|
||||
if (volume <= 0 || !TryChangeMaterialAmount(uid, msg.Material, -volume))
|
||||
return;
|
||||
|
||||
// Frontier
|
||||
// If we made it this far, turn off the magnet before spawning materials
|
||||
if (TryComp<MaterialStorageMagnetPickupComponent>(uid, out var magnet))
|
||||
{
|
||||
magnet.MagnetEnabled = false;
|
||||
}
|
||||
// end Frontier
|
||||
|
||||
var mats = SpawnMultipleFromMaterial(volume, material, Transform(uid).Coordinates, out _);
|
||||
foreach (var mat in mats.Where(mat => !TerminatingOrDeleted(mat)))
|
||||
{
|
||||
@@ -111,12 +102,14 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
|
||||
if (!base.TryInsertMaterialEntity(user, toInsert, receiver, storage, material, composition))
|
||||
return false;
|
||||
_audio.PlayPvs(storage.InsertingSound, receiver);
|
||||
_popup.PopupEntity(Loc.GetString("machine-insert-item",
|
||||
("user", user),
|
||||
("machine", receiver),
|
||||
("item", toInsert)),
|
||||
receiver);
|
||||
if (user != receiver) // Goobstation - for automation to not spam popups
|
||||
_popup.PopupEntity(Loc.GetString("machine-insert-item",
|
||||
("user", user),
|
||||
("machine", receiver),
|
||||
("item", toInsert)),
|
||||
receiver);
|
||||
_popup.PopupEntity(Loc.GetString("machine-insert-item", ("user", user), ("machine", receiver),
|
||||
("item", toInsert)), receiver);
|
||||
QueueDel(toInsert);
|
||||
|
||||
// Logging
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed partial class WarDeclaratorComponent : Component
|
||||
/// Time allowed for declaration of war
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WarDeclarationDelay = 8.0f; // DeltaV - was 6.0
|
||||
public float WarDeclarationDelay = 6.0f;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan DisableAt;
|
||||
|
||||
@@ -51,9 +51,9 @@ public sealed partial class RadiationSystem : EntitySystem
|
||||
_accumulator = 0f;
|
||||
}
|
||||
|
||||
public void IrradiateEntity(EntityUid uid, float radsPerSecond, float time, EntityUid? origin = null)
|
||||
public void IrradiateEntity(EntityUid uid, float radsPerSecond, float time)
|
||||
{
|
||||
var msg = new OnIrradiatedEvent(time, radsPerSecond, origin);
|
||||
var msg = new OnIrradiatedEvent(time, radsPerSecond, uid);
|
||||
RaiseLocalEvent(uid, msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,13 +55,6 @@ public sealed partial class XenoArtifactSystem
|
||||
var trigger = PrototypeManager.Index<XenoArchTriggerPrototype>(triggerId);
|
||||
if (_entityWhitelist.IsWhitelistFail(trigger.Whitelist, ent))
|
||||
continue;
|
||||
// DeltaV - start of TriggerBlacklist
|
||||
if (
|
||||
(trigger.TriggerBlacklist != null && triggerPool.Any(otherTrigger => trigger.TriggerBlacklist.Contains(otherTrigger.ID)))
|
||||
|| triggerPool.Any(otherTrigger => otherTrigger.TriggerBlacklist != null && otherTrigger.TriggerBlacklist.Contains(triggerId))
|
||||
)
|
||||
continue;
|
||||
// DeltaV - end of TriggerBlacklist
|
||||
|
||||
triggerPool.Add(trigger);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
using System; // DeltaV
|
||||
using Content.Server.Research.Systems;
|
||||
using Content.Server.Xenoarchaeology.Artifact;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Psionics.Glimmer;// DeltaV
|
||||
using Content.Shared._DV.Xenoarchaeology.BUI;// DeltaV
|
||||
using Content.Shared.Xenoarchaeology.Equipment;
|
||||
using Content.Shared.Xenoarchaeology.Equipment.Components;
|
||||
|
||||
using Robust.Server.GameObjects; // DeltaV
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -20,8 +14,6 @@ public sealed class ArtifactAnalyzerSystem : SharedArtifactAnalyzerSystem
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly ResearchSystem _research = default!;
|
||||
[Dependency] private readonly XenoArtifactSystem _xenoArtifact = default!;
|
||||
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!; // DeltaV
|
||||
[Dependency] private readonly ArtifactAnalyzerSystem _analyzerSystem = default!; //DeltaV
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -39,52 +31,21 @@ public sealed class ArtifactAnalyzerSystem : SharedArtifactAnalyzerSystem
|
||||
if (!_research.TryGetClientServer(ent, out var server, out var serverComponent))
|
||||
return;
|
||||
|
||||
// Begin DeltaV - Variables for extraction totals
|
||||
var sumResearch = 0;
|
||||
var sumGlimmer = 0;
|
||||
if (!_analyzerSystem.TryGetAnalyzer(ent, out var analyzer))
|
||||
return;
|
||||
// End DeltaV
|
||||
|
||||
foreach (var node in _xenoArtifact.GetAllNodes(artifact.Value))
|
||||
{
|
||||
var research = _xenoArtifact.GetResearchValue(node);
|
||||
_xenoArtifact.SetConsumedResearchValue(node, node.Comp.ConsumedResearchValue + research);
|
||||
|
||||
// Begin DeltaV - Only run if we have an artifact ready for extraction
|
||||
if (analyzer != null)
|
||||
{
|
||||
sumGlimmer += (int)(research / (float)analyzer.Value.Comp.ExtractRatio);
|
||||
research = (int)(research * GetGlimmerMultiplier(analyzer.Value.Comp));
|
||||
}
|
||||
// End DeltaV
|
||||
sumResearch += research;
|
||||
}
|
||||
UpdateClientUI(ent, analyzer!.Value); // DeltaV
|
||||
|
||||
// 4-16-25: It's a sad day when a scientist makes negative 5k research
|
||||
if (sumResearch <= 0)
|
||||
return;
|
||||
|
||||
_glimmerSystem.Glimmer += sumGlimmer; // DeltaV - Add glimmer based on extracted points.
|
||||
_research.ModifyServerPoints(server.Value, sumResearch, serverComponent);
|
||||
_audio.PlayPvs(ent.Comp.ExtractSound, artifact.Value);
|
||||
_popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"), artifact.Value, PopupType.Large);
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
private void UpdateClientUI(EntityUid console, ArtifactAnalyzerComponent analyzer)
|
||||
{
|
||||
|
||||
var uiSystem = EntityManager.System<UserInterfaceSystem>();
|
||||
uiSystem.SetUiState(console, ArtifactAnalyzerUiKey.Key,
|
||||
new AnalysisConsoleBoundUserInterfaceState(GetGlimmerMultiplier(analyzer), (float)analyzer.ExtractRatio));
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
private float GetGlimmerMultiplier(ArtifactAnalyzerComponent comp)
|
||||
{
|
||||
return 1 + (MathF.Pow(_glimmerSystem.Glimmer / 1000f, 2f) * comp.PointGlimmerMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
using Content.Shared._DEN.TimedDespawn;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DEN.TimedDespawn;
|
||||
|
||||
/// <summary>
|
||||
/// This handles the <see cref="TimedDespawnDetailedComponent"/>
|
||||
/// </summary>
|
||||
public sealed class TimedDespawnDetailedSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _timedDespawns = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<TimedDespawnDetailedComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<TimedDespawnDetailedComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var entity in _timedDespawns)
|
||||
{
|
||||
if (!Exists(entity) || !TryComp<TimedDespawnDetailedComponent>(entity, out var timedDespawn))
|
||||
continue;
|
||||
|
||||
TryDelete((entity, timedDespawn));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExamine(Entity<TimedDespawnDetailedComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
var timeLeft = GetTimeRemaining(ent);
|
||||
|
||||
if (timeLeft == null || ent.Comp.ExamineLocId == null)
|
||||
return;
|
||||
|
||||
var stringTime = double.Round(timeLeft.Value.TotalSeconds, 1);
|
||||
var examineText = Loc.GetString(ent.Comp.ExamineLocId, ("remaining", stringTime));
|
||||
args.PushMarkup(examineText, 1);
|
||||
}
|
||||
|
||||
public void StartTimer(Entity<TimedDespawnDetailedComponent> ent)
|
||||
{
|
||||
ent.Comp.StartTime = _gameTiming.CurTime;
|
||||
_timedDespawns.Add(ent);
|
||||
|
||||
if (ent.Comp.StartSound != null)
|
||||
{
|
||||
var entCoords = _transformSystem.GetMoverCoordinates(ent);
|
||||
_audioSystem.PlayPredicted(ent.Comp.StartSound, entCoords, null, ent.Comp.StartSoundParams);
|
||||
}
|
||||
}
|
||||
|
||||
public void StopTimer(Entity<TimedDespawnDetailedComponent> ent)
|
||||
{
|
||||
ent.Comp.StartTime = TimeSpan.Zero;
|
||||
_timedDespawns.Remove(ent);
|
||||
}
|
||||
|
||||
public TimeSpan? GetTimeRemaining(Entity<TimedDespawnDetailedComponent> ent)
|
||||
{
|
||||
if (!_timedDespawns.Contains(ent))
|
||||
return null;
|
||||
|
||||
var despawnAfterAsSpan = TimeSpan.FromSeconds(ent.Comp.Lifetime);
|
||||
var timeLeft = (ent.Comp.StartTime + despawnAfterAsSpan) - _gameTiming.CurTime;
|
||||
|
||||
return timeLeft;
|
||||
}
|
||||
|
||||
public void TryDelete(Entity<TimedDespawnDetailedComponent> ent)
|
||||
{
|
||||
var remaining = GetTimeRemaining(ent);
|
||||
|
||||
if (remaining == null || remaining.Value.TotalSeconds > 0)
|
||||
return;
|
||||
|
||||
if (ent.Comp.EndSound != null)
|
||||
{
|
||||
var entCoords = _transformSystem.GetMoverCoordinates(ent);
|
||||
_audioSystem.PlayPredicted(ent.Comp.EndSound, entCoords, null, ent.Comp.EndSoundParams);
|
||||
}
|
||||
|
||||
StopTimer(ent);
|
||||
EntityManager.QueueDeleteEntity(ent);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<TimedDespawnDetailedComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
var entCoords = _transformSystem.GetMoverCoordinates(ent);
|
||||
|
||||
if (ent.Comp.StartSound != null)
|
||||
_audioSystem.PlayPredicted(ent.Comp.StartSound, entCoords, null, ent.Comp.StartSoundParams);
|
||||
|
||||
StartTimer(ent);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using Content.Server.Chat.Systems;
|
||||
using Content.Server.Cloning;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._DV.Abilities.Psionics;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
@@ -72,8 +71,7 @@ public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
||||
component.NextSwap = _timing.CurTime + TimeSpan.FromSeconds(_random.Next(300, 1200));
|
||||
|
||||
if (HasComp<FracturedFormBodyComponent>(entity)) return; // Don't generate a new body if we're already part of a network.
|
||||
var bodyComp = AddComp<FracturedFormBodyComponent>(entity);
|
||||
bodyComp.ControllingForm = entity.Owner;
|
||||
AddComp<FracturedFormBodyComponent>(entity);
|
||||
component.Bodies.Add(entity);
|
||||
GenerateForm(entity);
|
||||
}
|
||||
@@ -115,9 +113,9 @@ public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
||||
}
|
||||
|
||||
var bodies = EntityQueryEnumerator<FracturedFormBodyComponent>();
|
||||
while (bodies.MoveNext(out var uid, out var comp))
|
||||
while (bodies.MoveNext(out var uid, out var _))
|
||||
{
|
||||
if (!HasComp<SleepingComponent>(uid) && !_mind.GetMind(uid).HasValue)
|
||||
if (!HasComp<SleepingComponent>(uid) && !HasComp<FracturedFormPowerComponent>(uid))
|
||||
{
|
||||
_sleeping.TrySleeping(uid);
|
||||
}
|
||||
@@ -127,10 +125,6 @@ public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
||||
// Ensure the body isn't forcesleep'd by the SSD system.
|
||||
ssd.IsSSD = false;
|
||||
}
|
||||
|
||||
// Cleanup the component from any out-of-network bodies. (See Mindbreaking)
|
||||
if (!comp.ControllingForm.IsValid() || Deleted(comp.ControllingForm) || !HasComp<FracturedFormPowerComponent>(comp.ControllingForm))
|
||||
RemCompDeferred<FracturedFormBodyComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,18 +149,8 @@ public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
||||
var speciesPrototypes = _prototype.EnumeratePrototypes<SpeciesPrototype>();
|
||||
foreach (var proto in speciesPrototypes)
|
||||
{
|
||||
var speciesEntityPrototype = _prototype.Index<EntityPrototype>(proto.Prototype);
|
||||
|
||||
if (proto.RoundStart && speciesEntityPrototype.TryGetComponent<PotentialPsionicComponent>(out var canBePsionic, Factory))
|
||||
{
|
||||
var chance = canBePsionic.Chance;
|
||||
|
||||
if (speciesEntityPrototype.TryGetComponent<PsionicBonusChanceComponent>(out var bonusChance, Factory))
|
||||
chance = (chance * bonusChance.Multiplier) + bonusChance.FlatBonus;
|
||||
|
||||
if (chance > 0)
|
||||
validSpecies.Add(proto.ID);
|
||||
}
|
||||
if (proto.RoundStart)
|
||||
validSpecies.Add(proto.ID);
|
||||
}
|
||||
var species = _random.Pick(validSpecies);
|
||||
var character = HumanoidCharacterProfile.RandomWithSpecies(species);
|
||||
@@ -180,9 +164,8 @@ public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
||||
|
||||
if (newBody is { } body && !Deleted(body))
|
||||
{
|
||||
var bodyComp = AddComp<FracturedFormBodyComponent>(body);
|
||||
AddComp<FracturedFormBodyComponent>(body);
|
||||
original.Comp.Bodies.Add(body);
|
||||
bodyComp.ControllingForm = original.Owner;
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -243,14 +226,6 @@ public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
||||
|
||||
var duplicate = AddComp<FracturedFormPowerComponent>(targetBody);
|
||||
duplicate.Bodies = entity.Comp.Bodies;
|
||||
// Pass the controlling form to the new component's bodies.
|
||||
foreach (var body in duplicate.Bodies)
|
||||
{
|
||||
if (TryComp<FracturedFormBodyComponent>(body, out var bodyComp))
|
||||
{
|
||||
bodyComp.ControllingForm = targetBody;
|
||||
}
|
||||
}
|
||||
RemCompDeferred(entity, entity.Comp);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ public sealed partial class PsychokineticScreamPowerSystem : EntitySystem
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly ShatterLightsAbilitySystem _shatterLights = default!;
|
||||
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -55,8 +54,6 @@ public sealed partial class PsychokineticScreamPowerSystem : EntitySystem
|
||||
|
||||
SpawnAttachedTo(entity.Comp.Effect, entity.Owner.ToCoordinates());
|
||||
|
||||
_psionics.LogPowerUsed(entity.Owner, "psychokinetic scream", 3, 6);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,17 +46,11 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<NanoChatCartridgeComponent, CartridgeUiReadyEvent>(OnUiReady);
|
||||
SubscribeLocalEvent<NanoChatCartridgeComponent, CartridgeMessageEvent>(OnMessage);
|
||||
SubscribeLocalEvent<NanoChatCartridgeComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
Subs.CVar(_cfg, CCVars.MaxNameLength, x => _maxNameLength = x, true);
|
||||
Subs.CVar(_cfg, CCVars.MaxIdJobLength, x => _maxIdJobLength = x, true);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<NanoChatCartridgeComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
UpdateUIForAllCards();
|
||||
}
|
||||
|
||||
private void UpdateClosed(Entity<NanoChatCartridgeComponent> ent)
|
||||
{
|
||||
if (!TryComp<CartridgeComponent>(ent, out var cartridge) ||
|
||||
@@ -130,7 +124,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
HandleCloseChat(card);
|
||||
break;
|
||||
case NanoChatUiMessageType.ToggleMute:
|
||||
HandleToggleMute(card, msg);
|
||||
HandleToggleMute(card);
|
||||
break;
|
||||
case NanoChatUiMessageType.ToggleMuteChat:
|
||||
HandleToggleMuteChat(card, msg);
|
||||
@@ -142,22 +136,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
HandleSendMessage(ent, card, msg);
|
||||
break;
|
||||
case NanoChatUiMessageType.ToggleListNumber:
|
||||
HandleToggleListNumber(card, msg);
|
||||
break;
|
||||
case NanoChatUiMessageType.CreateGroupChat:
|
||||
HandleCreateGroupChat(card, msg);
|
||||
break;
|
||||
case NanoChatUiMessageType.InviteToGroup:
|
||||
HandleInviteToGroup(card, msg);
|
||||
break;
|
||||
case NanoChatUiMessageType.KickFromGroup:
|
||||
HandleKickFromGroup(card, msg);
|
||||
break;
|
||||
case NanoChatUiMessageType.AdminUser:
|
||||
HandleAdminUser(card, msg);
|
||||
break;
|
||||
case NanoChatUiMessageType.DeadminUser:
|
||||
HandleDeadminUser(card, msg);
|
||||
HandleToggleListNumber(card);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -211,24 +190,16 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Add new recipient
|
||||
var recipient = new NanoChatRecipient(
|
||||
msg.RecipientNumber.Value,
|
||||
var recipient = new NanoChatRecipient(msg.RecipientNumber.Value,
|
||||
name,
|
||||
jobTitle,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
jobTitle);
|
||||
|
||||
// Initialize or update recipient
|
||||
_nanoChat.SetRecipient((card, card.Comp), msg.RecipientNumber.Value, recipient);
|
||||
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} created new NanoChat conversation with #{msg.RecipientNumber:D4} ({name})");
|
||||
$"{ToPrettyString(msg.Actor):user} created new NanoChat conversation with #{msg.RecipientNumber:D4} ({name})");
|
||||
|
||||
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
||||
RaiseLocalEvent(ref recipientEv);
|
||||
@@ -306,65 +277,15 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
if (msg.RecipientNumber == null || card.Comp.Number == null)
|
||||
return;
|
||||
|
||||
var chatNumber = msg.RecipientNumber.Value;
|
||||
var recipient = _nanoChat.GetRecipient((card, card.Comp), chatNumber);
|
||||
|
||||
if (recipient is { IsGroup: true } groupChat)
|
||||
{
|
||||
var members = groupChat.Members ?? new HashSet<uint>();
|
||||
if (members.Remove(card.Comp.Number.Value))
|
||||
{
|
||||
var admins = groupChat.Admins ?? new HashSet<uint>();
|
||||
admins.Remove(card.Comp.Number.Value);
|
||||
|
||||
// If the creator is leaving, transfer ownership
|
||||
uint? newCreatorId = groupChat.CreatorId;
|
||||
var isCreatorLeaving = card.Comp.Number.Value == groupChat.CreatorId;
|
||||
if (isCreatorLeaving)
|
||||
{
|
||||
if (admins.Count > 0)
|
||||
{
|
||||
newCreatorId = admins.First();
|
||||
}
|
||||
else if (members.Count > 0)
|
||||
{
|
||||
newCreatorId = members.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
newCreatorId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the group for all remaining members
|
||||
var updatedRecipient = newCreatorId != null
|
||||
? groupChat with { Members = members, Admins = admins, CreatorId = newCreatorId }
|
||||
: groupChat with { Members = members, Admins = admins };
|
||||
|
||||
foreach (var memberNumber in members)
|
||||
{
|
||||
var cardQuery = EntityQueryEnumerator<NanoChatCardComponent>();
|
||||
while (cardQuery.MoveNext(out var memberCardUid, out var memberCard))
|
||||
{
|
||||
if (memberCard.Number == memberNumber)
|
||||
{
|
||||
_nanoChat.SetRecipient((memberCardUid, memberCard), chatNumber, updatedRecipient);
|
||||
UpdateUIForCard(memberCardUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete chat but keep the messages
|
||||
var deleted = _nanoChat.TryDeleteChat((card, card.Comp), chatNumber, true);
|
||||
var deleted = _nanoChat.TryDeleteChat((card, card.Comp), msg.RecipientNumber.Value, true);
|
||||
|
||||
if (!deleted)
|
||||
return;
|
||||
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} deleted NanoChat conversation with #{chatNumber:D4}");
|
||||
$"{ToPrettyString(msg.Actor):user} deleted NanoChat conversation with #{msg.RecipientNumber:D4}");
|
||||
|
||||
UpdateUIForCard(card);
|
||||
}
|
||||
@@ -372,15 +293,9 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Handles toggling notification mute state.
|
||||
/// </summary>
|
||||
private void HandleToggleMute(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||
private void HandleToggleMute(Entity<NanoChatCardComponent> card)
|
||||
{
|
||||
var muted = _nanoChat.GetNotificationsMuted((card, card.Comp));
|
||||
_nanoChat.SetNotificationsMuted((card, card.Comp), !muted);
|
||||
|
||||
_adminLogger.Add(LogType.Chat,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} {(muted ? "un" : "")}muted the NanoChat on {ToPrettyString(card)}");
|
||||
|
||||
_nanoChat.SetNotificationsMuted((card, card.Comp), !_nanoChat.GetNotificationsMuted((card, card.Comp)));
|
||||
UpdateUIForCard(card);
|
||||
}
|
||||
|
||||
@@ -388,25 +303,13 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
{
|
||||
if (msg.RecipientNumber is not uint chat)
|
||||
return;
|
||||
|
||||
_nanoChat.ToggleChatMuted((card, card.Comp), chat);
|
||||
|
||||
_adminLogger.Add(LogType.Chat,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} toggled NanoChat notifcations for chat #{chat:D4} on {ToPrettyString(card)}");
|
||||
|
||||
UpdateUIForCard(card);
|
||||
}
|
||||
|
||||
private void HandleToggleListNumber(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||
private void HandleToggleListNumber(Entity<NanoChatCardComponent> card)
|
||||
{
|
||||
var numberListed = _nanoChat.GetListNumber((card, card.Comp));
|
||||
_nanoChat.SetListNumber((card, card.Comp), !numberListed);
|
||||
|
||||
_adminLogger.Add(LogType.Chat,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} toggled the NanoChat listing of {ToPrettyString(card)} {(numberListed ? "off" : "on")}");
|
||||
|
||||
_nanoChat.SetListNumber((card, card.Comp), !_nanoChat.GetListNumber((card, card.Comp)));
|
||||
UpdateUIForAllCards();
|
||||
}
|
||||
|
||||
@@ -420,11 +323,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
if (msg.RecipientNumber == null || msg.Content == null || card.Comp.Number == null)
|
||||
return;
|
||||
|
||||
var recipient = _nanoChat.GetRecipient((card, card.Comp), msg.RecipientNumber.Value);
|
||||
var isGroupChat = recipient?.IsGroup ?? false;
|
||||
|
||||
// Only ensure recipient exists for non-group chats
|
||||
if (!isGroupChat && !EnsureRecipientExists(card, msg.RecipientNumber.Value))
|
||||
if (!EnsureRecipientExists(card, msg.RecipientNumber.Value))
|
||||
return;
|
||||
|
||||
var content = msg.Content;
|
||||
@@ -442,19 +341,8 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
(uint)card.Comp.Number
|
||||
);
|
||||
|
||||
List<Entity<NanoChatCardComponent>> recipients;
|
||||
bool deliveryFailed;
|
||||
|
||||
if (isGroupChat && recipient != null)
|
||||
{
|
||||
// For group chats, deliver to all members
|
||||
(deliveryFailed, recipients) = AttemptGroupMessageDelivery(cartridge, recipient.Value, card.Comp.Number.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For regular chats, deliver to single recipient
|
||||
(deliveryFailed, recipients) = AttemptMessageDelivery(cartridge, msg.RecipientNumber.Value);
|
||||
}
|
||||
// Attempt delivery
|
||||
var (deliveryFailed, recipients) = AttemptMessageDelivery(cartridge, msg.RecipientNumber.Value);
|
||||
|
||||
// Update delivery status
|
||||
message = message with { DeliveryFailed = deliveryFailed };
|
||||
@@ -469,7 +357,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
|
||||
_adminLogger.Add(LogType.Chat,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} sent NanoChat message to {recipientsText}: {content}{(deliveryFailed ? " [DELIVERY FAILED]" : "")}");
|
||||
$"{ToPrettyString(card):user} sent NanoChat message to {recipientsText}: {content}{(deliveryFailed ? " [DELIVERY FAILED]" : "")}");
|
||||
|
||||
var msgEv = new NanoChatMessageReceivedEvent(card);
|
||||
RaiseLocalEvent(ref msgEv);
|
||||
@@ -477,9 +365,9 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
if (deliveryFailed)
|
||||
return;
|
||||
|
||||
foreach (var recipientCard in recipients)
|
||||
foreach (var recipient in recipients)
|
||||
{
|
||||
DeliverMessageToRecipient(card, recipientCard, message, msg.RecipientNumber.Value, recipient);
|
||||
DeliverMessageToRecipient(card, recipient, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,88 +480,27 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
/// <param name="sender">The sender's card entity</param>
|
||||
/// <param name="recipient">The recipient's card entity</param>
|
||||
/// <param name="message">The <see cref="NanoChatMessage" /> to deliver</param>
|
||||
/// <param name="chatNumber">The chat number (for group chats, this is the group number)</param>
|
||||
/// <param name="groupRecipient">Optional group recipient info if this is a group chat</param>
|
||||
private void DeliverMessageToRecipient(Entity<NanoChatCardComponent> sender,
|
||||
Entity<NanoChatCardComponent> recipient,
|
||||
NanoChatMessage message,
|
||||
uint chatNumber,
|
||||
NanoChatRecipient? groupRecipient = null)
|
||||
NanoChatMessage message)
|
||||
{
|
||||
if (sender.Comp.Number is not uint senderNumber)
|
||||
return;
|
||||
|
||||
// For group chats, use the group number. For regular chats, use the sender's number
|
||||
var recipientNumber = (groupRecipient != null && groupRecipient.Value.IsGroup) ? chatNumber : senderNumber;
|
||||
|
||||
if (groupRecipient != null && groupRecipient.Value.IsGroup)
|
||||
{
|
||||
var existingRecipient = _nanoChat.GetRecipient((recipient, recipient.Comp), recipientNumber);
|
||||
|
||||
if (existingRecipient == null || !existingRecipient.Value.IsGroup)
|
||||
{
|
||||
_nanoChat.SetRecipient((recipient, recipient.Comp), recipientNumber, groupRecipient.Value);
|
||||
}
|
||||
else if (groupRecipient.Value.Members != null)
|
||||
{
|
||||
var existingMembers = existingRecipient.Value.Members ?? new HashSet<uint>();
|
||||
if (!existingMembers.SetEquals(groupRecipient.Value.Members))
|
||||
{
|
||||
_nanoChat.SetRecipient((recipient, recipient.Comp), recipientNumber, groupRecipient.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingAdmins = existingRecipient.Value.Admins ?? new HashSet<uint>();
|
||||
var groupAdmins = groupRecipient.Value.Admins ?? new HashSet<uint>();
|
||||
if (!existingAdmins.SetEquals(groupAdmins))
|
||||
{
|
||||
_nanoChat.SetRecipient((recipient, recipient.Comp), recipientNumber, groupRecipient.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!EnsureRecipientExists(recipient, recipientNumber))
|
||||
{
|
||||
// Always try to get and add sender info to recipient's contacts
|
||||
if (!EnsureRecipientExists(recipient, senderNumber))
|
||||
return;
|
||||
}
|
||||
|
||||
_nanoChat.AddMessage((recipient, recipient.Comp), recipientNumber, message with { DeliveryFailed = false });
|
||||
_nanoChat.AddMessage((recipient, recipient.Comp), senderNumber, message with { DeliveryFailed = false });
|
||||
|
||||
if (recipient.Comp.IsClosed || _nanoChat.GetCurrentChat((recipient, recipient.Comp)) != recipientNumber)
|
||||
HandleUnreadNotification(recipient, message, recipientNumber);
|
||||
if (recipient.Comp.IsClosed || _nanoChat.GetCurrentChat((recipient, recipient.Comp)) != senderNumber)
|
||||
HandleUnreadNotification(recipient, message, senderNumber);
|
||||
|
||||
var msgEv = new NanoChatMessageReceivedEvent(recipient);
|
||||
RaiseLocalEvent(ref msgEv);
|
||||
UpdateUIForCard(recipient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to deliver a message to all members of a group chat.
|
||||
/// </summary>
|
||||
private (bool failed, List<Entity<NanoChatCardComponent>> recipients) AttemptGroupMessageDelivery(
|
||||
Entity<NanoChatCartridgeComponent> sender,
|
||||
NanoChatRecipient groupRecipient,
|
||||
uint senderNumber)
|
||||
{
|
||||
if (groupRecipient.Members == null)
|
||||
return (true, new List<Entity<NanoChatCardComponent>>());
|
||||
|
||||
var deliverableRecipients = new List<Entity<NanoChatCardComponent>>();
|
||||
|
||||
foreach (var memberNumber in groupRecipient.Members)
|
||||
{
|
||||
if (memberNumber == senderNumber)
|
||||
continue;
|
||||
|
||||
var (failed, memberCards) = AttemptMessageDelivery(sender, memberNumber);
|
||||
if (!failed)
|
||||
deliverableRecipients.AddRange(memberCards);
|
||||
}
|
||||
|
||||
// Delivery failed if no recipients could receive the message
|
||||
return (deliverableRecipients.Count == 0, deliverableRecipients);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles unread message notifications and updates unread status.
|
||||
/// </summary>
|
||||
@@ -683,21 +510,21 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
{
|
||||
// Get sender name from contacts or fall back to number
|
||||
var recipients = _nanoChat.GetRecipients((recipient, recipient.Comp));
|
||||
var senderName = recipients.TryGetValue(senderNumber, out var senderRecipient)
|
||||
var senderName = recipients.TryGetValue(message.SenderId, out var senderRecipient)
|
||||
? senderRecipient.Name
|
||||
: $"#{senderNumber:D4}";
|
||||
: $"#{message.SenderId:D4}";
|
||||
var hasSelectedCurrentChat = _nanoChat.GetCurrentChat((recipient, recipient.Comp)) == senderNumber;
|
||||
|
||||
// Update unread status
|
||||
if (!hasSelectedCurrentChat)
|
||||
_nanoChat.SetRecipient((recipient, recipient.Comp),
|
||||
senderNumber,
|
||||
message.SenderId,
|
||||
senderRecipient with { HasUnread = true });
|
||||
|
||||
// Temporary local to avoid trouble with read-only access; Contains doesn't modify the collection
|
||||
HashSet<uint> mutedChats = recipient.Comp.MutedChats;
|
||||
if (recipient.Comp.NotificationsMuted ||
|
||||
mutedChats.Contains(senderNumber) ||
|
||||
mutedChats.Contains(message.SenderId) ||
|
||||
recipient.Comp.PdaUid is not { } pdaUid ||
|
||||
!TryComp<CartridgeLoaderComponent>(pdaUid, out var loader) ||
|
||||
// Don't notify if the recipient has the NanoChat program open with this chat selected.
|
||||
@@ -766,27 +593,14 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
|
||||
// Try to get job title from ID card if possible
|
||||
string? jobTitle = null;
|
||||
string? department = null;
|
||||
var name = "Unknown";
|
||||
if (TryComp<IdCardComponent>(uid, out var idCard))
|
||||
{
|
||||
jobTitle = idCard.LocalizedJobTitle;
|
||||
name = idCard.FullName ?? name;
|
||||
if (idCard.JobDepartments is { } depts && depts.Count > 0)
|
||||
department = depts[0].ToString();
|
||||
}
|
||||
|
||||
return new NanoChatRecipient(
|
||||
number,
|
||||
name,
|
||||
jobTitle,
|
||||
department,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
return new NanoChatRecipient(number, name, jobTitle);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -810,34 +624,12 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
var query = AllEntityQuery<NanoChatCardComponent, IdCardComponent>();
|
||||
while (query.MoveNext(out var entityId, out var nanoChatCard, out var idCardComponent))
|
||||
{
|
||||
if (!nanoChatCard.ListNumber)
|
||||
continue;
|
||||
if (nanoChatCard.PdaUid is null)
|
||||
continue;
|
||||
if (nanoChatCard.Number is not { } nanoChatNumber)
|
||||
continue;
|
||||
if (idCardComponent.FullName is not { } fullName)
|
||||
continue;
|
||||
if (_station.GetOwningStation(entityId) != station)
|
||||
continue;
|
||||
|
||||
string? department = null;
|
||||
if (idCardComponent.JobDepartments is { } depts && depts.Count > 0)
|
||||
department = depts[0].ToString();
|
||||
|
||||
contacts.Add(new NanoChatRecipient(
|
||||
nanoChatNumber,
|
||||
fullName,
|
||||
idCardComponent.LocalizedJobTitle,
|
||||
department,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
));
|
||||
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName && _station.GetOwningStation(entityId) == station)
|
||||
{
|
||||
contacts.Add(new NanoChatRecipient(nanoChatNumber, fullName));
|
||||
}
|
||||
}
|
||||
contacts.Sort();
|
||||
contacts.Sort((contactA, contactB) => string.CompareOrdinal(contactA.Name, contactB.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -876,314 +668,4 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
listNumber);
|
||||
_cartridge.UpdateCartridgeUiState(loader, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles creation of a new group chat.
|
||||
/// </summary>
|
||||
private void HandleCreateGroupChat(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||
{
|
||||
if (msg.Content == null || card.Comp.Number == null)
|
||||
return;
|
||||
|
||||
var name = msg.Content;
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = name.Trim();
|
||||
if (name.Length > _maxNameLength)
|
||||
name = name[.._maxNameLength];
|
||||
}
|
||||
|
||||
// Generate a unique group number (surely unique I actually have no idea how to generate good unique numbers.)
|
||||
var groupNumber = (uint)(HashCode.Combine(card.Comp.Number.Value, _timing.CurTime.Ticks) & 0x7FFFFFFF);
|
||||
|
||||
// This fucking sucks (fire emoji)
|
||||
while (_nanoChat.GetRecipient((card, card.Comp), groupNumber) != null)
|
||||
{
|
||||
groupNumber++;
|
||||
}
|
||||
|
||||
// Create group chat recipient
|
||||
var recipient = new NanoChatRecipient(
|
||||
groupNumber,
|
||||
name,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
[card.Comp.Number.Value],
|
||||
card.Comp.Number.Value,
|
||||
null
|
||||
);
|
||||
|
||||
_nanoChat.SetRecipient((card, card.Comp), groupNumber, recipient);
|
||||
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} created group chat '{name}' (#{groupNumber:D4})");
|
||||
|
||||
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
||||
RaiseLocalEvent(ref recipientEv);
|
||||
UpdateUIForCard(card);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles inviting a user to a group chat.
|
||||
/// </summary>
|
||||
private void HandleInviteToGroup(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||
{
|
||||
if (msg.RecipientNumber == null || msg.Content == null || card.Comp.Number == null)
|
||||
return;
|
||||
|
||||
var groupNumber = msg.RecipientNumber.Value;
|
||||
if (!uint.TryParse(msg.Content, out var inviteeNumber))
|
||||
return;
|
||||
|
||||
var recipient = _nanoChat.GetRecipient((card, card.Comp), groupNumber);
|
||||
if (recipient == null || !recipient.Value.IsGroup)
|
||||
return;
|
||||
|
||||
// Only the creator or admins can invite
|
||||
var isCreator = recipient.Value.CreatorId == card.Comp.Number.Value;
|
||||
var admins = recipient.Value.Admins ?? new HashSet<uint>();
|
||||
var isAdmin = admins.Contains(card.Comp.Number.Value);
|
||||
|
||||
if (!isCreator && !isAdmin)
|
||||
return;
|
||||
|
||||
var members = recipient.Value.Members ?? new HashSet<uint>();
|
||||
if (members.Contains(inviteeNumber))
|
||||
return;
|
||||
|
||||
// Add member to group
|
||||
members.Add(inviteeNumber);
|
||||
var updatedRecipient = recipient.Value with { Members = members };
|
||||
_nanoChat.SetRecipient((card, card.Comp), groupNumber, updatedRecipient);
|
||||
|
||||
// Update member lists for all members
|
||||
var memberCards = new List<Entity<NanoChatCardComponent>>();
|
||||
var cardQuery = EntityQueryEnumerator<NanoChatCardComponent>();
|
||||
while (cardQuery.MoveNext(out var cardUid, out var memberCard))
|
||||
{
|
||||
if (memberCard.Number != null && members.Contains(memberCard.Number.Value))
|
||||
{
|
||||
memberCards.Add((cardUid, memberCard));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var memberCard in memberCards)
|
||||
{
|
||||
_nanoChat.SetRecipient((memberCard, memberCard.Comp), groupNumber, updatedRecipient);
|
||||
UpdateUIForCard(memberCard);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} invited #{inviteeNumber:D4} to group chat #{groupNumber:D4}");
|
||||
|
||||
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
||||
RaiseLocalEvent(ref recipientEv);
|
||||
UpdateUIForCard(card);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles kicking a user from a group chat.
|
||||
/// </summary>
|
||||
private void HandleKickFromGroup(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||
{
|
||||
if (msg.RecipientNumber == null || msg.Content == null || card.Comp.Number == null)
|
||||
return;
|
||||
|
||||
var groupNumber = msg.RecipientNumber.Value;
|
||||
if (!uint.TryParse(msg.Content, out var kickeeNumber))
|
||||
return;
|
||||
|
||||
var recipient = _nanoChat.GetRecipient((card, card.Comp), groupNumber);
|
||||
if (recipient == null || !recipient.Value.IsGroup)
|
||||
return;
|
||||
|
||||
var isCreator = recipient.Value.CreatorId == card.Comp.Number.Value;
|
||||
var admins = recipient.Value.Admins ?? new HashSet<uint>();
|
||||
var isAdmin = admins.Contains(card.Comp.Number.Value);
|
||||
var isSelfKick = kickeeNumber == card.Comp.Number.Value;
|
||||
|
||||
if (!isSelfKick && !isCreator && !isAdmin)
|
||||
return;
|
||||
|
||||
// Allow creator to leave the group but not be kicked by other members.
|
||||
var isCreatorLeaving = kickeeNumber == recipient.Value.CreatorId && isCreator;
|
||||
if (kickeeNumber == recipient.Value.CreatorId && !isCreatorLeaving)
|
||||
return;
|
||||
|
||||
// Find all cards belonging to the kickee
|
||||
var kickeeCards = new List<Entity<NanoChatCardComponent>>();
|
||||
var cardQuery = EntityQueryEnumerator<NanoChatCardComponent>();
|
||||
while (cardQuery.MoveNext(out var cardUid, out var kickeeCard))
|
||||
{
|
||||
if (kickeeCard.Number == kickeeNumber)
|
||||
{
|
||||
kickeeCards.Add((cardUid, kickeeCard));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kickeeCard in kickeeCards)
|
||||
{
|
||||
var deleteMsg = new NanoChatUiMessageEvent(NanoChatUiMessageType.DeleteChat, groupNumber, null, null)
|
||||
{
|
||||
Actor = msg.Actor
|
||||
};
|
||||
HandleDeleteChat(kickeeCard, deleteMsg);
|
||||
}
|
||||
|
||||
if (isCreatorLeaving)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} left group chat #{groupNumber:D4}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} kicked #{kickeeNumber:D4} from group chat #{groupNumber:D4}");
|
||||
}
|
||||
|
||||
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
||||
RaiseLocalEvent(ref recipientEv);
|
||||
UpdateUIForCard(card);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles promoting a user to admin in a group chat.
|
||||
/// </summary>
|
||||
private void HandleAdminUser(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||
{
|
||||
if (msg.RecipientNumber == null || msg.Content == null || card.Comp.Number == null)
|
||||
return;
|
||||
|
||||
var groupNumber = msg.RecipientNumber.Value;
|
||||
if (!uint.TryParse(msg.Content, out var targetNumber))
|
||||
return;
|
||||
|
||||
var recipient = _nanoChat.GetRecipient((card, card.Comp), groupNumber);
|
||||
if (recipient == null || !recipient.Value.IsGroup)
|
||||
return;
|
||||
|
||||
// Only the creator or admins can make admins
|
||||
var isCreator = recipient.Value.CreatorId == card.Comp.Number.Value;
|
||||
var admins = recipient.Value.Admins ?? new HashSet<uint>();
|
||||
var isAdmin = admins.Contains(card.Comp.Number.Value);
|
||||
|
||||
if (!isCreator && !isAdmin)
|
||||
return;
|
||||
|
||||
var members = recipient.Value.Members ?? new HashSet<uint>();
|
||||
if (!members.Contains(targetNumber))
|
||||
return; // how tf
|
||||
|
||||
if (targetNumber == recipient.Value.CreatorId)
|
||||
return;
|
||||
|
||||
if (!isCreator && admins.Contains(targetNumber))
|
||||
return;
|
||||
|
||||
if (!admins.Add(targetNumber))
|
||||
return;
|
||||
|
||||
var updatedRecipient = recipient.Value with { Admins = admins };
|
||||
_nanoChat.SetRecipient((card, card.Comp), groupNumber, updatedRecipient);
|
||||
|
||||
// Sync to all members
|
||||
foreach (var memberNumber in members)
|
||||
{
|
||||
var memberCards = new List<Entity<NanoChatCardComponent>>();
|
||||
var cardQuery = EntityQueryEnumerator<NanoChatCardComponent>();
|
||||
while (cardQuery.MoveNext(out var cardUid, out var memberCard))
|
||||
{
|
||||
if (memberCard.Number == memberNumber)
|
||||
{
|
||||
memberCards.Add((cardUid, memberCard));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var memberCard in memberCards)
|
||||
{
|
||||
_nanoChat.SetRecipient((memberCard, memberCard.Comp), groupNumber, updatedRecipient);
|
||||
UpdateUIForCard(memberCard);
|
||||
}
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} promoted #{targetNumber:D4} to admin in group chat #{groupNumber:D4}");
|
||||
|
||||
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
||||
RaiseLocalEvent(ref recipientEv);
|
||||
UpdateUIForCard(card);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles demoting a user from admin in a group chat.
|
||||
/// </summary>
|
||||
private void HandleDeadminUser(Entity<NanoChatCardComponent> card, NanoChatUiMessageEvent msg)
|
||||
{
|
||||
if (msg.RecipientNumber == null || msg.Content == null || card.Comp.Number == null)
|
||||
return;
|
||||
|
||||
var groupNumber = msg.RecipientNumber.Value;
|
||||
if (!uint.TryParse(msg.Content, out var targetNumber))
|
||||
return;
|
||||
|
||||
var recipient = _nanoChat.GetRecipient((card, card.Comp), groupNumber);
|
||||
if (recipient == null || !recipient.Value.IsGroup)
|
||||
return;
|
||||
|
||||
// Only the creator or admins can remove admins
|
||||
var isCreator = recipient.Value.CreatorId == card.Comp.Number.Value;
|
||||
var admins = recipient.Value.Admins ?? new HashSet<uint>();
|
||||
var isAdmin = admins.Contains(card.Comp.Number.Value);
|
||||
|
||||
if (!isCreator && !isAdmin)
|
||||
return;
|
||||
|
||||
var members = recipient.Value.Members ?? new HashSet<uint>();
|
||||
if (!members.Contains(targetNumber))
|
||||
return; // how
|
||||
|
||||
if (!isCreator && (targetNumber == recipient.Value.CreatorId || admins.Contains(targetNumber)))
|
||||
return;
|
||||
|
||||
if (!admins.Remove(targetNumber))
|
||||
return;
|
||||
|
||||
var updatedRecipient = recipient.Value with { Admins = admins };
|
||||
_nanoChat.SetRecipient((card, card.Comp), groupNumber, updatedRecipient);
|
||||
|
||||
// Sync to all members
|
||||
foreach (var memberNumber in members)
|
||||
{
|
||||
var memberCards = new List<Entity<NanoChatCardComponent>>();
|
||||
var cardQuery = EntityQueryEnumerator<NanoChatCardComponent>();
|
||||
while (cardQuery.MoveNext(out var cardUid, out var memberCard))
|
||||
{
|
||||
if (memberCard.Number == memberNumber)
|
||||
{
|
||||
memberCards.Add((cardUid, memberCard));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var memberCard in memberCards)
|
||||
{
|
||||
_nanoChat.SetRecipient((memberCard, memberCard.Comp), groupNumber, updatedRecipient);
|
||||
UpdateUIForCard(memberCard);
|
||||
}
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(msg.Actor)} removed admin from #{targetNumber:D4} in group chat #{groupNumber:D4}");
|
||||
|
||||
var recipientEv = new NanoChatRecipientUpdatedEvent(card);
|
||||
RaiseLocalEvent(ref recipientEv);
|
||||
UpdateUIForCard(card);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ public sealed class CosmicRiftSystem : EntitySystem
|
||||
|
||||
var pos = Transform(uid).Coordinates;
|
||||
Spawn(comp.PulseVFX, pos);
|
||||
_mobs.Clear();
|
||||
_lookup.GetEntitiesInRange<MobStateComponent>(pos, comp.PulseRange, _mobs);
|
||||
_mobs.RemoveWhere(target => _chaplainsQuery.HasComp(target) || _cultistsQuery.HasComp(target) || _colossiQuery.HasComp(target));
|
||||
foreach(var mob in _mobs)
|
||||
|
||||
@@ -19,6 +19,6 @@ public sealed class SiliconEmpSystem : EntitySystem
|
||||
{
|
||||
args.Cancel(); // Stop all the normal effects of the EMP
|
||||
if (args.Damage is not { } damage) return;
|
||||
_damageable.TryChangeDamage(ent, damage / 2, false); // Damage is divided by 2 because the event is raised twice (once from entity itself, and another is relayed from it's power cell) and I'm too lazy for an actual fix - NoElka | Make EMP not ignore armor.
|
||||
_damageable.TryChangeDamage(ent, damage / 2, true); // Damage is divided by 2 because the event is raised twice (once from entity itself, and another is relayed from it's power cell) and I'm too lazy for an actual fix
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Prohibits meteor swarms from targeting their impact near this component.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AntiMeteorZoneComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the area around this entity to avoid.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ZoneRadius = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Percentage (roughly) of meteors that are BLOCKED from hitting near this component.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float AvoidanceRate = 0.75f;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Prohibits meteor swarms from targeting their impact near this component.
|
||||
/// Standardized avoidance rate; cannot be changed in View Variables.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AntiMeteorZoneStandardRateComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the area around this entity to avoid.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ZoneRadius = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Percentage (roughly) of meteors that are BLOCKED from hitting near this component.
|
||||
/// Standardized avoidance rate; cannot be changed in View Variables.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)] // I tried to inherit AntiMeteorZoneComponent, but this VVAccess wouldn't take effect
|
||||
public float AvoidanceRate = 0.75f;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When fired, turns off power on the station for a few seconds, playing <see cref="EpsilonEventRuleComponent.PowerOffSound"/>
|
||||
/// Afterwards turns power back on and sets alert level to epsilon
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(EpsilonEventRule))]
|
||||
public sealed partial class EpsilonEventRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Default sound of the announcement when power turns off.
|
||||
/// </summary>
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultPowerOff = new("PowerOff");
|
||||
|
||||
/// <summary>
|
||||
/// Sound of the announcement to play when power turns off.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier PowerOffSound = new SoundCollectionSpecifier(DefaultPowerOff, AudioParams.Default.WithVolume(-2f));
|
||||
|
||||
public EntityUid AffectedStation;
|
||||
public readonly HashSet<Entity<ApcComponent>> ToggledAPCs = new();
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Content.Server._DV.StationEvents.Events;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MinorMassMindSwapRule))]
|
||||
public sealed partial class MinorMassMindSwapRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The mind swap is only temporary if true.
|
||||
/// </summary>
|
||||
[DataField("isTemporary")]
|
||||
public bool IsTemporary = false;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(60);
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier AnnouncementSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg");
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier SwapWarningSound = new SoundPathSpecifier("/Audio/_DV/Effects/clang2.ogg");
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? SoundTime;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? SwapTime;
|
||||
|
||||
[DataField]
|
||||
public int MaxNumberOfPairs = 3;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Station.Components;
|
||||
using Content.Server.AlertLevel;
|
||||
using Content.Server.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
public sealed class EpsilonEventRule : StationEventSystem<EpsilonEventRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
||||
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
|
||||
[Dependency] private readonly ServerGlobalSoundSystem _sound = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, EpsilonEventRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
// This should be checked before playing the sound
|
||||
if (!TryGetRandomStation(out var chosenStation))
|
||||
return;
|
||||
component.AffectedStation = chosenStation.Value;
|
||||
|
||||
// Plays the power off sound to the station.
|
||||
_sound.PlayGlobalOnStation(component.AffectedStation, _audio.ResolveSound(component.PowerOffSound));
|
||||
|
||||
var query = AllEntityQuery<ApcComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var apcUid, out var apc, out var transform))
|
||||
{
|
||||
|
||||
if (CompOrNull<StationMemberComponent>(transform.GridUid)?.Station != chosenStation)
|
||||
continue;
|
||||
|
||||
// Turns off APCs during the event. Basic power-outage event.
|
||||
if (apc.MainBreakerEnabled)
|
||||
{
|
||||
_apcSystem.ApcToggleBreaker(apcUid, apc);
|
||||
component.ToggledAPCs.Add((apcUid, apc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Ended(EntityUid uid, EpsilonEventRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||
{
|
||||
base.Ended(uid, component, gameRule, args);
|
||||
|
||||
// Checks if station exists.
|
||||
if (!Exists(component.AffectedStation))
|
||||
return;
|
||||
|
||||
// Restores the power to the station.
|
||||
foreach (var apc in component.ToggledAPCs)
|
||||
{
|
||||
if (Deleted(apc))
|
||||
continue;
|
||||
|
||||
if (!apc.Comp.MainBreakerEnabled)
|
||||
_apcSystem.ApcToggleBreaker(apc, apc);
|
||||
}
|
||||
component.ToggledAPCs.Clear();
|
||||
|
||||
// Final effect, sets the code to Epsilon
|
||||
if (!HasComp<AlertLevelComponent>(component.AffectedStation))
|
||||
return;
|
||||
|
||||
_alertLevelSystem.SetLevel(component.AffectedStation, "epsilon", true, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
@@ -8,7 +7,6 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Spawners;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
@@ -89,83 +87,35 @@ namespace Content.Server.StationEvents.Events
|
||||
(var target, var targetArea) = GetTarget();
|
||||
var targetSpread = (targetArea.TopRight - targetArea.Center).Length() * component.TargetingSpread;
|
||||
|
||||
var protectedAreas = new List<(MapCoordinates center, float radiusSquared, float protectionRate)>();
|
||||
|
||||
var protectedAreaQuery = AllEntityQuery<AntiMeteorZoneComponent, TransformComponent>();
|
||||
while (protectedAreaQuery.MoveNext(out var protectedEntityUid, out var antiZone, out var transform))
|
||||
{
|
||||
protectedAreas.Add((
|
||||
center: _transform.ToMapCoordinates(transform.Coordinates),
|
||||
radiusSquared: MathF.Pow(antiZone.ZoneRadius, 2f),
|
||||
protectionRate: antiZone.AvoidanceRate
|
||||
));
|
||||
}
|
||||
// dang, I wish I could use Linq on queries
|
||||
var standardRateProtectedAreaQuery = AllEntityQuery<AntiMeteorZoneStandardRateComponent, TransformComponent>();
|
||||
while (standardRateProtectedAreaQuery.MoveNext(out var protectedEntityUid, out var antiZone, out var transform))
|
||||
{
|
||||
protectedAreas.Add((
|
||||
center: _transform.ToMapCoordinates(transform.Coordinates),
|
||||
radiusSquared: MathF.Pow(antiZone.ZoneRadius, 2f),
|
||||
protectionRate: antiZone.AvoidanceRate
|
||||
));
|
||||
}
|
||||
|
||||
float maxImpactTime = 0;
|
||||
for (var i = 0; i < component.MeteorsPerWave; i++)
|
||||
{
|
||||
var protectedAreasThisMeteor =
|
||||
protectedAreas.Where(protectedArea => RobustRandom.Prob(protectedArea.protectionRate)).ToList();
|
||||
|
||||
MapCoordinates spawnPosition;
|
||||
Vector2 velocity;
|
||||
|
||||
int targetingAttempts = 0;
|
||||
bool targetingSafe;
|
||||
do
|
||||
{
|
||||
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||
spawnPosition = new MapCoordinates(
|
||||
target.X + targetSpread * (2f * RobustRandom.NextFloat() - 1f) + offset.X,
|
||||
target.Y + targetSpread * (2f * RobustRandom.NextFloat() - 1f) + offset.Y,
|
||||
mapId
|
||||
);
|
||||
velocity = -offset.Normalized() * component.MeteorVelocity;
|
||||
|
||||
targetingSafe = true;
|
||||
foreach (var protectedArea in protectedAreasThisMeteor)
|
||||
{
|
||||
var timeUntilClosestApproachToProtectedArea = TimeUntilClosestApproach(spawnPosition, velocity, protectedArea.center);
|
||||
var pointOfClosestApproachToProtectedArea = spawnPosition.Position + timeUntilClosestApproachToProtectedArea * velocity;
|
||||
if (
|
||||
Vector2.DistanceSquared(protectedArea.center.Position, pointOfClosestApproachToProtectedArea)
|
||||
< protectedArea.radiusSquared
|
||||
)
|
||||
{
|
||||
targetingSafe = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
targetingAttempts++;
|
||||
}
|
||||
while (!targetingSafe && targetingAttempts <= 3); // attempt to avoid the protected areas a few times
|
||||
|
||||
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||
var spawnPosition = new MapCoordinates(
|
||||
target.X + targetSpread * (2f * RobustRandom.NextFloat() - 1f) + offset.X,
|
||||
target.Y + targetSpread * (2f * RobustRandom.NextFloat() - 1f) + offset.Y,
|
||||
mapId
|
||||
);
|
||||
var meteor = Spawn(proto, spawnPosition);
|
||||
var physics = EntityManager.GetComponent<PhysicsComponent>(meteor);
|
||||
_physics.SetBodyStatus(meteor, physics, BodyStatus.InAir);
|
||||
_physics.SetLinearDamping(meteor, physics, 0f);
|
||||
_physics.SetAngularDamping(meteor, physics, 0f);
|
||||
var velocity = -offset.Normalized() * component.MeteorVelocity;
|
||||
_physics.ApplyLinearImpulse(meteor, velocity * physics.Mass, body: physics);
|
||||
_physics.SetAngularVelocity(meteor, (component.MaxAngularVelocity - component.MinAngularVelocity) * RobustRandom.NextFloat() + component.MinAngularVelocity, body: physics);
|
||||
|
||||
EnsureComp<TimedDespawnComponent>(meteor).Lifetime = component.MeteorLifetime;
|
||||
|
||||
var timeUntilClosestApproachToTarget = TimeUntilClosestApproach(spawnPosition, velocity, target);
|
||||
if (i == 0 || timeUntilClosestApproachToTarget > maxImpactTime)
|
||||
var relativeSpawnPosition = new Vector2(target.X - spawnPosition.X, target.Y - spawnPosition.Y);
|
||||
var timeUntilClosestApproach = (
|
||||
(relativeSpawnPosition.X * velocity.X + relativeSpawnPosition.Y * velocity.Y) /
|
||||
(MathF.Pow(velocity.X, 2f) + MathF.Pow(velocity.Y, 2f))
|
||||
);
|
||||
if (i == 0 || timeUntilClosestApproach > maxImpactTime)
|
||||
{
|
||||
maxImpactTime = timeUntilClosestApproachToTarget;
|
||||
maxImpactTime = timeUntilClosestApproach;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,14 +132,5 @@ namespace Content.Server.StationEvents.Events
|
||||
component.Cooldown += (component.MaximumCooldown - component.MinimumCooldown) * RobustRandom.NextFloat() + component.MinimumCooldown;
|
||||
}
|
||||
}
|
||||
|
||||
private static float TimeUntilClosestApproach(MapCoordinates startPoint, Vector2 velocity, MapCoordinates approachPoint)
|
||||
{
|
||||
var approachPointRelativeToStartPoint = new Vector2(approachPoint.X - startPoint.X, approachPoint.Y - startPoint.Y);
|
||||
return (
|
||||
(approachPointRelativeToStartPoint.X * velocity.X + approachPointRelativeToStartPoint.Y * velocity.Y) /
|
||||
(MathF.Pow(velocity.X, 2f) + MathF.Pow(velocity.Y, 2f))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
using Content.Server.Abilities.Psionics;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server._DV.StationEvents.Components;
|
||||
using Content.Server.Psionics;
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._DV.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Forces a mind swap on a small amount of non-insulated psionic entities.
|
||||
/// </summary>
|
||||
internal sealed class MinorMassMindSwapRule : StationEventSystem<MinorMassMindSwapRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly MindSwapPowerSystem _mindSwap = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobstateSystem = default!;
|
||||
|
||||
private TimeSpan _warningSoundLength;
|
||||
private ResolvedSoundSpecifier _resolvedWarningSound = string.Empty;
|
||||
|
||||
protected override void Started(EntityUid uid, MinorMassMindSwapRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
_resolvedWarningSound = _audio.ResolveSound(component.SwapWarningSound);
|
||||
_warningSoundLength = _audio.GetAudioLength(_resolvedWarningSound);
|
||||
|
||||
component.SwapTime = Timing.CurTime + component.Delay;
|
||||
component.SoundTime = component.SwapTime - _warningSoundLength;
|
||||
|
||||
component.MaxNumberOfPairs = component.MaxNumberOfPairs < 1 ? 1 : component.MaxNumberOfPairs;
|
||||
|
||||
var announcement = Loc.GetString("minor-mass-mind-swap-event-announcement", ("time", component.Delay.TotalSeconds));
|
||||
var sender = Loc.GetString("minor-mass-mind-swap-event-sender");
|
||||
|
||||
_chat.DispatchGlobalAnnouncement(announcement, sender, true, component.AnnouncementSound, Color.White);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<MinorMassMindSwapRuleComponent, GameRuleComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var ruleComp))
|
||||
{
|
||||
if (comp.SwapTime == null)
|
||||
continue;
|
||||
|
||||
if (comp.SoundTime != null && comp.SoundTime <= Timing.CurTime)
|
||||
{
|
||||
_audio.PlayGlobal(_resolvedWarningSound, Filter.Broadcast(), true);
|
||||
comp.SoundTime = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (comp.SwapTime > Timing.CurTime)
|
||||
continue;
|
||||
|
||||
SwapMinds(comp);
|
||||
comp.SwapTime = null;
|
||||
GameTicker.EndGameRule(uid, ruleComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void SwapMinds(MinorMassMindSwapRuleComponent component)
|
||||
{
|
||||
List<EntityUid> psionicActors = new();
|
||||
|
||||
var query = EntityQueryEnumerator<PotentialPsionicComponent, MobStateComponent>();
|
||||
while (query.MoveNext(out var psion, out _, out _))
|
||||
{
|
||||
if (_mobstateSystem.IsAlive(psion) && !HasComp<PsionicInsulationComponent>(psion)
|
||||
&& HasComp<ActorComponent>(psion))
|
||||
// Only a list of Players
|
||||
psionicActors.Add(psion);
|
||||
}
|
||||
|
||||
// We go with 4 pairs for now
|
||||
List<EntityUid> actorsToSwap = new();
|
||||
var swapPairCount = _random.Next(1, component.MaxNumberOfPairs);
|
||||
|
||||
for (; swapPairCount > 0 && psionicActors.Count > 1; swapPairCount--)
|
||||
{
|
||||
var target01 = _random.PickAndTake(psionicActors);
|
||||
var target02 = _random.PickAndTake(psionicActors);
|
||||
|
||||
_mindSwap.Swap(target01, target02);
|
||||
|
||||
if (!component.IsTemporary)
|
||||
{
|
||||
_mindSwap.GetTrapped(target01);
|
||||
_mindSwap.GetTrapped(target02);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
using Content.Shared._EE.Flight;
|
||||
|
||||
namespace Content.Server._EE.Flight;
|
||||
|
||||
public sealed class FlightSystem : SharedFlightSystem;
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared._EE.Flight; // DeltaV
|
||||
using Content.Shared._EE.FootPrint;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared._EE.FootPrint;
|
||||
// using Content.Shared.Standing;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
@@ -21,12 +21,10 @@ public sealed class FootPrintsSystem : EntitySystem
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV
|
||||
|
||||
private EntityQuery<TransformComponent> _transformQuery;
|
||||
private EntityQuery<MobThresholdsComponent> _mobThresholdQuery;
|
||||
private EntityQuery<AppearanceComponent> _appearanceQuery;
|
||||
|
||||
// private EntityQuery<LayingDownComponent> _layingQuery;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -49,9 +47,6 @@ public sealed class FootPrintsSystem : EntitySystem
|
||||
|
||||
private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (_flight.IsFlying(uid)) // DeltaV - Flying players won't make footprints
|
||||
return;
|
||||
|
||||
if (component.PrintsColor.A <= 0f
|
||||
|| !_transformQuery.TryComp(uid, out var transform)
|
||||
|| !_mobThresholdQuery.TryComp(uid, out var mobThreshHolds)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Shared._EE.Flight; // DeltaV
|
||||
using Content.Shared._EE.FootPrint;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
@@ -13,7 +12,6 @@ public sealed class PuddleFootPrintsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -23,9 +21,6 @@ public sealed class PuddleFootPrintsSystem : EntitySystem
|
||||
|
||||
private void OnStepTrigger(EntityUid uid, PuddleFootPrintsComponent component, ref EndCollideEvent args)
|
||||
{
|
||||
if (_flight.IsFlying(uid)) // DeltaV - Flying players won't make footprints
|
||||
return;
|
||||
|
||||
if (!TryComp<AppearanceComponent>(uid, out var appearance)
|
||||
|| !TryComp<PuddleComponent>(uid, out var puddle)
|
||||
|| !TryComp<FootPrintsComponent>(args.OtherEntity, out var tripper)
|
||||
|
||||
@@ -69,14 +69,12 @@ public sealed class IdExaminableSystem : EntitySystem
|
||||
{
|
||||
var jobSuffix = string.IsNullOrWhiteSpace(id.LocalizedJobTitle) ? string.Empty : $" ({id.LocalizedJobTitle})";
|
||||
|
||||
var val = FormattedMessage.EscapeText( // DeltaV - Escape job
|
||||
string.IsNullOrWhiteSpace(id.FullName)
|
||||
var val = string.IsNullOrWhiteSpace(id.FullName)
|
||||
? Loc.GetString(id.NameLocId,
|
||||
("jobSuffix", jobSuffix))
|
||||
: Loc.GetString(id.FullNameLocId,
|
||||
("fullName", id.FullName),
|
||||
("jobSuffix", jobSuffix))
|
||||
);
|
||||
("jobSuffix", jobSuffix));
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -425,11 +425,6 @@ public abstract class SharedAnomalySystem : EntitySystem
|
||||
|
||||
if (!settings.CanSpawnOnEntities)
|
||||
{
|
||||
// DeltaV - start of duplicate spawn fix (borrowed from upstream #37833)
|
||||
// If it can't spawn on entities, ensure that maximum one entity will be spawned here this pulse.
|
||||
tilerefs.Remove(tileref);
|
||||
// DeltaV - end of duplicate spawn fix (borrowed from upstream #37833)
|
||||
|
||||
var valid = true;
|
||||
foreach (var ent in _map.GetAnchoredEntities(xform.GridUid.Value, grid, tileref.GridIndices))
|
||||
{
|
||||
@@ -446,7 +441,7 @@ public abstract class SharedAnomalySystem : EntitySystem
|
||||
}
|
||||
if (!valid)
|
||||
{
|
||||
// DeltaV - duplicate spawn fix removed: tilerefs.Remove(tileref);
|
||||
tilerefs.Remove(tileref);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,7 @@ public abstract class SharedTypingIndicatorSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if(ev.State != TypingIndicatorState.Idle) // DeltaV - don't remove override when transitioning to idle
|
||||
SetTypingOverride(uid.Value, ev.OverrideIndicator); // DeltaV
|
||||
|
||||
SetTypingOverride(uid.Value, ev.OverrideIndicator); // DeltaV
|
||||
SetTypingIndicatorState(uid.Value, ev.State);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,18 +65,6 @@ public sealed partial class ChameleonClothingComponent : Component
|
||||
/// </summary>
|
||||
[AutoPausedField, DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextEmpChange = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Delta-V: Override for the name of the chameleon stuff, so you can have items use the chameleon system without being illegal.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? WindowTitleOverride;
|
||||
|
||||
/// <summary>
|
||||
/// Delta-V: Override for the name of the chameleon stuff, so you can have items use the chameleon system without being illegal.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? VerbNameOverride;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -132,7 +132,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
|
||||
|
||||
args.Verbs.Add(new InteractionVerb()
|
||||
{
|
||||
Text = Loc.GetString(ent.Comp.VerbNameOverride ?? "chameleon-component-verb-text"), // Delta-V: Override for the name of the Chameleon Verb
|
||||
Text = Loc.GetString("chameleon-component-verb-text"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
Act = () => UI.TryToggleUi(ent.Owner, ChameleonUiKey.Key, user)
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Shared._EE.Flight; // DeltaV - Harpy Flight
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
@@ -56,7 +55,6 @@ namespace Content.Shared.Cuffs
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly UseDelaySystem _delay = default!;
|
||||
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
|
||||
[Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV - Harpy flight
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -516,15 +514,6 @@ namespace Content.Shared.Cuffs
|
||||
return false;
|
||||
}
|
||||
|
||||
// EE - Harpy Flight
|
||||
if (_flight.IsFlying(target))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("handcuff-component-target-flying-error",
|
||||
("targetName", Identity.Name(target, EntityManager, user))), user, user);
|
||||
return false;
|
||||
}
|
||||
// END EE
|
||||
|
||||
var cuffTime = handcuffComponent.CuffTime;
|
||||
|
||||
if (HasComp<StunnedComponent>(target))
|
||||
|
||||
@@ -45,7 +45,6 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
||||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _status = default!;
|
||||
[Dependency] protected readonly SharedStunSystem StunSystem = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!; // EE - Harpy Flight
|
||||
|
||||
/// <summary>
|
||||
/// How much of a buffer is there between the stun duration and when stuns can be re-applied.
|
||||
@@ -266,8 +265,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
||||
}
|
||||
|
||||
public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? component = null,
|
||||
EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null, bool ignoreResist = false,
|
||||
bool? allowsSlowdown = true) // EE - Harpy Flight
|
||||
EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null, bool ignoreResist = false)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
@@ -285,9 +283,6 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
||||
|
||||
value = UniversalStaminaDamageModifier * value;
|
||||
|
||||
if (allowsSlowdown == true) // EE - Harpy Flight
|
||||
_movement.RefreshMovementSpeedModifiers(uid);
|
||||
|
||||
// Have we already reached the point of max stamina damage?
|
||||
if (component.Critical)
|
||||
return;
|
||||
@@ -367,23 +362,12 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
||||
{
|
||||
// Just in case we have active but not stamina we'll check and account for it.
|
||||
if (!stamQuery.TryGetComponent(uid, out var comp) ||
|
||||
comp.StaminaDamage <= 0f && !comp.Critical && comp.ActiveDrains.Count == 0) // EE - Harpy Flight
|
||||
comp.StaminaDamage <= 0f && !comp.Critical)
|
||||
{
|
||||
RemComp<ActiveStaminaComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
// EE - Harpy Flight
|
||||
if (comp.ActiveDrains.Count > 0)
|
||||
foreach (var (source, (drainRate, modifiesSpeed)) in comp.ActiveDrains)
|
||||
TakeStaminaDamage(uid,
|
||||
drainRate * frameTime,
|
||||
comp,
|
||||
source: source,
|
||||
visual: false,
|
||||
allowsSlowdown: modifiesSpeed);
|
||||
// End EE
|
||||
|
||||
// Shouldn't need to consider paused time as we're only iterating non-paused stamina components.
|
||||
var nextUpdate = comp.NextUpdate;
|
||||
|
||||
@@ -396,11 +380,10 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
||||
|
||||
comp.NextUpdate += TimeSpan.FromSeconds(1f);
|
||||
|
||||
if (comp.ActiveDrains.Count == 0) // EE - Harpy Flight
|
||||
TakeStaminaDamage(
|
||||
uid,
|
||||
comp.AfterCritical ? -comp.Decay * comp.AfterCritDecayMultiplier : -comp.Decay, // Recover faster after crit
|
||||
comp);
|
||||
TakeStaminaDamage(
|
||||
uid,
|
||||
comp.AfterCritical ? -comp.Decay * comp.AfterCritDecayMultiplier : -comp.Decay, // Recover faster after crit
|
||||
comp);
|
||||
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Shared._EE.Flight.Events;
|
||||
|
||||
namespace Content.Shared.Gravity;
|
||||
|
||||
/// <summary>
|
||||
/// Handles offsetting a sprite when there is no gravity
|
||||
/// </summary>
|
||||
public abstract partial class SharedFloatingVisualizerSystem : EntitySystem // DeltaV - Made Partial for Harpy Flying
|
||||
public abstract class SharedFloatingVisualizerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
|
||||
@@ -17,8 +16,6 @@ public abstract partial class SharedFloatingVisualizerSystem : EntitySystem // D
|
||||
|
||||
SubscribeLocalEvent<FloatingVisualsComponent, ComponentStartup>(OnComponentStartup);
|
||||
SubscribeLocalEvent<FloatingVisualsComponent, WeightlessnessChangedEvent>(OnWeightlessnessChanged);
|
||||
|
||||
SubscribeNetworkEvent<FlightEvent>(OnFlight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared._EE.Flight; // DeltaV - Harpy Flight
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Throwing;
|
||||
@@ -17,7 +16,6 @@ public abstract partial class SharedGravitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV - Harpy Flight
|
||||
|
||||
public static readonly ProtoId<AlertPrototype> WeightlessAlert = "Weightless";
|
||||
|
||||
@@ -71,9 +69,6 @@ public abstract partial class SharedGravitySystem : EntitySystem
|
||||
if (entity.Comp2.BodyType is BodyType.Static or BodyType.Kinematic)
|
||||
return false;
|
||||
|
||||
if (_flight.IsFlying(entity.Owner)) // DeltaV - Harpy Flight
|
||||
return true;
|
||||
|
||||
// Check if something other than the grid or map is overriding our gravity
|
||||
var ev = new IsWeightlessEvent();
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
|
||||
@@ -11,7 +11,6 @@ public abstract partial class SharedHandsSystem
|
||||
private void InitializeEventListeners()
|
||||
{
|
||||
SubscribeLocalEvent<HandsComponent, GetStandUpTimeEvent>(OnStandupArgs);
|
||||
SubscribeLocalEvent<HandsComponent, KnockedDownRefreshEvent>(OnKnockedDownRefresh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -29,27 +28,4 @@ public abstract partial class SharedHandsSystem
|
||||
|
||||
time.DoAfterTime *= (float)ent.Comp.Count / (hands + ent.Comp.Count);
|
||||
}
|
||||
|
||||
private void OnKnockedDownRefresh(Entity<HandsComponent> ent, ref KnockedDownRefreshEvent args)
|
||||
{
|
||||
var freeHands = CountFreeHands(ent.AsNullable());
|
||||
var totalHands = GetHandCount(ent.AsNullable());
|
||||
|
||||
// Can't crawl around without any hands.
|
||||
// Entities without the HandsComponent will always have full crawling speed.
|
||||
if (totalHands == 0)
|
||||
args.SpeedModifier = 0f;
|
||||
// DeltaV - 1 hand free = 75% movespeed, no hands free = 50% movespeed
|
||||
else if (totalHands > freeHands)
|
||||
{
|
||||
// For some reason, this feels a bit dirty
|
||||
// 0 free hands out of 1 total hands = (1-(1-0)*.50) = 50% additional reduction to current move speed mod
|
||||
// 1 free hand out of 2 total hands = (1-(2-1)*.25) = 75%
|
||||
// 1 free hand out of 4 total hands = (1-(4-1)*.125) = 62.5%
|
||||
// 6 free hands out of 10 total hands hands = (1-(10-6)*0.05) = 80%
|
||||
var reductionPerHand = 1f / (totalHands + totalHands);
|
||||
args.SpeedModifier *= (float)Math.Clamp(1f - (totalHands - freeHands) * reductionPerHand, 0.5, 1.0);
|
||||
}
|
||||
// END DeltaV
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,13 +108,13 @@ public sealed partial class SpeciesPrototype : IPrototype
|
||||
/// Characters younger than this appear young.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int YoungAge = 40; // DeltaV - up from 30
|
||||
public int YoungAge = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Characters older than this appear old. Characters in between young and old age appear middle aged.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int OldAge = 70; // DeltaV - up from 60
|
||||
public int OldAge = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Characters cannot be older than this. Only used for restrictions...
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed partial class LabelSystem : EntitySystem
|
||||
return;
|
||||
label ??= EnsureComp<LabelComponent>(uid);
|
||||
|
||||
label.CurrentLabel = text == null ? null : FormattedMessage.EscapeText(text);
|
||||
label.CurrentLabel = text;
|
||||
_nameModifier.RefreshNameModifiers(uid);
|
||||
|
||||
Dirty(uid, label);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Content.Shared.Movement.Components
|
||||
|
||||
// weightless
|
||||
public const float DefaultWeightlessFriction = 1f;
|
||||
public const float DefaultWeightlessModifier = 0.70f;
|
||||
public const float DefaultWeightlessModifier = 0.7f;
|
||||
public const float DefaultWeightlessAcceleration = 1f;
|
||||
|
||||
// friction
|
||||
|
||||
@@ -4,13 +4,13 @@ namespace Content.Shared.Radiation.Events;
|
||||
/// Raised on entity when it was irradiated
|
||||
/// by some radiation source.
|
||||
/// </summary>
|
||||
public readonly record struct OnIrradiatedEvent(float FrameTime, float RadsPerSecond, EntityUid? Origin)
|
||||
public readonly record struct OnIrradiatedEvent(float FrameTime, float RadsPerSecond, EntityUid Origin)
|
||||
{
|
||||
public readonly float FrameTime = FrameTime;
|
||||
|
||||
public readonly float RadsPerSecond = RadsPerSecond;
|
||||
|
||||
public readonly EntityUid? Origin = Origin;
|
||||
public readonly EntityUid Origin = Origin;
|
||||
|
||||
public float TotalRads => RadsPerSecond * FrameTime;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Movement.Events;
|
||||
@@ -54,7 +53,7 @@ public abstract partial class SharedStunSystem
|
||||
SubscribeLocalEvent<KnockedDownComponent, BuckleAttemptEvent>(OnBuckleAttempt);
|
||||
SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandAttempt);
|
||||
|
||||
// Updating movement and friction
|
||||
// Updating movement a friction
|
||||
SubscribeLocalEvent<KnockedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshKnockedSpeed);
|
||||
SubscribeLocalEvent<KnockedDownComponent, RefreshFrictionModifiersEvent>(OnRefreshFriction);
|
||||
SubscribeLocalEvent<KnockedDownComponent, TileFrictionEvent>(OnKnockedTileFriction);
|
||||
@@ -66,9 +65,6 @@ public abstract partial class SharedStunSystem
|
||||
SubscribeLocalEvent<CrawlerComponent, KnockedDownRefreshEvent>(OnKnockdownRefresh);
|
||||
SubscribeLocalEvent<CrawlerComponent, DamageChangedEvent>(OnDamaged);
|
||||
SubscribeLocalEvent<KnockedDownComponent, WeightlessnessChangedEvent>(OnWeightlessnessChanged);
|
||||
SubscribeLocalEvent<KnockedDownComponent, DidEquipHandEvent>(OnHandEquipped);
|
||||
SubscribeLocalEvent<KnockedDownComponent, DidUnequipHandEvent>(OnHandUnequipped);
|
||||
SubscribeLocalEvent<KnockedDownComponent, HandCountChangedEvent>(OnHandCountChanged);
|
||||
SubscribeLocalEvent<GravityAffectedComponent, KnockDownAttemptEvent>(OnKnockdownAttempt);
|
||||
SubscribeLocalEvent<GravityAffectedComponent, GetStandUpTimeEvent>(OnGetStandUpTime);
|
||||
|
||||
@@ -235,14 +231,7 @@ public abstract partial class SharedStunSystem
|
||||
if (playerSession.AttachedEntity is not { Valid: true } playerEnt || !Exists(playerEnt))
|
||||
return;
|
||||
|
||||
// DeltaV - Double-tap Standup bind forces standup (unless hands full)
|
||||
if (_standingState.IsDown(playerEnt)
|
||||
&& TryComp<KnockedDownComponent>(playerEnt, out var knockedDown)
|
||||
&& knockedDown.DoAfterId != null)
|
||||
ForceStandUp(playerEnt);
|
||||
else
|
||||
ToggleKnockdown(playerEnt);
|
||||
// END DeltaV
|
||||
ToggleKnockdown(playerEnt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -376,7 +365,7 @@ public abstract partial class SharedStunSystem
|
||||
|
||||
private void OnForceStandup(ForceStandUpEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity is not { } user)
|
||||
if (args.SenderSession.AttachedEntity is not {} user)
|
||||
return;
|
||||
|
||||
ForceStandUp(user);
|
||||
@@ -518,30 +507,6 @@ public abstract partial class SharedStunSystem
|
||||
RemCompDeferred<KnockedDownComponent>(entity);
|
||||
}
|
||||
|
||||
private void OnHandEquipped(Entity<KnockedDownComponent> entity, ref DidEquipHandEvent args)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return; // The result of the change is already networked separately in the same game state
|
||||
|
||||
RefreshKnockedMovement(entity);
|
||||
}
|
||||
|
||||
private void OnHandUnequipped(Entity<KnockedDownComponent> entity, ref DidUnequipHandEvent args)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return; // The result of the change is already networked separately in the same game state
|
||||
|
||||
RefreshKnockedMovement(entity);
|
||||
}
|
||||
|
||||
private void OnHandCountChanged(Entity<KnockedDownComponent> entity, ref HandCountChangedEvent args)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return; // The result of the change is already networked separately in the same game state
|
||||
|
||||
RefreshKnockedMovement(entity);
|
||||
}
|
||||
|
||||
private void OnKnockdownAttempt(Entity<GravityAffectedComponent> entity, ref KnockDownAttemptEvent args)
|
||||
{
|
||||
// Directed, targeted moth attack.
|
||||
@@ -602,7 +567,6 @@ public abstract partial class SharedStunSystem
|
||||
|
||||
ent.Comp.SpeedModifier = ev.SpeedModifier;
|
||||
ent.Comp.FrictionModifier = ev.FrictionModifier;
|
||||
Dirty(ent);
|
||||
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(ent);
|
||||
_movementSpeedModifier.RefreshFrictionModifiers(ent);
|
||||
|
||||
@@ -91,31 +91,6 @@ public sealed partial class XenoArtifactComponent : Component
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextUnlockTime;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The end time of the last-completed XenoArtifactUnlockingComponent phase.
|
||||
/// If null, no unlocking phase has occurred yet, and the other LastUnlocking... fields are also invalid.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan? LastUnlockingEndTime = null;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The set of related nodes that were triggered during the last completed XenoArtifactUnlockingComponent phase.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<int> LastUnlockingTriggeredNodeIndexesRelated = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The list of nodes that were triggered during the last completed XenoArtifactUnlockingComponent phase.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<int> LastUnlockingTriggeredNodeIndexesOrdered = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Whether the last completed unlocking phase resulted in a newly-unlocked node.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool LastUnlockingSuccessful = false;
|
||||
#endregion
|
||||
|
||||
// NOTE: you should not be accessing any of these values directly. Use the methods in SharedXenoArtifactSystem.Graph
|
||||
@@ -199,18 +174,6 @@ public sealed partial class XenoArtifactComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId<InstantActionComponent> SelfActivateAction = "ActionArtifactActivate";
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Probability for each node that its effect will be hidden in the analysis console until it is unlocked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float LockedEffectHiddenProbability = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Probability for each node that its effect will give a generic hint, rather than a specific one (until it is unlocked).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float LockedEffectVagueProbability = 0.67f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -28,30 +28,6 @@ public sealed partial class XenoArtifactNodeComponent : Component
|
||||
[DataField, AutoNetworkedField]
|
||||
public LocId? TriggerTip;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Unique effect description.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public LocId? EffectTipSpecific;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Vague effect description that may be shared by other effects.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public LocId? EffectTipVague;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Whether the effect of the node is hidden in the analysis console until unlocked.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool LockedEffectTipHidden = false;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Whether the effect of the node is described only vaguely in the analysis console until unlocked.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool LockedEffectTipVague = false;
|
||||
|
||||
/// <summary>
|
||||
/// The entity whose graph this node is a part of.
|
||||
/// </summary>
|
||||
|
||||
@@ -15,21 +15,6 @@ public sealed partial class XenoArtifactUnlockingComponent : Component
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<int> TriggeredNodeIndexes = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Ordered list of triggered nodes.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<int> TriggeredNodeIndexesOrdered = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - A subset of TriggeredNodeIndexes whose elements are "related" to eachother.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See definition of relatedness in GetRelatedNodes() of SharedXenoArtifactSystem.DV.cs
|
||||
/// </remarks
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<int> TriggeredNodeIndexesRelated = new();
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the unlocking state ends.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,12 +25,6 @@ public sealed partial class XenoArchTriggerPrototype : IPrototype
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Triggers that CANNOT appear on the same artifact as this trigger.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<ProtoId<XenoArchTriggerPrototype>>? TriggerBlacklist;
|
||||
|
||||
/// <summary>
|
||||
/// List of components that represent ways to trigger node.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System.Linq;
|
||||
using Content.Shared._DV.Xenoarchaeology.Artifact.Components; // DeltaV
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Xenoarchaeology.Artifact.Components;
|
||||
using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random; // DeltaV
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Xenoarchaeology.Artifact;
|
||||
@@ -113,17 +111,6 @@ public abstract partial class SharedXenoArtifactSystem
|
||||
var nodeComponent = nodeEnt.Value.Comp;
|
||||
nodeComponent.Depth = depth;
|
||||
nodeComponent.TriggerTip = trigger.Tip;
|
||||
// DeltaV - start of hide locked node effects
|
||||
nodeComponent.LockedEffectTipHidden = RobustRandom.Prob(ent.Comp.LockedEffectHiddenProbability);
|
||||
nodeComponent.LockedEffectTipVague = RobustRandom.Prob(ent.Comp.LockedEffectVagueProbability);
|
||||
if (TryComp<XAEDetailsComponent>(nodeEnt.Value.Owner, out var details))
|
||||
{
|
||||
nodeComponent.EffectTipSpecific = details.SpecificTip;
|
||||
nodeComponent.EffectTipVague = details.VagueTip;
|
||||
if (!details.AllowLockedEffectHiding)
|
||||
nodeComponent.LockedEffectTipHidden = false;
|
||||
}
|
||||
// DeltaV - end of hide locked node effects
|
||||
EntityManager.AddComponents(nodeEnt.Value, trigger.Components);
|
||||
|
||||
Dirty(nodeEnt.Value);
|
||||
|
||||
@@ -82,13 +82,11 @@ public abstract partial class SharedXenoArtifactSystem
|
||||
// var activated = ActivateNode((ent, artifactComponent), node.Value, null, null, Transform(ent).Coordinates, false);
|
||||
// if (activated)
|
||||
soundEffect = unlockingComponent.UnlockActivationSuccessfulSound;
|
||||
artifactComponent.LastUnlockingSuccessful = true; // DeltaV - node scanner overhaul
|
||||
}
|
||||
else
|
||||
{
|
||||
unlockAttemptResultMsg = "artifact-unlock-state-end-failure";
|
||||
soundEffect = unlockingComponent.UnlockActivationFailedSound;
|
||||
artifactComponent.LastUnlockingSuccessful = false; // DeltaV - node scanner overhaul
|
||||
}
|
||||
|
||||
if (_net.IsServer)
|
||||
@@ -97,21 +95,6 @@ public abstract partial class SharedXenoArtifactSystem
|
||||
_audio.PlayPvs(soundEffect, ent.Owner);
|
||||
}
|
||||
|
||||
// DeltaV - start of node scanner overhaul
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesOrdered.Clear();
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesRelated.Clear();
|
||||
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesOrdered
|
||||
.AddRange(unlockingComponent.TriggeredNodeIndexesOrdered);
|
||||
// one day we'll get a HashSet.AddRange
|
||||
foreach (var i in unlockingComponent.TriggeredNodeIndexesRelated)
|
||||
artifactComponent.LastUnlockingTriggeredNodeIndexesRelated.Add(i);
|
||||
|
||||
artifactComponent.LastUnlockingEndTime = unlockingComponent.EndTime;
|
||||
|
||||
Dirty(ent, artifactComponent);
|
||||
// DeltaV - end of node scanner overhaul
|
||||
|
||||
RemComp(ent, unlockingComponent);
|
||||
RaiseUnlockingFinished(ent, node);
|
||||
artifactComponent.NextUnlockTime = _timing.CurTime + artifactComponent.UnlockStateRefractory;
|
||||
|
||||
@@ -63,15 +63,6 @@ public abstract partial class SharedXenoArtifactSystem
|
||||
if (!force && _timing.CurTime < ent.Comp.NextUnlockTime)
|
||||
return;
|
||||
|
||||
// DeltaV - start of node scanner overhaul
|
||||
(Entity<XenoArtifactNodeComponent> node, int index)? parsedNode =
|
||||
(node == null)
|
||||
? null
|
||||
: (node.Value, GetIndex(ent, node.Value));
|
||||
|
||||
bool partOfRelatedTriggersSet = true;
|
||||
// DeltaV - end of node scanner overhaul
|
||||
|
||||
if (!_unlockingQuery.TryGetComponent(ent, out var unlockingComp))
|
||||
{
|
||||
unlockingComp = EnsureComp<XenoArtifactUnlockingComponent>(ent);
|
||||
@@ -82,37 +73,24 @@ public abstract partial class SharedXenoArtifactSystem
|
||||
_popup.PopupEntity(Loc.GetString("artifact-unlock-state-begin"), ent);
|
||||
Dirty(ent);
|
||||
}
|
||||
else if (parsedNode != null)
|
||||
else if (node != null)
|
||||
{
|
||||
// DeltaV - start of node scanner overhaul
|
||||
var index = GetIndex(ent, node.Value);
|
||||
|
||||
var relatedNodeIndices = GetRelatedNodes((ent, ent), parsedNode.Value.index);
|
||||
partOfRelatedTriggersSet = unlockingComp.TriggeredNodeIndexesRelated.All(x => relatedNodeIndices.Contains(x));
|
||||
|
||||
// Checking for trigger "relatedness" is a much more accurate measurement
|
||||
// of "is this locking phase going to fail" than upstream's predecessor/successor check.
|
||||
// Upstream's version of this check had edge-cases where time would not add, even though the
|
||||
// unlocking phase ends up succeeding.
|
||||
// See definition of GetRelatedNodes() for details on the concept of "relatedness".
|
||||
if (
|
||||
unlockingComp.TriggeredNodeIndexes.Count == unlockingComp.TriggeredNodeIndexesRelated.Count
|
||||
&& partOfRelatedTriggersSet
|
||||
)
|
||||
var predecessorNodeIndices = GetPredecessorNodes((ent, ent), index);
|
||||
var successorNodeIndices = GetSuccessorNodes((ent, ent), index);
|
||||
if (unlockingComp.TriggeredNodeIndexes.Count == 0
|
||||
|| unlockingComp.TriggeredNodeIndexes.All(
|
||||
x => predecessorNodeIndices.Contains(x) || successorNodeIndices.Contains(x)
|
||||
)
|
||||
)
|
||||
// we add time on each new trigger, if it is not going to fail us
|
||||
unlockingComp.EndTime += ent.Comp.UnlockStateIncrementPerNode;
|
||||
|
||||
// DeltaV - end of node scanner overhaul
|
||||
}
|
||||
|
||||
if (parsedNode != null && unlockingComp.TriggeredNodeIndexes.Add(parsedNode.Value.index))
|
||||
if (node != null && unlockingComp.TriggeredNodeIndexes.Add(GetIndex(ent, node.Value)))
|
||||
{
|
||||
// DeltaV - start of changes
|
||||
// node scanner overhaul:
|
||||
unlockingComp.TriggeredNodeIndexesOrdered.Add(parsedNode.Value.index);
|
||||
if (partOfRelatedTriggersSet)
|
||||
unlockingComp.TriggeredNodeIndexesRelated.Add(parsedNode.Value.index);
|
||||
|
||||
// faster unlock effect:
|
||||
// DeltaV - start of faster unlock effect
|
||||
if (
|
||||
ent.Comp.UnlockCompleteDuration is {} completeDuration
|
||||
&& TryGetNodeFromUnlockState((ent.Owner, unlockingComp, ent.Comp), out var unlockingNode)
|
||||
@@ -120,7 +98,7 @@ public abstract partial class SharedXenoArtifactSystem
|
||||
{
|
||||
unlockingComp.EndTime = _timing.CurTime + completeDuration;
|
||||
}
|
||||
// DeltaV - end of changes
|
||||
// DeltaV - end of faster unlock effect
|
||||
|
||||
Dirty(ent, unlockingComp);
|
||||
}
|
||||
|
||||
@@ -16,16 +16,6 @@ public sealed partial class ArtifactAnalyzerComponent : Component
|
||||
[DataField]
|
||||
public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The ratio of research points per one glimmer.
|
||||
/// </summary>
|
||||
public int ExtractRatio = 750;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The maximum added multiplier, reached at max glimmer.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PointGlimmerMultiplier = 8f;
|
||||
/// <summary>
|
||||
/// The current artifact placed on this analyzer.
|
||||
/// Can be null if none are present.
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Shared._DEN.TimedDespawn;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for a more detailed timed despawn component.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TimedDespawnDetailedComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan StartTime { get; set; } = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// How long the entity will exist, in seconds, before despawning.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Lifetime = 5f;
|
||||
|
||||
[DataField("examineText")]
|
||||
public LocId? ExamineLocId { get; set; } = "timed-despawn-holoprojection-examine";
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier? StartSound { get; set; } = new SoundPathSpecifier("/Audio/_DEN/Effects/holofan_sound_looped.ogg");
|
||||
|
||||
[DataField]
|
||||
public AudioParams StartSoundParams { get; set; } = AudioParams.Default;
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier? EndSound { get; set; }
|
||||
|
||||
[DataField]
|
||||
public AudioParams EndSoundParams { get; set; } = AudioParams.Default;
|
||||
}
|
||||
@@ -3,11 +3,4 @@ using Robust.Shared.GameStates;
|
||||
namespace Content.Shared._DV.Abilities.Psionics;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class FracturedFormBodyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity uid of the form that is currently being controlled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid ControllingForm { get; set; } = default!;
|
||||
}
|
||||
public sealed partial class FracturedFormBodyComponent : Component;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Access.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component to denote the ID card as one specific to a borg.
|
||||
/// Used to prevent agent IDs from copying access from borg IDs chips.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class PreventAgentIdComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public LocId? PopupText = "agent-id-card-cannot-copy";
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.CartridgeLoader.Cartridges;
|
||||
@@ -58,107 +57,45 @@ public enum NanoChatUiMessageType : byte
|
||||
ToggleMute,
|
||||
ToggleMuteChat,
|
||||
ToggleListNumber,
|
||||
CreateGroupChat,
|
||||
InviteToGroup,
|
||||
KickFromGroup,
|
||||
ViewGroupMembers,
|
||||
AdminUser,
|
||||
DeadminUser,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an actual NanoChat Chat
|
||||
/// Either a Group, if <paramref name="isGroup"/> is <see langword="true" />, else a normal user.
|
||||
/// </summary>
|
||||
/// <param name="number">The recipient's NanoChat number</param>
|
||||
/// <param name="name">The recipient's display name</param>
|
||||
/// <param name="jobTitle">Optional job title for the recipient</param>
|
||||
/// <param name="department">Optional department ID for the recipient</param>
|
||||
/// <param name="hasUnread">Whether there are unread messages from this recipient</param>
|
||||
/// <param name="isGroup">Whether this is a group chat</param>
|
||||
/// <param name="members">For group chats: list of member NanoChat numbers</param>
|
||||
/// <param name="creatorId">For group chats: the creator's NanoChat number</param>
|
||||
/// <param name="admins">For group chats: set of admin NanoChat numbers</param>
|
||||
// putting this here because i can
|
||||
[Serializable, NetSerializable, DataRecord]
|
||||
public struct NanoChatRecipient(
|
||||
uint number,
|
||||
string name,
|
||||
string? jobTitle,
|
||||
string? department,
|
||||
bool hasUnread,
|
||||
bool isGroup,
|
||||
HashSet<uint>? members,
|
||||
uint? creatorId,
|
||||
HashSet<uint>? admins
|
||||
) : IComparable<NanoChatRecipient>
|
||||
public struct NanoChatRecipient
|
||||
{
|
||||
/// <summary>
|
||||
/// The recipient's unique NanoChat number.
|
||||
/// </summary>
|
||||
public uint Number = number;
|
||||
public uint Number;
|
||||
|
||||
/// <summary>
|
||||
/// The recipient's display name, typically from their ID card.
|
||||
/// </summary>
|
||||
public string Name = name;
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// The recipient's job title, if available.
|
||||
/// </summary>
|
||||
public string? JobTitle = jobTitle;
|
||||
|
||||
/// <summary>
|
||||
/// The recipient's department ID, if available.
|
||||
/// </summary>
|
||||
public string? Department = department;
|
||||
public string? JobTitle;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this recipient has unread messages.
|
||||
/// </summary>
|
||||
public bool HasUnread = hasUnread;
|
||||
public bool HasUnread;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is a group chat.
|
||||
/// Creates a new NanoChat recipient.
|
||||
/// </summary>
|
||||
public bool IsGroup = isGroup;
|
||||
|
||||
/// <summary>
|
||||
/// For group chats: list of member NanoChat numbers.
|
||||
/// </summary>
|
||||
public HashSet<uint>? Members = members;
|
||||
|
||||
/// <summary>
|
||||
/// For group chats: the NanoChat number of the creator.
|
||||
/// </summary>
|
||||
public uint? CreatorId = creatorId;
|
||||
|
||||
/// <summary>
|
||||
/// For group chats: set of admin NanoChat numbers who can invite/kick.
|
||||
/// </summary>
|
||||
public HashSet<uint>? Admins = admins;
|
||||
|
||||
readonly int IComparable<NanoChatRecipient>.CompareTo(NanoChatRecipient other)
|
||||
/// <param name="number">The recipient's NanoChat number</param>
|
||||
/// <param name="name">The recipient's display name</param>
|
||||
/// <param name="jobTitle">Optional job title for the recipient</param>
|
||||
/// <param name="hasUnread">Whether there are unread messages from this recipient</param>
|
||||
public NanoChatRecipient(uint number, string name, string? jobTitle = null, bool hasUnread = false)
|
||||
{
|
||||
// Groups should go before normal users
|
||||
if (IsGroup && !other.IsGroup)
|
||||
return -1;
|
||||
else if (!IsGroup && other.IsGroup)
|
||||
return 1;
|
||||
|
||||
// FIXME: Order by Department?
|
||||
|
||||
// Order by name
|
||||
var nameCompare = string.CompareOrdinal(Name, other.Name);
|
||||
if (nameCompare != 0)
|
||||
return nameCompare;
|
||||
|
||||
// Smaller number goes first
|
||||
if (Number > other.Number)
|
||||
return 1;
|
||||
else if (Number < other.Number)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
Number = number;
|
||||
Name = name;
|
||||
JobTitle = jobTitle;
|
||||
HasUnread = hasUnread;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user