Upstream stable sync (#1558)
* Adjust uplink buy button to be under item icon (#38596) * Adjusted uplink buy button to be under item icon * Put the discount subtext under the icon * Indent fixes, added margin --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Automatic changelog update * More guidebook fixes (#38699) * First commit - Resolving a few different pages, fixing typos and updating spellings and phrasing to be more coherent. * More slight changes * Reverting slightly the salvage page change * Fixing Science wording inconsistency * Few other small guidebook fixes - Updating NukeOps page to properly refer to "Corpsman" rather than "Agent". - Typo and slight wording change in Traitors.xml. - Updating small typos in Antagonists.xml and Zombies.xml pages. * Automatic changelog update * Fixes missing underwear FTLs (#38720) * Validate `ShaderPrototype` IDs (#38728) * Convert all shader prototype string literals to protoids in overlays * Convert more shader prototype literal strings to protoids * Convert ValidatePrototypeId to ProtoId * Later * Janitor Tool: Wire Brush (#38667) * Wow! It's -brush- * spacing. * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * fixed changes --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * [BUGFIX] Fixed revenant malfunction ability not working properly only MediBots and Stasis bed (#38664) * fixed * clean up * orks fix smart * review fix 1 * more requested changes * less cursed * more descriptive description * better wording * Automatic changelog update * Tiny Tiny Cleanup of the EyeClosingSystem. (#38734) Update EyeClosingSystem.cs * Typofixes for figurine dialogue (#38737) * Typofixes for figurine dialogue * Forgot two * fix: Use PredictedQueueDel for gib spell (#38729) * Fix human skin tone distribution (#38701) * fix: don't default to uncharged sprite state for cells (#38730) * Fix Hristov description - remove inaccurate technical specs (#38746) - Removes inaccurate 'armor piercing 14.5mm shells' reference - Replaces it with a more funny description, matching the style of the other snipers and guns - Keeps ammunition type '.60 anti-materiel ammo' specification - Fixes issue #38590 Co-authored-by: Arthur Fiorese de Andrade <aandrade@cmcxs.gov.br> * Validate `ProtoId`s in tests (#38745) * Convert string literals to protoids in Content.Tests * Convert string literals to protoids or consts in Content.IntegrationTests * Fix linter failures Tricksy static using misled me * Validate remaining `ProtoId` strings (#38747) Validate remaining ProtoId strings * feat: allow mopping evaporating puddles (#38743) * Automatic changelog update * Cleanup warning in StomachSystem (#38748) you did not see this * Vox scars (#38592) * Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl * Revert "Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl" This reverts commit c73da55ba3b39ddf93b493aecd85604c54dd8a15. * locale key fix * Changed top surgery scar names to be more generalized * Adjusted face scars * Formatting fixes --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Automatic changelog update * UnlockNode command to LEC. (#38751) * commit * Update UnlockNodeCommand.cs * commit * move command locale to its own file. * Update Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * fix: spellbooks can have infinite charges (#38376) * fix: spellbooks can have infinite charges * refactor: indicate infinite spellbook charges with null Not sure if I like this much better... * Automatic changelog update * Minor escape menu UX improvements (#38650) * Automatic changelog update * Meta.json cleanup - pins.rsi (#38781) cleanup * Log an error when adding an admin log containing a null char (#38782) * Log an error when adding an admin log containing a null char * Also log LogType and LogImpact * Remove them * Fix incorrect conditions on black pepper's forced cough (#38783) no one to blame but myself Co-authored-by: iaada <iaada@users.noreply.github.com> * Update Credits (#38786) Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> * Fixing incorrect headset being given to QM visitor (#38785) * Stasis bed cleanup and bugfixes. (#38762) * Stasis bed sent to shed * Code Review * Code Review 2 * Automatic changelog update * Cleanup warnings: CS0414 (#38793) Cleanup * Move moth movement modifiers to the Base (#38795) * fix centcom pda (#38796) * Catchable items, playable basketball (#37702) * catching * fix * improve * fix linter * cleanup * fix prediction * do the same here * fix comment * Automatic changelog update * Fix storage voice control bug after handssystem refactor (#38810) * Automatic changelog update * Reducing the amount of space in the gogo hat (#38808) Reducing the amount of space in the hat from 7*4 to 4*3 to make a bit more sense with the fact it is only a hat. Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> * Automatic changelog update * Golden pai (#38549) * Golden pai * Make golden PAI public and rare * Golden PAI = legendary salvage * Automatic changelog update * Cleanup: Remove ``TelephoneComponent.RequiresPower`` (#38823) * Cleanup * Forgot about yml * Branded lighters addition (#35514) * newlighters * port complete * donk pocket box addition * should fix test fail, proper credit * captalisation issue * fixed * typo corrected * removed accidental change to combat bakery * fixed lock message, made NT flippo start fueled. * Requested changes * fixed discount dan * moved matchbox * requested changes, discount dan more broken than ever (somehow) * unbungle * uhhhhh * maybe fixes? * changed inheritance * fixed enum? * nevermind * finally fixed * breaking changes * inheritance unbungled * Automatic changelog update * Cleanup: Remove ``TryInsert`` method from the ``DisposableSystem`` and use event subscriptions instead (#38819) Cleanup * JumpBoots Attempt №2 (#36862) Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: unknown <wainzor1337@gmail.com> Co-authored-by: ScarKy0 <scarky0@onet.eu> * Automatic changelog update * Replace `ValidatePrototypeId` uses with `ProtoId` or `EntProtoId` (#38814) * The easy ones * For certain values of easy * Easy test * Hair * Fix sandbox violations * Sort usings * Fix SSD sleep misprediction (#38831) init * New science unlock: the H.A.R.M.P.A.C.K (#38824) * Automatic changelog update * Fix: Add cluwne translation in ftl (#38835) Fix * Artifact glue reagent (#38670) * a * Revert "a" This reverts commit 2b9ba4ea67a9395d30b7ab37c8065f627f1a961a. * f * dev it * a? * ad * forgor * Revert "f" This reverts commit 39228c7cbe4d28ba43b73580b55e01c3979eb869. * derandomisation! * reviv * flavor * fixe * dwwasdwasdwa * dwasdwasdwas * fuck you * Automatic changelog update * Contraband category fixes (#38698) * Adding contraband to some items missing it - This includes updating contraband to follow more consistently where items are expected to be used - Fixing the spacing in arrays to be more consistent with other files * Updating PPD to be Eng contra * Automatic changelog update * Added Vox Heterochromia (#38595) * Added Vox Heterochromia * Forgot to change moth to vox * minor formatting fix --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Automatic changelog update * Make mopping predicted (and some other stuff) (#38749) * refactor: move puddle evaporation + absorbents to shared * refactor: move SolutionRegeneration to shared * refactor: make AbsorbentSystem visuals clientside * style: general formatting/cleanup on touched files - Few logical simplifications - Add field for hard-coded sparkle effect ent - Switch stuff to Entity<T> No actual prediction fixes in this commit (though in retrospect I should've done this commit last). * fix: use predicted variants for predicted code * fix: average out evaporation rates in mixtures * refactor: move SolutionPurge to shared * style: Basic SolutionPurgeComponent field cleanup * fix: general prediction + timing + networking fixes - Moves client side visuals back to shared because other players exist - Don't accumulate CurTime in Purge/RegenerationSystem - Network the next update field in Purge/RegenerationSystem to deal with UI mispredictions??? * fix: add udder bug workaround Not needed for SolutionPurgeSystem which doesn't resolve solutions (probably fine that SolutionPurgeSystem doesn't cache since it's much rarer, though it probably should), and likely not needed for AbsorbentSystem since it only resolves against puddles which, I don't think can be in containers. * fix: don't divide by zero for evaporation speed = 0. * refactor: revert evaporation changes Will cherry-pick these out in another PR. Also reverting the evaporation speed bugfix since it's easier to revert all at once. :) * fix: component cleanup; autopause fields, use ProtoID * fix: remove unused AbsorbentComponentState * fix: ProtoId is not string * refactor: move PuddleSystem.UpdateAppearance to shared * style: general PuddleSystem.UpdateAppearance tweaks - Switch to Entity<T> - Use ProtoIds - Minor simplifications * fix: add udderly silly PVS workaround * cleanup * fix --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * fix: Register OnRequestMovePulledObject under the right system (#38484) * fix: Register OnRequestMovePulledObject under the right system * nukeops test flake * fix: correct ToggleableVisuals for toy sword and pirate helm (#38867) * Fix lingering ghost roles (and expand tests to catch it) (#38788) * Improve and expand `TakeRoleAndReturn` to fail on bug #38292 * fix #38292 and expanded test cases * use validated EntProtoIds for tests remove unusued using declarations * use const strings that match the TestPrototypes * Automatic changelog update * Fix non-humanoid mobs being destroyed on devour (#38087) * Allow non-preference living things to be added to a devourer's stomach * Fix ordering of devour logic * Minor refactor for whitelist on storage and food preference * Fix linter issue * Coerce workflow to run again; also fix bad indenting error * Code review changes * Automatic changelog update * Prefer file-scoped namespaces in `.editorconfig` (#38879) Prefer file-scoped namespaces in .editorconfig * Parroting Parrots part 1: Help maints! SQUAWK! Maints! (#38243) * parrots have ears. add poly * high tech parrot functionality * adjust times * add accent to radio message * don't spam everything all at once probably * learn about the existence of prob(float) * actually use Prob(float) correctly * newline * add pet spawner for poly * move chance to talk on radio to component * missing comment * minor edits and doc additions * the reviewerrrrrrr * parrot can't learn when crit or dead * increase default memory * rename poly to polly * crude way to ignore whispers. chatcode please * This is Polly. It is set to broadcast over the engineering frequency * add missing initialize * add displacement map for parrot ears * review comments - Errant * minor things * large rework * fix attempting to talk when entity has no channels * use list of active radios again to track channels * fix bad return, some comments * fix long learn cooldown * minor adjustments * use FromMinutes * the voices told me to make these changes * remove default reassignment * Review changes * remove polly's accent * decouple radio stuff from parrotsystem * minor stuff * split vocalization and parroting * minor review work * re-add missing check * add admin verb for clearing parrot messages * minor action icon update * oops * increase icon number text size * Admin erase parrot messages associated with players * part 1 beck review * add whitelist and blacklist for parrots * Downgrade missing component error to warning * Add comment * add some missing comments * Remove active radio entity tracking, use all inventory slots * Minor changes * small review stuff * review radio stuff * swap ears displacement to invisible death displacement * remove syncsprite * vscode why do yo have to hurt my feelings * review changes * use checkboth * Automatic changelog update * Predict RepairableSystem (#38886) * commit * Update SharedRepairableSystem.cs * compo * final touches. * Update RepairableComponent.cs * Update RepairableSystem.cs * Update Content.Shared/Repairable/RepairableSystem.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Replace usages of customTypeSerializer PrototypeIdListSerializer with something that doesn't take 20 separate words to type out (#37959) * Replace usages of customTypeSerializer PrototypeIdListSerializer with something that doesn't take 20 separate words to type out * Missed one * Missed another * Fix data field ids * Fix yaml linter and misc errors (#37444) * Fix yaml linter * Revert "fix cluwne pda pen slot (#35611)" This reverts commit66e926843f. * More fixes * Try again with the engine requirement removed * Decrease number of brass sheets dropped by clockwork windoors --------- Co-authored-by: Tayrtahn <tayrtahn@gmail.com> * Add VV button to the solution editor (#38889) add vv button to solution editor * Automatic changelog update * fix: wide swings with resistanceBypass now bypass resists (#38496) * Allow pAIs to emote like a borg (#38425) * Automatic changelog update * reduced motion flash effect version 3 (#37824) * V3 * Apply suggestions from code review * Automatic changelog update * feat: allow admins to interact under subfloors (#38813) * feat: allow admins to interact under subfloors * feat: use BypassInteractionChecks instead * Automatic changelog update * Operation Remove Gun Bloat (#38104) * Remove long-unused magazines Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Not sure why that failed, going to rerun tests real quick * Testing if this resolves the tests. Anyone know how to run tests locally, without waiting for github? Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Fully removed boxes of vector ammo and the vector ammo tag Signed-off-by: Nox38 <nebulousnox38@gmail.com> * This might fix the tests Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Nulled more = Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Nulled more migrations Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Minigun inhands + HMG multihand and slow move speed (#35344) * hmg move speed * minigun inhands * even better inhands * touch-up * Automatic changelog update * Add Bolas to SecTech vendor (#38902) * Automatic changelog update * Kobold/monkey AI holograms (#38888) * Added kobold/monkey AI holograms * Updated meta.json * Automatic changelog update * Fix localization error when trying to change hair on someone with a hat (#38907) * change Identity.Name to Identity.Entity for delivery popups (#38909) * Replace `AdvertiseComponent` with `DatasetVocalizerComponent` (#38887) * Replace AdvertiseComponent with DatasetVocalizerComponent * No vocalizing while broken or without power * Kill AdvertiseComponent/System * This really shouldn't be here * xmldoc for VocalizerRequiresPowerComponent * TryIndex -> Index * Bagel genpop (#38829) * skelaton (skeleton) * basic genpop * genpop mostly done * general layout * all details * maints firelocks, tweaks * invalids bye, remove residual air and fire alarm * uhh tweaked something dont remember * remove actions * armory: truncheon and riot crate * remove more invalid devices * armory holopad, graffiti tweak * no windoor into sec substation, add flasher * Revert "no windoor into sec substation, add flasher" This reverts commit a7b919fb74433b82f48f92f98d5521ecb1f3eae4. * no sub windoor, flasher * fix camera blocking turret * move warden and armory, restyle decals * maints flashlights :D * fix locker room dark tile placement * Automatic changelog update * fix clones not getting the thieving skill (#38914) * Automatic changelog update * Make more objects spray paintable (Reviving #31328) (#37341) * PaintableAirlockComponent and AirlockGroupPrototype have been replaced * Slightly redesigned SprayPainterSystem for greater versatility * Added handling of changes to the appearance of doors and storages * PaintableGroup prototypes have been created * Generating tabs with styles in the UI * Fix error with undiscovered layer * Slight improvement * Removed unnecessary property * The category for `PaintableGroup` was allocated to a separate prototype so that the engine itself would check if the category existed * Added canisters, but repainting doesn't work * Added localization to styles * Fix sprite changing * Added the ability to paint canisters * slight ui improvement * Fix yamllinter errors * Fix test * The UI now remembers which tab was open * Fix build (?) * Rename * Charges have been added to the spray painter * Added a charge texture for the spray painter * Now spray painter can paint decals * Increased number of charges * Spawning dummy objects has been replaced by PrototypeManager * added a signature about the painting of the object * fix * Code commenting * Fix upstream * Update Content.Shared/SprayPainter/Components/SprayPainterAmmo.cs Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> * review * Now decals can only be painted if the corresponding tab in the menu is open. * Fixed a bug with pipe and decal tabs not being remembered * Update EntityStorageVisualizerSystem.cs * record * loc * Cleanup * Revert electrified visuals * more cleanup, fix charges, del ammo4 * no empty file, remove meta component * closet exceptions, storage visualizer fixes * enable/disable decal through alt-verb * Fix missed merge conflicts * fix snap offset, button event handlers * simpler order, fix snap loc string * Remove PaintableViz.BaseRSI, no decal item, A-Z * State-respecting UI, BUI updates, FTL fixes * revert DecalPlacerWindow changes * revert unwanted changes, cleanup function order * Limit SprayPainterAmmo write access to AmmoSystem * Remove PaintedSystem * spray paint ammo lathe recipe, youtool listing * category as a list, groups as subtabs * Restore inhand copyright in meta.json * empty spray painter, recipe produces an empty one * allow alpha on spray painter decals * add comments * paintable wall lockers * Restrict painting more objects * Suggested event changes, event cleanup * component comments, fix ammo inhands * uncleanable decals, dirty styles on mapinit * organize paintables, separate emergency/closet grp * fix categories newline at EOF * airlock group whitespace cleanup * realphabetize * Clean up EntityStorageViz merge conflict markers * Apply requested changes * Apply suggestions from sowelipililimute's review Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> * betrayal most foul * Remove members from EntityPaintedEvent * No emerg. group, steelsec to secure, locker/closet * Enable repainting the medical wall locker * comments, no flags on PaintableVisuals * Remove locked variants from closets/wall closets * removable decals * off value consistency * can't paint away those bones * fix precedence * Remove AirlockDepartment, AirlockGroup protos Both unused. * whitelist consistency re: ammo component * add standing emergency closet styles * alphabetize the spray painter listings --------- Co-authored-by: Ertanic <black.ikra.14@gmail.com> Co-authored-by: Эдуард <36124833+Ertanic@users.noreply.github.com> Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> * Automatic changelog update * Add Serializable, NetSerializable attributes to DecalPaintMode (#38921) Serializable, Net" on DecalPaintMode * Rotated turret wall panel sprites (#38464) rotated sprites * Stun and Stamina Visuals (#37196) * Stun animation * Commit 2 * Almost working commit * Best commit * Minor cleanup and value adjustments * Fix animation data getting wasted and cleaned up some stuff * Don't animate if dead * AppearanceSystem is for chumps * Cleanup * More cleanup * More cleanup * Half working commit * Documentation * Works * ComponentHandleState my beloved * AppearanceComp compatibility * Address review * Borgar * AND NOW THE END IS NEAR * AppearanceSystem compliance (Real) * Don't need to log missing there * I actually hate mob prototypes so much you don't even know --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> * Automatic changelog update * Fix AddHandCommand not working on aghosts (#38866) * Component for clothes to suppress emotes and scream action in general, and the muzzle to suppress vocal emotes in particular (#32588) * Component for clothes to suppress scream noise GaggedComponent + AddGaggedClothingComponent and relevant systems to make them work. Currently only stifles the scream _action_, not all emotes because if a mime can silently emote, so can gagged you! * fix comments * swap to inventory relay and make it more general such that specific emotes or emotes of a given category can be blocked * power gloves shouldn't block snapping * easy fixes * blockable emote event * pr comments, switch to using emote event mostly * pr comments add beforeEmoteEvent add emote blocker name to popup maybe some other stuff, I forget * get rid of emoteevent's source because I don't need it anymore * smol clean * formatting, style, and one minor thing where having a muzzle in your pocket would gag you * Automatic changelog update * Tweaks to ShowRulesCommand structure, localization, and autocomplete. (#38855) commit * Automatic changelog update * Convert Locker/Closet fills to Entity Tables: Part 2 (#38254) Co-authored-by: Velcroboy <velcroboy333@hotmail.com> * Fixing a singular pixel on the frame of the AI (#38936) * Singular pixel ops * Third commit - upholding CC-BY-SA by indicating changes made * Bottle Drink Inhands (#38937) * there is now bottles of booze in your hands * Messed up the vermouth inhand fills slightly. * oh whoops that's redundant * Automatic changelog update * refactor: rework the new status effect system to use containers (#38915) * refactor: rework the new status effect system to use containers (#38915) (#38943) Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com> * fix: don't divide by zero in FragmentIntoProjectiles (#38946) * Accents Event to Shared (#38948) * 27 file diff * 27 file diff 2 * Apply suggestions from code review --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Update Credits (#38955) Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> * SharedGunSystem spread bugfix (#38960) two bytes * Fixed error thrown when examining indestructible plastitanium windows (#38950) * Fixed error thrown when examining indestructible plastitanium windows * Fixed indentation to match yaml standards * Fix extra dollar sign in admin log for machine toggle (#38961) * Don't compile EF Core designer files on release builds (#38927) * Hats (and glasses) for pets - Part 1 - Ian and McGriff (#38634) * new branch new me * cursed displacement * 2 pixels of death * forgot a line * fixed some edge cases on corgis with the displacement * fix conflict * fix indentations * a bit more displacement fixes * Automatic changelog update * [BUGFIX] Stops scurrets from suffocating in crates (#38951) Prevent scurrets from dying! * Automatic changelog update * Cleanup warnings: CS0649 (#38797) * make ocarina small (#38971) Update instruments_wind.yml * Automatic changelog update * Add contraband parent to war declarator (#38972) * Automatic changelog update * Remove prototype caching from `ZombieComponent` (#38979) Remove prototype caching from ZombieComponent * Vox customization additions (+eyeshadows) (#38906) vox customization stuff * Automatic changelog update * Refactor ExaminableDamage (#38978) * Remove prototype caching in ExaminableDamageComponent * Replace ExaminableDamagePrototype with LocalizedDatasetPrototype * Allow null * Voltage enum to shared (#38964) * fix: ghosts shouldn't see whisper obfuscation (#38202) * Fix devices in terminal mispredicting power state (#38647) * The simple solution * The better solution * Revert "The better solution" This reverts commit 611e56e031636734abab1ad3e77bf88b69a4de13. * make biogenerator not accept low-nutrient plants (#38427) * anti-noob * dsaswa * Minor grammar change --------- Co-authored-by: beck-thompson <beck314159@hotmail.com> * Make RunVerbAs take and return EntityUids (#38155) fix: make RunVerbAs take and return EntityUids * Automatic changelog update * Buff parrot learn rates and radio chatter (#38984) * buff parrots * ok but not that smart * oop * Remove prototype caching from `TransformableContainerComponent` (#38988) Remove prototype caching from TransformableContainer * New holy books (#38986) * Adds a few new holy books, implements one existing, and makes them pickable from loadout * Almost forgot the yaml * Forgot to change fork-specific paths * Modified copyright to be less specific as requested by maintainer * Newlines * Replaced scroll torah with a book-bound tanakh, as recommended by dvir001 * Changed all mentions to use tanakh instead of torah * Made the Mosin bayonet usable. (#38295) * Made mosin an alt-melee weapon, added reagent fill for the weapon. * fixes --------- Co-authored-by: RedBookcase <Usualmoves@gmail.com> Co-authored-by: beck-thompson <beck314159@hotmail.com> * Automatic changelog update * Adjust throwables hitboxes to match sprites (#38985) Initial commit * Automatic changelog update * Moproaches (#38700) * MOPROACHES * rerun test - random test fail of doom * added to random pest event, with same prob as cancer mouse * rerun test - random test fail of doom 2 electric boogaloo * changed moproach obtain methods * no crafting of moproaches allowed :( * Reverted content of file. * requested changes * requested changes * requested changes * Increase moproach health by 5 - requested by Emo on discord * spaaaaaaaaaaaaaaaaaaaaaaaaace * microwave recipe added * no surgery this week * spaaaaaaaaaaaaaaaaaaace 2 more space for your space * space * new mopshoes sprite * in-hands * inhands v2 --------- Co-authored-by: ScarKy0 <scarky0@onet.eu> * Automatic changelog update * Remove the Qur'an (#39000) * Mostly fix reaction sound effect stacking :( (#38999) * fix: band-aid the reaction sound effect stacking It's so funny I'm so sad I'm writing this commit :( * fix: remove unused hashset Drive by fix. 'Tis never read from. * fix: switch to just making it server only * fix: uncomment the if lol Commented it out for recording video oopsie * Convert some voice samples to mono (#39002) * Convert Xenoborg voice sounds to mono * Convert female human cough sounds to mono * PressureEvent removed (dead code) (#39004) yep * Predict DevourSystem. (#38970) Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Whitelist extension for tool belt (#35212) * sd * xd * im so dumb * revert to just remote signaller * forgot * add code comment --------- Co-authored-by: shampunj <140359015+shampunj@users.noreply.github.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * Fix firelocks failing to drop fast enough (#38918) * Automatic changelog update * Fix Mjollnir throw while on delay (#39018) * init * fuck dirty * yippee * Automatic changelog update * Give admin bags explosion resistance (#38384) Co-authored-by: youtissoum <51883137+youtissoum@users.noreply.github.com> * Automatic changelog update * Inconsistent Produce Inhands Fix (#38860) * V1 Sprites * Adding heldPrefix * Updated Sprites * 4 New Inhands & Sprite Tweak * WaterMelon/HolyMelon Front Change * Meta Guh * Sugarcane Updated Inhands * Re-id 'Medical Doctor' guide entry to 'MedicalDoctor' (#39029) chore: re-id 'Medical Doctor' guide entry * Make diagonal windows prevent electrocution (#39032) Add the window tag * Automatic changelog update * Carps Can No Longer Suicide (#39033) Update carp.yml * Wearable banana peels (#38868) * Add wearable helmet sprites for banana and mimana peels Made it so banana peels and their variants can be worn as head gear. The sprites were done by myself, so feel free to change or update them! Includes hamster compatibility. * fixed a minor state bug * Review fixes - Removed blank space - Added "FlipBrooke" to all modified meta.jsons, with the added note of which sprites I got the colors from/referenced. * I forgot a comma adds a comma and some periods * NPC spiders sometimes spin webs 🕷️🕸️ (#38319) * NPC spiders now spin webs * oops * move logic to always update next spawn, to prevent rare web spam * WebSpawnCooldown is timespan * remove vv * add web spawn method, no sus action event method call * dont spin web immediately at spawn * move NextWebSpawn value init to update * oop * remove unused game timing * web spawn cooldown to 45 * Automatic changelog update * Fix Respirator Asserts (#38911) * Fix errors * Cleanup CanMetabolizeInhaledAir * Wait no don't do that * Revert changes for real * Fix * Code cleanup and some safety rails * Better tests and also comments * Better comments --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> * Add supercritical sounds for ALL anomalies (#36425) * add supercritical sounds for flesh anomaly and shadow anomaly * fix attribution yml * use sound collections, add TWO -- count em TWO tech anomaly supercrit sounds. * add sound for present anomaly * wrong soundcollection for flesh * actually add the present anomaly sound... * add fire anom sound * add gravity anomaly supercritical sound * add electric anomaly supercritical sound * add bluespace anomaly supercritical and explosion sound * add ice anomaly supercritical sound * add fluid anomaly supercritical sound * fix busted link * typo * add floral supercritical sound, fix typos * Add supercritical sounds to injected anomalies * add rock anomaly supercritical sound * supercritical sound follows the entity, important for injected anomalies * make the rock crit rumble a little less subtle at the start * use CC0 freesound alternatives where available * New pyro anomaly sound by GonTar * New sounds by GonTar. Change AnomalyComponent so that you can change the length of supercritical properly in the prototype. * oopsie, forgot the traps * use TimeSpan for duration * Automatic changelog update * Metashield Rules Update (Removal) (#38953) * Revert "Don't compile EF Core designer files on release builds" (#39057) * Revert "Kobold/monkey AI holograms (#38888)" This reverts commit97fcebb92b. Please check the maintainer meeting and the recording at time 41:58 for further information https://docs.spacestation14.com/en/maintainer-meetings/maintainer-meeting-2025-07-19.html * Replace SharedStatusEffectsSystem with StatusEffectsSystem Updated references from SharedStatusEffectsSystem to StatusEffectsSystem in spell and magic weakness systems to use the correct status effects system implementation. * Update examine_messages.yml * fix * Update round_end.yml --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> Co-authored-by: Mora <46364955+TrixxedHeart@users.noreply.github.com> Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: Andrew Malcolm O'Neill <105134723+maland1@users.noreply.github.com> Co-authored-by: Minerva <218184747+mnva0@users.noreply.github.com> Co-authored-by: Tayrtahn <tayrtahn@gmail.com> Co-authored-by: Tiniest Shark <head.rebel@yahoo.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: Velken <8467292+Velken@users.noreply.github.com> Co-authored-by: Kyle Tyo <36606155+VerinSenpai@users.noreply.github.com> Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com> Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: Arthur Fiorese de Andrade <170277843+ADM2PLAY@users.noreply.github.com> Co-authored-by: Arthur Fiorese de Andrade <aandrade@cmcxs.gov.br> Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Co-authored-by: āda <ss.adasts@gmail.com> Co-authored-by: iaada <iaada@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: B_Kirill <153602297+B-Kirill@users.noreply.github.com> Co-authored-by: poklj <compgeek223@gmail.com> Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com> Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> Co-authored-by: AsnDen <75905158+AsnDen@users.noreply.github.com> Co-authored-by: Winkarst-cpu <74284083+Winkarst-cpu@users.noreply.github.com> Co-authored-by: Smugman <85798843+Smugman@users.noreply.github.com> Co-authored-by: Голубь <124601871+Golubgik@users.noreply.github.com> Co-authored-by: unknown <wainzor1337@gmail.com> Co-authored-by: ScarKy0 <scarky0@onet.eu> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: kosticia <kosticia46@gmail.com> Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com> Co-authored-by: Sparlight <twiksparlight@gmail.com> Co-authored-by: Crude Oil <124208219+CroilBird@users.noreply.github.com> Co-authored-by: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: Tobias Berger <toby@tobot.dev> Co-authored-by: Nox <nebulousnox38@gmail.com> Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com> Co-authored-by: Hitlinemoss <209321380+Hitlinemoss@users.noreply.github.com> Co-authored-by: qwerltaz <69696513+qwerltaz@users.noreply.github.com> Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com> Co-authored-by: Ertanic <black.ikra.14@gmail.com> Co-authored-by: Эдуард <36124833+Ertanic@users.noreply.github.com> Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> Co-authored-by: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: Centronias <me@centronias.com> Co-authored-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com> Co-authored-by: Velcroboy <velcroboy333@hotmail.com> Co-authored-by: Mono <182929384+Monotheonist@users.noreply.github.com> Co-authored-by: Fildrance <fildrance@gmail.com> Co-authored-by: Myra <vasilis@pikachu.systems> Co-authored-by: KOTOB <59124164+kotobdev@users.noreply.github.com> Co-authored-by: devinschubert14 <59582670+devinschubert14@users.noreply.github.com> Co-authored-by: SpaceManiac <tad@platymuus.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: ToastEnjoyer <masondoesgamingyes@gmail.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: UpAndLeaves <92269094+Alpha-Two@users.noreply.github.com> Co-authored-by: beck-thompson <beck314159@hotmail.com> Co-authored-by: Trosling <111116669+RHSvenson@users.noreply.github.com> Co-authored-by: RedBookcase <crazykid1590@gmail.com> Co-authored-by: RedBookcase <Usualmoves@gmail.com> Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Co-authored-by: Hannah Giovanna Dawson <karakkaraz@gmail.com> Co-authored-by: Łukasz Lindert <lukasz.lindert@protonmail.com> Co-authored-by: nabegator <stunprod228@outlook.com> Co-authored-by: shampunj <140359015+shampunj@users.noreply.github.com> Co-authored-by: youtissoum <51883137+youtissoum@users.noreply.github.com> Co-authored-by: Prole <172158352+Prole0@users.noreply.github.com> Co-authored-by: FungiFellow <151778459+FungiFellow@users.noreply.github.com> Co-authored-by: FlipBrooke <87075384+FlipBrooke@users.noreply.github.com> Co-authored-by: Admiral-Obvious-001 <89495925+Admiral-Obvious-001@users.noreply.github.com>
This commit is contained in:
@@ -77,6 +77,8 @@ csharp_style_expression_bodied_methods = false:suggestion
|
||||
#csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
|
||||
# Pattern matching preferences
|
||||
#csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
#csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
@@ -18,9 +19,11 @@ namespace Content.Benchmarks;
|
||||
[Virtual, MemoryDiagnoser]
|
||||
public class SpawnEquipDeleteBenchmark
|
||||
{
|
||||
private static readonly EntProtoId Mob = "MobHuman";
|
||||
private static readonly ProtoId<StartingGearPrototype> CaptainStartingGear = "CaptainGear";
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private StationSpawningSystem _spawnSys = default!;
|
||||
private const string Mob = "MobHuman";
|
||||
private StartingGearPrototype _gear = default!;
|
||||
private EntityUid _entity;
|
||||
private EntityCoordinates _coords;
|
||||
@@ -39,7 +42,7 @@ public class SpawnEquipDeleteBenchmark
|
||||
var mapData = await _pair.CreateTestMap();
|
||||
_coords = mapData.GridCoords;
|
||||
_spawnSys = server.System<StationSpawningSystem>();
|
||||
_gear = server.ProtoMan.Index<StartingGearPrototype>("CaptainGear");
|
||||
_gear = server.ProtoMan.Index(CaptainStartingGear);
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Content.Client.Access.UI;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
{
|
||||
private static readonly ProtoId<AccessGroupPrototype> GeneralAccessGroup = "General";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
private bool _isMonotone;
|
||||
@@ -63,7 +65,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
|
||||
// Ensure that the 'general' access group is added to handle
|
||||
// misc. access levels that aren't associated with any group
|
||||
if (_protoManager.TryIndex<AccessGroupPrototype>("General", out var generalAccessProto))
|
||||
if (_protoManager.TryIndex(GeneralAccessGroup, out var generalAccessProto))
|
||||
_groupedAccessLevels.TryAdd(generalAccessProto, new());
|
||||
|
||||
// Assign known access levels with their associated groups
|
||||
|
||||
@@ -8,6 +8,21 @@
|
||||
<OptionButton Name="SolutionOption" HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 4">
|
||||
<Button Name="VVButton"
|
||||
Text="{Loc 'admin-solutions-window-vv-button'}"
|
||||
ToolTip="{Loc 'admin-solutions-window-vv-button-tooltip'}"
|
||||
Disabled="True"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenRight"/>
|
||||
<Button Name="SolutionButton"
|
||||
Text="{Loc 'admin-solutions-window-solution-button'}"
|
||||
ToolTip="{Loc 'admin-solutions-window-solution-button-tooltip'}"
|
||||
Disabled="True"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- The total volume / capacity of the solution -->
|
||||
<BoxContainer Name="VolumeBox" Orientation="Vertical" HorizontalExpand="True" Margin="0 4"/>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
@@ -20,6 +21,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
|
||||
private NetEntity _target = NetEntity.Invalid;
|
||||
private string? _selectedSolution;
|
||||
@@ -34,6 +36,11 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
|
||||
SolutionOption.OnItemSelected += SolutionSelected;
|
||||
AddButton.OnPressed += OpenAddReagentWindow;
|
||||
VVButton.OnPressed += OpenVVWindow;
|
||||
SolutionButton.OnPressed += OpenSolutionWindow;
|
||||
|
||||
VVButton.Disabled = !_admin.CanViewVar();
|
||||
SolutionButton.Disabled = !_admin.CanViewVar();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
@@ -271,6 +278,32 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
_addReagentWindow.OpenCentered();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open the corresponding solution entity in a ViewVariables window.
|
||||
/// </summary>
|
||||
private void OpenVVWindow(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if (_solutions == null
|
||||
|| _selectedSolution == null
|
||||
|| !_solutions.TryGetValue(_selectedSolution, out var uid)
|
||||
|| !_entityManager.TryGetNetEntity(uid, out var netEntity))
|
||||
return;
|
||||
_consoleHost.ExecuteCommand($"vv {netEntity}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open the corresponding Solution instance in a ViewVariables window.
|
||||
/// </summary>
|
||||
private void OpenSolutionWindow(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if (_solutions == null
|
||||
|| _selectedSolution == null
|
||||
|| !_solutions.TryGetValue(_selectedSolution, out var uid)
|
||||
|| !_entityManager.TryGetNetEntity(uid, out var netEntity))
|
||||
return;
|
||||
_consoleHost.ExecuteCommand($"vv /entity/{netEntity}/Solution/Solution");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a new solution is selected, set _selectedSolution and update the reagent list.
|
||||
/// </summary>
|
||||
|
||||
@@ -62,11 +62,11 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<AnomalySupercriticalComponent, SpriteComponent>();
|
||||
var query = EntityQueryEnumerator<AnomalyComponent, AnomalySupercriticalComponent, SpriteComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var super, out var sprite))
|
||||
while (query.MoveNext(out var uid, out var anomaly, out var super, out var sprite))
|
||||
{
|
||||
var completion = 1f - (float)((super.EndTime - _timing.CurTime) / super.SupercriticalDuration);
|
||||
var completion = 1f - (float) ((super.EndTime - _timing.CurTime) / anomaly.SupercriticalDuration);
|
||||
var scale = completion * (super.MaxScaleAmount - 1f) + 1f;
|
||||
_sprite.SetScale((uid, sprite), new Vector2(scale, scale));
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.SprayPainter.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Used to change the appearance of gas canisters.
|
||||
/// </summary>
|
||||
public sealed class GasCanisterAppearanceSystem : VisualizerSystem<GasCanisterComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, GasCanisterComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!AppearanceSystem.TryGetData<string>(uid, PaintableVisuals.Prototype, out var protoName, args.Component) || args.Sprite is not { } old)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.HasIndex(protoName))
|
||||
return;
|
||||
|
||||
// Create the given prototype and get its first layer.
|
||||
var tempUid = Spawn(protoName);
|
||||
SpriteSystem.LayerSetRsiState(uid, 0, SpriteSystem.LayerGetRsiState(tempUid, 0));
|
||||
QueueDel(tempUid);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ namespace Content.Client.Atmos.Overlays
|
||||
{
|
||||
public sealed class GasTileOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
@@ -54,7 +56,7 @@ namespace Content.Client.Atmos.Overlays
|
||||
_mapManager = IoCManager.Resolve<IMapManager>();
|
||||
_mapSystem = entManager.System<SharedMapSystem>();
|
||||
_xformSys = xformSys;
|
||||
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_shader = protoMan.Index(UnshadedShader).Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
_gasCount = system.VisibleGasId.Length;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Shared.Bed;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Bed;
|
||||
|
||||
public sealed class StasisBedSystem : VisualizerSystem<StasisBedVisualsComponent>
|
||||
{
|
||||
protected override void OnAppearanceChange(EntityUid uid, StasisBedVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite != null
|
||||
&& AppearanceSystem.TryGetData<bool>(uid, StasisBedVisuals.IsOn, out var isOn, args.Component))
|
||||
{
|
||||
SpriteSystem.LayerSetVisible((uid, args.Sprite), StasisBedVisualLayers.IsOn, isOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum StasisBedVisualLayers : byte
|
||||
{
|
||||
IsOn,
|
||||
}
|
||||
6
Content.Client/Body/Systems/MetabolizerSystem.cs
Normal file
6
Content.Client/Body/Systems/MetabolizerSystem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Client.Body.Systems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class MetabolizerSystem : SharedMetabolizerSystem;
|
||||
@@ -18,14 +18,10 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
{
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
[ValidatePrototypeId<MixingCategoryPrototype>]
|
||||
private const string DefaultMixingCategory = "DummyMix";
|
||||
[ValidatePrototypeId<MixingCategoryPrototype>]
|
||||
private const string DefaultGrindCategory = "DummyGrind";
|
||||
[ValidatePrototypeId<MixingCategoryPrototype>]
|
||||
private const string DefaultJuiceCategory = "DummyJuice";
|
||||
[ValidatePrototypeId<MixingCategoryPrototype>]
|
||||
private const string DefaultCondenseCategory = "DummyCondense";
|
||||
private static readonly ProtoId<MixingCategoryPrototype> DefaultMixingCategory = "DummyMix";
|
||||
private static readonly ProtoId<MixingCategoryPrototype> DefaultGrindCategory = "DummyGrind";
|
||||
private static readonly ProtoId<MixingCategoryPrototype> DefaultJuiceCategory = "DummyJuice";
|
||||
private static readonly ProtoId<MixingCategoryPrototype> DefaultCondenseCategory = "DummyCondense";
|
||||
|
||||
private readonly Dictionary<string, List<ReagentSourceData>> _reagentSources = new();
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Content.Client.CombatMode
|
||||
{
|
||||
public sealed class ColoredScreenBorderOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "ColoredScreenBorder";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
@@ -16,7 +18,7 @@ namespace Content.Client.CombatMode
|
||||
public ColoredScreenBorderOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _prototypeManager.Index<ShaderPrototype>("ColoredScreenBorder").Instance();
|
||||
_shader = _prototypeManager.Index(Shader).Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -27,8 +27,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
|
||||
private EntityUid _owner;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
public const string NoBoardEffectId = "FlatpackerNoBoardEffect";
|
||||
public static readonly EntProtoId NoBoardEffectId = "FlatpackerNoBoardEffect";
|
||||
|
||||
private EntityUid? _currentBoard = EntityUid.Invalid;
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace Content.Client.Cooldown
|
||||
{
|
||||
public sealed class CooldownGraphic : Control
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "CooldownAnimation";
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
|
||||
@@ -15,7 +17,7 @@ namespace Content.Client.Cooldown
|
||||
public CooldownGraphic()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _protoMan.Index<ShaderPrototype>("CooldownAnimation").InstanceUnique();
|
||||
_shader = _protoMan.Index(Shader).InstanceUnique();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -33,8 +33,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
public readonly EntityUid Console;
|
||||
|
||||
[ValidatePrototypeId<LocalizedDatasetPrototype>]
|
||||
private const string ReasonPlaceholders = "CriminalRecordsWantedReasonPlaceholders";
|
||||
private static readonly ProtoId<LocalizedDatasetPrototype> ReasonPlaceholders = "CriminalRecordsWantedReasonPlaceholders";
|
||||
|
||||
public Action<uint?>? OnKeySelected;
|
||||
public Action<StationRecordFilterType, string>? OnFiltersChanged;
|
||||
@@ -296,7 +295,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
var field = "reason";
|
||||
var title = Loc.GetString("criminal-records-status-" + status.ToString().ToLower());
|
||||
var placeholders = _proto.Index<LocalizedDatasetPrototype>(ReasonPlaceholders);
|
||||
var placeholders = _proto.Index(ReasonPlaceholders);
|
||||
var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders))); // just funny it doesn't actually get used
|
||||
var prompt = Loc.GetString("criminal-records-console-reason");
|
||||
var entry = new QuickDialogEntry(field, QuickDialogEntryType.LongText, prompt, placeholder);
|
||||
|
||||
@@ -1,7 +1,126 @@
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Client.Stunnable;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Damage.Systems;
|
||||
|
||||
public sealed partial class StaminaSystem : SharedStaminaSystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!; // Clientside Stun System
|
||||
|
||||
private const string StaminaAnimationKey = "stamina";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StaminaComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<ActiveStaminaComponent, ComponentShutdown>(OnActiveStaminaShutdown);
|
||||
SubscribeLocalEvent<StaminaComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
}
|
||||
|
||||
protected override void OnStamHandleState(Entity<StaminaComponent> entity, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
base.OnStamHandleState(entity, ref args);
|
||||
|
||||
TryStartAnimation(entity);
|
||||
}
|
||||
|
||||
private void OnActiveStaminaShutdown(Entity<ActiveStaminaComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
// If we don't have active stamina, we shouldn't have stamina damage. If the update loop can trust it we can trust it.
|
||||
if (!TryComp<StaminaComponent>(entity, out var stamina))
|
||||
return;
|
||||
|
||||
StopAnimation((entity, stamina));
|
||||
}
|
||||
|
||||
protected override void OnShutdown(Entity<StaminaComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
base.OnShutdown(entity, ref args);
|
||||
|
||||
StopAnimation(entity);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(Entity<StaminaComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Dead)
|
||||
StopAnimation(ent);
|
||||
}
|
||||
|
||||
private void TryStartAnimation(Entity<StaminaComponent> entity)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(entity, out var sprite))
|
||||
return;
|
||||
|
||||
// If the animation is running, the system should update it accordingly
|
||||
// If we're below the threshold to animate, don't try to animate
|
||||
// If we're in stamcrit don't override it
|
||||
if (entity.Comp.AnimationThreshold > entity.Comp.StaminaDamage || _animation.HasRunningAnimation(entity, StaminaAnimationKey))
|
||||
return;
|
||||
|
||||
// Don't animate if we're dead
|
||||
if (_mobState.IsDead(entity))
|
||||
return;
|
||||
|
||||
entity.Comp.StartOffset = sprite.Offset;
|
||||
|
||||
PlayAnimation((entity, entity.Comp, sprite));
|
||||
}
|
||||
|
||||
private void StopAnimation(Entity<StaminaComponent, SpriteComponent?> entity)
|
||||
{
|
||||
if(!Resolve(entity, ref entity.Comp2))
|
||||
return;
|
||||
|
||||
_animation.Stop(entity.Owner, StaminaAnimationKey);
|
||||
entity.Comp1.StartOffset = entity.Comp2.Offset;
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(Entity<StaminaComponent> entity, ref AnimationCompletedEvent args)
|
||||
{
|
||||
if (args.Key != StaminaAnimationKey || !args.Finished || !TryComp<SpriteComponent>(entity, out var sprite))
|
||||
return;
|
||||
|
||||
// stop looping if we're below the threshold
|
||||
if (entity.Comp.AnimationThreshold > entity.Comp.StaminaDamage)
|
||||
{
|
||||
_animation.Stop(entity.Owner, StaminaAnimationKey);
|
||||
_sprite.SetOffset((entity, sprite), entity.Comp.StartOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<AnimationPlayerComponent>(entity))
|
||||
return;
|
||||
|
||||
PlayAnimation((entity, entity.Comp, sprite));
|
||||
}
|
||||
|
||||
private void PlayAnimation(Entity<StaminaComponent, SpriteComponent> entity)
|
||||
{
|
||||
var step = Math.Clamp((entity.Comp1.StaminaDamage - entity.Comp1.AnimationThreshold) /
|
||||
(entity.Comp1.CritThreshold - entity.Comp1.AnimationThreshold),
|
||||
0f,
|
||||
1f); // The things I do for project 0 warnings
|
||||
var frequency = entity.Comp1.FrequencyMin + step * entity.Comp1.FrequencyMod;
|
||||
var jitter = entity.Comp1.JitterAmplitudeMin + step * entity.Comp1.JitterAmplitudeMod;
|
||||
var breathing = entity.Comp1.BreathingAmplitudeMin + step * entity.Comp1.BreathingAmplitudeMod;
|
||||
|
||||
_animation.Play(entity.Owner,
|
||||
_stun.GetFatigueAnimation(entity.Comp2,
|
||||
frequency,
|
||||
entity.Comp1.Jitters,
|
||||
jitter * entity.Comp1.JitterMin,
|
||||
jitter * entity.Comp1.JitterMax,
|
||||
breathing,
|
||||
entity.Comp1.StartOffset,
|
||||
ref entity.Comp1.LastJitter),
|
||||
StaminaAnimationKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class DeliveryVisualizerSystem : VisualizerSystem<DeliveryComponen
|
||||
|
||||
if (!_prototype.TryIndex<JobIconPrototype>(job, out var icon))
|
||||
{
|
||||
SpriteSystem.LayerSetTexture((uid, args.Sprite), DeliveryVisualLayers.JobStamp, SpriteSystem.Frame0(_prototype.Index("JobIconUnknown")));
|
||||
SpriteSystem.LayerSetTexture((uid, args.Sprite), DeliveryVisualLayers.JobStamp, SpriteSystem.Frame0(_prototype.Index(UnknownIcon).Icon));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
using Content.Shared.Devour;
|
||||
|
||||
namespace Content.Client.Devour;
|
||||
public sealed class DevourSystem : SharedDevourSystem
|
||||
{
|
||||
}
|
||||
@@ -14,6 +14,8 @@ namespace Content.Client.DoAfter;
|
||||
|
||||
public sealed class DoAfterOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IGameTiming _timing;
|
||||
private readonly IPlayerManager _player;
|
||||
@@ -50,7 +52,7 @@ public sealed class DoAfterOverlay : Overlay
|
||||
var sprite = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/progress_bar.rsi"), "icon");
|
||||
_barTexture = _entManager.EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(sprite);
|
||||
|
||||
_unshadedShader = protoManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_unshadedShader = protoManager.Index(UnshadedShader).Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.SprayPainter.Prototypes;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Doors;
|
||||
|
||||
public sealed class DoorSystem : SharedDoorSystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -85,8 +86,8 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
if (!AppearanceSystem.TryGetData<DoorState>(entity, DoorVisuals.State, out var state, args.Component))
|
||||
state = DoorState.Closed;
|
||||
|
||||
if (AppearanceSystem.TryGetData<string>(entity, DoorVisuals.BaseRSI, out var baseRsi, args.Component))
|
||||
UpdateSpriteLayers((entity.Owner, args.Sprite), baseRsi);
|
||||
if (AppearanceSystem.TryGetData<string>(entity, PaintableVisuals.Prototype, out var prototype, args.Component))
|
||||
UpdateSpriteLayers((entity.Owner, args.Sprite), prototype);
|
||||
|
||||
if (_animationSystem.HasRunningAnimation(entity, DoorComponent.AnimationKey))
|
||||
_animationSystem.Stop(entity.Owner, DoorComponent.AnimationKey);
|
||||
@@ -139,14 +140,14 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSpriteLayers(Entity<SpriteComponent> sprite, string baseRsi)
|
||||
private void UpdateSpriteLayers(Entity<SpriteComponent> sprite, string targetProto)
|
||||
{
|
||||
if (!_resourceCache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res))
|
||||
{
|
||||
Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
|
||||
if (!_prototypeManager.TryIndex(targetProto, out var target))
|
||||
return;
|
||||
}
|
||||
|
||||
_sprite.SetBaseRsi(sprite.AsNullable(), res.RSI);
|
||||
if (!target.TryGetComponent(out SpriteComponent? targetSprite, _componentFactory))
|
||||
return;
|
||||
|
||||
_sprite.SetBaseRsi(sprite.AsNullable(), targetSprite.BaseRSI);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ namespace Content.Client.Drowsiness;
|
||||
|
||||
public sealed class DrowsinessOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "Drowsiness";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
private readonly SharedStatusEffectsSystem _statusEffects = default!;
|
||||
private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override bool RequestScreenTexture => true;
|
||||
@@ -31,9 +33,9 @@ public sealed class DrowsinessOverlay : Overlay
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
|
||||
_statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
|
||||
|
||||
_drowsinessShader = _prototypeManager.Index<ShaderPrototype>("Drowsiness").InstanceUnique();
|
||||
_drowsinessShader = _prototypeManager.Index(Shader).InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
|
||||
private DrowsinessOverlay _overlay = default!;
|
||||
|
||||
|
||||
@@ -12,13 +12,15 @@ namespace Content.Client.Drugs;
|
||||
|
||||
public sealed class RainbowOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "Rainbow";
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
private readonly SharedStatusEffectsSystem _statusEffects = default!;
|
||||
private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override bool RequestScreenTexture => true;
|
||||
@@ -39,9 +41,9 @@ public sealed class RainbowOverlay : Overlay
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
|
||||
_statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
|
||||
|
||||
_rainbowShader = _prototypeManager.Index<ShaderPrototype>("Rainbow").InstanceUnique();
|
||||
_rainbowShader = _prototypeManager.Index(Shader).InstanceUnique();
|
||||
_config.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Content.Client.Drunk;
|
||||
|
||||
public sealed class DrunkOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "Drunk";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
@@ -30,7 +32,7 @@ public sealed class DrunkOverlay : Overlay
|
||||
public DrunkOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_drunkShader = _prototypeManager.Index<ShaderPrototype>("Drunk").InstanceUnique();
|
||||
_drunkShader = _prototypeManager.Index(Shader).InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Content.Client.Explosion;
|
||||
[UsedImplicitly]
|
||||
public sealed class ExplosionOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
@@ -26,7 +28,7 @@ public sealed class ExplosionOverlay : Overlay
|
||||
public ExplosionOverlay(SharedAppearanceSystem appearanceSystem)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _proto.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_shader = _proto.Index(UnshadedShader).Instance();
|
||||
_transformSystem = _entMan.System<SharedTransformSystem>();
|
||||
_appearance = appearanceSystem;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace Content.Client.Eye.Blinding
|
||||
{
|
||||
public sealed class BlindOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> GreyscaleShader = "GreyscaleFullscreen";
|
||||
private static readonly ProtoId<ShaderPrototype> CircleShader = "CircleMask";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
@@ -27,8 +30,8 @@ namespace Content.Client.Eye.Blinding
|
||||
public BlindOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_greyscaleShader = _prototypeManager.Index<ShaderPrototype>("GreyscaleFullscreen").InstanceUnique();
|
||||
_circleMaskShader = _prototypeManager.Index<ShaderPrototype>("CircleMask").InstanceUnique();
|
||||
_greyscaleShader = _prototypeManager.Index(GreyscaleShader).InstanceUnique();
|
||||
_circleMaskShader = _prototypeManager.Index(CircleShader).InstanceUnique();
|
||||
}
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,9 @@ namespace Content.Client.Eye.Blinding
|
||||
{
|
||||
public sealed class BlurryVisionOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> CataractsShader = "Cataracts";
|
||||
private static readonly ProtoId<ShaderPrototype> CircleShader = "CircleMask";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -33,8 +36,8 @@ namespace Content.Client.Eye.Blinding
|
||||
public BlurryVisionOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_cataractsShader = _prototypeManager.Index<ShaderPrototype>("Cataracts").InstanceUnique();
|
||||
_circleMaskShader = _prototypeManager.Index<ShaderPrototype>("CircleMask").InstanceUnique();
|
||||
_cataractsShader = _prototypeManager.Index(CataractsShader).InstanceUnique();
|
||||
_circleMaskShader = _prototypeManager.Index(CircleShader).InstanceUnique();
|
||||
|
||||
_circleMaskShader.SetParameter("CircleMinDist", 0.0f);
|
||||
_circleMaskShader.SetParameter("CirclePow", NoMotion_Pow);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Flash;
|
||||
using Content.Shared.Flash.Components;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -11,25 +13,31 @@ namespace Content.Client.Flash
|
||||
{
|
||||
public sealed class FlashOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> FlashedEffectShader = "FlashedEffect";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
private readonly SharedFlashSystem _flash;
|
||||
private readonly StatusEffectsSystem _statusSys;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
private readonly ShaderInstance _shader;
|
||||
public float PercentComplete = 0.0f;
|
||||
private bool _reducedMotion;
|
||||
public float PercentComplete;
|
||||
public Texture? ScreenshotTexture;
|
||||
|
||||
public FlashOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _prototypeManager.Index<ShaderPrototype>("FlashedEffect").InstanceUnique();
|
||||
_shader = _prototypeManager.Index(FlashedEffectShader).InstanceUnique();
|
||||
_flash = _entityManager.System<SharedFlashSystem>();
|
||||
_statusSys = _entityManager.System<StatusEffectsSystem>();
|
||||
|
||||
_configManager.OnValueChanged(CCVars.ReducedMotion, (b) => { _reducedMotion = b; }, invokeImmediately: true);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -47,8 +55,8 @@ namespace Content.Client.Flash
|
||||
return;
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
var lastsFor = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds;
|
||||
var timeDone = (float) (curTime - time.Value.Item1).TotalSeconds;
|
||||
var lastsFor = (float)(time.Value.Item2 - time.Value.Item1).TotalSeconds;
|
||||
var timeDone = (float)(curTime - time.Value.Item1).TotalSeconds;
|
||||
|
||||
PercentComplete = timeDone / lastsFor;
|
||||
}
|
||||
@@ -74,10 +82,22 @@ namespace Content.Client.Flash
|
||||
return;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
_shader.SetParameter("percentComplete", PercentComplete);
|
||||
worldHandle.UseShader(_shader);
|
||||
worldHandle.DrawTextureRectRegion(ScreenshotTexture, args.WorldBounds);
|
||||
worldHandle.UseShader(null);
|
||||
if (_reducedMotion)
|
||||
{
|
||||
// TODO: This is a very simple placeholder.
|
||||
// Replace it with a proper shader once we come up with something good.
|
||||
// Turns out making an effect that is supposed to be a bright, sudden, and disorienting flash
|
||||
// not do any of that while also being equivalent in terms of game balance is hard.
|
||||
var alpha = 1 - MathF.Pow(PercentComplete, 8f); // similar falloff curve to the flash shader
|
||||
worldHandle.DrawRect(args.WorldBounds, new Color(0f, 0f, 0f, alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
_shader.SetParameter("percentComplete", PercentComplete);
|
||||
worldHandle.UseShader(_shader);
|
||||
worldHandle.DrawTextureRectRegion(ScreenshotTexture, args.WorldBounds);
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
|
||||
@@ -21,8 +21,7 @@ namespace Content.Client.Guidebook.Controls;
|
||||
[UsedImplicitly, GenerateTypedNameReferences]
|
||||
public sealed partial class GuideReagentReaction : BoxContainer, ISearchableControl
|
||||
{
|
||||
[ValidatePrototypeId<MixingCategoryPrototype>]
|
||||
private const string DefaultMixingCategory = "DummyMix";
|
||||
private static readonly ProtoId<MixingCategoryPrototype> DefaultMixingCategory = "DummyMix";
|
||||
|
||||
private readonly IPrototypeManager _protoMan;
|
||||
|
||||
@@ -55,7 +54,7 @@ public sealed partial class GuideReagentReaction : BoxContainer, ISearchableCont
|
||||
}
|
||||
else
|
||||
{
|
||||
mixingCategories.Add(protoMan.Index<MixingCategoryPrototype>(DefaultMixingCategory));
|
||||
mixingCategories.Add(protoMan.Index(DefaultMixingCategory));
|
||||
}
|
||||
SetMixingCategory(mixingCategories, prototype, sysMan);
|
||||
}
|
||||
|
||||
@@ -7,17 +7,14 @@ namespace Content.Client.Interactable.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class InteractionOutlineComponent : Component
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> ShaderInRange = "SelectionOutlineInrange";
|
||||
private static readonly ProtoId<ShaderPrototype> ShaderOutOfRange = "SelectionOutline";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private const float DefaultWidth = 1;
|
||||
|
||||
[ValidatePrototypeId<ShaderPrototype>]
|
||||
private const string ShaderInRange = "SelectionOutlineInrange";
|
||||
|
||||
[ValidatePrototypeId<ShaderPrototype>]
|
||||
private const string ShaderOutOfRange = "SelectionOutline";
|
||||
|
||||
private bool _inRange;
|
||||
private ShaderInstance? _shader;
|
||||
private int _lastRenderScale;
|
||||
@@ -65,7 +62,7 @@ namespace Content.Client.Interactable.Components
|
||||
{
|
||||
var shaderName = inRange ? ShaderInRange : ShaderOutOfRange;
|
||||
|
||||
var instance = _prototypeManager.Index<ShaderPrototype>(shaderName).InstanceUnique();
|
||||
var instance = _prototypeManager.Index(shaderName).InstanceUnique();
|
||||
instance.SetParameter("outline_width", DefaultWidth * renderScale);
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ namespace Content.Client.Interaction;
|
||||
/// </summary>
|
||||
public sealed class DragDropSystem : SharedDragDropSystem
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> ShaderDropTargetInRange = "SelectionOutlineInrange";
|
||||
|
||||
private static readonly ProtoId<ShaderPrototype> ShaderDropTargetOutOfRange = "SelectionOutline";
|
||||
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
@@ -54,12 +58,6 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
// mousedown event so it can be treated like a regular click
|
||||
private const float MaxMouseDownTimeForReplayingClick = 0.85f;
|
||||
|
||||
[ValidatePrototypeId<ShaderPrototype>]
|
||||
private const string ShaderDropTargetInRange = "SelectionOutlineInrange";
|
||||
|
||||
[ValidatePrototypeId<ShaderPrototype>]
|
||||
private const string ShaderDropTargetOutOfRange = "SelectionOutline";
|
||||
|
||||
/// <summary>
|
||||
/// Current entity being dragged around.
|
||||
/// </summary>
|
||||
@@ -113,8 +111,8 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
|
||||
Subs.CVar(_cfgMan, CCVars.DragDropDeadZone, SetDeadZone, true);
|
||||
|
||||
_dropTargetInRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetInRange).Instance();
|
||||
_dropTargetOutOfRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetOutOfRange).Instance();
|
||||
_dropTargetInRangeShader = _prototypeManager.Index(ShaderDropTargetInRange).Instance();
|
||||
_dropTargetOutOfRangeShader = _prototypeManager.Index(ShaderDropTargetOutOfRange).Instance();
|
||||
// needs to fire on mouseup and mousedown so we can detect a drag / drop
|
||||
CommandBinds.Builder
|
||||
.BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false, true), new[] { typeof(SharedInteractionSystem) })
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Content.Client.Light;
|
||||
/// </summary>
|
||||
public sealed class AmbientOcclusionOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
private static readonly ProtoId<ShaderPrototype> StencilMaskShader = "StencilMask";
|
||||
private static readonly ProtoId<ShaderPrototype> StencilEqualDrawShader = "StencilEqualDraw";
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
@@ -87,7 +91,7 @@ public sealed class AmbientOcclusionOverlay : Overlay
|
||||
args.WorldHandle.RenderInRenderTarget(_aoTarget,
|
||||
() =>
|
||||
{
|
||||
worldHandle.UseShader(_proto.Index<ShaderPrototype>("unshaded").Instance());
|
||||
worldHandle.UseShader(_proto.Index(UnshadedShader).Instance());
|
||||
var invMatrix = _aoTarget.GetWorldToLocalMatrix(viewport.Eye!, scale);
|
||||
|
||||
foreach (var entry in query.QueryAabb(mapId, worldBounds))
|
||||
@@ -110,7 +114,7 @@ public sealed class AmbientOcclusionOverlay : Overlay
|
||||
() =>
|
||||
{
|
||||
// Don't want lighting affecting it.
|
||||
worldHandle.UseShader(_proto.Index<ShaderPrototype>("unshaded").Instance());
|
||||
worldHandle.UseShader(_proto.Index(UnshadedShader).Instance());
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||
{
|
||||
@@ -131,11 +135,11 @@ public sealed class AmbientOcclusionOverlay : Overlay
|
||||
}, Color.Transparent);
|
||||
|
||||
// Draw the stencil texture to depth buffer.
|
||||
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilMask").Instance());
|
||||
worldHandle.UseShader(_proto.Index(StencilMaskShader).Instance());
|
||||
worldHandle.DrawTextureRect(_aoStencilTarget!.Texture, worldBounds);
|
||||
|
||||
// Draw the Blurred AO texture finally.
|
||||
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilEqualDraw").Instance());
|
||||
worldHandle.UseShader(_proto.Index(StencilEqualDrawShader).Instance());
|
||||
worldHandle.DrawTextureRect(_aoTarget!.Texture, worldBounds, color);
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
|
||||
@@ -238,6 +238,9 @@ namespace Content.Client.Light.Components
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
// This is very janky. This could easily result in no visible animation at all if the random values happen
|
||||
// to all be close to each other.
|
||||
// TODO ANIMATIONS
|
||||
_randomValue1 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
|
||||
_randomValue2 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
|
||||
_randomValue3 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Content.Client.Light;
|
||||
|
||||
public sealed class SunShadowOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> MixShader = "Mix";
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
@@ -150,7 +152,7 @@ public sealed class SunShadowOverlay : Overlay
|
||||
viewport.LightRenderTarget.GetWorldToLocalMatrix(eye, scale);
|
||||
worldHandle.SetTransform(invMatrix);
|
||||
|
||||
var maskShader = _protoManager.Index<ShaderPrototype>("Mix").Instance();
|
||||
var maskShader = _protoManager.Index(MixShader).Instance();
|
||||
worldHandle.UseShader(maskShader);
|
||||
|
||||
worldHandle.DrawTextureRect(_target.Texture, worldBounds, Color.Black.WithAlpha(alpha));
|
||||
|
||||
@@ -104,8 +104,7 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
private bool _isDirty;
|
||||
|
||||
[ValidatePrototypeId<GuideEntryPrototype>]
|
||||
private const string DefaultSpeciesGuidebook = "Species";
|
||||
private static readonly ProtoId<GuideEntryPrototype> DefaultSpeciesGuidebook = "Species";
|
||||
|
||||
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
|
||||
|
||||
@@ -810,9 +809,9 @@ namespace Content.Client.Lobby.UI
|
||||
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
var page = DefaultSpeciesGuidebook;
|
||||
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
|
||||
page = species;
|
||||
page = new ProtoId<GuideEntryPrototype>(species.Id); // Gross. See above todo comment.
|
||||
|
||||
if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
|
||||
if (_prototypeManager.TryIndex(DefaultSpeciesGuidebook, out var guideRoot))
|
||||
{
|
||||
var dict = new Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>();
|
||||
dict.Add(DefaultSpeciesGuidebook, guideRoot);
|
||||
@@ -1464,17 +1463,13 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
return;
|
||||
}
|
||||
var hairMarking = Profile.Appearance.HairStyleId switch
|
||||
{
|
||||
HairStyles.DefaultHairStyle => new List<Marking>(),
|
||||
_ => new() { new(Profile.Appearance.HairStyleId, new List<Color>() { Profile.Appearance.HairColor }) },
|
||||
};
|
||||
var hairMarking = Profile.Appearance.HairStyleId == HairStyles.DefaultHairStyle
|
||||
? new List<Marking>()
|
||||
: new() { new(Profile.Appearance.HairStyleId, new List<Color>() { Profile.Appearance.HairColor }) };
|
||||
|
||||
var facialHairMarking = Profile.Appearance.FacialHairStyleId switch
|
||||
{
|
||||
HairStyles.DefaultFacialHairStyle => new List<Marking>(),
|
||||
_ => new() { new(Profile.Appearance.FacialHairStyleId, new List<Color>() { Profile.Appearance.FacialHairColor }) },
|
||||
};
|
||||
var facialHairMarking = Profile.Appearance.FacialHairStyleId == HairStyles.DefaultFacialHairStyle
|
||||
? new List<Marking>()
|
||||
: new() { new(Profile.Appearance.FacialHairStyleId, new List<Color>() { Profile.Appearance.FacialHairColor }) };
|
||||
|
||||
HairStylePicker.UpdateData(
|
||||
hairMarking,
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Content.Client.Mapping;
|
||||
|
||||
public sealed class MappingOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
@@ -35,7 +37,7 @@ public sealed class MappingOverlay : Overlay
|
||||
_sprite = _entities.System<SpriteSystem>();
|
||||
|
||||
_state = state;
|
||||
_shader = _prototypes.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_shader = _prototypes.Index(UnshadedShader).Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Content.Client.Movement.Systems;
|
||||
|
||||
public sealed class FloorOcclusionSystem : SharedFloorOcclusionSystem
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> HorizontalCut = "HorizontalCut";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private EntityQuery<SpriteComponent> _spriteQuery;
|
||||
@@ -48,7 +50,7 @@ public sealed class FloorOcclusionSystem : SharedFloorOcclusionSystem
|
||||
if (!_spriteQuery.Resolve(sprite.Owner, ref sprite.Comp, false))
|
||||
return;
|
||||
|
||||
var shader = _proto.Index<ShaderPrototype>("HorizontalCut").Instance();
|
||||
var shader = _proto.Index(HorizontalCut).Instance();
|
||||
|
||||
if (sprite.Comp.PostShader is not null && sprite.Comp.PostShader != shader)
|
||||
return;
|
||||
|
||||
@@ -23,8 +23,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string Action = "ActionClearNetworkLinkOverlays";
|
||||
private static readonly EntProtoId Action = "ActionClearNetworkLinkOverlays";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
Resizable="False">
|
||||
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="4" MinWidth="150">
|
||||
<changelog:ChangelogButton Access="Public" Name="ChangelogButton"/>
|
||||
<ui:VoteCallMenuButton />
|
||||
<Button Access="Public" Name="OptionsButton" Text="{Loc 'ui-escape-options'}" />
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 2.5 0 2.5" />
|
||||
<Button Access="Public" Name="RulesButton" Text="{Loc 'ui-escape-rules'}" />
|
||||
<Button Access="Public" Name="GuidebookButton" Text="{Loc 'ui-escape-guidebook'}" />
|
||||
<Button Access="Public" Name="WikiButton" Text="{Loc 'ui-escape-wiki'}" />
|
||||
<changelog:ChangelogButton Access="Public" Name="ChangelogButton" />
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 2.5 0 2.5" />
|
||||
<Button Access="Public" Name="OptionsButton" Text="{Loc 'ui-escape-options'}" />
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 2.5 0 2.5" />
|
||||
<Button Access="Public" Name="DisconnectButton" Text="{Loc 'ui-escape-disconnect'}" />
|
||||
<Button Access="Public" Name="QuitButton" Text="{Loc 'ui-escape-quit'}" />
|
||||
<Button Access="Public" Name="QuitButton" Text="{Loc 'ui-escape-quit'}" StyleClasses="ButtonColorRed" />
|
||||
</BoxContainer>
|
||||
</ui1:EscapeMenu>
|
||||
|
||||
@@ -15,6 +15,9 @@ namespace Content.Client.Outline;
|
||||
/// </summary>
|
||||
public sealed class TargetOutlineSystem : EntitySystem
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> ShaderTargetValid = "SelectionOutlineInrange";
|
||||
private static readonly ProtoId<ShaderPrototype> ShaderTargetInvalid = "SelectionOutline";
|
||||
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
@@ -70,12 +73,6 @@ public sealed class TargetOutlineSystem : EntitySystem
|
||||
|
||||
private Vector2 LookupVector => new(LookupSize, LookupSize);
|
||||
|
||||
[ValidatePrototypeId<ShaderPrototype>]
|
||||
private const string ShaderTargetValid = "SelectionOutlineInrange";
|
||||
|
||||
[ValidatePrototypeId<ShaderPrototype>]
|
||||
private const string ShaderTargetInvalid = "SelectionOutline";
|
||||
|
||||
private ShaderInstance? _shaderTargetValid;
|
||||
private ShaderInstance? _shaderTargetInvalid;
|
||||
|
||||
@@ -85,8 +82,8 @@ public sealed class TargetOutlineSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_shaderTargetValid = _prototypeManager.Index<ShaderPrototype>(ShaderTargetValid).InstanceUnique();
|
||||
_shaderTargetInvalid = _prototypeManager.Index<ShaderPrototype>(ShaderTargetInvalid).InstanceUnique();
|
||||
_shaderTargetValid = _prototypeManager.Index(ShaderTargetValid).InstanceUnique();
|
||||
_shaderTargetInvalid = _prototypeManager.Index(ShaderTargetInvalid).InstanceUnique();
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class BlackAndWhiteOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "GreyscaleFullscreen";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
@@ -15,7 +17,7 @@ public sealed partial class BlackAndWhiteOverlay : Overlay
|
||||
public BlackAndWhiteOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_greyscaleShader = _prototypeManager.Index<ShaderPrototype>("GreyscaleFullscreen").InstanceUnique();
|
||||
_greyscaleShader = _prototypeManager.Index(Shader).InstanceUnique();
|
||||
ZIndex = 10; // draw this over the DamageOverlay, RainbowOverlay etc.
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class NoirOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "Noir";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
@@ -15,7 +17,7 @@ public sealed partial class NoirOverlay : Overlay
|
||||
public NoirOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_noirShader = _prototypeManager.Index<ShaderPrototype>("Noir").InstanceUnique();
|
||||
_noirShader = _prototypeManager.Index(Shader).InstanceUnique();
|
||||
ZIndex = 9; // draw this over the DamageOverlay, RainbowOverlay etc, but before the black and white shader
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ public sealed class ShowJobIconsSystem : EquipmentHudSystem<ShowJobIconsComponen
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
|
||||
[ValidatePrototypeId<JobIconPrototype>]
|
||||
private const string JobIconForNoId = "JobIconNoId";
|
||||
private static readonly ProtoId<JobIconPrototype> JobIconForNoId = "JobIconNoId";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -52,7 +51,7 @@ public sealed class ShowJobIconsSystem : EquipmentHudSystem<ShowJobIconsComponen
|
||||
}
|
||||
}
|
||||
|
||||
if (_prototype.TryIndex<JobIconPrototype>(iconId, out var iconPrototype))
|
||||
if (_prototype.TryIndex(iconId, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
else
|
||||
Log.Error($"Invalid job icon prototype: {iconPrototype}");
|
||||
|
||||
@@ -45,13 +45,13 @@ public sealed partial class StencilOverlay
|
||||
}, Color.Transparent);
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
|
||||
worldHandle.UseShader(_protoManager.Index(StencilMask).Instance());
|
||||
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
|
||||
var curTime = _timing.RealTime;
|
||||
var sprite = _sprite.GetFrame(new SpriteSpecifier.Texture(new ResPath("/Textures/Parallaxes/noise.png")), curTime);
|
||||
|
||||
// Draw the rain
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||
worldHandle.UseShader(_protoManager.Index(StencilDraw).Instance());
|
||||
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, new Vector2(0.5f, 0f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ public sealed partial class StencilOverlay
|
||||
}, Color.Transparent);
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
|
||||
worldHandle.UseShader(_protoManager.Index(StencilMask).Instance());
|
||||
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
|
||||
var curTime = _timing.RealTime;
|
||||
var sprite = _sprite.GetFrame(weatherProto.Sprite, curTime);
|
||||
|
||||
// Draw the rain
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||
worldHandle.UseShader(_protoManager.Index(StencilDraw).Instance());
|
||||
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, weatherProto.OffsetSpeed, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha*weatherProto.Alpha)); //CP14 alpha and offset scrolling
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
|
||||
@@ -18,6 +18,10 @@ namespace Content.Client.Overlays;
|
||||
/// </summary>
|
||||
public sealed partial class StencilOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> CircleShader = "WorldGradientCircle";
|
||||
private static readonly ProtoId<ShaderPrototype> StencilMask = "StencilMask";
|
||||
private static readonly ProtoId<ShaderPrototype> StencilDraw = "StencilDraw";
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
@@ -44,7 +48,7 @@ public sealed partial class StencilOverlay : Overlay
|
||||
_sprite = sprite;
|
||||
_weather = weather;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _protoManager.Index<ShaderPrototype>("WorldGradientCircle").InstanceUnique();
|
||||
_shader = _protoManager.Index(CircleShader).InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Content.Client.Paper.UI;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StampLabel : Label
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> PaperStamp = "PaperStamp";
|
||||
|
||||
/// A scale that's applied to the text to ensure it
|
||||
/// fits in the allowed space.
|
||||
private Vector2 _textScaling = Vector2.One;
|
||||
@@ -26,7 +28,7 @@ public sealed partial class StampLabel : Label
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
var prototypes = IoCManager.Resolve<IPrototypeManager>();
|
||||
_stampShader = prototypes.Index<ShaderPrototype>("PaperStamp").InstanceUnique();
|
||||
_stampShader = prototypes.Index(PaperStamp).InstanceUnique();
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Content.Client.Paper.UI;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StampWidget : PanelContainer
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> PaperStamp = "PaperStamp";
|
||||
|
||||
private StyleBoxTexture _borderTexture;
|
||||
private ShaderInstance? _stampShader;
|
||||
|
||||
@@ -42,7 +44,7 @@ public sealed partial class StampWidget : PanelContainer
|
||||
PanelOverride = _borderTexture;
|
||||
|
||||
var prototypes = IoCManager.Resolve<IPrototypeManager>();
|
||||
_stampShader = prototypes.Index<ShaderPrototype>("PaperStamp").InstanceUnique();
|
||||
_stampShader = prototypes.Index(PaperStamp).InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
|
||||
@@ -195,7 +195,6 @@ public sealed class GeneratedParallaxCache : IPostInjectInit
|
||||
public required ResPath ConfigPath;
|
||||
public required Task<Texture> LoadTask;
|
||||
public required CancellationTokenSource CancellationSource;
|
||||
public ValueList<CancellationTokenRegistration> CancelRegistrations;
|
||||
|
||||
public int RefCount;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ public sealed class ParallaxSystem : SharedParallaxSystem
|
||||
[Dependency] private readonly IParallaxManager _parallax = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
[ValidatePrototypeId<ParallaxPrototype>]
|
||||
private const string Fallback = "Default";
|
||||
private static readonly ProtoId<ParallaxPrototype> Fallback = "Default";
|
||||
|
||||
public const int ParallaxZIndex = 0;
|
||||
|
||||
|
||||
@@ -262,6 +262,7 @@ public sealed partial class ParticleAcceleratorControlMenu : FancyWindow
|
||||
|
||||
public sealed class PASegmentControl : Control
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> GreyscaleShaderId = "Greyscale";
|
||||
private readonly ShaderInstance _greyScaleShader;
|
||||
private readonly TextureRect _base;
|
||||
private readonly TextureRect _unlit;
|
||||
@@ -272,7 +273,7 @@ public sealed class PASegmentControl : Control
|
||||
|
||||
public PASegmentControl()
|
||||
{
|
||||
_greyScaleShader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("Greyscale").Instance();
|
||||
_greyScaleShader = IoCManager.Resolve<IPrototypeManager>().Index(GreyscaleShaderId).Instance();
|
||||
|
||||
AddChild(_base = new TextureRect());
|
||||
AddChild(_unlit = new TextureRect());
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Content.Client.Popups;
|
||||
/// </summary>
|
||||
public sealed class PopupOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly IConfigurationManager _configManager;
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IPlayerManager _playerMgr;
|
||||
@@ -48,7 +50,7 @@ public sealed class PopupOverlay : Overlay
|
||||
_popup = popup;
|
||||
_controller = controller;
|
||||
|
||||
_shader = protoManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_shader = protoManager.Index(UnshadedShader).Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -9,7 +9,7 @@ public static class StaticPowerSystem
|
||||
public static bool IsPowered(this EntitySystem system, EntityUid uid, IEntityManager entManager, ApcPowerReceiverComponent? receiver = null)
|
||||
{
|
||||
if (receiver == null && !entManager.TryGetComponent(uid, out receiver))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
return receiver.Powered;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ namespace Content.Client.Power.Generation.Teg;
|
||||
/// <seealso cref="TegCirculatorComponent"/>
|
||||
public sealed class TegSystem : EntitySystem
|
||||
{
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string ArrowPrototype = "TegCirculatorArrow";
|
||||
private static readonly EntProtoId ArrowPrototype = "TegCirculatorArrow";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -48,8 +48,9 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
|
||||
if (!_sprite.LayerExists((uid, args.Sprite), PowerCellVisualLayers.Unshaded))
|
||||
return;
|
||||
|
||||
// If no appearance data is set, rely on whatever existing sprite state is set being correct.
|
||||
if (!_appearance.TryGetData<byte>(uid, PowerCellVisuals.ChargeLevel, out var level, args.Component))
|
||||
level = 0;
|
||||
return;
|
||||
|
||||
var positiveCharge = level > 0;
|
||||
_sprite.LayerSetVisible((uid, args.Sprite), PowerCellVisualLayers.Unshaded, positiveCharge);
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Content.Client.Radiation.Overlays
|
||||
{
|
||||
public sealed class RadiationPulseOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> RadiationShader = "Radiation";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -29,7 +31,7 @@ namespace Content.Client.Radiation.Overlays
|
||||
public RadiationPulseOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_baseShader = _prototypeManager.Index<ShaderPrototype>("Radiation").Instance().Duplicate();
|
||||
_baseShader = _prototypeManager.Index(RadiationShader).Instance().Duplicate();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace Content.Client.Shuttles;
|
||||
/// </summary>
|
||||
public sealed class FtlArrivalOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
|
||||
|
||||
private EntityLookupSystem _lookups;
|
||||
@@ -36,7 +38,7 @@ public sealed class FtlArrivalOverlay : Overlay
|
||||
_maps = _entManager.System<SharedMapSystem>();
|
||||
_sprites = _entManager.System<SpriteSystem>();
|
||||
|
||||
_shader = _protos.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_shader = _protos.Index(UnshadedShader).Instance();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -25,7 +25,6 @@ public sealed partial class BorgSelectTypeMenu : FancyWindow
|
||||
|
||||
public event Action<ProtoId<BorgTypePrototype>>? ConfirmedBorgType;
|
||||
|
||||
[ValidatePrototypeId<GuideEntryPrototype>]
|
||||
private static readonly List<ProtoId<GuideEntryPrototype>> GuidebookEntries = new() { "Cyborgs", "Robotics" };
|
||||
|
||||
public BorgSelectTypeMenu()
|
||||
|
||||
@@ -84,12 +84,13 @@ public sealed partial class LawDisplay : Control
|
||||
|
||||
radioChannelButton.OnPressed += _ =>
|
||||
{
|
||||
switch (radioChannel)
|
||||
if (radioChannel == SharedChatSystem.CommonChannel)
|
||||
{
|
||||
case SharedChatSystem.CommonChannel:
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
|
||||
default:
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio);
|
||||
}
|
||||
else
|
||||
{
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio);
|
||||
}
|
||||
_nextAllowedPress[radioChannelButton] = _timing.CurTime + PressCooldown;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace Content.Client.Silicons.StationAi;
|
||||
|
||||
public sealed class StationAiOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> CameraStaticShader = "CameraStatic";
|
||||
private static readonly ProtoId<ShaderPrototype> StencilMaskShader = "StencilMask";
|
||||
private static readonly ProtoId<ShaderPrototype> StencilDrawShader = "StencilDraw";
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
@@ -91,7 +95,7 @@ public sealed class StationAiOverlay : Overlay
|
||||
() =>
|
||||
{
|
||||
worldHandle.SetTransform(invMatrix);
|
||||
var shader = _proto.Index<ShaderPrototype>("CameraStatic").Instance();
|
||||
var shader = _proto.Index(CameraStaticShader).Instance();
|
||||
worldHandle.UseShader(shader);
|
||||
worldHandle.DrawRect(worldBounds, Color.White);
|
||||
},
|
||||
@@ -114,11 +118,11 @@ public sealed class StationAiOverlay : Overlay
|
||||
}
|
||||
|
||||
// Use the lighting as a mask
|
||||
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilMask").Instance());
|
||||
worldHandle.UseShader(_proto.Index(StencilMaskShader).Instance());
|
||||
worldHandle.DrawTextureRect(_stencilTexture!.Texture, worldBounds);
|
||||
|
||||
// Draw the static
|
||||
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||
worldHandle.UseShader(_proto.Index(StencilDrawShader).Instance());
|
||||
worldHandle.DrawTextureRect(_staticTexture!.Texture, worldBounds);
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace Content.Client.Singularity
|
||||
{
|
||||
public sealed class SingularityOverlay : Overlay, IEntityEventSubscriber
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "Singularity";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private SharedTransformSystem? _xformSystem = null;
|
||||
@@ -29,7 +31,7 @@ namespace Content.Client.Singularity
|
||||
public SingularityOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _prototypeManager.Index<ShaderPrototype>("Singularity").Instance().Duplicate();
|
||||
_shader = _prototypeManager.Index(Shader).Instance().Duplicate();
|
||||
_shader.SetParameter("maxDistance", MaxDistance * EyeManager.PixelsPerMeter);
|
||||
_entMan.EventBus.SubscribeEvent<PixelToMapEvent>(EventSource.Local, this, OnProjectFromScreenToMap);
|
||||
ZIndex = 101; // Should be drawn after the placement overlay so admins placing items near the singularity can tell where they're going.
|
||||
|
||||
@@ -1,56 +1,129 @@
|
||||
using Content.Shared.SprayPainter;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Graphics;
|
||||
using Content.Client.Items;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.SprayPainter;
|
||||
using Content.Shared.SprayPainter.Components;
|
||||
using Content.Shared.SprayPainter.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.SprayPainter;
|
||||
|
||||
/// <summary>
|
||||
/// Client-side spray painter functions. Caches information for spray painter windows and updates the UI to reflect component state.
|
||||
/// </summary>
|
||||
public sealed class SprayPainterSystem : SharedSprayPainterSystem
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
public List<SprayPainterEntry> Entries { get; private set; } = new();
|
||||
public List<SprayPainterDecalEntry> Decals = [];
|
||||
public Dictionary<string, List<string>> PaintableGroupsByCategory = new();
|
||||
public Dictionary<string, Dictionary<string, EntProtoId>> PaintableStylesByGroup = new();
|
||||
|
||||
protected override void CacheStyles()
|
||||
public override void Initialize()
|
||||
{
|
||||
base.CacheStyles();
|
||||
base.Initialize();
|
||||
|
||||
Entries.Clear();
|
||||
foreach (var style in Styles)
|
||||
Subs.ItemStatus<SprayPainterComponent>(ent => new StatusControl(ent));
|
||||
SubscribeLocalEvent<SprayPainterComponent, AfterAutoHandleStateEvent>(OnStateUpdate);
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
|
||||
CachePrototypes();
|
||||
}
|
||||
|
||||
private void OnStateUpdate(Entity<SprayPainterComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateUi(ent);
|
||||
}
|
||||
|
||||
protected override void UpdateUi(Entity<SprayPainterComponent> ent)
|
||||
{
|
||||
if (_ui.TryGetOpenUi(ent.Owner, SprayPainterUiKey.Key, out var bui))
|
||||
bui.Update();
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
if (!args.WasModified<PaintableGroupCategoryPrototype>() || !args.WasModified<PaintableGroupPrototype>() || !args.WasModified<DecalPrototype>())
|
||||
return;
|
||||
|
||||
CachePrototypes();
|
||||
}
|
||||
|
||||
private void CachePrototypes()
|
||||
{
|
||||
PaintableGroupsByCategory.Clear();
|
||||
PaintableStylesByGroup.Clear();
|
||||
foreach (var category in Proto.EnumeratePrototypes<PaintableGroupCategoryPrototype>().OrderBy(x => x.ID))
|
||||
{
|
||||
var name = style.Name;
|
||||
string? iconPath = Groups
|
||||
.FindAll(x => x.StylePaths.ContainsKey(name))?
|
||||
.MaxBy(x => x.IconPriority)?.StylePaths[name];
|
||||
if (iconPath == null)
|
||||
var groupList = new List<string>();
|
||||
foreach (var groupId in category.Groups)
|
||||
{
|
||||
Entries.Add(new SprayPainterEntry(name, null));
|
||||
continue;
|
||||
if (!Proto.TryIndex(groupId, out var group))
|
||||
continue;
|
||||
|
||||
groupList.Add(groupId);
|
||||
PaintableStylesByGroup[groupId] = group.Styles;
|
||||
}
|
||||
|
||||
RSIResource doorRsi = _resourceCache.GetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / new ResPath(iconPath));
|
||||
if (!doorRsi.RSI.TryGetState("closed", out var icon))
|
||||
{
|
||||
Entries.Add(new SprayPainterEntry(name, null));
|
||||
continue;
|
||||
}
|
||||
if (groupList.Count > 0)
|
||||
PaintableGroupsByCategory[category.ID] = groupList;
|
||||
}
|
||||
|
||||
Entries.Add(new SprayPainterEntry(name, icon.Frame0));
|
||||
Decals.Clear();
|
||||
foreach (var decalPrototype in Proto.EnumeratePrototypes<DecalPrototype>().OrderBy(x => x.ID))
|
||||
{
|
||||
if (!decalPrototype.Tags.Contains("station")
|
||||
&& !decalPrototype.Tags.Contains("markings")
|
||||
|| decalPrototype.Tags.Contains("dirty"))
|
||||
continue;
|
||||
|
||||
Decals.Add(new SprayPainterDecalEntry(decalPrototype.ID, decalPrototype.Sprite));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly RichTextLabel _label;
|
||||
private readonly Entity<SprayPainterComponent> _entity;
|
||||
private DecalPaintMode? _lastPaintingDecals = null;
|
||||
|
||||
public StatusControl(Entity<SprayPainterComponent> ent)
|
||||
{
|
||||
_entity = ent;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
AddChild(_label);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_entity.Comp.DecalMode == _lastPaintingDecals)
|
||||
return;
|
||||
|
||||
_lastPaintingDecals = _entity.Comp.DecalMode;
|
||||
|
||||
string modeLocString = _entity.Comp.DecalMode switch
|
||||
{
|
||||
DecalPaintMode.Add => "spray-painter-item-status-add",
|
||||
DecalPaintMode.Remove => "spray-painter-item-status-remove",
|
||||
_ => "spray-painter-item-status-off"
|
||||
};
|
||||
|
||||
_label.SetMarkupPermissive(Robust.Shared.Localization.Loc.GetString("spray-painter-item-status-label",
|
||||
("mode", Robust.Shared.Localization.Loc.GetString(modeLocString))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SprayPainterEntry
|
||||
{
|
||||
public string Name;
|
||||
public Texture? Icon;
|
||||
|
||||
public SprayPainterEntry(string name, Texture? icon)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// A spray paintable decal, mapped by ID.
|
||||
/// </summary>
|
||||
public sealed record SprayPainterDecalEntry(string Name, SpriteSpecifier Sprite);
|
||||
|
||||
@@ -1,42 +1,96 @@
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.SprayPainter;
|
||||
using Content.Shared.SprayPainter.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.SprayPainter.UI;
|
||||
|
||||
public sealed class SprayPainterBoundUserInterface : BoundUserInterface
|
||||
/// <summary>
|
||||
/// A BUI for a spray painter. Allows selecting pipe colours, decals, and paintable object types sorted by category.
|
||||
/// </summary>
|
||||
public sealed class SprayPainterBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private SprayPainterWindow? _window;
|
||||
|
||||
public SprayPainterBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<SprayPainterWindow>();
|
||||
|
||||
_window.OnSpritePicked = OnSpritePicked;
|
||||
_window.OnColorPicked = OnColorPicked;
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out SprayPainterComponent? comp))
|
||||
if (_window == null)
|
||||
{
|
||||
_window.Populate(EntMan.System<SprayPainterSystem>().Entries, comp.Index, comp.PickedColor, comp.ColorPalette);
|
||||
_window = this.CreateWindow<SprayPainterWindow>();
|
||||
|
||||
_window.OnSpritePicked += OnSpritePicked;
|
||||
_window.OnSetPipeColor += OnSetPipeColor;
|
||||
_window.OnTabChanged += OnTabChanged;
|
||||
_window.OnDecalChanged += OnDecalChanged;
|
||||
_window.OnDecalColorChanged += OnDecalColorChanged;
|
||||
_window.OnDecalAngleChanged += OnDecalAngleChanged;
|
||||
_window.OnDecalSnapChanged += OnDecalSnapChanged;
|
||||
}
|
||||
|
||||
var sprayPainter = EntMan.System<SprayPainterSystem>();
|
||||
_window.PopulateCategories(sprayPainter.PaintableStylesByGroup, sprayPainter.PaintableGroupsByCategory, sprayPainter.Decals);
|
||||
Update();
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out SprayPainterComponent? sprayPainterComp))
|
||||
_window.SetSelectedTab(sprayPainterComp.SelectedTab);
|
||||
}
|
||||
|
||||
private void OnSpritePicked(ItemList.ItemListSelectedEventArgs args)
|
||||
public override void Update()
|
||||
{
|
||||
SendMessage(new SprayPainterSpritePickedMessage(args.ItemIndex));
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (!EntMan.TryGetComponent(Owner, out SprayPainterComponent? sprayPainter))
|
||||
return;
|
||||
|
||||
_window.PopulateColors(sprayPainter.ColorPalette);
|
||||
if (sprayPainter.PickedColor != null)
|
||||
_window.SelectColor(sprayPainter.PickedColor);
|
||||
_window.SetSelectedStyles(sprayPainter.StylesByGroup);
|
||||
_window.SetSelectedDecal(sprayPainter.SelectedDecal);
|
||||
_window.SetDecalAngle(sprayPainter.SelectedDecalAngle);
|
||||
_window.SetDecalColor(sprayPainter.SelectedDecalColor);
|
||||
_window.SetDecalSnap(sprayPainter.SnapDecals);
|
||||
}
|
||||
|
||||
private void OnColorPicked(ItemList.ItemListSelectedEventArgs args)
|
||||
private void OnDecalSnapChanged(bool snap)
|
||||
{
|
||||
SendPredictedMessage(new SprayPainterSetDecalSnapMessage(snap));
|
||||
}
|
||||
|
||||
private void OnDecalAngleChanged(int angle)
|
||||
{
|
||||
SendPredictedMessage(new SprayPainterSetDecalAngleMessage(angle));
|
||||
}
|
||||
|
||||
private void OnDecalColorChanged(Color? color)
|
||||
{
|
||||
SendPredictedMessage(new SprayPainterSetDecalColorMessage(color));
|
||||
}
|
||||
|
||||
private void OnDecalChanged(ProtoId<DecalPrototype> protoId)
|
||||
{
|
||||
SendPredictedMessage(new SprayPainterSetDecalMessage(protoId));
|
||||
}
|
||||
|
||||
private void OnTabChanged(int index, bool isSelectedTabWithDecals)
|
||||
{
|
||||
SendPredictedMessage(new SprayPainterTabChangedMessage(index, isSelectedTabWithDecals));
|
||||
}
|
||||
|
||||
private void OnSpritePicked(string group, string style)
|
||||
{
|
||||
SendPredictedMessage(new SprayPainterSetPaintableStyleMessage(group, style));
|
||||
}
|
||||
|
||||
private void OnSetPipeColor(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
var key = _window?.IndexToColorKey(args.ItemIndex);
|
||||
SendMessage(new SprayPainterColorPickedMessage(key));
|
||||
SendPredictedMessage(new SprayPainterSetPipeColorMessage(key));
|
||||
}
|
||||
}
|
||||
|
||||
26
Content.Client/SprayPainter/UI/SprayPainterDecals.xaml
Normal file
26
Content.Client/SprayPainter/UI/SprayPainterDecals.xaml
Normal file
@@ -0,0 +1,26 @@
|
||||
<controls:SprayPainterDecals
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.SprayPainter.UI">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Text="{Loc 'spray-painter-selected-decals'}" />
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<GridContainer Columns="7" Name="DecalsGrid">
|
||||
<!-- populated by code -->
|
||||
</GridContainer>
|
||||
</ScrollContainer>
|
||||
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<ColorSelectorSliders Name="ColorSelector" IsAlphaVisible="True" />
|
||||
<CheckBox Name="UseCustomColorCheckBox" Text="{Loc 'spray-painter-use-custom-color'}" />
|
||||
<CheckBox Name="SnapToTileCheckBox" Text="{Loc 'spray-painter-use-snap-to-tile'}" />
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'spray-painter-angle-rotation'}" />
|
||||
<SpinBox Name="AngleSpinBox" HorizontalExpand="True" />
|
||||
<Button Text="{Loc 'spray-painter-angle-rotation-90-sub'}" Name="SubAngleButton" />
|
||||
<Button Text="{Loc 'spray-painter-angle-rotation-reset'}" Name="SetZeroAngleButton" />
|
||||
<Button Text="{Loc 'spray-painter-angle-rotation-90-add'}" Name="AddAngleButton" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:SprayPainterDecals>
|
||||
174
Content.Client/SprayPainter/UI/SprayPainterDecals.xaml.cs
Normal file
174
Content.Client/SprayPainter/UI/SprayPainterDecals.xaml.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Decals;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.SprayPainter.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Used to control decal painting parameters for the spray painter.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SprayPainterDecals : Control
|
||||
{
|
||||
public Action<ProtoId<DecalPrototype>>? OnDecalSelected;
|
||||
public Action<Color?>? OnColorChanged;
|
||||
public Action<int>? OnAngleChanged;
|
||||
public Action<bool>? OnSnapChanged;
|
||||
|
||||
private SpriteSystem? _sprite;
|
||||
private string _selectedDecal = string.Empty;
|
||||
private List<SprayPainterDecalEntry> _decals = [];
|
||||
|
||||
public SprayPainterDecals()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
AddAngleButton.OnButtonUp += _ => AngleSpinBox.Value += 90;
|
||||
SubAngleButton.OnButtonUp += _ => AngleSpinBox.Value -= 90;
|
||||
SetZeroAngleButton.OnButtonUp += _ => AngleSpinBox.Value = 0;
|
||||
AngleSpinBox.ValueChanged += args => OnAngleChanged?.Invoke(args.Value);
|
||||
|
||||
UseCustomColorCheckBox.OnPressed += UseCustomColorCheckBoxOnOnPressed;
|
||||
SnapToTileCheckBox.OnPressed += SnapToTileCheckBoxOnOnPressed;
|
||||
ColorSelector.OnColorChanged += OnColorSelected;
|
||||
}
|
||||
|
||||
private void UseCustomColorCheckBoxOnOnPressed(BaseButton.ButtonEventArgs _)
|
||||
{
|
||||
OnColorChanged?.Invoke(UseCustomColorCheckBox.Pressed ? ColorSelector.Color : null);
|
||||
UpdateColorButtons(UseCustomColorCheckBox.Pressed);
|
||||
}
|
||||
|
||||
private void SnapToTileCheckBoxOnOnPressed(BaseButton.ButtonEventArgs _)
|
||||
{
|
||||
OnSnapChanged?.Invoke(SnapToTileCheckBox.Pressed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the decal list.
|
||||
/// </summary>
|
||||
public void PopulateDecals(List<SprayPainterDecalEntry> decals, SpriteSystem sprite)
|
||||
{
|
||||
_sprite ??= sprite;
|
||||
|
||||
_decals = decals;
|
||||
DecalsGrid.Children.Clear();
|
||||
|
||||
foreach (var decal in decals)
|
||||
{
|
||||
var button = new TextureButton()
|
||||
{
|
||||
TextureNormal = sprite.Frame0(decal.Sprite),
|
||||
Name = decal.Name,
|
||||
ToolTip = decal.Name,
|
||||
Scale = new Vector2(2, 2),
|
||||
};
|
||||
button.OnPressed += DecalButtonOnPressed;
|
||||
|
||||
if (UseCustomColorCheckBox.Pressed)
|
||||
{
|
||||
button.Modulate = ColorSelector.Color;
|
||||
}
|
||||
|
||||
if (_selectedDecal == decal.Name)
|
||||
{
|
||||
var panelContainer = new PanelContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = StyleNano.ButtonColorDefault,
|
||||
},
|
||||
Children =
|
||||
{
|
||||
button,
|
||||
},
|
||||
};
|
||||
DecalsGrid.AddChild(panelContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecalsGrid.AddChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnColorSelected(Color color)
|
||||
{
|
||||
if (!UseCustomColorCheckBox.Pressed)
|
||||
return;
|
||||
|
||||
OnColorChanged?.Invoke(color);
|
||||
|
||||
UpdateColorButtons(UseCustomColorCheckBox.Pressed);
|
||||
}
|
||||
|
||||
private void UpdateColorButtons(bool apply)
|
||||
{
|
||||
Color modulateColor = apply ? ColorSelector.Color : Color.White;
|
||||
foreach (var button in DecalsGrid.Children)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case TextureButton:
|
||||
button.Modulate = modulateColor;
|
||||
break;
|
||||
case PanelContainer panelContainer:
|
||||
{
|
||||
foreach (TextureButton textureButton in panelContainer.Children)
|
||||
textureButton.Modulate = modulateColor;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DecalButtonOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if (obj.Button.Name is not { } name)
|
||||
return;
|
||||
|
||||
_selectedDecal = name;
|
||||
OnDecalSelected?.Invoke(_selectedDecal);
|
||||
|
||||
if (_sprite is null)
|
||||
return;
|
||||
|
||||
PopulateDecals(_decals, _sprite);
|
||||
}
|
||||
|
||||
public void SetSelectedDecal(string name)
|
||||
{
|
||||
_selectedDecal = name;
|
||||
|
||||
if (_sprite is null)
|
||||
return;
|
||||
|
||||
PopulateDecals(_decals, _sprite);
|
||||
}
|
||||
|
||||
public void SetAngle(int degrees)
|
||||
{
|
||||
AngleSpinBox.OverrideValue(degrees);
|
||||
}
|
||||
|
||||
public void SetColor(Color? color)
|
||||
{
|
||||
UseCustomColorCheckBox.Pressed = color != null;
|
||||
if (color != null)
|
||||
ColorSelector.Color = color.Value;
|
||||
UpdateColorButtons(UseCustomColorCheckBox.Pressed);
|
||||
}
|
||||
|
||||
public void SetSnap(bool snap)
|
||||
{
|
||||
SnapToTileCheckBox.Pressed = snap;
|
||||
}
|
||||
}
|
||||
12
Content.Client/SprayPainter/UI/SprayPainterGroup.xaml
Normal file
12
Content.Client/SprayPainter/UI/SprayPainterGroup.xaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<BoxContainer
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Orientation="Vertical">
|
||||
<Label Text="{Loc 'spray-painter-selected-style'}" />
|
||||
<controls:ListContainer
|
||||
Name="StyleList"
|
||||
Toggle="True"
|
||||
Group="True">
|
||||
<!-- populated by code -->
|
||||
</controls:ListContainer>
|
||||
</BoxContainer>
|
||||
66
Content.Client/SprayPainter/UI/SprayPainterGroup.xaml.cs
Normal file
66
Content.Client/SprayPainter/UI/SprayPainterGroup.xaml.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.SprayPainter.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Used to display a group of paintable styles in the spray painter menu.
|
||||
/// (e.g. each type of paintable locker or plastic crate)
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SprayPainterGroup : BoxContainer
|
||||
{
|
||||
public event Action<SpriteListData>? OnButtonPressed;
|
||||
|
||||
public SprayPainterGroup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
StyleList.GenerateItem = GenerateItems;
|
||||
}
|
||||
|
||||
public void PopulateList(List<SpriteListData> spriteList)
|
||||
{
|
||||
StyleList.PopulateList(spriteList);
|
||||
}
|
||||
|
||||
public void SelectItemByStyle(string key)
|
||||
{
|
||||
foreach (var elem in StyleList.Data)
|
||||
{
|
||||
if (elem is not SpriteListData spriteElem)
|
||||
continue;
|
||||
|
||||
if (spriteElem.Style == key)
|
||||
{
|
||||
StyleList.Select(spriteElem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateItems(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not SpriteListData spriteListData)
|
||||
return;
|
||||
|
||||
var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal };
|
||||
var protoView = new EntityPrototypeView();
|
||||
protoView.SetPrototype(spriteListData.Prototype);
|
||||
var label = new Label()
|
||||
{
|
||||
Text = Loc.GetString($"spray-painter-style-{spriteListData.Group.ToLower()}-{spriteListData.Style.ToLower()}")
|
||||
};
|
||||
|
||||
box.AddChild(protoView);
|
||||
box.AddChild(label);
|
||||
button.AddChild(box);
|
||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
||||
button.OnPressed += _ => OnButtonPressed?.Invoke(spriteListData);
|
||||
|
||||
if (spriteListData.SelectedIndex == button.Index)
|
||||
button.Pressed = true;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,6 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
MinSize="500 300"
|
||||
SetSize="500 500"
|
||||
Title="{Loc 'spray-painter-window-title'}">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
SeparationOverride="4"
|
||||
MinWidth="450">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
SeparationOverride="4"
|
||||
MinWidth="200">
|
||||
<Label Name="SelectedSpriteLabel"
|
||||
Text="{Loc 'spray-painter-selected-style'}">
|
||||
</Label>
|
||||
<ItemList Name="SpriteList"
|
||||
SizeFlagsStretchRatio="8"
|
||||
VerticalExpand="True"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
SeparationOverride="4"
|
||||
MinWidth="200">
|
||||
<Label Name="SelectedColorLabel"
|
||||
Text="{Loc 'spray-painter-selected-color'}"/>
|
||||
<ItemList Name="ColorList"
|
||||
SizeFlagsStretchRatio="8"
|
||||
VerticalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
MinSize="520 300"
|
||||
SetSize="520 700"
|
||||
Title="{Loc 'spray-painter-window-title'}">
|
||||
<TabContainer Name="Tabs"/>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Decals;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.SprayPainter.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A window to select spray painter settings by object type, as well as pipe colours and decals.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SprayPainterWindow : DefaultWindow
|
||||
{
|
||||
@@ -15,13 +22,33 @@ public sealed partial class SprayPainterWindow : DefaultWindow
|
||||
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public Action<ItemList.ItemListSelectedEventArgs>? OnSpritePicked;
|
||||
public Action<ItemList.ItemListSelectedEventArgs>? OnColorPicked;
|
||||
// Events
|
||||
public event Action<string, string>? OnSpritePicked;
|
||||
public event Action<int, bool>? OnTabChanged;
|
||||
public event Action<ProtoId<DecalPrototype>>? OnDecalChanged;
|
||||
public event Action<ItemList.ItemListSelectedEventArgs>? OnSetPipeColor;
|
||||
public event Action<Color?>? OnDecalColorChanged;
|
||||
public event Action<int>? OnDecalAngleChanged;
|
||||
public event Action<bool>? OnDecalSnapChanged;
|
||||
|
||||
// Pipe color data
|
||||
private ItemList _colorList = default!;
|
||||
public Dictionary<string, int> ItemColorIndex = new();
|
||||
|
||||
private Dictionary<string, Color> currentPalette = new();
|
||||
private const string colorLocKeyPrefix = "pipe-painter-color-";
|
||||
private List<SprayPainterEntry> CurrentEntries = new List<SprayPainterEntry>();
|
||||
private Dictionary<string, Color> _currentPalette = new();
|
||||
private const string ColorLocKeyPrefix = "pipe-painter-color-";
|
||||
|
||||
// Paintable objects
|
||||
private Dictionary<string, Dictionary<string, EntProtoId>> _currentStylesByGroup = new();
|
||||
private Dictionary<string, List<string>> _currentGroupsByCategory = new();
|
||||
|
||||
// Tab controls
|
||||
private Dictionary<string, SprayPainterGroup> _paintableControls = new();
|
||||
private BoxContainer? _pipeControl;
|
||||
|
||||
// Decals
|
||||
private List<SprayPainterDecalEntry> _currentDecals = [];
|
||||
private SprayPainterDecals? _sprayPainterDecals;
|
||||
|
||||
private readonly SpriteSpecifier _colorEntryIconTexture = new SpriteSpecifier.Rsi(
|
||||
new ResPath("Structures/Piping/Atmospherics/pipe.rsi"),
|
||||
@@ -32,13 +59,14 @@ public sealed partial class SprayPainterWindow : DefaultWindow
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_spriteSystem = _sysMan.GetEntitySystem<SpriteSystem>();
|
||||
Tabs.OnTabChanged += (index) => OnTabChanged?.Invoke(index, _sprayPainterDecals?.GetPositionInParent() == index);
|
||||
}
|
||||
|
||||
private string GetColorLocString(string? colorKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(colorKey))
|
||||
return Loc.GetString("pipe-painter-no-color-selected");
|
||||
var locKey = colorLocKeyPrefix + colorKey;
|
||||
var locKey = ColorLocKeyPrefix + colorKey;
|
||||
|
||||
if (!_loc.TryGetString(locKey, out var locString))
|
||||
locString = colorKey;
|
||||
@@ -48,51 +76,229 @@ public sealed partial class SprayPainterWindow : DefaultWindow
|
||||
|
||||
public string? IndexToColorKey(int index)
|
||||
{
|
||||
return (string?) ColorList[index].Metadata;
|
||||
return _colorList[index].Text;
|
||||
}
|
||||
|
||||
public void Populate(List<SprayPainterEntry> entries, int selectedStyle, string? selectedColorKey, Dictionary<string, Color> palette)
|
||||
private void OnStyleSelected(ListData data)
|
||||
{
|
||||
// Only clear if the entries change. Otherwise the list would "jump" after selecting an item
|
||||
if (!CurrentEntries.Equals(entries))
|
||||
if (data is SpriteListData listData)
|
||||
OnSpritePicked?.Invoke(listData.Group, listData.Style);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper to allow for selecting/deselecting the event to avoid loops
|
||||
/// </summary>
|
||||
private void OnColorPicked(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
OnSetPipeColor?.Invoke(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup function for the window.
|
||||
/// </summary>
|
||||
/// <param name="stylesByGroup">Each group, mapped by name to the set of named styles by their associated entity prototype.</param>
|
||||
/// <param name="groupsByCategory">The set of categories and the groups associated with them.</param>
|
||||
/// <param name="decals">A list of each decal.</param>
|
||||
public void PopulateCategories(Dictionary<string, Dictionary<string, EntProtoId>> stylesByGroup, Dictionary<string, List<string>> groupsByCategory, List<SprayPainterDecalEntry> decals)
|
||||
{
|
||||
bool tabsCleared = false;
|
||||
var lastTab = Tabs.CurrentTab;
|
||||
|
||||
if (!_currentGroupsByCategory.Equals(groupsByCategory))
|
||||
{
|
||||
CurrentEntries = entries;
|
||||
SpriteList.Clear();
|
||||
foreach (var entry in entries)
|
||||
// Destroy all existing tabs
|
||||
tabsCleared = true;
|
||||
_paintableControls.Clear();
|
||||
_pipeControl = null;
|
||||
_sprayPainterDecals = null;
|
||||
Tabs.RemoveAllChildren();
|
||||
}
|
||||
|
||||
// Only clear if the entries change. Otherwise the list would "jump" after selecting an item
|
||||
if (tabsCleared || !_currentStylesByGroup.Equals(stylesByGroup))
|
||||
{
|
||||
_currentStylesByGroup = stylesByGroup;
|
||||
|
||||
var tabIndex = 0;
|
||||
foreach (var (categoryName, categoryGroups) in groupsByCategory.OrderBy(c => c.Key))
|
||||
{
|
||||
SpriteList.AddItem(entry.Name, entry.Icon);
|
||||
if (categoryGroups.Count <= 0)
|
||||
continue;
|
||||
|
||||
// Repopulating controls:
|
||||
// ensure that categories with multiple groups have separate subtabs
|
||||
// but single-group categories do not.
|
||||
if (tabsCleared)
|
||||
{
|
||||
TabContainer? subTabs = null;
|
||||
if (categoryGroups.Count > 1)
|
||||
subTabs = new();
|
||||
|
||||
foreach (var group in categoryGroups)
|
||||
{
|
||||
if (!stylesByGroup.TryGetValue(group, out var styles))
|
||||
continue;
|
||||
|
||||
var groupControl = new SprayPainterGroup();
|
||||
groupControl.OnButtonPressed += OnStyleSelected;
|
||||
_paintableControls[group] = groupControl;
|
||||
if (categoryGroups.Count > 1)
|
||||
{
|
||||
if (subTabs != null)
|
||||
{
|
||||
subTabs?.AddChild(groupControl);
|
||||
var subTabLocalization = Loc.GetString("spray-painter-tab-group-" + group.ToLower());
|
||||
TabContainer.SetTabTitle(groupControl, subTabLocalization);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Tabs.AddChild(groupControl);
|
||||
}
|
||||
}
|
||||
|
||||
if (subTabs != null)
|
||||
Tabs.AddChild(subTabs);
|
||||
|
||||
var tabLocalization = Loc.GetString("spray-painter-tab-category-" + categoryName.ToLower());
|
||||
Tabs.SetTabTitle(tabIndex, tabLocalization);
|
||||
tabIndex++;
|
||||
}
|
||||
|
||||
// Finally, populate all groups with new data.
|
||||
foreach (var group in categoryGroups)
|
||||
{
|
||||
if (!stylesByGroup.TryGetValue(group, out var styles) ||
|
||||
!_paintableControls.TryGetValue(group, out var control))
|
||||
continue;
|
||||
|
||||
var dataList = styles
|
||||
.Select(e => new SpriteListData(group, e.Key, e.Value, 0))
|
||||
.OrderBy(d => Loc.GetString($"spray-painter-style-{group.ToLower()}-{d.Style.ToLower()}"))
|
||||
.ToList();
|
||||
control.PopulateList(dataList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentPalette.Equals(palette))
|
||||
{
|
||||
currentPalette = palette;
|
||||
ItemColorIndex.Clear();
|
||||
ColorList.Clear();
|
||||
PopulateColors(_currentPalette);
|
||||
|
||||
if (!_currentDecals.Equals(decals))
|
||||
{
|
||||
_currentDecals = decals;
|
||||
|
||||
if (_sprayPainterDecals is null)
|
||||
{
|
||||
_sprayPainterDecals = new SprayPainterDecals();
|
||||
|
||||
_sprayPainterDecals.OnDecalSelected += id => OnDecalChanged?.Invoke(id);
|
||||
_sprayPainterDecals.OnColorChanged += color => OnDecalColorChanged?.Invoke(color);
|
||||
_sprayPainterDecals.OnAngleChanged += angle => OnDecalAngleChanged?.Invoke(angle);
|
||||
_sprayPainterDecals.OnSnapChanged += snap => OnDecalSnapChanged?.Invoke(snap);
|
||||
|
||||
Tabs.AddChild(_sprayPainterDecals);
|
||||
TabContainer.SetTabTitle(_sprayPainterDecals, Loc.GetString("spray-painter-tab-category-decals"));
|
||||
}
|
||||
|
||||
_sprayPainterDecals.PopulateDecals(decals, _spriteSystem);
|
||||
}
|
||||
|
||||
if (tabsCleared)
|
||||
SetSelectedTab(lastTab);
|
||||
}
|
||||
|
||||
public void PopulateColors(Dictionary<string, Color> palette)
|
||||
{
|
||||
// Create pipe tab controls if they don't exist
|
||||
bool tabCreated = false;
|
||||
if (_pipeControl == null)
|
||||
{
|
||||
_pipeControl = new BoxContainer() { Orientation = BoxContainer.LayoutOrientation.Vertical };
|
||||
|
||||
var label = new Label() { Text = Loc.GetString("spray-painter-selected-color") };
|
||||
|
||||
_colorList = new ItemList() { VerticalExpand = true };
|
||||
_colorList.OnItemSelected += OnColorPicked;
|
||||
|
||||
_pipeControl.AddChild(label);
|
||||
_pipeControl.AddChild(_colorList);
|
||||
|
||||
Tabs.AddChild(_pipeControl);
|
||||
TabContainer.SetTabTitle(_pipeControl, Loc.GetString("spray-painter-tab-category-pipes"));
|
||||
tabCreated = true;
|
||||
}
|
||||
|
||||
// Populate the tab if needed (new tab/new data)
|
||||
if (tabCreated || !_currentPalette.Equals(palette))
|
||||
{
|
||||
_currentPalette = palette;
|
||||
ItemColorIndex.Clear();
|
||||
_colorList.Clear();
|
||||
|
||||
int index = 0;
|
||||
foreach (var color in palette)
|
||||
{
|
||||
var locString = GetColorLocString(color.Key);
|
||||
var item = ColorList.AddItem(locString, _spriteSystem.Frame0(_colorEntryIconTexture));
|
||||
var item = _colorList.AddItem(locString, _spriteSystem.Frame0(_colorEntryIconTexture), metadata: color.Key);
|
||||
item.IconModulate = color.Value;
|
||||
item.Metadata = color.Key;
|
||||
|
||||
ItemColorIndex.Add(color.Key, ColorList.IndexOf(item));
|
||||
ItemColorIndex.Add(color.Key, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable event so we don't send a new event for pre-selectedStyle entry and end up in a loop
|
||||
|
||||
if (selectedColorKey != null)
|
||||
{
|
||||
var index = ItemColorIndex[selectedColorKey];
|
||||
ColorList.OnItemSelected -= OnColorPicked;
|
||||
ColorList[index].Selected = true;
|
||||
ColorList.OnItemSelected += OnColorPicked;
|
||||
}
|
||||
|
||||
SpriteList.OnItemSelected -= OnSpritePicked;
|
||||
SpriteList[selectedStyle].Selected = true;
|
||||
SpriteList.OnItemSelected += OnSpritePicked;
|
||||
}
|
||||
|
||||
# region Setters
|
||||
public void SetSelectedStyles(Dictionary<string, string> selectedStyles)
|
||||
{
|
||||
foreach (var (group, style) in selectedStyles)
|
||||
{
|
||||
if (!_paintableControls.TryGetValue(group, out var control))
|
||||
continue;
|
||||
|
||||
control.SelectItemByStyle(style);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectColor(string color)
|
||||
{
|
||||
if (_colorList != null && ItemColorIndex.TryGetValue(color, out var colorIdx))
|
||||
{
|
||||
_colorList.OnItemSelected -= OnColorPicked;
|
||||
_colorList[colorIdx].Selected = true;
|
||||
_colorList.OnItemSelected += OnColorPicked;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSelectedTab(int tab)
|
||||
{
|
||||
Tabs.CurrentTab = int.Min(tab, Tabs.ChildCount - 1);
|
||||
}
|
||||
|
||||
public void SetSelectedDecal(string decal)
|
||||
{
|
||||
if (_sprayPainterDecals != null)
|
||||
_sprayPainterDecals.SetSelectedDecal(decal);
|
||||
}
|
||||
|
||||
public void SetDecalAngle(int angle)
|
||||
{
|
||||
if (_sprayPainterDecals != null)
|
||||
_sprayPainterDecals.SetAngle(angle);
|
||||
}
|
||||
|
||||
public void SetDecalColor(Color? color)
|
||||
{
|
||||
if (_sprayPainterDecals != null)
|
||||
_sprayPainterDecals.SetColor(color);
|
||||
}
|
||||
|
||||
public void SetDecalSnap(bool snap)
|
||||
{
|
||||
if (_sprayPainterDecals != null)
|
||||
_sprayPainterDecals.SetSnap(snap);
|
||||
}
|
||||
# endregion
|
||||
}
|
||||
|
||||
public record SpriteListData(string Group, string Style, EntProtoId Prototype, int SelectedIndex) : ListData;
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Content.Shared.StatusEffectNew.Components;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.StatusEffectNew;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class ClientStatusEffectsSystem : SharedStatusEffectsSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StatusEffectContainerComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<StatusEffectContainerComponent> ent, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not StatusEffectContainerComponentState state)
|
||||
return;
|
||||
|
||||
var toRemove = new ValueList<EntityUid>();
|
||||
foreach (var effect in ent.Comp.ActiveStatusEffects)
|
||||
{
|
||||
if (state.ActiveStatusEffects.Contains(GetNetEntity(effect)))
|
||||
continue;
|
||||
|
||||
toRemove.Add(effect);
|
||||
}
|
||||
|
||||
foreach (var effect in toRemove)
|
||||
{
|
||||
ent.Comp.ActiveStatusEffects.Remove(effect);
|
||||
var ev = new StatusEffectRemovedEvent(ent);
|
||||
RaiseLocalEvent(effect, ref ev);
|
||||
}
|
||||
|
||||
foreach (var effect in state.ActiveStatusEffects)
|
||||
{
|
||||
var effectUid = GetEntity(effect);
|
||||
if (ent.Comp.ActiveStatusEffects.Contains(effectUid))
|
||||
continue;
|
||||
|
||||
ent.Comp.ActiveStatusEffects.Add(effectUid);
|
||||
var ev = new StatusEffectAppliedEvent(ent);
|
||||
RaiseLocalEvent(effectUid, ref ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ namespace Content.Client.StatusIcon;
|
||||
|
||||
public sealed class StatusIconOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
@@ -29,7 +31,7 @@ public sealed class StatusIconOverlay : Overlay
|
||||
_sprite = _entity.System<SpriteSystem>();
|
||||
_transform = _entity.System<TransformSystem>();
|
||||
_statusIcon = _entity.System<StatusIconSystem>();
|
||||
_unshadedShader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_unshadedShader = _prototype.Index(UnshadedShader).Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Content.Client.Stealth;
|
||||
|
||||
public sealed class StealthSystem : SharedStealthSystem
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "Stealth";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
@@ -20,7 +22,7 @@ public sealed class StealthSystem : SharedStealthSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_shader = _protoMan.Index<ShaderPrototype>("Stealth").InstanceUnique();
|
||||
_shader = _protoMan.Index(Shader).InstanceUnique();
|
||||
|
||||
SubscribeLocalEvent<StealthComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<StealthComponent, ComponentStartup>(OnStartup);
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
using Content.Shared.SprayPainter.Prototypes;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Storage.Visualizers;
|
||||
|
||||
public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStorageVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -26,12 +31,34 @@ public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStora
|
||||
SpriteSystem.LayerSetRsiState((uid, sprite), StorageVisualLayers.Base, comp.StateBaseClosed);
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, EntityStorageVisualsComponent comp, ref AppearanceChangeEvent args)
|
||||
protected override void OnAppearanceChange(EntityUid uid,
|
||||
EntityStorageVisualsComponent comp,
|
||||
ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null
|
||||
|| !AppearanceSystem.TryGetData<bool>(uid, StorageVisuals.Open, out var open, args.Component))
|
||||
|| !AppearanceSystem.TryGetData<bool>(uid, StorageVisuals.Open, out var open, args.Component))
|
||||
return;
|
||||
|
||||
var forceRedrawBase = false;
|
||||
if (AppearanceSystem.TryGetData<string>(uid, PaintableVisuals.Prototype, out var prototype, args.Component))
|
||||
{
|
||||
if (_prototypeManager.TryIndex(prototype, out var proto))
|
||||
{
|
||||
if (proto.TryGetComponent(out SpriteComponent? sprite, _componentFactory))
|
||||
{
|
||||
SpriteSystem.SetBaseRsi((uid, args.Sprite), sprite.BaseRSI);
|
||||
}
|
||||
if (proto.TryGetComponent(out EntityStorageVisualsComponent? visuals, _componentFactory))
|
||||
{
|
||||
comp.StateBaseOpen = visuals.StateBaseOpen;
|
||||
comp.StateBaseClosed = visuals.StateBaseClosed;
|
||||
comp.StateDoorOpen = visuals.StateDoorOpen;
|
||||
comp.StateDoorClosed = visuals.StateDoorClosed;
|
||||
forceRedrawBase = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open/Closed state for the storage entity.
|
||||
if (SpriteSystem.LayerMapTryGet((uid, args.Sprite), StorageVisualLayers.Door, out _, false))
|
||||
{
|
||||
@@ -52,6 +79,8 @@ public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStora
|
||||
|
||||
if (comp.StateBaseOpen != null)
|
||||
SpriteSystem.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Base, comp.StateBaseOpen);
|
||||
else if (forceRedrawBase && comp.StateBaseClosed != null)
|
||||
SpriteSystem.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Base, comp.StateBaseClosed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -68,6 +97,8 @@ public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStora
|
||||
|
||||
if (comp.StateBaseClosed != null)
|
||||
SpriteSystem.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Base, comp.StateBaseClosed);
|
||||
else if (forceRedrawBase && comp.StateBaseOpen != null)
|
||||
SpriteSystem.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Base, comp.StateBaseOpen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<BoxContainer Margin="8,8,8,8" Orientation="Vertical">
|
||||
<BoxContainer Margin="8,0,8,8" Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="StoreItemName" HorizontalExpand="True" />
|
||||
<Label Name="DiscountSubText"
|
||||
HorizontalAlignment="Right"/>
|
||||
<Button
|
||||
Name="StoreItemBuyButton"
|
||||
MinWidth="64"
|
||||
HorizontalAlignment="Right"
|
||||
Access="Public" />
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="HighDivider" />
|
||||
<PanelContainer StyleClasses="HighDivider" Margin="0,1,0,2"/>
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
||||
<TextureRect
|
||||
Name="StoreItemTexture"
|
||||
Margin="0,0,4,0"
|
||||
MinSize="48 48"
|
||||
Stretch="KeepAspectCentered" />
|
||||
<Control MinWidth="5"/>
|
||||
<RichTextLabel Name="StoreItemDescription" />
|
||||
<BoxContainer Orientation="Vertical" VerticalAlignment="Center" Margin="0,0,4,0">
|
||||
<TextureRect
|
||||
Name="StoreItemTexture"
|
||||
Margin="0,0,4,0"
|
||||
MinSize="48 48"
|
||||
Stretch="KeepAspectCentered" />
|
||||
<Button
|
||||
Name="StoreItemBuyButton"
|
||||
MinWidth="48"
|
||||
MaxHeight="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,4,0,0"
|
||||
Access="Public" />
|
||||
<Label Name="DiscountSubText"
|
||||
HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
<RichTextLabel Name="StoreItemDescription" VerticalAlignment="Top"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,9 +1,164 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Stunnable
|
||||
namespace Content.Client.Stunnable;
|
||||
|
||||
public sealed class StunSystem : SharedStunSystem
|
||||
{
|
||||
public sealed class StunSystem : SharedStunSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
|
||||
|
||||
private readonly int[] _sign = [-1, 1];
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StunVisualsComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<StunVisualsComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add stun visual layers
|
||||
/// </summary>
|
||||
private void OnComponentInit(Entity<StunVisualsComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(entity, out var sprite))
|
||||
return;
|
||||
|
||||
var spriteEntity = (entity.Owner, sprite);
|
||||
|
||||
_spriteSystem.LayerMapReserve(spriteEntity, StunVisualLayers.StamCrit);
|
||||
_spriteSystem.LayerSetVisible(spriteEntity, StunVisualLayers.StamCrit, false);
|
||||
_spriteSystem.LayerSetOffset(spriteEntity, StunVisualLayers.StamCrit, new Vector2(0, 0.3125f));
|
||||
|
||||
_spriteSystem.LayerSetRsi(spriteEntity, StunVisualLayers.StamCrit, entity.Comp.StarsPath);
|
||||
|
||||
UpdateAppearance((entity, sprite), entity.Comp.State);
|
||||
}
|
||||
|
||||
private void OnAppearanceChanged(Entity<StunVisualsComponent> entity, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite != null)
|
||||
UpdateAppearance((entity, args.Sprite), entity.Comp.State);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(Entity<SpriteComponent?> entity, string state)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return;
|
||||
|
||||
if (!_spriteSystem.LayerMapTryGet((entity, entity.Comp), StunVisualLayers.StamCrit, out var index, false))
|
||||
return;
|
||||
|
||||
var visible = Appearance.TryGetData<bool>(entity, StunVisuals.SeeingStars, out var stars) && stars;
|
||||
|
||||
_spriteSystem.LayerSetVisible((entity, entity.Comp), index, visible);
|
||||
_spriteSystem.LayerSetRsiState((entity, entity.Comp), index, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple fatigue animation, a mild modification of the jittering animation. The animation constructor is
|
||||
/// quite complex, but that's because the AnimationSystem doesn't have proper adjustment layers. In a potential
|
||||
/// future where proper adjustment layers are added feel free to clean this up to be an animation with two adjustment
|
||||
/// layers rather than one mega layer.
|
||||
/// </summary>
|
||||
/// <param name="sprite">The spriteComponent we're adjusting the offset of</param>
|
||||
/// <param name="frequency">How many times per second does the animation run?</param>
|
||||
/// <param name="jitters">How many times should we jitter during the animation? Also determines breathing frequency</param>
|
||||
/// <param name="minJitter">Mininum jitter offset multiplier for X and Y directions</param>
|
||||
/// <param name="maxJitter">Maximum jitter offset multiplier for X and Y directions</param>
|
||||
/// <param name="breathing">Maximum breathing offset, this is in the Y direction</param>
|
||||
/// <param name="startOffset">Starting offset because we don't have adjustment layers</param>
|
||||
/// <param name="lastJitter">Last jitter so we don't jitter to the same quadrant</param>
|
||||
/// <returns></returns>
|
||||
public Animation GetFatigueAnimation(SpriteComponent sprite,
|
||||
float frequency,
|
||||
int jitters,
|
||||
Vector2 minJitter,
|
||||
Vector2 maxJitter,
|
||||
float breathing,
|
||||
Vector2 startOffset,
|
||||
ref Vector2 lastJitter)
|
||||
{
|
||||
// avoid animations with negative length or infinite length
|
||||
if (frequency <= 0)
|
||||
return new Animation();
|
||||
|
||||
var breaths = new Vector2(0, breathing * 2) / jitters;
|
||||
|
||||
var length = 1 / frequency;
|
||||
var frames = length / jitters;
|
||||
|
||||
var keyFrames = new List<AnimationTrackProperty.KeyFrame> { new(sprite.Offset, 0f) };
|
||||
|
||||
// Spits out a list of keyframes to feed to the AnimationPlayer based on the variables we've inputted
|
||||
for (var i = 1; i <= jitters; i++)
|
||||
{
|
||||
var offset = new Vector2(_random.NextFloat(minJitter.X, maxJitter.X),
|
||||
_random.NextFloat(minJitter.Y, maxJitter.Y));
|
||||
offset.X *= _random.Pick(_sign);
|
||||
offset.Y *= _random.Pick(_sign);
|
||||
|
||||
if (i == 1 && Math.Sign(offset.X) == Math.Sign(lastJitter.X)
|
||||
&& Math.Sign(offset.Y) == Math.Sign(lastJitter.Y))
|
||||
{
|
||||
// If the sign is the same as last time on both axis we flip one randomly
|
||||
// to avoid jitter staying in one quadrant too much.
|
||||
if (_random.Prob(0.5f))
|
||||
offset.X *= -1;
|
||||
else
|
||||
offset.Y *= -1;
|
||||
}
|
||||
|
||||
lastJitter = offset;
|
||||
|
||||
// For the first half of the jitter, we vertically displace the sprite upwards to simulate breathing in
|
||||
if (i <= jitters / 2)
|
||||
{
|
||||
keyFrames.Add(new AnimationTrackProperty.KeyFrame(startOffset + breaths * i + offset, frames));
|
||||
}
|
||||
// For the next quarter we displace the sprite down, to about 12.5% breathing offset below our starting position
|
||||
// Simulates breathing out
|
||||
else if (i < jitters * 3 / 4)
|
||||
{
|
||||
keyFrames.Add(
|
||||
new AnimationTrackProperty.KeyFrame(startOffset + breaths * ( jitters - i * 1.5f ) + offset, frames));
|
||||
}
|
||||
// Return to our starting position for breathing, jitter reaches its final position
|
||||
else
|
||||
{
|
||||
keyFrames.Add(
|
||||
new AnimationTrackProperty.KeyFrame(startOffset + breaths * ( i - jitters ) + offset, frames));
|
||||
}
|
||||
}
|
||||
|
||||
return new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(length),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty
|
||||
{
|
||||
// Heavy Breathing
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Offset),
|
||||
InterpolationMode = AnimationInterpolationMode.Cubic,
|
||||
KeyFrames = keyFrames,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum StunVisualLayers : byte
|
||||
{
|
||||
StamCrit,
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Content.Client.SurveillanceCamera.UI;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> CameraStaticShader = "CameraStatic";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
@@ -53,7 +55,7 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||
|
||||
// This could be done better. I don't want to deal with stylesheets at the moment.
|
||||
var texture = _resourceCache.GetTexture("/Textures/Interface/Nano/square_black.png");
|
||||
var shader = _prototypeManager.Index<ShaderPrototype>("CameraStatic").Instance().Duplicate();
|
||||
var shader = _prototypeManager.Index(CameraStaticShader).Instance().Duplicate();
|
||||
|
||||
CameraView.ViewportSize = new Vector2i(500, 500);
|
||||
CameraView.Eye = _defaultEye; // sure
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed partial class SurveillanceCameraSetupWindow : DefaultWindow
|
||||
}
|
||||
|
||||
// Pass in a list of frequency prototype IDs.
|
||||
public void LoadAvailableNetworks(uint currentNetwork, List<string> networks)
|
||||
public void LoadAvailableNetworks(uint currentNetwork, List<ProtoId<DeviceFrequencyPrototype>> networks)
|
||||
{
|
||||
NetworkSelector.Clear();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Content.Client.UserInterface.RichText;
|
||||
/// </summary>
|
||||
public sealed class MonoTag : IMarkupTag
|
||||
{
|
||||
[ValidatePrototypeId<FontPrototype>] public const string MonoFont = "Monospace";
|
||||
public static readonly ProtoId<FontPrototype> MonoFont = "Monospace";
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
@@ -67,8 +67,7 @@ public sealed partial class ChatUIController : UIController
|
||||
[UISystemDependency] private readonly MindSystem? _mindSystem = default!;
|
||||
[UISystemDependency] private readonly RoleCodewordSystem? _roleCodewordSystem = default!;
|
||||
|
||||
[ValidatePrototypeId<ColorPalettePrototype>]
|
||||
private const string ChatNamePalette = "ChatNames";
|
||||
private static readonly ProtoId<ColorPalettePrototype> ChatNamePalette = "ChatNames";
|
||||
private string[] _chatNameColors = default!;
|
||||
private bool _chatNameColorsEnabled;
|
||||
|
||||
@@ -232,7 +231,7 @@ public sealed partial class ChatUIController : UIController
|
||||
gameplayStateLoad.OnScreenLoad += OnScreenLoad;
|
||||
gameplayStateLoad.OnScreenUnload += OnScreenUnload;
|
||||
|
||||
var nameColors = _prototypeManager.Index<ColorPalettePrototype>(ChatNamePalette).Colors.Values.ToArray();
|
||||
var nameColors = _prototypeManager.Index(ChatNamePalette).Colors.Values.ToArray();
|
||||
_chatNameColors = new string[nameColors.Length];
|
||||
for (var i = 0; i < nameColors.Length; i++)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace Content.Client.UserInterface.Systems.DamageOverlays.Overlays;
|
||||
|
||||
public sealed class DamageOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> CircleMaskShader = "GradientCircleMask";
|
||||
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
@@ -49,9 +51,9 @@ public sealed class DamageOverlay : Overlay
|
||||
{
|
||||
// TODO: Replace
|
||||
IoCManager.InjectDependencies(this);
|
||||
_oxygenShader = _prototypeManager.Index<ShaderPrototype>("GradientCircleMask").InstanceUnique();
|
||||
_critShader = _prototypeManager.Index<ShaderPrototype>("GradientCircleMask").InstanceUnique();
|
||||
_bruteShader = _prototypeManager.Index<ShaderPrototype>("GradientCircleMask").InstanceUnique();
|
||||
_oxygenShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
|
||||
_critShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
|
||||
_bruteShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -19,8 +19,7 @@ public sealed class InfoUIController : UIController, IOnStateExited<GameplayStat
|
||||
private RulesPopup? _rulesPopup;
|
||||
private RulesAndInfoWindow? _infoWindow;
|
||||
|
||||
[ValidatePrototypeId<GuideEntryPrototype>]
|
||||
private const string DefaultRuleset = "DefaultRuleset";
|
||||
private static readonly ProtoId<GuideEntryPrototype> DefaultRuleset = "DefaultRuleset";
|
||||
|
||||
public ProtoId<GuideEntryPrototype> RulesEntryId = DefaultRuleset;
|
||||
|
||||
@@ -92,7 +91,7 @@ public sealed class InfoUIController : UIController, IOnStateExited<GameplayStat
|
||||
{
|
||||
if (!_prototype.TryIndex(RulesEntryId, out var guideEntryPrototype))
|
||||
{
|
||||
guideEntryPrototype = _prototype.Index<GuideEntryPrototype>(DefaultRuleset);
|
||||
guideEntryPrototype = _prototype.Index(DefaultRuleset);
|
||||
Log.Error($"Couldn't find the following prototype: {RulesEntryId}. Falling back to {DefaultRuleset}, please check that the server has the rules set up correctly");
|
||||
return guideEntryPrototype;
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
public const string HitscanProto = "HitscanEffect";
|
||||
public static readonly EntProtoId HitscanProto = "HitscanEffect";
|
||||
|
||||
public bool SpreadOverlay
|
||||
{
|
||||
|
||||
@@ -7,10 +7,12 @@ namespace Content.IntegrationTests.Tests.Atmos
|
||||
[TestOf(typeof(AtmosAlarmThreshold))]
|
||||
public sealed class AlarmThresholdTest
|
||||
{/*
|
||||
private const string AlarmThresholdTestDummyId = "AlarmThresholdTestDummy";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
private const string Prototypes = $@"
|
||||
- type: alarmThreshold
|
||||
id: AlarmThresholdTestDummy
|
||||
id: {AlarmThresholdTestDummyId}
|
||||
upperBound: !type:AlarmThresholdSetting
|
||||
threshold: 5
|
||||
lowerBound: !type:AlarmThresholdSetting
|
||||
@@ -30,7 +32,7 @@ namespace Content.IntegrationTests.Tests.Atmos
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
AtmosAlarmThreshold threshold = default!;
|
||||
|
||||
var proto = prototypeManager.Index<AtmosAlarmThresholdPrototype>("AlarmThresholdTestDummy");
|
||||
var proto = prototypeManager.Index<AtmosAlarmThresholdPrototype>(AlarmThresholdTestDummyId);
|
||||
threshold = new(proto);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
[TestOf(typeof(RejuvenateSystem))]
|
||||
public sealed class RejuvenateTest
|
||||
{
|
||||
private static readonly ProtoId<DamageGroupPrototype> TestDamageGroup = "Toxin";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
@@ -62,7 +64,7 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
});
|
||||
|
||||
// Kill the entity
|
||||
DamageSpecifier damage = new(prototypeManager.Index<DamageGroupPrototype>("Toxin"), FixedPoint2.New(10000000));
|
||||
DamageSpecifier damage = new(prototypeManager.Index(TestDamageGroup), FixedPoint2.New(10000000));
|
||||
|
||||
damSystem.TryChangeDamage(human, damage, true);
|
||||
|
||||
|
||||
@@ -53,6 +53,8 @@ public sealed class SuicideCommandTests
|
||||
components:
|
||||
- type: MaterialReclaimer";
|
||||
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
|
||||
private static readonly ProtoId<DamageTypePrototype> DamageType = "Slash";
|
||||
|
||||
/// <summary>
|
||||
/// Run the suicide command in the console
|
||||
/// Should successfully kill the player and ghost them
|
||||
@@ -144,7 +146,7 @@ public sealed class SuicideCommandTests
|
||||
mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
|
||||
damageableComp = entManager.GetComponent<DamageableComponent>(player);
|
||||
|
||||
if (protoMan.TryIndex<DamageTypePrototype>("Slash", out var slashProto))
|
||||
if (protoMan.TryIndex(DamageType, out var slashProto))
|
||||
damageableSystem.TryChangeDamage(player, new DamageSpecifier(slashProto, FixedPoint2.New(46.5)));
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Content.IntegrationTests.Tests.Construction.Interaction;
|
||||
|
||||
public sealed class WindowRepair : InteractionTest
|
||||
{
|
||||
private static readonly ProtoId<DamageTypePrototype> BluntDamageType = "Blunt";
|
||||
|
||||
[Test]
|
||||
public async Task RepairReinforcedWindow()
|
||||
{
|
||||
@@ -16,7 +18,7 @@ public sealed class WindowRepair : InteractionTest
|
||||
// Damage the entity.
|
||||
var sys = SEntMan.System<DamageableSystem>();
|
||||
var comp = Comp<DamageableComponent>();
|
||||
var damageType = Server.ResolveDependency<IPrototypeManager>().Index<DamageTypePrototype>("Blunt");
|
||||
var damageType = Server.ProtoMan.Index(BluntDamageType);
|
||||
var damage = new DamageSpecifier(damageType, FixedPoint2.New(10));
|
||||
Assert.That(comp.Damage.GetTotal(), Is.EqualTo(FixedPoint2.Zero));
|
||||
await Server.WaitPost(() => sys.TryChangeDamage(SEntMan.GetEntity(Target), damage, ignoreResistances: true));
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -14,66 +12,79 @@ namespace Content.IntegrationTests.Tests.Damageable
|
||||
[TestOf(typeof(DamageableSystem))]
|
||||
public sealed class DamageableTest
|
||||
{
|
||||
private const string TestDamageableEntityId = "TestDamageableEntityId";
|
||||
private const string TestGroup1 = "TestGroup1";
|
||||
private const string TestGroup2 = "TestGroup2";
|
||||
private const string TestGroup3 = "TestGroup3";
|
||||
private const string TestDamage1 = "TestDamage1";
|
||||
private const string TestDamage2a = "TestDamage2a";
|
||||
private const string TestDamage2b = "TestDamage2b";
|
||||
|
||||
private const string TestDamage3a = "TestDamage3a";
|
||||
|
||||
private const string TestDamage3b = "TestDamage3b";
|
||||
private const string TestDamage3c = "TestDamage3c";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
private const string Prototypes = $@"
|
||||
# Define some damage groups
|
||||
- type: damageType
|
||||
id: TestDamage1
|
||||
id: {TestDamage1}
|
||||
name: damage-type-blunt
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage2a
|
||||
id: {TestDamage2a}
|
||||
name: damage-type-blunt
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage2b
|
||||
id: {TestDamage2b}
|
||||
name: damage-type-blunt
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage3a
|
||||
id: {TestDamage3a}
|
||||
name: damage-type-blunt
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage3b
|
||||
id: {TestDamage3b}
|
||||
name: damage-type-blunt
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage3c
|
||||
id: {TestDamage3c}
|
||||
name: damage-type-blunt
|
||||
|
||||
# Define damage Groups with 1,2,3 damage types
|
||||
- type: damageGroup
|
||||
id: TestGroup1
|
||||
id: {TestGroup1}
|
||||
name: damage-group-brute
|
||||
damageTypes:
|
||||
- TestDamage1
|
||||
- {TestDamage1}
|
||||
|
||||
- type: damageGroup
|
||||
id: TestGroup2
|
||||
id: {TestGroup2}
|
||||
name: damage-group-brute
|
||||
damageTypes:
|
||||
- TestDamage2a
|
||||
- TestDamage2b
|
||||
- {TestDamage2a}
|
||||
- {TestDamage2b}
|
||||
|
||||
- type: damageGroup
|
||||
id: TestGroup3
|
||||
id: {TestGroup3}
|
||||
name: damage-group-brute
|
||||
damageTypes:
|
||||
- TestDamage3a
|
||||
- TestDamage3b
|
||||
- TestDamage3c
|
||||
- {TestDamage3a}
|
||||
- {TestDamage3b}
|
||||
- {TestDamage3c}
|
||||
|
||||
# This container should not support TestDamage1 or TestDamage2b
|
||||
- type: damageContainer
|
||||
id: testDamageContainer
|
||||
supportedGroups:
|
||||
- TestGroup3
|
||||
- {TestGroup3}
|
||||
supportedTypes:
|
||||
- TestDamage2a
|
||||
- {TestDamage2a}
|
||||
|
||||
- type: entity
|
||||
id: TestDamageableEntityId
|
||||
name: TestDamageableEntityId
|
||||
id: {TestDamageableEntityId}
|
||||
name: {TestDamageableEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: testDamageContainer
|
||||
@@ -113,20 +124,20 @@ namespace Content.IntegrationTests.Tests.Damageable
|
||||
{
|
||||
var coordinates = map.MapCoords;
|
||||
|
||||
sDamageableEntity = sEntityManager.SpawnEntity("TestDamageableEntityId", coordinates);
|
||||
sDamageableEntity = sEntityManager.SpawnEntity(TestDamageableEntityId, coordinates);
|
||||
sDamageableComponent = sEntityManager.GetComponent<DamageableComponent>(sDamageableEntity);
|
||||
sDamageableSystem = sEntitySystemManager.GetEntitySystem<DamageableSystem>();
|
||||
|
||||
group1 = sPrototypeManager.Index<DamageGroupPrototype>("TestGroup1");
|
||||
group2 = sPrototypeManager.Index<DamageGroupPrototype>("TestGroup2");
|
||||
group3 = sPrototypeManager.Index<DamageGroupPrototype>("TestGroup3");
|
||||
group1 = sPrototypeManager.Index<DamageGroupPrototype>(TestGroup1);
|
||||
group2 = sPrototypeManager.Index<DamageGroupPrototype>(TestGroup2);
|
||||
group3 = sPrototypeManager.Index<DamageGroupPrototype>(TestGroup3);
|
||||
|
||||
type1 = sPrototypeManager.Index<DamageTypePrototype>("TestDamage1");
|
||||
type2a = sPrototypeManager.Index<DamageTypePrototype>("TestDamage2a");
|
||||
type2b = sPrototypeManager.Index<DamageTypePrototype>("TestDamage2b");
|
||||
type3a = sPrototypeManager.Index<DamageTypePrototype>("TestDamage3a");
|
||||
type3b = sPrototypeManager.Index<DamageTypePrototype>("TestDamage3b");
|
||||
type3c = sPrototypeManager.Index<DamageTypePrototype>("TestDamage3c");
|
||||
type1 = sPrototypeManager.Index<DamageTypePrototype>(TestDamage1);
|
||||
type2a = sPrototypeManager.Index<DamageTypePrototype>(TestDamage2a);
|
||||
type2b = sPrototypeManager.Index<DamageTypePrototype>(TestDamage2b);
|
||||
type3a = sPrototypeManager.Index<DamageTypePrototype>(TestDamage3a);
|
||||
type3b = sPrototypeManager.Index<DamageTypePrototype>(TestDamage3b);
|
||||
type3c = sPrototypeManager.Index<DamageTypePrototype>(TestDamage3c);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
@@ -54,8 +54,8 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var bruteDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBrute");
|
||||
var burnDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBurn");
|
||||
var bruteDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>(TestBruteDamageGroupId);
|
||||
var burnDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>(TestBurnDamageGroupId);
|
||||
|
||||
DamageSpecifier bruteDamage = new(bruteDamageGroup, FixedPoint2.New(5));
|
||||
DamageSpecifier burnDamage = new(burnDamageGroup, FixedPoint2.New(5));
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var bluntDamageType = protoManager.Index<DamageTypePrototype>("TestBlunt");
|
||||
var slashDamageType = protoManager.Index<DamageTypePrototype>("TestSlash");
|
||||
var bluntDamageType = protoManager.Index<DamageTypePrototype>(TestBluntDamageTypeId);
|
||||
var slashDamageType = protoManager.Index<DamageTypePrototype>(TestSlashDamageTypeId);
|
||||
|
||||
var bluntDamage = new DamageSpecifier(bluntDamageType, 5);
|
||||
var slashDamage = new DamageSpecifier(slashDamageType, 5);
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var coordinates = sEntityManager.GetComponent<TransformComponent>(sDestructibleEntity).Coordinates;
|
||||
var bruteDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBrute");
|
||||
var bruteDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>(TestBruteDamageGroupId);
|
||||
DamageSpecifier bruteDamage = new(bruteDamageGroup, 50);
|
||||
|
||||
#pragma warning disable NUnit2045 // Interdependent assertions.
|
||||
|
||||
@@ -7,48 +7,56 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
public const string DestructibleDestructionEntityId = "DestructibleTestsDestructibleDestructionEntity";
|
||||
public const string DestructibleDamageTypeEntityId = "DestructibleTestsDestructibleDamageTypeEntity";
|
||||
public const string DestructibleDamageGroupEntityId = "DestructibleTestsDestructibleDamageGroupEntity";
|
||||
public const string TestBruteDamageGroupId = "TestBrute";
|
||||
public const string TestBurnDamageGroupId = "TestBurn";
|
||||
public const string TestBluntDamageTypeId = "TestBlunt";
|
||||
public const string TestSlashDamageTypeId = "TestSlash";
|
||||
public const string TestPiercingDamageTypeId = "TestPiercing";
|
||||
public const string TestHeatDamageTypeId = "TestHeat";
|
||||
public const string TestShockDamageTypeId = "TestShock";
|
||||
public const string TestColdDamageTypeId = "TestCold";
|
||||
|
||||
[TestPrototypes]
|
||||
public const string DamagePrototypes = $@"
|
||||
- type: damageType
|
||||
id: TestBlunt
|
||||
id: {TestBluntDamageTypeId}
|
||||
name: damage-type-blunt
|
||||
|
||||
- type: damageType
|
||||
id: TestSlash
|
||||
id: {TestSlashDamageTypeId}
|
||||
name: damage-type-slash
|
||||
|
||||
- type: damageType
|
||||
id: TestPiercing
|
||||
id: {TestPiercingDamageTypeId}
|
||||
name: damage-type-piercing
|
||||
|
||||
- type: damageType
|
||||
id: TestHeat
|
||||
id: {TestHeatDamageTypeId}
|
||||
name: damage-type-heat
|
||||
|
||||
- type: damageType
|
||||
id: TestShock
|
||||
id: {TestShockDamageTypeId}
|
||||
name: damage-type-shock
|
||||
|
||||
- type: damageType
|
||||
id: TestCold
|
||||
id: {TestColdDamageTypeId}
|
||||
name: damage-type-cold
|
||||
|
||||
- type: damageGroup
|
||||
id: TestBrute
|
||||
id: {TestBruteDamageGroupId}
|
||||
name: damage-group-brute
|
||||
damageTypes:
|
||||
- TestBlunt
|
||||
- TestSlash
|
||||
- TestPiercing
|
||||
- {TestBluntDamageTypeId}
|
||||
- {TestSlashDamageTypeId}
|
||||
- {TestPiercingDamageTypeId}
|
||||
|
||||
- type: damageGroup
|
||||
id: TestBurn
|
||||
id: {TestBurnDamageGroupId}
|
||||
name: damage-group-burn
|
||||
damageTypes:
|
||||
- TestHeat
|
||||
- TestShock
|
||||
- TestCold
|
||||
- {TestHeatDamageTypeId}
|
||||
- {TestShockDamageTypeId}
|
||||
- {TestColdDamageTypeId}
|
||||
|
||||
- type: entity
|
||||
id: {SpawnedEntityId}
|
||||
@@ -114,10 +122,10 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
!type:AndTrigger
|
||||
triggers:
|
||||
- !type:DamageTypeTrigger
|
||||
damageType: TestBlunt
|
||||
damageType: {TestBluntDamageTypeId}
|
||||
damage: 10
|
||||
- !type:DamageTypeTrigger
|
||||
damageType: TestSlash
|
||||
damageType: {TestSlashDamageTypeId}
|
||||
damage: 10
|
||||
|
||||
- type: entity
|
||||
@@ -131,10 +139,10 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
!type:AndTrigger
|
||||
triggers:
|
||||
- !type:DamageGroupTrigger
|
||||
damageGroup: TestBrute
|
||||
damageGroup: {TestBruteDamageGroupId}
|
||||
damage: 10
|
||||
- !type:DamageGroupTrigger
|
||||
damageGroup: TestBurn
|
||||
damageGroup: {TestBurnDamageGroupId}
|
||||
damage: 10";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var bluntDamage = new DamageSpecifier(sPrototypeManager.Index<DamageTypePrototype>("TestBlunt"), 10);
|
||||
var bluntDamage = new DamageSpecifier(sPrototypeManager.Index<DamageTypePrototype>(TestBluntDamageTypeId), 10);
|
||||
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage, true);
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ public sealed class AbsorbentTest
|
||||
solutionContainerSystem.AddSolution(refillableSoln.Value, new Solution(NonEvaporablePrototypeId, testCase.InitialRefillableSolution.VolumeOfNonEvaporable));
|
||||
|
||||
// Act
|
||||
absorbentSystem.Mop(user, refillable, absorbent, component);
|
||||
absorbentSystem.Mop((absorbent, component), user, refillable);
|
||||
|
||||
// Assert
|
||||
var absorbentComposition = absorbentSolution.GetReagentPrototypes(prototypeManager).ToDictionary(r => r.Key.ID, r => r.Value);
|
||||
@@ -167,7 +167,7 @@ public sealed class AbsorbentTest
|
||||
solutionContainerSystem.AddSolution(refillableSoln.Value, new Solution(NonEvaporablePrototypeId, testCase.InitialRefillableSolution.VolumeOfNonEvaporable));
|
||||
|
||||
// Act
|
||||
absorbentSystem.Mop(user, refillable, absorbent, component);
|
||||
absorbentSystem.Mop((absorbent, component), user, refillable);
|
||||
|
||||
// Assert
|
||||
var absorbentComposition = absorbentSolution.GetReagentPrototypes(prototypeManager).ToDictionary(r => r.Key.ID, r => r.Value);
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Shared.Tag;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Linter;
|
||||
|
||||
@@ -66,25 +65,25 @@ public sealed class StaticFieldValidationTest
|
||||
[Reflect(false)]
|
||||
private sealed class StringValid
|
||||
{
|
||||
[ValidatePrototypeId<TagPrototype>] public static string Tag = "StaticFieldTestTag";
|
||||
public static readonly ProtoId<TagPrototype> Tag = "StaticFieldTestTag";
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class StringInvalid
|
||||
{
|
||||
[ValidatePrototypeId<TagPrototype>] public static string Tag = string.Empty;
|
||||
public static readonly ProtoId<TagPrototype> Tag = string.Empty;
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class StringArrayValid
|
||||
{
|
||||
[ValidatePrototypeId<TagPrototype>] public static string[] Tag = ["StaticFieldTestTag", "StaticFieldTestTag"];
|
||||
public static readonly ProtoId<TagPrototype>[] Tag = ["StaticFieldTestTag", "StaticFieldTestTag"];
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class StringArrayInvalid
|
||||
{
|
||||
[ValidatePrototypeId<TagPrototype>] public static string[] Tag = [string.Empty, "StaticFieldTestTag", string.Empty];
|
||||
public static readonly ProtoId<TagPrototype>[] Tag = [string.Empty, "StaticFieldTestTag", string.Empty];
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
|
||||
@@ -1,38 +1,48 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Minds;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class GhostRoleTests
|
||||
{
|
||||
private const string GhostRoleProtoId = "GhostRoleTestEntity";
|
||||
private const string TestMobProtoId = "GhostRoleTestMob";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
id: GhostRoleTestEntity
|
||||
components:
|
||||
- type: MindContainer
|
||||
- type: GhostRole
|
||||
- type: GhostTakeoverAvailable
|
||||
";
|
||||
private const string Prototypes = $"""
|
||||
- type: entity
|
||||
id: {GhostRoleProtoId}
|
||||
components:
|
||||
- type: MindContainer
|
||||
- type: GhostRole
|
||||
- type: GhostTakeoverAvailable
|
||||
- type: MobState
|
||||
|
||||
- type: entity
|
||||
id: {TestMobProtoId}
|
||||
components:
|
||||
- type: MobState # MobState is required for correct determination of if the player can return to body or not
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// This is a simple test that just checks if a player can take a ghost role and then regain control of their
|
||||
/// original entity without encountering errors.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TakeRoleAndReturn()
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public async Task TakeRoleAndReturn(bool adminGhost)
|
||||
{
|
||||
var ghostCommand = adminGhost ? "aghost" : "ghost";
|
||||
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Dirty = true,
|
||||
@@ -49,36 +59,67 @@ public sealed class GhostRoleTests
|
||||
var conHost = client.ResolveDependency<IConsoleHost>();
|
||||
var mindSystem = entMan.System<SharedMindSystem>();
|
||||
var session = sPlayerMan.Sessions.Single();
|
||||
var originalMindId = session.ContentData()!.Mind!.Value;
|
||||
var originalPlayerMindId = session.ContentData()!.Mind!.Value;
|
||||
|
||||
// Check that there are no ghosts
|
||||
Assert.That(entMan.Count<GhostComponent>(), Is.Zero);
|
||||
|
||||
// Spawn player entity & attach
|
||||
EntityUid originalMob = default;
|
||||
EntityUid originalPlayerMob = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
originalMob = entMan.SpawnEntity(null, mapData.GridCoords);
|
||||
mindSystem.TransferTo(originalMindId, originalMob, true);
|
||||
originalPlayerMob = entMan.SpawnEntity(TestMobProtoId, mapData.GridCoords);
|
||||
mindSystem.TransferTo(originalPlayerMindId, originalPlayerMob, true);
|
||||
});
|
||||
|
||||
// Check player got attached.
|
||||
await pair.RunTicksSync(10);
|
||||
Assert.That(session.AttachedEntity, Is.EqualTo(originalMob));
|
||||
var originalMind = entMan.GetComponent<MindComponent>(originalMindId);
|
||||
Assert.That(originalMind.OwnedEntity, Is.EqualTo(originalMob));
|
||||
Assert.That(originalMind.VisitingEntity, Is.Null);
|
||||
var originalPlayerMind = entMan.GetComponent<MindComponent>(originalPlayerMindId);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Check player got attached.
|
||||
Assert.That(session.AttachedEntity, Is.EqualTo(originalPlayerMob));
|
||||
Assert.That(originalPlayerMind.OwnedEntity, Is.EqualTo(originalPlayerMob));
|
||||
Assert.That(originalPlayerMind.VisitingEntity, Is.Null);
|
||||
Assert.That(originalPlayerMind.OriginalOwnerUserId, Is.EqualTo(session.UserId));
|
||||
|
||||
// Check that there are still no ghosts
|
||||
Assert.That(entMan.Count<GhostComponent>(), Is.Zero);
|
||||
});
|
||||
|
||||
// Use the ghost command
|
||||
conHost.ExecuteCommand("ghost");
|
||||
conHost.ExecuteCommand(ghostCommand);
|
||||
await pair.RunTicksSync(10);
|
||||
var ghost = session.AttachedEntity;
|
||||
Assert.That(entMan.HasComponent<GhostComponent>(ghost));
|
||||
Assert.That(ghost, Is.Not.EqualTo(originalMob));
|
||||
Assert.That(session.ContentData()?.Mind, Is.EqualTo(originalMindId));
|
||||
Assert.That(originalMind.OwnedEntity, Is.EqualTo(originalMob), $"Original mob: {originalMob}, Ghost: {ghost}");
|
||||
Assert.That(originalMind.VisitingEntity, Is.EqualTo(ghost));
|
||||
var ghostOne = session.AttachedEntity;
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Assert that the ghost is a new entity with a new mind
|
||||
Assert.That(entMan.HasComponent<GhostComponent>(ghostOne));
|
||||
Assert.That(ghostOne, Is.Not.EqualTo(originalPlayerMob));
|
||||
Assert.That(session.ContentData()?.Mind, Is.EqualTo(originalPlayerMindId));
|
||||
if (adminGhost)
|
||||
{
|
||||
// aghost, so the player mob should still own the mind, but the mind is visiting the ghost.
|
||||
Assert.That(originalPlayerMind.OwnedEntity, Is.EqualTo(originalPlayerMob));
|
||||
Assert.That(originalPlayerMind.VisitingEntity, Is.EqualTo(ghostOne));
|
||||
Assert.That(originalPlayerMind.UserId, Is.EqualTo(session.UserId));
|
||||
}
|
||||
else
|
||||
{
|
||||
// player ghost, can't return. The mind is owned by the ghost, and is not visiting.
|
||||
Assert.That(originalPlayerMind.OwnedEntity, Is.EqualTo(ghostOne));
|
||||
Assert.That(originalPlayerMind.VisitingEntity, Is.Null);
|
||||
}
|
||||
|
||||
// Check that we're tracking the original owner for round end screen
|
||||
Assert.That(originalPlayerMind.OriginalOwnerUserId, Is.EqualTo(session.UserId));
|
||||
|
||||
// Check that there is only one ghost
|
||||
Assert.That(entMan.Count<GhostComponent>(), Is.EqualTo(1));
|
||||
});
|
||||
|
||||
// Spawn ghost takeover entity.
|
||||
EntityUid ghostRole = default;
|
||||
await server.WaitPost(() => ghostRole = entMan.SpawnEntity("GhostRoleTestEntity", mapData.GridCoords));
|
||||
await server.WaitPost(() => ghostRole = entMan.SpawnEntity(GhostRoleProtoId, mapData.GridCoords));
|
||||
|
||||
// Take the ghost role
|
||||
await server.WaitPost(() =>
|
||||
@@ -89,40 +130,118 @@ public sealed class GhostRoleTests
|
||||
|
||||
// Check player got attached to ghost role.
|
||||
await pair.RunTicksSync(10);
|
||||
var newMindId = session.ContentData()!.Mind!.Value;
|
||||
var newMind = entMan.GetComponent<MindComponent>(newMindId);
|
||||
Assert.That(newMindId, Is.Not.EqualTo(originalMindId));
|
||||
Assert.That(session.AttachedEntity, Is.EqualTo(ghostRole));
|
||||
Assert.That(newMind.OwnedEntity, Is.EqualTo(ghostRole));
|
||||
Assert.That(newMind.VisitingEntity, Is.Null);
|
||||
var ghostRoleMindId = session.ContentData()!.Mind!.Value;
|
||||
var ghostRoleMind = entMan.GetComponent<MindComponent>(ghostRoleMindId);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Check that the ghost role mind is new
|
||||
Assert.That(ghostRoleMindId, Is.Not.EqualTo(originalPlayerMindId));
|
||||
|
||||
// Original mind should be unaffected, but the ghost will have deleted itself.
|
||||
Assert.That(originalMind.OwnedEntity, Is.EqualTo(originalMob));
|
||||
Assert.That(originalMind.VisitingEntity, Is.Null);
|
||||
Assert.That(entMan.Deleted(ghost));
|
||||
// Check that the session and mind are properly attached to the ghost role
|
||||
Assert.That(session.AttachedEntity, Is.EqualTo(ghostRole));
|
||||
Assert.That(ghostRoleMind.OwnedEntity, Is.EqualTo(ghostRole));
|
||||
Assert.That(ghostRoleMind.VisitingEntity, Is.Null);
|
||||
|
||||
// Original mind should be unaffected, but the ghost will have deleted itself.
|
||||
if (adminGhost)
|
||||
{
|
||||
// aghost case, the original player mob should still own the mind, and that mind is not visiting.
|
||||
Assert.That(originalPlayerMind.OwnedEntity, Is.EqualTo(originalPlayerMob));
|
||||
}
|
||||
else
|
||||
{
|
||||
// player ghost case, the original mind is disconnected and not owned by an entity.
|
||||
// This mind cannot be returned to
|
||||
Assert.That(originalPlayerMind.OwnedEntity, Is.Null);
|
||||
}
|
||||
|
||||
// In either case the original player mind is not visiting anything, not connected to any user.
|
||||
Assert.That(originalPlayerMind.VisitingEntity, Is.Null);
|
||||
Assert.That(originalPlayerMind.UserId, Is.Null);
|
||||
|
||||
// Now the original owner of both minds should permanently be set to this session.
|
||||
Assert.That(originalPlayerMind.OriginalOwnerUserId, Is.EqualTo(session.UserId));
|
||||
Assert.That(ghostRoleMind.OriginalOwnerUserId, Is.EqualTo(session.UserId));
|
||||
|
||||
// Make sure that the ghost was deleted
|
||||
Assert.That(entMan.Deleted(ghostOne));
|
||||
|
||||
// Check that there is are no lingereing ghosts
|
||||
Assert.That(entMan.Count<GhostComponent>(), Is.Zero);
|
||||
});
|
||||
|
||||
// Ghost again.
|
||||
conHost.ExecuteCommand("ghost");
|
||||
conHost.ExecuteCommand(ghostCommand);
|
||||
await pair.RunTicksSync(10);
|
||||
var otherGhost = session.AttachedEntity;
|
||||
Assert.That(entMan.HasComponent<GhostComponent>(otherGhost));
|
||||
Assert.That(otherGhost, Is.Not.EqualTo(originalMob));
|
||||
Assert.That(otherGhost, Is.Not.EqualTo(ghostRole));
|
||||
Assert.That(session.ContentData()?.Mind, Is.EqualTo(newMindId));
|
||||
Assert.That(newMind.OwnedEntity, Is.EqualTo(ghostRole));
|
||||
Assert.That(newMind.VisitingEntity, Is.EqualTo(session.AttachedEntity));
|
||||
var ghostTwo = session.AttachedEntity;
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Check that the new ghost is a new entity
|
||||
Assert.That(entMan.HasComponent<GhostComponent>(ghostTwo));
|
||||
Assert.That(ghostTwo, Is.Not.EqualTo(originalPlayerMob));
|
||||
Assert.That(ghostTwo, Is.Not.EqualTo(ghostRole));
|
||||
Assert.That(session.ContentData()?.Mind, Is.EqualTo(ghostRoleMindId));
|
||||
|
||||
if(adminGhost)
|
||||
{
|
||||
// aghost case, the ghost role mind should be owned by the ghost role entity,
|
||||
// the ghost role mind is visiting the new ghost
|
||||
Assert.That(ghostRoleMind.OwnedEntity, Is.EqualTo(ghostRole));
|
||||
Assert.That(ghostRoleMind.VisitingEntity, Is.EqualTo(ghostTwo));
|
||||
}
|
||||
else
|
||||
{
|
||||
// player ghost, can't return. The mind is owned by the ghost, and is not visiting.
|
||||
Assert.That(ghostRoleMind.OwnedEntity, Is.EqualTo(ghostTwo));
|
||||
Assert.That(ghostRoleMind.VisitingEntity, Is.Null);
|
||||
}
|
||||
|
||||
// Check that the original mind is still not attached to a user
|
||||
Assert.That(originalPlayerMind.UserId, Is.Null);
|
||||
|
||||
// Check that original owners of other minds are still tracked
|
||||
Assert.That(originalPlayerMind.OriginalOwnerUserId, Is.EqualTo(session.UserId));
|
||||
Assert.That(ghostRoleMind.OriginalOwnerUserId, Is.EqualTo(session.UserId));
|
||||
|
||||
// Check that there is exactly one ghost
|
||||
Assert.That(entMan.Count<GhostComponent>(), Is.EqualTo(1));
|
||||
});
|
||||
|
||||
if (!adminGhost)
|
||||
{
|
||||
// End of the normal player ghost role test
|
||||
await pair.CleanReturnAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
// Next, control the original entity again:
|
||||
await server.WaitPost(() => mindSystem.SetUserId(originalMindId, session.UserId));
|
||||
await server.WaitPost(() => mindSystem.SetUserId(originalPlayerMindId, session.UserId));
|
||||
await pair.RunTicksSync(10);
|
||||
Assert.That(session.AttachedEntity, Is.EqualTo(originalMob));
|
||||
Assert.That(originalMind.OwnedEntity, Is.EqualTo(originalMob));
|
||||
Assert.That(originalMind.VisitingEntity, Is.Null);
|
||||
|
||||
// the ghost-role mind is unaffected, though the ghost will have deleted itself
|
||||
Assert.That(newMind.OwnedEntity, Is.EqualTo(ghostRole));
|
||||
Assert.That(newMind.VisitingEntity, Is.Null);
|
||||
Assert.That(entMan.Deleted(otherGhost));
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Check that we are attached
|
||||
Assert.That(session.AttachedEntity, Is.EqualTo(originalPlayerMob));
|
||||
|
||||
// Check the ownership of the original mind
|
||||
Assert.That(originalPlayerMind.OwnedEntity, Is.EqualTo(originalPlayerMob));
|
||||
Assert.That(originalPlayerMind.VisitingEntity, Is.Null);
|
||||
Assert.That(originalPlayerMind.UserId, Is.EqualTo(session.UserId));
|
||||
|
||||
// Check that the ghost-role mind is unaffected
|
||||
Assert.That(ghostRoleMind.OwnedEntity, Is.EqualTo(ghostRole));
|
||||
Assert.That(ghostRoleMind.VisitingEntity, Is.Null);
|
||||
|
||||
// Check that the second ghost is deleted
|
||||
Assert.That(entMan.Deleted(ghostTwo));
|
||||
|
||||
// Check that the original owners of the previous minds are still tracked
|
||||
Assert.That(originalPlayerMind.OriginalOwnerUserId, Is.EqualTo(session.UserId));
|
||||
Assert.That(ghostRoleMind.OriginalOwnerUserId, Is.EqualTo(session.UserId));
|
||||
|
||||
// Check that there is are no lingereing ghosts
|
||||
Assert.That(entMan.Count<GhostComponent>(), Is.Zero);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace Content.IntegrationTests.Tests.Minds;
|
||||
[TestFixture]
|
||||
public sealed partial class MindTests
|
||||
{
|
||||
private static readonly ProtoId<DamageTypePrototype> BluntDamageType = "Blunt";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
@@ -144,7 +146,7 @@ public sealed partial class MindTests
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var damageable = entMan.GetComponent<DamageableComponent>(entity);
|
||||
if (!protoMan.TryIndex<DamageTypePrototype>("Blunt", out var prototype))
|
||||
if (!protoMan.TryIndex(BluntDamageType, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Content.IntegrationTests.Tests
|
||||
//CrystallEdge Map replacement end
|
||||
};
|
||||
|
||||
private static readonly ProtoId<EntityCategoryPrototype> DoNotMapCategory = "DoNotMap";
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that specific files have been saved as grids and not maps.
|
||||
/// </summary>
|
||||
@@ -250,7 +252,7 @@ namespace Content.IntegrationTests.Tests
|
||||
return;
|
||||
|
||||
var yamlEntities = node["entities"];
|
||||
if (!protoManager.TryIndex<EntityCategoryPrototype>("DoNotMap", out var dnmCategory))
|
||||
if (!protoManager.TryIndex(DoNotMapCategory, out var dnmCategory))
|
||||
return;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
|
||||
@@ -17,8 +17,10 @@ namespace Content.IntegrationTests.Tests.Station;
|
||||
[TestOf(typeof(StationJobsSystem))]
|
||||
public sealed class StationJobsTest
|
||||
{
|
||||
private const string StationMapId = "FooStation";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
private const string Prototypes = $@"
|
||||
- type: playTimeTracker
|
||||
id: PlayTimeDummyAssistant
|
||||
|
||||
@@ -35,13 +37,13 @@ public sealed class StationJobsTest
|
||||
id: PlayTimeDummyChaplain
|
||||
|
||||
- type: gameMap
|
||||
id: FooStation
|
||||
id: {StationMapId}
|
||||
minPlayers: 0
|
||||
mapName: FooStation
|
||||
mapName: {StationMapId}
|
||||
mapPath: /Maps/Test/empty.yml
|
||||
stations:
|
||||
Station:
|
||||
mapNameTemplate: FooStation
|
||||
mapNameTemplate: {StationMapId}
|
||||
stationProto: StandardNanotrasenStation
|
||||
components:
|
||||
- type: StationJobs
|
||||
@@ -87,7 +89,7 @@ public sealed class StationJobsTest
|
||||
var server = pair.Server;
|
||||
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
|
||||
var fooStationProto = prototypeManager.Index<GameMapPrototype>(StationMapId);
|
||||
var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
|
||||
var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
|
||||
var stationSystem = entSysMan.GetEntitySystem<StationSystem>();
|
||||
@@ -161,7 +163,7 @@ public sealed class StationJobsTest
|
||||
var server = pair.Server;
|
||||
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
|
||||
var fooStationProto = prototypeManager.Index<GameMapPrototype>(StationMapId);
|
||||
var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
|
||||
var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
|
||||
var stationSystem = entSysMan.GetEntitySystem<StationSystem>();
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Vending;
|
||||
|
||||
@@ -17,6 +18,7 @@ public sealed class VendingInteractionTest : InteractionTest
|
||||
private const string RestockBoxProtoId = "InteractionTestRestockBox";
|
||||
|
||||
private const string RestockBoxOtherProtoId = "InteractionTestRestockBoxOther";
|
||||
private static readonly ProtoId<DamageTypePrototype> TestDamageType = "Blunt";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string TestPrototypes = $@"
|
||||
@@ -196,7 +198,7 @@ public sealed class VendingInteractionTest : InteractionTest
|
||||
Assert.That(damageableComp.Damage.GetTotal(), Is.EqualTo(FixedPoint2.Zero), $"{VendingMachineProtoId} started with unexpected damage.");
|
||||
|
||||
// Damage the vending machine to the point that it breaks
|
||||
var damageType = ProtoMan.Index<DamageTypePrototype>("Blunt");
|
||||
var damageType = ProtoMan.Index(TestDamageType);
|
||||
var damage = new DamageSpecifier(damageType, FixedPoint2.New(100));
|
||||
await Server.WaitPost(() => damageableSys.TryChangeDamage(SEntMan.GetEntity(Target), damage, ignoreResistances: true));
|
||||
await RunTicks(5);
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace Content.IntegrationTests.Tests
|
||||
[TestOf(typeof(VendingMachineSystem))]
|
||||
public sealed class VendingMachineRestockTest : EntitySystem
|
||||
{/*
|
||||
private static readonly ProtoId<DamageTypePrototype> TestDamageType = "Blunt";
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
@@ -293,7 +295,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"Did not start with zero ramen.");
|
||||
|
||||
restock = entityManager.SpawnEntity("TestRestockExplode", coordinates);
|
||||
var damageSpec = new DamageSpecifier(prototypeManager.Index<DamageTypePrototype>("Blunt"), 100);
|
||||
var damageSpec = new DamageSpecifier(prototypeManager.Index(TestDamageType), 100);
|
||||
var damageResult = damageableSystem.TryChangeDamage(restock, damageSpec);
|
||||
|
||||
#pragma warning disable NUnit2045
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user