Compare commits

...

545 Commits

Author SHA1 Message Date
Redrover1760 ea8c844578 fixes 2026-03-30 22:01:57 -04:00
Redrover1760 a9e4468d78 Revert "Revert "Merge branch 'april1st' into main" (#3655)"
This reverts commit f3919fafc1.
2026-03-30 21:08:29 -04:00
Redrover1760 f3919fafc1 Revert "Merge branch 'april1st' into main" (#3655) 2026-03-30 21:01:19 -04:00
Redrover1760 974b78863b Merge branch 'april1st' into main 2026-03-30 20:48:47 -04:00
monolith8319 1654bd5cf8 Automated Changelog (#3650) 2026-03-30 19:42:07 +00:00
mikus d353667f97 [April 1st] Only True Noticers (#3650) 2026-03-30 15:41:35 -04:00
monolith8319 47116d8ba7 Automated Changelog (#3648) 2026-03-30 18:16:21 +00:00
Ilya246 9bd6d5f3f2 [April 1st] Asteroid Update (#3648) 2026-03-30 18:15:50 +00:00
Corrupt 1dc696deb6 AAAAA (#3646) 2026-03-30 19:34:15 +02:00
monolith8319 f2df23dc81 Automated Changelog (#3645) 2026-03-30 17:33:41 +00:00
mikus 76d6ea96e0 [April 1st] New Company (#3645)
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-03-30 17:33:10 +00:00
monolith8319 93ba265e00 Automated Changelog (#3642) 2026-03-30 17:08:08 +00:00
tonotom1 16f73ec031 [April 1st] Buffs Space Pen (#3642) 2026-03-30 17:07:38 +00:00
monolith8319 9d3b0778b3 Automated Changelog (#3643) 2026-03-30 16:56:33 +00:00
lopsidedmanboobs 32b15951e2 PIrate CBT (#3643) 2026-03-30 18:56:00 +02:00
monolith8319 7a0f0d04c5 Automated Changelog (#3641) 2026-03-30 16:42:14 +00:00
mikus bcce8e67a3 [April 1st] 65 (#3641) 2026-03-30 16:41:45 +00:00
monolith8319 5afcdd9379 Automated Changelog (#3615) 2026-03-30 16:22:32 +00:00
mikus 01c7ea464a [April 1st] 65 (#3615) 2026-03-30 16:22:01 +00:00
monolith8319 55f6108312 Automated Changelog (#3514) 2026-03-30 16:16:08 +00:00
UnicornOnLSD 8be27c1658 Reworks the DIS Rig (#3514)
Co-authored-by: Corrupt <compeldominik6@gmail.com>
2026-03-30 18:15:38 +02:00
monolith8319 4df38cc9cc Automated Changelog (#3640) 2026-03-30 15:45:53 +00:00
mikus 8c0d2a50df [April 1st] Clanker Rebalance (#3640) 2026-03-30 15:45:19 +00:00
monolith8319 2d8105de13 Automated Changelog (#3611) 2026-03-30 15:43:28 +00:00
burrrkeye 01bb73bf56 [APRIL 1st] Charon Rebalance (#3611)
Co-authored-by: Corrupt <compeldominik6@gmail.com>
Co-authored-by: monolith8319 <195513600+monolith8319@users.noreply.github.com>
2026-03-30 15:42:55 +00:00
monolith8319 fb09b866f2 Automated Changelog (#3639) 2026-03-30 13:57:16 +00:00
tonotom1 0458a500ff Harmonica-Fy All Handheld Instruments (#3639) 2026-03-30 09:56:46 -04:00
monolith8319 4334b07093 Automated Changelog (#3638) 2026-03-30 09:23:54 +00:00
Isratosh ad2dd99a9b Thruster Capacitor Uniformity (#3638) 2026-03-30 13:23:22 +04:00
monolith8319 f11c83a96e Automated Changelog (#3626) 2026-03-30 04:38:58 +00:00
mikus 36d1740829 [April 1st] Adjusts (#3626) 2026-03-30 08:38:28 +04:00
Ghost581 1e201bb8d2 Viper Group Whitelist Fix (#3634) 2026-03-29 23:36:29 -04:00
monolith8319 c7a34dfc22 Automated Changelog (#3627) 2026-03-29 18:38:24 +00:00
Redrover1760 e366c4d7cc [April 1st] Adds a Very Fair Drone to the Game (#3627)
Co-authored-by: Corrupt <compeldominik6@gmail.com>
Co-authored-by: monolith8319 <195513600+monolith8319@users.noreply.github.com>
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
Co-authored-by: EckoAurum <202461799+EckoAurum@users.noreply.github.com>
Co-authored-by: ScarletLightweaver <82061820+ScarletLightweaverSS14@users.noreply.github.com>
2026-03-29 14:37:54 -04:00
monolith8319 bc0151e540 Automated Changelog (#3564) 2026-03-29 18:15:53 +00:00
UnicornOnLSD b2307660de Uniformises Shutter Sprites (#3564) 2026-03-29 20:15:23 +02:00
monolith8319 5d5f6b4654 Automated Changelog (#3622) 2026-03-29 18:10:30 +00:00
mikus 165f9950b5 {April 1st] Brings Back the Tactics of Evil Hidden TSF Ninja Warriors (#3622) 2026-03-29 14:10:01 -04:00
monolith8319 ef9858936d Automated Changelog (#3619) 2026-03-29 18:09:42 +00:00
Redrover1760 d0a95f5d8b [April 1st] Upgrades Bomb Collars (#3619)
Co-authored-by: Corrupt <compeldominik6@gmail.com>
Co-authored-by: monolith8319 <195513600+monolith8319@users.noreply.github.com>
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
Co-authored-by: EckoAurum <202461799+EckoAurum@users.noreply.github.com>
Co-authored-by: ScarletLightweaver <82061820+ScarletLightweaverSS14@users.noreply.github.com>
2026-03-29 14:09:11 -04:00
monolith8319 ee386b0633 Automated Changelog (#3623) 2026-03-29 18:08:59 +00:00
mikus de38cc18ef [April 1st] Buffs Shortband (#3623) 2026-03-29 14:08:28 -04:00
monolith8319 ca928bdc83 Automated Changelog (#3625) 2026-03-29 17:56:37 +00:00
mikus cee6ce9566 [April 1st] Updates Ownership of the Treasury to the Bank Of America (#3625) 2026-03-29 17:56:06 +00:00
monolith8319 984a2c9b71 Automated Changelog (#3624) 2026-03-29 17:16:08 +00:00
Ilya246 548e9eccb6 [April 1st] Mobile Pois 2 (#3624) 2026-03-29 17:15:36 +00:00
monolith8319 cd817abd2c Automated Changelog (#3620) 2026-03-29 16:54:09 +00:00
NazrinNya f276df8fe9 [1 April] Add Larp (#3620)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-29 16:53:40 +00:00
monolith8319 dd323be01d Automated Changelog (#3621) 2026-03-29 16:53:31 +00:00
mikus 0cf9b35f08 [April 1st] Adds New Forms of Energy Production (#3621) 2026-03-29 19:52:59 +03:00
monolith8319 114a09f290 Automated Changelog (#3618) 2026-03-29 16:20:15 +00:00
Redrover1760 dc4b50e71e updates VSC/build configurations (#3618) 2026-03-29 12:19:46 -04:00
monolith8319 c405c32c89 Automated Changelog (#3598) 2026-03-29 15:49:50 +00:00
monolith8319 34ff30a736 Automated Changelog (#3599) 2026-03-29 15:49:22 +00:00
ScarletLightweaver c8e162baa1 Made Bulwark Gauntlets Wideswing (#3598) 2026-03-29 11:49:12 -04:00
Ilya246 ffcbc32c43 Add Carpsuit to Xenopsychology (#3599) 2026-03-29 11:48:53 -04:00
monolith8319 351b225102 Automated Changelog (#3616) 2026-03-29 15:47:58 +00:00
gonz0 496432881e [April 1st] Detroit Borers (#3616) 2026-03-29 11:47:30 -04:00
monolith8319 9fd2a7b5af Automated Changelog (#3614) 2026-03-29 15:16:12 +00:00
EckoAurum 8972408ec1 Drone Turret Small Fix (#3614) 2026-03-29 11:15:43 -04:00
monolith8319 5798b57788 Automated Changelog (#3612) 2026-03-29 14:46:04 +00:00
mikus ec88162ac0 [April 1st] Adds New Type of Bluespace Drive (#3612) 2026-03-29 14:45:36 +00:00
monolith8319 3992aa846f Automated Changelog (#3610) 2026-03-29 14:18:14 +00:00
Ilya246 4b62f35f10 [April 1st] ilya changes round 2 (#3610) 2026-03-29 14:17:46 +00:00
Ilya246 cc1c91f39f Actually Fix Changelog (#3609) 2026-03-29 14:10:42 +00:00
mikus c5241854ce [April 1st] Piras314 Wrote This (#3606) 2026-03-29 13:57:07 +00:00
Ilya246 d02895a50f prune changelog 2026-03-29 13:55:25 +00:00
Ilya246 c41d005cf4 fix changelog workflow (#3607) 2026-03-29 13:52:43 +00:00
monolith8319 013f57a73d Automated Changelog (#3605) 2026-03-29 13:40:51 +00:00
mikus c3f687a493 [April 1st] Rebalances Gunnery Controls (#3605) 2026-03-29 13:40:19 +00:00
monolith8319 f837710be1 Automated Changelog (#3604) 2026-03-29 13:25:12 +00:00
Ilya246 7b00d623e0 [April 1st] Ilya April1st Changes Round 1 (#3603) 2026-03-29 13:24:54 +00:00
mikus dafbb5f3d8 [April 1st] Makes Borgs Balanced (#3604) 2026-03-29 13:24:41 +00:00
Corrupt d4487d9a5a RSC WL Update no.3 (#3601)
brr
2026-03-29 15:03:48 +02:00
monolith8319 7b9f602c55 Automated Changelog (#3571) 2026-03-29 06:31:08 +00:00
Antoine 97509dd3b6 Chat Message Highlighting (#3571)
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-03-29 06:30:38 +00:00
monolith8319 55c2a03ea4 Automated Changelog (#3594) 2026-03-29 05:46:36 +00:00
Redrover1760 543c19645f Gigabuffs Signaller Range (#3594) 2026-03-29 01:46:05 -04:00
monolith8319 da8b19dd05 Automated Changelog (#3595) 2026-03-29 02:40:33 +00:00
Redrover1760 4fc44d3a9c Really Ensures Delayed Gamerules Don't Trigger Early (#3595) 2026-03-28 22:40:06 -04:00
Redrover1760 55688cc9e0 Nuke Collar Buff (#3593) 2026-03-28 18:29:23 -04:00
Redrover1760 5357469644 Adds Tsf/Pdv Borgs to Debug Map (#3591) 2026-03-28 17:59:46 -04:00
monolith8319 6040135534 Automated Changelog (#3590) 2026-03-28 19:05:03 +00:00
Ilya246 52649a871e Fastify Apoc (#3590) 2026-03-28 15:04:33 -04:00
monolith8319 fef33511d8 Automated Changelog (#3444) 2026-03-28 18:43:41 +00:00
EckoAurum fec5bfce0a Faction Borg Specialization | Independent Cyborg Selectable Whitelist (#3444) 2026-03-28 14:43:12 -04:00
monolith8319 11f82f95d3 Automated Changelog (#3459) 2026-03-28 18:17:06 +00:00
NazrinNya 3dbad97455 Small Mech Rebalance. (#3459)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-28 21:16:36 +03:00
monolith8319 a193ab828e Automated Changelog (#3574) 2026-03-28 16:46:17 +00:00
Vinny Shick c75083140d Vasnyx 150mm Launcher Buff (#3574) 2026-03-28 19:45:49 +03:00
Redrover1760 106f9b6d28 Actually Removes Lancelot Wildcard Trade Crate Selling (#3577) 2026-03-28 19:45:07 +03:00
monolith8319 fe24fc22bc Automated Changelog (#3589) 2026-03-28 16:43:10 +00:00
Redrover1760 85c9636003 Buffs Thruster Speed Increase (#3589) 2026-03-28 19:42:39 +03:00
monolith8319 a1d2f4dcbc Automated Changelog (#3587) 2026-03-28 16:22:15 +00:00
NazrinNya 3381fe165c Various Patches. (#3587)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-28 12:21:43 -04:00
monolith8319 a913c35b21 Automated Changelog (#3588) 2026-03-28 16:18:20 +00:00
Redrover1760 0706b759d4 Fixes Diagonal Collisions, Can Kill Kudzu/Chimerazu Under It (#3588) 2026-03-28 12:17:49 -04:00
monolith8319 73a91c0b0a Automated Changelog (#3585) 2026-03-28 06:42:25 +00:00
Redrover1760 9e390680bd Speciest Turrets Fix (#3585) 2026-03-28 02:41:55 -04:00
monolith8319 d3d391f5a0 Automated Changelog (#3582) 2026-03-27 21:58:06 +00:00
Redrover1760 a6506c7f46 Anti-Chimera RIP Turrets to CC and MD (#3582) 2026-03-27 17:57:35 -04:00
monolith8319 958421b6c9 Automated Changelog (#3461) 2026-03-27 19:26:53 +00:00
NazrinNya c513938ed9 Make Heavy Ballistic Turrets Less Painful. (#3461)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-03-27 15:26:22 -04:00
monolith8319 a6431c9bab Automated Changelog (#3570) 2026-03-27 17:58:37 +00:00
Ilya246 f3390a81cc Gamemode Fixes and Changes (#3570)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-03-27 13:58:07 -04:00
monolith8319 6ba80381cc Automated Changelog (#3565) 2026-03-27 07:21:00 +00:00
Redrover1760 ef095aec88 Mono Claws Guidebook Update (#3565) 2026-03-27 03:20:31 -04:00
monolith8319 d02738089c Automated Changelog (#3575) 2026-03-27 07:19:51 +00:00
Redrover1760 b9f0de8576 Improves Hydrakin Head Displacement (#3575) 2026-03-27 03:19:24 -04:00
monolith8319 5a6d4b74b1 Automated Changelog (#3524) 2026-03-27 05:49:41 +00:00
significant harassment 1ac13bf597 Triple Rot Delay (#3524)
Co-authored-by: Onezero0 <onezero0@outlook.com>
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-03-27 01:49:14 -04:00
monolith8319 90ee912816 Automated Changelog (#3573) 2026-03-26 22:06:02 +00:00
Redrover1760 27665829e6 Viscerators No Longer Drop Lootboxes (#3572) 2026-03-26 18:05:41 -04:00
Redrover1760 e1459b0d43 Trade Crates Can No Longer Be Sold on Helios or Lancelot (#3573) 2026-03-26 18:05:32 -04:00
monolith8319 395d334dd7 Automated Changelog (#3560) 2026-03-26 05:55:40 +00:00
UnicornOnLSD 75e166e9b3 Maintains the DIS Femur (#3560)
Merged by Corrupt, Has 1 Fakefail.
2026-03-26 06:55:12 +01:00
monolith8319 d987cfe88d Automated Changelog (#3563) 2026-03-25 22:19:01 +00:00
Redrover1760 a942f3e7b1 Reflect Fixes (#3563) 2026-03-25 18:18:31 -04:00
monolith8319 4108933834 Automated Changelog (#3542) 2026-03-25 19:54:26 +00:00
Redrover1760 6af6f3b386 Fireteam Time Requirement Tweak (#3542) 2026-03-25 15:53:53 -04:00
monolith8319 9a590069d2 Automated Changelog (#3555) 2026-03-25 18:33:51 +00:00
Redrover1760 a60321f201 Ports CMO Webbing From Goob (#3555) 2026-03-25 19:33:22 +01:00
monolith8319 dfabb71045 Automated Changelog (#3554) 2026-03-25 06:13:58 +00:00
Redrover1760 ce034c3f19 Claws Tweak, Claws-Based Chemicals (#3554) 2026-03-25 02:13:30 -04:00
monolith8319 79c9dee112 Automated Changelog (#3558) 2026-03-25 05:50:21 +00:00
lopsidedmanboobs 6a048def33 Kill Ransom (#3558) 2026-03-25 01:49:54 -04:00
monolith8319 e41ef48441 Automated Changelog (#3556) 2026-03-25 04:12:58 +00:00
Redrover1760 27e7b2f70a Research Client Fix (#3556) 2026-03-25 00:12:27 -04:00
monolith8319 2bd317dadb Automated Changelog (#3551) 2026-03-25 00:09:05 +00:00
Maxim Bychkov 1145822797 Fix Voidboots Ability To Lie In Space (#3551) 2026-03-24 20:08:36 -04:00
monolith8319 27dca32ef3 Automated Changelog (#3260) 2026-03-24 23:42:18 +00:00
Avalon 4bdad8f6ee Ullman Coat 2 Electric Boogaloo (#3260)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-03-24 19:41:50 -04:00
monolith8319 38ebc2699c Automated Changelog (#3552) 2026-03-24 19:54:37 +00:00
Ilya246 82f75ff37f Nerf Big Ame (#3552) 2026-03-24 19:54:07 +00:00
Maxim Bychkov c84cde54cb Fix Map Painter (#3544) 2026-03-24 19:28:05 +00:00
monolith8319 f0c6624762 Automated Changelog (#3550) 2026-03-24 19:01:03 +00:00
Ilya246 277ad91140 Bugfixes (#3550) 2026-03-24 19:00:31 +00:00
monolith8319 b9419b25a1 Automated Changelog (#3155) 2026-03-24 15:08:01 +00:00
gelliii e1a5488f14 New Scrap Ship - Horsefly (#3155) 2026-03-24 16:07:30 +01:00
Redrover1760 d9b46ca6dc Update README.md 2026-03-24 09:15:30 -04:00
tonotom1 a672e9bd28 Revert "Cable Destruction Lag Reduction" (#3532) 2026-03-24 08:18:13 -04:00
monolith8319 2a5fe8fe6a Automated Changelog (#3548) 2026-03-24 12:07:19 +00:00
Redrover1760 e069f2ba9d Makes Printed Contraband Worth Things Again (#3548) 2026-03-24 08:06:49 -04:00
monolith8319 db3675b3e8 Automated Changelog (#3549) 2026-03-24 11:45:08 +00:00
chuga-git 79bf0fec68 Buffs MARAUDER Projectile Speed (#3549) 2026-03-24 07:44:39 -04:00
monolith8319 41b3fb9113 Automated Changelog (#3543) 2026-03-23 17:24:06 +00:00
Redrover1760 7cd749dfa6 Datafarm Fixes (#3543) 2026-03-23 13:23:36 -04:00
monolith8319 eb44deeeda Automated Changelog (#3534) 2026-03-23 06:05:19 +00:00
Redrover1760 7035e752a9 Majorly Buffs Part Wound Tending (#3534) 2026-03-23 02:04:49 -04:00
monolith8319 2fdd1cd573 Automated Changelog (#3381) 2026-03-23 04:08:14 +00:00
Kiithnaras 6c4101e0c0 Added Roofing and Emergency Lighting to Comet (#3381) 2026-03-23 00:00:28 -04:00
monolith8319 5f90433697 Automated Changelog (#3536) 2026-03-23 03:42:43 +00:00
Redrover1760 9f54374593 Research Datafarm Fix (#3536) 2026-03-22 23:42:15 -04:00
monolith8319 5272291275 Automated Changelog (#3533) 2026-03-22 23:23:01 +00:00
Redrover1760 4a0f4cec3a Buffed Chems and Surgery Part Healing/Damage (#3533) 2026-03-22 19:22:33 -04:00
monolith8319 5d68716090 Automated Changelog (#3530) 2026-03-22 22:54:38 +00:00
Redrover1760 a4b1a5317c People No Longer Thermal Regulate While Dead, Hydrakin Do Not While Crit Either (#3530) 2026-03-22 18:54:10 -04:00
monolith8319 ee48853724 Automated Changelog (#3520) 2026-03-22 22:41:50 +00:00
tonotom1 a8651f6457 Cable Destruction Lag Reduction (#3520) 2026-03-22 18:41:10 -04:00
monolith8319 47378ac726 Automated Changelog (#3499) 2026-03-22 19:50:36 +00:00
chuga-git 9ca8d4a82a Make Directions to Purchased Shuttles Point the Right Way (#3473) 2026-03-22 22:49:33 +03:00
Redrover1760 f8e940c5f5 Faction Borg Health Decrease (#3499) 2026-03-22 22:49:14 +03:00
monolith8319 89b73cdc76 Automated Changelog (#2620) 2026-03-22 19:46:59 +00:00
significant harassment 881ee08f91 Data Farms (#2620)
Co-authored-by: Onezero0 <onezero0@outlook.com>
Co-authored-by: monolith8319 <195513600+monolith8319@users.noreply.github.com>
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
Co-authored-by: Ilya246 <ilyukarno@gmail.com>
2026-03-22 22:46:27 +03:00
monolith8319 19d869bda1 Automated Changelog (#3201) 2026-03-22 19:44:53 +00:00
tonotom1 964b5768fd Jingle Jangle (#3201) 2026-03-22 22:44:22 +03:00
monolith8319 925933bbcb Automated Changelog (#3247) 2026-03-22 19:43:47 +00:00
Aithaed 277086116a Mech Laser Buff, The Snails Know, TSF Mech Pulse Rifle, TSF Volta Tweak (#3247)
Co-authored-by: NazrinNya <137837419+NazrinNya@users.noreply.github.com>
2026-03-22 22:43:16 +03:00
monolith8319 54412b25e4 Automated Changelog (#3371) 2026-03-22 19:40:40 +00:00
Mrrpip b75be35a1d Turns USSP Locker and Button Access to Mercenary. (#3371) 2026-03-22 22:40:11 +03:00
monolith8319 1d184cfd27 Automated Changelog (#3415) 2026-03-22 19:40:01 +00:00
Ilya246 ea8ce6d5b5 Optimise Shuttle Impacts (#3415)
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-03-22 22:39:33 +03:00
monolith8319 a9a05d0da3 Automated Changelog (#3519) 2026-03-22 16:01:37 +00:00
Redrover1760 e2c193f7c7 Edits MD/Halcyon/Drillsite Distances (#3517) 2026-03-22 12:01:34 -04:00
Kiithnaras 060a7565c5 Bedhealing Changes (#3519) 2026-03-22 12:01:07 -04:00
monolith8319 2d94504508 Automated Changelog (#3500) 2026-03-22 14:53:38 +00:00
tonotom1 9ec05018b0 Rad Hater's Initiative (#3500)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-22 10:53:08 -04:00
monolith8319 9574d6fe1f Automated Changelog (#3522) 2026-03-22 13:49:56 +00:00
Ilya246 937131c2a4 Ship Htn Fix (#3522) 2026-03-22 13:49:25 +00:00
monolith8319 f2cd5a6f3a Automated Changelog (#3426) 2026-03-22 11:13:31 +00:00
Corrupt 7749344876 Wyvern Nerf (#3426) 2026-03-22 12:13:01 +01:00
monolith8319 cda0c1f802 Automated Changelog (#3469) 2026-03-22 11:05:16 +00:00
Maxim Bychkov ad37c2895a [DNM] v271.2.0 RT Update - .NET 10 (#3469)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-03-22 11:04:46 +00:00
monolith8319 7ceedb6014 Automated Changelog (#3508) 2026-03-22 11:04:17 +00:00
Ilya246 a8c0d92351 [Testmerge End] Revert Movable POIs (#3508) 2026-03-22 11:03:41 +00:00
Redrover1760 1ead85c1c5 reee (#3516) 2026-03-21 22:51:15 -04:00
monolith8319 135335f7c9 Automated Changelog (#3513) 2026-03-21 22:51:22 +00:00
Redrover1760 c1a5f4d8c5 Natural Letoferal Guidebook Again (#3513) 2026-03-21 18:50:51 -04:00
monolith8319 9f204d62d1 Automated Changelog (#3512) 2026-03-21 22:41:11 +00:00
Redrover1760 7ef79b3fda Natural Letoferol Guidebook Description Update (#3512) 2026-03-21 18:40:38 -04:00
monolith8319 3f0670b985 Automated Changelog (#3511) 2026-03-21 22:12:04 +00:00
Redrover1760 c7060a0dc3 Asakim Autopulser Burst Nerf (#3511) 2026-03-21 18:11:32 -04:00
monolith8319 6a48576e4e Automated Changelog (#3510) 2026-03-21 21:38:09 +00:00
Redrover1760 e4412cfbb6 Critical Shipgun Fixture Fix (#3510) 2026-03-21 17:37:36 -04:00
monolith8319 a119933e1a Automated Changelog (#3458) 2026-03-21 21:23:39 +00:00
significant harassment 7602c54c29 Nuke Synthalloy (#3458)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-03-21 17:23:08 -04:00
monolith8319 f58931c915 Automated Changelog (#3468) 2026-03-21 20:04:23 +00:00
Mrrpip 686d6c8c37 Adds a Ship Based Flare Launcher. (And a Slight Spyglass Buff?) (#3468) 2026-03-21 16:03:52 -04:00
monolith8319 e8850079f0 Automated Changelog (#3482) 2026-03-21 20:00:45 +00:00
Ghost581 9cfdbd75e0 VIPER GROUP STUFF + SOME YAML TIDYING (#3482) 2026-03-21 16:00:13 -04:00
monolith8319 100800d09c Automated Changelog (#3449) 2026-03-21 19:55:43 +00:00
Mrrpip 863cd54169 Swaps the Triage's Generator. Ups It's Armor. Changes the Engine/Weapon Floors. (#3449) 2026-03-21 20:55:12 +01:00
monolith8319 b4bd44a589 Automated Changelog (#3241) 2026-03-21 19:53:23 +00:00
EldritchPineapple 8ab20f7058 New Tsf Frigate : The Altair (#3241)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-21 20:52:52 +01:00
monolith8319 29e20df5b0 Automated Changelog (#3506) 2026-03-21 18:35:12 +00:00
Redrover1760 e7b1f19944 Increases World Loading (#3506) 2026-03-21 14:34:40 -04:00
monolith8319 615cc446e0 Automated Changelog (#3505) 2026-03-21 16:49:05 +00:00
Redrover1760 4e794d6a42 Anchor Fixes (#3505) 2026-03-21 12:48:32 -04:00
monolith8319 63ac33ebce Automated Changelog (#3504) 2026-03-21 11:39:43 +00:00
UnicornOnLSD d5d89acbb4 Port Ghost Types (#37949) and Subsequent Necessities (#3504)
Co-authored-by: Thinbug <101073555+Thinbug0@users.noreply.github.com>
Co-authored-by: Fildrance <fildrance@gmail.com>
Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-03-21 11:39:13 +00:00
monolith8319 1cee621041 Automated Changelog (#3470) 2026-03-21 09:07:05 +00:00
NazrinNya 9dbf4629a0 Hailstorm Nerf, Exped Ore Buff. (#3470)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-21 05:06:33 -04:00
monolith8319 67e804698e Automated Changelog (#3503) 2026-03-21 09:05:14 +00:00
Redrover1760 b63dd431e8 Mobile POIs (#3503) 2026-03-21 05:04:44 -04:00
monolith8319 849b094574 Automated Changelog (#3502) 2026-03-21 08:16:39 +00:00
Redrover1760 2dcf189143 Yet Another Shortband Buff (#3502) 2026-03-21 04:16:08 -04:00
monolith8319 9e4a84a3b2 Automated Changelog (#3501) 2026-03-21 07:44:54 +00:00
Isratosh e69a387839 Add Vanyk and Vespera Missile Magazine Recipes to Military and Ammo Techfab (#3501) 2026-03-21 03:44:24 -04:00
Corrupt 3f50e9e035 RSC WL Updates (#3496) 2026-03-20 20:44:06 -04:00
monolith8319 6b1977d0f9 Automated Changelog (#3497) 2026-03-21 00:38:12 +00:00
NazrinNya b9c02cd366 Nerf Platelets (#3497)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-20 20:37:38 -04:00
monolith8319 f35f5e264d Automated Changelog (#3388) 2026-03-21 00:33:19 +00:00
Ilya246 60c6741960 ADM Nerf (#3388)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-03-20 20:32:45 -04:00
monolith8319 94812b996b Automated Changelog (#3476) 2026-03-21 00:29:39 +00:00
chuga-git b291b1cfd6 Fix Charon Range (#3476) 2026-03-20 20:29:05 -04:00
significant harassment 96902972d5 Template (#3494)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-03-20 19:26:55 -04:00
monolith8319 45bce4ed33 Automated Changelog (#3498) 2026-03-20 18:43:21 +00:00
Redrover1760 9347f67507 Draupnir Accuracy Buff (#3498) 2026-03-20 14:42:51 -04:00
monolith8319 e3635d4c91 Automated Changelog (#3495) 2026-03-20 11:39:15 +00:00
Redrover1760 a40b954978 Chimera and Other Mobs No Longer Randomly Die on Colossus (#3495) 2026-03-20 14:38:42 +03:00
monolith8319 adb4296d38 Automated Changelog (#3493) 2026-03-20 00:18:11 +00:00
significant harassment a48a235bf6 Catwalk Directional Tiles (#3493)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-03-19 20:17:39 -04:00
monolith8319 176055630f Automated Changelog (#3492) 2026-03-19 20:31:41 +00:00
Redrover1760 ff6bf1c131 Borer Radio/Polymorph Bugfixes (#3492) 2026-03-19 16:31:07 -04:00
monolith8319 1be801abf4 Automated Changelog (#3382) 2026-03-19 20:29:27 +00:00
Kiithnaras 0f25271add Specialized Armor for Industrial Mechs (#3382) 2026-03-19 23:28:56 +03:00
monolith8319 24bc95450a Automated Changelog (#3471) 2026-03-19 20:26:28 +00:00
Nikovnik 77ebbdad20 Claws and Bows (#3471) 2026-03-19 23:25:57 +03:00
monolith8319 44a6d7eca6 Automated Changelog (#3487) 2026-03-19 19:42:52 +00:00
EldritchPineapple eb5c8ac4e9 Spica and Aldebaran Hotfix (#3487) 2026-03-19 20:42:18 +01:00
monolith8319 188070b757 Automated Changelog (#3475) 2026-03-19 19:35:28 +00:00
EldritchPineapple 3f97487c0b Nuke... 2026-03-19 20:34:56 +01:00
monolith8319 c7de9b660a Automated Changelog (#3490) 2026-03-19 19:14:06 +00:00
EldritchPineapple 4b44b891d3 A (#3490) 2026-03-19 20:13:32 +01:00
monolith8319 b895b2ba42 Automated Changelog (#3485) 2026-03-19 12:24:15 +00:00
Mrrpip 8a6b39a560 Fixes the Hades' Offsets. (#3485) 2026-03-19 08:23:42 -04:00
monolith8319 ad7633023b Automated Changelog (#3287) 2026-03-19 06:09:52 +00:00
Redrover1760 8777215b34 Bomb Collars (#3287)
Co-authored-by: Redrover1760 <gnarlysheep.personal@gmail.com>
2026-03-19 02:09:21 -04:00
monolith8319 4007b079f8 Automated Changelog (#3474) 2026-03-19 05:34:23 +00:00
chuga-git 79067884e1 Remove Dumpable From the RPED (#3474) 2026-03-19 05:33:51 +00:00
monolith8319 ae72c4b217 Automated Changelog (#3486) 2026-03-19 00:45:06 +00:00
lopsidedmanboobs 12485a5ce2 Mildly Important Safezone Clarifications Added (#3486) 2026-03-19 11:44:34 +11:00
monolith8319 53b74938fd Automated Changelog (#3440) 2026-03-17 23:48:45 +00:00
NazrinNya 42c12489e3 Return Limited Missle Ammo. (#3440)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-17 19:48:16 -04:00
monolith8319 3c31450ab3 Automated Changelog (#3074) 2026-03-17 23:46:38 +00:00
Ghost581 273ca2cd2c Viper Group Hardsuits and Lore Update (#3074) 2026-03-17 19:46:06 -04:00
monolith8319 64a800ab06 Automated Changelog (#3455) 2026-03-16 13:46:00 +00:00
Mrrpip 1fec90e647 Adds a Ammo Variant for the Heavy Weapons Case. (#3455) 2026-03-16 09:45:25 -04:00
monolith8319 389b1a39fe Automated Changelog (#3436) 2026-03-15 19:36:36 +00:00
DevArchwave 8ef3e658b4 Allows Armor Plates to Have Their Durability Examined Outside of Armor (#3436) 2026-03-15 15:36:04 -04:00
monolith8319 b6623e107b Automated Changelog (#3450) 2026-03-15 14:20:19 +00:00
significant harassment 136225a067 Sprite Dump (#3450)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-03-15 10:19:46 -04:00
monolith8319 7e88f38fa9 Automated Changelog (#3174) 2026-03-15 14:14:20 +00:00
EldritchPineapple 5abbb4beb3 Added the Aldebaran, a Tsf Corvette to Replace the Calypso (#3174) 2026-03-15 15:13:49 +01:00
monolith8319 cdcb4454b0 Automated Changelog (#3106) 2026-03-15 11:21:46 +00:00
EldritchPineapple 4185d83e64 Fracture Placement : PIN-Spica (#3106)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-15 12:21:12 +01:00
Mrrpip 8f921ed705 Updates a Bunch of Ship Author Details (#3447)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-14 22:22:50 +01:00
monolith8319 9a454d9b9a Automated Changelog (#3221) 2026-03-14 19:14:06 +00:00
UnicornOnLSD 6d5e1cb119 Pluto Mk.II Maintenance (#3221) 2026-03-14 20:13:37 +01:00
Corrupt 44b443bdf9 RSC Whitelist Updates! (#3446) 2026-03-14 19:42:15 +01:00
monolith8319 696736da06 Automated Changelog (#3439) 2026-03-14 16:00:17 +00:00
Corrupt 926c4fab49 Delist Scallywag From Normal Shipyards (#3439) 2026-03-14 16:59:46 +01:00
monolith8319 a80e0db8a0 Automated Changelog (#3418) 2026-03-14 15:48:45 +00:00
EldritchPineapple c4954e4bd2 Monolithed the Tanker (#3418) 2026-03-14 16:48:12 +01:00
monolith8319 981017a9b4 Automated Changelog (#3307) 2026-03-13 22:17:03 +00:00
UnicornOnLSD 84b90912f9 Makes (Nearly) ALL Magboots Sprites Uniform (#3307)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-03-13 18:16:28 -04:00
monolith8319 78d90096a2 Automated Changelog (#3380) 2026-03-13 21:28:00 +00:00
significant harassment a9a5f194f5 Full Tech Corners, Battle Table, Live Fire Warn (#3380)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-03-13 17:27:17 -04:00
monolith8319 bc85b73fee Automated Changelog (#3427) 2026-03-12 16:48:25 +00:00
Redrover1760 0f316130e4 Fang Buff, More Projectile Speed/Range (#3427)
Co-authored-by: Redrover1760 <gnarlysheep.personal@gmail.com>
2026-03-12 19:42:06 +03:00
monolith8319 275ecb8e01 Automated Changelog (#3431) 2026-03-12 16:39:39 +00:00
Ilya246 a3ee39de70 Increase Mech Speed (#3433) 2026-03-12 19:37:24 +03:00
chuga-git 9d38f200d7 Reptilian Claw Popup Fix (#3431) 2026-03-12 19:35:42 +03:00
Toastical 8350c4d544 Let Shuttle Console Open Attempts Be Predicted on the Client (#3428) 2026-03-12 05:54:03 +00:00
Corrupt e4d67e8d33 RSC WL Updates (#3430) 2026-03-11 08:52:08 +01:00
monolith8319 278d73dd78 Automated Changelog (#3425) 2026-03-09 23:04:59 +00:00
Redrover1760 3ab8f43ea3 Core Rule 2 Speciesm Clarification (#3425) 2026-03-09 19:04:25 -04:00
monolith8319 0bd1b0800e Automated Changelog (#3346) 2026-03-09 23:00:00 +00:00
burrrkeye c0a10d659a Defector Re-Imagined (#3346)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-10 09:59:28 +11:00
monolith8319 bf89ec2037 Automated Changelog (#3421) 2026-03-09 20:51:31 +00:00
Redrover1760 77811a2d7f Autorecharge Pause Time Down on Dymeres (#3421) 2026-03-09 16:50:55 -04:00
monolith8319 fa7b973bb3 Automated Changelog (#3424) 2026-03-09 20:46:47 +00:00
Redrover1760 b686b65058 l6 saw borg de-ap (#3424) 2026-03-09 16:46:14 -04:00
monolith8319 361c0111ca Automated Changelog (#3423) 2026-03-09 20:40:16 +00:00
Redrover1760 c62943f2e0 Jetpack Blip Fix (#3423) 2026-03-09 16:39:45 -04:00
monolith8319 701883e108 Automated Changelog (#3422) 2026-03-09 19:54:52 +00:00
Redrover1760 b68630257f jaws of life can be used to clip lizard nails now (#3422) 2026-03-09 15:54:17 -04:00
monolith8319 7072751108 Automated Changelog (#3420) 2026-03-09 19:41:49 +00:00
Redrover1760 469c42f2a4 Increases Visible Blip Distance to 2048 From 1024 (#3420) 2026-03-09 15:41:19 -04:00
Corrupt dd8070316f RSC WL Additions/Fixes (#3417) 2026-03-09 13:23:59 -04:00
monolith8319 6e0dbdd1e7 Automated Changelog (#3416) 2026-03-09 17:03:03 +00:00
Ilya246 ef47aad488 Hotfix Companies (#3416) 2026-03-09 13:02:31 -04:00
monolith8319 7f00a4c13a Automated Changelog (#3414) 2026-03-09 08:57:34 +00:00
Ilya246 a651e15ab5 Try Fix some Lag (#3414)
Co-authored-by: NazrinNya <137837419+NazrinNya@users.noreply.github.com>
2026-03-09 08:57:00 +00:00
monolith8319 f089bf5a29 Automated Changelog (#3395) 2026-03-09 08:48:14 +00:00
Corrupt 785e6abe69 RedSail Corsairs (RSC) (#3395) 2026-03-09 09:47:43 +01:00
monolith8319 6c0061a2d3 Automated Changelog (#3391) 2026-03-09 08:42:00 +00:00
UnicornOnLSD c3e008866a Reshades the Wardlord Hardsuit (#3391) 2026-03-09 11:41:29 +03:00
monolith8319 8245850cb0 Automated Changelog (#3412) 2026-03-09 08:41:21 +00:00
Kiithnaras 5da958f7a9 Alternate Hydrakin Heating (#3412)
Co-authored-by: NazrinNya <137837419+NazrinNya@users.noreply.github.com>
2026-03-09 11:40:51 +03:00
NazrinNya 27513e7a28 Fix the Tests (#3413)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-09 11:07:55 +03:00
significant harassment c39c6ca2f5 World Controller System Delay (#3402)
Co-authored-by: Onezero0 <onezero0@outlook.com>
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-03-09 07:32:30 +00:00
monolith8319 319b12b138 Automated Changelog (#3372) 2026-03-09 07:30:38 +00:00
Kiithnaras 21ba67d599 Buffs ExoPKA to Gather Multiple Tiles Like Handheld PKA (#3372) 2026-03-09 07:30:09 +00:00
monolith8319 454f33ad43 Automated Changelog (#3400) 2026-03-09 07:06:50 +00:00
Ilya246 ad9093094c Try Improve Autopilot (#3400) 2026-03-09 07:06:16 +00:00
Aiden 09c7bf422b Fix2 (#3411)
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-03-09 05:33:58 +00:00
Ilya246 d24a85c6d8 Actually Fill the Configs (#3407) 2026-03-09 05:14:11 +00:00
monolith8319 8709244f6f Automated Changelog (#3409) 2026-03-09 02:09:09 +00:00
lopsidedmanboobs 856093613e Update Portstrike Radius for Ship Attacks (#3409) 2026-03-09 13:08:40 +11:00
monolith8319 6b1aabb8f5 Automated Changelog (#3403) 2026-03-08 23:57:14 +00:00
UnicornOnLSD 2939e6b9bc RSC Scallywag Armament Tweak (#3403) 2026-03-09 10:56:45 +11:00
monolith8319 9476e003c2 Automated Changelog (#3408) 2026-03-08 23:09:18 +00:00
lopsidedmanboobs 6e220606fb Remove Memes (#3408) 2026-03-08 19:08:47 -04:00
Ilya246 a56db3da27 Maint Custompoll (#3397) 2026-03-08 18:43:04 -04:00
Ilya246 f7960e90ab add configs to ConfigPresets (#3406) 2026-03-08 22:15:49 +00:00
Ilya246 313e4f1db6 Thermal Sig Sys Microoptimisation (#3401) 2026-03-08 17:39:48 -04:00
Ilya246 d9721a8328 Revert "v264.0.0-fixphysics to v264.0.4-fixphysics" (#3398)
Co-authored-by: NazrinNya <137837419+NazrinNya@users.noreply.github.com>
2026-03-08 18:02:39 +00:00
monolith8319 8100503e6a Automated Changelog (#3323) 2026-03-08 16:34:56 +00:00
Redrover1760 a0f3d8193b v264.0.0-fixphysics to v264.0.4-fixphysics (#3323) 2026-03-08 16:34:27 +00:00
NazrinNya cacbaec4cf Dehardcode Assigning Companies to the Job (#3355)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-08 16:31:36 +00:00
monolith8319 d5e213abd9 Automated Changelog (#3190) 2026-03-08 16:26:06 +00:00
tonotom1 eacac0b2e3 Port Wizden #42613: Optical Thermal Scanner Implementation (#3193)
Co-authored-by: InsoPL <lukasz.lindert@protonmail.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Co-authored-by: psykana <36602558+psykana@users.noreply.github.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-03-08 16:25:43 +00:00
tonotom1 b3e51ba5a9 Plate Bang (#3190)
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-03-08 16:25:35 +00:00
monolith8319 d54cc99491 Automated Changelog (#3387) 2026-03-08 16:21:01 +00:00
tonotom1 51797e0933 Repairable + Climbable Shipgun & Examinable Damage (#3387) 2026-03-08 16:20:28 +00:00
monolith8319 9d97920e55 Automated Changelog (#3394) 2026-03-08 15:56:34 +00:00
Ilya246 68da6b039c Autopilot (#3394) 2026-03-08 15:56:03 +00:00
monolith8319 9a8c9b76e2 Automated Changelog (#3396) 2026-03-08 15:33:27 +00:00
Redrover1760 5d0fe0a94b Revert "Rammable POIs (#3344)" (#3396) 2026-03-08 11:32:56 -04:00
monolith8319 88c1e7fd1d Automated Changelog (#3164) 2026-03-08 11:40:44 +00:00
Corrupt 3f594f2387 RSC Scallywag (#3164)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-08 22:39:22 +11:00
monolith8319 b19ec1eb9f Automated Changelog (#3354) 2026-03-07 11:14:31 +00:00
Mrrpip f76e2bb807 Adds a Fan to the Talon-SYN Ship. (#3354)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-07 12:14:01 +01:00
monolith8319 ee02f8afdd Automated Changelog (#3188) 2026-03-07 11:03:49 +00:00
tonotom1 0a4753edae Sellsword Dronehunt Edition (#3188)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-07 12:02:56 +01:00
monolith8319 d0ceadf130 Automated Changelog (#3385) 2026-03-07 00:43:32 +00:00
significant harassment 05c046d055 Fighter Spam Fix (#3385)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-03-06 19:43:00 -05:00
NazrinNya b1aa743a40 Kill Peranol Pop-Up Message. (#3384) 2026-03-06 19:13:46 -05:00
monolith8319 5125fc084e Automated Changelog (#3364) 2026-03-06 16:46:19 +00:00
Ilya246 2fa487e4b3 Chimera Drip (#3364) 2026-03-06 19:45:46 +03:00
monolith8319 d63c42b0ee Automated Changelog (#3378) 2026-03-06 15:49:21 +00:00
NazrinNya da16de09a5 Missle Turbonerf (#3378)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-06 10:48:50 -05:00
monolith8319 58fa85d648 Automated Changelog (#3377) 2026-03-06 13:43:24 +00:00
NazrinNya 3ce7df2433 Adjust Hydrakin (#3377)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-06 08:42:50 -05:00
NazrinNya a11952bdd2 Reptilian Claws Hotfix (#3376)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-06 16:37:23 +03:00
monolith8319 22c29f0a46 Automated Changelog (#3373) 2026-03-06 12:19:13 +00:00
significant harassment 39358d68cd Lore Primer (#3373)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-03-06 07:18:42 -05:00
monolith8319 52cd9ad5c5 Automated Changelog (#3374) 2026-03-06 03:53:37 +00:00
Arcticular 3b272d0572 Vanyk Damage Nerf (#3374) 2026-03-05 22:53:06 -05:00
monolith8319 fa47f767a3 Automated Changelog (#3173) 2026-03-06 00:46:07 +00:00
Avalon 94d15b0fcf Incendiary Ammo Rebalance (#3173) 2026-03-05 19:45:37 -05:00
monolith8319 9b20d3aee4 Automated Changelog (#3319) 2026-03-05 23:59:28 +00:00
UnicornOnLSD 13a4c7eb63 Resprites the DI Emblem & Adds Appropriate Banner (#3319)
Co-authored-by: NL-42 <lombardini.nicolas17@gmail.com>
Co-authored-by: NazrinNya <137837419+NazrinNya@users.noreply.github.com>
2026-03-05 18:58:56 -05:00
monolith8319 a364ff074b Automated Changelog (#3356) 2026-03-05 23:57:33 +00:00
Redrover1760 6c33c74260 Reduces Harbringer Range From 8000m to 3520m (#3348) 2026-03-05 18:57:23 -05:00
NazrinNya e03bc781c8 Adjust Reptilians (#3356)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-05 18:56:56 -05:00
monolith8319 a76089a1d8 Automated Changelog (#3370) 2026-03-05 23:56:33 +00:00
NazrinNya 14c3ca4ed6 [Bugfix] Actually Make Hydrakins Heat Up (#3369)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-05 18:56:21 -05:00
NazrinNya 6378076a5d Small IPC Nerf (#3370)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-05 18:56:02 -05:00
monolith8319 e01cb2be78 Automated Changelog (#3343) 2026-03-05 21:36:53 +00:00
Mrrpip a2cbcddd12 Adds the QJ340 Support Fighter to the Tumour's Shipyard. (#3343)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-03-05 22:36:19 +01:00
monolith8319 70b903467e Automated Changelog (#3357) 2026-03-05 20:00:16 +00:00
Mrrpip fdb9954a22 Adjusts Description on Standard Diagonal Plastitanium Walls. (#3357) 2026-03-05 22:59:45 +03:00
monolith8319 01e622b43d Automated Changelog (#3351) 2026-03-05 19:58:39 +00:00
Mrrpip 1154e94757 Allows All Moveable Shipguns to Be Rotated. (#3351) 2026-03-05 22:58:09 +03:00
monolith8319 d95e82922d Automated Changelog (#3349) 2026-03-05 19:57:42 +00:00
Redrover1760 24e77e2fc4 Fixes Wyverns Spawning Nonstop (#3349) 2026-03-05 22:57:11 +03:00
monolith8319 f15d0e74c9 Automated Changelog (#3365) 2026-03-05 17:34:23 +00:00
tonotom1 1107c9c288 Holy Sensors Dude (#3365) 2026-03-05 20:05:14 +03:00
Ilya246 57c5287e02 Upgrade Map Painter (#3367) 2026-03-05 12:57:58 +00:00
monolith8319 838a56a68a Automated Changelog (#3309) 2026-03-05 04:57:24 +00:00
Redrover1760 8134e4733e Halcyon Update (#3309) 2026-03-05 15:56:49 +11:00
Redrover1760 b7fc2cc7e7 oversight (#3362) 2026-03-04 21:10:43 -05:00
monolith8319 b5618452d5 Automated Changelog (#3361) 2026-03-04 23:44:58 +00:00
Redrover1760 3c697015a6 Adm Tweaks (#3361) 2026-03-04 18:44:29 -05:00
monolith8319 5be002dbc2 Automated Changelog (#3358) 2026-03-04 17:40:31 +00:00
NazrinNya e8989e1fc3 Adjust Mech Destruction Threshold (#3358)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-04 12:39:58 -05:00
monolith8319 a5fcf434ec Automated Changelog (#3353) 2026-03-04 13:21:55 +00:00
NazrinNya 8ee21957a3 Smart Magnum Adjustments (#3353)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-03-04 08:21:23 -05:00
monolith8319 88c6b190b5 Automated Changelog (#3350) 2026-03-04 09:04:18 +00:00
tonotom1 e490f5216c Free Ironman Frameworks (#3350) 2026-03-04 12:03:47 +03:00
monolith8319 4da4b3b01a Automated Changelog (#3297) 2026-03-03 21:33:02 +00:00
Redrover1760 f457d5c33c Wizden/Frontier Armor Vest Buffs (#3297) 2026-03-03 16:32:29 -05:00
monolith8319 f2726120f6 Automated Changelog (#3318) 2026-03-03 20:33:29 +00:00
freturt 04a3fc2949 ATU Leader's Buff (#3318) 2026-03-03 23:32:57 +03:00
monolith8319 3e38958cab Automated Changelog (#3303) 2026-03-03 19:09:18 +00:00
Redrover1760 0642406657 Lets Mindshields Be Removed From People (#3286) 2026-03-03 22:09:07 +03:00
Jae c62bade639 Convert WSPR to General 7.62x39mm (#3303) 2026-03-03 22:08:32 +03:00
Redrover1760 1365399931 More Ads Nerfs (#3335) 2026-03-03 22:07:52 +03:00
monolith8319 b02070a3c0 Automated Changelog (#3315) 2026-03-03 18:17:59 +00:00
UnicornOnLSD 94a7413968 DIS Femur Patch, 2nd Edition (#3315) 2026-03-03 21:17:29 +03:00
monolith8319 c984c461c7 Automated Changelog (#3321) 2026-03-03 18:14:21 +00:00
DevArchwave e102989c28 Utility Loadout Access for More Roles (#3321) 2026-03-03 21:11:14 +03:00
monolith8319 f8b1355bbb Automated Changelog (#3328) 2026-03-03 18:04:04 +00:00
tonotom1 52e11f7a59 Port Wizden #37129: Add EvenHealthChange Effect (#3328)
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-03-03 21:03:33 +03:00
monolith8319 5e152a2875 Automated Changelog (#3340) 2026-03-03 17:43:03 +00:00
Mrrpip 3f40d8f59f Adjusts the Zephyr, Swaps Thrusters for TSF Variants, Lowers the Gunnery Server to Low. (#3340) 2026-03-03 20:42:31 +03:00
monolith8319 56727c248c Automated Changelog (#3344) 2026-03-03 17:28:45 +00:00
Ilya246 56dbf295c2 Rammable POIs (#3344) 2026-03-03 20:28:11 +03:00
monolith8319 1f5f3d38d5 Automated Changelog (#3325) 2026-03-03 17:27:58 +00:00
burrrkeye 86f70184d7 PDV Helmets Get Sechuds, Neptune Gets Station Records and IFF Console (#3325) 2026-03-03 20:27:25 +03:00
monolith8319 3a1f36ea15 Automated Changelog (#3055) 2026-03-03 17:07:56 +00:00
NazrinNya 036870c2d0 Lizard Rework. (#3055)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-03-03 20:07:25 +03:00
monolith8319 108864d7ed Automated Changelog (#3342) 2026-03-03 14:41:12 +00:00
NazrinNya 4a21e19afe Revert "LPB Updates Plus New Syndie Borg Ghostrole (#3215)" (#3342)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-03-03 17:40:40 +03:00
Ilya246 1d53b774b4 Fix Dynamic Role Timer (#3327) 2026-03-03 00:28:30 -05:00
monolith8319 6f3508f497 Automated Changelog (#3316) 2026-03-03 04:08:58 +00:00
Corrupt faf89dc740 Wyvern Tweaks (#3316) 2026-03-02 23:08:28 -05:00
monolith8319 2e2c0e2a97 Automated Changelog (#3215) 2026-03-02 23:53:39 +00:00
Loldaddy5676 424c5001a1 LPB Updates Plus New Syndie Borg Ghostrole (#3215) 2026-03-02 18:53:09 -05:00
monolith8319 e845a6f77c Automated Changelog (#3333) 2026-03-02 21:42:31 +00:00
Redrover1760 e6e9346979 rebuffs the Scythe (#3333) 2026-03-02 16:41:44 -05:00
monolith8319 30a6e47d6f Automated Changelog (#3330) 2026-03-02 21:19:09 +00:00
Redrover1760 298ed43ee5 Prefracture Ship Repairable (#3330) 2026-03-02 16:18:37 -05:00
monolith8319 c992c56fa3 Automated Changelog (#3329) 2026-03-02 18:26:24 +00:00
Ilya246 fe071126d5 Optimise Blip Network Usage (#3329) 2026-03-02 13:25:53 -05:00
monolith8319 86d46357bd Automated Changelog (#3326) 2026-03-02 14:33:32 +00:00
Redrover1760 32ab30233f Gamerule Fixes Maybe (#3326)
Co-authored-by: Redrover1760 <gnarlysheep.personal@gmail.com>
2026-03-02 09:33:00 -05:00
Redrover1760 afb33ea720 Doubles Clientside Network Blip Throttling (#3322) 2026-03-02 08:24:42 -05:00
monolith8319 d83e97be76 Automated Changelog (#3306) 2026-03-01 23:48:10 +00:00
Hverabrao 77323026ae Updates Buran (#3306) 2026-03-02 10:47:39 +11:00
monolith8319 f9d115f708 Automated Changelog (#3298) 2026-03-01 23:35:07 +00:00
burrrkeye 0fffae63af PDV BT-Y Fenrir Class Light Carrier Destroyer (#3298) 2026-03-02 10:34:38 +11:00
monolith8319 4848763feb Automated Changelog (#3301) 2026-03-01 23:33:12 +00:00
Redrover1760 b52fea4920 Just ADS Re-Alot of Things (#3301)
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
Co-authored-by: Ilya246 <ilyukarno@gmail.com>
2026-03-01 18:32:41 -05:00
monolith8319 ca50c40b3b Automated Changelog (#3140) 2026-03-01 22:43:38 +00:00
Arcticular 5d8b66f491 ADS Wyrm (#3140) 2026-03-01 17:43:10 -05:00
monolith8319 0e717b1cd6 Automated Changelog (#3314) 2026-03-01 17:22:27 +00:00
Ilya246 3da48f94d1 Fix Drone Collision Evasion (#3314) 2026-03-01 17:21:58 +00:00
monolith8319 92eaee9dc0 Automated Changelog (#3313) 2026-03-01 17:07:50 +00:00
DevArchwave 9945dcc5fa Self-Defense Loadout for Colonial Staff (#3313) 2026-03-01 12:07:21 -05:00
monolith8319 e91ee50973 Automated Changelog (#3288) 2026-03-01 11:40:43 +00:00
Kiithnaras abf7764557 Improves AME, Nerfs ZPinch (#3288) 2026-03-01 11:40:11 +00:00
monolith8319 b5d0ce7866 Automated Changelog (#3310) 2026-02-28 23:17:13 +00:00
UnicornOnLSD f9f4c37b03 Fixes DIS Femur Issues (#3310) 2026-02-28 18:16:42 -05:00
monolith8319 0d340bbae9 Automated Changelog (#3311) 2026-02-28 22:18:37 +00:00
Redrover1760 81a1371eb4 Missiles and Tarnyx Have Infinite Base Ammo Now. (#3311) 2026-02-28 17:18:06 -05:00
monolith8319 2825f0f400 Automated Changelog (#3291) 2026-02-28 19:56:24 +00:00
Redrover1760 c218faac8d Hydrakin Marking Update (#3291) 2026-02-28 14:55:54 -05:00
monolith8319 1fb4bca1b3 Automated Changelog (#3167) 2026-02-28 05:08:42 +00:00
Corrupt 846fb711a6 ADS Wyvern Replacement (#3167)
Good
2026-02-28 16:08:11 +11:00
monolith8319 c943010bc1 Automated Changelog (#3109) 2026-02-28 03:44:19 +00:00
Arcticular c497df354b 520mm Syndicate Howitzer (#3109)
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-02-27 22:43:46 -05:00
Redrover1760 f692d16b1a description fix (#3300) 2026-02-27 19:16:57 -05:00
Redrover1760 885c2feec3 Mild Chemmastercode Update (#3296) 2026-02-27 14:38:38 -05:00
monolith8319 ab4e4daa48 Automated Changelog (#3257) 2026-02-27 17:11:09 +00:00
Aithaed 8743651e1b ATTENTION STALKER, AN EMISSION IS APPROACHING (#3257) 2026-02-27 12:10:39 -05:00
monolith8319 ccff51b77b Automated Changelog (#3268) 2026-02-27 03:31:26 +00:00
UnicornOnLSD 52915390bb Makes All Computer Sprites Uniform (#3268) 2026-02-26 22:30:54 -05:00
monolith8319 09150f5a7d Automated Changelog (#3295) 2026-02-27 01:07:38 +00:00
Redrover1760 9ea3e34f49 Optimizes Blip Memory GC/Usage (#3295) 2026-02-26 20:07:07 -05:00
monolith8319 1ca09e96d3 Automated Changelog (#3294) 2026-02-27 00:24:01 +00:00
lopsidedmanboobs 44aac3d94b Core Rule 2: Slurs - Head Admin Approved (#3294) 2026-02-26 19:23:30 -05:00
monolith8319 75f18469aa Automated Changelog (#3261) 2026-02-26 05:27:52 +00:00
Redrover1760 956658b305 Retires the Ruby (#3261) 2026-02-26 00:27:23 -05:00
monolith8319 297262017b Automated Changelog (#3282) 2026-02-24 22:28:18 +00:00
Redrover1760 7536925649 Clarifies that Military Outposts Are Protected Under ADS Lawset Under Its Laws (#3282) 2026-02-24 17:27:47 -05:00
monolith8319 d463794da7 Automated Changelog (#3212) 2026-02-24 20:31:00 +00:00
burrrkeye ee903e3735 Hazel Loadout Change (#3212) 2026-02-24 23:30:29 +03:00
monolith8319 3f9e35f900 Automated Changelog (#3168) 2026-02-24 20:15:50 +00:00
rishani-ss14 90b2bd34e2 Fixes Service Workers' Valet Voucher (#3168) 2026-02-24 23:15:17 +03:00
monolith8319 0fe194c209 Automated Changelog (#3178) 2026-02-24 20:14:24 +00:00
Kei Kiyomi 3cbfc5055c Add High Command Phone (#3178) 2026-02-24 23:13:54 +03:00
monolith8319 c4d5fa6813 Automated Changelog (#3272) 2026-02-24 20:00:34 +00:00
UnicornOnLSD 41e8348d96 Adds the DIS Femur, Removes SHM Acera (#3272) 2026-02-24 23:00:03 +03:00
monolith8319 f715d4c00b Automated Changelog (#3253) 2026-02-24 19:42:55 +00:00
Kiithnaras 685d12c35f Tweaks and Balancing to Big Mecha and Power Cages/Cells (#3253) 2026-02-24 22:42:25 +03:00
monolith8319 1182332c0d Automated Changelog (#3279) 2026-02-24 19:00:55 +00:00
Loldaddy5676 9ce9d7258d MLA Additonal Ammo (#3279) 2026-02-24 22:00:21 +03:00
monolith8319 23489aaadd Automated Changelog (#3275) 2026-02-24 18:58:17 +00:00
Mrrpip b36192bf3b Adds (Re-Adds?) Signal Control to Various Ship Weapons. (#3275) 2026-02-24 21:57:45 +03:00
monolith8319 417cd92c4b Automated Changelog (#3283) 2026-02-24 13:50:16 +00:00
Redrover1760 7561cdfb92 Thieving Trait Nerf (#3283) 2026-02-24 08:49:46 -05:00
monolith8319 dfe9c65221 Automated Changelog (#3280) 2026-02-24 13:24:24 +00:00
Redrover1760 7f852a8cb8 90mm Nerf (#3280) 2026-02-24 14:23:51 +01:00
monolith8319 6895525e91 Automated Changelog (#3281) 2026-02-24 12:49:51 +00:00
Kiithnaras dc230618e3 Fixes Watchdog Respirator States on Hydrakin (#3281) 2026-02-24 15:49:18 +03:00
monolith8319 d5530fb5ee Automated Changelog (#3284) 2026-02-24 04:32:22 +00:00
Aithaed 1a6bd945c5 Carrion Suit Storage Fix (#3284) 2026-02-23 23:31:52 -05:00
monolith8319 69c0484989 Automated Changelog (#3273) 2026-02-23 16:07:29 +00:00
Ilya246 c7f86e6b6f Ftl Lock Lodging Bandaid Fix (#3273) 2026-02-23 11:06:55 -05:00
monolith8319 8cb67fbbe2 Automated Changelog (#3278) 2026-02-23 13:25:27 +00:00
Kiithnaras 4c7d77502c Adds Hunger and Thirst Processing Components to Hydrakin. (#3278) 2026-02-23 08:24:55 -05:00
monolith8319 7b6ebef36f Automated Changelog (#3263) 2026-02-23 05:19:08 +00:00
Redrover1760 974cb92dc5 Increases MD Distance Variance (#3263) 2026-02-23 00:18:37 -05:00
monolith8319 4728ca23d7 Automated Changelog (#3271) 2026-02-22 21:19:12 +00:00
Redrover1760 7e4824a145 Differentiates Unknown Labeled Ships by Ship Mass (#3271) 2026-02-22 16:18:40 -05:00
monolith8319 977815809c Automated Changelog (#3274) 2026-02-22 19:16:08 +00:00
Redrover1760 86aa49c368 Decreases Anomaly Rock Spawn Odds (#3274) 2026-02-22 20:15:39 +01:00
monolith8319 ba6846858f Automated Changelog (#3254) 2026-02-22 05:37:13 +00:00
UnicornOnLSD 9b82f44c47 Removes Drake Industries Related Lockers (#3254) 2026-02-22 00:36:44 -05:00
monolith8319 658651d9f5 Automated Changelog (#3183) 2026-02-22 04:21:29 +00:00
Corrupt c0e2eb143d Ticks! Ticks! And More Ticks! (Ship) (#3183) 2026-02-21 23:21:00 -05:00
monolith8319 f868adde71 Automated Changelog (#3205) 2026-02-22 04:05:18 +00:00
tonotom1 0168527964 Cargo Liberation Pass (#3205)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-02-21 23:04:49 -05:00
monolith8319 f8ada83ac6 Automated Changelog (#3210) 2026-02-22 03:05:55 +00:00
tonotom1 af9e5f9e39 Port DV #2853, #2864: Paperwork Expansion (#3210)
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: ConstantlyConfused <67894661+Eternally-Confused@users.noreply.github.com>
2026-02-21 22:05:27 -05:00
Mrrpip a5aec5f2d2 Enable Signal Networks on Large Thrusters (#3256) 2026-02-21 21:37:30 -05:00
monolith8319 be00b6647f Automated Changelog (#3265) 2026-02-22 02:35:58 +00:00
Aithaed 46bfa7a456 Shuttle Event Minimum Spawn Timer Decrease (#3265) 2026-02-21 21:35:26 -05:00
Mrrpip 7c72df2b93 Adds Diagonal Lattices and Radar Markings to the Tumour Fighters. (#3249) 2026-02-21 21:33:24 -05:00
monolith8319 7f5c9e7b54 Automated Changelog (#3259) 2026-02-22 01:24:10 +00:00
Aithaed 977407aac6 Anomaly Artifact Asteroid Additions (#3259) 2026-02-21 20:23:41 -05:00
monolith8319 5f1b01604c Automated Changelog (#3248) 2026-02-22 01:16:43 +00:00
Redrover1760 9b763e1337 Removes Gridpacifier Component From CC/MD (#3248) 2026-02-21 20:16:12 -05:00
monolith8319 50c992f678 Automated Changelog (#3255) 2026-02-21 16:49:24 +00:00
10KE f9d33d734e Fix Thrasher Tail (#3255) 2026-02-21 11:48:54 -05:00
monolith8319 3d0bb2e741 Automated Changelog (#3264) 2026-02-21 14:34:23 +00:00
Aithaed 46be764897 Smuggling Crate Radar Blips (#3264) 2026-02-21 17:33:52 +03:00
monolith8319 454131d131 Automated Changelog (#3218) 2026-02-19 19:54:50 +00:00
Ilya246 7a835183eb Add Missile Tracking Targets (#3186) 2026-02-19 22:54:38 +03:00
Kei Kiyomi 569657571c Change Radio CentComm to HighComm (#3206) 2026-02-19 22:53:12 +03:00
Aithaed bf30edf019 Whatever, Go My ASMGTs (#3216) 2026-02-19 22:51:15 +03:00
Aithaed e11d29b99f I AM THE LAW (Security Guards Are Out, Judges Are In) (#3218) 2026-02-19 22:50:27 +03:00
monolith8319 3b4239d733 Automated Changelog (#3220) 2026-02-19 19:49:50 +00:00
UnicornOnLSD 2679fd6dd4 DIS Vaquita Maintenance (#3220) 2026-02-19 22:48:34 +03:00
monolith8319 8780e7cc4b Automated Changelog (#3240) 2026-02-19 19:45:35 +00:00
Aithaed 4a955f1f5b Spyglass C*C Radar Buff. (#3223) 2026-02-19 22:42:57 +03:00
gonz0 3c2486489a ERT Hardsuit Sprites For Hydrakin (#3240) 2026-02-19 22:42:06 +03:00
monolith8319 1bf0ea7b0c Automated Changelog (#3230) 2026-02-19 19:41:38 +00:00
UnicornOnLSD 012421b262 DIS Notch Maintenance (#3230) 2026-02-19 22:41:07 +03:00
monolith8319 23712f4814 Automated Changelog (#3251) 2026-02-19 18:51:15 +00:00
chuga-git dba2b120aa RND Consoles Hide Unresearchable Tech Nodes (#3250) 2026-02-19 21:50:52 +03:00
Kiithnaras fcf4847936 CDM Comet - Minor Fixes (#3251) 2026-02-19 21:50:41 +03:00
Redrover1760 b6e4b9cb42 Improves Explosion Admin Alerts and Logging (#3246) 2026-02-17 23:35:13 -05:00
monolith8319 b025902141 Automated Changelog (#3242) 2026-02-18 00:26:38 +00:00
Redrover1760 154d8dcfed Restores Scream Action, Sets It to Not Auto-Populate on Spawn (#3242) 2026-02-17 19:26:05 -05:00
monolith8319 ca6b3e70ad Automated Changelog (#3238) 2026-02-18 00:12:50 +00:00
Redrover1760 4ddc73ce0b Observe Clarification (#3245) 2026-02-17 19:12:41 -05:00
Redrover1760 a217ba1885 Minor NLR Rule Clarification (#3238) 2026-02-17 19:12:21 -05:00
monolith8319 20e456b3ed Automated Changelog (#3243) 2026-02-18 00:09:57 +00:00
Redrover1760 efa7daa13c Minor Hydrakin Visual Fixes + Hunger/Thirst (#3243) 2026-02-17 19:09:26 -05:00
monolith8319 498a96042f Automated Changelog (#3236) 2026-02-17 05:05:06 +00:00
Redrover1760 4c8a74d0fd Minor Hydrakin Marking Fix (#3236) 2026-02-17 00:04:35 -05:00
1508 changed files with 170744 additions and 121400 deletions
+1 -1
View File
@@ -348,7 +348,7 @@ resharper_csharp_qualified_using_at_nested_scope = false
resharper_csharp_prefer_qualified_reference = false
resharper_csharp_allow_alias = false
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,json}]
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,json,slnx}]
indent_size = 2
[nuget.config]
+7 -8
View File
@@ -1,4 +1,3 @@
<!-- Guidelines: https://docs.spacestation14.io/en/getting-started/pr-guideline -->
## About the PR
<!-- What did you change? -->
@@ -7,23 +6,23 @@
## Why / Balance
<!-- Discuss how this would affect game balance or explain why it was changed. Link any relevant discussions or issues. -->
## How to test
<!-- Describe the way it can be tested -->
## Media
<!-- Attach media if the PR makes ingame changes (clothing, items, features, etc).
Small fixes/refactors are exempt. Media may be used in SS14 progress reports with credit. -->
## Requirements
<!-- Confirm the following by placing an X in the brackets [X]: -->
- [ ] I have read and am following the [Pull Request and Changelog Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
- [ ] I have read relevant guidelines/documentation to this PR found on [our devwiki](https://monolith-station.github.io/mono-docs/).
- [ ] I have added media to this PR or it does not require an ingame showcase.
- [ ] I can confirm this PR contains no AI-generated content, and did not use any AI-generated content.
- [ ] I can confirm this PR contains either no AI-generated content, or AI-generated content that meets our guidelines.
<!-- You should understand that not following the above may get your PR closed at maintainers discretion -->
<!-- When using AI, make sure you are already proficient in creating whatever content you are attempting to generate and that you already have experience contributing to SS14. This means using AI to enhance your preexisting workflow, not vibecoding. -->
## How to test
<!-- Describe the way it can be tested -->
## Breaking changes
<!-- List any breaking changes, including namespaces, public class/method/field changes, prototype renames; and provide instructions for fixing them.
This will be posted in #codebase-changes. -->
<!-- List any breaking changes, including namespaces, public class/method/field changes, prototype renames; and provide instructions for fixing them. -->
**Changelog**
<!-- Add a Changelog entry to make players aware of new features or changes that could affect gameplay.
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
+5 -5
View File
@@ -1,5 +1,5 @@
name: PR Changelogs
concurrency: commit_action
concurrency: commit_action-${{ github.event.pull_request.base.ref }} # Mono
on:
pull_request_target:
types: [closed]
@@ -16,11 +16,11 @@ jobs:
permissions:
contents: write
steps:
- name: Checkout Master
- name: Checkout Base Branch # Mono
uses: actions/checkout@v3
with:
token: ${{ secrets.BOT_TOKEN }}
ref: "${{ vars.CHANGELOG_BRANCH }}"
ref: "${{ github.event.pull_request.base.ref }}" # Mono
- name: Setup Git
run: |
@@ -47,9 +47,9 @@ jobs:
- name: Commit Changelog
run: |
git pull origin main
git pull origin "${{ github.event.pull_request.base.ref }}"
git add "${{ env.CHANGELOG_DIR }}"
git commit -m "Automated Changelog (#${{ env.PR_NUMBER }})"
git push
git push origin "${{ github.event.pull_request.base.ref }}"
shell: bash
continue-on-error: true
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Get Engine Tag
run: |
+1 -1
View File
@@ -55,7 +55,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
+3
View File
@@ -1,3 +1,6 @@
# MSbuild binlog files
*.binlog
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
+53 -7
View File
@@ -13,6 +13,26 @@
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Client - Tools",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Tools",
"program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll",
"args": [],
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Client - Release",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Release",
"program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll",
"args": [],
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Client (Compatibility renderer)",
"type": "coreclr",
@@ -31,6 +51,26 @@
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": "Server - Tools",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Tools",
"program": "${workspaceFolder}/bin/Content.Server/Content.Server.dll",
"args": [""],
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": "Server - Release",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Release",
"program": "${workspaceFolder}/bin/Content.Server/Content.Server.dll",
"args": [""],
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": "YAML Linter",
"type": "coreclr",
@@ -40,7 +80,7 @@
"cwd": "${workspaceFolder}/Content.YAMLLinter",
"console": "internalConsole",
"stopAtEntry": false
}
},
],
"compounds": [
{
@@ -51,14 +91,21 @@
],
"preLaunchTask": "build"
},
// Frontier begin
{
"name": "Server/Client (Release)",
"name": "Server/Client - Tools",
"configurations": [
"Server",
"Client"
"Server - Tools",
"Client - Tools"
],
"preLaunchTask": "build-release"
"preLaunchTask": "build - Tools"
},
{
"name": "Server/Client - Release",
"configurations": [
"Server - Release",
"Client - Release"
],
"preLaunchTask": "build - Release"
},
{
"name": "Server/Client (No Build)",
@@ -67,6 +114,5 @@
"Client"
]
}
// Frontier end
]
}
+26 -6
View File
@@ -21,26 +21,46 @@
},
"problemMatcher": "$msCompile"
},
// Frontier begin
{
"label": "build-release",
"label": "build - Tools",
"command": "dotnet",
"type": "shell",
"args": [
"build",
"--configuration=Release", // Build in release mode. Note: --, not /. /configuration doesn't work, because Microsoft.
"--configuration",
"Tools",
"/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names.
"/consoleloggerparameters:NoSummary" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:'ForceNoAlign;NoSummary'", // Do not generate summary otherwise it leads to duplicate errors in Problems panel
],
"group": {
"kind": "build"
"kind": "build",
"isDefault": false
},
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
},
{
"label": "build - Release",
"command": "dotnet",
"type": "shell",
"args": [
"build",
"--configuration",
"Release",
"/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names.
"/consoleloggerparameters:'ForceNoAlign;NoSummary'", // Do not generate summary otherwise it leads to duplicate errors in Problems panel
],
"group": {
"kind": "build",
"isDefault": false
},
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
},
// Frontier end
{
"label": "build-yaml-linter",
"command": "dotnet",
+1 -1
View File
@@ -8,7 +8,7 @@ import shutil
from pathlib import Path
from typing import List
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
SOLUTION_PATH = Path("..") / "SpaceStation14.slnx"
# If this doesn't match the saved version we overwrite them all.
CURRENT_HOOKS_VERSION = "2"
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
+14 -9
View File
@@ -1,17 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>12</LangVersion>
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
<Nullable>disable</Nullable>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<!-- pin transitive deps -->
<PackageReference Include="System.Management" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
@@ -19,10 +22,12 @@
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Benchmarks\Robust.Benchmarks.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Server.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<Import Project="..\RobustToolbox\Imports\Benchmarks.props" />
<Import Project="..\RobustToolbox\Imports\Testing.props" />
</Project>
+3
View File
@@ -0,0 +1,3 @@
// Global usings for Content.Benchmarks
global using Robust.UnitTesting.Pool;
@@ -0,0 +1,253 @@
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using System.Numerics;
namespace Content.Client.Atmos.Overlays;
/// <summary>
/// Renders a thermal heatmap overlay for gas tiles, used for equipment like thermal glasses.
/// /// </summary>
public sealed class GasTileDangerousTemperatureOverlay : Overlay
{
public override bool RequestScreenTexture { get; set; } = false;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IClyde _clyde = default!;
private GasTileOverlaySystem? _gasTileOverlay;
private readonly SharedTransformSystem _xformSys;
private EntityQuery<GasTileOverlayComponent> _overlayQuery;
private IRenderTexture? _temperatureTarget;
// Cache used to transform ThermalByte into Color for overlay
private readonly Color[] _colorCache = new Color[256];
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public GasTileDangerousTemperatureOverlay()
{
IoCManager.InjectDependencies(this);
_xformSys = _entManager.System<SharedTransformSystem>();
_overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
for (byte i = 0; i <= ThermalByte.TempResolution; i++)
{
_colorCache[i] = PreCalculateColor(i);
}
_colorCache[ThermalByte.StateVacuum] = Color.Teal;
_colorCache[ThermalByte.StateVacuum].A = 0.6f;
_colorCache[ThermalByte.AtmosImpossible] = Color.Transparent;
#if DEBUG // This shouldn't happend so tell me if you see this LimeGreen on the screen
_colorCache[ThermalByte.ReservedFuture0] = Color.LimeGreen;
_colorCache[ThermalByte.ReservedFuture1] = Color.LimeGreen;
_colorCache[ThermalByte.ReservedFuture2] = Color.LimeGreen;
#else
_colorCache[ThermalByte.ReservedFuture0] = Color.Transparent;
_colorCache[ThermalByte.ReservedFuture1] = Color.Transparent;
_colorCache[ThermalByte.ReservedFuture2] = Color.Transparent;
#endif
}
/// <summary>
/// Used for Calculating onscreen color from ThermalByte core value
/// /// </summary>
private static Color PreCalculateColor(byte byteTemp)
{
// Color Thresholds in Kelvin
// -150 C
const float deepFreezeK = 123.15f;
// -50 C
const float freezeStartK = 223.15f;
// 0 C
const float waterFreezeK = 273.15f;
// 50 C
const float heatStartK = 323.15f;
// 100 C
const float waterBoilK = 373.15f;
// 300 C
const float superHeatK = 573.15f;
var tempK = byteTemp * ThermalByte.TempDegreeResolution;
// Neutral Zone Check (0C to 50C)
// If between 273.15K and 323.15K, it's transparent.
if (tempK >= waterFreezeK && tempK < heatStartK)
{
return Color.Transparent;
}
Color resultingColor;
switch (tempK)
{
case < deepFreezeK:
resultingColor = Color.FromHex("#330066");
resultingColor.A = 0.7f;
break;
case < freezeStartK:
// Interpolate Deep Purple -> Blue
// Range: 123.15 to 223.15 (Span: 100)
resultingColor = Color.InterpolateBetween(
Color.FromHex("#330066"),
Color.Blue,
(tempK - deepFreezeK) * 0.01f);
resultingColor.A = 0.6f;
break;
case < waterFreezeK:
// Interpolate Blue -> Transparent
// Range: 223.15 to 273.15 (Span: 50)
resultingColor = Color.InterpolateBetween(
new Color(Color.Blue.R, Color.Blue.G, Color.Blue.B, 0.6f),
new Color(Color.Blue.R, Color.Blue.G, Color.Blue.B, 0.2f),
(tempK - freezeStartK) * 0.02f);
break;
case < waterBoilK:
// Interpolate Transparent -> Yellow
// Range: 323.15 to 373.15 (Span: 50)
resultingColor = Color.InterpolateBetween(
new Color(Color.Yellow.R, Color.Yellow.G, Color.Yellow.B, 0.2f),
new Color(Color.Yellow.R, Color.Yellow.G, Color.Yellow.B, 0.6f),
(tempK - heatStartK) * 0.02f);
break;
case < superHeatK:
// Interpolate Yellow -> Red
// Range: 373.15 to 573.15 (Span: 200)
resultingColor = Color.InterpolateBetween(
Color.Yellow,
Color.Red,
(tempK - waterBoilK) * 0.005f);
resultingColor.A = 0.6f;
break;
default:
resultingColor = Color.DarkRed;
resultingColor.A = 0.7f;
break;
}
return resultingColor;
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (args.MapId == MapId.Nullspace)
return false;
_gasTileOverlay ??= _entManager.System<GasTileOverlaySystem>();
if (_gasTileOverlay == null)
return false;
var target = args.Viewport.RenderTarget;
if (_temperatureTarget?.Texture.Size != target.Size)
{
_temperatureTarget?.Dispose();
_temperatureTarget = _clyde.CreateRenderTarget(
target.Size,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
name: nameof(GasTileDangerousTemperatureOverlay));
}
var drawHandle = args.WorldHandle;
var worldBounds = args.WorldBounds;
var worldAABB = args.WorldAABB;
var mapId = args.MapId;
var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix();
var anyGasDrawn = false;
List<Entity<MapGridComponent>> grids = new();
drawHandle.RenderInRenderTarget(_temperatureTarget,
() =>
{
grids.Clear();
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref grids);
foreach (var grid in grids)
{
if (!_overlayQuery.TryGetComponent(grid.Owner, out var comp))
continue;
var gridTileSizeVec = grid.Comp.TileSizeVector;
var gridTileCenterVec = grid.Comp.TileSizeHalfVector;
var gridEntToWorld = _xformSys.GetWorldMatrix(grid.Owner);
var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal;
drawHandle.SetTransform(gridEntToViewportLocal);
var worldToGridLocal = _xformSys.GetInvWorldMatrix(grid.Owner);
var floatBounds = worldToGridLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize);
var localBounds = new Box2i(
(int)MathF.Floor(floatBounds.Left),
(int)MathF.Floor(floatBounds.Bottom),
(int)MathF.Ceiling(floatBounds.Right),
(int)MathF.Ceiling(floatBounds.Top));
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
while (enumerator.MoveNext(out var tileGas))
{
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
if (!localBounds.Contains(tilePosition))
continue;
var gasColor = _colorCache[tileGas.ByteGasTemperature.Value];
if (gasColor.A <= 0f)
continue;
anyGasDrawn = true;
drawHandle.DrawRect(
Box2.CenteredAround(tilePosition + gridTileCenterVec, gridTileSizeVec),
gasColor
);
}
}
}
},
new Color(0, 0, 0, 0));
drawHandle.SetTransform(Matrix3x2.Identity);
if (!anyGasDrawn)
{
_temperatureTarget?.Dispose();
_temperatureTarget = null;
return false;
}
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (_temperatureTarget is null)
return;
args.WorldHandle.DrawTextureRect(_temperatureTarget.Texture, args.WorldBounds);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
}
protected override void DisposeBehavior()
{
_temperatureTarget?.Dispose();
_temperatureTarget = null;
base.DisposeBehavior();
}
}
@@ -36,8 +36,6 @@ public sealed partial class ContentAudioSystem
[Dependency] private readonly IStateManager _state = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly CombatModeSystem _combatModeSystem = default!; //CLIENT ONE. WHY ARE THERE 3???
[Dependency] private readonly IPrototypeManager _protMan = default!;
[Dependency] private readonly SpaceBiomeSystem _spaceBiome = default!;
//options menu ---
private static float _volumeSliderAmbient;
@@ -8,7 +8,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Serilog;
namespace Content.Client.Cargo.UI;
@@ -32,6 +32,10 @@ public sealed class CharacterInfoSystem : EntitySystem
private void OnCharacterInfoEvent(CharacterInfoEvent msg, EntitySessionEventArgs args)
{
var entity = GetEntity(msg.NetEntity);
// Mono - fix
if (TerminatingOrDeleted(entity))
return;
var data = new CharacterData(entity, msg.JobTitle, msg.Objectives, msg.Briefing, Name(entity));
OnCharacterUpdate?.Invoke(data);
+2 -2
View File
@@ -9,13 +9,13 @@
<!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="False">
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-general'}" TargetLayer="General" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" Name="GeneralCategoryTexture"/>
</ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-vocal'}" TargetLayer="Vocal" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Emotes/vocal.png"/>
</ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-hands'}" TargetLayer="Hands" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" Name="HandsCategoryTexture"/>
</ui:RadialMenuTextureButtonWithSector>
</ui:RadialContainer>
@@ -5,10 +5,12 @@ using Content.Shared.Speech;
using Content.Shared.Whitelist;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Chat.UI;
@@ -86,6 +88,9 @@ public sealed partial class EmotesMenu : RadialMenu
continue;
AddEmoteClickAction(container);
}
GeneralCategoryTexture.Texture = spriteSystem.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Head/Soft/mimesoft.rsi"), "icon"));
HandsCategoryTexture.Texture = spriteSystem.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/latex.rsi"), "icon"));
}
private void AddEmoteClickAction(RadialContainer container)
@@ -6,7 +6,6 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
@@ -14,6 +13,7 @@ using System.Numerics;
using Content.Shared.FixedPoint;
using Robust.Client.Graphics;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Robust.Client.GameObjects;
namespace Content.Client.Chemistry.UI
{
@@ -24,6 +24,8 @@ namespace Content.Client.Chemistry.UI
public sealed partial class ChemMasterWindow : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly SpriteSystem _sprite;
public event Action<BaseButton.ButtonEventArgs, ReagentButton>? OnReagentButtonPressed;
public readonly Button[] PillTypeButtons;
@@ -37,6 +39,7 @@ namespace Content.Client.Chemistry.UI
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entityManager.System<SpriteSystem>();
// Pill type selection buttons, in total there are 20 pills.
// Pill rsi file should have states named as pill1, pill2, and so on.
@@ -67,9 +70,9 @@ namespace Content.Client.Chemistry.UI
// Generate buttons textures
var specifier = new SpriteSpecifier.Rsi(resourcePath, "pill" + (i + 1));
TextureRect pillTypeTexture = new TextureRect
var pillTypeTexture = new TextureRect
{
Texture = specifier.Frame0(),
Texture = _sprite.Frame0(specifier),
TextureScale = new Vector2(1.75f, 1.75f),
Stretch = TextureRect.StretchMode.KeepCentered,
};
@@ -249,7 +252,7 @@ namespace Content.Client.Chemistry.UI
ChemMasterSortingType.Latest => Loc.GetString("chem-master-window-sort-type-latest"),
_ => Loc.GetString("chem-master-window-sort-type-none")
};
OutputBufferDraw.Pressed = state.DrawSource == ChemMasterDrawSource.Internal;
OutputBeakerDraw.Pressed = state.DrawSource == ChemMasterDrawSource.External;
@@ -434,7 +437,7 @@ namespace Content.Client.Chemistry.UI
get => LabelLineEdit.Text;
set => LabelLineEdit.Text = value;
}
private void SetBufferText(FixedPoint2? volume, string text)
{
BufferCurrentVolume.Text = $" {volume ?? FixedPoint2.Zero}u";
+10 -12
View File
@@ -1,26 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Client\</OutputPath>
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
<WarningsAsErrors>nullable</WarningsAsErrors>
<Nullable>enable</Nullable>
<Configurations>Debug;Release;Tools;DebugOpt</Configurations>
<Platforms>AnyCPU</Platforms>
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="Nett" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="Pidgin" />
<PackageReference Include="Robust.Shared.AuthLib" />
</ItemGroup>
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<ItemGroup>
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
</ItemGroup>
<ItemGroup>
+3 -2
View File
@@ -1,4 +1,5 @@
using Robust.Client.Graphics;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -34,7 +35,7 @@ namespace Content.Client.Cooldown
if (Progress >= 0f)
{
var hue = (5f / 18f) * lerp;
color = Color.FromHsv((hue, 0.75f, 0.75f, 0.50f));
color = Color.FromHsv(new Vector4(hue, 0.75f, 0.75f, 0.50f));
}
else
{
+2 -1
View File
@@ -1,4 +1,5 @@
using Content.Shared.Disposal;
using System.Numerics;
using Content.Shared.Disposal;
using Content.Shared.Disposal.Unit;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
@@ -36,39 +36,37 @@ public sealed partial class DocumentParsingManager
.Assert(_tagControlParsers.ContainsKey, tag => $"unknown tag: {tag}")
.Bind(tag => _tagControlParsers[tag]);
// Frontier: comment parser
Parser<char, Unit> whitespaceAndCommentParser = SkipWhitespaces.Then(Try(String("<!--").Then(Parser<char>.Any.SkipUntil(Try(String("-->"))))).SkipMany());
var whitespaceAndCommentParser = SkipWhitespaces.Then(Try(String("<!--").Then(Parser<char>.Any.SkipUntil(Try(String("-->"))))).SkipMany());
_controlParser = OneOf(_tagParser, TryHeaderControl, TryListControl, TextControlParser)
.Before(whitespaceAndCommentParser);
// End Frontier
foreach (var typ in _reflectionManager.GetAllChildren<IDocumentTag>())
{
_tagControlParsers.Add(typ.Name, CreateTagControlParser(typ.Name, typ, _sandboxHelper));
}
ControlParser = whitespaceAndCommentParser.Then(_controlParser.Many()); // Frontier: SkipWhitespaces<whitespaceAndCommentParser
ControlParser = whitespaceAndCommentParser.Then(_controlParser.Many());
_sawmill = Logger.GetSawmill("Guidebook");
}
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId)
{
if (!_prototype.TryIndex(entryId, out var entry))
if (!_prototype.Resolve(entryId, out var entry))
return false;
using var file = _resourceManager.ContentFileReadText(entry.Text);
return TryAddMarkup(control, file.ReadToEnd(), log);
return TryAddMarkup(control, file.ReadToEnd());
}
public bool TryAddMarkup(Control control, GuideEntry entry, bool log = true)
public bool TryAddMarkup(Control control, GuideEntry entry)
{
using var file = _resourceManager.ContentFileReadText(entry.Text);
return TryAddMarkup(control, file.ReadToEnd(), log);
return TryAddMarkup(control, file.ReadToEnd());
}
public bool TryAddMarkup(Control control, string text, bool log = true)
public bool TryAddMarkup(Control control, string text)
{
try
{
@@ -83,7 +83,7 @@ public sealed partial class DocumentParsingManager
}
msg.Pop();
rt.SetMessage(msg);
rt.SetMessage(msg, tagsAllowed: null);
return rt;
},
TextParser)
+1
View File
@@ -5,6 +5,7 @@ using Robust.Client.Graphics;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using System.Linq;
using System.Numerics;
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
namespace Content.Client.Holopad;
+1 -1
View File
@@ -18,7 +18,7 @@ public sealed partial class InfoSection : BoxContainer
{
TitleLabel.Text = title;
if (markup)
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()), tagsAllowed: null);
else
Content.SetMessage(text);
}
+1 -1
View File
@@ -24,7 +24,7 @@ namespace Content.Client.Info
}
public void SetInfoBlob(string markup)
{
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup), tagsAllowed: null);
}
}
}
@@ -1,4 +1,5 @@
using System.Linq;
using System.Numerics;
using Content.Client.Items.Systems;
using Content.Shared.Clothing;
using Content.Shared.Hands;
@@ -1,6 +1,4 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Prometheus;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -1,3 +1,4 @@
using System.Numerics;
using Content.Shared.Nyanotrasen.Kitchen.UI;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
@@ -0,0 +1,7 @@
<Control xmlns="https://spacestation14.io">
<BoxContainer Orientation="Vertical">
<Label Name="TitleLabel" Access="Public" />
<Label Name="ExampleLabel" Access="Public" />
<ColorSelectorSliders Name="Slider" Access="Public" HorizontalExpand="True" />
</BoxContainer>
</Control>
@@ -0,0 +1,31 @@
using Content.Client.Options.UI;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
namespace Content.Client.Options.UI;
/// <summary>
/// Standard UI control used for color sliders in the options menu. Intended for use with <see cref="OptionsTabControlRow"/>.
/// </summary>
/// <seealso cref="OptionsTabControlRow.AddOptionColorSlider"/>
[GenerateTypedNameReferences]
public sealed partial class OptionColorSlider : Control
{
/// <summary>
/// The text describing what this slider affects.
/// </summary>
public string? Title
{
get => TitleLabel.Text;
set => TitleLabel.Text = value;
}
/// <summary>
/// The example text showing the current color of the slider.
/// </summary>
public string? Example
{
get => ExampleLabel.Text;
set => ExampleLabel.Text = value;
}
}
@@ -143,6 +143,19 @@ public sealed partial class OptionsTabControlRow : Control
return AddOption(new OptionSliderIntCVar(this, _cfg, cVar, slider, min, max, format ?? FormatInt));
}
/// <summary>
/// Add a color slider option, backed by a simple string CVar.
/// </summary>
/// <param name="cVar">The CVar represented by the slider.</param>
/// <param name="slider">The UI control for the option.</param>
/// <returns>The option instance backing the added option.</returns>
public OptionColorSliderCVar AddOptionColorSlider(
CVarDef<string> cVar,
OptionColorSlider slider)
{
return AddOption(new OptionColorSliderCVar(this, _cfg, cVar, slider));
}
/// <summary>
/// Add a drop-down option, backed by a CVar.
/// </summary>
@@ -518,6 +531,54 @@ public sealed class OptionSliderFloatCVar : BaseOptionCVar<float>
}
}
public sealed class OptionColorSliderCVar : BaseOptionCVar<string>
{
private readonly OptionColorSlider _slider;
protected override string Value
{
get => _slider.Slider.Color.ToHex();
set
{
_slider.Slider.Color = Color.FromHex(value);
UpdateLabelColor();
}
}
/// <summary>
/// Creates a new instance of this type.
/// </summary>
/// <remarks>
/// <para>
/// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
/// such as <see cref="OptionsTabControlRow.AddOptionPercentSlider"/> instead of instantiating this type directly.
/// </para>
/// </remarks>
/// <param name="controller">The control row that owns this option.</param>
/// <param name="cfg">The configuration manager to get and set values from.</param>
/// <param name="cVar">The CVar that is being controlled by this option.</param>
/// <param name="slider">The UI control for the option.</param>
public OptionColorSliderCVar(
OptionsTabControlRow controller,
IConfigurationManager cfg,
CVarDef<string> cVar,
OptionColorSlider slider) : base(controller, cfg, cVar)
{
_slider = slider;
slider.Slider.OnColorChanged += _ =>
{
ValueChanged();
UpdateLabelColor();
};
}
private void UpdateLabelColor()
{
_slider.ExampleLabel.FontColorOverride = Color.FromHex(Value);
}
}
/// <summary>
/// Implementation of a CVar option that simply corresponds with an integer <see cref="OptionSlider"/>.
/// </summary>
@@ -12,6 +12,10 @@
<ui:OptionSlider Name="SpeechBubbleTextOpacitySlider" Title="{Loc 'ui-options-speech-bubble-text-opacity'}" />
<ui:OptionSlider Name="SpeechBubbleSpeakerOpacitySlider" Title="{Loc 'ui-options-speech-bubble-speaker-opacity'}" />
<ui:OptionSlider Name="SpeechBubbleBackgroundOpacitySlider" Title="{Loc 'ui-options-speech-bubble-background-opacity'}" />
<CheckBox Name="AutoFillHighlightsCheckBox" Text="{Loc 'ui-options-auto-fill-highlights'}" />
<ui:OptionColorSlider Name="HighlightsColorSlider"
Title="{Loc 'ui-options-highlights-color'}"
Example="{Loc 'ui-options-highlights-color-example'}"/>
</BoxContainer>
</ScrollContainer>
<ui:OptionsTabControlRow Name="Control" Access="Public" />
@@ -20,6 +20,8 @@ public sealed partial class AccessibilityTab : Control
Control.AddOptionPercentSlider(CCVars.SpeechBubbleTextOpacity, SpeechBubbleTextOpacitySlider);
Control.AddOptionPercentSlider(CCVars.SpeechBubbleSpeakerOpacity, SpeechBubbleSpeakerOpacitySlider);
Control.AddOptionPercentSlider(CCVars.SpeechBubbleBackgroundOpacity, SpeechBubbleBackgroundOpacitySlider);
Control.AddOptionCheckBox(CCVars.ChatAutoFillHighlights, AutoFillHighlightsCheckBox);
Control.AddOptionColorSlider(CCVars.ChatHighlightsColor, HighlightsColorSlider);
Control.Initialize();
}
@@ -0,0 +1,34 @@
using Content.Client.Atmos.Overlays;
using Content.Shared.Inventory.Events;
using Content.Shared.Overlays;
using Robust.Client.Graphics;
namespace Content.Client.Overlays;
public sealed partial class ThermalSightOverlaySystem : EquipmentHudSystem<ThermalSightComponent>
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
private GasTileDangerousTemperatureOverlay _temperatureOverlay = default!;
public override void Initialize()
{
base.Initialize();
_temperatureOverlay = new();
}
protected override void UpdateInternal(RefreshEquipmentHudEvent<ThermalSightComponent> component)
{
base.UpdateInternal(component);
_overlayMan.AddOverlay(_temperatureOverlay);
}
protected override void DeactivateInternal()
{
base.DeactivateInternal();
_overlayMan.RemoveOverlay(_temperatureOverlay);
}
}
@@ -98,10 +98,13 @@ public sealed class ParallaxManager : IParallaxManager
}
else
{
layers = await Task.WhenAll(
// Explicitly allocate params array to avoid sandbox violation since C# 14.
var tasks = new[]
{
LoadParallaxLayers(parallaxPrototype.Layers, loadedLayers, cancel),
LoadParallaxLayers(parallaxPrototype.LayersLQ, loadedLayers, cancel)
);
LoadParallaxLayers(parallaxPrototype.LayersLQ, loadedLayers, cancel),
};
layers = await Task.WhenAll(tasks);
}
cancel.ThrowIfCancellationRequested();
@@ -11,6 +11,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
using System.Numerics;
namespace Content.Client.Power.APC.UI
{
@@ -6,7 +6,6 @@ using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Content.Client.Power;
@@ -6,6 +6,9 @@ using Robust.Client.UserInterface;
using Robust.Shared.Log;
using Robust.Shared.Map;
// Mono
using Content.Shared._Mono.Shuttles;
namespace Content.Client.Shuttles.BUI;
[UsedImplicitly]
@@ -25,6 +28,7 @@ public sealed partial class ShuttleConsoleBoundUserInterface : BoundUserInterfac
_window.RequestFTL += OnFTLRequest;
_window.RequestBeaconFTL += OnFTLBeaconRequest;
_window.RequestAutopilot += OnAutopilotRequest; // Mono
_window.DockRequest += OnDockRequest;
_window.UndockRequest += OnUndockRequest;
_window.UndockAllRequest += OnUndockAllRequest;
@@ -78,6 +82,16 @@ public sealed partial class ShuttleConsoleBoundUserInterface : BoundUserInterfac
});
}
// Mono
private void OnAutopilotRequest(MapCoordinates obj, Angle angle)
{
SendMessage(new ShuttleConsoleAutopilotPositionMessage()
{
Coordinates = obj,
Angle = angle,
});
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
@@ -1,6 +1,7 @@
using Content.Shared.Shuttles.Systems;
using Content.Shared.Shuttles.Components;
using Content.Shared.Access.Components;
using Content.Shared.UserInterface;
namespace Content.Client.Shuttles.Systems;
@@ -17,4 +18,17 @@ public sealed class ShuttleConsoleLockSystem : SharedShuttleConsoleLockSystem
// Prediction only
return false;
}
}
/// <summary>
/// Client extension of OnUIOpenAttempt. Prevents UI flashing open on client.
/// </summary>
protected override void OnUIOpenAttempt(EntityUid uid,
ShuttleConsoleLockComponent component,
ActivatableUIOpenAttemptEvent args)
{
base.OnUIOpenAttempt(uid, component, args);
if(Timing.IsFirstTimePredicted && args.Cancelled)
Popup.PopupEntity(Loc.GetString("shuttle-console-locked"), uid, args.User);
}
}
@@ -82,6 +82,12 @@
Text="{controls:Loc 'shuttle-console-ftl-button'}"
TextAlign="Center"
StyleClasses="ButtonSquare"/>
<!-- Mono - Autopilot -->
<controls:Button Name="MapAutopilotButton"
ToggleMode="True"
Text="{controls:Loc 'shuttle-console-autopilot-button'}"
TextAlign="Center"
StyleClasses="ButtonSquare"/>
<controls:Button Name="MapRebuildButton"
Text="{controls:Loc 'shuttle-console-map-rebuild'}"
TextAlign="Center"
+39 -5
View File
@@ -51,6 +51,9 @@ public sealed partial class MapScreen : BoxContainer
private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3);
private TimeSpan _nextMapDequeue;
// Mono
private bool _autopilotTargeting = false;
private float _minMapDequeue = 0.05f;
private float _maxMapDequeue = 0.25f;
@@ -59,6 +62,9 @@ public sealed partial class MapScreen : BoxContainer
public event Action<MapCoordinates, Angle>? RequestFTL;
public event Action<NetEntity, Angle>? RequestBeaconFTL;
// Mono
public event Action<MapCoordinates, Angle>? RequestAutopilot;
private readonly Dictionary<MapId, BoxContainer> _mapHeadings = new();
private readonly Dictionary<MapId, List<IMapObject>> _mapObjects = new();
private readonly List<(MapId mapId, IMapObject mapobj)> _pendingMapObjects = new();
@@ -86,6 +92,7 @@ public sealed partial class MapScreen : BoxContainer
OnVisibilityChanged += OnVisChange;
MapFTLButton.OnToggled += FtlPreviewToggled;
MapAutopilotButton.OnToggled += AutopilotPreviewToggled; // Mono
_ftlStyle = new StyleBoxFlat(Color.LimeGreen);
FTLBar.ForegroundStyleBoxOverride = _ftlStyle;
@@ -93,7 +100,15 @@ public sealed partial class MapScreen : BoxContainer
// Just pass it on up.
MapRadar.RequestFTL += (coords, angle) =>
{
RequestFTL?.Invoke(coords, angle);
if (_autopilotTargeting) // Mono
{
RequestAutopilot?.Invoke(coords, angle);
SetTargeting(false, true);
}
else
{
RequestFTL?.Invoke(coords, angle);
}
};
MapRadar.RequestBeaconFTL += (ent, angle) =>
@@ -192,12 +207,31 @@ public sealed partial class MapScreen : BoxContainer
private void FtlPreviewToggled(BaseButton.ButtonToggledEventArgs obj)
{
MapRadar.FtlMode = obj.Pressed;
SetTargeting(obj.Pressed, false);
}
// When FTL button is toggled, disable the ShowFTLRangeOnly mode
if (obj.Pressed)
// Mono
private void AutopilotPreviewToggled(BaseButton.ButtonToggledEventArgs obj)
{
SetTargeting(obj.Pressed, true);
}
// Mono
private void SetTargeting(bool pressed, bool isAutopilot)
{
if (pressed)
{
MapRadar.FtlMode = true;
MapRadar.ShowFTLRangeOnly = false;
MapRadar.ShowFTLRange = !isAutopilot;
MapRadar.NoFTLRange = isAutopilot;
MapFTLButton.Pressed = !isAutopilot;
MapAutopilotButton.Pressed = isAutopilot;
_autopilotTargeting = isAutopilot;
}
else
{
MapRadar.FtlMode = false;
}
}
@@ -356,7 +390,7 @@ public sealed partial class MapScreen : BoxContainer
var name = hideLabel ?
detectionLevel == DetectionLevel.PartialDetected ?
Loc.GetString($"shuttle-console-signature-infrared")
: Loc.GetString($"shuttle-console-signature-unknown")
: _detection.HandleUnknownMassLabel(grid.Owner)
: _entManager.GetComponent<MetaDataComponent>(grid.Owner).EntityName;
var gridObj = new GridMapObject()
+2 -2
View File
@@ -62,13 +62,13 @@ public sealed partial class NavScreen : BoxContainer
!string.IsNullOrEmpty(companyComp.CompanyName))
{
// Try to match the company ID directly
if (companyComp.CompanyName.Contains(text, StringComparison.OrdinalIgnoreCase))
if (companyComp.CompanyName.Id.Contains(text, StringComparison.OrdinalIgnoreCase))
{
return true;
}
// Try to match company name from prototype
if (_prototypeManager.TryIndex<CompanyPrototype>(
if (_prototypeManager.TryIndex(
companyComp.CompanyName, out var prototype) &&
prototype.Name.Contains(text, StringComparison.OrdinalIgnoreCase))
{
@@ -20,6 +20,9 @@ public sealed partial class ShuttleConsoleWindow : FancyWindow,
public event Action<MapCoordinates, Angle>? RequestFTL;
public event Action<NetEntity, Angle>? RequestBeaconFTL;
// Mono
public event Action<MapCoordinates, Angle>? RequestAutopilot;
public event Action<NetEntity, NetEntity>? DockRequest;
public event Action<NetEntity>? UndockRequest;
public event Action<List<NetEntity>>? UndockAllRequest;
@@ -55,6 +58,12 @@ public sealed partial class ShuttleConsoleWindow : FancyWindow,
RequestBeaconFTL?.Invoke(ent, angle);
};
// Mono
MapContainer.RequestAutopilot += (coords, angle) =>
{
RequestAutopilot?.Invoke(coords, angle);
};
DockContainer.DockRequest += (entity, netEntity) =>
{
DockRequest?.Invoke(entity, netEntity);
@@ -64,12 +73,12 @@ public sealed partial class ShuttleConsoleWindow : FancyWindow,
{
UndockRequest?.Invoke(entity);
};
DockContainer.UndockAllRequest += dockEntities =>
{
UndockAllRequest?.Invoke(dockEntities);
};
DockContainer.ToggleFTLLockRequest += (dockEntities, enabled) =>
{
ToggleFTLLockRequest?.Invoke(dockEntities, enabled);
@@ -57,6 +57,16 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
/// </summary>
public bool ShowFTLRangeOnly;
/// <summary>
/// Mono - Whether to show FTL range at all.
/// </summary>
public bool ShowFTLRange = true;
/// <summary>
/// Mono - Whether to ignore FTL obstructions and range for preview display.
/// </summary>
public bool NoFTLRange = false;
private Angle _ftlAngle;
/// <summary>
@@ -275,7 +285,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
// Draw our FTL range + no FTL zones
// Do it up here because we want this layered below most things.
if (FtlMode || ShowFTLRangeOnly)
if ((FtlMode || ShowFTLRangeOnly) && ShowFTLRange) // Mono
{
if (EntManager.TryGetComponent<TransformComponent>(_shuttleEntity, out var shuttleXform))
{
@@ -413,7 +423,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
var iffText = hideLabel ?
detectionLevel == DetectionLevel.PartialDetected ?
Loc.GetString($"shuttle-console-signature-infrared")
: Loc.GetString($"shuttle-console-signature-unknown")
: _detection.HandleUnknownMassLabel(grid.Owner)
: _shuttles.GetIFFLabel(grid, self: true, component: iffComp);
if (string.IsNullOrEmpty(iffText))
@@ -515,7 +525,8 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
var mouseMapPos = InverseMapPosition(mouseLocalPos);
var ftlFree = (!beaconsOnly || foundBeacon != default) &&
_shuttles.FTLFree(_shuttleEntity.Value, new EntityCoordinates(viewedMapUid, mouseMapPos), _ftlAngle, _viewportExclusions);
_shuttles.FTLFree(_shuttleEntity.Value, new EntityCoordinates(viewedMapUid, mouseMapPos), _ftlAngle, _viewportExclusions)
|| NoFTLRange; // Mono
var color = ftlFree ? Color.LimeGreen : Color.Magenta;
@@ -51,6 +51,17 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
private Dictionary<NetEntity, List<DockingPortState>> _docks = new();
// temporary buffers to avoid per-frame heap churn
private readonly List<BlipData> _tempBlipDataList = new();
private readonly HashSet<EntityUid> _visibleGridsSet = new();
private static readonly Vector2[] RadarPosVertsCache =
[
new Vector2(0f, -2f),
new Vector2(1f, 0f),
new Vector2(0f, 2f),
new Vector2(-1f, 0f),
];
public bool ShowIFF { get; set; } = true;
public bool ShowIFFShuttles { get; set; } = true;
public bool ShowDocks { get; set; } = true;
@@ -341,14 +352,12 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
}
// Draw radar position on the station
const float radarVertRadius = 2f;
var radarPosVerts = new Vector2[]
// Mono - use precalculated verts and scale each point rather than allocating a fresh array
var radarPosVerts = new Vector2[RadarPosVertsCache.Length];
for (var i = 0; i < radarPosVerts.Length; i++)
{
ScalePosition(new Vector2(0f, -radarVertRadius)),
ScalePosition(new Vector2(radarVertRadius / 2f, 0f)),
ScalePosition(new Vector2(0f, radarVertRadius)),
ScalePosition(new Vector2(radarVertRadius / -2f, 0f)),
};
radarPosVerts[i] = ScalePosition(RadarPosVertsCache[i]);
}
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, radarPosVerts, Color.Lime);
@@ -358,10 +367,10 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
_grids.Clear();
_mapManager.FindGridsIntersecting(xform.MapID, new Box2(mapPos.Position - MaxRadarRangeVector, mapPos.Position + MaxRadarRangeVector), ref _grids, approx: true, includeMap: false);
// Frontier - collect blip location data outside foreach - more changes ahead
var blipDataList = new List<BlipData>();
// Mono edited: Frontier - collect blip location data outside foreach - more changes ahead
_tempBlipDataList.Clear();
var visibleGrids = new HashSet<EntityUid>();
_visibleGridsSet.Clear();
// Draw other grids... differently
foreach (var grid in _grids)
@@ -385,8 +394,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
continue;
if (!blipOnly)
visibleGrids.Add(grid);
_visibleGridsSet.Add(grid);
var curGridToWorld = _transform.GetWorldMatrix(gUid);
var curGridToView = curGridToWorld * worldToView;
@@ -402,7 +410,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
var labelName = noLabel ? null : hideLabel ?
detectionLevel == DetectionLevel.PartialDetected ?
Loc.GetString($"shuttle-console-signature-infrared")
: Loc.GetString($"shuttle-console-signature-unknown")
: _detection.HandleUnknownMassLabel(grid.Owner)
: _shuttles.GetIFFLabel(grid, self: false, component: iff);
var shouldDrawIFF = ShowIFF && labelName != null;
@@ -562,12 +570,12 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
}
}
NfAddBlipToList(blipDataList, isOutsideRadarCircle, uiPosition, uiXCentre, uiYCentre, labelColor, hideLabel ? default : gUid); // Frontier code
NfAddBlipToList(_tempBlipDataList, isOutsideRadarCircle, uiPosition, uiXCentre, uiYCentre, labelColor, hideLabel ? default : gUid); // Frontier code
// End Frontier: IFF drawing functions
}
// Frontier Don't skip drawing blips if they're out of range.
NfDrawBlips(handle, blipDataList);
NfDrawBlips(handle, _tempBlipDataList);
// Detailed view
var gridAABB = curGridToWorld.TransformBox(grid.Comp.LocalAABB);
@@ -626,7 +634,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
if (blip.GridUid is { } grid)
{
// check detection if we're on a grid and that grid isn't our grid
if (!visibleGrids.Contains(grid) && grid != ourGridId)
if (!_visibleGridsSet.Contains(grid) && grid != ourGridId)
continue;
}
@@ -33,9 +33,22 @@ public sealed partial class BorgSelectTypeMenu : FancyWindow
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
foreach (var borgType in _prototypeManager.EnumeratePrototypes<BorgTypePrototype>().OrderBy(PrototypeName))
// Goobstation: Customizable borgs sprites
ConfirmTypeButton.OnPressed += ConfirmButtonPressed;
HelpGuidebookIds = GuidebookEntries;
SubtypeSelection.SubtypeSelected += () =>
ConfirmTypeButton.Disabled = false;
}
// foreach (var borgType in _prototypeManager.EnumeratePrototypes<BorgTypePrototype>().OrderBy(PrototypeName))
// Mono: Selectable borg whitelist
public void Populate(IReadOnlyList<ProtoId<BorgTypePrototype>> whitelist)
{
IEnumerable<BorgTypePrototype> types = whitelist.Select(id => _prototypeManager.Index(id)).OrderBy(PrototypeName);
foreach (var borgType in types)
// Mono: Selectable borg whitelist end
{
// Goobstation-Start: Customizable borgs sprites
var chassisList = new EntityPrototypeView
{
Scale = new Vector2(2, 2),
@@ -49,13 +62,6 @@ public sealed partial class BorgSelectTypeMenu : FancyWindow
};
SelectionsContainer.AddChild(chassisList);
}
ConfirmTypeButton.OnPressed += ConfirmButtonPressed;
HelpGuidebookIds = GuidebookEntries;
SubtypeSelection.SubtypeSelected += () =>
ConfirmTypeButton.Disabled = false;
// Goobstation-End: Customizable borgs sprites
}
@@ -23,8 +23,12 @@ public sealed class BorgSelectTypeUserInterface : BoundUserInterface
protected override void Open()
{
base.Open();
//Mono: Selectable borg whitelist
EntMan.TryGetComponent<BorgSwitchableTypeComponent>(Owner, out var comp);
var whitelist = comp?.TypeWhitelist ?? [];
_menu = this.CreateWindow<BorgSelectTypeMenu>();
_menu.Populate(whitelist); //Mono
_menu.ConfirmedBorgType += (prototype, subtype) => SendPredictedMessage(new BorgSelectTypeMessage(prototype, subtype));
}
}
@@ -46,18 +46,15 @@ public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
{
if (TryComp(entity, out SpriteComponent? sprite))
{
_sprite.LayerSetRsiState((entity, sprite), BorgVisualLayers.Body, prototype.SpriteBodyState);
_sprite.LayerSetRsiState((entity, sprite), BorgVisualLayers.Light, prototype.SpriteBodyState);
_sprite.LayerSetRsiState((entity, sprite), BorgVisualLayers.LightStatus, prototype.SpriteBodyState);
var rsiPath = SpriteSpecifierSerializer.TextureRoot / subtypePrototype.SpritePath;
if (_resourceCache.TryGetResource<RSIResource>(rsiPath, out var resource))
{
_sprite.LayerSetRsi((entity, sprite), BorgVisualLayers.Body, resource.RSI);
_sprite.LayerSetRsi((entity, sprite), BorgVisualLayers.Light, resource.RSI);
_sprite.LayerSetRsi((entity, sprite), BorgVisualLayers.LightStatus, resource.RSI);
_sprite.LayerSetRsi((entity, sprite), BorgVisualLayers.Body, resource.RSI, prototype.SpriteBodyState);
_sprite.LayerSetRsi((entity, sprite), BorgVisualLayers.Light, resource.RSI, prototype.SpriteBodyState);
_sprite.LayerSetRsi((entity, sprite), BorgVisualLayers.LightStatus, resource.RSI, prototype.SpriteToggleLightState);
}
_sprite.LayerSetRsiState((entity, sprite), BorgVisualLayers.Body, prototype.SpriteBodyState);
_sprite.LayerSetRsiState((entity, sprite), BorgVisualLayers.LightStatus, prototype.SpriteBodyState);
_sprite.LayerSetRsiState((entity, sprite), BorgVisualLayers.LightStatus, prototype.SpriteToggleLightState);
}
@@ -0,0 +1,38 @@
using System.Numerics;
using Content.Shared.Sprite;
using Robust.Client.GameObjects;
namespace Content.Client.Sprite;
public sealed class ScaleVisualsSystem : SharedScaleVisualsSystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ScaleVisualsComponent, AppearanceChangeEvent>(OnChangeData);
}
private void OnChangeData(Entity<ScaleVisualsComponent> ent, ref AppearanceChangeEvent args)
{
if (!args.AppearanceData.TryGetValue(ScaleVisuals.Scale, out var scale) ||
args.Sprite == null) return;
// save the original scale
ent.Comp.OriginalScale ??= args.Sprite.Scale;
var vecScale = (Vector2)scale;
_sprite.SetScale((ent.Owner, args.Sprite), vecScale);
}
// revert to the original scale
protected override void ResetScale(Entity<ScaleVisualsComponent> ent)
{
base.ResetScale(ent);
if (ent.Comp.OriginalScale != null)
_sprite.SetScale(ent.Owner, ent.Comp.OriginalScale.Value);
}
}
@@ -0,0 +1,264 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Content.Shared.CCVar;
using Content.Client.CharacterInfo;
using static Content.Client.CharacterInfo.CharacterInfoSystem;
namespace Content.Client.UserInterface.Systems.Chat;
public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSystem>
{
[Dependency] private readonly ILocalizationManager _loc = default!;
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
private static readonly Regex StartDoubleQuote = new("\"$");
private static readonly Regex EndDoubleQuote = new("^\"|(?<=^@)\"");
private static readonly Regex StartAtSign = new("^@");
private readonly List<string> _highlights = new();
private readonly List<string> _autoFillRawKeywords = new();
private string? _highlightsColor;
private bool _autoFillHighlightsEnabled;
private bool _charInfoIsAttach = false;
private string? _currentCharacterName;
public event Action<string>? HighlightsUpdated;
public event Action<string>? AutoFillUpdated;
private void InitializeHighlights()
{
_config.OnValueChanged(CCVars.ChatAutoFillHighlights, (value) => { _autoFillHighlightsEnabled = value; }, true);
_config.OnValueChanged(CCVars.ChatHighlightsColor, (value) => { _highlightsColor = value; }, true);
var cvar = _config.GetCVar(CCVars.ChatHighlights);
}
public void OnSystemLoaded(CharacterInfoSystem system)
{
system.OnCharacterUpdate += OnCharacterUpdated;
}
public void OnSystemUnloaded(CharacterInfoSystem system)
{
system.OnCharacterUpdate -= OnCharacterUpdated;
}
private void UpdateAutoFillHighlights()
{
_currentCharacterName = null;
_autoFillRawKeywords.Clear();
_highlights.Clear();
AutoFillUpdated?.Invoke("");
HighlightsUpdated?.Invoke("");
_charInfoIsAttach = true;
_characterInfo.RequestCharacterInfo();
}
/// <summary>
/// Saves the user's custom keywords for the given character into the CVar,
/// then rebuilds the active highlight list.
/// Called when the user submits keywords from the popup.
/// </summary>
public void UpdateHighlights(string customKeywords)
{
if (_currentCharacterName == null)
{
return;
}
SaveCustomKeywords(_currentCharacterName, customKeywords);
RebuildHighlightsList(customKeywords);
HighlightsUpdated?.Invoke(customKeywords);
}
private void OnCharacterUpdated(CharacterData data)
{
if (!_charInfoIsAttach)
{
return;
}
var (_, job, _, _, entityName) = data;
_currentCharacterName = entityName;
_autoFillRawKeywords.Clear();
var autoFillDisplay = "";
if (_autoFillHighlightsEnabled)
{
// Special rules for names can be added here
var nameHighlights = "@" + entityName;
// Split on spaces first (handles any number of space-separated words).
if (nameHighlights.Contains(' '))
nameHighlights = nameHighlights.Replace(" ", "\n@");
// Handle hyphenated tokens: single hyphen splits normally (e.g. "First-Last"),
// multiple hyphens use the lizard rule — keep only first and last part (e.g. "Eats-The-Food" → @Eats @Food).
var dashParts = nameHighlights.Split('-');
if (dashParts.Length == 2)
nameHighlights = nameHighlights.Replace("-", "\n@");
else if (dashParts.Length > 2)
nameHighlights = dashParts[0] + "\n@" + dashParts[^1];
foreach (var token in nameHighlights.Split('\n', StringSplitOptions.RemoveEmptyEntries))
{
_autoFillRawKeywords.Add(token);
}
var jobKey = job.Replace(' ', '-').ToLower();
if (_loc.TryGetString($"highlights-{jobKey}", out var jobMatches))
{
foreach (var jobWord in jobMatches.Replace(", ", "\n").Split('\n', StringSplitOptions.RemoveEmptyEntries))
_autoFillRawKeywords.Add(jobWord);
}
autoFillDisplay = string.Join("\n", _autoFillRawKeywords);
}
var customKeywords = LoadCustomKeywords(entityName);
RebuildHighlightsList(customKeywords);
AutoFillUpdated?.Invoke(autoFillDisplay);
HighlightsUpdated?.Invoke(customKeywords);
_charInfoIsAttach = false;
}
private string LoadCustomKeywords(string characterName)
{
var cvar = _config.GetCVar(CCVars.ChatHighlights);
if (string.IsNullOrEmpty(cvar))
return "";
// Shouldn't be needed as no-one should have data by the time this rolls out but we never know
if (!cvar.Contains("||"))
{
return cvar;
}
foreach (var line in cvar.Split('\n', StringSplitOptions.RemoveEmptyEntries))
{
var sep = line.IndexOf("||", StringComparison.Ordinal);
if (sep < 0)
continue;
if (line[..sep] == characterName)
return DecodeKeywords(line[(sep + 2)..]);
}
return "";
}
private void SaveCustomKeywords(string characterName, string customKeywords)
{
var cvar = _config.GetCVar(CCVars.ChatHighlights);
var entries = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(cvar) && cvar.Contains("||"))
{
foreach (var line in cvar.Split('\n', StringSplitOptions.RemoveEmptyEntries))
{
var sep = line.IndexOf("||", StringComparison.Ordinal);
if (sep < 0)
continue;
entries[line[..sep]] = line[(sep + 2)..];
}
}
entries[characterName] = EncodeKeywords(customKeywords);
var sb = new StringBuilder();
foreach (var (name, encoded) in entries)
sb.Append(name).Append("||").Append(encoded).Append('\n');
var newValue = sb.ToString().TrimEnd('\n');
_config.SetCVar(CCVars.ChatHighlights, newValue);
_config.SaveToFile();
}
private static string EncodeKeywords(string raw)
{
return raw
.Replace(@"\", @"\\")
.Replace("|", @"\|")
.Replace("\n", @"\n");
}
private static string DecodeKeywords(string encoded)
{
var sb = new StringBuilder(encoded.Length);
for (var i = 0; i < encoded.Length; i++)
{
if (encoded[i] == '\\' && i + 1 < encoded.Length)
{
switch (encoded[i + 1])
{
case 'n': sb.Append('\n'); i++; break;
case '|': sb.Append('|'); i++; break;
case '\\': sb.Append('\\'); i++; break;
default: sb.Append(encoded[i]); break;
}
}
else
{
sb.Append(encoded[i]);
}
}
return sb.ToString();
}
private void RebuildHighlightsList(string customRaw)
{
_highlights.Clear();
foreach (var raw in _autoFillRawKeywords)
_highlights.Add(ProcessKeyword(raw));
if (!string.IsNullOrWhiteSpace(customRaw))
{
foreach (var raw in customRaw.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
_highlights.Add(ProcessKeyword(raw));
}
_highlights.Sort((x, y) => y.Length.CompareTo(x.Length));
}
private string ProcessKeyword(string raw)
{
// Replace every "\" to prevent "\n", "\0", etc. in regex.
var keyword = raw.Replace(@"\", @"\\");
// Escape special regex characters.
keyword = Regex.Escape(keyword);
// Re-escape "[" for markup-adjacent positions.
keyword = keyword.Replace(@"\[", @"\\\[");
// words in double quotes will only be hightlighted as unique, not within another word
if (keyword.Any(c => c == '"'))
{
keyword = StartDoubleQuote.Replace(keyword, "(?!\\w)");
keyword = EndDoubleQuote.Replace(keyword, "(?<!\\w)");
}
// Make it so it doesn't hightlight your own name when you are talking
keyword = StartAtSign.Replace(keyword, @"(?<=\[BubbleContent\].*)");
return keyword;
}
}
@@ -44,7 +44,7 @@ using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Chat;
public sealed class ChatUIController : UIController
public sealed partial class ChatUIController : UIController
{
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IChatManager _manager = default!;
@@ -248,6 +248,8 @@ public sealed class ChatUIController : UIController
_config.OnValueChanged(CCVars.ChatWindowOpacity, OnChatWindowOpacityChanged);
InitializeHighlights();
}
public void OnScreenLoad()
@@ -434,6 +436,7 @@ public sealed class ChatUIController : UIController
private void OnAttachedChanged(EntityUid uid)
{
UpdateChannelPermissions();
UpdateAutoFillHighlights();
}
private void AddSpeechBubble(ChatMessage msg, SpeechBubble.SpeechType speechType)
@@ -877,6 +880,11 @@ public sealed class ChatUIController : UIController
}
}
foreach (var highlight in _highlights)
{
msg.WrappedMessage = SharedChatSystem.InjectTagAroundString(msg, highlight, "color", _highlightsColor);
}
// Log all incoming chat to repopulate when filter is un-toggled
if (!msg.HideChat)
{
@@ -912,7 +920,6 @@ public sealed class ChatUIController : UIController
case ChatChannel.Dead:
if (_ghost is not {IsGhost: true})
break;
AddSpeechBubble(msg, SpeechBubble.SpeechType.Say);
break;
@@ -1,10 +1,30 @@
<controls:ChannelFilterPopup
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Chat.Controls">
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Chat.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<PanelContainer Name="FilterPopupPanel" StyleClasses="BorderedWindowPanel">
<BoxContainer Orientation="Horizontal">
<Control MinSize="4 0"/>
<BoxContainer Name="FilterVBox" MinWidth="110" Margin="0 10" Orientation="Vertical" SeparationOverride="4"/>
<BoxContainer Name="HighlightsVBox" MinWidth="120" Margin="0 10" Orientation="Vertical" SeparationOverride="4">
<BoxContainer Name="AutoFillVBox" Orientation="Vertical" SeparationOverride="2" Visible="False">
<Label Text="{Loc 'hud-chatbox-autofill-highlights'}"/>
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#262736"/>
</PanelContainer.PanelOverride>
<RichTextLabel Name="AutoFillLabel" Margin="5 5"/>
</PanelContainer>
</BoxContainer>
<Label Text="{Loc 'hud-chatbox-highlights'}"/>
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#323446"/>
</PanelContainer.PanelOverride>
<TextEdit Name="HighlightEdit" MinHeight="150" Margin="5 5"/>
</PanelContainer>
<Button Name="HighlightButton" Text="{Loc 'hud-chatbox-highlights-button'}" ToolTip="{Loc 'hud-chatbox-highlights-tooltip'}"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
</controls:ChannelFilterPopup>
@@ -2,6 +2,7 @@ using Content.Shared.Chat;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.UserInterface.Systems.Chat.Controls;
@@ -30,10 +31,13 @@ public sealed partial class ChannelFilterPopup : Popup
private readonly Dictionary<ChatChannel, ChannelFilterCheckbox> _filterStates = new();
public event Action<ChatChannel, bool>? OnChannelFilter;
public event Action<string>? OnNewHighlights;
public ChannelFilterPopup()
{
RobustXamlLoader.Load(this);
HighlightButton.OnPressed += HighlightsEntered;
HighlightEdit.Placeholder = new Rope.Leaf(Loc.GetString("hud-chatbox-highlights-placeholder"));
}
public bool IsActive(ChatChannel channel)
@@ -95,4 +99,36 @@ public sealed partial class ChannelFilterPopup : Popup
if (_filterStates.TryGetValue(channel, out var checkbox))
checkbox.UpdateUnreadCount(unread);
}
// Highlight
/// <summary>
/// Updates the read-only auto-fill section with name/job tokens.
/// </summary>
public void UpdateAutoFill(string autoFillKeywords)
{
AutoFillVBox.Visible = !string.IsNullOrEmpty(autoFillKeywords);
if (string.IsNullOrEmpty(autoFillKeywords))
return;
var msg = new FormattedMessage();
var lines = autoFillKeywords.Split('\n', StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < lines.Length; i++)
{
if (i > 0) msg.PushNewline();
msg.AddText(lines[i]);
}
AutoFillLabel.SetMessage(msg);
}
public void UpdateHighlights(string highlights)
{
HighlightEdit.TextRope = new Rope.Leaf(highlights);
}
private void HighlightsEntered(ButtonEventArgs _args)
{
OnNewHighlights?.Invoke(Rope.Collapse(HighlightEdit.TextRope));
}
}
@@ -47,9 +47,12 @@ public partial class ChatBox : UIWidget
ChatInput.Input.OnFocusExit += OnFocusExit;
ChatInput.ChannelSelector.OnChannelSelect += OnChannelSelect;
ChatInput.FilterButton.Popup.OnChannelFilter += OnChannelFilter;
ChatInput.FilterButton.Popup.OnNewHighlights += OnNewHighlights;
_controller = UserInterfaceManager.GetUIController<ChatUIController>();
_controller.MessageAdded += OnMessageAdded;
_controller.HighlightsUpdated += OnHighlightsUpdated;
_controller.AutoFillUpdated += OnAutoFillUpdated;
_controller.RegisterChat(this);
// WD EDIT START
@@ -133,6 +136,21 @@ public partial class ChatBox : UIWidget
}
}
private void OnNewHighlights(string highlighs)
{
_controller.UpdateHighlights(highlighs);
}
private void OnHighlightsUpdated(string highlights)
{
ChatInput.FilterButton.Popup.UpdateHighlights(highlights);
}
private void OnAutoFillUpdated(string autoFillKeywords)
{
ChatInput.FilterButton.Popup.UpdateAutoFill(autoFillKeywords);
}
public void AddLine(string message, Color color, int repeat = 0) // WD EDIT
{
var formatted = new FormattedMessage(4); // WD EDIT // specifying size beforehand smells like a useless microoptimisation, but i'll give them the benefit of doubt
@@ -148,7 +166,7 @@ public partial class ChatBox : UIWidget
("size", 8+sizeIncrease)
));
} // WD EDIT END
Contents.AddMessage(formatted);
Contents.AddMessage(formatted, tagsAllowed: null);
}
public void Focus(ChatSelectChannel? channel = null)
@@ -1,3 +1,4 @@
using System.Numerics;
using Content.Shared.Mobs;
using Robust.Client.Graphics;
using Robust.Client.Player;
@@ -1,3 +1,4 @@
using System.Numerics;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
@@ -43,7 +44,7 @@ public sealed class ProgressColorSystem : EntitySystem
// lerp
var hue = 5f / 18f * progress;
return Color.FromHsv((hue, 1f, 0.75f, 1f));
return Color.FromHsv(new Vector4(hue, 1f, 0.75f, 1f));
}
return InterpolateColorGaussian(Plasma, progress);
@@ -614,7 +614,7 @@ public sealed class StorageWindow : BaseWindow
{
marked.Add(cell);
cell.ModulateSelfOverride = spotFree
? Color.FromHsv((0.18f, 1 / spot, 0.5f / spot + 0.5f, 1f))
? Color.FromHsv(new Vector4(0.18f, 1 / spot, 0.5f / spot + 0.5f, 1f))
: Color.FromHex("#2222CC");
}
}
+5 -1
View File
@@ -64,7 +64,11 @@ namespace Content.Client.Voting.UI
var entry = _vote.Entries[i];
if (_vote.DisplayVotes)
{
_voteButtons[i].Text = Loc.GetString("ui-vote-button", ("text", entry.Text), ("votes", entry.Votes));
// Mono
if (entry.RealVotes != entry.Votes)
_voteButtons[i].Text = Loc.GetString("ui-vote-button-weighted", ("text", entry.Text), ("votes", entry.Votes), ("realvotes", entry.RealVotes));
else
_voteButtons[i].Text = Loc.GetString("ui-vote-button", ("text", entry.Text), ("votes", entry.Votes));
}
else
{
+2
View File
@@ -192,6 +192,7 @@ namespace Content.Client.Voting
for (var i = 0; i < message.Options.Length; i++)
{
existingVote.Entries[i].Votes = message.Options[i].votes;
existingVote.Entries[i].RealVotes = message.Options[i].realVotes; // Mono
}
if (@new && _popupContainer != null)
@@ -259,6 +260,7 @@ namespace Content.Client.Voting
{
public string Text { get; }
public int Votes { get; set; }
public int RealVotes; // Mono
public VoteEntry(string text)
{
@@ -0,0 +1,5 @@
using Content.Shared._Mono.Claws;
namespace Content.Client._Mono.Claws;
public sealed class ClawsSystem : SharedClawsSystem;
@@ -32,6 +32,8 @@ public sealed class FireControlNavControl : ShuttleNavControl
private FireControllableEntry[]? _controllables;
private HashSet<NetEntity> _selectedWeapons = new();
private readonly Dictionary<NetEntity, Color> _blipColors = new();
// Add a limit to how often we update the cursor position to prevent network spam
private float _lastCursorUpdateTime = 0f;
private const float CursorUpdateInterval = 0.1f; // 10 updates per second
@@ -77,9 +79,9 @@ public sealed class FireControlNavControl : ShuttleNavControl
Matrix3x2.Invert(worldToView, out var viewToWorld);
var blips = _blips.GetCurrentBlips();
var colors = new Dictionary<NetEntity, Color>();
_blipColors.Clear();
foreach (var blip in blips)
colors[blip.NetUid] = blip.Config.Color;
_blipColors[blip.NetUid] = blip.Config.Color;
if (_controllables != null)
{
@@ -100,7 +102,7 @@ public sealed class FireControlNavControl : ShuttleNavControl
var results = _physics.IntersectRay(xform.MapID, ray, direction.Length(), ignoredEnt: _coordinates?.EntityId);
if (!results.Any() && colors.TryGetValue(controllable.NetEntity, out var color))
if (!results.Any() && _blipColors.TryGetValue(controllable.NetEntity, out var color))
handle.DrawLine(Vector2.Transform(worldPos, worldToView), cursorViewPos, color.WithAlpha(0.3f));
}
}
+26 -44
View File
@@ -13,13 +13,16 @@ public sealed partial class RadarBlipsSystem : EntitySystem
[Dependency] private readonly SharedTransformSystem _xform = default!;
private const double BlipStaleSeconds = 3.0;
private static readonly List<(Vector2 Start, Vector2 End, float Thickness, Color Color)> EmptyHitscanList = new();
private TimeSpan _lastRequestTime = TimeSpan.Zero;
private static readonly TimeSpan RequestThrottle = TimeSpan.FromMilliseconds(250);
private static readonly TimeSpan RequestThrottle = TimeSpan.FromMilliseconds(500);
private TimeSpan _lastUpdatedTime;
private List<BlipNetData> _blips = new();
private List<(Vector2 Start, Vector2 End, float Thickness, Color Color)> _hitscans = new();
private List<HitscanNetData> _hitscans = new();
private List<BlipConfig> _configPalette = new();
// cached results to avoid allocating on every draw/frame
private readonly List<BlipData> _cachedBlipData = new();
public override void Initialize()
{
@@ -30,24 +33,9 @@ public sealed partial class RadarBlipsSystem : EntitySystem
private void HandleReceiveBlips(GiveBlipsEvent ev, EntitySessionEventArgs args)
{
if (ev?.Blips == null)
{
_blips.Clear();
}
else
{
_blips = ev.Blips;
}
if (ev?.HitscanLines == null)
{
_hitscans = EmptyHitscanList;
}
else
{
_hitscans = ev.HitscanLines;
}
_configPalette = ev.ConfigPalette;
_blips = ev.Blips;
_hitscans = ev.HitscanLines;
_lastUpdatedTime = _timing.CurTime;
}
@@ -79,13 +67,12 @@ public sealed partial class RadarBlipsSystem : EntitySystem
/// </summary>
public List<BlipData> GetCurrentBlips()
{
// If it's been more than the stale threshold since our last update,
// the data is considered stale - return an empty list
// clear the cache and bail early if the data is stale
_cachedBlipData.Clear();
if (_timing.CurTime.TotalSeconds - _lastUpdatedTime.TotalSeconds > BlipStaleSeconds)
return new();
var result = new List<BlipData>(_blips.Count);
return _cachedBlipData;
// populate the cached list instead of allocating a new one each frame
foreach (var blip in _blips)
{
var coord = GetCoordinates(blip.Position);
@@ -97,37 +84,32 @@ public sealed partial class RadarBlipsSystem : EntitySystem
var predictedMap = _xform.ToMapCoordinates(predictedPos);
var config = blip.Config;
var config = _configPalette[blip.ConfigIndex];
var rotation = blip.Rotation;
// hijack our shape if we're on a grid and we want to do that
if (_map.TryFindGridAt(predictedMap, out var grid, out _) && grid != EntityUid.Invalid && blip.OnGridConfig != null)
config = blip.OnGridConfig.Value;
if (_map.TryFindGridAt(predictedMap, out var grid, out _) && grid != EntityUid.Invalid)
{
if (blip.OnGridConfigIndex is { } gridIdx)
config = _configPalette[gridIdx];
rotation += Transform(grid).LocalRotation;
}
var maybeGrid = grid != EntityUid.Invalid ? grid : (EntityUid?)null;
result.Add(new(blip.Uid, predictedPos, blip.Rotation, maybeGrid, config));
_cachedBlipData.Add(new(blip.Uid, predictedPos, rotation, maybeGrid, config));
}
return result;
return _cachedBlipData;
}
/// <summary>
/// Gets the hitscan lines to be rendered on the radar
/// </summary>
public List<(Vector2 Start, Vector2 End, float Thickness, Color Color)> GetHitscanLines()
public List<HitscanNetData> GetHitscanLines()
{
if (_timing.CurTime.TotalSeconds - _lastUpdatedTime.TotalSeconds > BlipStaleSeconds)
return new List<(Vector2, Vector2, float, Color)>();
return new();
var result = new List<(Vector2, Vector2, float, Color)>(_hitscans.Count);
foreach (var hitscan in _hitscans)
{
var worldStart = hitscan.Start;
var worldEnd = hitscan.End;
result.Add((worldStart, worldEnd, hitscan.Thickness, hitscan.Color));
}
return result;
return _hitscans;
}
}
@@ -126,6 +126,9 @@ public sealed partial class StationPickerControl : PickerControl
foreach (var (stationEntity, stationJobInformation) in stationList)
{
var icon = stationJobInformation.StationDisplayInfo?.StationIcon;
var iconTexture = icon != null ? _spriteSystem.Frame0(icon) : null;
var viewState = new StationListItem.ViewState(
stationEntity,
stationJobInformation.GetStationNameWithJobCount(),
@@ -136,7 +139,7 @@ public sealed partial class StationPickerControl : PickerControl
? _loc.GetString(stationJobInformation.StationDisplayInfo.StationDescription)
: "",
_lastSelectedStation?.StationEntity == stationEntity,
stationJobInformation.StationDisplayInfo?.StationIcon?.CanonPath
iconTexture
);
// Always select the first station in the list if none is selected yet.
@@ -1,4 +1,5 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -13,7 +14,7 @@ public sealed partial class StationListItem : PanelContainer
string stationSubtext,
string stationDescription,
bool selected,
string? iconPath)
Texture? icon)
{
public string StationName { get; } = stationName;
public string StationSubtext { get; } = stationSubtext;
@@ -22,7 +23,7 @@ public sealed partial class StationListItem : PanelContainer
public bool Selected { get; set; } = selected;
public string IconPath { get; } = iconPath ?? "";
public Texture? Icon { get; } = icon;
}
public StationListItem(ViewState state)
@@ -35,9 +36,9 @@ public sealed partial class StationListItem : PanelContainer
// Disallow repeated selection of same station that does UI reloads.
StationButton.Disabled = state.Selected;
if (state.IconPath.Length > 0)
if (state.Icon != null)
{
StationIcon.TexturePath = state.IconPath;
StationIcon.Texture = state.Icon;
}
}
}
@@ -22,7 +22,7 @@ public sealed class RMCCombatModeUISystem : EntitySystem
_hands.GetActiveHandEntity() is { } held &&
_rmcCombatMode.GetCrosshair(held) != null)
{
_crosshairCursor ??= _clyde.CreateCursor(new Image<Rgba32>(1, 1), Vector2i.One);
_crosshairCursor ??= _clyde.CreateCursor(new Image<Rgba32>(1, 1), Vector2i.Zero);
_clyde.SetCursor(_crosshairCursor);
}
else
@@ -1,12 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<OutputPath>..\bin\Content.IntegrationTests\</OutputPath>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<LangVersion>12</LangVersion>
<Nullable>disable</Nullable>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
@@ -17,12 +15,12 @@
<ProjectReference Include="..\Content.Server\Content.Server.csproj" />
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<Import Project="..\RobustToolbox\MSBuild\Robust.CompNetworkGenerator.targets" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Server.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<Import Project="..\RobustToolbox\Imports\Testing.props" />
</Project>
+1
View File
@@ -3,3 +3,4 @@
global using NUnit.Framework;
global using System;
global using System.Threading.Tasks;
global using Robust.UnitTesting.Pool;
@@ -1,23 +0,0 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Content.IntegrationTests.Pair;
/// <summary>
/// Simple data class that stored information about a map being used by a test.
/// </summary>
public sealed class TestMapData
{
public EntityUid MapUid { get; set; }
public Entity<MapGridComponent> Grid;
public MapId MapId;
public EntityCoordinates GridCoords { get; set; }
public MapCoordinates MapCoords { get; set; }
public TileRef Tile { get; set; }
// Client-side uids
public EntityUid CMapUid { get; set; }
public EntityUid CGridUid { get; set; }
public EntityCoordinates CGridCoords { get; set; }
}
@@ -1,69 +0,0 @@
#nullable enable
using System.Collections.Generic;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Pair;
public sealed partial class TestPair
{
private readonly Dictionary<string, object> _modifiedClientCvars = new();
private readonly Dictionary<string, object> _modifiedServerCvars = new();
private void OnServerCvarChanged(CVarChangeInfo args)
{
_modifiedServerCvars.TryAdd(args.Name, args.OldValue);
}
private void OnClientCvarChanged(CVarChangeInfo args)
{
_modifiedClientCvars.TryAdd(args.Name, args.OldValue);
}
internal void ClearModifiedCvars()
{
_modifiedClientCvars.Clear();
_modifiedServerCvars.Clear();
}
/// <summary>
/// Reverts any cvars that were modified during a test back to their original values.
/// </summary>
public async Task RevertModifiedCvars()
{
await Server.WaitPost(() =>
{
foreach (var (name, value) in _modifiedServerCvars)
{
if (Server.CfgMan.GetCVar(name).Equals(value))
continue;
Server.Log.Info($"Resetting cvar {name} to {value}");
Server.CfgMan.SetCVar(name, value);
}
// I just love order dependent cvars
if (_modifiedServerCvars.TryGetValue(CCVars.PanicBunkerEnabled.Name, out var panik))
Server.CfgMan.SetCVar(CCVars.PanicBunkerEnabled.Name, panik);
});
await Client.WaitPost(() =>
{
foreach (var (name, value) in _modifiedClientCvars)
{
if (Client.CfgMan.GetCVar(name).Equals(value))
continue;
var flags = Client.CfgMan.GetCVarFlags(name);
if (flags.HasFlag(CVar.REPLICATED) && flags.HasFlag(CVar.SERVER))
continue;
Client.Log.Info($"Resetting cvar {name} to {value}");
Client.CfgMan.SetCVar(name, value);
}
});
ClearModifiedCvars();
}
}
+30 -142
View File
@@ -1,180 +1,68 @@
#nullable enable
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Preferences.Managers;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.UnitTesting;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Pair;
// Contains misc helper functions to make writing tests easier.
public sealed partial class TestPair
{
public Task<TestMapData> CreateTestMap(bool initialized = true)
=> CreateTestMap(initialized, "Plating");
/// <summary>
/// Creates a map, a grid, and a tile, and gives back references to them.
/// Loads a test map and returns a <see cref="TestMapData"/> representing it.
/// </summary>
[MemberNotNull(nameof(TestMap))]
public async Task<TestMapData> CreateTestMap(bool initialized = true, string tile = "Plating")
/// <param name="testMapPath">The <see cref="ResPath"/> to the test map to load.</param>
/// <param name="initialized">Whether to initialize the map on load.</param>
/// <returns>A <see cref="TestMapData"/> representing the loaded map.</returns>
public async Task<TestMapData> LoadTestMap(ResPath testMapPath, bool initialized = true)
{
var mapData = new TestMapData();
TestMap = mapData;
await Server.WaitIdleAsync();
var tileDefinitionManager = Server.ResolveDependency<ITileDefinitionManager>();
TestMapData mapData = new();
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = initialized };
var mapLoaderSys = Server.EntMan.System<MapLoaderSystem>();
var mapSys = Server.System<SharedMapSystem>();
TestMap = mapData;
await Server.WaitPost(() =>
// Load our test map in and assert that it exists.
await Server.WaitAssertion(() =>
{
mapData.MapUid = Server.System<SharedMapSystem>().CreateMap(out mapData.MapId, runMapInit: initialized);
mapData.Grid = Server.MapMan.CreateGridEntity(mapData.MapId);
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
var plating = tileDefinitionManager[tile];
var platingTile = new Tile(plating.TileId);
Server.System<SharedMapSystem>().SetTile(mapData.Grid.Owner, mapData.Grid.Comp, mapData.GridCoords, platingTile);
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
mapData.Tile = Server.System<SharedMapSystem>().GetAllTiles(mapData.Grid.Owner, mapData.Grid.Comp).First();
});
Assert.That(mapLoaderSys.TryLoadMap(testMapPath, out var map, out var gridSet, deserializationOptions),
$"Failed to load map {testMapPath}.");
Assert.That(gridSet, Is.Not.Empty, "There were no grids loaded from the map!");
TestMap = mapData;
if (!Settings.Connected)
return mapData;
mapData.MapUid = map!.Value.Owner;
mapData.MapId = map!.Value.Comp.MapId;
mapData.Grid = gridSet!.First();
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
mapData.Tile = mapSys.GetAllTiles(mapData.Grid.Owner, mapData.Grid.Comp).First();
});
await RunTicksSync(10);
mapData.CMapUid = ToClientUid(mapData.MapUid);
mapData.CGridUid = ToClientUid(mapData.Grid);
mapData.CGridCoords = new EntityCoordinates(mapData.CGridUid, 0, 0);
TestMap = mapData;
return mapData;
}
/// <summary>
/// Convert a client-side uid into a server-side uid
/// </summary>
public EntityUid ToServerUid(EntityUid uid) => ConvertUid(uid, Client, Server);
/// <summary>
/// Convert a server-side uid into a client-side uid
/// </summary>
public EntityUid ToClientUid(EntityUid uid) => ConvertUid(uid, Server, Client);
private static EntityUid ConvertUid(
EntityUid uid,
RobustIntegrationTest.IntegrationInstance source,
RobustIntegrationTest.IntegrationInstance destination)
{
if (!uid.IsValid())
return EntityUid.Invalid;
if (!source.EntMan.TryGetComponent<MetaDataComponent>(uid, out var meta))
{
Assert.Fail($"Failed to resolve MetaData while converting the EntityUid for entity {uid}");
return EntityUid.Invalid;
}
if (!destination.EntMan.TryGetEntity(meta.NetEntity, out var otherUid))
{
Assert.Fail($"Failed to resolve net ID while converting the EntityUid entity {source.EntMan.ToPrettyString(uid)}");
return EntityUid.Invalid;
}
return otherUid.Value;
}
/// <summary>
/// Execute a command on the server and wait some number of ticks.
/// </summary>
public async Task WaitCommand(string cmd, int numTicks = 10)
{
await Server.ExecuteCommand(cmd);
await RunTicksSync(numTicks);
}
/// <summary>
/// Execute a command on the client and wait some number of ticks.
/// </summary>
public async Task WaitClientCommand(string cmd, int numTicks = 10)
{
await Client.ExecuteCommand(cmd);
await RunTicksSync(numTicks);
}
/// <summary>
/// Retrieve all entity prototypes that have some component.
/// </summary>
public List<(EntityPrototype, T)> GetPrototypesWithComponent<T>(
HashSet<string>? ignored = null,
bool ignoreAbstract = true,
bool ignoreTestPrototypes = true)
where T : IComponent, new()
{
if (!Server.ResolveDependency<IComponentFactory>().TryGetRegistration<T>(out var reg)
&& !Client.ResolveDependency<IComponentFactory>().TryGetRegistration<T>(out reg))
{
Assert.Fail($"Unknown component: {typeof(T).Name}");
return new();
}
var id = reg.Name;
var list = new List<(EntityPrototype, T)>();
foreach (var proto in Server.ProtoMan.EnumeratePrototypes<EntityPrototype>())
{
if (ignored != null && ignored.Contains(proto.ID))
continue;
if (ignoreAbstract && proto.Abstract)
continue;
if (ignoreTestPrototypes && IsTestPrototype(proto))
continue;
if (proto.Components.TryGetComponent(id, out var cmp))
list.Add((proto, (T)cmp));
}
return list;
}
/// <summary>
/// Retrieve all entity prototypes that have some component.
/// </summary>
public List<EntityPrototype> GetPrototypesWithComponent(Type type,
HashSet<string>? ignored = null,
bool ignoreAbstract = true,
bool ignoreTestPrototypes = true)
{
var id = Server.ResolveDependency<IComponentFactory>().GetComponentName(type);
var list = new List<EntityPrototype>();
foreach (var proto in Server.ProtoMan.EnumeratePrototypes<EntityPrototype>())
{
if (ignored != null && ignored.Contains(proto.ID))
continue;
if (ignoreAbstract && proto.Abstract)
continue;
if (ignoreTestPrototypes && IsTestPrototype(proto))
continue;
if (proto.Components.ContainsKey(id))
list.Add((proto));
}
return list;
}
/// <summary>
/// Set a user's antag preferences. Modified preferences are automatically reset at the end of the test.
/// </summary>
public async Task SetAntagPreference(ProtoId<AntagPrototype> id, bool value, NetUserId? user = null)
{
user ??= Client.User!.Value;
if (user is not {} userId)
if (user is not { } userId)
return;
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
@@ -183,7 +71,7 @@ public sealed partial class TestPair
// Automatic preference resetting only resets slot 0.
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
var profile = (HumanoidCharacterProfile)prefs.Characters[0];
var newProfile = profile.WithAntagPreference(id, value);
_modifiedProfiles.Add(userId);
await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait());
@@ -211,7 +99,7 @@ public sealed partial class TestPair
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
var prefs = prefMan.GetPreferences(user);
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
var profile = (HumanoidCharacterProfile)prefs.Characters[0];
var dictionary = new Dictionary<ProtoId<JobPrototype>, JobPriority>(profile.JobPriorities);
// Automatic preference resetting only resets slot 0.
@@ -1,64 +0,0 @@
#nullable enable
using System.Collections.Generic;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Robust.UnitTesting;
namespace Content.IntegrationTests.Pair;
// This partial class contains helper methods to deal with yaml prototypes.
public sealed partial class TestPair
{
private Dictionary<Type, HashSet<string>> _loadedPrototypes = new();
private HashSet<string> _loadedEntityPrototypes = new();
public async Task LoadPrototypes(List<string> prototypes)
{
await LoadPrototypes(Server, prototypes);
await LoadPrototypes(Client, prototypes);
}
private async Task LoadPrototypes(RobustIntegrationTest.IntegrationInstance instance, List<string> prototypes)
{
var changed = new Dictionary<Type, HashSet<string>>();
foreach (var file in prototypes)
{
instance.ProtoMan.LoadString(file, changed: changed);
}
await instance.WaitPost(() => instance.ProtoMan.ReloadPrototypes(changed));
foreach (var (kind, ids) in changed)
{
_loadedPrototypes.GetOrNew(kind).UnionWith(ids);
}
if (_loadedPrototypes.TryGetValue(typeof(EntityPrototype), out var entIds))
_loadedEntityPrototypes.UnionWith(entIds);
}
public bool IsTestPrototype(EntityPrototype proto)
{
return _loadedEntityPrototypes.Contains(proto.ID);
}
public bool IsTestEntityPrototype(string id)
{
return _loadedEntityPrototypes.Contains(id);
}
public bool IsTestPrototype<TPrototype>(string id) where TPrototype : IPrototype
{
return IsTestPrototype(typeof(TPrototype), id);
}
public bool IsTestPrototype<TPrototype>(TPrototype proto) where TPrototype : IPrototype
{
return IsTestPrototype(typeof(TPrototype), proto.ID);
}
public bool IsTestPrototype(Type kind, string id)
{
return _loadedPrototypes.TryGetValue(kind, out var ids) && ids.Contains(id);
}
}
+21 -156
View File
@@ -8,82 +8,17 @@ using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Preferences;
using Robust.Client;
using Robust.Server.Player;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Content.IntegrationTests.Pair;
// This partial class contains logic related to recycling & disposing test pairs.
public sealed partial class TestPair : IAsyncDisposable
public sealed partial class TestPair
{
public PairState State { get; private set; } = PairState.Ready;
private async Task OnDirtyDispose()
protected override async Task Cleanup()
{
var usageTime = Watch.Elapsed;
Watch.Restart();
await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Test gave back pair {Id} in {usageTime.TotalMilliseconds} ms");
Kill();
var disposeTime = Watch.Elapsed;
await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Disposed pair {Id} in {disposeTime.TotalMilliseconds} ms");
// Test pairs should only dirty dispose if they are failing. If they are not failing, this probably happened
// because someone forgot to clean-return the pair.
Assert.Warn("Test was dirty-disposed.");
}
private async Task OnCleanDispose()
{
await Server.WaitIdleAsync();
await Client.WaitIdleAsync();
await base.Cleanup();
await ResetModifiedPreferences();
await Server.RemoveAllDummySessions();
if (TestMap != null)
{
await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid));
TestMap = null;
}
await RevertModifiedCvars();
var usageTime = Watch.Elapsed;
Watch.Restart();
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Test borrowed pair {Id} for {usageTime.TotalMilliseconds} ms");
// Let any last minute failures the test cause happen.
await ReallyBeIdle();
if (!Settings.Destructive)
{
if (Client.IsAlive == false)
{
throw new Exception($"{nameof(CleanReturnAsync)}: Test killed the client in pair {Id}:", Client.UnhandledException);
}
if (Server.IsAlive == false)
{
throw new Exception($"{nameof(CleanReturnAsync)}: Test killed the server in pair {Id}:", Server.UnhandledException);
}
}
if (Settings.MustNotBeReused)
{
Kill();
await ReallyBeIdle();
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Clean disposed in {Watch.Elapsed.TotalMilliseconds} ms");
return;
}
var sRuntimeLog = Server.ResolveDependency<IRuntimeLog>();
if (sRuntimeLog.ExceptionCount > 0)
throw new Exception($"{nameof(CleanReturnAsync)}: Server logged exceptions");
var cRuntimeLog = Client.ResolveDependency<IRuntimeLog>();
if (cRuntimeLog.ExceptionCount > 0)
throw new Exception($"{nameof(CleanReturnAsync)}: Client logged exceptions");
var returnTime = Watch.Elapsed;
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
}
private async Task ResetModifiedPreferences()
@@ -93,61 +28,14 @@ public sealed partial class TestPair : IAsyncDisposable
{
await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait());
}
_modifiedProfiles.Clear();
}
public async ValueTask CleanReturnAsync()
protected override async Task Recycle(PairSettings next, TextWriter testOut)
{
if (State != PairState.InUse)
throw new Exception($"{nameof(CleanReturnAsync)}: Unexpected state. Pair: {Id}. State: {State}.");
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Return of pair {Id} started");
State = PairState.CleanDisposed;
await OnCleanDispose();
State = PairState.Ready;
PoolManager.NoCheckReturn(this);
ClearContext();
}
public async ValueTask DisposeAsync()
{
switch (State)
{
case PairState.Dead:
case PairState.Ready:
break;
case PairState.InUse:
await _testOut.WriteLineAsync($"{nameof(DisposeAsync)}: Dirty return of pair {Id} started");
await OnDirtyDispose();
PoolManager.NoCheckReturn(this);
ClearContext();
break;
default:
throw new Exception($"{nameof(DisposeAsync)}: Unexpected state. Pair: {Id}. State: {State}.");
}
}
public async Task CleanPooledPair(PoolSettings settings, TextWriter testOut)
{
Settings = default!;
Watch.Restart();
await testOut.WriteLineAsync($"Recycling...");
var gameTicker = Server.System<GameTicker>();
var cNetMgr = Client.ResolveDependency<IClientNetManager>();
await RunTicksSync(1);
// Disconnect the client if they are connected.
if (cNetMgr.IsConnected)
{
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Disconnecting client.");
await Client.WaitPost(() => cNetMgr.ClientDisconnect("Test pooling cleanup disconnect"));
await RunTicksSync(1);
}
Assert.That(cNetMgr.IsConnected, Is.False);
// Move to pre-round lobby. Required to toggle dummy ticker on and off
var gameTicker = Server.System<GameTicker>();
if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby)
{
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting round.");
@@ -160,8 +48,7 @@ public sealed partial class TestPair : IAsyncDisposable
//Apply Cvars
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Setting CVar ");
await PoolManager.SetupCVars(Client, settings);
await PoolManager.SetupCVars(Server, settings);
await ApplySettings(next);
await RunTicksSync(1);
// Restart server.
@@ -169,52 +56,30 @@ public sealed partial class TestPair : IAsyncDisposable
await Server.WaitPost(() => Server.EntMan.FlushEntities());
await Server.WaitPost(() => gameTicker.RestartRound());
await RunTicksSync(1);
// Connect client
if (settings.ShouldBeConnected)
{
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Connecting client");
Client.SetConnectTarget(Server);
await Client.WaitPost(() => cNetMgr.ClientConnect(null!, 0, null!));
}
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Idling");
await ReallyBeIdle();
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Done recycling");
}
public void ValidateSettings(PoolSettings settings)
public override void ValidateSettings(PairSettings s)
{
base.ValidateSettings(s);
var settings = (PoolSettings) s;
var cfg = Server.CfgMan;
Assert.That(cfg.GetCVar(CCVars.AdminLogsEnabled), Is.EqualTo(settings.AdminLogsEnabled));
Assert.That(cfg.GetCVar(CCVars.GameLobbyEnabled), Is.EqualTo(settings.InLobby));
Assert.That(cfg.GetCVar(CCVars.GameDummyTicker), Is.EqualTo(settings.UseDummyTicker));
Assert.That(cfg.GetCVar(CCVars.GameDummyTicker), Is.EqualTo(settings.DummyTicker));
var entMan = Server.ResolveDependency<EntityManager>();
var ticker = entMan.System<GameTicker>();
Assert.That(ticker.DummyTicker, Is.EqualTo(settings.UseDummyTicker));
var ticker = Server.System<GameTicker>();
Assert.That(ticker.DummyTicker, Is.EqualTo(settings.DummyTicker));
var expectPreRound = settings.InLobby | settings.DummyTicker;
var expectedLevel = expectPreRound ? GameRunLevel.PreRoundLobby : GameRunLevel.InRound;
Assert.That(ticker.RunLevel, Is.EqualTo(expectedLevel));
var baseClient = Client.ResolveDependency<IBaseClient>();
var netMan = Client.ResolveDependency<INetManager>();
Assert.That(netMan.IsConnected, Is.Not.EqualTo(!settings.ShouldBeConnected));
if (!settings.ShouldBeConnected)
if (ticker.DummyTicker || !settings.Connected)
return;
Assert.That(baseClient.RunLevel, Is.EqualTo(ClientRunLevel.InGame));
var cPlayer = Client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
var sPlayer = Server.ResolveDependency<IPlayerManager>();
Assert.That(sPlayer.Sessions.Count(), Is.EqualTo(1));
var sPlayer = Server.ResolveDependency<ISharedPlayerManager>();
var session = sPlayer.Sessions.Single();
Assert.That(cPlayer.LocalSession?.UserId, Is.EqualTo(session.UserId));
if (ticker.DummyTicker)
return;
var status = ticker.PlayerGameStatuses[session.UserId];
var expected = settings.InLobby
? PlayerGameStatus.NotReadyToPlay
@@ -229,11 +94,11 @@ public sealed partial class TestPair : IAsyncDisposable
}
Assert.That(session.AttachedEntity, Is.Not.Null);
Assert.That(entMan.EntityExists(session.AttachedEntity));
Assert.That(entMan.HasComponent<MindContainerComponent>(session.AttachedEntity));
var mindCont = entMan.GetComponent<MindContainerComponent>(session.AttachedEntity!.Value);
Assert.That(Server.EntMan.EntityExists(session.AttachedEntity));
Assert.That(Server.EntMan.HasComponent<MindContainerComponent>(session.AttachedEntity));
var mindCont = Server.EntMan.GetComponent<MindContainerComponent>(session.AttachedEntity!.Value);
Assert.That(mindCont.Mind, Is.Not.Null);
Assert.That(entMan.TryGetComponent(mindCont.Mind, out MindComponent? mind));
Assert.That(Server.EntMan.TryGetComponent(mindCont.Mind, out MindComponent? mind));
Assert.That(mind!.VisitingEntity, Is.Null);
Assert.That(mind.OwnedEntity, Is.EqualTo(session.AttachedEntity!.Value));
Assert.That(mind.UserId, Is.EqualTo(session.UserId));
@@ -1,77 +0,0 @@
#nullable enable
namespace Content.IntegrationTests.Pair;
// This partial class contains methods for running the server/client pairs for some number of ticks
public sealed partial class TestPair
{
/// <summary>
/// Runs the server-client pair in sync
/// </summary>
/// <param name="ticks">How many ticks to run them for</param>
public async Task RunTicksSync(int ticks)
{
for (var i = 0; i < ticks; i++)
{
await Server.WaitRunTicks(1);
await Client.WaitRunTicks(1);
}
}
/// <summary>
/// Convert a time interval to some number of ticks.
/// </summary>
public int SecondsToTicks(float seconds)
{
return (int) Math.Ceiling(seconds / Server.Timing.TickPeriod.TotalSeconds);
}
/// <summary>
/// Run the server & client in sync for some amount of time
/// </summary>
public async Task RunSeconds(float seconds)
{
await RunTicksSync(SecondsToTicks(seconds));
}
/// <summary>
/// Runs the server-client pair in sync, but also ensures they are both idle each tick.
/// </summary>
/// <param name="runTicks">How many ticks to run</param>
public async Task ReallyBeIdle(int runTicks = 25)
{
for (var i = 0; i < runTicks; i++)
{
await Client.WaitRunTicks(1);
await Server.WaitRunTicks(1);
for (var idleCycles = 0; idleCycles < 4; idleCycles++)
{
await Client.WaitIdleAsync();
await Server.WaitIdleAsync();
}
}
}
/// <summary>
/// Run the server/clients until the ticks are synchronized.
/// By default the client will be one tick ahead of the server.
/// </summary>
public async Task SyncTicks(int targetDelta = 1)
{
var sTick = (int)Server.Timing.CurTick.Value;
var cTick = (int)Client.Timing.CurTick.Value;
var delta = cTick - sTick;
if (delta == targetDelta)
return;
if (delta > targetDelta)
await Server.WaitRunTicks(delta - targetDelta);
else
await Client.WaitRunTicks(targetDelta - delta);
sTick = (int)Server.Timing.CurTick.Value;
cTick = (int)Client.Timing.CurTick.Value;
delta = cTick - sTick;
Assert.That(delta, Is.EqualTo(targetDelta));
}
}
+88 -134
View File
@@ -1,16 +1,17 @@
#nullable enable
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Content.Client.IoC;
using Content.Client.Parallax.Managers;
using Content.IntegrationTests.Tests.Destructible;
using Content.IntegrationTests.Tests.DeviceNetwork;
using Content.Server.GameTicking;
using Content.Shared.CCVar;
using Content.Shared.Players;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.UnitTesting;
namespace Content.IntegrationTests.Pair;
@@ -18,156 +19,109 @@ namespace Content.IntegrationTests.Pair;
/// <summary>
/// This object wraps a pooled server+client pair.
/// </summary>
public sealed partial class TestPair
public sealed partial class TestPair : RobustIntegrationTest.TestPair
{
public readonly int Id;
private bool _initialized;
private TextWriter _testOut = default!;
public readonly Stopwatch Watch = new();
public readonly List<string> TestHistory = new();
public PoolSettings Settings = default!;
public TestMapData? TestMap;
private List<NetUserId> _modifiedProfiles = new();
private int _nextServerSeed;
private int _nextClientSeed;
public int ServerSeed;
public int ClientSeed;
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
public void Deconstruct(
out RobustIntegrationTest.ServerIntegrationInstance server,
out RobustIntegrationTest.ClientIntegrationInstance client)
{
server = Server;
client = Client;
}
public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value);
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
public PoolTestLogHandler ServerLogHandler { get; private set; } = default!;
public PoolTestLogHandler ClientLogHandler { get; private set; } = default!;
public TestPair(int id)
protected override async Task Initialize()
{
Id = id;
}
await base.Initialize();
public async Task Initialize(PoolSettings settings, TextWriter testOut, List<string> testPrototypes)
{
if (_initialized)
throw new InvalidOperationException("Already initialized");
_initialized = true;
Settings = settings;
(Client, ClientLogHandler) = await PoolManager.GenerateClient(settings, testOut);
(Server, ServerLogHandler) = await PoolManager.GenerateServer(settings, testOut);
ActivateContext(testOut);
Client.CfgMan.OnCVarValueChanged += OnClientCvarChanged;
Server.CfgMan.OnCVarValueChanged += OnServerCvarChanged;
if (!settings.NoLoadTestPrototypes)
await LoadPrototypes(testPrototypes!);
if (!settings.UseDummyTicker)
// Prevent info log spam in some tests (particularly SpawnAndDeleteAllEntitiesOnDifferentMaps)
Server.System<SharedMapSystem>().Log.Level = LogLevel.Warning;
Client.EntMan.EntitySysManager.SystemLoaded += (_, e) =>
{
var gameTicker = Server.ResolveDependency<IEntityManager>().System<GameTicker>();
if (e.System is SharedMapSystem map)
map.Log.Level = LogLevel.Warning;
};
var settings = (PoolSettings)Settings;
if (!settings.DummyTicker)
{
var gameTicker = Server.System<GameTicker>();
await Server.WaitPost(() => gameTicker.RestartRound());
}
}
// Always initially connect clients to generate an initial random set of preferences/profiles.
// This is to try and prevent issues where if the first test that connects the client is consistently some test
// that uses a fixed seed, it would effectively prevent it from beingrandomized.
public override async Task RevertModifiedCvars()
{
// I just love order dependent cvars
// I.e., cvars that when changed automatically cause others to also change.
var modified = ModifiedServerCvars.TryGetValue(CCVars.PanicBunkerEnabled.Name, out var panik);
Client.SetConnectTarget(Server);
await Client.WaitIdleAsync();
var netMgr = Client.ResolveDependency<IClientNetManager>();
await Client.WaitPost(() => netMgr.ClientConnect(null!, 0, null!));
await ReallyBeIdle(10);
await Client.WaitRunTicks(1);
await base.RevertModifiedCvars();
if (!settings.ShouldBeConnected)
if (!modified)
return;
await Server.WaitPost(() => Server.CfgMan.SetCVar(CCVars.PanicBunkerEnabled.Name, panik!));
ClearModifiedCvars();
}
protected override async Task ApplySettings(IIntegrationInstance instance, PairSettings n)
{
var next = (PoolSettings)n;
await base.ApplySettings(instance, next);
var cfg = instance.CfgMan;
await instance.WaitPost(() =>
{
await Client.WaitPost(() => netMgr.ClientDisconnect("Initial disconnect"));
await ReallyBeIdle(10);
}
if (cfg.IsCVarRegistered(CCVars.GameDummyTicker.Name))
cfg.SetCVar(CCVars.GameDummyTicker, next.DummyTicker);
var cRand = Client.ResolveDependency<IRobustRandom>();
var sRand = Server.ResolveDependency<IRobustRandom>();
_nextClientSeed = cRand.Next();
_nextServerSeed = sRand.Next();
if (cfg.IsCVarRegistered(CCVars.GameLobbyEnabled.Name))
cfg.SetCVar(CCVars.GameLobbyEnabled, next.InLobby);
if (cfg.IsCVarRegistered(CCVars.GameMap.Name))
cfg.SetCVar(CCVars.GameMap, next.Map);
if (cfg.IsCVarRegistered(CCVars.AdminLogsEnabled.Name))
cfg.SetCVar(CCVars.AdminLogsEnabled, next.AdminLogsEnabled);
});
}
public void Kill()
protected override RobustIntegrationTest.ClientIntegrationOptions ClientOptions()
{
State = PairState.Dead;
ServerLogHandler.ShuttingDown = true;
ClientLogHandler.ShuttingDown = true;
Server.Dispose();
Client.Dispose();
}
var opts = base.ClientOptions();
private void ClearContext()
{
_testOut = default!;
ServerLogHandler.ClearContext();
ClientLogHandler.ClearContext();
}
public void ActivateContext(TextWriter testOut)
{
_testOut = testOut;
ServerLogHandler.ActivateContext(testOut);
ClientLogHandler.ActivateContext(testOut);
}
public void Use()
{
if (State != PairState.Ready)
throw new InvalidOperationException($"Pair is not ready to use. State: {State}");
State = PairState.InUse;
}
public enum PairState : byte
{
Ready = 0,
InUse = 1,
CleanDisposed = 2,
Dead = 3,
}
public void SetupSeed()
{
var sRand = Server.ResolveDependency<IRobustRandom>();
if (Settings.ServerSeed is { } severSeed)
opts.LoadTestAssembly = false;
opts.ContentStart = true;
opts.FailureLogLevel = LogLevel.Warning;
opts.Options = new()
{
ServerSeed = severSeed;
sRand.SetSeed(ServerSeed);
}
else
{
ServerSeed = _nextServerSeed;
sRand.SetSeed(ServerSeed);
_nextServerSeed = sRand.Next();
}
LoadConfigAndUserData = false,
};
var cRand = Client.ResolveDependency<IRobustRandom>();
if (Settings.ClientSeed is { } clientSeed)
opts.BeforeStart += () =>
{
ClientSeed = clientSeed;
cRand.SetSeed(ClientSeed);
}
else
IoCManager.Resolve<IModLoader>().SetModuleBaseCallbacks(new ClientModuleTestingCallbacks
{
ClientBeforeIoC = () => IoCManager.Register<IParallaxManager, DummyParallaxManager>(true)
});
};
return opts;
}
protected override RobustIntegrationTest.ServerIntegrationOptions ServerOptions()
{
var opts = base.ServerOptions();
opts.LoadTestAssembly = false;
opts.ContentStart = true;
opts.Options = new()
{
ClientSeed = _nextClientSeed;
cRand.SetSeed(ClientSeed);
_nextClientSeed = cRand.Next();
}
LoadConfigAndUserData = false,
};
opts.BeforeStart += () =>
{
// Server-only systems (i.e., systems that subscribe to events with server-only components)
// There's probably a better way to do this.
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
entSysMan.LoadExtraSystemType<DeviceNetworkTestSystem>();
entSysMan.LoadExtraSystemType<TestDestructibleListenerSystem>();
};
return opts;
}
}
+5 -47
View File
@@ -1,15 +1,14 @@
#nullable enable
using Content.Shared.CCVar;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.UnitTesting;
namespace Content.IntegrationTests;
// Partial class containing cvar logic
// Partial class containing test cvars
// This could probably be merged into the main file, but I'm keeping it separate to reduce
// conflicts for forks.
public static partial class PoolManager
{
private static readonly (string cvar, string value)[] TestCvars =
public static readonly (string cvar, string value)[] TestCvars =
{
// @formatter:off
(CCVars.DatabaseSynchronous.Name, "true"),
@@ -17,9 +16,7 @@ public static partial class PoolManager
(CCVars.HolidaysEnabled.Name, "false"),
(CCVars.GameMap.Name, TestMap),
(CCVars.AdminLogsQueueSendDelay.Name, "0"),
(CVars.NetPVS.Name, "false"),
(CCVars.NPCMaxUpdates.Name, "999999"),
(CVars.ThreadParallelCount.Name, "1"),
(CCVars.GameRoleTimers.Name, "false"),
(CCVars.GameRoleWhitelist.Name, "false"),
(CCVars.GridFill.Name, "false"),
@@ -29,55 +26,16 @@ public static partial class PoolManager
(CCVars.ProcgenPreload.Name, "false"),
(CCVars.WorldgenEnabled.Name, "false"),
(CCVars.GatewayGeneratorEnabled.Name, "false"),
(CVars.ReplayClientRecordingEnabled.Name, "false"),
(CVars.ReplayServerRecordingEnabled.Name, "false"),
(CCVars.GameDummyTicker.Name, "true"),
(CCVars.GameLobbyEnabled.Name, "false"),
(CCVars.ConfigPresetDevelopment.Name, "false"),
(CCVars.AdminLogsEnabled.Name, "false"),
(CCVars.AutosaveEnabled.Name, "false"),
(CVars.NetBufferSize.Name, "0"),
(CCVars.InteractionRateLimitCount.Name, "9999999"),
(CCVars.InteractionRateLimitPeriod.Name, "0.1"),
(CCVars.MovementMobPushing.Name, "false"),
(CCVars.GameLobbyDefaultPreset.Name, "secret"), // Frontier: Adventure takes ages, default to secret
(CCVars.StaticStorageUI.Name, "true"),// Frontier: causes storage test failures
(CCVars.StorageLimit.Name, "1")// Frontier: test failures with multiple storage enabled
(CCVars.StorageLimit.Name, "1")// Frontier: test failures with multiple storage
};
public static async Task SetupCVars(RobustIntegrationTest.IntegrationInstance instance, PoolSettings settings)
{
var cfg = instance.ResolveDependency<IConfigurationManager>();
await instance.WaitPost(() =>
{
if (cfg.IsCVarRegistered(CCVars.GameDummyTicker.Name))
cfg.SetCVar(CCVars.GameDummyTicker, settings.UseDummyTicker);
if (cfg.IsCVarRegistered(CCVars.GameLobbyEnabled.Name))
cfg.SetCVar(CCVars.GameLobbyEnabled, settings.InLobby);
if (cfg.IsCVarRegistered(CVars.NetInterp.Name))
cfg.SetCVar(CVars.NetInterp, settings.DisableInterpolate);
if (cfg.IsCVarRegistered(CCVars.GameMap.Name))
cfg.SetCVar(CCVars.GameMap, settings.Map);
if (cfg.IsCVarRegistered(CCVars.AdminLogsEnabled.Name))
cfg.SetCVar(CCVars.AdminLogsEnabled, settings.AdminLogsEnabled);
if (cfg.IsCVarRegistered(CVars.NetInterp.Name))
cfg.SetCVar(CVars.NetInterp, !settings.DisableInterpolate);
if (cfg.IsCVarRegistered(CCVars.GameLobbyDefaultPreset.Name) && !string.IsNullOrEmpty(settings.GameLobbyDefaultPreset)) // Frontier
cfg.SetCVar(CCVars.GameLobbyDefaultPreset, settings.GameLobbyDefaultPreset); // Frontier
});
}
private static void SetDefaultCVars(RobustIntegrationTest.IntegrationOptions options)
{
foreach (var (cvar, value) in TestCvars)
{
options.CVarOverrides[cvar] = value;
}
}
}
+37 -375
View File
@@ -1,368 +1,17 @@
#nullable enable
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Content.Client.IoC;
using Content.Client.Parallax.Managers;
using Content.IntegrationTests.Pair;
using Content.IntegrationTests.Tests;
using Content.IntegrationTests.Tests.Destructible;
using Content.IntegrationTests.Tests.DeviceNetwork;
using Content.IntegrationTests.Tests.Interaction.Click;
using Robust.Client;
using Robust.Server;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Content.Shared.CCVar;
using Robust.UnitTesting;
namespace Content.IntegrationTests;
/// <summary>
/// Making clients, and servers is slow, this manages a pool of them so tests can reuse them.
/// </summary>
// The static class exist to avoid breaking changes
public static partial class PoolManager
{
public static readonly ContentPoolManager Instance = new();
public const string TestMap = "Empty";
private static int _pairId;
private static readonly object PairLock = new();
private static bool _initialized;
// Pair, IsBorrowed
private static readonly Dictionary<TestPair, bool> Pairs = new();
private static bool _dead;
private static Exception? _poolFailureReason;
private static HashSet<Assembly> _contentAssemblies = default!;
public static async Task<(RobustIntegrationTest.ServerIntegrationInstance, PoolTestLogHandler)> GenerateServer(
PoolSettings poolSettings,
TextWriter testOut)
{
var options = new RobustIntegrationTest.ServerIntegrationOptions
{
ContentStart = true,
Options = new ServerOptions()
{
LoadConfigAndUserData = false,
LoadContentResources = !poolSettings.NoLoadContent,
},
ContentAssemblies = _contentAssemblies.ToArray()
};
var logHandler = new PoolTestLogHandler("SERVER");
logHandler.ActivateContext(testOut);
options.OverrideLogHandler = () => logHandler;
options.BeforeStart += () =>
{
// Server-only systems (i.e., systems that subscribe to events with server-only components)
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
entSysMan.LoadExtraSystemType<DeviceNetworkTestSystem>();
entSysMan.LoadExtraSystemType<TestDestructibleListenerSystem>();
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
IoCManager.Resolve<IConfigurationManager>()
.OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true);
};
SetDefaultCVars(options);
var server = new RobustIntegrationTest.ServerIntegrationInstance(options);
await server.WaitIdleAsync();
await SetupCVars(server, poolSettings);
return (server, logHandler);
}
/// <summary>
/// This shuts down the pool, and disposes all the server/client pairs.
/// This is a one time operation to be used when the testing program is exiting.
/// </summary>
public static void Shutdown()
{
List<TestPair> localPairs;
lock (PairLock)
{
if (_dead)
return;
_dead = true;
localPairs = Pairs.Keys.ToList();
}
foreach (var pair in localPairs)
{
pair.Kill();
}
_initialized = false;
}
public static string DeathReport()
{
lock (PairLock)
{
var builder = new StringBuilder();
var pairs = Pairs.Keys.OrderBy(pair => pair.Id);
foreach (var pair in pairs)
{
var borrowed = Pairs[pair];
builder.AppendLine($"Pair {pair.Id}, Tests Run: {pair.TestHistory.Count}, Borrowed: {borrowed}");
for (var i = 0; i < pair.TestHistory.Count; i++)
{
builder.AppendLine($"#{i}: {pair.TestHistory[i]}");
}
}
return builder.ToString();
}
}
public static async Task<(RobustIntegrationTest.ClientIntegrationInstance, PoolTestLogHandler)> GenerateClient(
PoolSettings poolSettings,
TextWriter testOut)
{
var options = new RobustIntegrationTest.ClientIntegrationOptions
{
FailureLogLevel = LogLevel.Warning,
ContentStart = true,
ContentAssemblies = new[]
{
typeof(Shared.Entry.EntryPoint).Assembly,
typeof(Client.Entry.EntryPoint).Assembly,
typeof(PoolManager).Assembly,
}
};
if (poolSettings.NoLoadContent)
{
Assert.Warn("NoLoadContent does not work on the client, ignoring");
}
options.Options = new GameControllerOptions()
{
LoadConfigAndUserData = false,
// LoadContentResources = !poolSettings.NoLoadContent
};
var logHandler = new PoolTestLogHandler("CLIENT");
logHandler.ActivateContext(testOut);
options.OverrideLogHandler = () => logHandler;
options.BeforeStart += () =>
{
IoCManager.Resolve<IModLoader>().SetModuleBaseCallbacks(new ClientModuleTestingCallbacks
{
ClientBeforeIoC = () =>
{
// do not register extra systems or components here -- they will get cleared when the client is
// disconnected. just use reflection.
IoCManager.Register<IParallaxManager, DummyParallaxManager>(true);
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
IoCManager.Resolve<IConfigurationManager>()
.OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true);
}
});
};
SetDefaultCVars(options);
var client = new RobustIntegrationTest.ClientIntegrationInstance(options);
await client.WaitIdleAsync();
await SetupCVars(client, poolSettings);
return (client, logHandler);
}
/// <summary>
/// Gets a <see cref="Pair.TestPair"/>, which can be used to get access to a server, and client <see cref="Pair.TestPair"/>
/// </summary>
/// <param name="poolSettings">See <see cref="PoolSettings"/></param>
/// <returns></returns>
public static async Task<TestPair> GetServerClient(PoolSettings? poolSettings = null)
{
return await GetServerClientPair(poolSettings ?? new PoolSettings());
}
private static string GetDefaultTestName(TestContext testContext)
{
return testContext.Test.FullName.Replace("Content.IntegrationTests.Tests.", "");
}
private static async Task<TestPair> GetServerClientPair(PoolSettings poolSettings)
{
if (!_initialized)
throw new InvalidOperationException($"Pool manager has not been initialized");
// Trust issues with the AsyncLocal that backs this.
var testContext = TestContext.CurrentContext;
var testOut = TestContext.Out;
DieIfPoolFailure();
var currentTestName = poolSettings.TestName ?? GetDefaultTestName(testContext);
var poolRetrieveTimeWatch = new Stopwatch();
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Called by test {currentTestName}");
TestPair? pair = null;
try
{
poolRetrieveTimeWatch.Start();
if (poolSettings.MustBeNew)
{
await testOut.WriteLineAsync(
$"{nameof(GetServerClientPair)}: Creating pair, because settings of pool settings");
pair = await CreateServerClientPair(poolSettings, testOut);
}
else
{
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Looking in pool for a suitable pair");
pair = GrabOptimalPair(poolSettings);
if (pair != null)
{
pair.ActivateContext(testOut);
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Suitable pair found");
var canSkip = pair.Settings.CanFastRecycle(poolSettings);
if (canSkip)
{
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Cleanup not needed, Skipping cleanup of pair");
await SetupCVars(pair.Client, poolSettings);
await SetupCVars(pair.Server, poolSettings);
await pair.RunTicksSync(1);
}
else
{
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Cleaning existing pair");
await pair.CleanPooledPair(poolSettings, testOut);
}
await pair.RunTicksSync(5);
await pair.SyncTicks(targetDelta: 1);
}
else
{
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Creating a new pair, no suitable pair found in pool");
pair = await CreateServerClientPair(poolSettings, testOut);
}
}
}
finally
{
if (pair != null && pair.TestHistory.Count > 0)
{
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Pair {pair.Id} Test History Start");
for (var i = 0; i < pair.TestHistory.Count; i++)
{
await testOut.WriteLineAsync($"- Pair {pair.Id} Test #{i}: {pair.TestHistory[i]}");
}
await testOut.WriteLineAsync($"{nameof(GetServerClientPair)}: Pair {pair.Id} Test History End");
}
}
pair.ValidateSettings(poolSettings);
var poolRetrieveTime = poolRetrieveTimeWatch.Elapsed;
await testOut.WriteLineAsync(
$"{nameof(GetServerClientPair)}: Retrieving pair {pair.Id} from pool took {poolRetrieveTime.TotalMilliseconds} ms");
pair.ClearModifiedCvars();
pair.Settings = poolSettings;
pair.TestHistory.Add(currentTestName);
pair.SetupSeed();
await testOut.WriteLineAsync(
$"{nameof(GetServerClientPair)}: Returning pair {pair.Id} with client/server seeds: {pair.ClientSeed}/{pair.ServerSeed}");
pair.Watch.Restart();
return pair;
}
private static TestPair? GrabOptimalPair(PoolSettings poolSettings)
{
lock (PairLock)
{
TestPair? fallback = null;
foreach (var pair in Pairs.Keys)
{
if (Pairs[pair])
continue;
if (!pair.Settings.CanFastRecycle(poolSettings))
{
fallback = pair;
continue;
}
pair.Use();
Pairs[pair] = true;
return pair;
}
if (fallback != null)
{
fallback.Use();
Pairs[fallback!] = true;
}
return fallback;
}
}
/// <summary>
/// Used by TestPair after checking the server/client pair, Don't use this.
/// </summary>
public static void NoCheckReturn(TestPair pair)
{
lock (PairLock)
{
if (pair.State == TestPair.PairState.Dead)
Pairs.Remove(pair);
else if (pair.State == TestPair.PairState.Ready)
Pairs[pair] = false;
else
throw new InvalidOperationException($"Attempted to return a pair in an invalid state. Pair: {pair.Id}. State: {pair.State}.");
}
}
private static void DieIfPoolFailure()
{
if (_poolFailureReason != null)
{
// If the _poolFailureReason is not null, we can assume at least one test failed.
// So we say inconclusive so we don't add more failed tests to search through.
Assert.Inconclusive(@$"
In a different test, the pool manager had an exception when trying to create a server/client pair.
Instead of risking that the pool manager will fail at creating a server/client pairs for every single test,
we are just going to end this here to save a lot of time. This is the exception that started this:\n {_poolFailureReason}");
}
if (_dead)
{
// If Pairs is null, we ran out of time, we can't assume a test failed.
// So we are going to tell it all future tests are a failure.
Assert.Fail("The pool was shut down");
}
}
private static async Task<TestPair> CreateServerClientPair(PoolSettings poolSettings, TextWriter testOut)
{
try
{
var id = Interlocked.Increment(ref _pairId);
var pair = new TestPair(id);
await pair.Initialize(poolSettings, testOut, _testPrototypes);
pair.Use();
await pair.RunTicksSync(5);
await pair.SyncTicks(targetDelta: 1);
return pair;
}
catch (Exception ex)
{
_poolFailureReason = ex;
throw;
}
}
/// <summary>
/// Runs a server, or a client until a condition is true
@@ -418,29 +67,42 @@ we are just going to end this here to save a lot of time. This is the exception
Assert.That(passed);
}
/// <summary>
/// Initialize the pool manager.
/// </summary>
/// <param name="extraAssemblies">Assemblies to search for to discover extra prototypes and systems.</param>
public static void Startup(params Assembly[] extraAssemblies)
public static async Task<TestPair> GetServerClient(
PoolSettings? settings = null,
ITestContextLike? testContext = null)
{
if (_initialized)
throw new InvalidOperationException("Already initialized");
return await Instance.GetPair(settings, testContext);
}
_initialized = true;
_contentAssemblies =
[
typeof(Shared.Entry.EntryPoint).Assembly,
typeof(Server.Entry.EntryPoint).Assembly,
typeof(PoolManager).Assembly
];
_contentAssemblies.UnionWith(extraAssemblies);
public static void Startup(params Assembly[] extra)
=> Instance.Startup(extra);
_testPrototypes.Clear();
DiscoverTestPrototypes(typeof(PoolManager).Assembly);
foreach (var assembly in extraAssemblies)
{
DiscoverTestPrototypes(assembly);
}
public static void Shutdown() => Instance.Shutdown();
public static string DeathReport() => Instance.DeathReport();
}
/// <summary>
/// Making clients, and servers is slow, this manages a pool of them so tests can reuse them.
/// </summary>
public sealed class ContentPoolManager : PoolManager<TestPair>
{
public override PairSettings DefaultSettings => new PoolSettings();
protected override string GetDefaultTestName(ITestContextLike testContext)
{
return testContext.FullName.Replace("Content.IntegrationTests.Tests.", "");
}
public override void Startup(params Assembly[] extraAssemblies)
{
DefaultCvars.AddRange(PoolManager.TestCvars);
var shared = extraAssemblies
.Append(typeof(Shared.Entry.EntryPoint).Assembly)
.Append(typeof(PoolManager).Assembly)
.ToArray();
Startup([typeof(Client.Entry.EntryPoint).Assembly],
[typeof(Server.Entry.EntryPoint).Assembly],
shared);
}
}
+22 -101
View File
@@ -1,43 +1,31 @@
#nullable enable
namespace Content.IntegrationTests;
using Robust.Shared.Random;
namespace Content.IntegrationTests;
/// <summary>
/// Settings for the pooled server, and client pair.
/// Some options are for changing the pair, and others are
/// so the pool can properly clean up what you borrowed.
/// </summary>
public sealed class PoolSettings
/// <inheritdoc/>
public sealed class PoolSettings : PairSettings
{
/// <summary>
/// Set to true if the test will ruin the server/client pair.
/// </summary>
public bool Destructive { get; init; }
public override bool Connected
{
get => _connected || InLobby;
init => _connected = value;
}
/// <summary>
/// Set to true if the given server/client pair should be created fresh.
/// </summary>
public bool Fresh { get; init; }
private readonly bool _dummyTicker = true;
private readonly bool _connected;
/// <summary>
/// Set to true if the given server should be using a dummy ticker. Ignored if <see cref="InLobby"/> is true.
/// </summary>
public bool DummyTicker { get; init; } = true;
public bool DummyTicker
{
get => _dummyTicker && !InLobby;
init => _dummyTicker = value;
}
/// <summary>
/// If true, this enables the creation of admin logs during the test.
/// </summary>
public bool AdminLogsEnabled { get; init; }
/// <summary>
/// Set to true if the given server/client pair should be connected from each other.
/// Defaults to disconnected as it makes dirty recycling slightly faster.
/// If <see cref="InLobby"/> is true, this option is ignored.
/// </summary>
public bool Connected { get; init; }
/// <summary>
/// Set to true if the given server/client pair should be in the lobby.
/// If the pair is not in the lobby at the end of the test, this test must be marked as dirty.
@@ -53,89 +41,22 @@ public sealed class PoolSettings
/// </summary>
public bool NoLoadContent { get; init; }
/// <summary>
/// This will return a server-client pair that has not loaded test prototypes.
/// Try avoiding this whenever possible, as this will always create & destroy a new pair.
/// Use <see cref="Pair.TestPair.IsTestPrototype(Robust.Shared.Prototypes.EntityPrototype)"/> if you need to exclude test prototypees.
/// </summary>
public bool NoLoadTestPrototypes { get; init; }
/// <summary>
/// Set this to true to disable the NetInterp CVar on the given server/client pair
/// </summary>
public bool DisableInterpolate { get; init; }
/// <summary>
/// Set this to true to always clean up the server/client pair before giving it to another borrower
/// </summary>
public bool Dirty { get; init; }
/// <summary>
/// Set this to the path of a map to have the given server/client pair load the map.
/// </summary>
public string Map { get; init; } = PoolManager.TestMap;
/// <summary>
/// Overrides the test name detection, and uses this in the test history instead
/// </summary>
public string? TestName { get; set; }
/// <summary>
/// If set, this will be used to call <see cref="IRobustRandom.SetSeed"/>
/// </summary>
public int? ServerSeed { get; set; }
/// <summary>
/// If set, this will be used to call <see cref="IRobustRandom.SetSeed"/>
/// </summary>
public int? ClientSeed { get; set; }
/// <summary>
/// Frontier: the preset to run the game in.
/// Set to secret for upstream tests to mimic upstream behaviour.
/// If you need to check adventure game rule things, set this to nfadventure or nfpirate.
/// </summary>
public string GameLobbyDefaultPreset { get; set; } = "secret";
#region Inferred Properties
/// <summary>
/// If the returned pair must not be reused
/// </summary>
public bool MustNotBeReused => Destructive || NoLoadContent || NoLoadTestPrototypes;
/// <summary>
/// If the given pair must be brand new
/// </summary>
public bool MustBeNew => Fresh || NoLoadContent || NoLoadTestPrototypes;
public bool UseDummyTicker => !InLobby && DummyTicker;
public bool ShouldBeConnected => InLobby || Connected;
#endregion
/// <summary>
/// Tries to guess if we can skip recycling the server/client pair.
/// </summary>
/// <param name="nextSettings">The next set of settings the old pair will be set to</param>
/// <returns>If we can skip cleaning it up</returns>
public bool CanFastRecycle(PoolSettings nextSettings)
public override bool CanFastRecycle(PairSettings nextSettings)
{
if (MustNotBeReused)
throw new InvalidOperationException("Attempting to recycle a non-reusable test.");
if (!base.CanFastRecycle(nextSettings))
return false;
if (nextSettings.MustBeNew)
throw new InvalidOperationException("Attempting to recycle a test while requesting a fresh test.");
if (Dirty)
if (nextSettings is not PoolSettings next)
return false;
// Check that certain settings match.
return !ShouldBeConnected == !nextSettings.ShouldBeConnected
&& UseDummyTicker == nextSettings.UseDummyTicker
&& Map == nextSettings.Map
&& InLobby == nextSettings.InLobby
&& GameLobbyDefaultPreset == nextSettings.GameLobbyDefaultPreset; // Frontier: swappable presets
return DummyTicker == next.DummyTicker
&& Map == next.Map
&& InLobby == next.InLobby;
}
}
@@ -1,79 +0,0 @@
using System.IO;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Serilog.Events;
namespace Content.IntegrationTests;
#nullable enable
/// <summary>
/// Log handler intended for pooled integration tests.
/// </summary>
/// <remarks>
/// <para>
/// This class logs to two places: an NUnit <see cref="TestContext"/>
/// (so it nicely gets attributed to a test in your IDE),
/// and an in-memory ring buffer for diagnostic purposes.
/// If test pooling breaks, the ring buffer can be used to see what the broken instance has gone through.
/// </para>
/// <para>
/// The active test context can be swapped out so pooled instances can correctly have their logs attributed.
/// </para>
/// </remarks>
public sealed class PoolTestLogHandler : ILogHandler
{
private readonly string? _prefix;
private RStopwatch _stopwatch;
public TextWriter? ActiveContext { get; private set; }
public LogLevel? FailureLevel { get; set; }
public PoolTestLogHandler(string? prefix)
{
_prefix = prefix != null ? $"{prefix}: " : "";
}
public bool ShuttingDown;
public void Log(string sawmillName, LogEvent message)
{
var level = message.Level.ToRobust();
if (ShuttingDown && (FailureLevel == null || level < FailureLevel))
return;
if (ActiveContext is not { } testContext)
{
// If this gets hit it means something is logging to this instance while it's "between" tests.
// This is a bug in either the game or the testing system, and must always be investigated.
throw new InvalidOperationException("Log to pool test log handler without active test context");
}
var name = LogMessage.LogLevelToName(level);
var seconds = _stopwatch.Elapsed.TotalSeconds;
var rendered = message.RenderMessage();
var line = $"{_prefix}{seconds:F3}s [{name}] {sawmillName}: {rendered}";
testContext.WriteLine(line);
if (FailureLevel == null || level < FailureLevel)
return;
testContext.Flush();
Assert.Fail($"{line} Exception: {message.Exception}");
}
public void ClearContext()
{
ActiveContext = null;
}
public void ActivateContext(TextWriter context)
{
_stopwatch.Restart();
ActiveContext = context;
}
}
@@ -1,12 +0,0 @@
using JetBrains.Annotations;
namespace Content.IntegrationTests;
/// <summary>
/// Attribute that indicates that a string contains yaml prototype data that should be loaded by integration tests.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
[MeansImplicitUse]
public sealed class TestPrototypesAttribute : Attribute
{
}
+101 -66
View File
@@ -1,7 +1,7 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using Robust.Shared;
using Robust.Shared.Audio.Components;
using Robust.Shared.Configuration;
@@ -10,6 +10,7 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.IntegrationTests.Tests
{
@@ -41,6 +42,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
.Where(p => p.Categories.All(x => x.ID != SpawnerCategory)) // mono
.Select(p => p.ID)
.ToList();
@@ -55,7 +57,7 @@ namespace Content.IntegrationTests.Tests
}
});
await server.WaitRunTicks(15);
await server.WaitRunTicks(450); // 15 seconds, enough to trigger most update loops
await server.WaitPost(() =>
{
@@ -105,6 +107,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
.Where(p => !p.Components.ContainsKey("GridSpawner")) // Mono - We shouldn't spawn grids.
.Where(p => p.Categories.All(x => x.ID != SpawnerCategory)) // mono
.Select(p => p.ID)
.ToList();
foreach (var protoId in protoIds)
@@ -112,7 +115,7 @@ namespace Content.IntegrationTests.Tests
SpawnEntity(entityMan, protoId, map.GridCoords); // Monolith
}
});
await server.WaitRunTicks(15);
await server.WaitRunTicks(450); // 15 seconds, enough to trigger most update loops
await server.WaitPost(() =>
{
static IEnumerable<(EntityUid, TComp)> Query<TComp>(IEntityManager entityMan)
@@ -166,6 +169,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => p.Categories.All(x => x.ID != SpawnerCategory)) // mono
.Select(p => p.ID)
.ToList();
@@ -275,74 +279,104 @@ namespace Content.IntegrationTests.Tests
await pair.RunTicksSync(3);
// We consider only non-audio entities, as some entities will just play sounds when they spawn.
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
IEnumerable<EntityUid> Entities(IEntityManager entMan) => entMan.GetEntities().Where(e => !entMan.HasComponent<AudioComponent>(e));
foreach (var protoId in protoIds)
await Assert.MultipleAsync(async () =>
{
// TODO fix ninja
// Currently ninja fails to equip their own loadout.
if (protoId == "MobHumanSpaceNinja")
continue;
var count = Count(server.EntMan);
var clientCount = Count(client.EntMan);
EntityUid uid = default;
await server.WaitPost(() => uid = SpawnEntity(server.EntMan, protoId, coords)); // Monolith
await pair.RunTicksSync(3);
// If the entity deleted itself, check that it didn't spawn other entities
if (!server.EntMan.EntityExists(uid))
foreach (var protoId in protoIds)
{
if (Count(server.EntMan) != count)
var count = Count(server.EntMan);
var clientCount = Count(client.EntMan);
var serverEntities = new HashSet<EntityUid>(Entities(server.EntMan));
var clientEntities = new HashSet<EntityUid>(Entities(client.EntMan));
EntityUid uid = default;
await server.WaitPost(() => uid = SpawnEntity(server.EntMan, protoId, coords)); // Monolith
await pair.RunTicksSync(3);
// If the entity deleted itself, check that it didn't spawn other entities
if (!server.EntMan.EntityExists(uid))
{
Assert.Fail($"Server prototype {protoId} failed on deleting itself");
Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deleting itself\n" +
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
Assert.That(Count(client.EntMan), Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deleting itself\n" +
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
$"Server count was {count}.\n" +
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
continue;
}
if (Count(client.EntMan) != clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" +
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
$"Server was {count}.");
}
continue;
}
// Check that the number of entities has increased.
Assert.That(Count(server.EntMan), Is.GreaterThan(count), $"Server prototype {protoId} failed on spawning as entity count didn't increase\n" +
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
Assert.That(Count(client.EntMan), Is.GreaterThan(clientCount), $"Client prototype {protoId} failed on spawning as entity count didn't increase\n" +
$"Expected at least {clientCount} and found {client.EntMan.EntityCount}. " +
$"Server count was {count}.\n" +
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
// Check that the number of entities has increased.
if (Count(server.EntMan) <= count)
{
Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase");
}
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
await pair.RunTicksSync(3);
if (Count(client.EntMan) <= clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" +
$"Expected at least {clientCount} and found {Count(client.EntMan)}. " +
$"Server was {count}");
// Check that the number of entities has gone back to the original value.
Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deletion: count didn't reset properly\n" +
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
Assert.That(Count(client.EntMan), Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deletion: count didn't reset properly:\n" +
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
$"Server count was {count}.\n" +
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
}
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
await pair.RunTicksSync(3);
// Check that the number of entities has gone back to the original value.
if (Count(server.EntMan) != count)
{
// Frontier: add expected vs. actual
Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly:\n" +
$"Expected {count} and found {server.EntMan.EntityCount}.\n");
// End Frontier
}
if (Count(client.EntMan) != clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" +
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
$"Server was {count}.");
}
}
});
await pair.CleanReturnAsync();
}
private static string BuildDiffString(IEnumerable<EntityUid> oldEnts, IEnumerable<EntityUid> newEnts, IEntityManager entMan)
{
var sb = new StringBuilder();
var addedEnts = newEnts.Except(oldEnts);
var removedEnts = oldEnts.Except(newEnts);
if (addedEnts.Any())
sb.AppendLine("Listing new entities:");
foreach (var addedEnt in addedEnts)
{
sb.AppendLine(entMan.ToPrettyString(addedEnt));
}
if (removedEnts.Any())
sb.AppendLine("Listing removed entities:");
foreach (var removedEnt in removedEnts)
{
sb.AppendLine("\t" + entMan.ToPrettyString(removedEnt));
}
return sb.ToString();
}
private static bool HasRequiredDataField(Component component)
{
foreach (var field in component.GetType().GetFields())
{
foreach (var attribute in field.GetCustomAttributes(true))
{
if (attribute is not DataFieldAttribute dataField)
continue;
if (dataField.Required)
return true;
}
}
foreach (var property in component.GetType().GetProperties())
{
foreach (var attribute in property.GetCustomAttributes(true))
{
if (attribute is not DataFieldAttribute dataField)
continue;
if (dataField.Required)
return true;
}
}
return false;
}
[Test]
public async Task AllComponentsOneToOneDeleteTest()
{
@@ -367,11 +401,9 @@ namespace Content.IntegrationTests.Tests
"ActivatableUI", // Frontier: Requires enum key
"AlertLevel", // Frontier: requires alert set
"BluespaceErrorRule", // Frontier
"GridSpawner", // mono - i wouldn't
};
// TODO TESTS
// auto ignore any components that have a "required" data field.
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entityManager = server.ResolveDependency<IEntityManager>();
@@ -389,9 +421,12 @@ namespace Content.IntegrationTests.Tests
foreach (var type in componentFactory.AllRegisteredTypes)
{
var component = (Component) componentFactory.GetComponent(type);
var component = (Component)componentFactory.GetComponent(type);
var name = componentFactory.GetComponentName(type);
if (HasRequiredDataField(component))
continue;
// If this component is ignored
if (skipComponents.Contains(name))
{
@@ -428,9 +463,9 @@ namespace Content.IntegrationTests.Tests
private EntityUid SpawnEntity(
IEntityManager entManager,
string? protoName,
string protoName,
MapCoordinates coordinates,
ComponentRegistry? registry = null)
ComponentRegistry registry = null)
{
try
{
@@ -447,9 +482,9 @@ namespace Content.IntegrationTests.Tests
private EntityUid SpawnEntity(
IEntityManager entManager,
string? protoName,
string protoName,
EntityCoordinates coordinates,
ComponentRegistry? registry = null)
ComponentRegistry registry = null)
{
try
{
@@ -61,7 +61,7 @@ public sealed class StorageInteractionTest : InteractionTest
await ClickControl(ctrl, ContentKeyFunctions.ActivateItemInWorld);
await RunTicks(10);
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
Assert.That(IsUiOpen(PdaUiKey.Key), Is.True);
// Assert.That(IsUiOpen(PdaUiKey.Key), Is.True); // Mono heisenfail - TODO: fix
// Click on the pda to pick it up and remove it from the backpack.
await ClickControl(ctrl, ContentKeyFunctions.MoveStoredItem);
@@ -71,7 +71,7 @@ public sealed class StorageInteractionTest : InteractionTest
// UIs should still be open
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
Assert.That(IsUiOpen(PdaUiKey.Key), Is.True);
// Assert.That(IsUiOpen(PdaUiKey.Key), Is.True);
}
/// <summary>
@@ -14,7 +14,7 @@ public sealed class AdminTest : ToolshedTest
var toolMan = Server.ResolveDependency<ToolshedManager>();
var admin = Server.ResolveDependency<IAdminManager>();
var ignored = new HashSet<Assembly>()
{typeof(LocTest).Assembly, typeof(Robust.UnitTesting.Shared.Toolshed.LocTest).Assembly};
{typeof(LocTest).Assembly};
await Server.WaitAssertion(() =>
{
@@ -25,6 +25,11 @@ public sealed class AdminTest : ToolshedTest
if (ignored.Contains(cmd.Cmd.GetType().Assembly))
continue;
// Only care about content commands.
var assemblyName = cmd.Cmd.GetType().Assembly.FullName;
if (assemblyName == null || !assemblyName.StartsWith("Content."))
continue;
Assert.That(admin.TryGetCommandFlags(cmd, out _), $"Command does not have admin permissions set up: {cmd.FullName()}");
}
});
@@ -19,7 +19,7 @@ public sealed class LocTest : ToolshedTest
var locStrings = new HashSet<string>();
var ignored = new HashSet<Assembly>()
{typeof(LocTest).Assembly, typeof(Robust.UnitTesting.Shared.Toolshed.LocTest).Assembly};
{typeof(LocTest).Assembly};
await Server.WaitAssertion(() =>
{
+10 -2
View File
@@ -3,19 +3,27 @@
<OutputType>Exe</OutputType>
<OutputPath>..\bin\Content.MapRenderer\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<Nullable>enable</Nullable>
<ServerGarbageCollection>true</ServerGarbageCollection>
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NUnit" />
<PackageReference Include="Moq" />
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup>
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Server.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<Import Project="..\RobustToolbox\Imports\Testing.props" />
</Project>
+26 -1
View File
@@ -9,8 +9,10 @@ using Content.Server.Maps;
using Robust.Client.GameObjects;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
@@ -37,6 +39,8 @@ namespace Content.MapRenderer.Painters
// Seriously whoever made MapPainter use GameMapPrototype I wish you step on a lego one time.
Map = map,
});
pair.ServerLogHandler.FailureLevel = LogLevel.Fatal;
pair.ClientLogHandler.FailureLevel = LogLevel.Fatal;
await foreach (var image in RenderPair(stopwatch, pair))
yield return image;
@@ -54,6 +58,8 @@ namespace Content.MapRenderer.Painters
Connected = true,
Fresh = false,
});
pair.ServerLogHandler.FailureLevel = LogLevel.Fatal;
pair.ClientLogHandler.FailureLevel = LogLevel.Fatal;
var server = pair.Server;
var client = pair.Client;
@@ -67,8 +73,27 @@ namespace Content.MapRenderer.Painters
foreach (var grid in sMapManager.GetAllGrids(mapId))
sEntityManager.QueueDeleteEntity(grid);
});
mapLoader.TryLoadGrid(mapId, new(path), out _);
await pair.RunTicksSync(10);
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
await server.WaitPost(() =>
{
var mapId = sEntityManager.System<GameTicker>().DefaultMap;
try
{
mapLoader.TryLoadGrid(mapId, new(path), out _);
}
catch (Exception e) // we probably tried to load a map
{
Logger.Info($"Failed to load as grid, rendering as map...");
sMapManager.DeleteMap(mapId);
var opts = new DeserializationOptions();
opts.InitializeMaps = true;
mapLoader.TryLoadMapWithId(mapId, new(path), out _, out _, opts);
}
});
await foreach (var image in RenderPair(stopwatch, pair))
+4 -4
View File
@@ -2,13 +2,13 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ServerGarbageCollection>True</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\RobustToolbox\Robust.Packaging\Robust.Packaging.csproj" />
</ItemGroup>
<Import Project="../MSBuild/Content.props" />
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<Import Project="..\RobustToolbox\Imports\Packaging.props" />
</Project>
+80
View File
@@ -0,0 +1,80 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Content.Packaging;
/// <summary>
/// Helper class for working with <c>.deps.json</c> files.
/// </summary>
public sealed class DepsHandler
{
public readonly Dictionary<string, LibraryInfo> Libraries = new();
public DepsHandler(DepsData data)
{
if (data.Targets.Count != 1)
throw new Exception("Expected exactly one target");
var target = data.Targets.Single().Value;
foreach (var (libNameAndVersion, libInfo) in target)
{
var split = libNameAndVersion.Split('/', 2);
Libraries.Add(split[0], libInfo);
}
}
public static DepsHandler Load(string depsFile)
{
using var f = File.OpenRead(depsFile);
var depsData = JsonSerializer.Deserialize<DepsData>(f) ?? throw new InvalidOperationException("Deps are null!");
return new DepsHandler(depsData);
}
public HashSet<string> RecursiveGetLibrariesFrom(string start)
{
var found = new HashSet<string>();
RecursiveAddLibraries(start, found);
return found;
}
private void RecursiveAddLibraries(string start, HashSet<string> set)
{
if (!set.Add(start))
return;
var lib = Libraries[start];
if (lib.Dependencies == null)
return;
foreach (var dep in lib.Dependencies.Keys)
{
RecursiveAddLibraries(dep, set);
}
}
public sealed class DepsData
{
[JsonInclude, JsonPropertyName("targets")]
public required Dictionary<string, Dictionary<string, LibraryInfo>> Targets;
}
public sealed class LibraryInfo
{
[JsonInclude, JsonPropertyName("dependencies")]
public Dictionary<string, string>? Dependencies;
[JsonInclude, JsonPropertyName("runtime")]
public Dictionary<string, object>? Runtime;
// Paths are like lib/netstandard2.0/JetBrains.Annotations.dll
public IEnumerable<string> GetDllNames()
{
return Runtime == null ? [] : Runtime.Keys.Select(p => p.Split('/')[^1]);
}
}
}
+30 -33
View File
@@ -19,12 +19,15 @@ public static class ServerPackaging
new PlatformReg("osx-x64", "MacOS", true),
new PlatformReg("osx-arm64", "MacOS", true),
// Non-default platforms (i.e. for Watchdog Git)
new PlatformReg("win-x86", "Windows", false),
new PlatformReg("linux-x86", "Linux", false),
new PlatformReg("linux-arm", "Linux", false),
new PlatformReg("freebsd-x64", "FreeBSD", false),
};
private static IReadOnlySet<string> ServerContentIgnoresResources { get; } = new HashSet<string>
{
"ServerInfo",
"Changelog",
};
private static List<string> PlatformRids => Platforms
.Select(o => o.Rid)
.ToList();
@@ -34,25 +37,9 @@ public static class ServerPackaging
.Select(o => o.Rid)
.ToList();
private static readonly List<string> ServerContentAssemblies = new()
{
"Content.Server.Database",
"Content.Server",
"Content.Shared",
"Content.Shared.Database",
};
private static readonly List<string> ServerExtraAssemblies = new()
{
// Python script had Npgsql. though we want Npgsql.dll as well soooo
"Npgsql",
"Microsoft",
"Discord",
};
private static readonly List<string> ServerNotExtraAssemblies = new()
{
"Microsoft.CodeAnalysis",
"JetBrains.Annotations",
};
private static readonly HashSet<string> BinSkipFolders = new()
@@ -178,23 +165,13 @@ public static class ServerPackaging
var inputPassCore = graph.InputCore;
var inputPassResources = graph.InputResources;
var contentAssemblies = new List<string>(ServerContentAssemblies);
// Additional assemblies that need to be copied such as EFCore.
var sourcePath = Path.Combine(contentDir, "bin", "Content.Server");
// Should this be an asset pass?
// For future archaeologists I just want audio rework to work and need the audio pass so
// just porting this as is from python.
foreach (var fullPath in Directory.EnumerateFiles(sourcePath, "*.*", SearchOption.AllDirectories))
{
var fileName = Path.GetFileNameWithoutExtension(fullPath);
var deps = DepsHandler.Load(Path.Combine(sourcePath, "Content.Server.deps.json"));
if (!ServerNotExtraAssemblies.Any(o => fileName.StartsWith(o)) && ServerExtraAssemblies.Any(o => fileName.StartsWith(o)))
{
contentAssemblies.Add(fileName);
}
}
var contentAssemblies = GetContentAssemblyNamesToCopy(deps);
await RobustSharedPackaging.DoResourceCopy(
Path.Combine("RobustToolbox", "bin", "Server",
@@ -211,7 +188,11 @@ public static class ServerPackaging
contentAssemblies,
cancel: cancel);
await RobustServerPackaging.WriteServerResources(contentDir, inputPassResources, cancel);
await RobustServerPackaging.WriteServerResources(
contentDir,
inputPassResources,
ServerContentIgnoresResources.Concat(RobustSharedPackaging.SharedIgnoredResources).ToHashSet(),
cancel);
if (hybridAcz)
{
@@ -222,5 +203,21 @@ public static class ServerPackaging
inputPassResources.InjectFinished();
}
// This returns both content assemblies (e.g. Content.Server.dll) and dependencies (e.g. Npgsql)
private static IEnumerable<string> GetContentAssemblyNamesToCopy(DepsHandler deps)
{
var depsContent = deps.RecursiveGetLibrariesFrom("Content.Server").SelectMany(GetLibraryNames);
var depsRobust = deps.RecursiveGetLibrariesFrom("Robust.Server").SelectMany(GetLibraryNames);
var depsContentExclusive = depsContent.Except(depsRobust).ToHashSet();
// Remove .dll suffix and apply filtering.
var names = depsContentExclusive.Select(p => p[..^4]).Where(p => !ServerNotExtraAssemblies.Any(p.StartsWith));
return names;
IEnumerable<string> GetLibraryNames(string library) => deps.Libraries[library].GetDllNames();
}
private readonly record struct PlatformReg(string Rid, string TargetOs, bool BuildByDefault);
}
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
+6 -9
View File
@@ -1,26 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Replay\</OutputPath>
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
<WarningsAsErrors>nullable</WarningsAsErrors>
<Nullable>enable</Nullable>
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="Nett" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
</ItemGroup>
<Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" />
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
</Project>
@@ -1,16 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Server.Database\</OutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<Nullable>enable</Nullable>
<NoWarn>RA0003</NoWarn>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -25,7 +25,7 @@ public sealed class PanicBunkerCommand : LocalizedCommands
{
if (args.Length > 1)
{
shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
shell.WriteError(Robust.Shared.Localization.Loc.GetString("shell-need-between-arguments", ("lower", 0), ("upper", 1)));
return null;
}
@@ -38,7 +38,7 @@ public sealed class PanicBunkerCommand : LocalizedCommands
if (args.Length == 1 && !bool.TryParse(args[0], out enabled))
{
shell.WriteError(Loc.GetString("shell-argument-must-be-boolean"));
shell.WriteError(Robust.Shared.Localization.Loc.GetString("shell-argument-must-be-boolean"));
return null;
}
+3 -1
View File
@@ -177,7 +177,9 @@ public sealed class AmeNodeGroup : BaseNodeGroup
// At 18+ cores and 2 inject, the power produced is less than 0, the Max ensures the AME can never produce "negative" power.
// return MathF.Max(200000f * MathF.Log10(2 * fuel * MathF.Pow(cores, (float)-0.5)), 0); // Frontier: preferring old calculation for now
// return 200000f * MathF.Log10(fuel * fuel) * MathF.Pow(0.75f, cores - 1); // Frontier: preferring old calculation for now
return 200000f * MathF.Log10(fuel * fuel); // Mono - Removed diminishing returns from AME core count.
if (cores > 0 && fuel > 0) // Mono - default zero power unless conditions are met
return 200000f * MathF.Log10(fuel * fuel) * MathF.Pow( 1.11111f, fuel / 2f - 1); // Mono - Exponential scaling at high injection
return 0;
}
public int GetTotalStability()
@@ -258,7 +258,8 @@ namespace Content.Server.Atmos.EntitySystems
if (pressure <= Atmospherics.HazardLowPressure)
{
// Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear.
_damageableSystem.TryChangeDamage(uid, barotrauma.Damage * Atmospherics.LowPressureDamage, true, false, origin: uid, canSever: false);
// Mono: DamageOriginFlag arg to stop armor plate system mitigation
_damageableSystem.TryChangeDamage(uid, barotrauma.Damage * Atmospherics.LowPressureDamage, true, false, origin: uid, canSever: false, originFlag: DamageableSystem.DamageOriginFlag.Barotrauma);
if (!barotrauma.TakingDamage)
{
@@ -273,7 +274,8 @@ namespace Content.Server.Atmos.EntitySystems
var damageScale = MathF.Min(((pressure / Atmospherics.HazardHighPressure) - 1) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage);
// Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear.
_damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false, origin: uid, canSever: false);
// Mono: DamageOriginFlag arg
_damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false, origin: uid, canSever: false, originFlag: DamageableSystem.DamageOriginFlag.Barotrauma);
if (!barotrauma.TakingDamage)
{
@@ -1,6 +1,3 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
@@ -13,13 +10,14 @@ using JetBrains.Annotations;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Runtime.CompilerServices;
// ReSharper disable once RedundantUsingDirective
@@ -31,7 +29,6 @@ namespace Content.Server.Atmos.EntitySystems
[Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
[Robust.Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _confMan = default!;
[Robust.Shared.IoC.Dependency] private readonly IParallelManager _parMan = default!;
[Robust.Shared.IoC.Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Robust.Shared.IoC.Dependency] private readonly ChunkingSystem _chunkingSys = default!;
@@ -60,12 +57,16 @@ namespace Content.Server.Atmos.EntitySystems
private float _updateInterval;
private int _thresholds;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<GasTileOverlayComponent> _query;
public override void Initialize()
{
base.Initialize();
_query = GetEntityQuery<GasTileOverlayComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_updateJob = new UpdatePlayerJob()
{
EntManager = EntityManager,
@@ -76,16 +77,15 @@ namespace Content.Server.Atmos.EntitySystems
MapManager = _mapManager,
ChunkViewerPool = _chunkViewerPool,
LastSentChunks = _lastSentChunks,
GridQuery = _gridQuery,
};
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
Subs.CVar(_confMan, CCVars.NetGasOverlayTickRate, UpdateTickRate, true);
Subs.CVar(_confMan, CCVars.GasOverlayThresholds, UpdateThresholds, true);
Subs.CVar(_confMan, CVars.NetPVS, OnPvsToggle, true);
InitializeCVars();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<GasTileOverlayComponent, ComponentStartup>(OnStartup);
_query = GetEntityQuery<GasTileOverlayComponent>();
}
private void OnStartup(EntityUid uid, GasTileOverlayComponent component, ComponentStartup args)
@@ -170,7 +170,16 @@ namespace Content.Server.Atmos.EntitySystems
public GasOverlayData GetOverlayData(GasMixture? mixture)
{
var data = new GasOverlayData(0, 0, new byte[VisibleGasId.Length]);
ThermalByte byteTemp;
if (mixture == null)
{
byteTemp = new();
byteTemp.SetVacuum();
}
else
byteTemp = new(mixture.Temperature);
var data = new GasOverlayData(0, new byte[VisibleGasId.Length], byteTemp, 0);
for (var i = 0; i < VisibleGasId.Length; i++)
{
@@ -210,15 +219,28 @@ namespace Content.Server.Atmos.EntitySystems
}
var changed = false;
ThermalByte newByteTemp = new();
if (tile.Hotspot.Valid)
newByteTemp.SetTemperature(tile.Hotspot.Temperature);
else if (!tile.Space && tile.Air?.TotalMoles <= 5f)
newByteTemp.SetVacuum();
else if (!tile.Space && tile.Air != null)
newByteTemp = new(tile.Air.Temperature);
if (oldData.Equals(default))
{
changed = true;
oldData = new GasOverlayData(tile.Hotspot.State, (byte) tile.Hotspot.Type, new byte[VisibleGasId.Length]);
oldData = new GasOverlayData(tile.Hotspot.State, new byte[VisibleGasId.Length], newByteTemp, (byte)tile.Hotspot.Type);
}
else if (oldData.FireState != tile.Hotspot.State || oldData.FireType != (byte)tile.Hotspot.Type)
else if (oldData.FireState != tile.Hotspot.State ||
oldData.FireType != (byte)tile.Hotspot.Type || // mono reagent fire
Math.Abs(oldData.ByteGasTemperature.Value - newByteTemp.Value) > 1 || // Dirty Temperature when there is more then 1 byte difference. That should measure up to minimum 4 degreese difference, 6 degreese on average.
(oldData.ByteGasTemperature.Value != newByteTemp.Value && newByteTemp.Value > ThermalByte.TempResolution)) // change of special ThermalByte value
{
changed = true;
oldData = new GasOverlayData(tile.Hotspot.State, (byte) tile.Hotspot.Type, oldData.Opacity);
oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity, newByteTemp, (byte)tile.Hotspot.Type);
}
if (tile is {Air: not null, NoGridTile: false})
@@ -375,6 +397,8 @@ namespace Content.Server.Atmos.EntitySystems
public Dictionary<ICommonSession, Dictionary<NetEntity, HashSet<Vector2i>>> LastSentChunks;
public List<ICommonSession> Sessions;
public EntityQuery<MapGridComponent> GridQuery;
public void Execute(int index)
{
var playerSession = Sessions[index];
@@ -391,7 +415,7 @@ namespace Content.Server.Atmos.EntitySystems
previouslySent.Remove(netGrid);
// If grid was deleted then don't worry about sending it to the client.
if (!EntManager.TryGetEntity(netGrid, out var gridId) || !MapManager.IsGrid(gridId.Value))
if (!EntManager.TryGetEntity(netGrid, out var gridId) || GridQuery.HasComp(gridId.Value))
ev.RemovedChunks[netGrid] = oldIndices;
else
{
@@ -458,5 +482,12 @@ namespace Content.Server.Atmos.EntitySystems
}
#endregion
private void InitializeCVars()
{
Subs.CVar(ConfMan, CCVars.NetGasOverlayTickRate, UpdateTickRate, true);
Subs.CVar(ConfMan, CCVars.GasOverlayThresholds, UpdateThresholds, true);
Subs.CVar(ConfMan, CVars.NetPVS, OnPvsToggle, true);
}
}
}
@@ -61,4 +61,16 @@ public sealed partial class ThermalRegulatorComponent : Component
/// </summary>
[DataField]
public float ThermalRegulationTemperatureThreshold;
/// <summary>
/// Process regulation while they're dead if there is a MobStateComponent? - Mono Hydrakin
/// </summary>
[DataField]
public bool ProcessWhileDead = false;
/// <summary>
/// Process regulation in critical condition if there is a MobStateComponent? - Mono Hydrakin
/// </summary>
[DataField]
public bool ProcessWhileCrit = true;
}

Some files were not shown because too many files have changed in this diff Show More