mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-16 21:17:39 +03:00
Upstream 28.02-03.03 (#268)
* More Tajaran Markings (#1834) <!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description <!-- Explain this PR in as much detail as applicable Some example prompts to consider: How might this affect the game? The codebase? What might be some alternatives to this? How/Who does this benefit/hurt [the game/codebase]? --> Description. Adds separate eye colors to Tajaran and makes most of the markings from "Fashion Update: Earrings & Makeup" available to Tajarans --- <!-- A list of everything you have to do before this PR is "complete" You probably won't have to complete everything before merging but it's good to leave future references --> --- <!-- This is default collapsed, readers click to expand it and see all your media The PR media section can get very large at times, so this is a good way to keep it clean The title is written using HTML tags The title must be within the <summary> tags or you won't see it --> <details><summary><h1>Media</h1></summary> <p>   </p> </details> --- # Changelog <!-- You can add an author after the `🆑` to change the name that appears in the changelog (ex: `🆑 Death`) Leaving it blank will default to your GitHub display name This includes all available types for the changelog --> 🆑 Tonk - add: Tajarans now have separate eye, wrist, tattoo, and makeup markings --------- Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit e45008ddf8a529c2126907ecac8ffff2a74058de) * Automatic Changelog Update (#1834) (cherry picked from commit 0091c1ebdc4bc768c0906049fa9d417d962d1839) * Cybernetics Trait Changes (#1828) # Description Changes/buffs to Cybernetic Traits. Some lesser used traits get some love, while some other stuff gets some logical re-balancing. Feel free to point out if some shitcode is broken or need explaining. --- # TODO - [ ] I got ideas cooking that I don't know how to code --- # Changelog 🆑 tweak: Striking Calluses no longer require you to be one of 3 jobs and Human. Also increased the +1 damage to +2. tweak: Bionic Spinarette SHOULD no longer have a hunger penalty and costs less. tweak: Platelet Factories heal rate buffed from 0.07 to 0.35, airloss from 0.7 to 0.25 and healing cap increased from 200 to 400. tweak: Decreased the cost of Thermal Vision to be in line with Night Vision. tweak: IPC Platelet Factories healing cap increased from 200 to 250 tweak: Cyber-Eyes Omnihud now pickable by Command too. fix: Fixed name and description of Cyber-Eyes Modules for Night Vision and Thermal Vision. remove: Mind over Machine from Cyber-Eyes Modules. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - "Striking Calluses" now delivers increased unarmed strike damage, enhancing combat performance. - **Documentation** - Trait names and descriptions have been updated for greater clarity and consistency, including changes to "Cyber-Eyes" terminology. - **Chores** - Redundant trait options were removed from the selection pool to streamline gameplay. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Raikyr0 <Kurohana@hotmail.com.au> Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit a480c6605ebdfdd32d87a9001f2aef8303433a8d) * Automatic Changelog Update (#1828) (cherry picked from commit 365dd4353a06854120e0a38ff05f193bad48bbc7) * Shadowkin Age Fixes & Plus Plushies (#1684) # Description Shadowkin middle-aged increased to 80, old age lowered to 175, max age lowered to 250. Shadowkin can now collect their goofy little plushie from a variety of sources --- # TODO - [x] Adjust Shadowkin age brackets - [x] Add Shadowkin plushie to crates and stuff --- # Changelog 🆑 ShirouAjisai - add: Added Shadowkin plushie to crates and stuff - tweak: Tweaked Shadowkin age brackets <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new "shadowkin plushie" loadout item, enhancing customization options. - Expanded the collectible pool by adding a new plushie available in multiple game areas, including reward systems and random spawners. - Enhanced the variety of items available for the `PresentRandom` entity with the addition of the "shadowkin plushie." - **Adjustments** - Refined life-stage parameters for the Shadowkin species, adjusting age thresholds to better define maturity. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: SixplyDev <einlichen@gmail.com> Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: ShirouAjisai <zaneromeave319@gmail.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit fb3d00036f6a21d7fa3c4b41341cd61b1e41e0d0) * Automatic Changelog Update (#1684) (cherry picked from commit caf8572352d38f51b15d21f0e1f92434f869dd14) * Trait Add Tag (#1846) # Description Added TraitAddTag Function, which for example can be used to add Spidercraft to the Spinerette trait. # Changelog 🆑 - add: TraitAddTag Function <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Entities now receive automatic tag assignments at spawn, enhancing the system's trait interaction and overall categorization capabilities. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Raikyr0 <Kurohana@hotmail.com.au> Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> (cherry picked from commit b1acdc4017dc1181b7f557351e82ef1df93635c2) * Automatic Changelog Update (#1846) (cherry picked from commit 9622d443d5308eda14231c3b3bb3130884465272) * Arachne SpiderCrafting (#1847) # Description Added SpiderCraft Tag to Arachne # Changelog 🆑 - add: Added SpiderCraft to Arachne <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new "SpiderCraft" classification for the Urist McArachne entity, expanding its behavior and interactions related to spider-specific mechanisms. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Raikyr0 <Kurohana@hotmail.com.au> Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> (cherry picked from commit ff4146f879d397993eee22a2a4807e986e404641) * Automatic Changelog Update (#1847) (cherry picked from commit 91d40483c2c49f86d7b2609a5ac9cd7b30d16c00) * Add Centcom Disabler (#1845) <!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description <!-- Explain this PR in as much detail as applicable Some example prompts to consider: How might this affect the game? The codebase? What might be some alternatives to this? How/Who does this benefit/hurt [the game/codebase]? --> it self recharges a bit. It's also green. It's also a steal target, because it's green. I noticed the Nanotrasen Representative has a disabler in his locker by default, but does not get to pick one in a loadout. I figured I'd remedy this, by giving him a shiny green Disabler that slightly recharges on its own. It deals the same stamina damage as the normal Disabler. The only differences are: - Green - Steal Target - Slightly higher rate of fire - Slightly recharges itself (half as slow as the antique pistol) - Admits Centcom doesn't trust you with lethals in its description --- <!-- This is default collapsed, readers click to expand it and see all your media The PR media section can get very large at times, so this is a good way to keep it clean The title is written using HTML tags The title must be within the <summary> tags or you won't see it --> <details><summary><h1>Media</h1></summary> <p>   https://github.com/user-attachments/assets/f7eaff3d-b8b9-4954-9688-fb9ef0d04588  </p> </details> --- # Changelog <!-- You can add an author after the `🆑` to change the name that appears in the changelog (ex: `🆑 Death`) Leaving it blank will default to your GitHub display name This includes all available types for the changelog --> 🆑 - add: Added CentCom disabler as loadout option for the Nanotrasen Representative. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Expanded loadout options for the Nanotrasen Representative role with a dedicated weapon configuration. - Introduced the "CentCom disabler," a new self-defense weapon option with advanced features. - Updated localization entries to reflect the new representative weapon grouping. - Added a new steal target group for the Nanotrasen representative's weapon. - Enhanced visual assets with updated animations and states for the new weapon. - Introduced new objectives related to the Nanotrasen Representative role, enhancing gameplay experiences. - Added the "Nanotrasen Representative" job title to localization. <!-- end of auto-generated comment: release notes by coderabbit.ai --> (cherry picked from commit 143d24951a200ab94f3e4e88d3a3a90eeb8856ca) * Automatic Changelog Update (#1845) (cherry picked from commit 7ca0757334ee9a1d87c9cbf1f9cc02a860ecc136) * Plant Analyzer Port (#1849) <!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description <!-- Explain this PR in as much detail as applicable Some example prompts to consider: How might this affect the game? The codebase? What might be some alternatives to this? How/Who does this benefit/hurt [the game/codebase]? --> Ported directly from https://github.com/Goob-Station/Goob-Station/pull/1685 I tweaked the sprite, and changed its usage of a Papersystem. I can't speak for the code quality, since I didn't write it, but I'm willing to fix things so long as I have the capability to do so. --- <!-- This is default collapsed, readers click to expand it and see all your media The PR media section can get very large at times, so this is a good way to keep it clean The title is written using HTML tags The title must be within the <summary> tags or you won't see it --> <details><summary><h1>Media</h1></summary> <p>    https://github.com/user-attachments/assets/0189567a-57ca-4e9d-ba0d-74e622e1d30d https://github.com/user-attachments/assets/25ea6100-1458-4804-98e4-5f70b6bfcd45 </p> </details> --- # Changelog <!-- You can add an author after the `🆑` to change the name that appears in the changelog (ex: `🆑 Death`) Leaving it blank will default to your GitHub display name This includes all available types for the changelog --> 🆑 - add: Port Plant Analyzer from botanySupremacist, who took it from ian321 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a comprehensive plant analyzer interface that displays detailed plant health, tray data, and environmental conditions. - Added an in-game report printing feature for easy access to analysis results. - **Enhancements** - Refined yield calculations and plant metabolism behaviors. - Integrated the analyzer item into crafting recipes, vending machines, and locker inventories. - Expanded localization for clearer, user-friendly plant analysis information. - Added new localization strings for printer status feedback. - Introduced new classes and messages for improved data handling and user interaction within the plant analyzer system. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Timfa <timfalken@hotmail.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> (cherry picked from commit 47a55408ad92af463159dea3325edd0c9c9611ce) * Automatic Changelog Update (#1849) (cherry picked from commit 4da1efdfd293d5df1c8bd889c621eea94ed5fed8) * Mind Role Entities (#31318) * Mind Role Entities wip * headrev count fix * silicon stuff, cleanup * exclusive antag config, cleanup * jobroleadd overwerite * logging stuff * MindHasRole cleanup, admin log stuff * last second cleanup * ocd * minor cleanup * remove createdTime datafield * now actually using the event replacement I made for role time tracking * weh (cherry picked from commit 24fae223e698b09cf9928c4a0f2f1dc774f266ab) * Fix error (cherry picked from commit d33bf89a62ae2f5d51f3af01b4ae2ef54341b5c5) * Update SharedContentIoC.cs (cherry picked from commit a50fed2fee56b57d0507a58ebf7bc13de82ad9d2) * dragon antag refactor (#28217) * remove dragon system usage of GenericAntag * add AntagRandomSpawn for making antags spawn at a random tile * add AntagSpawner to make an antag spawner just spawn an entity * add antag prototype for dragon since it never had one * make dragon spawner a GhostRoleAntagSpawner, remove GenericAntag * make dragon rule use AntagSelection and stuff * remove dragon GenericAntag rule * add back to spawn menu --------- Co-authored-by: deltanedas <@deltanedas:kde.org> (cherry picked from commit c0a56377bc5b9563de973d04f92d7d6923ca9145) * Cultist Mind Roles (cherry picked from commit 585e26103a67cc2bd185faaa468ddc5840a8e9c3) * Update midround.yml (cherry picked from commit b78d24ce6bb7f8cb4a85a89f6f974fbce1d83055) * Update ghost_roles.yml (cherry picked from commit 22df7509b5c5113afc8f1ba168223b0756de5d47) * Solarian Alliance Content (#1851) # Description This PR acts as a proper introduction to players for the Sol Aliance faction, a major antagonist group from my old home server's lore. To do so, I've ported a large number of assets from Aurora.3 to this repo, as well as created a new Midround Antagonist called "Deserters", which shows off this group to players. <details><summary><h1>Media</h1></summary> <p>    </p> </details> # Changelog 🆑 - add: A new Midround Antagonist has been added to the game. The "Solarian Deserters" are a group of highly trained soldiers who haven't been paid for far too long, whom have come to the station to loot it for everything valuable. - add: Lore guidebook entry for the Solarian Alliance, a majorly antagonistic faction. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced the "Solarian Navy Deserter" role with detailed localization, objectives, and traits. - Launched a dynamic shuttle event featuring interactive elements like secure doors, turret defenses, and specialized equipment. - Expanded gameplay with new storage options, tactical helmets, uniforms, identification cards, and door access tailored for the Sol Alliance. - Added a new NPC faction and game events enhancing combat and role-play dynamics. - Introduced various clothing items and uniforms associated with the Sol Alliance, including tactical gear and dress uniforms. - Added new metadata and structured entries for various clothing and equipment assets. - **Documentation** - Enriched lore and guidebook entries with expanded nation details, emphasizing the Solarian Alliance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> (cherry picked from commit 6d919038f3845bb4008a17e1d068196779162f4a) * Automatic Changelog Update (#1851) (cherry picked from commit ffaf99ca4b01e63f6bb98731e630f066fad25909) * Supermatter Atmos Mapping Assets (#1859) # Description This adds "High Flow" variants of all existing atmos devices, which are useful for supermatter engines. I also added the ability for FixAtmosMarkers to optionally accept a gas mixture directly, as opposed to the stupid hardcoded gas mixes that they were limited to using previously. # Changelog 🆑 - add: Added high pressure variants of atmos devices intended for supermatter engines. - add: Added engineering locked high security doors, also for use in supermatter engines. - add: Fix Atmos markers can now accept a gas mixture directly for modifying their tile. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced atmospheric commands now dynamically use specific gas mixtures for more flexible performance. - Introduced a new supermatter coolant entity, offering an alternative liquid nitrogen-like option. - Added several high-pressure and high-flow gas components, including pumps, filters, mixers, vents, and injectors. - Updated map elements by refining door access prototypes and labels for improved in-game clarity. <!-- end of auto-generated comment: release notes by coderabbit.ai --> (cherry picked from commit b9c3c8b366c15b5f09cfd641c90b09254f06de94) * Automatic Changelog Update (#1859) (cherry picked from commit 468a263863f17772e6233032e5099d6c83764616) * Rerotate Arena (#1853) <!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description <!-- Explain this PR in as much detail as applicable Some example prompts to consider: How might this affect the game? The codebase? What might be some alternatives to this? How/Who does this benefit/hurt [the game/codebase]? --> Rerotates Arena. Adds an AI satellite, and maps a few station maps, cameras, and psionic registry computers. Adds myself as the maintainer for it. Do note that I am still learning how to map. Please state any changes that is wished to be seen before it is ready to merge. It is 3AM, I need sleep. --- # TODO <!-- A list of everything you have to do before this PR is "complete" You probably won't have to complete everything before merging but it's good to leave future references --> - [x] Space cleanup - [x] Psionic Registry Computers - [x] AI Satellite & Related Stuff --- <!-- This is default collapsed, readers click to expand it and see all your media The PR media section can get very large at times, so this is a good way to keep it clean The title is written using HTML tags The title must be within the <summary> tags or you won't see it --> <details><summary><h1>Media</h1></summary> <p>  </p> </details> --- # Changelog <!-- You can add an author after the `🆑` to change the name that appears in the changelog (ex: `🆑 Death`) Leaving it blank will default to your GitHub display name This includes all available types for the changelog --> 🆑 - add: Arena is back <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - The Arena map is now reactivated with updated configurations and active maintenance. - Enhanced gameplay details and role assignments have been enabled for a more engaging experience. - The configuration for the Arena map has been fully activated, including various roles and attributes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> (cherry picked from commit 534a058eb489ceb4abaadac7e4943ed2baaa8c67) * Automatic Changelog Update (#1853) (cherry picked from commit 3b30c0a1fe8dc5e10c3cb0536e26d101893663a2) * Port Grab Intent From Goob (#1856) # Description After months, Grab intent is finally ported to EE, as a result of a 4 hour Adderall induced code binge. ## This PR is more shit than code. Required for CQC, an attempt to port that will come later. @Erisfiregamer1 requires this for [Changelings](https://github.com/Simple-Station/Einstein-Engines/pull/1855). Thanks to Gus for the Goobstation pr, and to Spatison for the original port on WWDP Tests on my local repo worked. # TODO * [ ] Await review * [ ] pain # Media  # Changelog 🆑 Eagle * add: Ported Grab Intent from Goobstation <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced pulling and grabbing interactions now feature multiple stages that impact how actions and collisions feel. - Virtual item handling during throws and drops has been refined for more dynamic in-game outcomes. - Alert visuals have been updated to provide nuanced feedback depending on the intensity of pulls and grabs. - Player movement and breathing mechanics have been fine-tuned for more realistic behavior. - New localization strings deliver clearer, context-sensitive notifications for grab-related actions. - Introduced a new component and system for managing entities thrown while grabbed, including damage handling and visual effects. - New event classes enhance interaction handling for virtual items during grabbing actions. - **Bug Fixes** - Improved logic for stopping pull actions to ignore grab states when necessary. - **Chores** - Added metadata for new textures related to alerts in the user interface. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit 18722e86f3190632026127af111dcc0d10d4af49) * Automatic Changelog Update (#1856) (cherry picked from commit 309ab74013fed2be64d9fb0457631210d860644b) * Port Role Types (#1860) Ports https://github.com/space-wizards/space-station-14/pull/33420 This is the last requirement before we can start mass-porting new antags. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced role displays in player and character interfaces with a new "Role Type" column. - Updated admin overlay options, including a classic antagonist label. - Expanded ghost role behaviors for various entities, offering more dynamic gameplay. - New localization entries for role types and UI settings for sounds and layout customization. - Added new mind roles and role types, improving role management and gameplay interactions. - Introduced new events for player spawning processes to enhance gameplay scenarios. - **Refactor** - Streamlined role management and update processes for improved reliability and performance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com> (cherry picked from commit e10c51cdb39845ed1f2bb9b08f0b226cefbd402e) * Rock And Stone <!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description <!-- Explain this PR in as much detail as applicable Some example prompts to consider: How might this affect the game? The codebase? What might be some alternatives to this? How/Who does this benefit/hurt [the game/codebase]? --> Ports Lavaland and required systems from Goobstation. --- # TODO <!-- A list of everything you have to do before this PR is "complete" You probably won't have to complete everything before merging but it's good to leave future references --> - [X] Port over _Lavaland - [x] Port over required codepatches - [-] Test locally (Should be fine) - [X] Pass tests --- <!-- This is default collapsed, readers click to expand it and see all your media The PR media section can get very large at times, so this is a good way to keep it clean The title is written using HTML tags The title must be within the <summary> tags or you won't see it --> <details><summary><h1>Media</h1></summary> <p>  </p> </details> --- # Changelog <!-- You can add an author after the `🆑` to change the name that appears in the changelog (ex: `🆑 Death`) Leaving it blank will default to your GitHub display name This includes all available types for the changelog --> 🆑 - add: Lavaland has been ported! <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a dynamic mining voucher interface allowing players to redeem various mining kits. - Enhanced shuttle docking systems with an updated console interface for smoother FTL transitions. - Added immersive boss music management for enhanced in-game boss encounters. - Expanded Lavaland gameplay with new procedural map generation, weather events, and storm scheduling. - Integrated new interactive commands and UI improvements for advanced weapon upgrades, Hierophant boss actions, and research features. - Added new components and systems for managing various gameplay elements, including damage squares, tendrils, and block charges. - Implemented new localization entries for improved player experience across various game features. - Introduced new components for managing mining vendors and vouchers, enhancing the interaction with mining kits. - Added a new system for managing the deployment of shelter capsules in the Lavaland environment. - **Tests** - Added integration tests to validate Lavaland planet generation and map initialization. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit f2f5d4610db795a124b37780230eec5d5ca0264a) * Automatic Changelog Update (#1844) (cherry picked from commit 990878b9ed60b4e22388038b63714ec2dc693bbf) * fixs * fix * fuck --------- Co-authored-by: Tonk-GCR <190437025+Tonk-GCR@users.noreply.github.com> Co-authored-by: SimpleStation Changelogs <SimpleStation14@users.noreply.github.com> Co-authored-by: Raikyr0 <kurohana@hotmail.com.au> Co-authored-by: SixplyDev <einlichen@gmail.com> Co-authored-by: Timfa <timfalken@hotmail.com> Co-authored-by: Errant <35878406+errant-4@users.noreply.github.com> Co-authored-by: sleepyyapril <123355664+sleepyyapril@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: astriloqua <129308840+astriloqua@users.noreply.github.com> Co-authored-by: Eagle-0 <114363363+Eagle-0@users.noreply.github.com> Co-authored-by: Eris <eris@erisws.com>
This commit is contained in:
@@ -1,73 +1,100 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Administration
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
internal sealed class AdminNameOverlay : Overlay
|
||||
{
|
||||
internal sealed class AdminNameOverlay : Overlay
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
private readonly AdminSystem _system;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||
private readonly Font _font;
|
||||
|
||||
//TODO make this adjustable via GUI
|
||||
private readonly ProtoId<RoleTypePrototype>[] _filter =
|
||||
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
|
||||
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
|
||||
private readonly Color _antagColorClassic = Color.OrangeRed;
|
||||
|
||||
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
|
||||
{
|
||||
private readonly AdminSystem _system;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly Font _font;
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup)
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
//TODO make this adjustable via GUI
|
||||
var classic = _config.GetCVar(CCVars.AdminOverlayClassic);
|
||||
|
||||
foreach (var playerInfo in _system.PlayerList)
|
||||
{
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
foreach (var playerInfo in _system.PlayerList)
|
||||
// Otherwise the entity can not exist yet
|
||||
if (entity == null || !_entityManager.EntityExists(entity))
|
||||
{
|
||||
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
|
||||
|
||||
// Otherwise the entity can not exist yet
|
||||
if (entity == null || !_entityManager.EntityExists(entity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// if not on the same map, continue
|
||||
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != _eyeManager.CurrentMap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var aabb = _entityLookup.GetWorldAABB(entity.Value);
|
||||
|
||||
// if not on screen, continue
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var lineoffset = new Vector2(0f, 11f);
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
if (playerInfo.Antag)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed);
|
||||
}
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if not on the same map, continue
|
||||
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var aabb = _entityLookup.GetWorldAABB(entity.Value);
|
||||
|
||||
// if not on screen, continue
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var uiScale = _userInterfaceManager.RootControl.UIScale;
|
||||
var lineoffset = new Vector2(0f, 11f) * uiScale;
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
|
||||
if (classic && playerInfo.Antag)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), _antagLabelClassic, uiScale, _antagColorClassic);
|
||||
}
|
||||
else if (!classic && _filter.Contains(playerInfo.RoleProto.ID))
|
||||
{
|
||||
var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||
var color = playerInfo.RoleProto.Color;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), label, uiScale, color);
|
||||
}
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Administration.Systems
|
||||
{
|
||||
@@ -11,6 +12,7 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly IClientAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -19,7 +21,7 @@ namespace Content.Client.Administration.Systems
|
||||
|
||||
private void InitializeOverlay()
|
||||
{
|
||||
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup);
|
||||
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager);
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
|
||||
@@ -197,6 +197,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
Header.Character => Compare(x.CharacterName, y.CharacterName),
|
||||
Header.Job => Compare(x.StartingJob, y.StartingJob),
|
||||
Header.Antagonist => x.Antag.CompareTo(y.Antag),
|
||||
Header.RoleType => Compare(x.RoleProto.Name , y.RoleProto.Name),
|
||||
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
||||
_ => 1
|
||||
};
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="RoleTypeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="OverallPlaytimeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -23,6 +23,8 @@ public sealed partial class PlayerTabEntry : PanelContainer
|
||||
if (player.IdentityName != player.CharacterName)
|
||||
CharacterLabel.Text += $" [{player.IdentityName}]";
|
||||
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
|
||||
RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
|
||||
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
|
||||
BackgroundColorPanel.PanelOverride = styleBoxFlat;
|
||||
OverallPlaytimeLabel.Text = player.PlaytimeString;
|
||||
PlayerEntity = player.NetEntity;
|
||||
|
||||
@@ -31,6 +31,14 @@
|
||||
ClipText="True"
|
||||
Text="{Loc player-tab-antagonist}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="RoleTypeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc player-tab-roletype}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="PlaytimeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -19,6 +19,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
CharacterLabel.OnKeyBindDown += CharacterClicked;
|
||||
JobLabel.OnKeyBindDown += JobClicked;
|
||||
AntagonistLabel.OnKeyBindDown += AntagonistClicked;
|
||||
RoleTypeLabel.OnKeyBindDown += RoleTypeClicked;
|
||||
PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
Header.Character => CharacterLabel,
|
||||
Header.Job => JobLabel,
|
||||
Header.Antagonist => AntagonistLabel,
|
||||
Header.RoleType => RoleTypeLabel,
|
||||
Header.Playtime => PlaytimeLabel,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
|
||||
};
|
||||
@@ -41,6 +43,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
CharacterLabel.Text = Loc.GetString("player-tab-character");
|
||||
JobLabel.Text = Loc.GetString("player-tab-job");
|
||||
AntagonistLabel.Text = Loc.GetString("player-tab-antagonist");
|
||||
RoleTypeLabel.Text = Loc.GetString("player-tab-roletype");
|
||||
PlaytimeLabel.Text = Loc.GetString("player-tab-playtime");
|
||||
}
|
||||
|
||||
@@ -75,6 +78,11 @@ public sealed partial class PlayerTabHeader : Control
|
||||
HeaderClicked(args, Header.Antagonist);
|
||||
}
|
||||
|
||||
private void RoleTypeClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.RoleType);
|
||||
}
|
||||
|
||||
private void PlaytimeClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.Playtime);
|
||||
@@ -90,6 +98,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
CharacterLabel.OnKeyBindDown -= CharacterClicked;
|
||||
JobLabel.OnKeyBindDown -= JobClicked;
|
||||
AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
|
||||
RoleTypeLabel.OnKeyBindDown -= RoleTypeClicked;
|
||||
PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
|
||||
}
|
||||
}
|
||||
@@ -100,6 +109,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
Character,
|
||||
Job,
|
||||
Antagonist,
|
||||
RoleType,
|
||||
Playtime
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Content.Shared.Botany.PlantAnalyzer;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Botany.PlantAnalyzer;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class PlantAnalyzerBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private PlantAnalyzerWindow? _window;
|
||||
|
||||
public PlantAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<PlantAnalyzerWindow>();
|
||||
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
_window.Print.OnPressed += _ => Print();
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
if (_window is null
|
||||
|| message is not PlantAnalyzerScannedUserMessage cast)
|
||||
return;
|
||||
|
||||
_window.Populate(cast);
|
||||
}
|
||||
|
||||
private void Print()
|
||||
{
|
||||
SendMessage(new PlantAnalyzerPrintMessage());
|
||||
if (_window is null)
|
||||
return;
|
||||
|
||||
_window.Print.Disabled = true;
|
||||
}
|
||||
}
|
||||
121
Content.Client/Botany/PlantAnalyzer/PlantAnalyzerWindow.xaml
Normal file
121
Content.Client/Botany/PlantAnalyzer/PlantAnalyzerWindow.xaml
Normal file
@@ -0,0 +1,121 @@
|
||||
<controls:FancyWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinWidth="320"
|
||||
MaxWidth="320">
|
||||
<ScrollContainer
|
||||
Margin="5 5 5 5"
|
||||
ReturnMeasure="True"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="False">
|
||||
<BoxContainer
|
||||
Name="RootContainer"
|
||||
VerticalExpand="True"
|
||||
Orientation="Vertical">
|
||||
<BoxContainer
|
||||
Name="DataContainer"
|
||||
Margin="0 0 0 5"
|
||||
Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
|
||||
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
|
||||
<TextureRect Name="NoDataIcon" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
|
||||
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
|
||||
<RichTextLabel Name="SeedLabel" SetWidth="150"
|
||||
Text="{Loc 'generic-unknown'}"/>
|
||||
<Label Name="ContainerLabel" VerticalAlignment="Top" StyleClasses="LabelSubText"
|
||||
Text="{Loc 'generic-unknown'}"/>
|
||||
</BoxContainer>
|
||||
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
|
||||
VerticalAlignment="Top" Name="ScanModeLabel"
|
||||
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<BoxContainer Name="PlantDataTags" Margin="5 5 0 0" Orientation="Horizontal" Visible="false">
|
||||
<RichTextLabel Margin="0 0 8 0" Name="Alive" Visible="false" Text="{Loc 'plant-analyzer-component-alive'}" />
|
||||
<RichTextLabel Margin="0 0 8 0" Name="Dead" Visible="false" Text="{Loc 'plant-analyzer-component-dead'}" />
|
||||
<RichTextLabel Margin="0 0 8 0" Name="Unviable" Visible="false" Text="{Loc 'plant-analyzer-component-unviable'}" />
|
||||
<RichTextLabel Margin="0 0 8 0" Name="Kudzu" Visible="false" Text="{Loc 'plant-analyzer-component-kudzu'}" />
|
||||
<RichTextLabel Margin="0 0 8 0" Name="Mutating" Visible="false" Text="{Loc 'plant-analyzer-component-mutating'}" />
|
||||
</BoxContainer>
|
||||
<GridContainer Name="PlantDataGrid" Margin="0 0 0 5" Columns="6" Visible="false">
|
||||
<Label Text=" · " />
|
||||
<Label Text="{Loc 'plant-analyzer-component-health'}" />
|
||||
<Label SetWidth="8" />
|
||||
<Label HorizontalAlignment="Right" Name="Health" />
|
||||
<Label Text=" / " />
|
||||
<Label HorizontalAlignment="Right" Name="Endurance" />
|
||||
<Label Text=" · " />
|
||||
<Label Text="{Loc 'plant-analyzer-component-age'}" />
|
||||
<Label SetWidth="8" />
|
||||
<Label HorizontalAlignment="Right" Name="Age" />
|
||||
<Label Text=" / " />
|
||||
<Label HorizontalAlignment="Right" Name="Lifespan" />
|
||||
</GridContainer>
|
||||
<PanelContainer Name="PlantDataDivider" Visible="false" StyleClasses="LowDivider" />
|
||||
<GridContainer Name="ContainerGrid" Margin="0 5" Columns="8" Visible="false">
|
||||
<!-- Max values from `PlantHolderSystem.CheckLevelSanity` -->
|
||||
<Label Text=" · " />
|
||||
<Label Text="{Loc 'plant-analyzer-component-water'}" />
|
||||
<Label SetWidth="8" />
|
||||
<Label FontColorOverride="cyan" HorizontalAlignment="Right" Name="WaterLevelLabel" />
|
||||
<Label Text=" / " />
|
||||
<Label HorizontalAlignment="Right" Text="100.00" />
|
||||
<Label Margin="12 0 0 0" Name="GtFieldIfTolerances1" />
|
||||
<Label HorizontalAlignment="Right" Name="WaterConsumptionLabel" />
|
||||
<Label Text=" · " />
|
||||
<Label Text="{Loc 'plant-analyzer-component-nutrition'}" />
|
||||
<Label SetWidth="8" />
|
||||
<Label FontColorOverride="orange" HorizontalAlignment="Right" Name="NutritionLevelLabel" />
|
||||
<Label Text=" / " />
|
||||
<Label HorizontalAlignment="Right" Text="100.00" />
|
||||
<Label Margin="12 0 0 0" Name="GtFieldIfTolerances2" />
|
||||
<Label HorizontalAlignment="Right" Name="NutritionConsumptionLabel" />
|
||||
<Label Text=" · " />
|
||||
<Label Text="{Loc 'plant-analyzer-component-toxins'}" />
|
||||
<Label SetWidth="8" />
|
||||
<Label FontColorOverride="yellowgreen" HorizontalAlignment="Right" Name="ToxinsLabel" />
|
||||
<Label Text=" / " />
|
||||
<Label HorizontalAlignment="Right" Text="100.00" />
|
||||
<Label Margin="12 0 0 0" Name="LtFieldIfTolerances1" />
|
||||
<Label HorizontalAlignment="Right" Name="ToxinsResistanceLabel" />
|
||||
<Label Text=" · " />
|
||||
<Label Text="{Loc 'plant-analyzer-component-pests'}" />
|
||||
<Label SetWidth="8" />
|
||||
<Label FontColorOverride="magenta" HorizontalAlignment="Right" Name="PestLevelLabel" />
|
||||
<Label Text=" / " />
|
||||
<Label HorizontalAlignment="Right" Text="10.00" />
|
||||
<Label Margin="12 0 0 0" Name="LtFieldIfTolerances2" />
|
||||
<Label HorizontalAlignment="Right" Name="PestResistanceLabel" />
|
||||
<Label Text=" · " />
|
||||
<Label Text="{Loc 'plant-analyzer-component-weeds'}" />
|
||||
<Label SetWidth="8" />
|
||||
<Label FontColorOverride="red" HorizontalAlignment="Right" Name="WeedLevelLabel" />
|
||||
<Label Text=" / " />
|
||||
<Label HorizontalAlignment="Right" Text="10.00" />
|
||||
<Label Margin="12 0 0 0" Name="LtFieldIfTolerances3" />
|
||||
<Label HorizontalAlignment="Right" Name="WeedResistanceLabel" />
|
||||
</GridContainer>
|
||||
<PanelContainer Name="ContainerDivider" Visible="false" StyleClasses="LowDivider" />
|
||||
<BoxContainer Name="ChemicalsInWaterBox" Visible="false" Orientation="Horizontal" Margin="5">
|
||||
<RichTextLabel Name="ChemicalsInWaterLabel" SetWidth="290" />
|
||||
</BoxContainer>
|
||||
<PanelContainer Name="ChemicalsInWaterDivider" Visible="false" StyleClasses="LowDivider" />
|
||||
<BoxContainer Name="EnvironmentBox" Visible="false" Orientation="Horizontal" Margin="5">
|
||||
<RichTextLabel Name="EnvironmentLabel" SetWidth="290" />
|
||||
</BoxContainer>
|
||||
<PanelContainer Name="EnvironmentDivider" Visible="false" StyleClasses="LowDivider" />
|
||||
<BoxContainer Name="ProduceBox" Visible="false" Orientation="Horizontal" Margin="5">
|
||||
<RichTextLabel Name="ProduceLabel" SetWidth="290" />
|
||||
</BoxContainer>
|
||||
<PanelContainer Name="ProduceDivider" Visible="false" StyleClasses="LowDivider" />
|
||||
<Button Name="Print"
|
||||
TextAlign="Center"
|
||||
HorizontalExpand="True"
|
||||
Access="Public"
|
||||
Disabled="True"
|
||||
Margin="0 5 0 0"
|
||||
Text="{Loc 'plant-analyzer-print'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</controls:FancyWindow>
|
||||
207
Content.Client/Botany/PlantAnalyzer/PlantAnalyzerWindow.xaml.cs
Normal file
207
Content.Client/Botany/PlantAnalyzer/PlantAnalyzerWindow.xaml.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Botany.PlantAnalyzer;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Botany.PlantAnalyzer;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlantAnalyzerWindow : FancyWindow
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly IGameTiming _gameTiming;
|
||||
|
||||
public PlantAnalyzerWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
var dependencies = IoCManager.Instance!;
|
||||
_entityManager = dependencies.Resolve<IEntityManager>();
|
||||
_prototypeManager = dependencies.Resolve<IPrototypeManager>();
|
||||
_gameTiming = dependencies.Resolve<IGameTiming>();
|
||||
}
|
||||
|
||||
public void Populate(PlantAnalyzerScannedUserMessage msg)
|
||||
{
|
||||
Print.Disabled = !msg.ScanMode.GetValueOrDefault(false)
|
||||
|| msg.PrintReadyAt.GetValueOrDefault(TimeSpan.MaxValue) > _gameTiming.CurTime
|
||||
|| msg.PlantData is null;
|
||||
|
||||
if (!_entityManager.TryGetEntity(msg.TargetEntity, out var target) || target is null)
|
||||
return;
|
||||
|
||||
// Section 1: Icon and basic information.
|
||||
SpriteView.SetEntity(target.Value);
|
||||
SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
|
||||
NoDataIcon.Visible = !SpriteView.Visible;
|
||||
|
||||
ScanModeLabel.Text = msg.ScanMode.HasValue
|
||||
? msg.ScanMode.Value
|
||||
? Loc.GetString("plant-analyzer-window-scan-mode-active")
|
||||
: Loc.GetString("plant-analyzer-window-scan-mode-inactive")
|
||||
: Loc.GetString("plant-analyzer-window-entity-unknown-text");
|
||||
ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
|
||||
|
||||
SeedLabel.Text = msg.PlantData == null
|
||||
? Loc.GetString("plant-analyzer-component-no-seed")
|
||||
: Loc.GetString(msg.PlantData.SeedDisplayName);
|
||||
|
||||
ContainerLabel.Text = _entityManager.HasComponent<MetaDataComponent>(target.Value)
|
||||
? Identity.Name(target.Value, _entityManager)
|
||||
: Loc.GetString("generic-unknown");
|
||||
|
||||
// Section 2: Information regarding the plant.
|
||||
if (msg.PlantData is not null)
|
||||
{
|
||||
Health.Text = msg.PlantData.Health.ToString("0.00");
|
||||
Endurance.Text = msg.PlantData.Endurance.ToString("0.00");
|
||||
Age.Text = msg.PlantData.Age.ToString("0.00");
|
||||
Lifespan.Text = msg.PlantData.Lifespan.ToString("0.00");
|
||||
|
||||
// These mostly exists to prevent shifting of the text.
|
||||
Dead.Visible = msg.PlantData.Dead;
|
||||
Alive.Visible = !Dead.Visible;
|
||||
|
||||
Unviable.Visible = !msg.PlantData.Viable;
|
||||
Mutating.Visible = msg.PlantData.Mutating;
|
||||
Kudzu.Visible = msg.PlantData.Kudzu;
|
||||
|
||||
PlantDataGrid.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlantDataGrid.Visible = false;
|
||||
}
|
||||
PlantDataTags.Visible = PlantDataGrid.Visible;
|
||||
PlantDataDivider.Visible = PlantDataGrid.Visible;
|
||||
|
||||
// Section 3: Input
|
||||
if (msg.TrayData is not null)
|
||||
{
|
||||
WaterLevelLabel.Text = msg.TrayData.WaterLevel.ToString("0.00");
|
||||
NutritionLevelLabel.Text = msg.TrayData.NutritionLevel.ToString("0.00");
|
||||
ToxinsLabel.Text = msg.TrayData.Toxins.ToString("0.00");
|
||||
PestLevelLabel.Text = msg.TrayData.PestLevel.ToString("0.00");
|
||||
WeedLevelLabel.Text = msg.TrayData.WeedLevel.ToString("0.00");
|
||||
|
||||
// Section 3.1: Tolerances part 1.
|
||||
if (msg.TolerancesData is not null)
|
||||
{
|
||||
GtFieldIfTolerances1.Text = ">";
|
||||
LtFieldIfTolerances1.Text = "<";
|
||||
|
||||
WaterConsumptionLabel.Text = msg.TolerancesData.WaterConsumption.ToString("0.00");
|
||||
NutritionConsumptionLabel.Text = msg.TolerancesData.NutrientConsumption.ToString("0.00");
|
||||
// Technically would be "x + epsilon" for toxin and pest.
|
||||
// But it makes no difference here since I only display two digits.
|
||||
ToxinsResistanceLabel.Text = msg.TolerancesData.ToxinsTolerance.ToString("0.00");
|
||||
PestResistanceLabel.Text = msg.TolerancesData.PestTolerance.ToString("0.00");
|
||||
WeedResistanceLabel.Text = msg.TolerancesData.WeedTolerance.ToString("0.00");
|
||||
}
|
||||
else
|
||||
{
|
||||
GtFieldIfTolerances1.Text = "";
|
||||
LtFieldIfTolerances1.Text = "";
|
||||
|
||||
WaterConsumptionLabel.Text = "";
|
||||
NutritionConsumptionLabel.Text = "";
|
||||
ToxinsResistanceLabel.Text = "";
|
||||
PestResistanceLabel.Text = "";
|
||||
WeedResistanceLabel.Text = "";
|
||||
}
|
||||
GtFieldIfTolerances2.Text = GtFieldIfTolerances1.Text;
|
||||
LtFieldIfTolerances2.Text = LtFieldIfTolerances1.Text;
|
||||
LtFieldIfTolerances3.Text = LtFieldIfTolerances1.Text;
|
||||
|
||||
ContainerGrid.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContainerGrid.Visible = false;
|
||||
}
|
||||
ContainerDivider.Visible = ContainerGrid.Visible;
|
||||
|
||||
|
||||
// Section 3.5: They are putting chemicals in the water!
|
||||
if (msg.TrayData?.Chemicals != null)
|
||||
{
|
||||
var count = msg.TrayData.Chemicals.Count;
|
||||
var holder = ContainerLabel.Text;
|
||||
var chemicals = PlantAnalyzerLocalizationHelper.ChemicalsToLocalizedStrings(msg.TrayData.Chemicals, _prototypeManager);
|
||||
if (count == 0)
|
||||
ChemicalsInWaterLabel.Text = Loc.GetString("plant-analyzer-soil-empty", ("holder", holder));
|
||||
else
|
||||
ChemicalsInWaterLabel.Text = Loc.GetString("plant-analyzer-soil", ("count", count), ("holder", holder), ("chemicals", chemicals));
|
||||
|
||||
ChemicalsInWaterBox.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ChemicalsInWaterBox.Visible = false;
|
||||
}
|
||||
ChemicalsInWaterDivider.Visible = ChemicalsInWaterBox.Visible;
|
||||
|
||||
// Section 4: Tolerances part 2.
|
||||
if (msg.TolerancesData is not null)
|
||||
{
|
||||
(string, string)[] parameters = [
|
||||
("seedName", SeedLabel.Text),
|
||||
("gases", PlantAnalyzerLocalizationHelper.GasesToLocalizedStrings(msg.TolerancesData.ConsumeGasses, _prototypeManager)),
|
||||
("kpa", msg.TolerancesData.IdealPressure.ToString("0.00")),
|
||||
("kpaTolerance", msg.TolerancesData.PressureTolerance.ToString("0.00")),
|
||||
("temp", msg.TolerancesData.IdealHeat.ToString("0.00")),
|
||||
("tempTolerance", msg.TolerancesData.HeatTolerance.ToString("0.00")),
|
||||
("lightLevel", msg.TolerancesData.IdealLight.ToString("0.00")),
|
||||
("lightTolerance", msg.TolerancesData.LightTolerance.ToString("0.00"))
|
||||
];
|
||||
EnvironmentLabel.Text = msg.TolerancesData.ConsumeGasses.Count == 0
|
||||
? msg.TolerancesData.IdealHeat - msg.TolerancesData.HeatTolerance <= 0f && msg.TolerancesData.IdealPressure - msg.TolerancesData.PressureTolerance <= 0f
|
||||
? Loc.GetString("plant-analyzer-component-environment-void", [.. parameters])
|
||||
: Loc.GetString("plant-analyzer-component-environment", [.. parameters])
|
||||
: Loc.GetString("plant-analyzer-component-environment-gas", [.. parameters]);
|
||||
|
||||
EnvironmentBox.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
EnvironmentBox.Visible = false;
|
||||
}
|
||||
EnvironmentDivider.Visible = EnvironmentBox.Visible;
|
||||
|
||||
// Section 5: Output
|
||||
if (msg.ProduceData is not null)
|
||||
{
|
||||
var gases = PlantAnalyzerLocalizationHelper.GasesToLocalizedStrings(msg.ProduceData.ExudeGasses, _prototypeManager);
|
||||
var (produce, producePlural) = PlantAnalyzerLocalizationHelper.ProduceToLocalizedStrings(msg.ProduceData.Produce, _prototypeManager);
|
||||
var chemicals = PlantAnalyzerLocalizationHelper.ChemicalsToLocalizedStrings(msg.ProduceData.Chemicals, _prototypeManager);
|
||||
|
||||
(string, object)[] parameters = [
|
||||
("yield", msg.ProduceData.Yield),
|
||||
("gasCount", msg.ProduceData.ExudeGasses.Count),
|
||||
("gases", gases),
|
||||
("potency", Loc.GetString(msg.ProduceData.Potency)),
|
||||
("seedless", msg.ProduceData.Seedless),
|
||||
("firstProduce", msg.ProduceData.Produce.FirstOrDefault() ?? ""),
|
||||
("produce", produce),
|
||||
("producePlural", producePlural),
|
||||
("chemCount", msg.ProduceData.Chemicals.Count),
|
||||
("chemicals", chemicals),
|
||||
("nothing", "")
|
||||
];
|
||||
|
||||
ProduceLabel.Text = Loc.GetString("plant-analyzer-output", [.. parameters]);
|
||||
ProduceBox.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProduceBox.Visible = false;
|
||||
}
|
||||
ProduceDivider.Visible = ProduceBox.Visible;
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,10 @@ using Content.Client.Voting;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Players.RateLimiting;
|
||||
using Content.Client.Replay;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.IoC
|
||||
{
|
||||
internal static class ClientContentIoC
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using System.Linq;
|
||||
using Content.Client.CharacterInfo;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Character.Controls;
|
||||
using Content.Client.UserInterface.Systems.Character.Windows;
|
||||
using Content.Client.UserInterface.Systems.Objectives.Controls;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
@@ -13,6 +17,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Client.CharacterInfo.CharacterInfoSystem;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
@@ -22,10 +27,25 @@ namespace Content.Client.UserInterface.Systems.Character;
|
||||
[UsedImplicitly]
|
||||
public sealed class CharacterUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CharacterInfoSystem>
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
|
||||
[UISystemDependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = _logMan.GetSawmill("character");
|
||||
|
||||
SubscribeNetworkEvent<MindRoleTypeChangedEvent>(OnRoleTypeChanged);
|
||||
}
|
||||
|
||||
private CharacterWindow? _window;
|
||||
private MenuButton? CharacterButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.CharacterButton;
|
||||
|
||||
@@ -108,6 +128,9 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
var (entity, job, objectives, briefing, entityName) = data;
|
||||
|
||||
_window.SpriteView.SetEntity(entity);
|
||||
|
||||
UpdateRoleType();
|
||||
|
||||
_window.NameLabel.Text = entityName;
|
||||
_window.SubText.Text = job;
|
||||
_window.Objectives.RemoveAllChildren();
|
||||
@@ -165,6 +188,37 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
_window.RolePlaceholder.Visible = briefing == null && !controls.Any() && !objectives.Any();
|
||||
}
|
||||
|
||||
private void OnRoleTypeChanged(MindRoleTypeChangedEvent ev, EntitySessionEventArgs _)
|
||||
{
|
||||
UpdateRoleType();
|
||||
}
|
||||
|
||||
private void UpdateRoleType()
|
||||
{
|
||||
if (_window == null || !_window.IsOpen)
|
||||
return;
|
||||
|
||||
if (!_ent.TryGetComponent<MindContainerComponent>(_player.LocalEntity, out var container)
|
||||
|| container.Mind is null)
|
||||
return;
|
||||
|
||||
if (!_ent.TryGetComponent<MindComponent>(container.Mind.Value, out var mind))
|
||||
return;
|
||||
|
||||
var roleText = Loc.GetString("role-type-crew-aligned-name");
|
||||
var color = Color.White;
|
||||
if (_prototypeManager.TryIndex(mind.RoleType, out var proto))
|
||||
{
|
||||
roleText = Loc.GetString(proto.Name);
|
||||
color = proto.Color;
|
||||
}
|
||||
else
|
||||
_sawmill.Error($"{_player.LocalEntity} has invalid Role Type '{mind.RoleType}'. Displaying '{roleText}' instead");
|
||||
|
||||
_window.RoleType.Text = roleText;
|
||||
_window.RoleType.FontColorOverride = color;
|
||||
}
|
||||
|
||||
private void CharacterDetached(EntityUid uid)
|
||||
{
|
||||
CloseWindow();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
MinHeight="545">
|
||||
<ScrollContainer>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Name="RoleType" VerticalAlignment="Top" Margin="0 6 0 10" HorizontalAlignment="Center" StyleClasses="LabelHeading" Access="Public"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64"/>
|
||||
<BoxContainer Orientation="Vertical" VerticalAlignment="Top">
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Content.Shared._DV.Salvage;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client._DV.Salvage.UI;
|
||||
|
||||
public sealed class MiningVoucherBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private MiningVoucherMenu? _menu;
|
||||
|
||||
public MiningVoucherBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<MiningVoucherMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.OnSelected += i =>
|
||||
{
|
||||
SendMessage(new MiningVoucherSelectMessage(i));
|
||||
Close();
|
||||
};
|
||||
}
|
||||
}
|
||||
6
Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml
Normal file
6
Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml
Normal file
@@ -0,0 +1,6 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
BackButtonStyleClass="RadialMenuBackButton" CloseButtonStyleClass="RadialMenuCloseButton"
|
||||
VerticalExpand="True" HorizontalExpand="True" MinSize="450 450">
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False" />
|
||||
</ui:RadialMenu>
|
||||
59
Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml.cs
Normal file
59
Content.Client/_DV/Salvage/UI/MiningVoucherMenu.xaml.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._DV.Salvage.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client._DV.Salvage.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MiningVoucherMenu : RadialMenu
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private readonly SpriteSystem _sprite;
|
||||
|
||||
public event Action<int>? OnSelected;
|
||||
|
||||
public MiningVoucherMenu()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_sprite = _entMan.System<SpriteSystem>();
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid owner)
|
||||
{
|
||||
if (!_entMan.TryGetComponent<MiningVendorComponent>(owner, out var comp))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < comp.Kits.Count; i++)
|
||||
{
|
||||
var index = i; // copy so the closure doesn't borrow it
|
||||
var kit = _proto.Index(comp.Kits[i]);
|
||||
var button = new RadialMenuTextureButton()
|
||||
{
|
||||
StyleClasses = { "RadialMenuTextureButton" },
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
ToolTip = Loc.GetString(kit.Description)
|
||||
};
|
||||
button.AddChild(new TextureRect()
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Texture = _sprite.Frame0(kit.Sprite),
|
||||
TextureScale = new Vector2(2f, 2f)
|
||||
});
|
||||
|
||||
button.OnPressed += _ => OnSelected?.Invoke(index);
|
||||
|
||||
Main.AddChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
5
Content.Client/_Lavaland/Aggression/AggressorsSystem.cs
Normal file
5
Content.Client/_Lavaland/Aggression/AggressorsSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared._Lavaland.Aggression;
|
||||
|
||||
namespace Content.Client._Lavaland.Aggression;
|
||||
|
||||
public sealed class AggressorsSystem : SharedAggressorsSystem;
|
||||
240
Content.Client/_Lavaland/Audio/BossMusicSystem.cs
Normal file
240
Content.Client/_Lavaland/Audio/BossMusicSystem.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using Content.Client.Audio;
|
||||
using Content.Shared._Lavaland.Audio;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mobs;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._Lavaland.Audio;
|
||||
|
||||
public sealed class BossMusicSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly ContentAudioSystem _audioContent = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private static float _volumeSlider;
|
||||
private Entity<AudioComponent?>? _bossMusicStream;
|
||||
private BossMusicPrototype? _musicProto;
|
||||
|
||||
// Need how much volume to change per tick and just remove it when it drops below "0"
|
||||
private readonly Dictionary<EntityUid, float> _fadingOut = new();
|
||||
|
||||
// Need volume change per tick + target volume.
|
||||
private readonly Dictionary<EntityUid, (float VolumeChange, float TargetVolume)> _fadingIn = new();
|
||||
|
||||
private readonly List<EntityUid> _fadeToRemove = new();
|
||||
|
||||
private const float MinVolume = -32f;
|
||||
private const float DefaultDuration = 2f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Subs.CVar(_configManager, CCVars.LobbyMusicVolume, BossVolumeCVarChanged, true);
|
||||
|
||||
SubscribeNetworkEvent<BossMusicStartupEvent>(OnBossInit);
|
||||
SubscribeNetworkEvent<BossMusicStopEvent>(OnBossDefeated);
|
||||
|
||||
SubscribeLocalEvent<LocalPlayerDetachedEvent>(OnMindRemoved);
|
||||
SubscribeLocalEvent<ActorComponent, MobStateChangedEvent>(OnPlayerDeath);
|
||||
SubscribeLocalEvent<ActorComponent, EntParentChangedMessage>(OnPlayerParentChange);
|
||||
SubscribeLocalEvent<RoundEndMessageEvent>(OnRoundEnd);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_bossMusicStream = _audio.Stop(_bossMusicStream);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
UpdateFade(frameTime);
|
||||
}
|
||||
|
||||
private void BossVolumeCVarChanged(float obj)
|
||||
{
|
||||
_volumeSlider = SharedAudioSystem.GainToVolume(obj);
|
||||
|
||||
if (_bossMusicStream != null && _musicProto != null)
|
||||
{
|
||||
_audio.SetVolume(_bossMusicStream, _musicProto.Sound.Params.Volume + _volumeSlider);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBossInit(BossMusicStartupEvent args)
|
||||
{
|
||||
if (_musicProto != null || _bossMusicStream != null)
|
||||
return;
|
||||
|
||||
_audioContent.DisableAmbientMusic();
|
||||
|
||||
var sound = _proto.Index(args.MusicId);
|
||||
_musicProto = sound;
|
||||
|
||||
var strim = _audio.PlayGlobal(
|
||||
sound.Sound,
|
||||
Filter.Local(),
|
||||
false,
|
||||
AudioParams.Default.WithVolume(sound.Sound.Params.Volume + _volumeSlider).WithLoop(true));
|
||||
|
||||
|
||||
if (_musicProto.FadeIn && strim != null)
|
||||
{
|
||||
_bossMusicStream = (strim.Value.Entity, strim.Value.Component);
|
||||
FadeIn(_bossMusicStream, strim.Value.Component, sound.FadeInTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBossDefeated(BossMusicStopEvent args)
|
||||
{
|
||||
EndAllMusic();
|
||||
}
|
||||
|
||||
private void OnMindRemoved(LocalPlayerDetachedEvent args)
|
||||
{
|
||||
EndAllMusic();
|
||||
}
|
||||
|
||||
private void OnPlayerDeath(Entity<ActorComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (ent.Comp.PlayerSession == _player.LocalSession &&
|
||||
args.NewMobState == MobState.Dead)
|
||||
EndAllMusic();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when salvager escapes from lavaland (ohio reference)
|
||||
/// </summary>
|
||||
private void OnPlayerParentChange(Entity<ActorComponent> ent, ref EntParentChangedMessage args)
|
||||
{
|
||||
if (ent.Comp.PlayerSession == _player.LocalSession &&
|
||||
args.OldMapId != null)
|
||||
EndAllMusic();
|
||||
}
|
||||
|
||||
private void OnRoundEnd(RoundEndMessageEvent args)
|
||||
{
|
||||
_bossMusicStream = _audio.Stop(_bossMusicStream);
|
||||
}
|
||||
|
||||
private void EndAllMusic()
|
||||
{
|
||||
if (_musicProto == null || _bossMusicStream == null)
|
||||
return;
|
||||
|
||||
if (_musicProto.FadeIn)
|
||||
{
|
||||
|
||||
FadeOut(_bossMusicStream, duration: _musicProto.FadeOutTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
_audio.Stop(_bossMusicStream);
|
||||
}
|
||||
|
||||
_musicProto = null;
|
||||
_bossMusicStream = null;
|
||||
}
|
||||
|
||||
#region Fades
|
||||
|
||||
private void FadeOut(EntityUid? stream, AudioComponent? component = null, float duration = DefaultDuration)
|
||||
{
|
||||
if (stream == null || duration <= 0f || !Resolve(stream.Value, ref component))
|
||||
return;
|
||||
|
||||
// Just in case
|
||||
// TODO: Maybe handle the removals by making it seamless?
|
||||
_fadingIn.Remove(stream.Value);
|
||||
var diff = component.Volume - MinVolume;
|
||||
_fadingOut.Add(stream.Value, diff / duration);
|
||||
}
|
||||
|
||||
private void FadeIn(EntityUid? stream, AudioComponent? component = null, float duration = DefaultDuration)
|
||||
{
|
||||
if (stream == null || duration <= 0f || !Resolve(stream.Value, ref component) || component.Volume < MinVolume)
|
||||
return;
|
||||
|
||||
_fadingOut.Remove(stream.Value);
|
||||
var curVolume = component.Volume;
|
||||
var change = (MinVolume - curVolume) / duration;
|
||||
_fadingIn.Add(stream.Value, (change, component.Volume));
|
||||
component.Volume = MinVolume;
|
||||
}
|
||||
|
||||
private void UpdateFade(float frameTime)
|
||||
{
|
||||
_fadeToRemove.Clear();
|
||||
|
||||
foreach (var (stream, change) in _fadingOut)
|
||||
{
|
||||
if (!TryComp(stream, out AudioComponent? component))
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
continue;
|
||||
}
|
||||
|
||||
var volume = component.Volume - change * frameTime;
|
||||
volume = MathF.Max(MinVolume, volume);
|
||||
_audio.SetVolume(stream, volume, component);
|
||||
|
||||
if (component.Volume.Equals(MinVolume))
|
||||
{
|
||||
_audio.Stop(stream);
|
||||
_fadeToRemove.Add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var stream in _fadeToRemove)
|
||||
{
|
||||
_fadingOut.Remove(stream);
|
||||
}
|
||||
|
||||
_fadeToRemove.Clear();
|
||||
|
||||
foreach (var (stream, (change, target)) in _fadingIn)
|
||||
{
|
||||
// Cancelled elsewhere
|
||||
if (!TryComp(stream, out AudioComponent? component))
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
continue;
|
||||
}
|
||||
|
||||
var volume = component.Volume - change * frameTime;
|
||||
volume = MathF.Min(target, volume);
|
||||
_audio.SetVolume(stream, volume, component);
|
||||
|
||||
if (component.Volume.Equals(target))
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var stream in _fadeToRemove)
|
||||
{
|
||||
_fadingIn.Remove(stream);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
5
Content.Client/_Lavaland/Mobs/DamageSquareSystem.cs
Normal file
5
Content.Client/_Lavaland/Mobs/DamageSquareSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared._Lavaland.Damage;
|
||||
|
||||
namespace Content.Client._Lavaland.Mobs;
|
||||
|
||||
public sealed class DamageSquareSystem : SharedDamageSquareSystem;
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared._Lavaland.Shuttles.Systems;
|
||||
|
||||
namespace Content.Client._Lavaland.Shuttles.Systems;
|
||||
|
||||
public sealed class DockingConsoleSystem : SharedDockingConsoleSystem;
|
||||
@@ -0,0 +1,39 @@
|
||||
using Content.Shared._Lavaland.Shuttles;
|
||||
|
||||
namespace Content.Client._Lavaland.Shuttles.UI;
|
||||
|
||||
public sealed class DockingConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private DockingConsoleWindow? _window;
|
||||
|
||||
public DockingConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new DockingConsoleWindow(Owner);
|
||||
_window.OnFTL += index => SendMessage(new DockingConsoleFTLMessage(index));
|
||||
_window.OnShuttleCall += args => SendMessage(new DockingConsoleShuttleCheckMessage());
|
||||
_window.OnClose += Close;
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (state is DockingConsoleState cast)
|
||||
_window?.UpdateState(cast);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
_window?.Orphan();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="500 500">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<ScrollContainer SetHeight="256" HorizontalExpand="True">
|
||||
<ItemList Name="Destinations"/> <!-- Populated from comp.Destinations -->
|
||||
</ScrollContainer>
|
||||
<controls:StripeBack MinSize="48 48">
|
||||
<Label Text="{Loc 'shuttle-console-ftl-label'}" VerticalExpand="True" HorizontalAlignment="Center"/>
|
||||
</controls:StripeBack>
|
||||
<Label Name="MapFTLState" Text="{Loc 'shuttle-console-ftl-state-Available'}" VerticalAlignment="Stretch" HorizontalAlignment="Center"/>
|
||||
<ProgressBar Name="FTLBar" HorizontalExpand="True" Margin="5" MinValue="0.0" MaxValue="1.0" Value="1.0" SetHeight="32"/>
|
||||
<controls:StripeBack HorizontalExpand="True">
|
||||
<Button Name="FTLButton" Text="{Loc 'docking-console-ftl'}" Disabled="False" SetSize="128 48" Margin="5" HorizontalAlignment="Left"/>
|
||||
<Button Name="ShuttleCallButton" Text="{Loc 'docking-console-call'}" Disabled="False" SetSize="128 48" Margin="5" HorizontalAlignment="Right"/>
|
||||
</controls:StripeBack>
|
||||
<Label Name="MapFTLMessage" Text="{Loc 'docking-console-ftl-message-Unknown'}" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -0,0 +1,158 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared._Lavaland.Shuttles;
|
||||
using Content.Shared._Lavaland.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._Lavaland.Shuttles.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DockingConsoleWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
private readonly AccessReaderSystem _access;
|
||||
|
||||
public event Action<int>? OnFTL;
|
||||
public event Action<bool>? OnShuttleCall;
|
||||
|
||||
private readonly EntityUid _owner;
|
||||
private readonly StyleBoxFlat _ftlStyle;
|
||||
|
||||
private FTLState _state;
|
||||
private int? _selected;
|
||||
private StartEndTime _ftlTime;
|
||||
|
||||
public DockingConsoleWindow(EntityUid owner)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_access = _entMan.System<AccessReaderSystem>();
|
||||
_owner = owner;
|
||||
|
||||
FTLButton.Disabled = false;
|
||||
ShuttleCallButton.Disabled = true;
|
||||
|
||||
_ftlStyle = new StyleBoxFlat(Color.LimeGreen);
|
||||
FTLBar.ForegroundStyleBoxOverride = _ftlStyle;
|
||||
|
||||
if (!_entMan.TryGetComponent<DockingConsoleComponent>(owner, out var comp))
|
||||
return;
|
||||
|
||||
Title = Loc.GetString(comp.WindowTitle);
|
||||
|
||||
if (!comp.HasShuttle)
|
||||
{
|
||||
MapFTLState.Text = Loc.GetString("docking-console-no-shuttle");
|
||||
_ftlStyle.BackgroundColor = Color.FromHex("#B02E26");
|
||||
|
||||
FTLButton.Disabled = true;
|
||||
ShuttleCallButton.Disabled = false;
|
||||
|
||||
ShuttleCallButton.OnPressed += _ =>
|
||||
{
|
||||
OnShuttleCall?.Invoke(true);
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Destinations.OnItemSelected += args =>
|
||||
{
|
||||
_selected = args.ItemIndex;
|
||||
UpdateButton();
|
||||
};
|
||||
|
||||
Destinations.OnItemDeselected += _ =>
|
||||
{
|
||||
_selected = null;
|
||||
UpdateButton();
|
||||
};
|
||||
|
||||
FTLButton.OnPressed += _ =>
|
||||
{
|
||||
if (_selected is {} index)
|
||||
OnFTL?.Invoke(index);
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateState(DockingConsoleState state)
|
||||
{
|
||||
_state = state.FTLState;
|
||||
_ftlTime = state.FTLTime;
|
||||
|
||||
MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}");
|
||||
_ftlStyle.BackgroundColor = Color.FromHex(_state switch
|
||||
{
|
||||
FTLState.Available => "#80C71F",
|
||||
FTLState.Starting => "#169C9C",
|
||||
FTLState.Travelling => "#8932B8",
|
||||
FTLState.Arriving => "#F9801D",
|
||||
_ => "#B02E26" // cooldown and fallback
|
||||
});
|
||||
|
||||
UpdateButton();
|
||||
|
||||
if (Destinations.Count == state.Destinations.Count)
|
||||
return;
|
||||
|
||||
Destinations.Clear();
|
||||
foreach (var dest in state.Destinations)
|
||||
{
|
||||
Destinations.AddItem(dest.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateButton()
|
||||
{
|
||||
MiningFtlState state = MiningFtlState.Unknown;
|
||||
|
||||
if (!HasAccess())
|
||||
state = MiningFtlState.NoAccess;
|
||||
else if (_selected == null)
|
||||
state = MiningFtlState.NoSelection;
|
||||
else
|
||||
{
|
||||
state = _state switch
|
||||
{
|
||||
FTLState.Available => MiningFtlState.Ready,
|
||||
FTLState.Cooldown => MiningFtlState.RechargingFtl,
|
||||
FTLState.Starting or FTLState.Travelling or FTLState.Arriving => MiningFtlState.InFtl,
|
||||
_ => state,
|
||||
};
|
||||
}
|
||||
|
||||
FTLButton.Disabled = state != MiningFtlState.Ready;
|
||||
MapFTLMessage.Text = Loc.GetString($"docking-console-ftl-message-{state.ToString()}");
|
||||
}
|
||||
|
||||
private bool HasAccess()
|
||||
{
|
||||
return _player.LocalSession?.AttachedEntity is {} player && _access.IsAllowed(player, _owner);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
var progress = _ftlTime.ProgressAt(_timing.CurTime);
|
||||
FTLBar.Value = float.IsFinite(progress) ? progress : 1;
|
||||
}
|
||||
|
||||
private enum MiningFtlState
|
||||
{
|
||||
Unknown,
|
||||
Ready,
|
||||
NoSelection,
|
||||
NoAccess,
|
||||
InFtl,
|
||||
RechargingFtl,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Shared._Lavaland.Weapons.Block;
|
||||
|
||||
namespace Content.Client._Lavaland.Weapons.Block;
|
||||
|
||||
public sealed class BlockChargeSystem : SharedBlockChargeSystem
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Shared._Lavaland.Weapons.Ranged.Upgrades;
|
||||
|
||||
namespace Content.Client._Lavaland.Weapons.Ranged.Upgrades;
|
||||
|
||||
public sealed class GunUpgradeSystem : SharedGunUpgradeSystem
|
||||
{
|
||||
|
||||
}
|
||||
26
Content.Client/_Lavaland/Weapons/WeaponAttachmentSystem.cs
Normal file
26
Content.Client/_Lavaland/Weapons/WeaponAttachmentSystem.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Content.Shared._Lavaland.Weapons;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client._Lavaland.Weapons;
|
||||
|
||||
public sealed class WeaponAttachmentSystem : SharedWeaponAttachmentSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<WeaponAttachmentComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleStateEvent);
|
||||
}
|
||||
|
||||
protected override void AddSharp(EntityUid uid) { }
|
||||
protected override void RemSharp(EntityUid uid) { }
|
||||
|
||||
private void OnAfterAutoHandleStateEvent(EntityUid uid, WeaponAttachmentComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.LayerSetVisible(WeaponVisualLayers.Bayonet, component.BayonetAttached);
|
||||
sprite.LayerSetVisible(WeaponVisualLayers.FlightOff, component.LightAttached && !component.LightOn);
|
||||
sprite.LayerSetVisible(WeaponVisualLayers.FlightOn, component.LightAttached && component.LightOn);
|
||||
}
|
||||
}
|
||||
@@ -265,7 +265,7 @@ namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
// TODO fix ninja
|
||||
// Currently ninja fails to equip their own loadout.
|
||||
if (protoId == "MobHumanSpaceNinja")
|
||||
if (protoId == "MobHumanSpaceNinja" || protoId == "LavalandHierophantTelepad") // TODO Lavaland Change: fix telepad
|
||||
continue;
|
||||
|
||||
// TODO fix tests properly upstream
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* WD edit
|
||||
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.GameTicking;
|
||||
@@ -111,11 +112,37 @@ public sealed class NukeOpsTest
|
||||
|
||||
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
|
||||
|
||||
var roles = roleSys.MindGetAllRoles(mind);
|
||||
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
|
||||
var roles = roleSys.MindGetAllRoleInfo(mind);
|
||||
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
|
||||
// The second dummy player should be a medic
|
||||
var dummyMind = mindSys.GetMind(dummyEnts[1])!.Value;
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(dummyEnts[1]));
|
||||
Assert.That(roleSys.MindIsAntagonist(dummyMind));
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
|
||||
roles = roleSys.MindGetAllRoleInfo(dummyMind);
|
||||
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
|
||||
// The other two players should have just spawned in as normal.
|
||||
CheckDummy(0);
|
||||
CheckDummy(2);
|
||||
void CheckDummy(int i)
|
||||
{
|
||||
var ent = dummyEnts[i];
|
||||
var mindCrew = mindSys.GetMind(ent)!.Value;
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
|
||||
Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
|
||||
var nukeroles = new List<string>() { "Nukeops", "NukeopsMedic", "NukeopsCommander" };
|
||||
Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
|
||||
}
|
||||
|
||||
// The game rule exists, and all the stations/shuttles/maps are properly initialized
|
||||
var rule = entMan.AllComponents<NukeopsRuleComponent>().Single().Component;
|
||||
var gridsRule = entMan.AllComponents<RuleGridsComponent>().Single().Component;
|
||||
@@ -209,13 +236,29 @@ public sealed class NukeOpsTest
|
||||
for (var tick = 0; tick < totalTicks; tick += increment)
|
||||
{
|
||||
await pair.RunTicksSync(increment);
|
||||
Assert.That(resp.SuffocationCycles, Is.LessThanOrEqualTo(resp.SuffocationCycleThreshold));
|
||||
Assert.That(resp.SuffocationCycWles, Is.LessThanOrEqualTo(resp.SuffocationCycleThreshold));
|
||||
Assert.That(damage.TotalDamage, Is.EqualTo(FixedPoint2.Zero));
|
||||
}
|
||||
|
||||
//ticker.SetGamePreset((GamePresetPrototype?)null); WD edit
|
||||
server.CfgMan.SetCVar(CCVars.GridFill, false);
|
||||
await pair.SetAntagPref("NukeopsCommander", false);
|
||||
// Check that the round does not end prematurely when agents are deleted in the outpost
|
||||
var nukies = dummyEnts.Where(entMan.HasComponent<NukeOperativeComponent>).Append(player).ToArray();
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
for (var i = 0; i < nukies.Length - 1; i++)
|
||||
{
|
||||
entMan.DeleteEntity(nukies[i]);
|
||||
Assert.That(roundEndSys.IsRoundEndRequested,
|
||||
Is.False,
|
||||
$"The round ended, but {nukies.Length - i - 1} nukies are still alive!");
|
||||
}
|
||||
// Delete the last nukie and make sure the round ends.
|
||||
entMan.DeleteEntity(nukies[^1]);
|
||||
|
||||
Assert.That(roundEndSys.IsRoundEndRequested,
|
||||
"All nukies were deleted, but the round didn't end!");
|
||||
});
|
||||
|
||||
ticker.SetGamePreset((GamePresetPrototype?) null);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
@@ -18,7 +16,6 @@ using Robust.Server.Console;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -287,27 +284,27 @@ public sealed partial class MindTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
|
||||
});
|
||||
|
||||
var traitorRole = new TraitorRoleComponent();
|
||||
var traitorRole = "MindRoleTraitor";
|
||||
|
||||
roleSystem.MindAddRole(mindId, traitorRole);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
|
||||
});
|
||||
|
||||
var jobRole = new JobComponent();
|
||||
var jobRole = "";
|
||||
|
||||
roleSystem.MindAddRole(mindId, jobRole);
|
||||
roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
|
||||
});
|
||||
|
||||
roleSystem.MindRemoveRole<TraitorRoleComponent>(mindId);
|
||||
@@ -315,15 +312,15 @@ public sealed partial class MindTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
|
||||
});
|
||||
|
||||
roleSystem.MindRemoveRole<JobComponent>(mindId);
|
||||
roleSystem.MindRemoveRole<JobRoleComponent>(mindId);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"Saltern", // Maintained by the Sin Mapping Team, ODJ, and TCJ.
|
||||
"Shoukou", // Maintained by Violet
|
||||
// "Tortuga", // De-rotated, no current maintainer.
|
||||
// "Arena", // De-rotated, no current maintainer.
|
||||
"Arena", // Maintained by astriloqua.
|
||||
// "Asterisk", // De-rotated, no current maintainer.
|
||||
"Glacier", // Maintained by Violet
|
||||
// "TheHive", // De-rotated, no current maintainer.
|
||||
@@ -66,6 +66,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"Lighthouse", // Maintained by Violet
|
||||
// "Submarine", // De-rotated, no current maintainer.
|
||||
"Gax", // Maintained by Estacao Pirata
|
||||
"Lavatest", // Lavaland Change
|
||||
"Rad", // Maintained by Estacao Pirata
|
||||
// "Europa", // De-rotated, has significant issues.
|
||||
"Meta", // Maintained by Estacao Pirata
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed class EvacShuttleTest
|
||||
var data = entMan.GetComponent<StationDataComponent>(station);
|
||||
var shuttleData = entMan.GetComponent<StationEmergencyShuttleComponent>(station);
|
||||
|
||||
var saltern = data.Grids.Single();
|
||||
var saltern = data.Grids.First(x => !entMan.HasComponent<Content.Server._Lavaland.Procedural.Components.LavalandStationComponent>(x)); // Lavaland change - ignore lavaland outpost
|
||||
Assert.That(entMan.HasComponent<MapGridComponent>(saltern));
|
||||
|
||||
var shuttle = shuttleData.EmergencyShuttle!.Value;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Linq;
|
||||
using Content.Server._Lavaland.Procedural.Components;
|
||||
using Content.Server._Lavaland.Procedural.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared._Lavaland.Procedural.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests._Lavaland;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(LavalandPlanetSystem))]
|
||||
public sealed class LavalandGenerationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task LavalandPlanetGenerationTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings { DummyTicker = false, Dirty = true, Fresh = true});
|
||||
var server = pair.Server;
|
||||
var entMan = server.EntMan;
|
||||
var protoMan = server.ProtoMan;
|
||||
var mapMan = server.MapMan;
|
||||
|
||||
var ticker = server.System<GameTicker>();
|
||||
var lavaSystem = entMan.System<LavalandPlanetSystem>();
|
||||
var mapSystem = entMan.System<SharedMapSystem>();
|
||||
|
||||
// Setup
|
||||
pair.Server.CfgMan.SetCVar(CCVars.LavalandEnabled, false);
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameDummyTicker, false);
|
||||
var gameMap = pair.Server.CfgMan.GetCVar(CCVars.GameMap);
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, "Saltern");
|
||||
|
||||
await server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.RunTicksSync(25);
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
||||
|
||||
// Get all possible types of Lavaland and test them.
|
||||
var planets = protoMan.EnumeratePrototypes<LavalandMapPrototype>().ToList();
|
||||
foreach (var planet in planets)
|
||||
{
|
||||
const int seed = 1;
|
||||
|
||||
var attempt = false;
|
||||
Entity<LavalandMapComponent>? lavaland = null;
|
||||
|
||||
// Seed is always the same to reduce randomness
|
||||
await server.WaitPost(() => attempt = lavaSystem.SetupLavalandPlanet(out lavaland, planet, seed));
|
||||
await pair.RunTicksSync(30);
|
||||
|
||||
Assert.That(lavaland, Is.Not.Null);
|
||||
|
||||
var mapId = entMan.GetComponent<TransformComponent>(lavaland.Value).MapID;
|
||||
|
||||
// Now check the basics
|
||||
Assert.That(attempt, Is.True);
|
||||
Assert.That(mapMan.MapExists(mapId));
|
||||
Assert.That(entMan.EntityExists(lavaland.Value.Owner));
|
||||
Assert.That(entMan.EntityExists(lavaland.Value.Comp.Outpost));
|
||||
Assert.That(mapMan.GetAllGrids(mapId).ToList(), Is.Not.Empty);
|
||||
Assert.That(mapSystem.IsInitialized(mapId));
|
||||
Assert.That(mapSystem.IsPaused(mapId), Is.False);
|
||||
|
||||
// Test that the biome setup is right
|
||||
var biome = entMan.GetComponent<BiomeComponent>(lavaland.Value);
|
||||
Assert.That(biome.Enabled, Is.True);
|
||||
Assert.That(biome.Seed, Is.EqualTo(seed));
|
||||
Assert.That(biome.Template, Is.Not.Null);
|
||||
Assert.That(biome.Layers, Is.Not.Empty);
|
||||
}
|
||||
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
var lavalands = lavaSystem.GetLavalands();
|
||||
Assert.That(planets, Has.Count.EqualTo(lavalands.Count));
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, gameMap);
|
||||
|
||||
pair.ClearModifiedCvars();
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
68
Content.Server/AbstractAnalyzer/AbstractAnalyzerComponent.cs
Normal file
68
Content.Server/AbstractAnalyzer/AbstractAnalyzerComponent.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.AbstractAnalyzer;
|
||||
|
||||
/// <summary>
|
||||
/// After scanning, retrieves the target Uid to use with its related UI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires <c>ItemToggleComponent</c>.
|
||||
/// </remarks>
|
||||
public abstract partial class AbstractAnalyzerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// When should the next update be sent for the patient
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Override this in your implementation with:
|
||||
///
|
||||
/// ```cs
|
||||
/// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
/// [AutoPausedField]
|
||||
/// public override TimeSpan NextUpdate { get; set; } = TimeSpan.Zero;
|
||||
/// ```
|
||||
/// </remarks>
|
||||
public abstract TimeSpan NextUpdate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The delay between patient health updates
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to scan someone.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan ScanDelay = TimeSpan.FromSeconds(0.8);
|
||||
|
||||
/// <summary>
|
||||
/// Which entity has been scanned, for continuous updates
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? ScannedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum range in tiles at which the analyzer can receive continuous updates
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MaxScanRange = 2.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played on scanning begin
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? ScanningBeginSound;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played on scanning end
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier ScanningEndSound = new SoundPathSpecifier("/Audio/Items/Medical/healthscanner.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show up the popup
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Silent;
|
||||
}
|
||||
194
Content.Server/AbstractAnalyzer/AbstractAnalyzerSystem.cs
Normal file
194
Content.Server/AbstractAnalyzer/AbstractAnalyzerSystem.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.AbstractAnalyzer;
|
||||
|
||||
public abstract class AbstractAnalyzerSystem<TAnalyzerComponent, TAnalyzerDoAfterEvent> : EntitySystem
|
||||
where TAnalyzerComponent : AbstractAnalyzerComponent
|
||||
where TAnalyzerDoAfterEvent : SimpleDoAfterEvent, new()
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly PowerCellSystem _cell = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _toggle = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<TAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<TAnalyzerComponent, TAnalyzerDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<TAnalyzerComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
|
||||
SubscribeLocalEvent<TAnalyzerComponent, ItemToggledEvent>(OnToggled);
|
||||
SubscribeLocalEvent<TAnalyzerComponent, DroppedEvent>(OnDropped);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var analyzerQuery = EntityQueryEnumerator<TAnalyzerComponent, TransformComponent>();
|
||||
while (analyzerQuery.MoveNext(out var uid, out var component, out var transform))
|
||||
{
|
||||
//Update rate limited to 1 second
|
||||
if (component.NextUpdate > _timing.CurTime
|
||||
|| component.ScannedEntity is not { } target)
|
||||
continue;
|
||||
|
||||
if (Deleted(target))
|
||||
{
|
||||
StopAnalyzingEntity((uid, component), target);
|
||||
continue;
|
||||
}
|
||||
|
||||
component.NextUpdate = _timing.CurTime + component.UpdateInterval;
|
||||
|
||||
//Get distance between analyzer and the scanned entity
|
||||
var targetCoordinates = Transform(target).Coordinates;
|
||||
if (!_transformSystem.InRange(targetCoordinates, transform.Coordinates, component.MaxScanRange))
|
||||
{
|
||||
//Range too far, disable updates
|
||||
StopAnalyzingEntity((uid, component), target);
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateScannedUser(uid, target, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger the doafter for scanning
|
||||
/// </summary>
|
||||
private void OnAfterInteract(Entity<TAnalyzerComponent> uid, ref AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target == null || !args.CanReach || !ValidScanTarget(args.Target) || !_cell.HasDrawCharge(uid, user: args.User))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(uid.Comp.ScanningBeginSound, uid);
|
||||
|
||||
var doAfterCancelled = !_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
||||
args.User, uid.Comp.ScanDelay, new TAnalyzerDoAfterEvent(), uid, target: args.Target, used: uid)
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnMove = true,
|
||||
});
|
||||
|
||||
if (args.Target == args.User || doAfterCancelled || uid.Comp.Silent || args.Target is null)
|
||||
return;
|
||||
|
||||
if (ScanTargetPopupMessage(uid, args, out var msg))
|
||||
_popupSystem.PopupEntity(msg, args.Target.Value, args.Target.Value, PopupType.Medium);
|
||||
}
|
||||
|
||||
private void OnDoAfter(Entity<TAnalyzerComponent> uid, ref TAnalyzerDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Target == null || !_cell.HasDrawCharge(uid, user: args.User))
|
||||
return;
|
||||
|
||||
if (!uid.Comp.Silent)
|
||||
_audio.PlayPvs(uid.Comp.ScanningEndSound, uid);
|
||||
|
||||
OpenUserInterface(args.User, uid);
|
||||
BeginAnalyzingEntity(uid, args.Target.Value);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turn off when placed into a storage item or moved between slots/hands
|
||||
/// </summary>
|
||||
private void OnInsertedIntoContainer(Entity<TAnalyzerComponent> uid, ref EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (uid.Comp.ScannedEntity is { })
|
||||
_toggle.TryDeactivate(uid.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable continuous updates once turned off
|
||||
/// </summary>
|
||||
private void OnToggled(Entity<TAnalyzerComponent> ent, ref ItemToggledEvent args)
|
||||
{
|
||||
if (!args.Activated && ent.Comp.ScannedEntity is { } target)
|
||||
StopAnalyzingEntity(ent, target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turn off the analyser when dropped
|
||||
/// </summary>
|
||||
private void OnDropped(Entity<TAnalyzerComponent> uid, ref DroppedEvent args)
|
||||
{
|
||||
if (uid.Comp.ScannedEntity is { })
|
||||
_toggle.TryDeactivate(uid.Owner);
|
||||
}
|
||||
|
||||
private void OpenUserInterface(EntityUid user, EntityUid analyzer)
|
||||
{
|
||||
if (!_uiSystem.HasUi(analyzer, GetUiKey()))
|
||||
return;
|
||||
|
||||
_uiSystem.OpenUi(analyzer, GetUiKey(), user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark the entity as being analyzed, and link the analyzer to it
|
||||
/// </summary>
|
||||
/// <param name="analyzer">The analyzer that should receive the updates</param>
|
||||
/// <param name="target">The entity to start analyzing</param>
|
||||
private void BeginAnalyzingEntity(Entity<TAnalyzerComponent> analyzer, EntityUid target)
|
||||
{
|
||||
//Link the analyzer to the scanned entity
|
||||
analyzer.Comp.ScannedEntity = target;
|
||||
|
||||
_toggle.TryActivate(analyzer.Owner);
|
||||
_cell.SetDrawEnabled(analyzer.Owner, true);
|
||||
|
||||
UpdateScannedUser(analyzer, target, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the analyzer from the active list, and remove the component if it has no active analyzers
|
||||
/// </summary>
|
||||
/// <param name="analyzer">The analyzer that's receiving the updates</param>
|
||||
/// <param name="target">The entity to analyze</param>
|
||||
private void StopAnalyzingEntity(Entity<TAnalyzerComponent> analyzer, EntityUid target)
|
||||
{
|
||||
//Unlink the analyzer
|
||||
analyzer.Comp.ScannedEntity = null;
|
||||
|
||||
_toggle.TryDeactivate(analyzer.Owner);
|
||||
_cell.SetDrawEnabled(analyzer.Owner, false);
|
||||
|
||||
UpdateScannedUser(analyzer, target, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an update for the target to the analyzer
|
||||
/// </summary>
|
||||
/// <param name="analyzer">The analyzer</param>
|
||||
/// <param name="target">The entity being scanned</param>
|
||||
/// <param name="scanMode">True makes the UI show ACTIVE, False makes the UI show INACTIVE</param>
|
||||
public abstract void UpdateScannedUser(EntityUid analyzer, EntityUid target, bool scanMode);
|
||||
|
||||
/// <returns>A <see cref="Robust.Shared.Serialization.NetSerializableAttribute"/> byte enum key.</returns>
|
||||
protected abstract Enum GetUiKey();
|
||||
|
||||
/// <summary>
|
||||
/// The message the scan target receives on scan.
|
||||
/// </summary>
|
||||
/// <returns>true if the message should be shown</returns>
|
||||
protected abstract bool ScanTargetPopupMessage(Entity<TAnalyzerComponent> uid, AfterInteractEvent args, [NotNullWhen(true)] out string? message);
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate if a specific entity is a valid target for a specific analyzer.
|
||||
/// </summary>
|
||||
protected abstract bool ValidScanTarget(EntityUid? target);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Popups;
|
||||
@@ -16,6 +15,7 @@ using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Popups;
|
||||
@@ -31,6 +31,7 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Systems
|
||||
{
|
||||
@@ -52,6 +53,7 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
|
||||
private readonly Dictionary<NetUserId, PlayerInfo> _playerList = new();
|
||||
|
||||
@@ -156,12 +158,14 @@ namespace Content.Server.Administration.Systems
|
||||
private void OnRoleEvent(RoleEvent ev)
|
||||
{
|
||||
var session = _minds.GetSession(ev.Mind);
|
||||
if (!ev.Antagonist || session == null)
|
||||
|
||||
if (!ev.RoleTypeUpdate || session == null)
|
||||
return;
|
||||
|
||||
UpdatePlayerList(session);
|
||||
}
|
||||
|
||||
|
||||
private void OnAdminPermsChanged(AdminPermsChangedEventArgs obj)
|
||||
{
|
||||
UpdatePanicBunker();
|
||||
@@ -216,7 +220,7 @@ namespace Content.Server.Administration.Systems
|
||||
RaiseNetworkEvent(ev, playerSession.Channel);
|
||||
}
|
||||
|
||||
private PlayerInfo GetPlayerInfo(SessionData data, ICommonSession session)
|
||||
private PlayerInfo GetPlayerInfo(SessionData data, ICommonSession? session)
|
||||
{
|
||||
var name = data.UserName;
|
||||
var entityName = string.Empty;
|
||||
@@ -229,9 +233,16 @@ namespace Content.Server.Administration.Systems
|
||||
}
|
||||
|
||||
var antag = false;
|
||||
|
||||
RoleTypePrototype roleType = new();
|
||||
var startingRole = string.Empty;
|
||||
if (_minds.TryGetMind(session, out var mindId, out _))
|
||||
if (_minds.TryGetMind(session, out var mindId, out var mindComp))
|
||||
{
|
||||
if (_protoMan.TryIndex(mindComp.RoleType, out var role))
|
||||
roleType = role;
|
||||
else
|
||||
Log.Error($"{ToPrettyString(mindId)} has invalid Role Type '{mindComp.RoleType}'. Displaying '{Loc.GetString(roleType.Name)}' instead");
|
||||
|
||||
antag = _role.MindIsAntagonist(mindId);
|
||||
startingRole = _jobs.MindTryGetJobName(mindId);
|
||||
}
|
||||
@@ -245,7 +256,7 @@ namespace Content.Server.Administration.Systems
|
||||
overallPlaytime = playTime;
|
||||
}
|
||||
|
||||
return new PlayerInfo(name, entityName, identityName, startingRole, antag, GetNetEntity(session?.AttachedEntity), data.UserId,
|
||||
return new PlayerInfo(name, entityName, identityName, startingRole, antag, roleType, GetNetEntity(session?.AttachedEntity), data.UserId,
|
||||
connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Content.Server.Alert.Click
|
||||
if (entManager.TryGetComponent(player, out PullerComponent? puller) &&
|
||||
entManager.TryGetComponent(puller.Pulling, out PullableComponent? pullableComp))
|
||||
{
|
||||
ps.TryStopPull(puller.Pulling.Value, pullableComp, user: player);
|
||||
ps.TryLowerGrabStage(puller.Pulling.Value, player, true); // Spacious Edit, kill me now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
Content.Server/Antag/AntagRandomSpawnRule.cs
Normal file
22
Content.Server/Antag/AntagRandomSpawnRule.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Content.Server.Antag.Components;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
|
||||
namespace Content.Server.Antag;
|
||||
|
||||
public sealed class AntagRandomSpawnSystem : GameRuleSystem<AntagRandomSpawnComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AntagRandomSpawnComponent, AntagSelectLocationEvent>(OnSelectLocation);
|
||||
}
|
||||
|
||||
private void OnSelectLocation(Entity<AntagRandomSpawnComponent> ent, ref AntagSelectLocationEvent args)
|
||||
{
|
||||
if (TryFindRandomTile(out _, out _, out _, out var coords))
|
||||
args.Coordinates.Add(_transform.ToMapCoordinates(coords));
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,16 @@ using Content.Server.Objectives;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Roles.Jobs;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.Antag;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -32,13 +34,13 @@ namespace Content.Server.Antag;
|
||||
|
||||
public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelectionComponent>
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IServerPreferencesManager _pref = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly GhostRoleSystem _ghostRole = default!;
|
||||
[Dependency] private readonly JobSystem _jobs = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IServerPreferencesManager _pref = default!;
|
||||
[Dependency] private readonly RoleSystem _role = default!;
|
||||
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
@@ -188,7 +190,10 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// <summary>
|
||||
/// Chooses antagonists from the given selection of players
|
||||
/// </summary>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool)
|
||||
/// <param name="ent">The antagonist rule entity</param>
|
||||
/// <param name="pool">The players to choose from</param>
|
||||
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false)
|
||||
{
|
||||
if (ent.Comp.SelectionsComplete)
|
||||
return;
|
||||
@@ -204,7 +209,14 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// <summary>
|
||||
/// Chooses antagonists from the given selection of players for the given antag definition.
|
||||
/// </summary>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, AntagSelectionDefinition def)
|
||||
/// <param name="ent">The antagonist rule entity</param>
|
||||
/// <param name="pool">The players to choose from</param>
|
||||
/// <param name="def">The antagonist selection parameters and criteria</param>
|
||||
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent,
|
||||
IList<ICommonSession> pool,
|
||||
AntagSelectionDefinition def,
|
||||
bool midround = false)
|
||||
{
|
||||
var playerPool = GetPlayerPool(ent, pool, def);
|
||||
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
|
||||
@@ -315,14 +327,17 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (session != null)
|
||||
{
|
||||
var curMind = session.GetMind();
|
||||
if (curMind == null)
|
||||
|
||||
if (curMind == null ||
|
||||
!TryComp<MindComponent>(curMind.Value, out var mindComp) ||
|
||||
mindComp.OwnedEntity != antagEnt)
|
||||
{
|
||||
curMind = _mind.CreateMind(session.UserId, Name(antagEnt.Value));
|
||||
_mind.SetUserId(curMind.Value, session.UserId);
|
||||
}
|
||||
|
||||
_mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true);
|
||||
_role.MindAddRoles(curMind.Value, def.MindComponents);
|
||||
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
|
||||
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
|
||||
SendBriefing(session, def.Briefing);
|
||||
}
|
||||
|
||||
21
Content.Server/Antag/AntagSpawnerSystem.cs
Normal file
21
Content.Server/Antag/AntagSpawnerSystem.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Content.Server.Antag.Components;
|
||||
|
||||
namespace Content.Server.Antag;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an entity when creating an antag for <see cref="AntagSpawnerComponent"/>.
|
||||
/// </summary>
|
||||
public sealed class AntagSpawnerSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AntagSpawnerComponent, AntagSelectEntityEvent>(OnSelectEntity);
|
||||
}
|
||||
|
||||
private void OnSelectEntity(Entity<AntagSpawnerComponent> ent, ref AntagSelectEntityEvent args)
|
||||
{
|
||||
args.Entity = Spawn(ent.Comp.Prototype);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Content.Server.Antag.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns this rule's antags at random tiles on a station using <c>TryGetRandomTile</c>.
|
||||
/// Requires <see cref="AntagSelectionComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AntagRandomSpawnComponent : Component;
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Administration.Systems;
|
||||
using Content.Shared.Antag;
|
||||
using Content.Shared.Destructible.Thresholds;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
@@ -144,10 +143,17 @@ public partial struct AntagSelectionDefinition()
|
||||
|
||||
/// <summary>
|
||||
/// Components added to the player's mind.
|
||||
/// Do NOT use this to add role-type components. Add those as MindRoles instead
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ComponentRegistry MindComponents = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of Mind Role Prototypes to be added to the player's mind.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<EntProtoId>? MindRoles;
|
||||
|
||||
/// <summary>
|
||||
/// A set of starting gear that's equipped to the player.
|
||||
/// </summary>
|
||||
|
||||
17
Content.Server/Antag/Components/AntagSpawnerComponent.cs
Normal file
17
Content.Server/Antag/Components/AntagSpawnerComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Content.Server.Antag;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Antag.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a prototype for antags created with a spawner.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AntagSpawnerSystem))]
|
||||
public sealed partial class AntagSpawnerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity to spawn.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntProtoId Prototype = string.Empty;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
/// <summary>
|
||||
@@ -7,7 +9,10 @@ namespace Content.Server.Atmos.Components
|
||||
public sealed partial class AtmosFixMarkerComponent : Component
|
||||
{
|
||||
// See FixGridAtmos for more details
|
||||
[DataField("mode")]
|
||||
[DataField]
|
||||
public int Mode { get; set; } = 0;
|
||||
|
||||
[DataField]
|
||||
public GasMixture? GasMix = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,90 +30,94 @@ public sealed partial class AtmosphereSystem
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
private void FixGridAtmosCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteError("Not enough arguments.");
|
||||
return;
|
||||
}
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteError("Not enough arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mixtures = new GasMixture[8];
|
||||
for (var i = 0; i < mixtures.Length; i++)
|
||||
mixtures[i] = new GasMixture(Atmospherics.CellVolume) { Temperature = Atmospherics.T20C };
|
||||
var mixtures = new GasMixture[8];
|
||||
for (var i = 0; i < mixtures.Length; i++)
|
||||
mixtures[i] = new GasMixture(Atmospherics.CellVolume) { Temperature = Atmospherics.T20C };
|
||||
|
||||
// 0: Air
|
||||
mixtures[0].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
|
||||
mixtures[0].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
|
||||
// 0: Air
|
||||
mixtures[0].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
|
||||
mixtures[0].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
|
||||
|
||||
// 1: Vaccum
|
||||
// 1: Vaccum
|
||||
|
||||
// 2: Oxygen (GM)
|
||||
mixtures[2].AdjustMoles(Gas.Oxygen, Atmospherics.MolesCellGasMiner);
|
||||
// 2: Oxygen (GM)
|
||||
mixtures[2].AdjustMoles(Gas.Oxygen, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 3: Nitrogen (GM)
|
||||
mixtures[3].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellGasMiner);
|
||||
// 3: Nitrogen (GM)
|
||||
mixtures[3].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 4: Plasma (GM)
|
||||
mixtures[4].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
// 4: Plasma (GM)
|
||||
mixtures[4].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 5: Instant Plasmafire (r)
|
||||
mixtures[5].AdjustMoles(Gas.Oxygen, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].Temperature = 5000f;
|
||||
// 5: Instant Plasmafire (r)
|
||||
mixtures[5].AdjustMoles(Gas.Oxygen, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].Temperature = 5000f;
|
||||
|
||||
// 6: (Walk-In) Freezer
|
||||
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
|
||||
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
|
||||
mixtures[6].Temperature = 235f; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
|
||||
// 6: (Walk-In) Freezer
|
||||
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
|
||||
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
|
||||
mixtures[6].Temperature = 235f; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
|
||||
|
||||
// 7: Nitrogen (101kpa) for vox rooms
|
||||
mixtures[7].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellStandard);
|
||||
// 7: Nitrogen (101kpa) for vox rooms
|
||||
mixtures[7].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellStandard);
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (!NetEntity.TryParse(arg, out var netEntity) || !TryGetEntity(netEntity, out var euid))
|
||||
{
|
||||
shell.WriteError($"Failed to parse euid '{arg}'.");
|
||||
return;
|
||||
}
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (!NetEntity.TryParse(arg, out var netEntity) || !TryGetEntity(netEntity, out var euid))
|
||||
{
|
||||
shell.WriteError($"Failed to parse euid '{arg}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp(euid, out MapGridComponent? gridComp))
|
||||
{
|
||||
shell.WriteError($"Euid '{euid}' does not exist or is not a grid.");
|
||||
return;
|
||||
}
|
||||
if (!TryComp(euid, out MapGridComponent? gridComp))
|
||||
{
|
||||
shell.WriteError($"Euid '{euid}' does not exist or is not a grid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp(euid, out GridAtmosphereComponent? gridAtmosphere))
|
||||
{
|
||||
shell.WriteError($"Grid \"{euid}\" has no atmosphere component, try addatmos.");
|
||||
continue;
|
||||
}
|
||||
if (!TryComp(euid, out GridAtmosphereComponent? gridAtmosphere))
|
||||
{
|
||||
shell.WriteError($"Grid \"{euid}\" has no atmosphere component, try addatmos.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Force Invalidate & update air on all tiles
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> grid =
|
||||
new(euid.Value, gridAtmosphere, Comp<GasTileOverlayComponent>(euid.Value), gridComp, Transform(euid.Value));
|
||||
// Force Invalidate & update air on all tiles
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> grid =
|
||||
new(euid.Value, gridAtmosphere, Comp<GasTileOverlayComponent>(euid.Value), gridComp, Transform(euid.Value));
|
||||
|
||||
RebuildGridTiles(grid);
|
||||
RebuildGridTiles(grid);
|
||||
|
||||
var query = GetEntityQuery<AtmosFixMarkerComponent>();
|
||||
foreach (var (indices, tile) in gridAtmosphere.Tiles.ToArray())
|
||||
{
|
||||
if (tile.Air is not {Immutable: false} air)
|
||||
continue;
|
||||
var query = GetEntityQuery<AtmosFixMarkerComponent>();
|
||||
foreach (var (indices, tile) in gridAtmosphere.Tiles.ToArray())
|
||||
{
|
||||
if (tile.Air is not {Immutable: false} air)
|
||||
continue;
|
||||
|
||||
air.Clear();
|
||||
var mixtureId = 0;
|
||||
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(grid, grid, indices);
|
||||
while (enumerator.MoveNext(out var entUid))
|
||||
{
|
||||
if (query.TryComp(entUid, out var marker))
|
||||
mixtureId = marker.Mode;
|
||||
}
|
||||
air.Clear();
|
||||
var mixtureId = 0;
|
||||
GasMixture? gasMix = null;
|
||||
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(grid, grid, indices);
|
||||
while (enumerator.MoveNext(out var entUid))
|
||||
{
|
||||
if (!query.TryComp(entUid, out var marker))
|
||||
continue;
|
||||
|
||||
var mixture = mixtures[mixtureId];
|
||||
Merge(air, mixture);
|
||||
air.Temperature = mixture.Temperature;
|
||||
}
|
||||
}
|
||||
mixtureId = marker.Mode;
|
||||
gasMix = marker.GasMix;
|
||||
}
|
||||
|
||||
var mixture = mixtures[mixtureId];
|
||||
Merge(air, gasMix is not null ? gasMix : mixture);
|
||||
air.Temperature = mixture.Temperature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -320,7 +320,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public void Ignite(EntityUid uid, EntityUid ignitionSource, FlammableComponent? flammable = null,
|
||||
EntityUid? ignitionSourceUser = null, bool ignoreFireProtection = false)
|
||||
{
|
||||
if (!Resolve(uid, ref flammable))
|
||||
if (!Resolve(uid, ref flammable, false)) // Lavaland Change: SHUT THE FUCK UP FLAMMABLE
|
||||
return;
|
||||
|
||||
if (flammable.AlwaysCombustible)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// <summary>
|
||||
/// Target volume to transfer. If <see cref="WideNet"/> is enabled, actual transfer rate will be much higher.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float TransferRate
|
||||
{
|
||||
get => _transferRate;
|
||||
|
||||
@@ -17,6 +17,7 @@ using Content.Shared.Bed.Cryostorage;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Robust.Server.Audio;
|
||||
|
||||
@@ -21,6 +21,8 @@ using Content.Shared.Mood;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Movement.Pulling.Components; // Goobstation
|
||||
using Content.Shared.Movement.Pulling.Systems; // Goobstation
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
@@ -53,6 +55,15 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||
}
|
||||
|
||||
// Goobstation start
|
||||
// Can breathe check for grab
|
||||
public bool CanBreathe(EntityUid uid, RespiratorComponent respirator)
|
||||
{
|
||||
if(respirator.Saturation < respirator.SuffocationThreshold)
|
||||
return false;
|
||||
return !TryComp<PullableComponent>(uid, out var pullable) || pullable.GrabStage != GrabStage.Suffocate;
|
||||
}
|
||||
// Goobstation end
|
||||
private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
|
||||
@@ -98,7 +109,7 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
if (respirator.Saturation < respirator.SuffocationThreshold)
|
||||
if (!CanBreathe(uid, respirator)) // Goobstation edit
|
||||
{
|
||||
if (_gameTiming.CurTime >= respirator.LastGaspPopupTime + respirator.GaspPopupCooldown)
|
||||
{
|
||||
|
||||
42
Content.Server/Botany/Components/PlantAnalyzerComponent.cs
Normal file
42
Content.Server/Botany/Components/PlantAnalyzerComponent.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Content.Server.AbstractAnalyzer;
|
||||
using Content.Server.Botany.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
[Access(typeof(PlantAnalyzerSystem))]
|
||||
public sealed partial class PlantAnalyzerComponent : AbstractAnalyzerComponent
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public override TimeSpan NextUpdate { get; set; } = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// When will the analyzer be ready to print again?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public TimeSpan PrintReadyAt = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// How often can the analyzer print?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan PrintCooldown = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// The sound that's played when the analyzer prints off a report.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SoundPrint = new SoundPathSpecifier("/Audio/Machines/short_print_and_rip.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// What the machine will print.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId MachineOutput = "PlantAnalyzerReportPaper";
|
||||
}
|
||||
@@ -145,17 +145,7 @@ public sealed partial class BotanySystem : EntitySystem
|
||||
|
||||
public IEnumerable<EntityUid> GenerateProduct(SeedData proto, EntityCoordinates position, int yieldMod = 1)
|
||||
{
|
||||
var totalYield = 0;
|
||||
if (proto.Yield > -1)
|
||||
{
|
||||
if (yieldMod < 0)
|
||||
totalYield = proto.Yield;
|
||||
else
|
||||
totalYield = proto.Yield * yieldMod;
|
||||
|
||||
totalYield = Math.Max(1, totalYield);
|
||||
}
|
||||
|
||||
var totalYield = CalculateTotalYield(proto.Yield, yieldMod);
|
||||
var products = new List<EntityUid>();
|
||||
|
||||
if (totalYield > 1 || proto.HarvestRepeat != HarvestType.NoRepeat)
|
||||
@@ -222,5 +212,8 @@ public sealed partial class BotanySystem : EntitySystem
|
||||
return !proto.Ligneous || proto.Ligneous && held != null && HasComp<SharpComponent>(held);
|
||||
}
|
||||
|
||||
public static int CalculateTotalYield(int yield, int yieldMod) =>
|
||||
yield > -1 ? Math.Max(1, yieldMod < 0 ? yield : yield * yieldMod) : 0;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
193
Content.Server/Botany/Systems/PlantAnalyzerSystem.cs
Normal file
193
Content.Server/Botany/Systems/PlantAnalyzerSystem.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.AbstractAnalyzer;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Server.Paper;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Botany.PlantAnalyzer;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
public sealed class PlantAnalyzerSystem : AbstractAnalyzerSystem<PlantAnalyzerComponent, PlantAnalyzerDoAfterEvent>
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly PaperSystem _paperSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedLabelSystem _labelSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PlantAnalyzerComponent, PlantAnalyzerPrintMessage>(OnPrint);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void UpdateScannedUser(EntityUid analyzer, EntityUid target, bool scanMode)
|
||||
{
|
||||
if (!_uiSystem.HasUi(analyzer, PlantAnalyzerUiKey.Key)
|
||||
|| !ValidScanTarget(target)
|
||||
|| !_entityManager.TryGetComponent<PlantAnalyzerComponent>(analyzer, out var analyzerComponent))
|
||||
return;
|
||||
|
||||
_uiSystem.ServerSendUiMessage(analyzer, PlantAnalyzerUiKey.Key, GatherData(analyzerComponent, scanMode, target: target));
|
||||
}
|
||||
|
||||
private PlantAnalyzerScannedUserMessage GatherData(PlantAnalyzerComponent analyzer, bool? scanMode = null, EntityUid? target = null)
|
||||
{
|
||||
target ??= analyzer.ScannedEntity;
|
||||
PlantAnalyzerPlantData? plantData = null;
|
||||
PlantAnalyzerTrayData? trayData = null;
|
||||
PlantAnalyzerTolerancesData? tolerancesData = null;
|
||||
PlantAnalyzerProduceData? produceData = null;
|
||||
if (_entityManager.TryGetComponent<PlantHolderComponent>(target, out var plantHolder))
|
||||
{
|
||||
if (plantHolder.Seed is not null)
|
||||
{
|
||||
plantData = new PlantAnalyzerPlantData(
|
||||
seedDisplayName: plantHolder.Seed.DisplayName,
|
||||
health: plantHolder.Health,
|
||||
endurance: plantHolder.Seed.Endurance,
|
||||
age: plantHolder.Age,
|
||||
lifespan: plantHolder.Seed.Lifespan,
|
||||
dead: plantHolder.Dead,
|
||||
viable: plantHolder.Seed.Viable,
|
||||
mutating: plantHolder.MutationLevel > 0f,
|
||||
kudzu: plantHolder.Seed.TurnIntoKudzu
|
||||
);
|
||||
tolerancesData = new PlantAnalyzerTolerancesData(
|
||||
waterConsumption: plantHolder.Seed.WaterConsumption,
|
||||
nutrientConsumption: plantHolder.Seed.NutrientConsumption,
|
||||
toxinsTolerance: plantHolder.Seed.ToxinsTolerance,
|
||||
pestTolerance: plantHolder.Seed.PestTolerance,
|
||||
weedTolerance: plantHolder.Seed.WeedTolerance,
|
||||
lowPressureTolerance: plantHolder.Seed.LowPressureTolerance,
|
||||
highPressureTolerance: plantHolder.Seed.HighPressureTolerance,
|
||||
idealHeat: plantHolder.Seed.IdealHeat,
|
||||
heatTolerance: plantHolder.Seed.HeatTolerance,
|
||||
idealLight: plantHolder.Seed.IdealLight,
|
||||
lightTolerance: plantHolder.Seed.LightTolerance,
|
||||
consumeGasses: [.. plantHolder.Seed.ConsumeGasses.Keys]
|
||||
);
|
||||
produceData = new PlantAnalyzerProduceData(
|
||||
yield: plantHolder.Seed.ProductPrototypes.Count == 0 ? 0 : BotanySystem.CalculateTotalYield(plantHolder.Seed.Yield, plantHolder.YieldMod),
|
||||
potency: plantHolder.Seed.Potency,
|
||||
chemicals: [.. plantHolder.Seed.Chemicals.Keys],
|
||||
produce: plantHolder.Seed.ProductPrototypes,
|
||||
exudeGasses: [.. plantHolder.Seed.ExudeGasses.Keys],
|
||||
seedless: plantHolder.Seed.Seedless
|
||||
);
|
||||
}
|
||||
trayData = new PlantAnalyzerTrayData(
|
||||
waterLevel: plantHolder.WaterLevel,
|
||||
nutritionLevel: plantHolder.NutritionLevel,
|
||||
toxins: plantHolder.Toxins,
|
||||
pestLevel: plantHolder.PestLevel,
|
||||
weedLevel: plantHolder.WeedLevel,
|
||||
chemicals: plantHolder.SoilSolution?.Comp.Solution.Contents.Select(r => r.Reagent.Prototype).ToList()
|
||||
);
|
||||
}
|
||||
|
||||
return new PlantAnalyzerScannedUserMessage(
|
||||
GetNetEntity(target),
|
||||
scanMode,
|
||||
plantData,
|
||||
trayData,
|
||||
tolerancesData,
|
||||
produceData,
|
||||
analyzer.PrintReadyAt
|
||||
);
|
||||
}
|
||||
|
||||
private void OnPrint(EntityUid uid, PlantAnalyzerComponent component, PlantAnalyzerPrintMessage args)
|
||||
{
|
||||
var user = args.Actor;
|
||||
|
||||
if (_gameTiming.CurTime < component.PrintReadyAt)
|
||||
{
|
||||
// This shouldn't occur due to the UI guarding against it, but
|
||||
// if it does, tell the user why nothing happened.
|
||||
_popupSystem.PopupEntity(Loc.GetString("plant-analyzer-printer-not-ready"), uid, user);
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn a piece of paper.
|
||||
var printed = EntityManager.SpawnEntity(component.MachineOutput, Transform(uid).Coordinates);
|
||||
_handsSystem.PickupOrDrop(args.Actor, printed, checkActionBlocker: false);
|
||||
|
||||
if (!TryComp<PaperComponent>(printed, out var paperComp))
|
||||
{
|
||||
Log.Error("Printed paper did not have PaperComponent.");
|
||||
return;
|
||||
}
|
||||
|
||||
var data = GatherData(component);
|
||||
var missingData = Loc.GetString("plant-analyzer-printout-missing");
|
||||
|
||||
var seedName = data.PlantData is not null ? Loc.GetString(data.PlantData.SeedDisplayName) : null;
|
||||
(string, object)[] parameters = [
|
||||
("seedName", seedName ?? missingData),
|
||||
("produce", data.ProduceData is not null ? PlantAnalyzerLocalizationHelper.ProduceToLocalizedStrings(data.ProduceData.Produce, _prototypeManager).Plural : missingData),
|
||||
("water", data.TolerancesData?.WaterConsumption.ToString("0.00") ?? missingData),
|
||||
("nutrients", data.TolerancesData?.NutrientConsumption.ToString("0.00") ?? missingData),
|
||||
("toxins", data.TolerancesData?.ToxinsTolerance.ToString("0.00") ?? missingData),
|
||||
("pests", data.TolerancesData?.PestTolerance.ToString("0.00") ?? missingData),
|
||||
("weeds", data.TolerancesData?.WeedTolerance.ToString("0.00") ?? missingData),
|
||||
("gasesIn", data.TolerancesData is not null ? PlantAnalyzerLocalizationHelper.GasesToLocalizedStrings(data.TolerancesData.ConsumeGasses, _prototypeManager) : missingData),
|
||||
("kpa", data.TolerancesData?.IdealPressure.ToString("0.00") ?? missingData),
|
||||
("kpaTolerance", data.TolerancesData?.PressureTolerance.ToString("0.00") ?? missingData),
|
||||
("temp", data.TolerancesData?.IdealHeat.ToString("0.00") ?? missingData),
|
||||
("tempTolerance", data.TolerancesData?.HeatTolerance.ToString("0.00") ?? missingData),
|
||||
("lightLevel", data.TolerancesData?.IdealLight.ToString("0.00") ?? missingData),
|
||||
("lightTolerance", data.TolerancesData?.LightTolerance.ToString("0.00") ?? missingData),
|
||||
("yield", data.ProduceData?.Yield ?? -1),
|
||||
("potency", data.ProduceData is not null ? Loc.GetString(data.ProduceData.Potency) : missingData),
|
||||
("chemicals", data.ProduceData is not null ? PlantAnalyzerLocalizationHelper.ChemicalsToLocalizedStrings(data.ProduceData.Chemicals, _prototypeManager) : missingData),
|
||||
("gasesOut", data.ProduceData is not null ? PlantAnalyzerLocalizationHelper.GasesToLocalizedStrings(data.ProduceData.ExudeGasses, _prototypeManager) : missingData),
|
||||
("endurance", data.PlantData?.Endurance.ToString("0.00") ?? missingData),
|
||||
("lifespan", data.PlantData?.Lifespan.ToString("0.00") ?? missingData),
|
||||
("seeds", data.ProduceData is not null ? (data.ProduceData.Seedless ? "no" : "yes") : "other"),
|
||||
("viable", data.PlantData is not null ? (data.PlantData.Viable ? "yes" : "no") : "other"),
|
||||
("kudzu", data.PlantData is not null ? (data.PlantData.Kudzu ? "yes" : "no") : "other"),
|
||||
("indent", " "),
|
||||
("nl", "\n")
|
||||
];
|
||||
|
||||
_paperSystem.SetContent(printed, Loc.GetString($"plant-analyzer-printout", [.. parameters]));
|
||||
_labelSystem.Label(printed, seedName);
|
||||
_audioSystem.PlayPvs(component.SoundPrint, uid,
|
||||
AudioParams.Default
|
||||
.WithVariation(0.25f)
|
||||
.WithVolume(3f)
|
||||
.WithRolloffFactor(2.8f)
|
||||
.WithMaxDistance(4.5f));
|
||||
|
||||
component.PrintReadyAt = _gameTiming.CurTime + component.PrintCooldown;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Enum GetUiKey() => PlantAnalyzerUiKey.Key;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool ScanTargetPopupMessage(Entity<PlantAnalyzerComponent> uid, AfterInteractEvent args, [NotNullWhen(true)] out string? message)
|
||||
{
|
||||
message = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool ValidScanTarget(EntityUid? target) => HasComp<PlantHolderComponent>(target);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public sealed class CharacterInfoSystem : EntitySystem
|
||||
if (_minds.TryGetMind(entity, out var mindId, out var mind))
|
||||
{
|
||||
// Get objectives
|
||||
foreach (var objective in mind.AllObjectives)
|
||||
foreach (var objective in mind.Objectives)
|
||||
{
|
||||
var info = _objectives.GetInfo(objective, mindId, mind);
|
||||
if (info == null)
|
||||
|
||||
@@ -242,7 +242,7 @@ public sealed partial class CloningSystem : EntitySystem
|
||||
|
||||
clonePod.ActivelyCloning = true;
|
||||
|
||||
if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype))
|
||||
if (_jobs.MindTryGetJob(mindEnt, out var prototype))
|
||||
foreach (var special in prototype.Special)
|
||||
if (special is AddComponentSpecial)
|
||||
special.AfterEquip(mob);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Paint;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Clothing.Loadouts.Prototypes;
|
||||
using Content.Shared.Clothing.Loadouts.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Players;
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class NotJobsRequirementSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// if player has no job then don't care
|
||||
if (!TryComp<JobComponent>(args.MindId, out var job))
|
||||
if (!TryComp<JobRoleComponent>(args.MindId, out var job))
|
||||
return;
|
||||
foreach (string forbidJob in comp.Jobs)
|
||||
if (job.Prototype == forbidJob)
|
||||
|
||||
@@ -77,7 +77,7 @@ public sealed class ParadoxAnomalySystem : EntitySystem
|
||||
if (!_proto.TryIndex<SpeciesPrototype>(humanoid.Species, out var species))
|
||||
continue;
|
||||
|
||||
if (_mind.GetMind(uid, mindContainer) is not {} mindId || !HasComp<JobComponent>(mindId))
|
||||
if (_mind.GetMind(uid, mindContainer) is not {} mindId || !HasComp<JobRoleComponent>(mindId))
|
||||
continue;
|
||||
|
||||
if (_role.MindIsAntagonist(mindId))
|
||||
@@ -99,7 +99,7 @@ public sealed class ParadoxAnomalySystem : EntitySystem
|
||||
return null;
|
||||
|
||||
var (uid, mindId, species, profile) = _random.Pick(candidates);
|
||||
var jobId = Comp<JobComponent>(mindId).Prototype;
|
||||
var jobId = Comp<JobRoleComponent>(mindId).Prototype;
|
||||
var job = _proto.Index<JobPrototype>(jobId!);
|
||||
|
||||
// Find a suitable spawn point.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.GenericAntag;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Content.Server.Popups;
|
||||
@@ -9,10 +8,10 @@ using Content.Shared.Maps;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -26,10 +25,11 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _faction = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly RoleSystem _role = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
private EntityQuery<CarpRiftsConditionComponent> _objQuery;
|
||||
|
||||
@@ -56,7 +56,6 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnSpawnRift);
|
||||
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
|
||||
SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<DragonComponent, GenericAntagCreatedEvent>(OnCreated);
|
||||
SubscribeLocalEvent<DragonComponent, EntityZombifiedEvent>(OnZombified);
|
||||
}
|
||||
|
||||
@@ -95,7 +94,8 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
comp.RiftAccumulator += frameTime;
|
||||
if (!_mobState.IsDead(uid))
|
||||
comp.RiftAccumulator += frameTime;
|
||||
|
||||
// Delete it, naughty dragon!
|
||||
if (comp.RiftAccumulator >= comp.RiftMaxAccumulator)
|
||||
@@ -149,7 +149,7 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
// cant stack rifts near eachother
|
||||
foreach (var (_, riftXform) in EntityQuery<DragonRiftComponent, TransformComponent>(true))
|
||||
{
|
||||
if (riftXform.Coordinates.InRange(EntityManager, _transform, xform.Coordinates, RiftRange))
|
||||
if (_transform.InRange(riftXform.Coordinates, xform.Coordinates, RiftRange))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("carp-rift-proximity", ("proximity", RiftRange)), uid, uid);
|
||||
return;
|
||||
@@ -157,7 +157,7 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
}
|
||||
|
||||
// cant put a rift on solars
|
||||
foreach (var tile in grid.GetTilesIntersecting(new Circle(xform.WorldPosition, RiftTileRadius), false))
|
||||
foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(_transform.GetWorldPosition(xform), RiftTileRadius), false))
|
||||
{
|
||||
if (!tile.IsSpace(_tileDef))
|
||||
continue;
|
||||
@@ -193,18 +193,6 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
DeleteRifts(uid, false, component);
|
||||
}
|
||||
|
||||
private void OnCreated(EntityUid uid, DragonComponent comp, ref GenericAntagCreatedEvent args)
|
||||
{
|
||||
var mindId = args.MindId;
|
||||
var mind = args.Mind;
|
||||
|
||||
_role.MindAddRole(mindId, new DragonRoleComponent(), mind);
|
||||
_role.MindAddRole(mindId, new RoleBriefingComponent()
|
||||
{
|
||||
Briefing = Loc.GetString("dragon-role-briefing")
|
||||
}, mind);
|
||||
}
|
||||
|
||||
private void OnZombified(Entity<DragonComponent> ent, ref EntityZombifiedEvent args)
|
||||
{
|
||||
// prevent carp attacking zombie dragon
|
||||
@@ -240,7 +228,7 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var mind = Comp<MindComponent>(mindContainer.Mind.Value);
|
||||
foreach (var objId in mind.AllObjectives)
|
||||
foreach (var objId in mind.Objectives)
|
||||
{
|
||||
if (_objQuery.TryGetComponent(objId, out var obj))
|
||||
{
|
||||
@@ -262,7 +250,7 @@ public sealed partial class DragonSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var mind = Comp<MindComponent>(mindContainer.Mind.Value);
|
||||
foreach (var objId in mind.AllObjectives)
|
||||
foreach (var objId in mind.Objectives)
|
||||
{
|
||||
if (_objQuery.TryGetComponent(objId, out var obj))
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Mind;
|
||||
|
||||
namespace Content.Server.EntityEffects.EffectConditions;
|
||||
|
||||
@@ -15,15 +16,22 @@ public sealed partial class JobCondition : EntityEffectCondition
|
||||
public override bool Condition(EntityEffectBaseArgs args)
|
||||
{
|
||||
args.EntityManager.TryGetComponent<MindContainerComponent>(args.TargetEntity, out var mindContainer);
|
||||
if (mindContainer != null && mindContainer.Mind != null)
|
||||
|
||||
if ( mindContainer is null
|
||||
|| !args.EntityManager.TryGetComponent<MindComponent>(mindContainer.Mind, out var mind))
|
||||
return false;
|
||||
|
||||
foreach (var roleId in mind.MindRoles)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
if (args.EntityManager.TryGetComponent<JobComponent>(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype))
|
||||
{
|
||||
foreach (var jobId in Job)
|
||||
if (prototype.ID == jobId)
|
||||
return true;
|
||||
}
|
||||
if(!args.EntityManager.HasComponent<JobRoleComponent>(roleId))
|
||||
continue;
|
||||
|
||||
if(!args.EntityManager.TryGetComponent<MindRoleComponent>(roleId, out var mindRole)
|
||||
|| mindRole.JobPrototype is null)
|
||||
continue;
|
||||
|
||||
if (Job.Contains(mindRole.JobPrototype.Value))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -35,5 +43,3 @@ public sealed partial class JobCondition : EntityEffectCondition
|
||||
return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed partial class PlantAdjustPests : PlantAdjustAttribute
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
|
||||
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false))
|
||||
return;
|
||||
|
||||
plantHolderComp.PestLevel += Amount;
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed partial class PlantAdjustToxins : PlantAdjustAttribute
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
|
||||
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false))
|
||||
return;
|
||||
|
||||
plantHolderComp.Toxins += Amount;
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed partial class PlantAdjustWeeds : PlantAdjustAttribute
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
|
||||
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false))
|
||||
return;
|
||||
|
||||
plantHolderComp.WeedLevel += Amount;
|
||||
|
||||
@@ -444,7 +444,7 @@ public sealed partial class ExplosionSystem
|
||||
foreach (var (entity, damage) in _toDamage)
|
||||
{
|
||||
// TODO EXPLOSIONS turn explosions into entities, and pass the the entity in as the damage origin.
|
||||
_damageableSystem.TryChangeDamage(entity, damage, ignoreResistances: true);
|
||||
_damageableSystem.TryChangeDamage(entity, damage, ignoreResistances: true, partMultiplier: 0.3f); // Shitmed: Temp change, nerf explosion delimbing
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,55 @@ public sealed class SpraySystem : EntitySystem
|
||||
if (diffPos == Vector2.Zero || diffPos == Vector2Helpers.NaN)
|
||||
return;
|
||||
|
||||
// Lavaland Shitcode Start - You should spray yourself NOW.
|
||||
// Too lazy to learn this system, so you get a copypaste job!
|
||||
if ((clickMapPos.Position - userMapPos.Position).Length() < 0.5f)
|
||||
{
|
||||
// Split a portion of the solution for the self-spray
|
||||
var adjustedSolutionAmount = entity.Comp.TransferAmount;
|
||||
var newSolution = _solutionContainer.SplitSolution(soln.Value, adjustedSolutionAmount);
|
||||
|
||||
if (newSolution.Volume > 0)
|
||||
{
|
||||
// Spawn vapor with a slight offset to create movement
|
||||
var offset = new Vector2(0.1f, 0); // Small offset to ensure collision
|
||||
var vapor = Spawn(entity.Comp.SprayedPrototype, userMapPos.Offset(offset));
|
||||
var vaporXform = xformQuery.GetComponent(vapor);
|
||||
|
||||
if (TryComp(vapor, out AppearanceComponent? appearance))
|
||||
{
|
||||
_appearance.SetData(vapor, VaporVisuals.Color, solution.GetColor(_proto).WithAlpha(1f), appearance);
|
||||
_appearance.SetData(vapor, VaporVisuals.State, true, appearance);
|
||||
}
|
||||
|
||||
var vaporComponent = Comp<VaporComponent>(vapor);
|
||||
var ent = (vapor, vaporComponent);
|
||||
_vapor.TryAddSolution(ent, newSolution);
|
||||
|
||||
// Create a slight movement effect
|
||||
var rotation = Angle.FromDegrees(45);
|
||||
var impulseDirection = -offset.Normalized();
|
||||
var time = 0.5f; // Shorter duration for self-spray
|
||||
var target = userMapPos.Offset(impulseDirection * 0.5f); // Small movement distance
|
||||
|
||||
_vapor.Start(ent, vaporXform, impulseDirection * 0.5f, entity.Comp.SprayVelocity, target, time, args.User);
|
||||
|
||||
if (TryComp<PhysicsComponent>(args.User, out var body))
|
||||
{
|
||||
if (_gravity.IsWeightless(args.User, body))
|
||||
_physics.ApplyLinearImpulse(args.User, -impulseDirection.Normalized() * entity.Comp.PushbackAmount, body: body);
|
||||
}
|
||||
|
||||
_audio.PlayPvs(entity.Comp.SpraySound, entity, entity.Comp.SpraySound.Params.WithVariation(0.125f));
|
||||
|
||||
if (useDelay != null)
|
||||
_useDelay.TryResetDelay((entity, useDelay));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Lavaland Shitcode End
|
||||
|
||||
var diffNorm = diffPos.Normalized();
|
||||
var diffLength = diffPos.Length();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Content.Server.GameTicking
|
||||
/// How long before RoundStartTime do we load maps.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public TimeSpan RoundPreloadTime { get; } = TimeSpan.FromSeconds(15);
|
||||
public TimeSpan RoundPreloadTime { get; } = TimeSpan.FromSeconds(20);
|
||||
|
||||
[ViewVariables]
|
||||
private TimeSpan _pauseTime;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Discord;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Maps;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
@@ -27,6 +28,7 @@ namespace Content.Server.GameTicking
|
||||
public sealed partial class GameTicker
|
||||
{
|
||||
[Dependency] private readonly DiscordWebhook _discord = default!;
|
||||
[Dependency] private readonly RoleSystem _role = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly AnnouncerSystem _announcer = default!;
|
||||
|
||||
@@ -381,7 +383,7 @@ namespace Content.Server.GameTicking
|
||||
var userId = mind.UserId ?? mind.OriginalOwnerUserId;
|
||||
|
||||
var connected = false;
|
||||
var observer = HasComp<ObserverRoleComponent>(mindId);
|
||||
var observer = _role.MindHasRole<ObserverRoleComponent>(mindId);
|
||||
// Continuing
|
||||
if (userId != null && _playerManager.ValidSessionId(userId.Value))
|
||||
{
|
||||
@@ -408,7 +410,7 @@ namespace Content.Server.GameTicking
|
||||
_pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true);
|
||||
}
|
||||
|
||||
var roles = _roles.MindGetAllRoles(mindId);
|
||||
var roles = _roles.MindGetAllRoleInfo(mindId);
|
||||
|
||||
var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo()
|
||||
{
|
||||
|
||||
@@ -2,8 +2,8 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.RandomMetadata;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Speech.Components;
|
||||
@@ -12,13 +12,12 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Silicon.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
@@ -33,6 +32,7 @@ namespace Content.Server.GameTicking
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly AdminSystem _admin = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
public const string ObserverPrototypeName = "MobObserver";
|
||||
@@ -248,21 +248,10 @@ namespace Content.Server.GameTicking
|
||||
_mind.SetUserId(newMind, data.UserId);
|
||||
|
||||
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
|
||||
var job = new JobComponent { Prototype = jobId };
|
||||
_roles.MindAddRole(newMind, job, silent: silent);
|
||||
var jobName = _jobs.MindTryGetJobName(newMind);
|
||||
|
||||
_playTimeTrackings.PlayerRolesChanged(player);
|
||||
|
||||
// Delta-V: Add AlwaysUseSpawner.
|
||||
var spawnPointType = SpawnPointType.Unset;
|
||||
if (jobPrototype.AlwaysUseSpawner)
|
||||
{
|
||||
lateJoin = false;
|
||||
spawnPointType = SpawnPointType.Job;
|
||||
}
|
||||
|
||||
var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, job, character, spawnPointType: spawnPointType);
|
||||
var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, jobId, character);
|
||||
DebugTools.AssertNotNull(mobMaybe);
|
||||
var mob = mobMaybe!.Value;
|
||||
|
||||
@@ -283,6 +272,10 @@ namespace Content.Server.GameTicking
|
||||
|
||||
_mind.TransferTo(newMind, mob);
|
||||
|
||||
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
|
||||
var jobName = _jobs.MindTryGetJobName(newMind);
|
||||
_admin.UpdatePlayerList(player);
|
||||
|
||||
if (lateJoin && !silent)
|
||||
{
|
||||
_chatSystem.DispatchStationAnnouncement(station,
|
||||
@@ -303,13 +296,17 @@ namespace Content.Server.GameTicking
|
||||
_stationJobs.TryAssignJob(station, jobPrototype, player.UserId);
|
||||
|
||||
if (lateJoin)
|
||||
{
|
||||
_adminLogger.Add(LogType.LateJoin,
|
||||
LogImpact.Medium,
|
||||
$"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.RoundStartJoin,
|
||||
LogImpact.Medium,
|
||||
$"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}.");
|
||||
}
|
||||
|
||||
// Make sure they're aware of extended access.
|
||||
if (Comp<StationJobsComponent>(station).ExtendedAccess
|
||||
@@ -324,28 +321,13 @@ namespace Content.Server.GameTicking
|
||||
Loc.GetString("job-greet-station-name", ("stationName", metaData.EntityName)));
|
||||
}
|
||||
|
||||
// Arrivals is unable to do this during spawning as no actor is attached yet.
|
||||
// We also want this message last.
|
||||
if (!silent && lateJoin && _arrivals.Enabled)
|
||||
{
|
||||
var arrival = _arrivals.NextShuttleArrival();
|
||||
if (arrival == null)
|
||||
{
|
||||
_chatManager.DispatchServerMessage(player, Loc.GetString("latejoin-arrivals-direction"));
|
||||
}
|
||||
else
|
||||
{
|
||||
_chatManager.DispatchServerMessage(player,
|
||||
Loc.GetString("latejoin-arrivals-direction-time", ("time", $"{arrival:mm\\:ss}")));
|
||||
}
|
||||
}
|
||||
|
||||
// We raise this event directed to the mob, but also broadcast it so game rules can do something now.
|
||||
PlayersJoinedRoundNormally++;
|
||||
var aev = new PlayerSpawnCompleteEvent(mob,
|
||||
player,
|
||||
jobId,
|
||||
lateJoin,
|
||||
silent,
|
||||
PlayersJoinedRoundNormally,
|
||||
station,
|
||||
character);
|
||||
@@ -410,7 +392,7 @@ namespace Content.Server.GameTicking
|
||||
var (mindId, mindComp) = _mind.CreateMind(player.UserId, name);
|
||||
mind = (mindId, mindComp);
|
||||
_mind.SetUserId(mind.Value, player.UserId);
|
||||
_roles.MindAddRole(mind.Value, new ObserverRoleComponent());
|
||||
_roles.MindAddRole(mind.Value, "MindRoleObserver");
|
||||
}
|
||||
|
||||
var ghost = _ghost.SpawnGhost(mind.Value);
|
||||
@@ -565,68 +547,4 @@ namespace Content.Server.GameTicking
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised broadcast before a player is spawned by the GameTicker.
|
||||
/// You can use this event to spawn a player off-station on late-join but also at round start.
|
||||
/// When this event is handled, the GameTicker will not perform its own player-spawning logic.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class PlayerBeforeSpawnEvent : HandledEntityEventArgs
|
||||
{
|
||||
public ICommonSession Player { get; }
|
||||
public HumanoidCharacterProfile Profile { get; }
|
||||
public string? JobId { get; }
|
||||
public bool LateJoin { get; }
|
||||
public EntityUid Station { get; }
|
||||
|
||||
public PlayerBeforeSpawnEvent(ICommonSession player,
|
||||
HumanoidCharacterProfile profile,
|
||||
string? jobId,
|
||||
bool lateJoin,
|
||||
EntityUid station)
|
||||
{
|
||||
Player = player;
|
||||
Profile = profile;
|
||||
JobId = jobId;
|
||||
LateJoin = lateJoin;
|
||||
Station = station;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised both directed and broadcast when a player has been spawned by the GameTicker.
|
||||
/// You can use this to handle people late-joining, or to handle people being spawned at round start.
|
||||
/// Can be used to give random players a role, modify their equipment, etc.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class PlayerSpawnCompleteEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Mob { get; }
|
||||
public ICommonSession Player { get; }
|
||||
public string? JobId { get; }
|
||||
public bool LateJoin { get; }
|
||||
public EntityUid Station { get; }
|
||||
public HumanoidCharacterProfile Profile { get; }
|
||||
|
||||
// Ex. If this is the 27th person to join, this will be 27.
|
||||
public int JoinOrder { get; }
|
||||
|
||||
public PlayerSpawnCompleteEvent(EntityUid mob,
|
||||
ICommonSession player,
|
||||
string? jobId,
|
||||
bool lateJoin,
|
||||
int joinOrder,
|
||||
EntityUid station,
|
||||
HumanoidCharacterProfile profile)
|
||||
{
|
||||
Mob = mob;
|
||||
Player = player;
|
||||
JobId = jobId;
|
||||
LateJoin = lateJoin;
|
||||
Station = station;
|
||||
Profile = profile;
|
||||
JoinOrder = joinOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,5 +87,5 @@ public sealed partial class TraitorRuleComponent : Component
|
||||
/// The amount of TC traitors start with.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FixedPoint2 StartingBalance = 20;
|
||||
public int StartingBalance = 20;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Mind;
|
||||
using Content.Server.Points;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Points;
|
||||
using Content.Shared.Storage;
|
||||
|
||||
@@ -13,7 +13,6 @@ using Content.Server.RoundEnd;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
@@ -37,9 +36,9 @@ namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
[Dependency] private readonly StoreSystem _store = default!;
|
||||
@@ -64,6 +63,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
SubscribeLocalEvent<NukeOperativeComponent, EntityZombifiedEvent>(OnOperativeZombified);
|
||||
|
||||
SubscribeLocalEvent<NukeOpsShuttleComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<NukeopsRoleComponent, GetBriefingEvent>(OnGetBriefing);
|
||||
|
||||
SubscribeLocalEvent<ConsoleFTLAttemptEvent>(OnShuttleFTLAttempt);
|
||||
SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared);
|
||||
@@ -72,7 +72,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
SubscribeLocalEvent<NukeopsRuleComponent, AfterAntagEntitySelectedEvent>(OnAfterAntagEntSelected);
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule,
|
||||
protected override void Started(EntityUid uid,
|
||||
NukeopsRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
GameRuleStartedEvent args)
|
||||
{
|
||||
var eligible = new List<Entity<StationEventEligibleComponent, NpcFactionMemberComponent>>();
|
||||
@@ -92,7 +94,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
protected override void AppendRoundEndText(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule,
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
NukeopsRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}");
|
||||
@@ -233,7 +237,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
|
||||
// If the disk is currently at Central Command, the crew wins - just slightly.
|
||||
// This also implies that some nuclear operatives have died.
|
||||
SetWinType(ent, diskAtCentCom
|
||||
SetWinType(ent,
|
||||
diskAtCentCom
|
||||
? WinType.CrewMinor
|
||||
: WinType.OpsMinor);
|
||||
ent.Comp.WinConditions.Add(diskAtCentCom
|
||||
@@ -461,8 +466,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
: WinCondition.AllNukiesDead);
|
||||
|
||||
SetWinType(ent, WinType.CrewMajor, false);
|
||||
_roundEndSystem.DoRoundEndBehavior(
|
||||
nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement);
|
||||
_roundEndSystem.DoRoundEndBehavior(nukeops.RoundEndBehavior,
|
||||
nukeops.EvacShuttleTime,
|
||||
nukeops.RoundEndTextSender,
|
||||
nukeops.RoundEndTextShuttleCall,
|
||||
nukeops.RoundEndTextAnnouncement);
|
||||
|
||||
// prevent it called multiple times
|
||||
nukeops.RoundEndBehavior = RoundEndBehavior.Nothing;
|
||||
@@ -470,16 +478,22 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
|
||||
private void OnAfterAntagEntSelected(Entity<NukeopsRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
if (ent.Comp.TargetStation is not { } station)
|
||||
return;
|
||||
var target = (ent.Comp.TargetStation is not null) ? Name(ent.Comp.TargetStation.Value) : "the target";
|
||||
|
||||
_antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome",
|
||||
("station", station),
|
||||
_antag.SendBriefing(args.Session,
|
||||
Loc.GetString("nukeops-welcome",
|
||||
("station", target),
|
||||
("name", Name(ent))),
|
||||
Color.Red,
|
||||
ent.Comp.GreetSoundNotification);
|
||||
}
|
||||
|
||||
private void OnGetBriefing(Entity<NukeopsRoleComponent> role, ref GetBriefingEvent args)
|
||||
{
|
||||
// TODO Different character screen briefing for the 3 nukie types
|
||||
args.Append(Loc.GetString("nukeops-briefing"));
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Is this method the shitty glue holding together the last of my sanity? yes.
|
||||
/// Do i have a better solution? not presently.
|
||||
|
||||
@@ -15,7 +15,6 @@ using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Mobs;
|
||||
@@ -38,8 +37,8 @@ namespace Content.Server.GameTicking.Rules;
|
||||
public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
|
||||
[Dependency] private readonly EuiManager _euiMan = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
@@ -49,7 +48,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
//Used in OnPostFlash, no reference to the rule component is available
|
||||
public readonly ProtoId<NpcFactionPrototype> RevolutionaryNpcFaction = "Revolutionary";
|
||||
@@ -59,9 +58,12 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CommandStaffComponent, MobStateChangedEvent>(OnCommandMobStateChanged);
|
||||
SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
|
||||
SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
|
||||
|
||||
SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
|
||||
SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
|
||||
|
||||
SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
|
||||
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
@@ -85,7 +87,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule,
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
RevolutionaryRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
base.AppendRoundEndText(uid, component, gameRule, ref args);
|
||||
@@ -101,7 +105,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count)));
|
||||
foreach (var (mind, data, name) in sessionData)
|
||||
{
|
||||
var count = CompOrNull<RevolutionaryRoleComponent>(mind)?.ConvertedCount ?? 0;
|
||||
_role.MindHasRole<RevolutionaryRoleComponent>(mind, out var role);
|
||||
var count = CompOrNull<RevolutionaryRoleComponent>(role)?.ConvertedCount ?? 0;
|
||||
|
||||
args.AddLine(Loc.GetString("rev-headrev-name-user",
|
||||
("name", name),
|
||||
("username", data.UserName),
|
||||
@@ -113,10 +119,8 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
|
||||
private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref GetBriefingEvent args)
|
||||
{
|
||||
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null)
|
||||
return;
|
||||
|
||||
var head = HasComp<HeadRevolutionaryComponent>(mind.OwnedEntity);
|
||||
var ent = args.Mind.Comp.OwnedEntity;
|
||||
var head = HasComp<HeadRevolutionaryComponent>(ent);
|
||||
args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing"));
|
||||
}
|
||||
|
||||
@@ -145,15 +149,20 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
|
||||
if (ev.User != null)
|
||||
{
|
||||
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
|
||||
_adminLogManager.Add(LogType.Mind,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
|
||||
|
||||
if (_mind.TryGetRole<RevolutionaryRoleComponent>(ev.User.Value, out var headrev))
|
||||
headrev.ConvertedCount++;
|
||||
if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _))
|
||||
{
|
||||
if (_role.MindHasRole<RevolutionaryRoleComponent>(revMindId, out var role))
|
||||
role.Value.Comp2.ConvertedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
|
||||
{
|
||||
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevPrototypeId });
|
||||
_role.MindAddRole(mindId, "MindRoleRevolutionary");
|
||||
}
|
||||
|
||||
if (mind?.Session != null)
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -26,32 +18,33 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
|
||||
SubscribeLocalEvent<ThiefRoleComponent, GetBriefingEvent>(OnGetBriefing);
|
||||
}
|
||||
|
||||
private void AfterAntagSelected(Entity<ThiefRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
|
||||
// Greeting upon thief activation
|
||||
private void AfterAntagSelected(Entity<ThiefRuleComponent> mindId, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind))
|
||||
return;
|
||||
|
||||
//Generate objectives
|
||||
_antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null);
|
||||
var ent = args.EntityUid;
|
||||
_antag.SendBriefing(ent, MakeBriefing(ent), null, null);
|
||||
}
|
||||
|
||||
//Add mind briefing
|
||||
private void OnGetBriefing(Entity<ThiefRoleComponent> thief, ref GetBriefingEvent args)
|
||||
// Character screen briefing
|
||||
private void OnGetBriefing(Entity<ThiefRoleComponent> role, ref GetBriefingEvent args)
|
||||
{
|
||||
if (!TryComp<MindComponent>(thief.Owner, out var mind) || mind.OwnedEntity == null)
|
||||
return;
|
||||
var ent = args.Mind.Comp.OwnedEntity;
|
||||
|
||||
args.Append(MakeBriefing(mind.OwnedEntity.Value));
|
||||
if (ent is null)
|
||||
return;
|
||||
args.Append(MakeBriefing(ent.Value));
|
||||
}
|
||||
|
||||
private string MakeBriefing(EntityUid thief)
|
||||
private string MakeBriefing(EntityUid ent)
|
||||
{
|
||||
var isHuman = HasComp<HumanoidAppearanceComponent>(thief);
|
||||
var isHuman = HasComp<HumanoidAppearanceComponent>(ent);
|
||||
var briefing = isHuman
|
||||
? Loc.GetString("thief-role-greeting-human")
|
||||
: Loc.GetString("thief-role-greeting-animal");
|
||||
|
||||
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
|
||||
if (isHuman)
|
||||
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
|
||||
|
||||
return briefing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Roles.RoleCodeword;
|
||||
@@ -28,17 +26,15 @@ namespace Content.Server.GameTicking.Rules;
|
||||
public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
{
|
||||
private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b");
|
||||
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!; // WD EDIT
|
||||
|
||||
public override void Initialize()
|
||||
@@ -46,7 +42,6 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
|
||||
|
||||
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
|
||||
}
|
||||
|
||||
@@ -76,7 +71,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
|
||||
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component)
|
||||
{
|
||||
//Grab the mind if it wasnt provided
|
||||
//Grab the mind if it wasn't provided
|
||||
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
|
||||
return false;
|
||||
|
||||
@@ -95,13 +90,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
{
|
||||
// Calculate the amount of currency on the uplink.
|
||||
var startingBalance = component.StartingBalance;
|
||||
if (_jobs.MindTryGetJob(mindId, out _, out var job))
|
||||
{
|
||||
if (startingBalance < job.AntagAdvantage) // Can't use Math functions on FixedPoint2
|
||||
startingBalance = 0;
|
||||
else
|
||||
startingBalance = startingBalance - job.AntagAdvantage;
|
||||
}
|
||||
if (_jobs.MindTryGetJob(mindId, out var prototype))
|
||||
startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0);
|
||||
|
||||
// creadth: we need to create uplink for the antag.
|
||||
// PDA should be in place already
|
||||
@@ -119,14 +109,18 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
|
||||
_antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification);
|
||||
|
||||
|
||||
component.TraitorMinds.Add(mindId);
|
||||
|
||||
// Assign briefing
|
||||
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
|
||||
//Since this provides neither an antag/job prototype, nor antag status/roletype,
|
||||
//and is intrinsically related to the traitor role
|
||||
//it does not need to be a separate Mind Role Entity
|
||||
_roleSystem.MindHasRole<TraitorRoleComponent>(mindId, out var traitorRole);
|
||||
if (traitorRole is not null)
|
||||
{
|
||||
Briefing = briefing
|
||||
}, mind, true);
|
||||
AddComp<RoleBriefingComponent>(traitorRole.Value.Owner);
|
||||
Comp<RoleBriefingComponent>(traitorRole.Value.Owner).Briefing = briefing;
|
||||
}
|
||||
|
||||
// Send codewords to only the traitor client
|
||||
var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Announcements.Systems;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
@@ -13,6 +14,7 @@ using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -22,26 +24,42 @@ namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly ZombieSystem _zombie = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly AnnouncerSystem _announcer = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly ZombieSystem _zombie = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InitialInfectedRoleComponent, GetBriefingEvent>(OnGetBriefing);
|
||||
SubscribeLocalEvent<ZombieRoleComponent, GetBriefingEvent>(OnGetBriefing);
|
||||
SubscribeLocalEvent<IncurableZombieComponent, ZombifySelfActionEvent>(OnZombifySelf);
|
||||
}
|
||||
|
||||
protected override void AppendRoundEndText(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule,
|
||||
private void OnGetBriefing(Entity<InitialInfectedRoleComponent> role, ref GetBriefingEvent args)
|
||||
{
|
||||
if (!_roles.MindHasRole<ZombieRoleComponent>(args.Mind.Owner))
|
||||
args.Append(Loc.GetString("zombie-patientzero-role-greeting"));
|
||||
}
|
||||
|
||||
private void OnGetBriefing(Entity<ZombieRoleComponent> role, ref GetBriefingEvent args)
|
||||
{
|
||||
args.Append(Loc.GetString("zombie-infection-greeting"));
|
||||
}
|
||||
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
ZombieRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
base.AppendRoundEndText(uid, component, gameRule, ref args);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
namespace Content.Server.Ghost
|
||||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Ghost;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to mark Observers properly, as they get Minds
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ObserverRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used to mark Observers properly, as they get Minds
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ObserverRoleComponent : Component
|
||||
{
|
||||
public string Name => Loc.GetString("observer-role-name");
|
||||
}
|
||||
public string Name => Loc.GetString("observer-role-name");
|
||||
}
|
||||
|
||||
@@ -2,99 +2,113 @@
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Shared.Customization.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Ghost.Roles.Components
|
||||
namespace Content.Server.Ghost.Roles.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(GhostRoleSystem))]
|
||||
public sealed partial class GhostRoleComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
[Access(typeof(GhostRoleSystem))]
|
||||
public sealed partial class GhostRoleComponent : Component
|
||||
[DataField("name")] private string _roleName = "Unknown";
|
||||
|
||||
[DataField("description")] private string _roleDescription = "Unknown";
|
||||
|
||||
[DataField("rules")] private string _roleRules = "ghost-role-component-default-rules";
|
||||
|
||||
// Actually make use of / enforce this requirement?
|
||||
// Why is this even here.
|
||||
// Move to ghost role prototype & respect CCvars.GameRoleTimerOverride
|
||||
[DataField("requirements")]
|
||||
public List<CharacterRequirement>? Requirements;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the <see cref="MakeSentientCommand"/> should run on the mob.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("makeSentient")]
|
||||
public bool MakeSentient = true;
|
||||
|
||||
/// <summary>
|
||||
/// The probability that this ghost role will be available after init.
|
||||
/// Used mostly for takeover roles that want some probability of being takeover, but not 100%.
|
||||
/// </summary>
|
||||
[DataField("prob")]
|
||||
public float Probability = 1f;
|
||||
|
||||
// We do this so updating RoleName and RoleDescription in VV updates the open EUIs.
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public string RoleName
|
||||
{
|
||||
[DataField("name")] private string _roleName = "Unknown";
|
||||
|
||||
[DataField("description")] private string _roleDescription = "Unknown";
|
||||
|
||||
[DataField("rules")] private string _roleRules = "ghost-role-component-default-rules";
|
||||
|
||||
[DataField("requirements")]
|
||||
public List<CharacterRequirement>? Requirements;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the <see cref="MakeSentientCommand"/> should run on the mob.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("makeSentient")]
|
||||
public bool MakeSentient = true;
|
||||
|
||||
/// <summary>
|
||||
/// The probability that this ghost role will be available after init.
|
||||
/// Used mostly for takeover roles that want some probability of being takeover, but not 100%.
|
||||
/// </summary>
|
||||
[DataField("prob")]
|
||||
public float Probability = 1f;
|
||||
|
||||
// We do this so updating RoleName and RoleDescription in VV updates the open EUIs.
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public string RoleName
|
||||
get => Loc.GetString(_roleName);
|
||||
set
|
||||
{
|
||||
get => Loc.GetString(_roleName);
|
||||
set
|
||||
{
|
||||
_roleName = value;
|
||||
EntitySystem.Get<GhostRoleSystem>().UpdateAllEui();
|
||||
}
|
||||
_roleName = value;
|
||||
IoCManager.Resolve<IEntityManager>().System<GhostRoleSystem>().UpdateAllEui();
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public string RoleDescription
|
||||
{
|
||||
get => Loc.GetString(_roleDescription);
|
||||
set
|
||||
{
|
||||
_roleDescription = value;
|
||||
EntitySystem.Get<GhostRoleSystem>().UpdateAllEui();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public string RoleRules
|
||||
{
|
||||
get => Loc.GetString(_roleRules);
|
||||
set
|
||||
{
|
||||
_roleRules = value;
|
||||
EntitySystem.Get<GhostRoleSystem>().UpdateAllEui();
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("allowSpeech")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AllowSpeech { get; set; } = true;
|
||||
|
||||
[DataField("allowMovement")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AllowMovement { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool Taken { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public uint Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reregisters the ghost role when the current player ghosts.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("reregister")]
|
||||
public bool ReregisterOnGhost { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// If set, ghost role is raffled, otherwise it is first-come-first-serve.
|
||||
/// </summary>
|
||||
[DataField("raffle")]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public GhostRoleRaffleConfig? RaffleConfig { get; set; }
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public string RoleDescription
|
||||
{
|
||||
get => Loc.GetString(_roleDescription);
|
||||
set
|
||||
{
|
||||
_roleDescription = value;
|
||||
IoCManager.Resolve<IEntityManager>().System<GhostRoleSystem>().UpdateAllEui();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public string RoleRules
|
||||
{
|
||||
get => Loc.GetString(_roleRules);
|
||||
set
|
||||
{
|
||||
_roleRules = value;
|
||||
IoCManager.Resolve<IEntityManager>().System<GhostRoleSystem>().UpdateAllEui();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mind roles that will be added to the mob's mind entity
|
||||
/// </summary>
|
||||
[DataField, Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // Don't make eye contact
|
||||
public List<EntProtoId> MindRoles = new() { "MindRoleGhostRoleNeutral" };
|
||||
|
||||
[DataField]
|
||||
public bool AllowSpeech { get; set; } = true;
|
||||
|
||||
[DataField]
|
||||
public bool AllowMovement { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool Taken { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public uint Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reregisters the ghost role when the current player ghosts.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("reregister")]
|
||||
public bool ReregisterOnGhost { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// If set, ghost role is raffled, otherwise it is first-come-first-serve.
|
||||
/// </summary>
|
||||
[DataField("raffle")]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||
public GhostRoleRaffleConfig? RaffleConfig { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Job the entity will receive after adding the mind.
|
||||
/// </summary>
|
||||
[DataField("job")]
|
||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // also FIXME Friends
|
||||
public ProtoId<JobPrototype>? JobProto = null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace Content.Server.Ghost.Roles.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Ghost.Roles.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for a ghost role which can be toggled on and off at will, like a PAI.
|
||||
@@ -6,33 +9,81 @@
|
||||
[RegisterComponent, Access(typeof(ToggleableGhostRoleSystem))]
|
||||
public sealed partial class ToggleableGhostRoleComponent : Component
|
||||
{
|
||||
[DataField("examineTextMindPresent")]
|
||||
/// <summary>
|
||||
/// The text shown on the entity's Examine when it is controlled by a player
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string ExamineTextMindPresent = string.Empty;
|
||||
|
||||
[DataField("examineTextMindSearching")]
|
||||
/// <summary>
|
||||
/// The text shown on the entity's Examine when it is waiting for a controlling player
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string ExamineTextMindSearching = string.Empty;
|
||||
|
||||
[DataField("examineTextNoMind")]
|
||||
/// <summary>
|
||||
/// The text shown on the entity's Examine when it has no controlling player
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string ExamineTextNoMind = string.Empty;
|
||||
|
||||
[DataField("beginSearchingText")]
|
||||
/// <summary>
|
||||
/// The popup text when the entity (PAI/positronic brain) it is activated to seek a controlling player
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string BeginSearchingText = string.Empty;
|
||||
|
||||
[DataField("roleName")]
|
||||
/// <summary>
|
||||
/// The name shown on the Ghost Role list
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string RoleName = string.Empty;
|
||||
|
||||
[DataField("roleDescription")]
|
||||
/// <summary>
|
||||
/// The description shown on the Ghost Role list
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string RoleDescription = string.Empty;
|
||||
|
||||
[DataField("wipeVerbText")]
|
||||
/// <summary>
|
||||
/// The introductory message shown when trying to take the ghost role/join the raffle
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string RoleRules = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// A list of mind roles that will be added to the entity's mind
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<EntProtoId> MindRoles;
|
||||
|
||||
/// <summary>
|
||||
/// The displayed name of the verb to wipe the controlling player
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string WipeVerbText = string.Empty;
|
||||
|
||||
[DataField("wipeVerbPopup")]
|
||||
/// /// <summary>
|
||||
/// The popup message when wiping the controlling player
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string WipeVerbPopup = string.Empty;
|
||||
|
||||
[DataField("stopSearchVerbText")]
|
||||
/// <summary>
|
||||
/// The displayed name of the verb to stop searching for a controlling player
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string StopSearchVerbText = string.Empty;
|
||||
|
||||
[DataField("stopSearchVerbPopup")]
|
||||
/// /// <summary>
|
||||
/// The popup message when stopping to search for a controlling player
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string StopSearchVerbPopup = string.Empty;
|
||||
|
||||
/// /// <summary>
|
||||
/// The prototype ID of the job that will be given to the controlling mind
|
||||
/// </summary>
|
||||
[DataField("job")]
|
||||
public ProtoId<JobPrototype>? JobProto;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
namespace Content.Server.Ghost.Roles;
|
||||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Ghost.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for round end display of ghost roles.
|
||||
/// It may also be used to ensure some ghost roles count as antagonists in future.
|
||||
/// Added to mind role entities to tag that they are a ghostrole.
|
||||
/// It also holds the name for the round end display
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class GhostRoleMarkerRoleComponent : Component
|
||||
public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
[DataField("name")] public string? Name;
|
||||
//TODO does anything still use this? It gets populated by GhostRolesystem but I don't see anything ever reading it
|
||||
[DataField] public string? Name;
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Ghost.Roles.Events;
|
||||
using Content.Server.Ghost.Roles.Raffles;
|
||||
using Content.Shared.Ghost.Roles.Raffles;
|
||||
using Content.Server.Ghost.Roles.UI;
|
||||
using Content.Server.Mind.Commands;
|
||||
@@ -398,8 +397,8 @@ namespace Content.Server.Ghost.Roles
|
||||
if (raffle.AllMembers.Add(player) && raffle.AllMembers.Count > 1
|
||||
&& raffle.CumulativeTime.Add(raffle.JoinExtendsDurationBy) <= raffle.MaxDuration)
|
||||
{
|
||||
raffle.Countdown += raffle.JoinExtendsDurationBy;
|
||||
raffle.CumulativeTime += raffle.JoinExtendsDurationBy;
|
||||
raffle.Countdown += raffle.JoinExtendsDurationBy;
|
||||
raffle.CumulativeTime += raffle.JoinExtendsDurationBy;
|
||||
}
|
||||
|
||||
UpdateAllEui();
|
||||
@@ -504,10 +503,14 @@ namespace Content.Server.Ghost.Roles
|
||||
|
||||
var newMind = _mindSystem.CreateMind(player.UserId,
|
||||
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName);
|
||||
_roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName });
|
||||
|
||||
_mindSystem.SetUserId(newMind, player.UserId);
|
||||
_mindSystem.TransferTo(newMind, mob);
|
||||
|
||||
_roleSystem.MindAddRoles(newMind.Owner, role.MindRoles, newMind.Comp);
|
||||
|
||||
if (_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind!, out var markerRole))
|
||||
markerRole.Value.Comp2.Name = role.RoleName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -51,15 +51,20 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
|
||||
|
||||
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
|
||||
EnsureComp<GhostTakeoverAvailableComponent>(uid);
|
||||
|
||||
//GhostRoleComponent inherits custom settings from the ToggleableGhostRoleComponent
|
||||
ghostRole.RoleName = Loc.GetString(component.RoleName);
|
||||
ghostRole.RoleDescription = Loc.GetString(component.RoleDescription);
|
||||
ghostRole.RoleRules = Loc.GetString(component.RoleRules);
|
||||
ghostRole.JobProto = component.JobProto;
|
||||
ghostRole.MindRoles = component.MindRoles;
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, ToggleableGhostRoleComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
|
||||
if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString(component.ExamineTextMindPresent));
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared._Shitmed.Body.Events; // Shitmed Change
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Explosion;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
@@ -95,7 +96,7 @@ namespace Content.Server.Hands.Systems
|
||||
|
||||
// Break any pulls
|
||||
if (TryComp(uid, out PullerComponent? puller) && TryComp(puller.Pulling, out PullableComponent? pullable))
|
||||
_pullingSystem.TryStopPull(puller.Pulling.Value, pullable);
|
||||
_pullingSystem.TryStopPull(puller.Pulling.Value, pullable, ignoreGrab: true); // Goobstation edit added check for grab
|
||||
|
||||
var offsetRandomCoordinates = _transformSystem.GetMoverCoordinates(args.Target).Offset(_random.NextVector2(1f, 1.5f));
|
||||
if (!ThrowHeldItem(args.Target, offsetRandomCoordinates))
|
||||
@@ -202,6 +203,20 @@ namespace Content.Server.Hands.Systems
|
||||
if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player))
|
||||
return false;
|
||||
|
||||
// Goobstation start
|
||||
if (TryGetActiveItem(player, out var item) && TryComp<VirtualItemComponent>(item, out var virtComp))
|
||||
{
|
||||
var userEv = new VirtualItemDropAttemptEvent(virtComp.BlockingEntity, player, item.Value, true);
|
||||
RaiseLocalEvent(player, userEv);
|
||||
|
||||
var targEv = new VirtualItemDropAttemptEvent(virtComp.BlockingEntity, player, item.Value, true);
|
||||
RaiseLocalEvent(virtComp.BlockingEntity, targEv);
|
||||
|
||||
if (userEv.Cancelled || targEv.Cancelled)
|
||||
return false;
|
||||
}
|
||||
// Goobstation end
|
||||
|
||||
return ThrowHeldItem(player, coordinates);
|
||||
}
|
||||
|
||||
@@ -215,6 +230,18 @@ namespace Content.Server.Hands.Systems
|
||||
hands.ActiveHandEntity is not { } throwEnt ||
|
||||
!_actionBlockerSystem.CanThrow(player, throwEnt))
|
||||
return false;
|
||||
// Goobstation start added throwing for grabbed mobs, mnoved direction.
|
||||
var direction = _transformSystem.ToMapCoordinates(coordinates).Position - _transformSystem.GetWorldPosition(player);
|
||||
|
||||
if (TryComp<VirtualItemComponent>(throwEnt, out var virt))
|
||||
{
|
||||
var userEv = new VirtualItemThrownEvent(virt.BlockingEntity, player, throwEnt, direction);
|
||||
RaiseLocalEvent(player, userEv);
|
||||
|
||||
var targEv = new VirtualItemThrownEvent(virt.BlockingEntity, player, throwEnt, direction);
|
||||
RaiseLocalEvent(virt.BlockingEntity, targEv);
|
||||
}
|
||||
// Goobstation end
|
||||
|
||||
if (_timing.CurTime < hands.NextThrowTime)
|
||||
return false;
|
||||
@@ -230,7 +257,6 @@ namespace Content.Server.Hands.Systems
|
||||
throwEnt = splitStack.Value;
|
||||
}
|
||||
|
||||
var direction = coordinates.ToMapPos(EntityManager, _transformSystem) - Transform(player).WorldPosition;
|
||||
if (direction == Vector2.Zero)
|
||||
return true;
|
||||
|
||||
|
||||
@@ -18,8 +18,6 @@ using Content.Server.Maps;
|
||||
using Content.Server.Players.JobWhitelist;
|
||||
using Content.Server.MoMMI;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Players.JobWhitelist;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Server.Preferences.Managers;
|
||||
|
||||
@@ -4,7 +4,7 @@ using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Emp;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Server.Medical.CrewMonitoring;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Content.Server.Mind.Commands
|
||||
builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedEntity);
|
||||
|
||||
var roles = _entities.System<SharedRoleSystem>();
|
||||
foreach (var role in roles.MindGetAllRoles(mindId))
|
||||
foreach (var role in roles.MindGetAllRoleInfo(mindId))
|
||||
{
|
||||
builder.AppendFormat("{0} ", role.Name);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,15 @@ public sealed partial class NPCMeleeCombatComponent : Component
|
||||
|
||||
[ViewVariables]
|
||||
public CombatStatus Status = CombatStatus.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// Lava edit - how much seconds does it take for a mob to begin attacking once in range.
|
||||
/// This is to prevent instant attacks and give more time to dodge.
|
||||
/// </summary>
|
||||
// Lavaland Change Start
|
||||
[ViewVariables] public float ChargeupDelay = 1f;
|
||||
[ViewVariables] public float ChargeupTimer = 0f;
|
||||
// Lavaland Change end
|
||||
}
|
||||
|
||||
public enum CombatStatus : byte
|
||||
|
||||
@@ -54,11 +54,12 @@ public sealed partial class NPCCombatSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
Attack(uid, comp, curTime, physicsQuery, xformQuery);
|
||||
Attack(uid, comp, curTime, frameTime, physicsQuery, xformQuery); // Lavaland Change - added frameTime
|
||||
}
|
||||
}
|
||||
|
||||
private void Attack(EntityUid uid, NPCMeleeCombatComponent component, TimeSpan curTime, EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery)
|
||||
// Lavaland Change - added frameTime
|
||||
private void Attack(EntityUid uid, NPCMeleeCombatComponent component, TimeSpan curTime, float frameTime, EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
component.Status = CombatStatus.Normal;
|
||||
|
||||
@@ -106,6 +107,16 @@ public sealed partial class NPCCombatSystem
|
||||
if (weapon.NextAttack > curTime || !Enabled)
|
||||
return;
|
||||
|
||||
// Lavaland Change Start
|
||||
if (component.ChargeupTimer < component.ChargeupDelay)
|
||||
{
|
||||
component.ChargeupTimer += frameTime;
|
||||
return;
|
||||
}
|
||||
|
||||
component.ChargeupTimer = 0f;
|
||||
// Lavaland Change End
|
||||
|
||||
if (_random.Prob(component.MissChance) &&
|
||||
physicsQuery.TryGetComponent(component.Target, out var targetPhysics) &&
|
||||
targetPhysics.LinearVelocity.LengthSquared() != 0f)
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Content.Server.Objectives.Commands
|
||||
}
|
||||
|
||||
shell.WriteLine($"Objectives for player {player.UserId}:");
|
||||
var objectives = mind.AllObjectives.ToList();
|
||||
var objectives = mind.Objectives.ToList();
|
||||
if (objectives.Count == 0)
|
||||
{
|
||||
shell.WriteLine("None.");
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class HelpProgressConditionSystem : EntitySystem
|
||||
if (!TryComp<MindComponent>(traitor, out var mind))
|
||||
continue;
|
||||
|
||||
foreach (var objective in mind.AllObjectives)
|
||||
foreach (var objective in mind.Objectives)
|
||||
{
|
||||
if (HasComp<HelpProgressConditionComponent>(objective))
|
||||
removeList.Add(traitor);
|
||||
@@ -88,7 +88,7 @@ public sealed class HelpProgressConditionSystem : EntitySystem
|
||||
|
||||
if (TryComp<MindComponent>(target, out var mind))
|
||||
{
|
||||
foreach (var objective in mind.AllObjectives)
|
||||
foreach (var objective in mind.Objectives)
|
||||
{
|
||||
// this has the potential to loop forever, anything setting target has to check that there is no HelpProgressCondition.
|
||||
var info = _objectives.GetInfo(objective, target, mind);
|
||||
|
||||
@@ -90,7 +90,7 @@ public sealed class KillPersonConditionSystem : EntitySystem
|
||||
foreach (var mind in allHumans)
|
||||
{
|
||||
// RequireAdminNotify used as a cheap way to check for command department
|
||||
if (_job.MindTryGetJob(mind, out _, out var prototype) && prototype.RequireAdminNotify)
|
||||
if (_job.MindTryGetJob(mind, out var prototype) && prototype.RequireAdminNotify)
|
||||
allHeads.Add(mind);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Warps;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Roles;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
@@ -16,6 +17,7 @@ public sealed class NinjaConditionsSystem : EntitySystem
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly NumberObjectiveSystem _number = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -46,10 +48,8 @@ public sealed class NinjaConditionsSystem : EntitySystem
|
||||
// spider charge
|
||||
private void OnSpiderChargeRequirementCheck(EntityUid uid, SpiderChargeConditionComponent comp, ref RequirementCheckEvent args)
|
||||
{
|
||||
if (args.Cancelled || !HasComp<NinjaRoleComponent>(args.MindId))
|
||||
{
|
||||
if (args.Cancelled || !_roles.MindHasRole<NinjaRoleComponent>(args.MindId))
|
||||
return;
|
||||
}
|
||||
|
||||
// choose spider charge detonation point
|
||||
var warps = new List<EntityUid>();
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed class NotCommandRequirementSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// cheap equivalent to checking that job department is command, since all command members require admin notification when leaving
|
||||
if (_job.MindTryGetJob(args.MindId, out _, out var prototype) && prototype.RequireAdminNotify)
|
||||
if (_job.MindTryGetJob(args.MindId, out var prototype) && prototype.RequireAdminNotify)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Content.Server.Objectives.Systems;
|
||||
/// </summary>
|
||||
public sealed class NotJobRequirementSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -20,11 +22,10 @@ public sealed class NotJobRequirementSystem : EntitySystem
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
// if player has no job then don't care
|
||||
if (!TryComp<JobComponent>(args.MindId, out var job))
|
||||
return;
|
||||
_jobs.MindTryGetJob(args.MindId, out var proto);
|
||||
|
||||
if (job.Prototype == comp.Job)
|
||||
// if player has no job then don't care
|
||||
if (proto is not null && proto.ID == comp.Job)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed class ObjectiveBlacklistRequirementSystem : EntitySystem
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
foreach (var objective in args.Mind.AllObjectives)
|
||||
foreach (var objective in args.Mind.Objectives)
|
||||
{
|
||||
if (_whitelistSystem.IsBlacklistPass(comp.Blacklist, objective))
|
||||
{
|
||||
|
||||
@@ -22,8 +22,6 @@ public sealed class RoleRequirementSystem : EntitySystem
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
// this whitelist trick only works because roles are components on the mind and not entities
|
||||
// if that gets reworked then this will need changing
|
||||
if (_whitelistSystem.IsWhitelistFail(comp.Roles, args.MindId))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
@@ -31,17 +31,16 @@ namespace Content.Server.Players.PlayTimeTracking;
|
||||
/// </summary>
|
||||
public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IAfkManager _afk = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly MindSystem _minds = default!;
|
||||
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly CharacterRequirementsSystem _characterRequirements = default!;
|
||||
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -52,8 +51,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<RoleAddedEvent>(OnRoleAdd);
|
||||
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleRemove);
|
||||
SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<AFKEvent>(OnAFK);
|
||||
SubscribeLocalEvent<UnAFKEvent>(OnUnAFK);
|
||||
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
|
||||
@@ -105,10 +104,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
|
||||
public IEnumerable<string> GetTimedRoles(EntityUid mindId)
|
||||
{
|
||||
var ev = new MindGetAllRolesEvent(new List<RoleInfo>());
|
||||
RaiseLocalEvent(mindId, ref ev);
|
||||
|
||||
foreach (var role in ev.Roles)
|
||||
foreach (var role in _roles.MindGetAllRoleInfo(mindId))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(role.PlayTimeTrackerId))
|
||||
continue;
|
||||
@@ -127,13 +123,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
return GetTimedRoles(contentData.Mind.Value);
|
||||
}
|
||||
|
||||
private void OnRoleRemove(RoleRemovedEvent ev)
|
||||
{
|
||||
if (_minds.TryGetSession(ev.Mind, out var session))
|
||||
_tracking.QueueRefreshTrackers(session);
|
||||
}
|
||||
|
||||
private void OnRoleAdd(RoleAddedEvent ev)
|
||||
private void OnRoleEvent(RoleEvent ev)
|
||||
{
|
||||
if (_minds.TryGetSession(ev.Mind, out var session))
|
||||
_tracking.QueueRefreshTrackers(session);
|
||||
@@ -226,7 +216,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
job,
|
||||
EntityManager,
|
||||
_prototypes,
|
||||
_config,
|
||||
_cfg,
|
||||
out _);
|
||||
}
|
||||
|
||||
@@ -257,7 +247,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
job,
|
||||
EntityManager,
|
||||
_prototypes,
|
||||
_config,
|
||||
_cfg,
|
||||
out _))
|
||||
continue;
|
||||
|
||||
@@ -304,7 +294,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
jobber,
|
||||
EntityManager,
|
||||
_prototypes,
|
||||
_config,
|
||||
_cfg,
|
||||
out _))
|
||||
{
|
||||
jobs.RemoveSwap(i);
|
||||
|
||||
@@ -11,3 +11,5 @@ public sealed partial class CommandStaffComponent : Component
|
||||
public float PsionicBonusModifier = 1;
|
||||
public float PsionicBonusOffset = 0.25f;
|
||||
}
|
||||
|
||||
//TODO this should probably be on a mind role, not the mob
|
||||
|
||||
@@ -4,9 +4,9 @@ using Content.Shared.Roles;
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Role used to keep track of space dragons for antag purposes.
|
||||
/// Added to mind role entities to tag that they are a space dragon.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(DragonSystem)), ExclusiveAntagonist]
|
||||
public sealed partial class DragonRoleComponent : AntagonistRoleComponent
|
||||
[RegisterComponent, Access(typeof(DragonSystem))]
|
||||
public sealed partial class DragonRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
[RegisterComponent, ExclusiveAntagonist]
|
||||
public sealed partial class InitialInfectedRoleComponent : AntagonistRoleComponent
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are an initial infected.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -19,10 +19,25 @@ public sealed class JobSystem : SharedJobSystem
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MindComponent, MindRoleAddedEvent>(MindOnDoGreeting);
|
||||
SubscribeLocalEvent<RoleAddedEvent>(OnRoleAddedEvent);
|
||||
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleRemovedEvent);
|
||||
}
|
||||
|
||||
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, ref MindRoleAddedEvent args)
|
||||
private void OnRoleAddedEvent(RoleAddedEvent args)
|
||||
{
|
||||
MindOnDoGreeting(args.MindId, args.Mind, args);
|
||||
|
||||
if (args.RoleTypeUpdate)
|
||||
_roles.RoleUpdateMessage(args.Mind);
|
||||
}
|
||||
|
||||
private void OnRoleRemovedEvent(RoleRemovedEvent args)
|
||||
{
|
||||
if (args.RoleTypeUpdate)
|
||||
_roles.RoleUpdateMessage(args.Mind);
|
||||
}
|
||||
|
||||
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, RoleAddedEvent args)
|
||||
{
|
||||
if (args.Silent)
|
||||
return;
|
||||
@@ -30,7 +45,7 @@ public sealed class JobSystem : SharedJobSystem
|
||||
if (!_mind.TryGetSession(mindId, out var session))
|
||||
return;
|
||||
|
||||
if (!MindTryGetJob(mindId, out _, out var prototype))
|
||||
if (!MindTryGetJob(mindId, out var prototype))
|
||||
return;
|
||||
|
||||
_chat.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name",
|
||||
@@ -47,6 +62,6 @@ public sealed class JobSystem : SharedJobSystem
|
||||
if (MindHasJobWithId(mindId, jobPrototypeId))
|
||||
return;
|
||||
|
||||
_roles.MindAddRole(mindId, new JobComponent { Prototype = jobPrototypeId });
|
||||
_roles.MindAddJobRole(mindId, null, false, jobPrototypeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
[RegisterComponent, ExclusiveAntagonist]
|
||||
public sealed partial class NinjaRoleComponent : AntagonistRoleComponent
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a space ninja.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class NinjaRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ using Content.Shared.Roles;
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind entities to tag that they are a nuke operative.
|
||||
/// Added to mind role entities to tag that they are a nuke operative.
|
||||
/// </summary>
|
||||
[RegisterComponent, ExclusiveAntagonist]
|
||||
public sealed partial class NukeopsRoleComponent : AntagonistRoleComponent
|
||||
[RegisterComponent]
|
||||
public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user