Compare commits

...

287 Commits

Author SHA1 Message Date
Redrover1760 aa8c6622b8 bazooka buffs 2026-04-22 22:18:15 -04:00
Redrover1760 bcc3402ef7 30 spread instead 2026-04-20 02:52:57 -04:00
Redrover1760 a8a3cec8e4 final changes 2026-04-20 02:47:05 -04:00
Redrover1760 e19e64b903 buff it actually 2026-04-20 02:43:01 -04:00
Redrover1760 5b6542720c some rebuffs 2026-04-20 02:37:20 -04:00
Redrover1760 db7066605a wa 2026-04-19 18:03:31 -04:00
Redrover1760 e14c8c0a8f tweaks 2026-04-19 18:02:29 -04:00
Redrover1760 c061eaf610 cuts marauder firerate to 1.25 2026-04-19 18:02:02 -04:00
Redrover1760 e56c2c5272 unnerfs metronome direct damage 2026-04-19 17:58:53 -04:00
Redrover1760 238d77ead3 final tweak 2026-04-19 15:22:55 -04:00
Redrover1760 dea4d76981 okay fair 2026-04-19 15:01:22 -04:00
Redrover1760 82b46194d1 coilgun nerf 2026-04-19 14:57:35 -04:00
Redrover1760 e6835a4934 mech weapons balance pass 2026-04-19 14:55:02 -04:00
Redrover1760 3d6597526b 1 2026-04-19 14:21:36 -04:00
monolith8319 8894d4e5ff Automated Changelog (#3827) 2026-04-19 02:04:51 +00:00
Mrrpip eef28959e2 Makes Osiris Inhands Visible. (#3827) 2026-04-18 22:04:23 -04:00
monolith8319 4ea83d389a Automated Changelog (#3821) 2026-04-18 15:48:18 +00:00
Diesel 02f74c26e9 Hot Fix - PTech Access Change (#3821) 2026-04-18 18:47:50 +03:00
monolith8319 c0ea5adf5d Automated Changelog (#3815) 2026-04-18 14:20:48 +00:00
significant harassment 6daf9f02e6 Various Ground Balancing Tweaks (#3815)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-04-18 10:20:18 -04:00
monolith8319 afabe747f2 Automated Changelog (#3818) 2026-04-18 14:18:17 +00:00
burrrkeye 699232f653 Asakim Breathe Air Update (#3818) 2026-04-18 10:17:50 -04:00
monolith8319 262aa8cd4b Automated Changelog (#3814) 2026-04-17 20:43:04 +00:00
significant harassment bcb9440046 TSFMC Guidebook Slop (#3814)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-04-17 16:42:34 -04:00
monolith8319 93bd8b8356 Automated Changelog (#3798) 2026-04-17 17:01:02 +00:00
Redrover1760 d14dd88149 Asakim Changes (#3798) 2026-04-17 13:00:31 -04:00
monolith8319 f520ddf911 Automated Changelog (#3812) 2026-04-17 16:56:49 +00:00
Corrupt c2ea1eaf94 Wyvern Tweaks! (#3812) 2026-04-17 12:56:15 -04:00
monolith8319 fc105b61fc Automated Changelog (#3811) 2026-04-17 14:52:46 +00:00
Ilya246 271c4e7efc Even More Ship HTN Upgrades (#3811) 2026-04-17 14:52:16 +00:00
monolith8319 a4138f4b11 Automated Changelog (#3809) 2026-04-17 00:42:16 +00:00
lopsidedmanboobs ff74cf3ae1 Brush up Rules (#3809) 2026-04-16 20:41:48 -04:00
monolith8319 663425a050 Automated Changelog (#3808) 2026-04-16 19:44:40 +00:00
NazrinNya b4e4a284bc De-WL Overseer (#3808)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-04-16 22:44:11 +03:00
monolith8319 3a8ac92ef8 Automated Changelog (#3807) 2026-04-16 19:32:57 +00:00
TheRex191 d3d3ab531e Gas Dock Not Docking, Now Docking. (#3807) 2026-04-16 22:32:26 +03:00
monolith8319 71cf5f42c7 Automated Changelog (#3454) 2026-04-16 18:28:15 +00:00
Diesel fce9e95910 Faction ID Console Compatibility and Fixes Company Banks (#3454)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-16 21:27:44 +03:00
monolith8319 ee337596d6 Automated Changelog (#3804) 2026-04-16 16:36:28 +00:00
NazrinNya a2b3ddecdf Components Adjustment, RCD Buff. (#3804)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-04-16 19:35:53 +03:00
monolith8319 0939533dc1 Automated Changelog (#3531) 2026-04-16 16:19:14 +00:00
Mrrpip 42f6f53be9 Sentinel Fuel Locker Hotfix. (#3531) 2026-04-16 19:18:43 +03:00
monolith8319 2651c00cfd Automated Changelog (#3538) 2026-04-16 16:17:32 +00:00
Lily Autumn a462d09fb9 Yip Woof Bark (#3538) 2026-04-16 19:16:57 +03:00
monolith8319 ce617c185e Automated Changelog (#3781) 2026-04-16 16:04:47 +00:00
doublechest 131c5e79a5 Auto Disable Jetpack when Entering Grid with Enabled Magboots (#3781)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-16 19:04:09 +03:00
monolith8319 86c9a843b1 Automated Changelog (#3794) 2026-04-16 15:55:59 +00:00
Ssar’keth of the First Clutch dd910f1550 Add Radar Mode Toggles and UI Injection Wrapper (#3794)
Co-authored-by: Ssar'keth of the First Clutch <ziz11@users.noreply.github.com>
Co-authored-by: Ilya246 <ilyukarno@gmail.com>
2026-04-16 18:55:16 +03:00
monolith8319 cd8d59c095 Automated Changelog (#3803) 2026-04-16 15:24:48 +00:00
Redrover1760 20bc7af2a4 Unbreaks Yowie Randomized Last Names (#3803) 2026-04-16 18:24:17 +03:00
monolith8319 65ea89b673 Automated Changelog (#3789) 2026-04-16 12:46:01 +00:00
Ilya246 44445f3d2a Explosions Tempbuff (#3789)
Co-authored-by: significant harassment <moguls-charade4n@icloud.com>
2026-04-16 12:45:27 +00:00
monolith8319 648cc99aff Automated Changelog (#3800) 2026-04-15 22:26:41 +00:00
Redrover1760 e8c2b72aa0 Dewhitelists Faction AIs (#3800) 2026-04-15 18:26:12 -04:00
monolith8319 7d85dbca00 Automated Changelog (#3799) 2026-04-15 20:57:28 +00:00
Redrover1760 85e0cb1c76 CPRing Now Checks for Unrevivable + CPR Revivals Now Notify Ghost (#3799) 2026-04-15 16:56:57 -04:00
monolith8319 3c32d2f6f5 Automated Changelog (#3792) 2026-04-15 20:16:30 +00:00
Redrover1760 9d8d0b9016 Ensures Mechs Can't Be Entered if Broken (#3792) 2026-04-15 16:15:59 -04:00
monolith8319 02e72a4c1d Automated Changelog (#3795) 2026-04-15 10:37:26 +00:00
PunishedJoe 5fac554802 Fixes Balaclava and Neck Layers (#3795) 2026-04-15 19:36:56 +09:00
Redrover1760 bc62bf3700 Close Prs on Master (#3793) 2026-04-14 19:55:45 -04:00
monolith8319 accb1404b0 Automated Changelog (#3276) 2026-04-14 21:02:17 +00:00
Redrover1760 326440aa22 Tarantula Revamp (#3276)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-04-14 17:01:36 -04:00
monolith8319 b4fe4ab5e7 Automated Changelog (#3785) 2026-04-14 19:17:32 +00:00
Redrover1760 8ff52fcd7b Infinite FMC/DC Contra Lathe Fix (#3785) 2026-04-14 15:16:58 -04:00
monolith8319 e8a620e044 Automated Changelog (#3788) 2026-04-14 16:21:49 +00:00
chuga-git 9ea80d6405 Revert "Fix Projectile Raycast" (#3788) 2026-04-14 12:21:16 -04:00
monolith8319 abec847ca3 Automated Changelog (#3790) 2026-04-14 13:51:22 +00:00
Ilya246 36214087e9 Fix Thermal Signatures (#3790) 2026-04-14 13:50:47 +00:00
monolith8319 2b4f4f2ba0 Automated Changelog (#3736) 2026-04-13 18:31:43 +00:00
Mrrpip 258bc12f16 WOE, YE FIEND. STAMPS UPON YE. (#3736)
Co-authored-by: NazrinNya <137837419+NazrinNya@users.noreply.github.com>
2026-04-13 21:31:11 +03:00
monolith8319 d73cf7aeaf Automated Changelog (#3033) 2026-04-13 18:14:00 +00:00
thoughtlessuser b1e200408d Polaris Re-Evil-Ening (#3033)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-13 21:13:27 +03:00
monolith8319 95227750d9 Automated Changelog (#3710) 2026-04-13 18:03:18 +00:00
chuga-git 34f71b59b9 Buffs "Entropy" Electron Torpedo Speed (#3710)
Co-authored-by: NazrinNya <137837419+NazrinNya@users.noreply.github.com>
2026-04-13 21:02:19 +03:00
monolith8319 547420d4bc Automated Changelog (#3441) 2026-04-13 17:44:48 +00:00
Mrrpip 510434fb65 Unlimited Mil-Fabs and Servers in the USSPvend (#3441) 2026-04-13 20:43:55 +03:00
monolith8319 862ce8352c Automated Changelog (#3484) 2026-04-13 17:43:20 +00:00
significant harassment 552ab5718a Large APC (#3484)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-04-13 20:42:50 +03:00
monolith8319 2bacec206b Automated Changelog (#3576) 2026-04-13 17:28:12 +00:00
Kiithnaras bd8ae0ec9b Medicine Improvement Attempt (#3576)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-13 20:27:35 +03:00
monolith8319 6ba17c6e1b Automated Changelog (#3702) 2026-04-13 17:10:50 +00:00
Ghost581 50d7de7818 Bolt Down Camelot's Military Assembler + Fax + Silo (#3702) 2026-04-13 20:10:17 +03:00
monolith8319 02348e780e Automated Changelog (#3712) 2026-04-13 15:47:27 +00:00
tonotom1 80cc460d69 Repairable CC Turret (#3712) 2026-04-13 18:46:56 +03:00
monolith8319 e95a87a1d8 Automated Changelog (#3713) 2026-04-13 15:46:07 +00:00
Redrover1760 78ecd13c0a Scythe Nerfs (#3713) 2026-04-13 18:45:38 +03:00
Ghost581 9eb4c8333c Fix some Wording in VG's Description (#3724) 2026-04-13 18:42:24 +03:00
monolith8319 882b1acae7 Automated Changelog (#3732) 2026-04-13 15:40:49 +00:00
Ilya246 df39f7bd6d Turret Shootthrough (#3732) 2026-04-13 18:40:17 +03:00
monolith8319 c68d329dd2 Automated Changelog (#3733) 2026-04-13 15:39:50 +00:00
Svarshik 5a2341407b Modsuits Yml Cleanup and Minor Buff (#3733) 2026-04-13 18:39:20 +03:00
monolith8319 b4b49016d9 Automated Changelog (#3741) 2026-04-13 15:25:57 +00:00
Redrover1760 53a40c6c3c Black Cloth Wraps (#3741) 2026-04-13 18:25:26 +03:00
monolith8319 fbf93d50be Automated Changelog (#3751) 2026-04-13 15:01:14 +00:00
Corrupt 68144857e8 RSC Ship Tweaks (#3751) 2026-04-13 18:00:42 +03:00
monolith8319 820a70c695 Automated Changelog (#3754) 2026-04-13 14:57:51 +00:00
Corrupt bff7436490 RS-2 Malandro (#3754) 2026-04-13 17:57:19 +03:00
monolith8319 2b76b0a55a Automated Changelog (#3761) 2026-04-13 14:40:32 +00:00
ToastEnjoyer fa61e959fb Fix Vox Neck Displacements (#3761) 2026-04-13 17:40:01 +03:00
ondrej008 b427f087b0 Adds Missing Locale Strings (#3775) 2026-04-13 17:19:10 +03:00
monolith8319 7a8b90339f Automated Changelog (#3780) 2026-04-13 14:16:14 +00:00
iramoye cecc52bff9 Adds Maintenance Jack to 'Expanding and Repairing Station' Guidebook (#3780) 2026-04-13 17:15:41 +03:00
monolith8319 378592a0ba Automated Changelog (#3767) 2026-04-13 12:55:55 +00:00
Corrupt 89dff2ec03 Remove Flashpoint (#3767) 2026-04-13 14:55:26 +02:00
monolith8319 4faad89b0c Automated Changelog (#3778) 2026-04-13 09:48:48 +00:00
lopsidedmanboobs 8f8b94862a I HATE ALL MEDICS (#3778) 2026-04-13 11:48:20 +02:00
monolith8319 7c29b33d27 Automated Changelog (#3766) 2026-04-12 16:17:24 +00:00
doublechest 8180e5d436 Increase Cargo Order Capacity From 20 to 60 in the Trade Mall (#3766) 2026-04-12 12:16:54 -04:00
monolith8319 e081dd59c6 Automated Changelog (#3765) 2026-04-12 10:10:37 +00:00
Ilya246 295ef88a95 Fix Poi Ftling (#3765) 2026-04-12 10:10:05 +00:00
monolith8319 39292553b3 Automated Changelog (#3764) 2026-04-12 09:27:34 +00:00
Ilya246 2652423c4d Fix New Ship Speed (#3764) 2026-04-12 09:27:05 +00:00
monolith8319 fb836a0ee7 Automated Changelog (#3756) 2026-04-11 20:32:31 +00:00
Ilya246 7a63aba489 Fix Projectile Raycast (#3756) 2026-04-11 16:32:01 -04:00
monolith8319 7ec10d578c Automated Changelog (#3753) 2026-04-11 15:45:44 +00:00
Ilya246 91f4eb7b51 More Gun Fixes (#3753) 2026-04-11 15:45:12 +00:00
Ssar’keth of the First Clutch e552e2498f Add Compass and Azimuth Ring to Shuttle Radar (#3737)
Co-authored-by: Ssar'keth of the First Clutch <ziz11@users.noreply.github.com>
2026-04-11 12:01:06 +00:00
Ilya246 ef34f255bf Also Fix Borg Mass Scanner (#3750) 2026-04-11 11:43:08 +00:00
monolith8319 0f3390aba8 Automated Changelog (#3749) 2026-04-11 08:48:00 +00:00
Ilya246 25033ae9c1 Shipgun Aiming Hotfix (#3749) 2026-04-11 08:47:32 +00:00
monolith8319 d32fd9dd0a Automated Changelog (#3748) 2026-04-11 06:05:33 +00:00
Ilya246 728c10974e Fix Scanner Rotation Stuff (#3748) 2026-04-11 06:05:04 +00:00
monolith8319 50c5891b4e Automated Changelog (#3746) 2026-04-10 18:58:54 +00:00
Ilya246 51818f6dc3 Companies Fix (#3746) 2026-04-10 18:58:20 +00:00
monolith8319 dcaefa16d7 Automated Changelog (#3745) 2026-04-10 15:17:15 +00:00
Ilya246 15936634b1 Increase Shiphtn Grid Evasion Buffer (#3745) 2026-04-10 15:16:43 +00:00
monolith8319 574bb25522 Automated Changelog (#3705) 2026-04-10 14:50:16 +00:00
Ilya246 1125715a3f No Gunnery Rotation (#3705)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-10 14:49:46 +00:00
monolith8319 9e3a270988 Automated Changelog (#3711) 2026-04-10 12:39:36 +00:00
Antoine bf1a02bfcb Fixed Radio Highlighting (#3711) 2026-04-10 21:39:07 +09:00
monolith8319 57c59e3684 Automated Changelog (#3586) 2026-04-10 12:25:32 +00:00
tonotom1 0e31d3885f Plant Analyzer Friction Deletion (#3586) 2026-04-10 15:25:02 +03:00
monolith8319 27b05a3b75 Automated Changelog (#3743) 2026-04-10 10:19:13 +00:00
Ilya246 f99e36e101 Ship Htn Hotfix (#3743) 2026-04-10 10:18:45 +00:00
monolith8319 5a17e7781f Automated Changelog (#3731) 2026-04-09 21:23:34 +00:00
significant harassment 98f8d334a1 Gun-Based Damage Modifier Port (#3731)
Co-authored-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-04-09 17:23:05 -04:00
monolith8319 c9bba3367d Automated Changelog (#3528) 2026-04-09 18:21:59 +00:00
Redrover1760 4ac8f937fb 12 Gauge Buff (#3528) 2026-04-09 14:21:28 -04:00
monolith8319 9648f54fc3 Automated Changelog (#3735) 2026-04-09 17:40:58 +00:00
Redrover1760 33e0bf8293 Gun Health Oversights (#3735) 2026-04-09 13:40:26 -04:00
monolith8319 81ed57e9fb Automated Changelog (#3734) 2026-04-09 17:23:09 +00:00
Redrover1760 0943abba3e Lying Down Makes Projectiles Sometimes Miss You (#3734) 2026-04-09 13:22:37 -04:00
monolith8319 886752cd7a Automated Changelog (#3698) 2026-04-09 16:17:45 +00:00
Ilya246 f8ba24d3f1 Make Shuttle Speedcap Slowdown Instead (#3698) 2026-04-09 19:17:10 +03:00
EckoAurum 1765c7d506 Company Commands for Offline Users (#3721) 2026-04-10 00:57:26 +09:00
monolith8319 16f40a5289 Automated Changelog (#3579) 2026-04-09 15:19:41 +00:00
Ghost581 b80fb6d554 No More Crawling for Non-Targetted Evasion (#3579)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-09 15:19:10 +00:00
Redrover1760 e1932935fe Decreases Thruster SRD Cost (#3729) 2026-04-08 20:33:33 -04:00
monolith8319 5aa06e8fc0 Automated Changelog (#3728) 2026-04-08 23:37:12 +00:00
Redrover1760 914c5529ec Minor Spelling Mistake (#3726) 2026-04-08 19:36:54 -04:00
Ilya246 f406be9a5d 2x Drone Targeting Range (#3728) 2026-04-08 19:36:44 -04:00
monolith8319 002ba09258 Automated Changelog (#3723) 2026-04-07 18:36:29 +00:00
Ilya246 f89e18df88 Kill Gunnery Server Thermal Sigs (#3723) 2026-04-07 14:35:47 -04:00
monolith8319 a7dcf1afea Automated Changelog (#3720) 2026-04-07 01:44:26 +00:00
EckoAurum 670785fd48 HotFix - AI Control Round Remove (#3720) 2026-04-06 21:43:58 -04:00
Ilya246 654a657788 Hotfix Company Owner Setting (#3714) 2026-04-06 18:01:33 +00:00
monolith8319 52137b967a Automated Changelog (#3716) 2026-04-06 17:16:14 +00:00
Redrover1760 8e0bff68cb Siebel Blast Doors Start Open (#3716) 2026-04-06 13:15:41 -04:00
monolith8319 af99be86ed Automated Changelog (#3707) 2026-04-06 15:53:43 +00:00
significant harassment 3ed4f7cc1a Fmc/Dc Value Buff (#3707)
Co-authored-by: Onezero0 <onezero0@outlook.com>
2026-04-06 11:53:14 -04:00
Ilya246 e72a06c19d emergency postgres migration fix 2026-04-06 16:42:30 +04:00
monolith8319 6619538fb9 Automated Changelog (#3709) 2026-04-05 22:14:21 +00:00
Redrover1760 005950f2a0 Hristov Structrual Buff (#3709) 2026-04-05 18:13:51 -04:00
monolith8319 468be1435c Automated Changelog (#3334) 2026-04-05 21:43:24 +00:00
Redrover1760 685422209e Increases 57mm Explosive Damage (#3334)
Co-authored-by: NazrinNya <137837419+NazrinNya@users.noreply.github.com>
2026-04-05 17:42:55 -04:00
monolith8319 ae58c4d021 Automated Changelog (#3075) 2026-04-05 21:35:16 +00:00
significant harassment 464975916d Kino Weapons Update - Finished (#3075)
Co-authored-by: Onezero0 <onezero0@outlook.com>
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-05 17:34:46 -04:00
monolith8319 2f39b9edce Automated Changelog (#3708) 2026-04-05 20:31:10 +00:00
10KE a18831a0c5 Update Shift Duration in Guidebook (#3708) 2026-04-05 16:30:35 -04:00
monolith8319 42dcb56e22 Automated Changelog (#3706) 2026-04-05 18:43:20 +00:00
Ilya246 3eeb83a16e Kill Nf Console Depower Autoanchor (#3706) 2026-04-05 18:42:53 +00:00
monolith8319 abf8bd7218 Automated Changelog (#3583) 2026-04-05 18:34:22 +00:00
Redrover1760 c6de815de4 Refactors and Depowercreeps Shipgun HP (#3583) 2026-04-05 14:33:51 -04:00
Maxim Bychkov 53b02b5830 Unhardcode Companies Whitelist (#3553)
Co-authored-by: Ilya246 <ilyukarno@gmail.com>
2026-04-05 18:32:46 +00:00
monolith8319 40d2f70e08 Automated Changelog (#3691) 2026-04-05 18:23:35 +00:00
Redrover1760 1791e631f7 Adjusts ADS Spawntable and Decreases Wyvern Spawnrate (#3691) 2026-04-05 14:23:06 -04:00
monolith8319 f79b69a3f6 Automated Changelog (#3688) 2026-04-05 18:04:46 +00:00
Redrover1760 b8d5ca7022 Scythe Firerate Nerf (#3688) 2026-04-05 14:03:54 -04:00
monolith8319 f37ef8c994 Automated Changelog (#3489) 2026-04-05 14:22:53 +00:00
Corrupt b4ae011b87 RSC-Serkesh (#3489)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
2026-04-05 16:21:51 +02:00
monolith8319 0adcb3c8a4 Automated Changelog (#3696) 2026-04-05 14:00:48 +00:00
NazrinNya ad1a263596 Lawbringer Port [Bounty] (#3696)
Co-authored-by: Centronias <me@centronias.com>
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-04-05 17:00:18 +03:00
monolith8319 591a6ba333 Automated Changelog (#3699) 2026-04-05 12:21:54 +00:00
Ilya246 ebf2e56159 Hotfix New Drone Targeting (#3699) 2026-04-05 12:21:26 +00:00
monolith8319 c6b36bf126 Automated Changelog (#3697) 2026-04-05 10:31:02 +00:00
NazrinNya 2cd30797ac Revert "Stinky Trait (#594)" (#3697)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-04-05 13:30:34 +03:00
monolith8319 2aad60a3d7 Automated Changelog (#3693) 2026-04-05 06:49:30 +00:00
Ilya246 f561fcb45e Make Drones Target People in Space (#3693) 2026-04-05 06:49:03 +00:00
monolith8319 a68c0d48ba Automated Changelog (#3521) 2026-04-04 21:45:28 +00:00
Mrrpip ec2c5a521d Shortens the Names of Missile Launchers. (#3521)
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-04 17:44:56 -04:00
monolith8319 39efe6f747 Automated Changelog (#3592) 2026-04-04 21:38:41 +00:00
Redrover1760 0816b4457c Changes Jetpack Stealth to Detection Radius (#3592) 2026-04-04 17:38:13 -04:00
monolith8319 6c87da3dc3 Automated Changelog (#3535) 2026-04-04 20:50:20 +00:00
Ghost581 05d4b2eebd Viper Group Tech Tree and Jugsuit Slowdown Alleviation + More YAML Roadrage (#3535) 2026-04-04 16:49:50 -04:00
monolith8319 6ee1e7d8f3 Automated Changelog (#3658) 2026-04-04 19:54:13 +00:00
EckoAurum f097148d99 Ai Control Port (#3658) 2026-04-04 19:53:42 +00:00
monolith8319 ef2ce6565d Automated Changelog (#3686) 2026-04-04 13:44:43 +00:00
EckoAurum 98ad67497b Fix Mechlag (Hopefully) (#3686) 2026-04-04 13:44:14 +00:00
monolith8319 67644068b6 Automated Changelog (#3332) 2026-04-04 11:15:08 +00:00
TheScienceOfKSP 1b64380dbb SEC KS-P Pacem Class Corvette (#3332)
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
Co-authored-by: Corrupt <compeldominik6@gmail.com>
2026-04-04 13:14:40 +02:00
monolith8319 465006f16b Automated Changelog (#3685) 2026-04-04 03:50:48 +00:00
Redrover1760 e1ec12fb3e Updates Datafarm Descriptions (#3685) 2026-04-03 23:50:20 -04:00
Redrover1760 d62e3a42d0 Lazy Yaml Slop Fix (#3683) 2026-04-03 21:21:06 -04:00
Redrover1760 02be1ef282 Merge branch 'main' of https://github.com/Monolith-Station/Monolith 2026-04-03 20:48:51 -04:00
Redrover1760 85c4a0c9ec fixes grilles protecting biomass 2026-04-03 20:48:12 -04:00
monolith8319 baeb53ee82 Automated Changelog (#3578) 2026-04-03 18:29:42 +00:00
Redrover1760 933dc75c33 Buffs Standard Borg HP, Removes Negative Burn Armor (#3578) 2026-04-03 21:29:10 +03:00
monolith8319 fc45f016ee Automated Changelog (#3602) 2026-04-03 18:28:53 +00:00
Ilya246 6a719f7c39 Adc Piloting Boost (#3602) 2026-04-03 21:28:23 +03:00
monolith8319 d83db373fe Automated Changelog (#3667) 2026-04-03 18:27:01 +00:00
monolith8319 ccec7894c6 Automated Changelog (#3680) 2026-04-03 18:26:27 +00:00
Redrover1760 3319b5f0d3 Makes Tsf Mech Blips Lighter (#3667) 2026-04-03 21:26:26 +03:00
EldritchPineapple 81d9ae37e3 Medicated Suture and Meshes Buff (#3680) 2026-04-03 21:25:56 +03:00
monolith8319 1990f2a272 Automated Changelog (#3636) 2026-04-03 17:46:40 +00:00
Kiithnaras 3665639420 Laser Weapon Improvements (#3636) 2026-04-03 20:46:11 +03:00
monolith8319 20e2ceb0db Automated Changelog (#3673) 2026-04-03 17:45:43 +00:00
chuga-git 84a116fd27 Torpedo/GPO Speed Buffs (#3673) 2026-04-03 20:45:12 +03:00
monolith8319 c3c402d380 Automated Changelog (#3681) 2026-04-03 17:43:07 +00:00
Redrover1760 92e8677e82 Research Datafarms Buffs (#3681) 2026-04-03 20:42:36 +03:00
monolith8319 65365135e3 Automated Changelog (#3285) 2026-04-03 11:09:38 +00:00
monolith8319 62e372c9d2 Automated Changelog (#3195) 2026-04-03 11:09:08 +00:00
NazrinNya 4279c1977d Hullmods (#3285)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
2026-04-03 14:08:54 +03:00
NazrinNya 6406a7cf4f Non-POI Vends Are Now Free. (#3195)
Co-authored-by: NazrinNya <macrohard@macrohard.localhost>
Co-authored-by: UnicornOnLSD <149102472+UnicornOnLSD@users.noreply.github.com>
Co-authored-by: Redrover1760 <39284090+Redrover1760@users.noreply.github.com>
2026-04-03 14:08:39 +03:00
monolith8319 806e22e8b4 Automated Changelog (#3679) 2026-04-03 09:51:27 +00:00
EckoAurum c2934d0bd7 Fix Borg Hand Whitelist Bug (#3679) 2026-04-03 09:50:59 +00:00
monolith8319 5316f1fdd9 Automated Changelog (#3678) 2026-04-03 09:04:37 +00:00
Ilya246 66fed8d2a2 Kill Shield Ramming (#3678) 2026-04-03 12:04:09 +03:00
monolith8319 28e3584a67 Automated Changelog (#3677) 2026-04-02 18:53:22 +00:00
Ilya246 78c94ddbc4 Far Reaches IV, New Drone (#3677) 2026-04-02 20:52:53 +02:00
monolith8319 cef95a03bf Automated Changelog (#3559) 2026-04-02 17:25:51 +00:00
Ilya246 bd3a35ee64 New Drone: Bracket, Refactor Drone Yaml a Bit (#3559) 2026-04-02 17:25:02 +00:00
Redrover1760 0b50c919e4 Stops Sloppy Vgroid Debug/Localhost Crashing (#3597) 2026-04-02 10:37:55 -04:00
monolith8319 afcf994545 Automated Changelog (#3672) 2026-04-01 22:09:20 +00:00
UnicornOnLSD 5aaee26a09 Adds a Single OREBOX to the DIS Rig (#3672) 2026-04-02 00:08:49 +02:00
monolith8319 fb3c49bd09 Automated Changelog (#3635) 2026-04-01 16:22:35 +00:00
Redrover1760 2c381cbcd0 Makes Biomass Less Painful to Deal With (#3635) 2026-04-01 12:22:03 -04:00
Ilya246 f4e3ac2478 Fix Logspam 2 (#3661) 2026-03-31 09:12:58 -04:00
Anri 31de4af5e2 Remove Repeat Work in ThermalSignatureSystem (#3631)
Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com>
2026-03-31 11:28:47 +00:00
Ilya246 5e67e63032 Fix Server Log Spam (#3657) 2026-03-31 06:46:54 +00: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 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 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 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
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 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 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 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
mikus c5241854ce [April 1st] Piras314 Wrote This (#3606) 2026-03-29 13:57:07 +00:00
mikus c3f687a493 [April 1st] Rebalances Gunnery Controls (#3605) 2026-03-29 13:40:19 +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
752 changed files with 72674 additions and 43545 deletions
+12 -12
View File
@@ -10,18 +10,18 @@ jobs:
if: ${{github.head_ref == 'master' || github.head_ref == 'main' || github.head_ref == 'develop'}}
steps:
# - uses: superbrothers/close-pull-request@v3
# with:
# comment: "Thank you for contributing to the Space Station 14 repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch."
- uses: superbrothers/close-pull-request@v3
with:
comment: "Thank you for contributing to the Monolith repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch."
# If you prefer to just comment on the pr and not close it, uncomment the bellow and comment the above
- uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: ${{ github.event.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: "Thank you for contributing to the Space Station 14 repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch. \n\n This pr won't be automatically closed. However, a maintainer may close it for this reason."
})
#- uses: actions/github-script@v7
# with:
# script: |
# github.rest.issues.createComment({
# issue_number: ${{ github.event.number }},
# owner: context.repo.owner,
# repo: context.repo.repo,
# body: "Thank you for contributing to the Monolith repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name>` and resetting the master branch. \n\n This pr won't be automatically closed. However, a maintainer may close it for this reason."
# })
+3
View File
@@ -38,6 +38,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Replays;
using Robust.Shared.Timing;
using Content.Client._NF.Emp.Overlays; // Frontier
using Content.Client._Mono.Company; // Mono
namespace Content.Client.Entry
{
@@ -75,6 +76,7 @@ namespace Content.Client.Entry
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly CompanyManager _companyManager = default!; // Mono
public override void Init()
{
@@ -137,6 +139,7 @@ namespace Content.Client.Entry
_extendedDisconnectInformation.Initialize();
_jobRequirements.Initialize();
_playbackMan.Initialize();
_companyManager.Initialize();
//AUTOSCALING default Setup!
_configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffX", 1080);
+2
View File
@@ -24,6 +24,7 @@ using Content.Shared.Administration.Managers;
using Content.Shared.Chat;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Players.RateLimiting;
using Content.Client._Mono.Company; // Mono
namespace Content.Client.IoC
{
@@ -60,6 +61,7 @@ namespace Content.Client.IoC
collection.Register<PlayerRateLimitManager>();
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
collection.Register<TitleWindowManager>();
collection.Register<CompanyManager>(); // Mono
}
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ public sealed class HandheldLightSystem : SharedHandheldLightSystem
return;
}
if (!_appearance.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, args.Component))
if (!_appearance.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, args.Component))
{
return;
}
+4 -1
View File
@@ -1,4 +1,5 @@
using System.Linq;
using Content.Client._Mono.Company; // Mono
using Content.Client._Mono.MonoCoins;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
@@ -41,6 +42,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly JobRequirementsManager _requirements = default!;
[Dependency] private readonly MarkingManager _markings = default!;
[Dependency] private readonly CompanyManager _companyManager = default!; // Mono
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
@@ -350,7 +352,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
_prototypeManager,
_resourceCache,
_requirements,
_markings);
_markings,
_companyManager); // Mono
_profileEditor.OnOpenGuidebook += _guide.OpenHelp;
@@ -35,6 +35,7 @@ using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Direction = Robust.Shared.Maths.Direction;
using Content.Client._Mono.Company;
namespace Content.Client.Lobby.UI
{
@@ -52,6 +53,7 @@ namespace Content.Client.Lobby.UI
private readonly JobRequirementsManager _requirements;
private readonly LobbyUIController _controller;
private readonly EntityWhitelistSystem _whitelist; // Frontier
private readonly CompanyManager _companyManager; // Mono
private FlavorText.FlavorText? _flavorText;
private TextEdit? _flavorTextEdit;
@@ -116,7 +118,8 @@ namespace Content.Client.Lobby.UI
IPrototypeManager prototypeManager,
IResourceManager resManager,
JobRequirementsManager requirements,
MarkingManager markings)
MarkingManager markings,
CompanyManager manager) // Mono
{
RobustXamlLoader.Load(this);
_sawmill = logManager.GetSawmill("profile.editor");
@@ -127,6 +130,7 @@ namespace Content.Client.Lobby.UI
_prototypeManager = prototypeManager;
_markingManager = markings;
_preferencesManager = preferencesManager;
_companyManager = manager; // Mono
_resManager = resManager;
_requirements = requirements;
_controller = UserInterfaceManager.GetUIController<LobbyUIController>();
@@ -446,12 +450,9 @@ namespace Content.Client.Lobby.UI
// Clear any existing items
CompanyButton.Clear();
var username = _playerManager.LocalPlayer?.Session?.Name; //Lua modified - company login support
// Add all companies from prototypes - use consistent sorting with UpdateCompanyControls
var companies = _prototypeManager.EnumeratePrototypes<CompanyPrototype>()
//.Where(c => !c.Disabled) // Filter out disabled companies
.Where(c => !c.Disabled || (username != null && c.Logins.Contains(username))) //Lua modified - company login support
.Where(c => !c.Disabled && _companyManager.IsAllowed(c.ID))
.ToList();
companies.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
@@ -2194,11 +2195,8 @@ namespace Content.Client.Lobby.UI
if (Profile is null)
return;
var username = _playerManager.LocalPlayer?.Session?.Name; //Lua modified - company login support
var companies = _prototypeManager.EnumeratePrototypes<CompanyPrototype>()
//.Where(c => !c.Disabled) // Filter out disabled companies
.Where(c => !c.Disabled || (username != null && c.Logins.Contains(username))) //Lua modified - company login support
.Where(c => !c.Disabled && _companyManager.IsAllowed(c.ID))
.ToList();
companies.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
@@ -77,6 +77,16 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
if (_selected is {} selected && !_cyborgs.ContainsKey(selected))
_selected = null;
// Corvax-Next-AiRemoteControl-Start
var isAiControllable = false;
if (_selected != null)
{
_cyborgs.TryGetValue(_selected, out var data);
isAiControllable = data.IsAiControllable;
}
// Corvax-Next-AiRemoteControl-End
var hasCyborgs = _cyborgs.Count > 0;
NoCyborgs.Visible = !hasCyborgs;
CyborgsContainer.Visible = hasCyborgs;
@@ -138,6 +138,293 @@ public partial class BaseShuttleControl : MapGridControl
}
// End Frontier Corvax
// Mono
protected void DrawAzimuthScale(DrawingHandleScreen handle, Angle rotation)
{
const int azimuthMinorTickStepDegrees = 10;
const int azimuthMajorTickInterval = 3;
const int azimuthCardinalTickInterval = 9;
const float edgeInset = 10f;
const float tickInset = 4f;
const float minorTickLength = 5f;
const float majorTickLength = 9f;
const float cardinalTickLength = 12f;
const float labelPadding = 3f;
const float labelOffset = tickInset + cardinalTickLength + labelPadding;
const float regularLabelScale = 0.62f;
const float cardinalLabelScale = 0.72f;
var origin = MidPointVector;
var viewportSize = new Vector2(PixelWidth, PixelHeight);
var subtleGray = Color.FromHex("#676767");
var northAccent = Color.FromHex("#7F7442");
var eastAccent = Color.FromHex("#456273");
var baseTickColor = subtleGray.WithAlpha(0.34f);
var majorTickColor = subtleGray.WithAlpha(0.5f);
var cardinalTickColor = subtleGray.WithAlpha(0.68f);
var regularLabelColor = subtleGray.WithAlpha(0.76f);
var cardinalLabelColor = subtleGray.WithAlpha(0.9f);
var labelShadowColor = Color.Black.WithAlpha(0.36f);
for (var step = 0; step < 360 / azimuthMinorTickStepDegrees; step++)
{
var degrees = step * azimuthMinorTickStepDegrees;
var direction = GetAzimuthDirection(rotation, degrees);
var isCardinal = step % azimuthCardinalTickInterval == 0;
var isMajor = step % azimuthMajorTickInterval == 0;
var isNorth = degrees == 0;
var isEast = degrees == 90;
var tickLength = isCardinal
? cardinalTickLength
: isMajor
? majorTickLength
: minorTickLength;
if (!TryGetViewportEdgePoint(origin, direction, viewportSize, edgeInset, out var edgePoint, out var inwardNormal))
continue;
var outer = edgePoint + inwardNormal * tickInset;
var inner = outer + inwardNormal * tickLength;
var tickColor = isNorth
? northAccent.WithAlpha(0.58f)
: isEast
? eastAccent.WithAlpha(0.58f)
: isCardinal
? cardinalTickColor
: isMajor
? majorTickColor
: baseTickColor;
handle.DrawLine(outer, inner, tickColor);
if (isNorth)
{
var sideOffset = new Vector2(-inwardNormal.Y, inwardNormal.X) * 0.35f;
handle.DrawLine(outer + sideOffset, inner + sideOffset, tickColor.WithAlpha(0.46f));
}
if (!isMajor)
continue;
var label = degrees.ToString();
var labelScale = isCardinal ? cardinalLabelScale : regularLabelScale;
var labelColor = isEast
? eastAccent.WithAlpha(0.76f)
: isCardinal
? cardinalLabelColor
: regularLabelColor;
var labelSize = handle.GetDimensions(Font, label, labelScale);
var labelPosition = GetEdgeLabelPosition(edgePoint, inwardNormal, labelSize, viewportSize, labelOffset);
handle.DrawString(Font, labelPosition + new Vector2(1f, 1f), label, labelScale, labelShadowColor);
handle.DrawString(Font, labelPosition, label, labelScale, labelColor);
}
}
protected void DrawAzimuthRing(DrawingHandleScreen handle, Angle rotation)
{
const int azimuthMinorTickStepDegrees = 10;
const int azimuthMajorTickInterval = 3;
const int azimuthCardinalTickInterval = 9;
const float ringInset = 16f;
const float minorTickLength = 5f;
const float majorTickLength = 9f;
const float cardinalTickLength = 12f;
const float labelPadding = 5f;
const float regularLabelScale = 0.62f;
const float cardinalLabelScale = 0.72f;
var origin = MidPointVector;
var radius = MathF.Max(8f, SizeFull * 0.5f - ringInset);
var subtleGray = Color.FromHex("#676767");
var northAccent = Color.FromHex("#7F7442");
var eastAccent = Color.FromHex("#456273");
var baseTickColor = subtleGray.WithAlpha(0.34f);
var majorTickColor = subtleGray.WithAlpha(0.5f);
var cardinalTickColor = subtleGray.WithAlpha(0.68f);
var regularLabelColor = subtleGray.WithAlpha(0.76f);
var cardinalLabelColor = subtleGray.WithAlpha(0.9f);
var labelShadowColor = Color.Black.WithAlpha(0.36f);
handle.DrawCircle(origin, radius, subtleGray.WithAlpha(0.12f), false);
for (var step = 0; step < 360 / azimuthMinorTickStepDegrees; step++)
{
var degrees = step * azimuthMinorTickStepDegrees;
var direction = GetAzimuthDirection(rotation, degrees);
var isCardinal = step % azimuthCardinalTickInterval == 0;
var isMajor = step % azimuthMajorTickInterval == 0;
var isNorth = degrees == 0;
var isEast = degrees == 90;
var tickLength = isCardinal
? cardinalTickLength
: isMajor
? majorTickLength
: minorTickLength;
var outer = origin + direction * radius;
var inner = origin + direction * (radius - tickLength);
var tickColor = isNorth
? northAccent.WithAlpha(0.58f)
: isEast
? eastAccent.WithAlpha(0.58f)
: isCardinal
? cardinalTickColor
: isMajor
? majorTickColor
: baseTickColor;
handle.DrawLine(outer, inner, tickColor);
if (isNorth)
{
var sideOffset = new Vector2(-direction.Y, direction.X) * 0.35f;
handle.DrawLine(outer + sideOffset, inner + sideOffset, tickColor.WithAlpha(0.46f));
}
if (!isMajor)
continue;
var label = degrees.ToString();
var labelScale = isCardinal ? cardinalLabelScale : regularLabelScale;
var labelColor = isEast
? eastAccent.WithAlpha(0.76f)
: isCardinal
? cardinalLabelColor
: regularLabelColor;
var labelSize = handle.GetDimensions(Font, label, labelScale);
var labelPosition = origin + direction * (radius - tickLength - labelPadding) - labelSize * 0.5f;
handle.DrawString(Font, labelPosition + new Vector2(1f, 1f), label, labelScale, labelShadowColor);
handle.DrawString(Font, labelPosition, label, labelScale, labelColor);
}
}
protected void DrawCompassOverlay(DrawingHandleScreen handle, Angle heading)
{
var compassCenter = new Vector2(44f, 44f);
const float compassRadius = 24f;
var ringColor = Color.White.WithAlpha(0.08f);
var northColor = Color.FromHex("#776C43");
var eastColor = Color.FromHex("#4C6572");
handle.DrawCircle(compassCenter, compassRadius, ringColor, false);
handle.DrawCircle(compassCenter, 2.5f, ringColor.WithAlpha(0.48f), true);
DrawCompassNeedle(handle, compassCenter, compassRadius, GetAzimuthDirection(heading, 0f), "N", northColor);
DrawCompassNeedle(handle, compassCenter, compassRadius * 0.82f, GetAzimuthDirection(heading, 90f), "E", eastColor);
}
private void DrawCompassNeedle(
DrawingHandleScreen handle,
Vector2 compassCenter,
float radius,
Vector2 direction,
string label,
Color color)
{
var tip = compassCenter + direction * radius;
var tail = compassCenter - direction * (radius * 0.35f);
DrawArrowLine(handle, tail, tip, color.WithAlpha(0.58f), 6f);
var labelSize = handle.GetDimensions(Font, label, 0.9f);
var labelPosition = tip + direction * 6f - new Vector2(labelSize.X * 0.5f, labelSize.Y * 0.5f);
handle.DrawString(Font, labelPosition, label, 0.9f, color.WithAlpha(0.7f));
}
private static void DrawArrowLine(
DrawingHandleScreen handle,
Vector2 start,
Vector2 end,
Color color,
float headLength)
{
var direction = end - start;
if (direction.LengthSquared() <= 0.001f)
return;
direction = Vector2.Normalize(direction);
var left = new Vector2(-direction.Y, direction.X);
handle.DrawLine(start, end, color);
handle.DrawLine(end, end - direction * headLength + left * (headLength * 0.45f), color);
handle.DrawLine(end, end - direction * headLength - left * (headLength * 0.45f), color);
}
private static bool TryGetViewportEdgePoint(
Vector2 origin,
Vector2 direction,
Vector2 viewportSize,
float inset,
out Vector2 edgePoint,
out Vector2 inwardNormal)
{
edgePoint = default;
inwardNormal = default;
if (direction.LengthSquared() <= 0.001f)
return false;
direction = Vector2.Normalize(direction);
var min = new Vector2(inset, inset);
var max = viewportSize - min;
if (max.X <= min.X || max.Y <= min.Y)
return false;
var tx = direction.X > 0f
? (max.X - origin.X) / direction.X
: direction.X < 0f
? (min.X - origin.X) / direction.X
: float.PositiveInfinity;
var ty = direction.Y > 0f
? (max.Y - origin.Y) / direction.Y
: direction.Y < 0f
? (min.Y - origin.Y) / direction.Y
: float.PositiveInfinity;
var t = Math.Min(tx, ty);
if (!float.IsFinite(t) || t <= 0f)
return false;
edgePoint = origin + direction * t;
if (tx < ty)
inwardNormal = direction.X > 0f ? -Vector2.UnitX : Vector2.UnitX;
else
inwardNormal = direction.Y > 0f ? -Vector2.UnitY : Vector2.UnitY;
return true;
}
private static Vector2 GetEdgeLabelPosition(
Vector2 edgePoint,
Vector2 inwardNormal,
Vector2 labelSize,
Vector2 viewportSize,
float offset)
{
var anchor = edgePoint + inwardNormal * offset;
Vector2 requested;
if (Math.Abs(inwardNormal.X) > Math.Abs(inwardNormal.Y))
{
requested = inwardNormal.X > 0f
? new Vector2(anchor.X, anchor.Y - labelSize.Y * 0.5f)
: new Vector2(anchor.X - labelSize.X, anchor.Y - labelSize.Y * 0.5f);
}
else
{
requested = inwardNormal.Y > 0f
? new Vector2(anchor.X - labelSize.X * 0.5f, anchor.Y)
: new Vector2(anchor.X - labelSize.X * 0.5f, anchor.Y - labelSize.Y);
}
var minPosition = new Vector2(offset, offset);
var maxPosition = viewportSize - labelSize - minPosition;
return Vector2.Clamp(requested, minPosition, maxPosition);
}
private static Vector2 GetAzimuthDirection(Angle baseAngle, float azimuthDegrees)
{
var angle = baseAngle + Angle.FromDegrees(azimuthDegrees);
var radians = (float) angle.Theta - MathF.PI / 2f;
return new Vector2(MathF.Cos(radians), MathF.Sin(radians));
}
// End Mono
protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 gridToView, Entity<MapGridComponent> grid, Color color, float alpha = 0.01f)
{
var rator = Maps.GetAllTilesEnumerator(grid.Owner, grid.Comp);
@@ -28,7 +28,7 @@
Orientation="Vertical">
<!-- FTL Lock Section -->
<controls1:StripeBack MinSize="48 48">
<controls:Label Text="{controls:Loc 'FTL Lock'}" HorizontalAlignment="Center"/>
<controls:Label Text="{controls:Loc 'shuttle-console-ftl-lock'}" HorizontalAlignment="Center"/>
</controls1:StripeBack>
<controls:PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="4 4 4 4">
<controls:PanelContainer.PanelOverride>
@@ -36,12 +36,12 @@
</controls:PanelContainer.PanelOverride>
<controls:BoxContainer Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="5 5 5 5">
<controls:Button Name="FTLLockEnabledButton"
Text="{controls:Loc 'Enabled'}"
Text="{controls:Loc 'shuttle-console-ftl-lock-enabled'}"
ToggleMode="True"
HorizontalExpand="True"
StyleClasses="OpenRight"/>
<controls:Button Name="FTLLockDisabledButton"
Text="{controls:Loc 'Disabled'}"
Text="{controls:Loc 'shuttle-console-ftl-lock-disabled'}"
ToggleMode="True"
HorizontalExpand="True"
StyleClasses="OpenLeft"/>
@@ -50,7 +50,7 @@
<!-- Docks Section -->
<controls1:StripeBack MinSize="48 48">
<controls:Label Text="{controls:Loc 'Docks'}" HorizontalAlignment="Center"/>
<controls:Label Text="{controls:Loc 'shuttle-console-docks-label'}" HorizontalAlignment="Center"/>
</controls1:StripeBack>
<controls:PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="4 4 4 4">
<controls:PanelContainer.PanelOverride>
@@ -62,7 +62,7 @@
<graphics:StyleBoxFlat BorderThickness="2" BorderColor="#333333" BackgroundColor="#121212"/>
</controls:PanelContainer.PanelOverride>
<controls:Button Name="UndockAllButton"
Text="{controls:Loc 'Undock All'}"
Text="{controls:Loc 'shuttle-console-undock-all'}"
Margin="5 5 5 5"
StyleClasses="ButtonSquare"/>
</controls:PanelContainer>
+16 -10
View File
@@ -4,12 +4,14 @@
xmlns:ui="clr-namespace:Content.Client.Shuttles.UI"
xmlns:controls1="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<ui:ShuttleNavControl Name="NavRadar"
MouseFilter="Stop"
VerticalAlignment="Stretch"
VerticalExpand="True"
HorizontalExpand="True"
Margin="5 4 10 5"/>
<!-- Mono - wrap -->
<controls:LayoutContainer Margin="5 4 10 5">
<ui:ShuttleNavControl Name="NavRadar"
MouseFilter="Stop"
VerticalAlignment="Stretch"
VerticalExpand="True"
HorizontalExpand="True"/>
</controls:LayoutContainer>
<!-- Nav controls -->
<controls:PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="4 4 4 4">
<controls:PanelContainer.PanelOverride>
@@ -173,11 +175,8 @@
<!-- Frontier - Maximum Shuttle Speed -->
<controls:BoxContainer Orientation="Vertical" HorizontalExpand="True" Name="MaximumShuttleSpeedBox" Margin="4 4 4 4">
<controls:Label Text="{controls:Loc 'shuttle-console-maximum-speed'}"/>
<controls:SliderIntInput Name="MaximumShuttleSpeedValue"
<controls:LineEdit Name="MaximumShuttleSpeedValue"
Access="Public"
MinValue="0"
MaxValue="140"
Value="140"
HorizontalExpand="True"/>
</controls:BoxContainer>
</controls:BoxContainer>
@@ -215,36 +214,43 @@
Text="{controls:Loc 'shuttle-console-device-button-1'}"
TextAlign="Center"
StyleClasses="ButtonSquare"
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
SetWidth="55"/>
<controls:Button Name="DeviceButton2"
Text="{controls:Loc 'shuttle-console-device-button-2'}"
TextAlign="Center"
StyleClasses="ButtonSquare"
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
SetWidth="55"/>
<controls:Button Name="DeviceButton3"
Text="{controls:Loc 'shuttle-console-device-button-3'}"
TextAlign="Center"
StyleClasses="ButtonSquare"
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
SetWidth="55"/>
<controls:Button Name="DeviceButton4"
Text="{controls:Loc 'shuttle-console-device-button-4'}"
TextAlign="Center"
StyleClasses="ButtonSquare"
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
SetWidth="55"/>
<controls:Button Name="DeviceButton5"
Text="{controls:Loc 'shuttle-console-device-button-5'}"
TextAlign="Center"
StyleClasses="ButtonSquare"
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
SetWidth="55"/>
<controls:Button Name="DeviceButton6"
Text="{controls:Loc 'shuttle-console-device-button-6'}"
TextAlign="Center"
StyleClasses="ButtonSquare"
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
SetWidth="55"/>
<controls:Button Name="DeviceButton7"
Text="{controls:Loc 'shuttle-console-device-button-7'}"
TextAlign="Center"
StyleClasses="ButtonSquare"
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
SetWidth="55"/>
<controls:Button Name="DeviceButton8"
Text="{controls:Loc 'shuttle-console-device-button-8'}"
+4 -3
View File
@@ -176,14 +176,15 @@ public sealed partial class NavScreen : BoxContainer
var (_, worldRot, worldMatrix) = _xformSystem.GetWorldPositionRotationMatrix(gridXform);
var worldPos = Vector2.Transform(gridBody.LocalCenter, worldMatrix);
// Get the positive reduced angle.
var displayRot = -worldRot.Reduced();
// Mono - remap to [0, 360)
var displayRot = (-worldRot).Reduced();
var displayRotDegrees = displayRot.FlipPositive().Degrees;
GridPosition.Text = Loc.GetString("shuttle-console-position-value",
("X", $"{worldPos.X:0.0}"),
("Y", $"{worldPos.Y:0.0}"));
GridOrientation.Text = Loc.GetString("shuttle-console-orientation-value",
("angle", $"{displayRot.Degrees:0.0}"));
("angle", $"{displayRotDegrees:0.0}"));
var gridVelocity = gridBody.LinearVelocity;
gridVelocity = displayRot.RotateVec(gridVelocity);
@@ -4,8 +4,10 @@
Title="{Loc 'radar-console-window-title'}"
SetSize="648 672"
MinSize="256 256">
<ui:ShuttleNavControl Name="RadarScreen"
Margin="4"
HorizontalExpand = "True"
VerticalExpand = "True"/>
<LayoutContainer Margin="4">
<!-- Mono - wrap -->
<ui:ShuttleNavControl Name="RadarScreen"
HorizontalExpand = "True"
VerticalExpand = "True"/>
</LayoutContainer>
</controls:FancyWindow>
@@ -13,6 +13,7 @@ using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Map;
@@ -45,9 +46,10 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
/// <summary>
/// Entity of controlling console
/// </summary>
private EntityUid? _consoleEntity;
protected EntityUid? _consoleEntity; // Mono
protected Angle? _rotation; // Mono
// Mono - is world rotation of the view.
protected Angle? _rotation = Angle.Zero;
private Dictionary<NetEntity, List<DockingPortState>> _docks = new();
@@ -65,7 +67,6 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
public bool ShowIFF { get; set; } = true;
public bool ShowIFFShuttles { get; set; } = true;
public bool ShowDocks { get; set; } = true;
public bool RotateWithEntity { get; set; } = true;
public float MaximumIFFDistance { get; set; } = 3000f; // Frontier // Mono - 3000 by default to not gigaclutter
public bool HideCoords { get; set; } = false; // Frontier
@@ -88,14 +89,16 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
public List<EntityUid>? Detectors = null;
#region Mono
public bool RelativePanning = false;
// These 2 handle timing updates
protected const float RadarUpdateInterval = 0f;
protected float _updateAccumulator = 0f;
// doesn't support absolute panning and angle follow, but why would you need that?
protected bool _relativePanning = false;
protected bool _angleFollow = true;
private bool _radarModeDefaultsApplied;
private bool _wasPanned = false;
private EntityCoordinates? _oldCoordinates;
#endregion
protected bool _isMouseDown;
@@ -104,6 +107,20 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
protected float _lastFireTime;
protected const float FireRateLimit = 0.1f; // 100ms between shots
private enum RadarAzimuthMode : byte
{
Edge,
Ring,
Off,
}
private BoxContainer? _radarModeButtons;
private RadarModeButton? _radarAzimuthButton;
private RadarModeButton? _radarRotationButton;
private RadarModeButton? _radarAnchorButton;
private RadarModeButton? _radarResetButton;
private RadarAzimuthMode _azimuthMode;
public ShuttleNavControl() : this(64f, 256f, 256f) { } // Mono
public ShuttleNavControl(float minRange, float maxRange, float range) : base(minRange, maxRange, range) // Mono
@@ -135,12 +152,279 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
return;
_coordinates = coordinates;
_rotation = angle;
// Mono - TODO: either remove this or make this do anything
// _rotation = angle;
}
public void SetConsole(EntityUid? consoleEntity)
{
if (_consoleEntity == consoleEntity)
return;
_consoleEntity = consoleEntity;
_radarModeDefaultsApplied = false;
ApplyRadarModeDefaults();
}
// converts Offset to world-relative offset so we can modify radar parameters without conversion headaches
// call this before potentially rotation-modifying operations and PanToEnt() after
private void PanToWorld()
{
if (!_angleFollow || _coordinates is not { } cord)
return;
var coordEntRot = _transform.GetWorldRotation(cord.EntityId);
Offset = coordEntRot.RotateVec(Offset);
}
// converts world-relative Offset to relative to current view
// call this after potentially rotation-modifying operations and PanToWorld() before
private void PanToEnt()
{
if (!_angleFollow || _coordinates is not { } cord)
return;
var coordEntRot = _transform.GetWorldRotation(cord.EntityId);
Offset = (-coordEntRot).RotateVec(Offset);
}
// reanchor our view to console or grid
private void ReanchorView()
{
if (_consoleEntity is not { } cons
|| _coordinates is not { } cord
|| !EntManager.TryGetComponent<TransformComponent>(cons, out var xform))
return;
PanToWorld();
var anchTo = cons;
if (xform.GridUid != null)
anchTo = xform.GridUid.Value;
_coordinates = _transform.WithEntityId(cord, anchTo);
_relativePanning = true;
PanToEnt();
}
private void EnsureRadarModeButtons()
{
if (_radarModeButtons != null)
return;
if (Parent is not LayoutContainer parent)
return;
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
_radarModeButtons = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 2,
HorizontalExpand = false,
VerticalExpand = false,
MouseFilter = MouseFilterMode.Stop,
};
_radarAzimuthButton = CreateRadarModeButton(RadarModeButtonIcon.Azimuth);
_radarAzimuthButton.ToolTip = Loc.GetString("shuttle-console-button-azimuth");
_radarAzimuthButton.OnPressed += _ =>
{
_azimuthMode = _azimuthMode switch
{
RadarAzimuthMode.Edge => RadarAzimuthMode.Ring,
RadarAzimuthMode.Ring => RadarAzimuthMode.Off,
_ => RadarAzimuthMode.Edge,
};
};
_radarRotationButton = CreateRadarModeButton(RadarModeButtonIcon.Rotation);
_radarRotationButton.ToolTip = Loc.GetString("shuttle-console-button-rotation");
_radarRotationButton.OnPressed += _ =>
{
PanToWorld();
_angleFollow ^= true;
PanToEnt();
};
_radarAnchorButton = CreateRadarModeButton(RadarModeButtonIcon.Anchor);
_radarAnchorButton.ToolTip = Loc.GetString("shuttle-console-button-anchor");
_radarAnchorButton.OnPressed += _ =>
{
if (_coordinates is not { } cord)
return;
// if we're anchored or not yet bound to map, just deanchor
if (_relativePanning || !EntManager.HasComponent<MapComponent>(cord.EntityId))
{
PanToWorld();
// we're deanchoring, so simply detach our view to the map
_coordinates = _transform.ToCoordinates(_transform.ToMapCoordinates(cord));
_relativePanning = false;
_wasPanned = true;
PanToEnt();
}
else
{
ReanchorView();
}
};
_radarResetButton = CreateRadarModeButton(RadarModeButtonIcon.Reset);
_radarResetButton.ToolTip = Loc.GetString("shuttle-console-button-reset");
_radarResetButton.OnPressed += _ =>
{
_radarModeDefaultsApplied = false;
ApplyRadarModeDefaults();
if (EntManager.TryGetComponent<TransformComponent>(_consoleEntity, out var xform))
{
Offset = Vector2.Zero;
_coordinates = xform.Coordinates;
ReanchorView();
}
};
_radarModeButtons.AddChild(_radarAzimuthButton);
_radarModeButtons.AddChild(_radarRotationButton);
_radarModeButtons.AddChild(_radarAnchorButton);
_radarModeButtons.AddChild(_radarResetButton);
parent.AddChild(_radarModeButtons);
LayoutContainer.SetAnchorAndMarginPreset(_radarModeButtons, LayoutContainer.LayoutPreset.BottomLeft, margin: 10);
}
private void ApplyRadarModeDefaults()
{
if (_radarModeDefaultsApplied)
return;
if (!EntManager.TryGetComponent<RadarConsoleComponent>(_consoleEntity, out var radar))
return;
_radarModeDefaultsApplied = true;
Draggable = radar.Pannable;
_angleFollow = !radar.NoRotate;
_relativePanning = radar.RelativePanning;
}
private static RadarModeButton CreateRadarModeButton(RadarModeButtonIcon icon = RadarModeButtonIcon.None)
{
return new RadarModeButton(icon)
{
MinWidth = 16f,
MaxWidth = 16f,
MinHeight = 16f,
MaxHeight = 16f,
HorizontalExpand = false,
VerticalExpand = false,
ModulateSelfOverride = Color.White.WithAlpha(0.55f),
};
}
internal enum RadarModeButtonIcon : byte
{
None,
Azimuth,
Rotation,
Anchor,
Reset
}
public sealed class RadarModeButton : BaseButton
{
private readonly RadarModeButtonIcon _icon;
internal RadarModeButton(RadarModeButtonIcon icon)
{
_icon = icon;
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
return new Vector2(16f, 16f);
}
protected override void Draw(DrawingHandleScreen handle)
{
var alpha = DrawMode switch
{
DrawModeEnum.Pressed => 0.62f,
DrawModeEnum.Hover => 0.48f,
_ => 0.32f,
};
var center = new Vector2(PixelSize.X * 0.5f, PixelSize.Y * 0.5f);
var radius = MathF.Min(PixelSize.X, PixelSize.Y) * 0.42f;
handle.DrawCircle(center, radius, Color.White.WithAlpha(alpha));
var up = new Vector2(0f, -radius);
var down = new Vector2(0f, radius);
var leftOffs = new Vector2(-radius, 0f);
var rightOffs = new Vector2(radius, 0f);
var top = center + up;
var bottom = center + down;
var left = center + leftOffs;
var right = center + rightOffs;
switch (_icon) {
case RadarModeButtonIcon.Azimuth:
{
var iconColor = Color.Black.WithAlpha(0.95f);
var topLeft = center + (up + leftOffs) / MathF.Sqrt(2f);
var topRight = center + (up + rightOffs) / MathF.Sqrt(2f);
var bottomLeft = center + (down + leftOffs) / MathF.Sqrt(2f);
var bottomRight = center + (down + rightOffs) / MathF.Sqrt(2f);
handle.DrawLine(topLeft, bottomRight, iconColor);
handle.DrawLine(bottomLeft, topRight, iconColor);
handle.DrawLine(left, right, iconColor);
handle.DrawLine(bottom, top, iconColor);
break;
}
case RadarModeButtonIcon.Rotation:
{
var iconColor = Color.Yellow.WithAlpha(0.95f);
var innerRatio = 0.8f;
var segCount = 3;
var prevOffs = up * innerRatio;
for (var i = 0; i < segCount; i++)
{
var angle = Angle.FromDegrees(120f * i / (float)segCount - 15f);
var thisOffs = angle.RotateVec(up * innerRatio);
var thisPointUp = center + thisOffs;
var thisPointDown = center - thisOffs;
var oldPointUp = center + prevOffs;
var oldPointDown = center - prevOffs;
handle.DrawLine(oldPointUp, thisPointUp, iconColor);
handle.DrawLine(oldPointDown, thisPointDown, iconColor);
prevOffs = thisOffs;
}
break;
}
case RadarModeButtonIcon.Anchor:
{
var iconColor = Color.Blue.WithAlpha(0.95f);
var stemTop = center + up * 0.5f;
var crossLeft = center + leftOffs * 0.3f + up * 0.7f;
var crossRight = center + rightOffs * 0.3f + up * 0.7f;;
var leftFluke = center + leftOffs * 0.6f + down * 0.6f;
var rightFluke = center + rightOffs * 0.6f + down * 0.6f;
var innerRadius = radius * 0.4f;
var innerTop = center + up * 0.6f;
handle.DrawCircle(innerTop, innerRadius, iconColor, false);
handle.DrawLine(stemTop, bottom, iconColor);
handle.DrawLine(crossLeft, crossRight, iconColor);
handle.DrawLine(bottom, leftFluke, iconColor);
handle.DrawLine(bottom, rightFluke, iconColor);
break;
}
case RadarModeButtonIcon.Reset:
{
var iconColor = Color.Red.WithAlpha(0.95f);
handle.DrawLine(left, right, iconColor);
break;
}
default:
break;
}
}
}
// Mono - evil hack
@@ -153,18 +437,15 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
if (!_draggin || _coordinates == null)
return;
if (!_wasPanned)
{
_wasPanned = true;
_oldCoordinates = RelativePanning ? _coordinates.Value : _transform.ToCoordinates(_transform.ToMapCoordinates(_coordinates.Value));
if (!RelativePanning)
_rotation = new Angle(0);
}
if (_oldCoordinates == null)
return;
if (!_relativePanning
&& _coordinates?.EntityId is { } coordEnt
&& EntManager.TryGetComponent<TransformComponent>(coordEnt, out var coordXform)
&& coordXform.MapUid != coordEnt
)
_coordinates = _transform.ToCoordinates(_transform.ToMapCoordinates(_coordinates.Value));
var offs = (_rotation ?? new Angle(0)).RotateVec(InverseMapPosition(MidPointVector));
_coordinates = _oldCoordinates.Value.Offset(offs);
_wasPanned = true;
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
@@ -184,21 +465,11 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
base.KeyBindUp(args);
if (args.Function != EngineKeyFunctions.UIClick)
{
return;
}
_isMouseDown = false;
if (_coordinates == null || _rotation == null || OnRadarClick == null)
{
return;
}
var a = InverseScalePosition(args.RelativePosition);
var relativeWorldPos = a with { Y = -a.Y };
relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos);
var coords = _coordinates.Value.Offset(relativeWorldPos);
var coords = GetMouseEntityCoordinates(args.RelativePosition);
OnRadarClick?.Invoke(coords);
}
@@ -235,13 +506,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
private void TryFireAtPosition(Vector2 relativePosition)
{
if (_coordinates == null || _rotation == null || OnRadarClick == null)
return;
var a = InverseScalePosition(relativePosition);
var relativeWorldPos = new Vector2(a.X, -a.Y);
relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos);
var coords = _coordinates.Value.Offset(relativeWorldPos);
var coords = GetMouseEntityCoordinates(relativePosition);
OnRadarClick?.Invoke(coords);
}
@@ -257,14 +522,13 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
}
var pos = _uiManager.MousePositionScaled.Position - GlobalPosition;
var relativeWorldPos = _rotation.Value.RotateVec(pos);
// I am not sure why the resulting point is 20 units under the mouse.
return _coordinates.Value.Offset(relativeWorldPos);
return GetMouseEntityCoordinates(pos);
}
public void UpdateState(NavInterfaceState state)
{
EnsureRadarModeButtons();
ApplyRadarModeDefaults();
SetMatrix(EntManager.GetCoordinates(state.Coordinates), state.Angle);
WorldMaxRange = state.MaxRange;
@@ -279,14 +543,6 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
ActualRadarRange = Math.Clamp(ActualRadarRange, WorldMinRange, WorldMaxRange);
// Mono
RelativePanning = state.RelativePanning;
Draggable = state.Pannable;
if (!Draggable)
_wasPanned = false; // also reset
RotateWithEntity = state.RotateWithEntity;
// Frontier
if (state.MaxIffRange != null)
MaximumIFFDistance = state.MaxIffRange.Value;
@@ -321,12 +577,24 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
return;
}
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
var posMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, _rotation.Value);
var ourEntRot = _wasPanned && !RelativePanning ? new Angle(0) : RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
var shuttleToWorld = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
Matrix3x2.Invert(shuttleToWorld, out var worldToShuttle);
// Mono
if (EntManager.TryGetComponent<RadarConsoleComponent>(_consoleEntity, out var radar))
{
Draggable = radar.Pannable;
}
// Mono - TODO: remove _rotation and make it a method instead
var coordEnt = _coordinates.Value.EntityId;
var coordEntRot = _transform.GetWorldRotation(coordEnt);
if (_angleFollow && EntManager.TryGetComponent<TransformComponent>(coordEnt, out var coordXform))
_rotation = coordEntRot;
else
_rotation = Angle.Zero;
var worldRot = _rotation.Value;
var mapPos = _transform.ToMapCoordinates(_coordinates.Value).Offset(_rotation.Value.RotateVec(Offset));
var mapCoord = _transform.ToCoordinates(mapPos);
var worldToShuttle = Matrix3Helpers.CreateTranslation(-mapCoord.Position) * Matrix3Helpers.CreateRotation(-worldRot);
Matrix3x2.Invert(worldToShuttle, out var shuttleToWorld);
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
var worldToView = worldToShuttle * shuttleToView;
@@ -334,8 +602,23 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
DrawShields(handle, xform, worldToShuttle);
// Frontier Corvax: north line drawing
var rot = ourEntRot + _rotation.Value;
DrawNorthLine(handle, rot);
DrawNorthLine(handle, worldRot);
// Mono
switch (_azimuthMode)
{
case RadarAzimuthMode.Edge:
DrawAzimuthScale(handle, worldRot);
break;
case RadarAzimuthMode.Ring:
DrawAzimuthRing(handle, worldRot);
break;
}
if (worldRot != Angle.Zero)
{
DrawCompassOverlay(handle, coordEntRot);
}
// Draw our grid in detail
var ourGridId = xform.GridUid;
@@ -361,7 +644,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, radarPosVerts, Color.Lime);
var viewBounds = new Box2Rotated(new Box2(-WorldRange, -WorldRange, WorldRange, WorldRange).Translated(mapPos.Position), rot, mapPos.Position);
var viewBounds = new Box2Rotated(new Box2(-WorldRange, -WorldRange, WorldRange, WorldRange).Translated(mapPos.Position), worldRot, mapPos.Position);
var viewAABB = viewBounds.CalcBoundingBox();
_grids.Clear();
@@ -426,9 +709,8 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
//shouldDrawIFF = NfCheckShouldDrawIffRangeCondition(shouldDrawIFF, mapCenter, curGridToWorld); // Frontier code
// Frontier: range checks // Mono
var gridMapPos = _transform.ToMapCoordinates(new EntityCoordinates(gUid, gridBody.LocalCenter)).Position;
var ourPos = _transform.ToMapCoordinates(_coordinates.Value);
if (!hideLabel) // Mono - show thermal signatures even at long range
shouldDrawIFF = NfCheckShouldDrawIffRangeCondition(shouldDrawIFF, ourPos.Position - gridMapPos);
shouldDrawIFF = NfCheckShouldDrawIffRangeCondition(shouldDrawIFF, mapPos.Position - gridMapPos);
// End Frontier
// Mono
@@ -629,7 +911,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
if (blip.Config.RespectZoom)
box.Box = new Box2(box.Box.BottomLeft * MinimapScale, box.Box.TopRight * MinimapScale);
if (blip.Config.Rotate)
box.Rotation = ourEntRot - blip.Rotation;
box.Rotation = worldRot - blip.Rotation;
if (blip.GridUid is { } grid)
{
@@ -873,11 +1155,24 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
protected Vector2 InverseScalePosition(Vector2 value)
{
// Account for UI scaling: value is unscaled, so adjust by UIScale
var scaledValue = value * UIScale;
return (scaledValue - MidPointVector) / MinimapScale;
}
// Mono
protected EntityCoordinates GetMouseEntityCoordinates(Vector2 relativePosition)
{
if (_coordinates is not { } cord || _rotation is not { } rot)
return new();
// multiply by UIScale because UI is Very Intelligent and doesn't scale mouse position for whatever reason
var screenRelativeWorldPos = InverseMapPosition(relativePosition * UIScale);
var relativeWorldPos = rot.RotateVec(screenRelativeWorldPos);
var coordEntRot = _transform.GetWorldRotation(cord.EntityId);
var coords = cord.Offset((-coordEntRot).RotateVec(relativeWorldPos));
return coords;
}
public class BlipData
{
public bool IsOutsideRadarCircle { get; set; }
@@ -1,32 +0,0 @@
using Content.Shared.Hands.Components;
namespace Content.Client.Toggleable;
/// <summary>
/// Component that handles the toggling the visuals of some light emitting entity.
/// </summary>
/// <remarks>
/// This will toggle the visibility of layers on an entity's sprite, the in-hand visuals, and the clothing/equipment
/// visuals. This will modify the color of any attached point lights.
/// </remarks>
[RegisterComponent]
public sealed partial class ToggleableLightVisualsComponent : Component
{
/// <summary>
/// Sprite layer that will have its visibility toggled when this item is toggled.
/// </summary>
[DataField("spriteLayer")]
public string? SpriteLayer = "light";
/// <summary>
/// Layers to add to the sprite of the player that is holding this entity (while the component is toggled on).
/// </summary>
[DataField("inhandVisuals")]
public Dictionary<HandLocation, List<PrototypeLayerData>> InhandVisuals = new();
/// <summary>
/// Layers to add to the sprite of the player that is wearing this entity (while the component is toggled on).
/// </summary>
[DataField("clothingVisuals")]
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
}
@@ -1,126 +0,0 @@
using Content.Client.Clothing;
using Content.Client.Items.Systems;
using Content.Shared.Clothing;
using Content.Shared.Hands;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Toggleable;
using Robust.Client.GameObjects;
using Robust.Shared.Utility;
using System.Linq;
namespace Content.Client.Toggleable;
public sealed class ToggleableLightVisualsSystem : VisualizerSystem<ToggleableLightVisualsComponent>
{
[Dependency] private readonly SharedItemSystem _itemSys = default!;
[Dependency] private readonly SharedPointLightSystem _lights = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ToggleableLightVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals, after: new[] { typeof(ItemSystem) });
SubscribeLocalEvent<ToggleableLightVisualsComponent, GetEquipmentVisualsEvent>(OnGetEquipmentVisuals, after: new[] { typeof(ClientClothingSystem) });
}
protected override void OnAppearanceChange(EntityUid uid, ToggleableLightVisualsComponent component, ref AppearanceChangeEvent args)
{
if (!AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, args.Component))
return;
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, args.Component);
// Update the item's sprite
if (args.Sprite != null && component.SpriteLayer != null && args.Sprite.LayerMapTryGet(component.SpriteLayer, out var layer))
{
args.Sprite.LayerSetVisible(layer, enabled);
if (modulate)
args.Sprite.LayerSetColor(layer, color);
}
// Update any point-lights
if (TryComp(uid, out PointLightComponent? light))
{
DebugTools.Assert(!light.NetSyncEnabled, "light visualizers require point lights without net-sync");
_lights.SetEnabled(uid, enabled, light);
if (enabled && modulate)
{
_lights.SetColor(uid, color, light);
}
}
// update clothing & in-hand visuals.
_itemSys.VisualsChanged(uid);
}
/// <summary>
/// Add the unshaded light overlays to any clothing sprites.
/// </summary>
private void OnGetEquipmentVisuals(EntityUid uid, ToggleableLightVisualsComponent component, GetEquipmentVisualsEvent args)
{
if (!TryComp(uid, out AppearanceComponent? appearance)
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, appearance)
|| !enabled)
return;
if (!TryComp(args.Equipee, out InventoryComponent? inventory))
return;
List<PrototypeLayerData>? layers = null;
// attempt to get species specific data
if (inventory.SpeciesId != null)
component.ClothingVisuals.TryGetValue($"{args.Slot}-{inventory.SpeciesId}", out layers);
// No species specific data. Try to default to generic data.
if (layers == null && !component.ClothingVisuals.TryGetValue(args.Slot, out layers))
return;
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, appearance);
var i = 0;
foreach (var layer in layers)
{
var key = layer.MapKeys?.FirstOrDefault();
if (key == null)
{
key = i == 0 ? $"{args.Slot}-toggle" : $"{args.Slot}-toggle-{i}";
i++;
}
if (modulate)
layer.Color = color;
args.Layers.Add((key, layer));
}
}
private void OnGetHeldVisuals(EntityUid uid, ToggleableLightVisualsComponent component, GetInhandVisualsEvent args)
{
if (!TryComp(uid, out AppearanceComponent? appearance)
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, appearance)
|| !enabled)
return;
if (!component.InhandVisuals.TryGetValue(args.Location, out var layers))
return;
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, appearance);
var i = 0;
var defaultKey = $"inhand-{args.Location.ToString().ToLowerInvariant()}-toggle";
foreach (var layer in layers)
{
var key = layer.MapKeys?.FirstOrDefault();
if (key == null)
{
key = i == 0 ? defaultKey : $"{defaultKey}-{i}";
i++;
}
if (modulate)
layer.Color = color;
args.Layers.Add((key, layer));
}
}
}
@@ -0,0 +1,30 @@
using Content.Shared.Hands.Components;
namespace Content.Client.Toggleable;
/// <summary>
/// Component that handles toggling the visuals of an entity, including layers on an entity's sprite,
/// the in-hand visuals, and the clothing/equipment visuals.
/// </summary>
/// <see cref="ToggleableVisualsSystem"/>
[RegisterComponent]
public sealed partial class ToggleableVisualsComponent : Component
{
/// <summary>
/// Sprite layer that will have its visibility toggled when this item is toggled.
/// </summary>
[DataField(required: true)]
public string? SpriteLayer;
/// <summary>
/// Layers to add to the sprite of the player that is holding this entity (while the component is toggled on).
/// </summary>
[DataField]
public Dictionary<HandLocation, List<PrototypeLayerData>> InhandVisuals = new();
/// <summary>
/// Layers to add to the sprite of the player that is wearing this entity (while the component is toggled on).
/// </summary>
[DataField]
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
}
@@ -0,0 +1,140 @@
using System.Linq;
using Content.Client.Clothing;
using Content.Client.Items.Systems;
using Content.Shared.Clothing;
using Content.Shared.Hands;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Light.Components;
using Content.Shared.Toggleable;
using Robust.Client.GameObjects;
using Robust.Shared.Utility;
namespace Content.Client.Toggleable;
/// <summary>
/// Implements the behavior of <see cref="ToggleableVisualsComponent"/> by reacting to
/// <see cref="AppearanceChangeEvent"/>, for the sprite directly; <see cref="OnGetHeldVisuals"/> for the
/// in-hand visuals; and <see cref="OnGetEquipmentVisuals"/> for the clothing visuals.
/// </summary>
/// <see cref="ToggleableVisualsComponent"/>
public sealed class ToggleableVisualsSystem : VisualizerSystem<ToggleableVisualsComponent>
{
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ToggleableVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals,
after: [typeof(ItemSystem)]);
SubscribeLocalEvent<ToggleableVisualsComponent, GetEquipmentVisualsEvent>(OnGetEquipmentVisuals,
after: [typeof(ClientClothingSystem)]);
}
protected override void OnAppearanceChange(EntityUid uid,
ToggleableVisualsComponent component,
ref AppearanceChangeEvent args)
{
if (!AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, args.Component))
return;
var modulateColor =
AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, args.Component);
// Update the item's sprite
if (args.Sprite != null && component.SpriteLayer != null &&
SpriteSystem.LayerMapTryGet((uid, args.Sprite), component.SpriteLayer, out var layer, false))
{
SpriteSystem.LayerSetVisible((uid, args.Sprite), layer, enabled);
if (modulateColor)
SpriteSystem.LayerSetColor((uid, args.Sprite), component.SpriteLayer, color);
}
// If there's a `ItemTogglePointLightComponent` that says to apply the color to attached lights, do so.
if (TryComp<ItemTogglePointLightComponent>(uid, out var toggleLights) &&
TryComp(uid, out PointLightComponent? light))
{
DebugTools.Assert(!light.NetSyncEnabled,
$"{typeof(ItemTogglePointLightComponent)} requires point lights without net-sync");
_pointLight.SetEnabled(uid, enabled, light);
if (modulateColor && toggleLights.ToggleableVisualsColorModulatesLights)
{
_pointLight.SetColor(uid, color, light);
}
}
// update clothing & in-hand visuals.
_item.VisualsChanged(uid);
}
private void OnGetEquipmentVisuals(EntityUid uid,
ToggleableVisualsComponent component,
GetEquipmentVisualsEvent args)
{
if (!TryComp(uid, out AppearanceComponent? appearance)
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, appearance)
|| !enabled)
return;
if (!TryComp(args.Equipee, out InventoryComponent? inventory))
return;
List<PrototypeLayerData>? layers = null;
// attempt to get species specific data
if (inventory.SpeciesId != null)
component.ClothingVisuals.TryGetValue($"{args.Slot}-{inventory.SpeciesId}", out layers);
// No species specific data. Try to default to generic data.
if (layers == null && !component.ClothingVisuals.TryGetValue(args.Slot, out layers))
return;
var modulateColor = AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, appearance);
var i = 0;
foreach (var layer in layers)
{
var key = layer.MapKeys?.FirstOrDefault();
if (key == null)
{
key = i == 0 ? $"{args.Slot}-toggle" : $"{args.Slot}-toggle-{i}";
i++;
}
if (modulateColor)
layer.Color = color;
args.Layers.Add((key, layer));
}
}
private void OnGetHeldVisuals(EntityUid uid, ToggleableVisualsComponent component, GetInhandVisualsEvent args)
{
if (!TryComp(uid, out AppearanceComponent? appearance)
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, appearance)
|| !enabled)
return;
if (!component.InhandVisuals.TryGetValue(args.Location, out var layers))
return;
var modulateColor = AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, appearance);
var i = 0;
var defaultKey = $"inhand-{args.Location.ToString().ToLowerInvariant()}-toggle";
foreach (var layer in layers)
{
var key = layer.MapKeys?.FirstOrDefault();
if (key == null)
{
key = i == 0 ? defaultKey : $"{defaultKey}-{i}";
i++;
}
if (modulateColor)
layer.Color = color;
args.Layers.Add((key, layer));
}
}
}
@@ -257,7 +257,7 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
}
// Make it so it doesn't hightlight your own name when you are talking
keyword = StartAtSign.Replace(keyword, @"(?<=\[BubbleContent\].*)");
keyword = StartAtSign.Replace(keyword, @"(?<=\[BubbleContent\].*|""\[/color\].*)");
return keyword;
}
@@ -76,7 +76,7 @@ public sealed class DamageOverlayUiController : UIController
{
if (mobState == null && !EntityManager.TryGetComponent(entity, out mobState) ||
thresholds == null && !EntityManager.TryGetComponent(entity, out thresholds) ||
damageable == null && !EntityManager.TryGetComponent(entity, out damageable))
damageable == null && !EntityManager.TryGetComponent(entity, out damageable))
return;
if (!_mobThresholdSystem.TryGetIncapThreshold(entity, out var foundThreshold, thresholds))
@@ -94,47 +94,48 @@ public sealed class DamageOverlayUiController : UIController
switch (mobState.CurrentState)
{
case MobState.Alive:
{
if (EntityManager.HasComponent<PainNumbnessComponent>(entity))
{
_overlay.BruteLevel = 0;
}
else if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage))
{
_overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float();
}
if (!EntityManager.HasComponent<PainNumbnessComponent>(entity)) // Mono - makes this look better
{
if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage))
_overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float();
if (damageable.DamagePerGroup.TryGetValue("Burn", out var burnDamage)) // Adds burn damage to pain overlay - Mono
_overlay.BruteLevel = FixedPoint2.Min(1f, _overlay.BruteLevel + (burnDamage / critThreshold)).Float();
}
else
_overlay.BruteLevel = 0;
if (damageable.DamagePerGroup.TryGetValue("Airloss", out var oxyDamage))
{
_overlay.OxygenLevel = FixedPoint2.Min(1f, oxyDamage / critThreshold).Float();
}
if (damageable.DamagePerGroup.TryGetValue("Airloss", out var oxyDamage))
{
_overlay.OxygenLevel = FixedPoint2.Min(1f, oxyDamage / critThreshold).Float();
}
if (_overlay.BruteLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
{
_overlay.BruteLevel = 0;
}
if (_overlay.BruteLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
{
_overlay.BruteLevel = 0;
}
_overlay.CritLevel = 0;
_overlay.DeadLevel = 0;
break;
}
_overlay.CritLevel = 0;
_overlay.DeadLevel = 0;
break;
}
case MobState.Critical:
{
if (!_mobThresholdSystem.TryGetDeadPercentage(entity,
FixedPoint2.Max(0.0, damageable.TotalDamage), out var critLevel))
return;
_overlay.CritLevel = critLevel.Value.Float();
{
if (!_mobThresholdSystem.TryGetDeadPercentage(entity,
FixedPoint2.Max(0.0, damageable.TotalDamage), out var critLevel))
return;
_overlay.CritLevel = critLevel.Value.Float();
_overlay.BruteLevel = 0;
_overlay.DeadLevel = 0;
break;
}
_overlay.BruteLevel = 0;
_overlay.DeadLevel = 0;
break;
}
case MobState.Dead:
{
_overlay.BruteLevel = 0;
_overlay.CritLevel = 0;
break;
}
{
_overlay.BruteLevel = 0;
_overlay.CritLevel = 0;
break;
}
}
}
}
@@ -82,7 +82,7 @@ namespace Content.Client.VendingMachines.UI
/// Populates the list of available items on the vending machine interface
/// and sets icons based on their prototypes
/// </summary>
public void Populate(List<VendingMachineInventoryEntry> inventory, float priceModifier, int balance, int? cashSlotBalance) // Frontier: add balance, cashSlotBalance
public void Populate(List<VendingMachineInventoryEntry> inventory, float priceModifier, int balance, int? cashSlotBalance, bool requiresCash) // Frontier: add balance, cashSlotBalance
{
UpdateBalance(balance); // Frontier
UpdateCashSlotBalance(cashSlotBalance); // Frontier
@@ -124,84 +124,11 @@ namespace Content.Client.VendingMachines.UI
_dummies.Add(entry.ID, dummy);
}
// Frontier: item pricing
// ok so we dont really have access to the pricing system so we are doing a quick price check
// based on prototype info since the items inside a vending machine dont actually exist as entities
// until they are spawned. So this little alg does the following:
// first, checks for a staticprice component, and if it has one, checks to make sure its not 0 since
// stacks and other items have 0 cost.
// If the price is 0, then we check for both a stack price and a stack component, since if it has one
// it should have the other too, and then calculates the price based on that.
// If the price is still 0 or non-existant (this is the case for food and containers since their value is
// determined dynamically by their contents/inventory), it then falls back to the default mystery
// hardcoded value of 20xMarketModifier.
var cost = 20;
if (prototype != null && prototype.TryGetComponent<StaticPriceComponent>(out var priceComponent, _entityManager.ComponentFactory))
{
if (priceComponent.Price != 0)
{
var price = (float)priceComponent.Price;
cost = (int)(price * priceModifier);
}
else
{
if (prototype.TryGetComponent<StackPriceComponent>(out var stackPrice, _entityManager.ComponentFactory)
&& prototype.TryGetComponent<StackComponent>(out var stack, _entityManager.ComponentFactory))
{
var price = stackPrice.Price * stack.Count;
cost = (int)(price * priceModifier);
}
else
cost = (int)(cost * priceModifier);
}
}
else
cost = (int)(cost * priceModifier);
if (prototype != null && prototype.TryGetComponent<SolutionContainerManagerComponent>(out var priceSolutions, _entityManager.ComponentFactory))
{
if (priceSolutions.Solutions != null)
{
foreach (var solution in priceSolutions.Solutions.Values)
{
foreach (var (reagent, quantity) in solution.Contents)
{
if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype,
out var reagentProto))
continue;
// TODO check ReagentData for price information?
var costReagent = quantity.Float() * reagentProto.PricePerUnit;
cost += (int)(costReagent * priceModifier);
}
}
}
}
// End Frontier: item pricing
// Frontier: calculate vending price (this duplicates Content.Server.PricingSystem.GetVendPrice - this should be moved to Content.Shared if possible)
if (prototype != null)
{
var price = 0.0;
if (prototype.TryGetComponent<StaticPriceComponent>(out var staticComp, _entityManager.ComponentFactory) && staticComp.VendPrice > 0.0)
{
price += staticComp.VendPrice;
}
else if (prototype.TryGetComponent<StackPriceComponent>(out var stackComp, _entityManager.ComponentFactory) && stackComp.VendPrice > 0.0)
{
price += stackComp.VendPrice;
}
// If there is anything that explicitly sets vending price - higher OR lower, override the base.
if (price > 0.0)
{
cost = (int)price;
}
}
// End Frontier
var itemName = Identity.Name(dummy, _entityManager);
var cost = 0; // mono
if (requiresCash) // frontier
cost = GetPrice(entry, prototype, priceModifier);
string itemText;
// Frontier: unlimited vending
@@ -211,7 +138,6 @@ namespace Content.Client.VendingMachines.UI
itemText = $"[{BankSystemExtensions.ToSpesoString(cost)}] {itemName}";
// End Frontier: unlimited vending
if (itemText.Length > longestEntry.Length)
longestEntry = itemText;
@@ -223,6 +149,88 @@ namespace Content.Client.VendingMachines.UI
SetSizeAfterUpdate(longestEntry.Length, inventory.Count);
}
// Mono: Moved out frontier pricing logic to the separate method
private int GetPrice(VendingMachineInventoryEntry entry, EntityPrototype? prototype, float priceModifier){
// Frontier: item pricing
// ok so we dont really have access to the pricing system so we are doing a quick price check
// based on prototype info since the items inside a vending machine dont actually exist as entities
// until they are spawned. So this little alg does the following:
// first, checks for a staticprice component, and if it has one, checks to make sure its not 0 since
// stacks and other items have 0 cost.
// If the price is 0, then we check for both a stack price and a stack component, since if it has one
// it should have the other too, and then calculates the price based on that.
// If the price is still 0 or non-existant (this is the case for food and containers since their value is
// determined dynamically by their contents/inventory), it then falls back to the default mystery
// hardcoded value of 20xMarketModifier.
var cost = 20;
if (prototype != null && prototype.TryGetComponent<StaticPriceComponent>(out var priceComponent, _entityManager.ComponentFactory))
{
if (priceComponent.Price != 0)
{
var price = (float)priceComponent.Price;
cost = (int)(price * priceModifier);
}
else
{
if (prototype.TryGetComponent<StackPriceComponent>(out var stackPrice, _entityManager.ComponentFactory)
&& prototype.TryGetComponent<StackComponent>(out var stack, _entityManager.ComponentFactory))
{
var price = stackPrice.Price * stack.Count;
cost = (int)(price * priceModifier);
}
else
cost = (int)(cost * priceModifier);
}
}
else
cost = (int)(cost * priceModifier);
if (prototype != null && prototype.TryGetComponent<SolutionContainerManagerComponent>(out var priceSolutions, _entityManager.ComponentFactory))
{
if (priceSolutions.Solutions != null)
{
foreach (var solution in priceSolutions.Solutions.Values)
{
foreach (var (reagent, quantity) in solution.Contents)
{
if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype,
out var reagentProto))
continue;
// TODO check ReagentData for price information?
var costReagent = quantity.Float() * reagentProto.PricePerUnit;
cost += (int)(costReagent * priceModifier);
}
}
}
}
// End Frontier: item pricing
// Frontier: calculate vending price (this duplicates Content.Server.PricingSystem.GetVendPrice - this should be moved to Content.Shared if possible)
if (prototype != null)
{
var price = 0.0;
if (prototype.TryGetComponent<StaticPriceComponent>(out var staticComp, _entityManager.ComponentFactory) && staticComp.VendPrice > 0.0)
{
price += staticComp.VendPrice;
}
else if (prototype.TryGetComponent<StackPriceComponent>(out var stackComp, _entityManager.ComponentFactory) && stackComp.VendPrice > 0.0)
{
price += stackComp.VendPrice;
}
// If there is anything that explicitly sets vending price - higher OR lower, override the base.
if (price > 0.0)
{
cost = (int)price;
}
}
return cost;
// End Frontier
}
// Frontier
public void UpdateBalance(int balance)
{
@@ -30,6 +30,8 @@ namespace Content.Client.VendingMachines
[ViewVariables]
private int _cashSlotBalance = 0;
// End Frontier
[ViewVariables]
private bool _requiresCash; // mono
public VendingMachineBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
@@ -74,6 +76,7 @@ namespace Content.Client.VendingMachines
if (EntMan.TryGetComponent<VendingMachineComponent>(Owner, out var vendingMachine))
{
_cashSlotBalance = vendingMachine.CashSlotBalance;
_requiresCash = vendingMachine.RequiresCash; // mono
if (vendingMachine.CashSlotName != null)
cashSlotValue = _cashSlotBalance;
}
@@ -83,7 +86,7 @@ namespace Content.Client.VendingMachines
}
// End Frontier
_menu?.Populate(_cachedInventory, _mod, _balance, cashSlotValue); // Frontier: add _balance
_menu?.Populate(_cachedInventory, _mod, _balance, cashSlotValue, _requiresCash); // Frontier: add _balance, mono: add _requiresCash
}
private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
using Content.Shared._CorvaxNext.Silicons.Borgs;
namespace Content.Client._CorvaxNext.Silicons.Borgs;
public sealed partial class AiRemoteControlSystem : SharedAiRemoteControlSystem
{
}
@@ -0,0 +1,18 @@
<Control xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Margin="0 0 0 10">
<PanelContainer VerticalExpand="True" StyleClasses="BackgroundDark">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
Margin="5 5 5 5">
<customControls:HSeparator Margin="0 5 0 5"/>
<RichTextLabel Name="DeviceName"/>
<BoxContainer Name="RemoteControlButtons" Orientation="Horizontal" HorizontalExpand="True" Margin="0 5 0 0">
<Button Name="MoveButton" StyleClasses="chatSelectorOptionButton" Text="{Loc ai-remote-ui-menu-moveto}"/>
<Control Margin="2 0 0 0"/>
<Button Name="TakeControlButton" StyleClasses="chatSelectorOptionButton" Text="{Loc ai-remote-control}"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
</Control>
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
using Robust.Client.UserInterface;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
namespace Content.Client._CorvaxNext.Silicons.Laws.Ui;
[GenerateTypedNameReferences]
public sealed partial class RemoteDeviceDisplay : Control
{
public event Action<RemoteDeviceActionEvent>? OnRemoteDeviceAction;
public RemoteDeviceDisplay(NetEntity netEntityUid, string displayName)
{
RobustXamlLoader.Load(this);
DeviceName.SetMessage(displayName);
MoveButton.OnPressed += _ =>
{
OnRemoteDeviceAction?.Invoke(new RemoteDeviceActionEvent(
RemoteDeviceActionEvent.RemoteDeviceActionType.MoveToDevice,
netEntityUid));
};
TakeControlButton.OnPressed += _ =>
{
OnRemoteDeviceAction?.Invoke(new RemoteDeviceActionEvent(
RemoteDeviceActionEvent.RemoteDeviceActionType.TakeControl,
netEntityUid));
};
}
}
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
using Robust.Client.UserInterface;
using static Content.Shared._CorvaxNext.Silicons.Borgs.Components.AiRemoteControllerComponent;
namespace Content.Client._CorvaxNext.Silicons.Laws.Ui;
public sealed class RemoteDevicesBoundUserInterface : BoundUserInterface
{
private RemoteDevicesMenu? _menu;
private EntityUid _owner;
public RemoteDevicesBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_owner = owner;
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<RemoteDevicesMenu>();
_menu.OnRemoteDeviceAction += SendAction;
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not RemoteDevicesBuiState msg)
return;
_menu?.Update(_owner, msg);
}
public void SendAction(RemoteDeviceActionEvent action)
{
SendMessage(new RemoteDeviceActionMessage(action));
}
}
@@ -0,0 +1,27 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc ai-remote-ui-menu-title}"
MinSize="355 100"
SetSize="355 415">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<PanelContainer VerticalExpand="True" Margin="10 10 10 10">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E"/>
</PanelContainer.PanelOverride>
<ScrollContainer
HScrollEnabled="False"
HorizontalExpand="True"
VerticalExpand="True">
<BoxContainer
Name="RemoteDevicesDisplayContainer"
Orientation="Vertical"
VerticalExpand="True"
Margin="10 10 10 0">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
using Robust.Client.UserInterface.XAML;
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
using Robust.Client.AutoGenerated;
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
namespace Content.Client._CorvaxNext.Silicons.Laws.Ui;
[GenerateTypedNameReferences]
public sealed partial class RemoteDevicesMenu : FancyWindow
{
public event Action<RemoteDeviceActionEvent>? OnRemoteDeviceAction;
public RemoteDevicesMenu()
{
RobustXamlLoader.Load(this);
}
public void Update(EntityUid uid, RemoteDevicesBuiState state)
{
RemoteDevicesDisplayContainer.Children.Clear();
if (state.DeviceList == null)
return;
foreach (var device in state.DeviceList)
{
var control = new RemoteDeviceDisplay(device.NetEntityUid, device.DisplayName);
control.OnRemoteDeviceAction += action =>
{
OnRemoteDeviceAction?.Invoke(action);
};
RemoteDevicesDisplayContainer.AddChild(control);
}
}
}
@@ -23,10 +23,13 @@
</BoxContainer>
</BoxContainer>
<ui:ShuttleNavControl Name="NavRadar"
HorizontalExpand="True"
VerticalExpand="True"
MinHeight="400"/>
<!-- Mono - wrap -->
<LayoutContainer>
<ui:ShuttleNavControl Name="NavRadar"
HorizontalExpand="True"
VerticalExpand="True"
MinHeight="400"/>
</LayoutContainer>
</GridContainer>
</controls:FancyWindow>
@@ -0,0 +1,12 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls;assembly=Content.Client"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"
ReserveSpaceForHiddenChildren="False" />
</ui:RadialMenu>
@@ -0,0 +1,93 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared._Goobstation.Weapons.AmmoSelector;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._Goobstation.AmmoSelector;
[GenerateTypedNameReferences]
public sealed partial class AmmoSelectorMenu : RadialMenu
{
[Dependency] private readonly EntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private SpriteSystem _sprites;
public event Action<ProtoId<SelectableAmmoPrototype>>? SendAmmoSelectorSystemMessageAction;
private EntityUid _item;
public AmmoSelectorMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_sprites = _entManager.System<SpriteSystem>();
}
public void SetEntity(EntityUid uid)
{
_item = uid;
Refresh();
}
public void Refresh()
{
var main = FindControl<RadialContainer>("Main");
main.RemoveAllChildren();
if (!_entManager.TryGetComponent(_item, out AmmoSelectorComponent? ammoSelector))
return;
foreach (var ammo in ammoSelector.Prototypes)
{
if (!_protoManager.TryIndex(ammo, out var prototype))
continue;
var button = new AmmoSelectorMenuButton
{
SetSize = new Vector2(64, 64),
ToolTip = Loc.GetString(prototype.Desc),
ProtoId = prototype.ID
};
var texture = new TextureRect
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Texture = _sprites.Frame0(prototype.Icon),
TextureScale = new Vector2(2f, 2f)
};
button.AddChild(texture);
main.AddChild(button);
}
AddAmmoSelectorMenuButtonOnClickActions(main);
}
private void AddAmmoSelectorMenuButtonOnClickActions(RadialContainer control)
{
foreach (var child in control.Children)
{
if (child is not AmmoSelectorMenuButton castChild)
continue;
castChild.OnButtonUp += _ =>
{
SendAmmoSelectorSystemMessageAction?.Invoke(castChild.ProtoId);
Close();
};
}
}
}
public sealed class AmmoSelectorMenuButton : RadialMenuTextureButtonWithSector
{
public ProtoId<SelectableAmmoPrototype> ProtoId { get; set; }
}
@@ -0,0 +1,39 @@
using Content.Shared._Goobstation.Weapons.AmmoSelector;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client._Goobstation.AmmoSelector;
[UsedImplicitly]
public sealed class AmmoSelectorMenuBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
private AmmoSelectorMenu? _menu;
public AmmoSelectorMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<AmmoSelectorMenu>();
_menu.SetEntity(Owner);
_menu.SendAmmoSelectorSystemMessageAction += SendAmmoSelectorSystemMessage;
var vpSize = _displayManager.ScreenSize;
_menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
}
public void SendAmmoSelectorSystemMessage(ProtoId<SelectableAmmoPrototype> protoId)
{
SendPredictedMessage(new AmmoSelectedMessage(protoId));
}
}
@@ -0,0 +1,45 @@
using Content.Shared._Mono.CCVar;
using Content.Shared._Mono.Company;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Client._Mono.Company;
public sealed partial class CompanyManager
{
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private HashSet<ProtoId<CompanyPrototype>> _whitelist = new();
public void Initialize()
{
_net.RegisterNetMessage<MsgCompanyWhitelist>(OnWhitelistMsg);
}
private void OnWhitelistMsg(MsgCompanyWhitelist msg)
{
_whitelist = msg.Whitelist;
}
public bool IsPlayerWhitelisted(ProtoId<CompanyPrototype> company)
{
return _whitelist.Contains(company);
}
public bool IsAllowed(ProtoId<CompanyPrototype> company)
{
if (!_config.GetCVar(MonoCVars.CompanyWhitelist))
return true;
if (!_proto.Resolve(company, out var companyPrototype) ||
!companyPrototype.Whitelisted)
{
return true;
}
return IsPlayerWhitelisted(company);
}
}
@@ -68,12 +68,14 @@ public sealed class FireControlNavControl : ShuttleNavControl
base.Draw(handle);
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
var posMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, _rotation.Value);
var ourEntRot = RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
var shuttleToWorld = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
Matrix3x2.Invert(shuttleToWorld, out var worldToShuttle);
var coordEntRot = _transform.GetWorldRotation(_coordinates.Value.EntityId);
var worldRot = _rotation.Value;
var mapPos = _transform.ToMapCoordinates(_coordinates.Value).Offset(_rotation.Value.RotateVec(Offset));
var mapCoord = _transform.ToCoordinates(mapPos);
var worldToShuttle = Matrix3Helpers.CreateTranslation(-mapCoord.Position) * Matrix3Helpers.CreateRotation(-worldRot);
Matrix3x2.Invert(worldToShuttle, out var shuttleToWorld);
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
var worldToView = worldToShuttle * shuttleToView;
Matrix3x2.Invert(worldToView, out var viewToWorld);
@@ -128,15 +130,7 @@ public sealed class FireControlNavControl : ShuttleNavControl
_lastCursorUpdateTime = (float)currentTime;
// Convert mouse position to world coordinates for missile tracking
if (_coordinates == null || _rotation == null || OnRadarClick == null)
return;
var a = InverseScalePosition(relativePosition);
var relativeWorldPos = new Vector2(a.X, -a.Y);
relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos);
var coords = _coordinates.Value.Offset(relativeWorldPos);
var coords = GetMouseEntityCoordinates(relativePosition);
// This will update the server of our cursor position without triggering actual firing
OnRadarClick?.Invoke(coords);
}
@@ -90,7 +90,9 @@
<!-- Radar Display -->
<BoxContainer Name="RadarContainer" Orientation="Horizontal" HorizontalExpand="True" Margin="50 50 50 50">
<ui:FireControlNavControl Name="NavRadar" />
<LayoutContainer>
<ui:FireControlNavControl Name="NavRadar" />
</LayoutContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -34,11 +34,6 @@ public sealed class PlantAnalyzerBoundUserInterface : BoundUserInterface
_window.Populate(cast);
}
public void AdvPressed(bool scanMode)
{
SendMessage(new PlantAnalyzerSetMode(scanMode));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
@@ -3,30 +3,28 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'plant-analyzer-interface-title'}">
<!-- Margin="left,top,right,bottom" -->
<GridContainer Rows="3" Name ="GridCont" Margin="10 5 10 5" VerticalAlignment="Stretch" HorizontalExpand="True">
<BoxContainer Name="Toggle">
<Label Name="AdvMode" Text="{Loc 'plant-analyzer-window-scanmode'}" Margin="5 0 5 0"/>
<Button Name="OnButton" Text="{Loc 'plant-analyzer-window-mode-on'}" StyleClasses="OpenRight"/>
<Button Name="OffButton" Text="{Loc 'plant-analyzer-window-mode-off'}" StyleClasses="OpenLeft"/>
</BoxContainer>
<GridContainer Rows="2" Name ="GridCont" Margin="10 5 10 5">
<BoxContainer Name="Top">
<Label Name="NoData" Text="{Loc 'plant-analyzer-window-no-seed-information-text'}" Margin="10 0 0 0"/>
</BoxContainer>
<TabContainer Name="Tabs" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalExpand="True">
<BoxContainer Name ="{Loc 'plant-analyzer-window-tab-basics'}" Orientation="Vertical" VerticalAlignment="Stretch" Margin="10 0 5 0">
<Label Name="PlantName" Margin="0 5 0 5"/>
<Label Name="PlantYield" Margin="0 5 0 5"/>
<Label Name="Potency" Margin="0 5 0 5"/>
<Label Name="Repeat" Margin="0 5 0 5"/>
<Label Name="Chemicals" Margin="0 5 0 5"/>
<Label Name="ConsumeGases" Margin="0 5 0 5" />
<Label Name="ExudeGases" Margin="0 5 0 5" />
<Label Name="Lifespan" Margin="0 5 0 5"/>
<Label Name="Maturation" Margin="0 5 0 5"/>
<Label Name="Production" Margin="0 5 0 5"/>
<Label Name="GrowthStages" Margin="0 5 0 5"/>
<Label Name="Endurance" Margin="0 5 0 5"/>
</BoxContainer>
<TabContainer Name="Tabs" VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Stretch" MinSize="300 400">
<ScrollContainer Name ="{Loc 'plant-analyzer-window-tab-basics'}">
<BoxContainer Orientation="Vertical" VerticalAlignment="Stretch" Margin="10 0 5 0">
<Label Name="PlantName" Margin="0 5 0 5"/>
<Label Name="PlantYield" Margin="0 5 0 5"/>
<Label Name="Potency" Margin="0 5 0 5"/>
<Label Name="Repeat" Margin="0 5 0 5"/>
<Label Name="Lifespan" Margin="0 5 0 5"/>
<Label Name="Maturation" Margin="0 5 0 5"/>
<Label Name="Production" Margin="0 5 0 5"/>
<Label Name="GrowthStages" Margin="0 5 0 5"/>
<Label Name="Endurance" Margin="0 5 0 5"/>
<Label Name="ConsumeGases" Margin="0 5 0 5" />
<Label Name="ExudeGases" Margin="0 5 0 5" />
<Label Name="Chemicals" Margin="0 5 0 5"/>
</BoxContainer>
</ScrollContainer>
<BoxContainer Name="{Loc 'plant-analyzer-window-tab-tolerances'}" Orientation="Vertical" VerticalAlignment="Stretch" Margin="10 0 5 0">
<Label Name="NutrientUsage" Margin="0 5 0 5"/>
<Label Name="WaterUsage" Margin="0 5 0 5"/>
@@ -1,11 +1,6 @@
using Content.Shared._NF.PlantAnalyzer;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using System.Linq;
using System.Text;
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
@@ -15,8 +10,6 @@ namespace Content.Client._NF.PlantAnalyzer.UI;
public sealed partial class PlantAnalyzerWindow : FancyWindow
{
private readonly IEntityManager _entityManager;
private readonly ButtonGroup _buttonGroup = new();
private const string IndentedNewline = "\n ";
public PlantAnalyzerWindow(PlantAnalyzerBoundUserInterface owner)
@@ -26,13 +19,6 @@ public sealed partial class PlantAnalyzerWindow : FancyWindow
var dependencies = IoCManager.Instance!;
_entityManager = dependencies.Resolve<IEntityManager>();
OnButton.Group = _buttonGroup;
OnButton.ToggleMode = true;
OffButton.Group = _buttonGroup;
OffButton.ToggleMode = true;
OnButton.OnPressed += _ => owner.AdvPressed(true);
OffButton.OnPressed += _ => owner.AdvPressed(false);
}
public void Populate(PlantAnalyzerScannedSeedPlantInformation msg)
@@ -47,15 +33,6 @@ public sealed partial class PlantAnalyzerWindow : FancyWindow
}
NoData.Visible = false;
if (msg.AdvancedInfo is not null)
{
OnButton.Pressed = true;
}
else
{
OffButton.Pressed = true;
}
// Process message fields into strings.
StringBuilder chemString = new();
if (msg.SeedChem != null)
@@ -79,60 +56,65 @@ public sealed partial class PlantAnalyzerWindow : FancyWindow
Potency.Text = Loc.GetString("plant-analyzer-plant-potency-text", ("seedPotency", $"{msg.SeedPotency:F0}"));
Repeat.Text = Loc.GetString("plant-analyzer-plant-harvest-text", ("plantHarvestType", Loc.GetString($"plant-analyzer-harvest-{msg.HarvestType}").ToString()));
Endurance.Text = Loc.GetString("plant-analyzer-plant-endurance-text", ("seedEndurance", $"{msg.Endurance:F0}"));
Chemicals.Text = Loc.GetString("plant-analyzer-plant-chemistry-text", ("seedChem", chemString));
ExudeGases.Text = Loc.GetString("plant-analyzer-plant-exude-text", ("gases", exudeGases.Length == 0 ? Loc.GetString("plant-analyzer-plant-gases-none") : exudeGases.ToString()));
ConsumeGases.Text = Loc.GetString("plant-analyzer-plant-consume-text", ("gases", consudeGases.Length == 0 ? Loc.GetString("plant-analyzer-plant-gases-none") : consudeGases.ToString()));
Lifespan.Text = Loc.GetString("plant-analyzer-plant-lifespan-text", ("lifespan", $"{msg.Lifespan:F1}"));
Maturation.Text = Loc.GetString("plant-analyzer-plant-maturation-text", ("maturation", $"{msg.Maturation:F1}"));
Production.Text = Loc.GetString("plant-analyzer-plant-production-text", ("production", $"{msg.Production:F1}"));
GrowthStages.Text = Loc.GetString("plant-analyzer-plant-growthstages-text", ("growthStages", $"{msg.GrowthStages:D0}"));
ConsumeGases.Text = Loc.GetString("plant-analyzer-plant-consume-text", ("gases", consudeGases.Length == 0 ? Loc.GetString("plant-analyzer-plant-gases-none") : consudeGases.ToString()));
ExudeGases.Text = Loc.GetString("plant-analyzer-plant-exude-text", ("gases", exudeGases.Length == 0 ? Loc.GetString("plant-analyzer-plant-gases-none") : exudeGases.ToString()));
Chemicals.Text = Loc.GetString("plant-analyzer-plant-chemistry-text", ("seedChem", chemString));
// Tolerances
var adv = msg.AdvancedInfo;
NutrientUsage.Text = Loc.GetString("plant-analyzer-tolerance-nutrient-usage", ("nutrientUsage", adv is null ? "-" : $"{adv.Value.NutrientConsumption:F2}"));
WaterUsage.Text = Loc.GetString("plant-analyzer-tolerance-water-usage", ("waterUsage", adv is null ? "-" : $"{adv.Value.WaterConsumption:F2}"));
IdealHeat.Text = Loc.GetString("plant-analyzer-tolerance-ideal-heat", ("idealHeat", adv is null ? "-" : $"{adv.Value.IdealHeat:F0}"));
HeatTolerance.Text = Loc.GetString("plant-analyzer-tolerance-heat-tolerance", ("heatTolerance", adv is null ? "-" : $"{adv.Value.HeatTolerance:F1}"));
IdealLight.Text = Loc.GetString("plant-analyzer-tolerance-ideal-light", ("idealLight", adv is null ? "-" : $"{adv.Value.IdealLight:F1}"));
LightTolerance.Text = Loc.GetString("plant-analyzer-tolerance-light-tolerance", ("lightTolerance", adv is null ? "-" : $"{adv.Value.LightTolerance:F1}"));
ToxinsTolerance.Text = Loc.GetString("plant-analyzer-tolerance-toxin-tolerance", ("toxinsTolerance", adv is null ? "-" : $"{adv.Value.ToxinsTolerance:F1}"));
LowPressureTolerance.Text = Loc.GetString("plant-analyzer-tolerance-low-pressure", ("lowPressureTolerance", adv is null ? "-" : $"{adv.Value.LowPressureTolerance:F1}")); ;
HighPressureTolerance.Text = Loc.GetString("plant-analyzer-tolerance-high-pressure", ("highPressureTolerance", adv is null ? "-" : $"{adv.Value.HighPressureTolerance:F1}"));
PestTolerance.Text = Loc.GetString("plant-analyzer-tolerance-pest-tolerance", ("pestTolerance", adv is null ? "-" : $"{adv.Value.PestTolerance:F1}"));
WeedTolerance.Text = Loc.GetString("plant-analyzer-tolerance-weed-tolerance", ("weedTolerance", adv is null ? "-" : $"{adv.Value.WeedTolerance:F1}"));
NutrientUsage.Text = Loc.GetString("plant-analyzer-tolerance-nutrient-usage", ("nutrientUsage", $"{msg.NutrientConsumption:F2}"));
WaterUsage.Text = Loc.GetString("plant-analyzer-tolerance-water-usage", ("waterUsage", $"{msg.WaterConsumption:F2}"));
IdealHeat.Text = Loc.GetString("plant-analyzer-tolerance-ideal-heat", ("idealHeat", $"{msg.IdealHeat:F0}"));
HeatTolerance.Text = Loc.GetString("plant-analyzer-tolerance-heat-tolerance", ("heatTolerance", $"{msg.HeatTolerance:F1}"));
IdealLight.Text = Loc.GetString("plant-analyzer-tolerance-ideal-light", ("idealLight", $"{msg.IdealLight:F1}"));
LightTolerance.Text = Loc.GetString("plant-analyzer-tolerance-light-tolerance", ("lightTolerance", $"{msg.LightTolerance:F1}"));
ToxinsTolerance.Text = Loc.GetString("plant-analyzer-tolerance-toxin-tolerance", ("toxinsTolerance", $"{msg.ToxinsTolerance:F1}"));
LowPressureTolerance.Text = Loc.GetString("plant-analyzer-tolerance-low-pressure", ("lowPressureTolerance", $"{msg.LowPressureTolerance:F1}"));
HighPressureTolerance.Text = Loc.GetString("plant-analyzer-tolerance-high-pressure", ("highPressureTolerance", $"{msg.HighPressureTolerance:F1}"));
PestTolerance.Text = Loc.GetString("plant-analyzer-tolerance-pest-tolerance", ("pestTolerance", $"{msg.PestTolerance:F1}"));
WeedTolerance.Text = Loc.GetString("plant-analyzer-tolerance-weed-tolerance", ("weedTolerance", $"{msg.WeedTolerance:F1}"));
// Misc
if (adv != null)
{
var advInst = adv.Value;
StringBuilder mutations = new();
if (advInst.Mutations.HasFlag(MutationFlags.TurnIntoKudzu))
{
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-turnintokudzu"));
}
if (advInst.Mutations.HasFlag(MutationFlags.Seedless))
{
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-seedless"));
}
if (advInst.Mutations.HasFlag(MutationFlags.Ligneous))
{
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-ligneous"));
}
if (advInst.Mutations.HasFlag(MutationFlags.CanScream))
{
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-canscream"));
}
StringBuilder mutations = new();
Traits.Text = Loc.GetString("plant-analyzer-plant-mutations-text", ("traits", mutations.ToString()));
}
else
if (msg.Mutations.HasFlag(MutationFlags.TurnIntoKudzu))
{
Traits.Text = Loc.GetString("plant-analyzer-plant-mutations-text", ("traits", "-"));
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-turnintokudzu"));
}
if (msg.Mutations.HasFlag(MutationFlags.Seedless))
{
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-seedless"));
}
if (msg.Mutations.HasFlag(MutationFlags.Ligneous))
{
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-ligneous"));
}
if (msg.Mutations.HasFlag(MutationFlags.CanScream))
{
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-canscream"));
}
if (msg.Mutations.HasFlag(MutationFlags.Unviable))
{
mutations.Append(IndentedNewline);
mutations.Append(Loc.GetString("plant-analyzer-mutation-unviable"));
}
var traitsText = mutations.Length == 0 ? "-" : mutations.ToString();
Traits.Text = Loc.GetString("plant-analyzer-plant-mutations-text", ("traits", traitsText));
StringBuilder speciation = new();
if (msg.Speciation is null)
{
@@ -24,15 +24,14 @@ namespace Content.Client.Shuttles.BUI
});
}
private void OnMaxShuttleSpeedChanged(NetEntity? entityUid, float maxSpeed)
private void OnMaxShuttleSpeedChanged(float? maxSpeed)
{
SendMessage(new SetMaxShuttleSpeedRequest
{
ShuttleEntityUid = entityUid,
MaxSpeed = maxSpeed,
});
}
private void OnNetworkPortButtonPressed(string sourcePort, string targetPort)
{
SendMessage(new ShuttlePortButtonPressedMessage
@@ -3,6 +3,8 @@
// See AGPLv3.txt for details.
using Content.Shared._NF.Shuttles.Events;
using Robust.Client.UserInterface.Controls;
using System.Text.RegularExpressions;
using System;
namespace Content.Client.Shuttles.UI
{
@@ -10,7 +12,7 @@ namespace Content.Client.Shuttles.UI
{
private readonly ButtonGroup _buttonGroup = new();
public event Action<NetEntity?, InertiaDampeningMode>? OnInertiaDampeningModeChanged;
public event Action<NetEntity?, float>? OnMaxShuttleSpeedChanged;
public event Action<float?>? OnMaxShuttleSpeedChanged;
public event Action<string, string>? OnNetworkPortButtonPressed;
private void NfInitialize()
@@ -23,8 +25,7 @@ namespace Content.Client.Shuttles.UI
MaximumIFFDistanceValue.OnValueChanged += args => OnRangeFilterChanged(args);
// Frontier - Maximum Shuttle Speed
MaximumShuttleSpeedValue.GetChild(0).GetChild(1).Margin = new Thickness(8, 0, 0, 0);
MaximumShuttleSpeedValue.OnValueChanged += args => OnMaxSpeedChanged(args);
MaximumShuttleSpeedValue.OnTextChanged += args => OnMaxSpeedChanged(args);
DampenerOff.OnPressed += _ => SetDampenerMode(InertiaDampeningMode.Off);
DampenerOn.OnPressed += _ => SetDampenerMode(InertiaDampeningMode.Dampen);
@@ -99,10 +100,11 @@ namespace Content.Client.Shuttles.UI
}
// Frontier - Maximum Shuttle Speed
private void OnMaxSpeedChanged(int value)
private void OnMaxSpeedChanged(LineEdit.LineEditEventArgs value)
{
_entManager.TryGetNetEntity(_shuttleEntity, out var shuttle);
OnMaxShuttleSpeedChanged?.Invoke(shuttle, value);
MaximumShuttleSpeedValue.Text = Regex.Replace(MaximumShuttleSpeedValue.Text, "[^0-9]", "");
float.TryParse(MaximumShuttleSpeedValue.Text, out var speed);
OnMaxShuttleSpeedChanged?.Invoke(MaximumShuttleSpeedValue.Text == "" ? null : speed);
}
private void NfAddShuttleDesignation(EntityUid? shuttle)
@@ -111,11 +113,11 @@ namespace Content.Client.Shuttles.UI
if (_entManager.TryGetComponent<MetaDataComponent>(shuttle, out var metadata))
{
var shipName = metadata.EntityName;
// Try to find a designation in the format XXX-### (like CIV-748)
// by checking each word in the ship name
var shipNameParts = shipName.Split(' ');
foreach (var part in shipNameParts)
{
// Check if this part matches the designation format (e.g., CIV-748)
@@ -132,7 +134,7 @@ namespace Content.Client.Shuttles.UI
}
}
}
// If we get here, no designation was found, so just show the full name
NavDisplayLabel.Text = shipName;
// Leave ShuttleDesignation.Text as "Unknown" (the default)
@@ -8,7 +8,7 @@ namespace Content.Client.Shuttles.UI
public sealed partial class ShuttleConsoleWindow
{
public event Action<NetEntity?, InertiaDampeningMode>? OnInertiaDampeningModeChanged;
public event Action<NetEntity?, float>? OnMaxShuttleSpeedChanged;
public event Action<float?>? OnMaxShuttleSpeedChanged;
public event Action<string, string>? OnNetworkPortButtonPressed;
private void NfInitialize()
@@ -18,11 +18,11 @@ namespace Content.Client.Shuttles.UI
OnInertiaDampeningModeChanged?.Invoke(entity, mode);
};
NavContainer.OnMaxShuttleSpeedChanged += (entityUid, maxSpeed) =>
NavContainer.OnMaxShuttleSpeedChanged += (maxSpeed) =>
{
OnMaxShuttleSpeedChanged?.Invoke(entityUid, maxSpeed);
OnMaxShuttleSpeedChanged?.Invoke(maxSpeed);
};
NavContainer.OnNetworkPortButtonPressed += (sourcePort, targetPort) =>
{
OnNetworkPortButtonPressed?.Invoke(sourcePort, targetPort);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class CompanyMembers2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class CompanyMembers : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "company_members",
columns: table => new
{
player_user_id = table.Column<Guid>(type: "uuid", nullable: false),
company_id = table.Column<string>(type: "text", nullable: false),
owner = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_company_members", x => new { x.player_user_id, x.company_id });
table.ForeignKey(
name: "FK_company_members_player_player_user_id",
column: x => x.player_user_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.Cascade);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "company_members");
}
}
}
@@ -20,7 +20,7 @@ namespace Content.Server.Database.Migrations.Postgres
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.1")
.HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -581,6 +581,26 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("blacklist", (string)null);
});
modelBuilder.Entity("Content.Server.Database.CompanyMember", b =>
{
b.Property<Guid>("PlayerUserId")
.HasColumnType("uuid")
.HasColumnName("player_user_id");
b.Property<string>("CompanyId")
.HasColumnType("text")
.HasColumnName("company_id");
b.Property<bool>("Owner")
.HasColumnType("boolean")
.HasColumnName("owner");
b.HasKey("PlayerUserId", "CompanyId")
.HasName("PK_company_members");
b.ToTable("company_members", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.Property<int>("Id")
@@ -1680,6 +1700,19 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.CompanyMember", b =>
{
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("CompanyMembers")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_company_members_player_player_user_id");
b.Navigation("Player");
});
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
@@ -2080,6 +2113,8 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("AdminWatchlistsReceived");
b.Navigation("CompanyMembers");
b.Navigation("JobWhitelists");
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class CompanyMembers : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "company_members",
columns: table => new
{
player_user_id = table.Column<Guid>(type: "TEXT", nullable: false),
company_id = table.Column<string>(type: "TEXT", nullable: false),
owner = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_company_members", x => new { x.player_user_id, x.company_id });
table.ForeignKey(
name: "FK_company_members_player_player_user_id",
column: x => x.player_user_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.Cascade);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "company_members");
}
}
}
@@ -15,7 +15,7 @@ namespace Content.Server.Database.Migrations.Sqlite
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
@@ -550,6 +550,26 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("blacklist", (string)null);
});
modelBuilder.Entity("Content.Server.Database.CompanyMember", b =>
{
b.Property<Guid>("PlayerUserId")
.HasColumnType("TEXT")
.HasColumnName("player_user_id");
b.Property<string>("CompanyId")
.HasColumnType("TEXT")
.HasColumnName("company_id");
b.Property<bool>("Owner")
.HasColumnType("INTEGER")
.HasColumnName("owner");
b.HasKey("PlayerUserId", "CompanyId")
.HasName("PK_company_members");
b.ToTable("company_members", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.Property<int>("Id")
@@ -1604,6 +1624,19 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.CompanyMember", b =>
{
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("CompanyMembers")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_company_members_player_player_user_id");
b.Navigation("Player");
});
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
@@ -1632,7 +1665,7 @@ namespace Content.Server.Database.Migrations.Sqlite
b1.HasKey("ConnectionLogId");
b1.ToTable("connection_log");
b1.ToTable("connection_log", (string)null);
b1.WithOwner()
.HasForeignKey("ConnectionLogId")
@@ -1677,7 +1710,7 @@ namespace Content.Server.Database.Migrations.Sqlite
b1.HasKey("PlayerId");
b1.ToTable("player");
b1.ToTable("player", (string)null);
b1.WithOwner()
.HasForeignKey("PlayerId")
@@ -1800,7 +1833,7 @@ namespace Content.Server.Database.Migrations.Sqlite
b1.HasKey("ServerBanId");
b1.ToTable("server_ban");
b1.ToTable("server_ban", (string)null);
b1.WithOwner()
.HasForeignKey("ServerBanId")
@@ -1877,7 +1910,7 @@ namespace Content.Server.Database.Migrations.Sqlite
b1.HasKey("ServerRoleBanId");
b1.ToTable("server_role_ban");
b1.ToTable("server_role_ban", (string)null);
b1.WithOwner()
.HasForeignKey("ServerRoleBanId")
@@ -2004,6 +2037,8 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("AdminWatchlistsReceived");
b.Navigation("CompanyMembers");
b.Navigation("JobWhitelists");
});
+24
View File
@@ -46,6 +46,7 @@ namespace Content.Server.Database
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
public DbSet<BanTemplate> BanTemplate { get; set; } = null!;
public DbSet<IPIntelCache> IPIntelCache { get; set; } = null!;
public DbSet<CompanyMember> CompanyMembers { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -371,6 +372,14 @@ namespace Content.Server.Database
.OwnsOne(p => p.HWId)
.Property(p => p.Type)
.HasDefaultValue(HwidType.Legacy);
// Mono
modelBuilder.Entity<CompanyMember>()
.HasOne(w => w.Player)
.WithMany(p => p.CompanyMembers)
.HasForeignKey(w => w.PlayerUserId)
.HasPrincipalKey(p => p.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
@@ -600,6 +609,7 @@ namespace Content.Server.Database
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
public List<CompanyMember> CompanyMembers { get; set; } = null!; // Mono
}
[Table("whitelist")]
@@ -1337,4 +1347,18 @@ namespace Content.Server.Database
/// </summary>
public float Score { get; set; }
}
// Mono-Start
[PrimaryKey(nameof(PlayerUserId), nameof(CompanyId))]
public class CompanyMember
{
[Required, ForeignKey("Player")]
public Guid PlayerUserId { get; set; }
public Player Player { get; set; } = default!;
public bool Owner { get; set; } = false;
[Required]
public string CompanyId { get; set; } = default!;
}
// Mono-End
}
@@ -287,7 +287,7 @@ namespace Content.Server.Atmos.EntitySystems
// This is intended so that matches & candles can re-use code for un-shaded layers on in-hand sprites.
// However, this could cause conflicts if something is ACTUALLY both a toggleable light and flammable.
// if that ever happens, then fire visuals will need to implement their own in-hand sprite management.
_appearance.SetData(uid, ToggleableLightVisuals.Enabled, flammable.OnFire, appearance);
_appearance.SetData(uid, ToggleableVisuals.Enabled, flammable.OnFire, appearance);
}
public void AdjustFireStacks(EntityUid uid, float relativeFireStacks, FlammableComponent? flammable = null, bool ignite = false)
+116
View File
@@ -6,8 +6,10 @@ using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Content.Server._Mono.Company;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Shared._Mono.Company;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Ghost.Roles;
@@ -1940,6 +1942,120 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
#endregion
// Mono
#region Company
public async Task<bool> AddCompanyMember(Guid player, ProtoId<CompanyPrototype> company)
{
await using var db = await GetDb();
var exists = await db.DbContext.CompanyMembers
.Where(w => w.PlayerUserId == player)
.Where(w => w.CompanyId == company.Id)
.AnyAsync();
if (exists)
return false;
var member = new CompanyMember
{
PlayerUserId = player,
CompanyId = company,
};
db.DbContext.CompanyMembers.Add(member);
await db.DbContext.SaveChangesAsync();
return true;
}
public async Task<List<string>> GetPlayerCompanies(Guid player, CancellationToken cancel)
{
await using var db = await GetDb(cancel);
return await db.DbContext.CompanyMembers
.Where(w => w.PlayerUserId == player)
.Select(w => w.CompanyId)
.ToListAsync(cancel);
}
public async Task<IEnumerable<CompanyMemberRecord>> GetCompanyMembers(ProtoId<CompanyPrototype> company, CancellationToken cancel)
{
await using var db = await GetDb(cancel);
var members = await db.DbContext.CompanyMembers
.Where(w => w.CompanyId == company.Id)
.Include(c => c.Player)
.ToListAsync(cancel);
return members.Select(m => new CompanyMemberRecord()
{
Company = company,
Owner = m.Owner,
PlayerUserId = m.PlayerUserId,
LastSeenUserName = m.Player.LastSeenUserName,
});
}
public async Task<IEnumerable<CompanyMemberRecord>> GetAllCompanyMembers(CancellationToken cancel)
{
await using var db = await GetDb(cancel);
var members = await db.DbContext.CompanyMembers
.Include(c => c.Player)
.ToListAsync(cancel);
return members.Select(m => new CompanyMemberRecord()
{
Company = m.CompanyId,
Owner = m.Owner,
PlayerUserId = m.PlayerUserId,
LastSeenUserName = m.Player.LastSeenUserName,
});
}
public async Task<CompanyMemberRecord?> GetCompanyMember(ProtoId<CompanyPrototype> company, Guid player, CancellationToken cancel)
{
await using var db = await GetDb(cancel);
var member = await db.DbContext.CompanyMembers
.Where(w => w.CompanyId == company.Id)
.Where(w => w.PlayerUserId == player)
.Include(c => c.Player)
.FirstOrDefaultAsync();
if (member == null)
return null;
return new CompanyMemberRecord()
{
Company = company,
LastSeenUserName = member.Player.LastSeenUserName,
Owner = member.Owner,
PlayerUserId = member.PlayerUserId,
};
}
public async Task SetCompanyOwner(ProtoId<CompanyPrototype> company, Guid player, bool owner)
{
await using var db = await GetDb();
await db.DbContext.CompanyMembers
.Where(w => w.CompanyId == company.Id)
.Where(w => w.PlayerUserId == player)
.ExecuteUpdateAsync(setters => setters.SetProperty(m => m.Owner, owner));
}
public async Task<bool> RemoveCompanyMember(Guid player, ProtoId<CompanyPrototype> company)
{
await using var db = await GetDb();
var entry = await db.DbContext.CompanyMembers
.Where(w => w.PlayerUserId == player)
.Where(w => w.CompanyId == company.Id)
.SingleOrDefaultAsync();
if (entry == null)
return false;
db.DbContext.CompanyMembers.Remove(entry);
await db.DbContext.SaveChangesAsync();
return true;
}
#endregion
public abstract Task SendNotification(DatabaseNotification notification);
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
+60 -1
View File
@@ -22,6 +22,8 @@ using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using LogLevel = Robust.Shared.Log.LogLevel;
using MSLogLevel = Microsoft.Extensions.Logging.LogLevel;
using Content.Shared._Mono.Company;
using Content.Server._Mono.Company; // Mono
namespace Content.Server.Database
{
@@ -351,6 +353,19 @@ namespace Content.Server.Database
#endregion
// Mono
#region Company
Task AddCompanyMember(Guid player, ProtoId<CompanyPrototype> company);
Task<List<string>> GetPlayerCompanies(Guid player, CancellationToken cancel = default);
Task<IEnumerable<CompanyMemberRecord>> GetCompanyMembers(ProtoId<CompanyPrototype> company, CancellationToken cancel = default);
Task<IEnumerable<CompanyMemberRecord>> GetAllCompanyMembers(CancellationToken cancel = default);
Task<CompanyMemberRecord?> GetCompanyMember(ProtoId<CompanyPrototype> company, Guid player, CancellationToken cancel = default);
Task SetCompanyOwner(ProtoId<CompanyPrototype> company, Guid player, bool owner);
Task<bool> RemoveCompanyMember(Guid player, ProtoId<CompanyPrototype> company);
#endregion
#region DB Notifications
void SubscribeToNotifications(Action<DatabaseNotification> handler);
@@ -1058,7 +1073,7 @@ namespace Content.Server.Database
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.RemoveJobWhitelist(player, job));
}
// Frontier: ghost role DB ops
public Task AddGhostRoleWhitelist(Guid player, ProtoId<GhostRolePrototype> ghostRole)
{
@@ -1095,6 +1110,50 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.CleanIPIntelCache(range));
}
// Mono-Start
public Task AddCompanyMember(Guid player, ProtoId<CompanyPrototype> company)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.AddCompanyMember(player, company));
}
public Task<List<string>> GetPlayerCompanies(Guid player, CancellationToken cancel = default)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetPlayerCompanies(player, cancel));
}
public Task<IEnumerable<CompanyMemberRecord>> GetCompanyMembers(ProtoId<CompanyPrototype> company, CancellationToken cancel = default)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetCompanyMembers(company, cancel));
}
public Task<IEnumerable<CompanyMemberRecord>> GetAllCompanyMembers(CancellationToken cancel = default)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAllCompanyMembers(cancel));
}
public Task<CompanyMemberRecord?> GetCompanyMember(ProtoId<CompanyPrototype> company, Guid player, CancellationToken cancel = default)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetCompanyMember(company, player, cancel));
}
public Task SetCompanyOwner(ProtoId<CompanyPrototype> company, Guid player, bool owner)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.SetCompanyOwner(company, player, owner));
}
public Task<bool> RemoveCompanyMember(Guid player, ProtoId<CompanyPrototype> company)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.RemoveCompanyMember(player, company));
}
// Mono-End
public void SubscribeToNotifications(Action<DatabaseNotification> handler)
{
lock (_notificationHandlers)
@@ -44,10 +44,15 @@ namespace Content.Server.Destructible
[Dependency] public readonly IPrototypeManager PrototypeManager = default!;
[Dependency] public readonly IAdminLogManager _adminLogger = default!;
// Mono
private EntityQuery<DestructibleComponent> _destructibleQuery;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DestructibleComponent, DamageChangedEvent>(Execute);
_destructibleQuery = GetEntityQuery<DestructibleComponent>();
}
/// <summary>
@@ -131,7 +136,7 @@ namespace Content.Server.Destructible
public bool TryGetDestroyedAt(Entity<DestructibleComponent?> ent, [NotNullWhen(true)] out FixedPoint2? destroyedAt)
{
destroyedAt = null;
if (!Resolve(ent, ref ent.Comp, false))
if (!_destructibleQuery.TryComp(ent, out ent.Comp)) // Mono
return false;
destroyedAt = DestroyedAt(ent, ent.Comp);
+2
View File
@@ -1,3 +1,4 @@
using Content.Server._Mono.Company; // Mono
using Content.Server._NF.Auth;
using Content.Server.Acz;
using Content.Server.Administration;
@@ -155,6 +156,7 @@ namespace Content.Server.Entry
_euiManager.Initialize();
IoCManager.Resolve<CompanyManager>().Initialize(); // Mono
IoCManager.Resolve<IGameMapManager>().Initialize();
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
IoCManager.Resolve<IBanManager>().Initialize();
@@ -1,9 +1,15 @@
namespace Content.Server.Explosion.Components;
/// <summary>
/// calls the trigger when the object is initialized
/// </summary>
[RegisterComponent]
public sealed partial class TriggerOnSpawnComponent : Component
namespace Content.Server.Explosion.Components
{
/// <summary>
/// calls the trigger when the object is initialized
/// </summary>
[RegisterComponent]
public sealed partial class TriggerOnSpawnComponent : Component
{
/// <summary>
/// Starts a timer on spawn rather than instantly triggering
/// </summary>
[DataField("timerOnly")] // Mono: Added "timerOnly" boolean
public bool timerOnly = false;
}
}
@@ -81,7 +81,7 @@ public sealed partial class ExplosionSystem
// As above, but for space-based explosion propagating from space onto grids.
HashSet<EntityUid> encounteredGrids = new();
Dictionary<EntityUid, HashSet<Vector2i>>? previousGridJump;
// Dictionary<EntityUid, HashSet<Vector2i>>? previousGridJump; // Mono
// variables for transforming between grid and space-coordinates
var spaceMatrix = Matrix3x2.Identity;
@@ -174,14 +174,27 @@ public sealed partial class ExplosionSystem
// In order to treat "cost" of moving off a grid on the same level as moving onto a grid, both space -> grid and grid -> space have to be delayed by one iteration.
previousSpaceJump = spaceJump;
previousGridJump = spaceData?.GridJump;
spaceJump = new();
var newTileCount = 0;
if (previousGridJump != null)
encounteredGrids.UnionWith(previousGridJump.Keys);
// Mono - explosion fix, feat. gemini from googer
// 1. Process space expansion FIRST.
// If space-data is null, but some grid-based explosion reached space, we need to initialize it.
if (spaceData == null && previousSpaceJump.Count != 0)
spaceData = new ExplosionSpaceTileFlood(this, epicenter, referenceGrid, localGrids, maxDistance);
// If the explosion has reached space, do that neighbors finding step as well.
if (spaceData != null)
newTileCount += spaceData.AddNewTiles(iteration, previousSpaceJump);
// 2. Feed the new grid jumps immediately into THIS iteration (0 delay for Space -> Grid transitions)
var currentGridJump = spaceData?.GridJump;
if (currentGridJump != null)
encounteredGrids.UnionWith(currentGridJump.Keys);
// 3. Process grids and immediate space->grid jumps
foreach (var grid in encounteredGrids)
{
// is this a new grid, for which we must create a new explosion data set
@@ -205,18 +218,10 @@ public sealed partial class ExplosionSystem
}
// get the new neighbours, and populate gridToSpaceTiles in the process.
newTileCount += data.AddNewTiles(iteration, previousGridJump?.GetValueOrDefault(grid));
newTileCount += data.AddNewTiles(iteration, currentGridJump?.GetValueOrDefault(grid));
spaceJump.UnionWith(data.SpaceJump);
}
// if space-data is null, but some grid-based explosion reached space, we need to initialize it.
if (spaceData == null && previousSpaceJump.Count != 0)
spaceData = new ExplosionSpaceTileFlood(this, epicenter, referenceGrid, localGrids, maxDistance);
// If the explosion has reached space, do that neighbors finding step as well.
if (spaceData != null)
newTileCount += spaceData.AddNewTiles(iteration, previousSpaceJump);
// Does adding these tiles bring us above the total target intensity?
tilesInIteration.Add(newTileCount);
if (newTileCount * stepSize >= remainingIntensity)
@@ -303,7 +303,10 @@ namespace Content.Server.Explosion.EntitySystems
private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args)
{
Trigger(uid);
if (component.timerOnly == true)
StartTimer(uid,uid);
else
Trigger(uid);
}
private void OnActivate(EntityUid uid, TriggerOnActivateComponent component, ActivateInWorldEvent args)
@@ -85,6 +85,9 @@ public sealed class SmokeSystem : EntitySystem
if (_smokeAffectedQuery.HasComponent(args.OtherEntity))
return;
if (TerminatingOrDeleted(args.OtherEntity)) // Mono: Testfail fix
return;
var smokeAffected = AddComp<SmokeAffectedComponent>(args.OtherEntity);
smokeAffected.SmokeEntity = entity;
smokeAffected.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
@@ -104,6 +107,9 @@ public sealed class SmokeSystem : EntitySystem
if (!TryComp<PhysicsComponent>(args.OtherEntity, out var body))
return;
if (TerminatingOrDeleted(args.OtherEntity)) // Mono: Testfail fix
return;
foreach (var ent in _physics.GetContactingEntities(args.OtherEntity, body))
{
if (exists && ent == entity.Owner)
+2
View File
@@ -1,3 +1,4 @@
using Content.Server._Mono.Company; // Mono
using Content.Server._NF.Auth;
using Content.Server.Administration;
using Content.Server.Administration.Logs;
@@ -78,6 +79,7 @@ namespace Content.Server.IoC
IoCManager.Register<MultiServerKickManager>();
IoCManager.Register<CVarControlManager>();
IoCManager.Register<MiniAuthManager>(); //Frontier
IoCManager.Register<CompanyManager>(); // Mono
IoCManager.Register<DiscordLink>();
IoCManager.Register<DiscordChatLink>();
@@ -17,5 +17,11 @@ public sealed partial class LatheProducingComponent : Component
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan ProductionLength;
/// <summary>
/// The Entity that queued this recipe - Mono
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public EntityUid? Actor;
}
+15 -5
View File
@@ -58,6 +58,7 @@ namespace Content.Server.Lathe
[Dependency] private readonly ReagentSpeedSystem _reagentSpeed = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly ContrabandTurnInSystem _contraband = default!; // Mono
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly DeviceLinkSystem _deviceLink = default!; // Mono
@@ -225,7 +226,7 @@ namespace Content.Server.Lathe
}
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, int quantity, LatheComponent? component = null, // Frontier: add quantity
bool canDebt = false) // Mono
EntityUid? actor = null, bool canDebt = false) // Mono
{
if (!Resolve(uid, ref component))
return false;
@@ -243,7 +244,8 @@ namespace Content.Server.Lathe
if (component.Queue.Count > 0 && component.Queue[^1].Recipe.ID == recipe.ID)
component.Queue[^1].ItemsRequested += quantity;
else
component.Queue.Add(new LatheRecipeBatch(recipe, 0, quantity));
component.Queue.Add(new LatheRecipeBatch(recipe, 0, quantity,
GetNetEntity(actor))); // Mono: Adds actor
// End Frontier
// component.Queue.Add(recipe); // Frontier
@@ -264,6 +266,7 @@ namespace Content.Server.Lathe
// Frontier: handle batches
var batch = component.Queue.First();
var actor = batch.Actor; // Mono: Adds actor
var recipe = batch.Recipe;
// <Mono> - resources now consumed as the production goes
if (!CanProduce(uid, recipe, 1, component))
@@ -296,6 +299,7 @@ namespace Content.Server.Lathe
var lathe = EnsureComp<LatheProducingComponent>(uid);
lathe.StartTime = _timing.CurTime;
lathe.ProductionLength = time * component.FinalTimeMultiplier; // Frontier: TimeMultiplier<FinalTimeMultiplier
lathe.Actor = GetEntity(actor);
component.CurrentRecipe = recipe;
var ev = new LatheStartPrintingEvent(recipe);
@@ -325,8 +329,13 @@ namespace Content.Server.Lathe
// Frontier: adjust price before merge (stack prices changed once)
if (result.Valid)
{
ModifyPrintedEntityPrice(uid, comp, result);
// End Frontier
// End Frontier
// Mono: Handle printable contraband
_contraband.HandleContrabandValueByCompany(result, prodComp.Actor);
}
_stack.TryMergeToContacts(result);
}
@@ -355,8 +364,9 @@ namespace Content.Server.Lathe
}
// <Mono>
// Add the actor that previously queued to looped items
if (comp.Loop)
TryAddToQueue(uid, comp.CurrentRecipe, 1, comp, true);
TryAddToQueue(uid, comp.CurrentRecipe, 1, comp, prodComp.Actor, true);
_deviceLink.SendSignal(uid, comp.ProducedPort, true);
// </Mono>
@@ -493,7 +503,7 @@ namespace Content.Server.Lathe
if (_proto.TryIndex(args.ID, out LatheRecipePrototype? recipe))
{
// Frontier: batching recipes
if (TryAddToQueue(uid, recipe, args.Quantity, component))
if (TryAddToQueue(uid, recipe, args.Quantity, component, args.Actor))
{
_adminLogger.Add(LogType.Action,
LogImpact.Low,
+7 -1
View File
@@ -121,8 +121,14 @@ public sealed partial class MechSystem : SharedMechSystem
if (!TryComp<BatteryComponent>(ent.Comp.BatterySlot.ContainedEntity, out var battery))
return;
ent.Comp.CriticalPowerState = battery.CurrentCharge / battery.MaxCharge <= 0.05f;
//ent.Comp.CriticalPowerState = battery.CurrentCharge / battery.MaxCharge <= 0.05f;
// Mono
var criticalState = battery.CurrentCharge / battery.MaxCharge <= 0.05f;
if (criticalState == ent.Comp.CriticalPowerState)
return;
ent.Comp.CriticalPowerState = criticalState;
// End Mono
Dirty(ent);
_movementSpeed.RefreshMovementSpeedModifiers(ent);
}
@@ -35,12 +35,13 @@ public sealed class JetpackSystem : SharedJetpackSystem
/// </summary>
private void OnJetpackActivated(EntityUid uid, ActiveJetpackComponent component, ComponentStartup args)
{
if (TryComp<JetpackComponent>(uid, out var jetpack) && jetpack.Stealth == false)
if (TryComp<JetpackComponent>(uid, out var jetpack) && jetpack.DetectionRange > 0)
{
var blip = EnsureComp<RadarBlipComponent>(uid);
blip.Config.Color = Color.Cyan;
blip.Config.Bounds = new(-0.25f, -0.25f, 0.25f, 0.25f);
blip.VisibleFromOtherGrids = true;
blip.MaxDistance = jetpack.DetectionRange;
}
}
@@ -50,6 +50,11 @@ public sealed partial class NPCRangedCombatComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
public float ShootAccumulator;
/// <summary>
/// Mono - accept having to fire for up to this many seconds to destroy an obstacle.
/// </summary>
public float ShotsThreshold = 2;
/// <summary>
/// Sound to play if the target enters line of sight.
/// </summary>
@@ -0,0 +1,13 @@
// Mono - whole file
namespace Content.Server.NPC.Queries.Considerations;
/// <summary>
/// Identical to NPCCombatSystem.Ranged LOS check.
/// </summary>
public sealed partial class GunTargetGoodCon : UtilityConsideration
{
[DataField]
public float ShootThroughThreshold = 2f;
}
// TODO: rewrite HTN and make this not evil
@@ -0,0 +1,10 @@
// Mono - whole file
namespace Content.Server.NPC.Queries.Considerations;
/// <summary>
/// Returns 1f if the target is alive or can't be alive, or 0f if not.
/// </summary>
public sealed partial class TargetIsAliveOrNACon : UtilityConsideration
{
}
@@ -6,12 +6,12 @@ using Content.Shared.Whitelist;
namespace Content.Server.NPC.Queries.Queries;
/// <summary>
/// Returns nearby shuttles considered hostile from <see cref="FactionSystem"/>
/// Returns nearby entities tagged with ShipNpcTargetComponent.
/// </summary>
public sealed partial class NearbyHostileShuttlesQuery : UtilityQuery
public sealed partial class NearbyNpcTargetsQuery : UtilityQuery
{
[DataField]
public float Range = 2000f;
public float Range = 4000f;
// TODO: make this use factions
[DataField]
@@ -1,6 +1,7 @@
using Content.Server.NPC.Queries.Considerations;
using Content.Server.NPC.Queries.Queries;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; // Mono
namespace Content.Server.NPC.Queries;
@@ -10,12 +11,22 @@ namespace Content.Server.NPC.Queries;
/// These results are then run through the considerations.
/// </summary>
[Prototype]
public sealed partial class UtilityQueryPrototype : IPrototype
public sealed partial class UtilityQueryPrototype : IPrototype, IInheritingPrototype // Mono
{
[IdDataField]
public string ID { get; private set; } = default!;
[ViewVariables(VVAccess.ReadWrite), DataField("query")]
// <Mono>
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer<UtilityQueryPrototype>))]
public string[]? Parents { get; private set; }
/// <inheritdoc />
[NeverPushInheritance]
[AbstractDataField]
public bool Abstract { get; private set; }
// </Mono>
[ViewVariables(VVAccess.ReadWrite), DataField("query"), AlwaysPushInheritance]
public List<UtilityQuery> Query = new();
[ViewVariables(VVAccess.ReadWrite), DataField("considerations")]
@@ -1,9 +1,11 @@
using System.Numerics;
using Content.Server.Destructible; // Mono
using Content.Server.NPC.Components;
using Content.Shared._Goobstation.Weapons.SmartGun;
using Content.Shared.CombatMode;
using Content.Shared.Damage.Components;
using Content.Shared.Interaction;
using Content.Shared.NPC.Components; // Mono
using Content.Shared.Physics;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
@@ -18,6 +20,7 @@ public sealed partial class NPCCombatSystem
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly RotateToFaceSystem _rotate = default!;
[Dependency] private readonly SharedLaserPointerSystem _pointer = default!; // Goobstation
[Dependency] private readonly DestructibleSystem _destructible = default!;
private EntityQuery<CombatModeComponent> _combatQuery;
private EntityQuery<NPCSteeringComponent> _steeringQuery;
@@ -25,6 +28,7 @@ public sealed partial class NPCCombatSystem
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<RequireProjectileTargetComponent> _requireTargetQuery; // Mono
private EntityQuery<NpcFactionMemberComponent> _factionQuery; // Mono
// TODO: Don't predict for hitscan
private const float ShootSpeed = 20f;
@@ -42,6 +46,7 @@ public sealed partial class NPCCombatSystem
_steeringQuery = GetEntityQuery<NPCSteeringComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_requireTargetQuery = GetEntityQuery<RequireProjectileTargetComponent>(); // Mono
_factionQuery = GetEntityQuery<NpcFactionMemberComponent>(); // Mono
SubscribeLocalEvent<NPCRangedCombatComponent, ComponentStartup>(OnRangedStartup);
SubscribeLocalEvent<NPCRangedCombatComponent, ComponentShutdown>(OnRangedShutdown);
@@ -74,10 +79,12 @@ public sealed partial class NPCCombatSystem
private void UpdateRanged(float frameTime)
{
var query = EntityQueryEnumerator<NPCRangedCombatComponent, TransformComponent>();
var query = EntityQueryEnumerator<NPCRangedCombatComponent>();
while (query.MoveNext(out var uid, out var comp, out var xform))
while (query.MoveNext(out var uid, out var comp))
{
var xform = Transform(uid); // Mono
if (!_gun.TryGetGun(uid, out var gunUid, out var gun))
{
comp.Status = CombatStatus.NoWeapon;
@@ -152,12 +159,8 @@ public sealed partial class NPCCombatSystem
if (comp.LOSAccumulator < 0f)
{
comp.LOSAccumulator += UnoccludedCooldown;
// For consistency with NPC steering. // Mono
comp.TargetInLOS = _interaction.InRangeUnobstructed(uid, comp.Target, distance + 0.1f, comp.ObstructedMask, predicate: (EntityUid entity) =>
{
return _physicsQuery.TryGetComponent(entity, out var physics) && (physics.CollisionLayer & (int)comp.BulletMask) == 0 // ignore if it can't collide with bullets
|| _requireTargetQuery.HasComponent(entity); // or if it requires targeting
});
// Mono
comp.TargetInLOS = InRangeGoodTarget((gunUid, gun), uid, comp.Target, distance, comp.ShotsThreshold, comp.ObstructedMask, comp.BulletMask);
}
if (!comp.TargetInLOS)
@@ -265,4 +268,37 @@ public sealed partial class NPCCombatSystem
target);
}
}
// Mono
public bool InRangeGoodTarget(Entity<GunComponent?> gun, EntityUid source, EntityUid target, float distance, float shotsThreshold, CollisionGroup obstructedMask = CollisionGroup.Opaque, CollisionGroup bulletMask = CollisionGroup.Impassable | CollisionGroup.BulletImpassable)
{
if (!Resolve(gun, ref gun.Comp, false))
return false;
var dmg = _gun.GetNextDamage(gun).GetTotal().Float(); // this will be wrong but damageable is evil and i have no idea how to do this better
var destroyAccum = 0f;
var firerate = gun.Comp.FireRateModified;
var threshold = shotsThreshold;
// For consistency with NPC steering. // Mono
return _interaction.InRangeUnobstructed(source, target, distance + 0.1f, obstructedMask,
predicate: (EntityUid uid) => {
if (_physicsQuery.TryGetComponent(uid, out var physics)
&& (physics.CollisionLayer & (int)bulletMask) == 0
) // this will collide with the bullet
return true;
if (_requireTargetQuery.HasComponent(uid)) // requires targeting so it won't collide
return true;
// do not consider living things for shoot-through
if (!_factionQuery.HasComp(uid)
&& _destructible.TryGetDestroyedAt(uid, out var at))
{
destroyAccum += at.Value.Float() / dmg / firerate;
if (destroyAccum < threshold)
return true;
}
return false;
});
}
}
+39 -6
View File
@@ -1,4 +1,5 @@
using Content.Server.Atmos.Components;
using Content.Server.Destructible; // Mono
using Content.Server.Fluids.EntitySystems;
using Content.Server._Mono.NPC.HTN; // Mono
using Content.Server.NPC.Queries;
@@ -12,6 +13,7 @@ using Content.Server.Shuttles.Components; // Mono
using Content.Server.Storage.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Examine;
using Content.Shared.Fluids.Components;
using Content.Shared.Hands.Components;
@@ -19,16 +21,20 @@ using Content.Shared.Inventory;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC.Components; // Mono
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Physics;
using Content.Shared.Tools.Systems;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Content.Server.Weapons.Ranged.Systems; // Mono
using Content.Shared.Whitelist;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.Containers;
using Robust.Shared.Physics.Components; // Mono
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
@@ -58,9 +64,15 @@ public sealed class NPCUtilitySystem : EntitySystem
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly MobThresholdSystem _thresholdSystem = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; // Frontier
[Dependency] private readonly DestructibleSystem _destructible = default!; // Mono
[Dependency] private readonly GunSystem _gun = default!; // Mono
[Dependency] private readonly NPCCombatSystem _npcCombat = default!;
private EntityQuery<PuddleComponent> _puddleQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<PhysicsComponent> _physicsQuery; // Mono
private EntityQuery<RequireProjectileTargetComponent> _requireTargetQuery; // Mono
private EntityQuery<NpcFactionMemberComponent> _factionQuery; // Mono
private ObjectPool<HashSet<EntityUid>> _entPool =
new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), 256);
@@ -75,6 +87,9 @@ public sealed class NPCUtilitySystem : EntitySystem
base.Initialize();
_puddleQuery = GetEntityQuery<PuddleComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>(); // Mono
_requireTargetQuery = GetEntityQuery<RequireProjectileTargetComponent>(); // Mono
_factionQuery = GetEntityQuery<NpcFactionMemberComponent>(); // Mono
}
/// <summary>
@@ -353,6 +368,17 @@ public sealed class NPCUtilitySystem : EntitySystem
return _examine.InRangeUnOccluded(owner, targetUid, radius + bufferRange, null) ? 1f : 0f;
}
// Mono
case GunTargetGoodCon con:
{
if (!_gun.TryGetGun(owner, out var gunUid, out var gun))
return 0f;
var radius = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
const float bufferRange = 0.5f;
return _npcCombat.InRangeGoodTarget((gunUid, gun), owner, targetUid, radius + bufferRange, con.ShootThroughThreshold) ? 1f : 0f;
}
case TargetIsAliveCon:
{
return _mobState.IsAlive(targetUid) ? 1f : 0f;
@@ -390,6 +416,11 @@ public sealed class NPCUtilitySystem : EntitySystem
return _statusEffectsSystem.HasStatusEffect(targetUid, "Stun") ? 1f : 0f;
}
// End Frontier
// Mono
case TargetIsAliveOrNACon:
{
return !TryComp<MobStateComponent>(targetUid, out var mobState) || _mobState.IsAlive(targetUid, mobState) ? 1f : 0f;
}
default:
throw new NotImplementedException();
}
@@ -494,7 +525,7 @@ public sealed class NPCUtilitySystem : EntitySystem
break;
}
// Mono - TODO: consider factions
case NearbyHostileShuttlesQuery shuttlesQuery:
case NearbyNpcTargetsQuery shuttlesQuery:
{
var xform = Transform(owner);
var ownGrid = xform.GridUid;
@@ -502,11 +533,13 @@ public sealed class NPCUtilitySystem : EntitySystem
{
var targetXform = Transform(target);
var targetGrid = targetXform.GridUid;
if (targetComp.NeedGrid && targetGrid == null ||
targetGrid == ownGrid ||
(_transform.GetWorldPosition(target) - _transform.GetWorldPosition(xform)).Length() > shuttlesQuery.Range ||
targetComp.NeedPower && !this.IsPowered(target, EntityManager) ||
targetGrid != null && _whitelistSystem.IsBlacklistPass(shuttlesQuery.Blacklist, targetGrid.Value))
if (targetComp.NeedGrid != NpcTargetGridMode.Either // if we care about grid..
// ..and our (non-)need for grid is equal to the (non-)absence of a grid
&& (targetComp.NeedGrid == NpcTargetGridMode.OnGrid) == (targetGrid == null)
|| targetGrid == ownGrid
|| (_transform.GetWorldPosition(target) - _transform.GetWorldPosition(xform)).Length() > shuttlesQuery.Range
|| targetComp.NeedPower && !this.IsPowered(target, EntityManager)
|| targetGrid != null && _whitelistSystem.IsBlacklistPass(shuttlesQuery.Blacklist, targetGrid.Value))
{
continue;
}
@@ -75,6 +75,7 @@ public sealed class MoverController : SharedMoverController
return;
args.Input = input;
args.SetMaxVelocity = entity.Comp.SetMaxVelocity;
}
private void PilotedShuttleRelayEvent<TEvent>(Entity<PilotedShuttleComponent> entity, ref TEvent args)
@@ -273,19 +274,27 @@ public sealed class MoverController : SharedMoverController
// Mono: all below code handling shuttle movement has been heavily modified by Monolith
//
/// <summary>
/// Get a shuttle's torque.
/// </summary>
public float GetTorque(ShuttleComponent shuttle)
{
return shuttle.AngularThrust * shuttle.AngularMultiplier;
}
/// <summary>
/// Get a shuttle's angular acceleration.
/// </summary>
public float GetAngularAcceleration(ShuttleComponent shuttle, PhysicsComponent body)
{
return shuttle.AngularThrust * body.InvI;
return GetTorque(shuttle) * body.InvI;
}
/// <summary>
/// Get shuttle thrust force in a given direction.
/// Takes local direction.
/// </summary>
public Vector2 GetDirectionThrust(Vector2 dir, ShuttleComponent shuttle)
public Vector2 GetDirectionThrust(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body, TransformComponent xform)
{
if (dir.Length() == 0f)
return Vector2.Zero;
@@ -302,28 +311,48 @@ public sealed class MoverController : SharedMoverController
// prevent NaNs
dir *= dir.X == 0 ? vertScale : dir.Y == 0 ? horizScale : MathF.Min(horizScale, vertScale);
return dir;
}
var northAngle = xform.LocalRotation;
var localVel = (-northAngle).RotateVec(body.LinearVelocity);
public Vector2 GetDirectionAccel(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body)
{
return GetDirectionThrust(dir, shuttle) * body.InvMass;
// scale our velocity-wards component by 1 / (vel/basemax + 1)
var dot = Vector2.Dot(dir, localVel);
if (dot > 0f)
{
var velLenSq = localVel.LengthSquared();
var dirCompVel = localVel * dot / velLenSq;
var velRatio = localVel.Length() / shuttle.BaseMaxLinearVelocity;
// less effect at lower velocities
var exponent = velRatio * MathF.Pow(velRatio / (1f + velRatio), 3f);
var scaledComp = dirCompVel / MathF.Pow(2f, exponent);
dir = dir - dirCompVel + scaledComp;
}
return dir * shuttle.AccelerationMultiplier;
}
/// <summary>
/// Helper function to extrapolate max velocity for a given Vector2 (really, its angle) and shuttle.
/// Get shuttle acceleration in a given direction.
/// Takes local direction.
/// </summary>
public Vector2 ObtainMaxVel(Vector2 vel, ShuttleComponent shuttle, PhysicsComponent body) // mono
public Vector2 GetDirectionAccel(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body, TransformComponent xform)
{
if (vel.Length() == 0f)
return Vector2.Zero;
return GetDirectionThrust(dir, shuttle, body, xform) * body.InvMass;
}
var thrust = GetDirectionThrust(vel, shuttle);
var twr = thrust.Length() / body.Mass;
var twrMult = MathF.Pow(twr / shuttle.BaseMaxVelocityTWR, shuttle.MaxVelocityScalingExponent);
/// <summary>
/// Get shuttle thrust force in a given world direction.
/// </summary>
public Vector2 GetWorldDirectionThrust(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body, TransformComponent xform)
{
return xform.LocalRotation.RotateVec(GetDirectionThrust((-xform.LocalRotation).RotateVec(dir), shuttle, body, xform));
}
return vel.Normalized() * MathF.Min(shuttle.BaseMaxLinearVelocity * twrMult, MathF.Min(shuttle.UpperMaxVelocity, shuttle.SetMaxVelocity));
/// <summary>
/// Get shuttle acceleration in a given world direction.
/// </summary>
public Vector2 GetWorldDirectionAccel(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body, TransformComponent xform)
{
return GetWorldDirectionThrust(dir, shuttle, body, xform) * body.InvMass;
}
private void HandleShuttleMovement(float frameTime)
@@ -335,6 +364,9 @@ public sealed class MoverController : SharedMoverController
// query all our pilots for input
var toRemove = new List<EntityUid>();
var angularMul = 0f;
var accelMul = 0f;
var setMaxVel = (float?)0f;
foreach (var pilot in piloted.InputSources)
{
var inputsEv = new GetShuttleInputsEvent(frameTime, uid);
@@ -343,7 +375,15 @@ public sealed class MoverController : SharedMoverController
if (!inputsEv.GotInput)
toRemove.Add(pilot);
else if (inputsEv.Input != null)
{
inputs.Add(inputsEv.Input.Value);
angularMul += inputsEv.AngularMul;
accelMul += inputsEv.AccelMul;
if (setMaxVel != null && inputsEv.SetMaxVelocity != null)
setMaxVel += inputsEv.SetMaxVelocity;
else
setMaxVel = null;
}
}
foreach (var remUid in toRemove)
@@ -359,6 +399,7 @@ public sealed class MoverController : SharedMoverController
{
_thruster.DisableLinearThrusters(shuttle);
PhysicsSystem.SetSleepingAllowed(uid, body, true);
shuttle.AngularMultiplier = shuttle.AccelerationMultiplier = 1f;
continue;
}
PhysicsSystem.SetSleepingAllowed(uid, body, false);
@@ -377,8 +418,17 @@ public sealed class MoverController : SharedMoverController
angularInput /= count;
brakeInput /= count;
angularMul /= count;
accelMul /= count;
if (setMaxVel != null)
setMaxVel /= count;
shuttle.AngularMultiplier = angularMul;
shuttle.AccelerationMultiplier = accelMul;
var shuttleNorthAngle = _xformSystem.GetWorldRotation(uid);
var xform = Transform(uid);
// handle movement: brake
if (brakeInput > 0f)
{
@@ -389,7 +439,7 @@ public sealed class MoverController : SharedMoverController
// Get velocity relative to the shuttle so we know which thrusters to fire
var shuttleVelocity = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
var force = GetDirectionThrust(-shuttleVelocity, shuttle);
var force = GetDirectionThrust(-shuttleVelocity, shuttle, body, xform);
if (force.X < 0f)
{
@@ -473,7 +523,7 @@ public sealed class MoverController : SharedMoverController
var linearDir = angle.GetDir();
var dockFlag = linearDir.AsFlag();
var totalForce = GetDirectionThrust(linearInput, shuttle);
var totalForce = GetDirectionThrust(linearInput, shuttle, body, xform);
// Won't just do cardinal directions.
foreach (DirectionFlag dir in Enum.GetValues(typeof(DirectionFlag)))
@@ -497,17 +547,20 @@ public sealed class MoverController : SharedMoverController
}
var localVel = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
// vector of max velocity we can be traveling with along current direction
var maxVelocity = ObtainMaxVel(localVel, shuttle, body);
// vector of max velocity we can be traveling with along wish-direction
var maxWishVelocity = ObtainMaxVel(totalForce, shuttle, body);
// if we're going faster than we can be, thrust to adjust our velocity to the max wish-direction velocity
if (localVel.LengthSquared() > maxVelocity.LengthSquared())
if (setMaxVel is { } speed && localVel.LengthSquared() != 0f && totalForce.LengthSquared() != 0f)
{
var velDelta = maxWishVelocity - localVel;
var maxForceLength = velDelta.Length() * body.Mass / frameTime;
var appliedLength = MathF.Min(totalForce.Length(), maxForceLength);
totalForce = velDelta.Length() == 0 ? Vector2.Zero : velDelta.Normalized() * appliedLength;
// vector of max velocity we can be traveling with along current direction
var maxVelocity = localVel.Normalized() * speed;
// vector of max velocity we can be traveling with along wish-direction
var maxWishVelocity = totalForce.Normalized() * speed;
// if we're going faster than we can be, thrust to adjust our velocity to the max wish-direction velocity
if (localVel.Length() / maxVelocity.Length() > 0.999f)
{
var velDelta = maxWishVelocity - localVel;
var maxForceLength = velDelta.Length() * body.Mass / frameTime;
var appliedLength = MathF.Min(totalForce.Length(), maxForceLength);
totalForce = velDelta.Length() == 0 ? Vector2.Zero : velDelta.Normalized() * appliedLength;
}
}
totalForce = shuttleNorthAngle.RotateVec(totalForce);
@@ -524,7 +577,7 @@ public sealed class MoverController : SharedMoverController
}
else
{
var torque = shuttle.AngularThrust * -angularInput;
var torque = GetTorque(shuttle) * -angularInput;
// Need to cap the velocity if 1 tick of input brings us over cap so we don't continuously
// edge onto the cap over and over.
@@ -9,9 +9,15 @@ public record struct ShuttleInput(Vector2 Strafe, float Rotation, float Brakes);
/// <summary>
/// Raised on pilots to get inputs given to a shuttle.
/// If GotInput is false, this piloted is removed from input sources.
/// Also queries for multipliers to acceleration and max speed.
/// </summary>
[ByRefEvent]
public record struct GetShuttleInputsEvent(float FrameTime, EntityUid ShuttleUid, ShuttleInput? Input = null, bool GotInput = false);
public record struct GetShuttleInputsEvent(float FrameTime, EntityUid ShuttleUid, ShuttleInput? Input = null, bool GotInput = false)
{
public float AngularMul = 1f;
public float AccelMul = 1f;
public float? SetMaxVelocity = null;
}
[ByRefEvent]
public record struct PilotedShuttleRelayedEvent<TEvent>(TEvent Args);
@@ -144,7 +144,8 @@ public sealed class ProjectileSystem : SharedProjectileSystem
continue;
var xform = Transform(uid);
var lastPosition = _transformSystem.GetWorldPosition(xform);
var lastMap = _transformSystem.GetMapCoordinates(xform);
var lastPosition = lastMap.Position;
var rayDirection = currentVelocity / velLen;
// Ensure rayDistance is not zero to prevent issues with IntersectRay if frametime or velocity is zero.
var rayDistance = velLen * frameTime;
@@ -243,9 +244,9 @@ public sealed class ProjectileSystem : SharedProjectileSystem
return false;
// teleport us so we hit it
// this is cursed but i don't think there's a better way to force a collision here
var hitXform = Transform(minHit.Uid.Value);
var hitPos = hitXform.Coordinates;
var hitMapCoord = lastMap.Offset(rayDirection * minHit.Distance);
var hitPos = _transformSystem.ToCoordinates(hitMapCoord);
// if we somehow hit something not directly parented to space or a grid
if (hitXform.Coordinates.EntityId != hitXform.GridUid && hitXform.GridUid != null)
hitPos = _transformSystem.WithEntityId(hitPos, hitXform.GridUid.Value);
@@ -17,10 +17,10 @@ namespace Content.Server.Shuttles.Components
public const float BrakeCoefficient = 1.5f;
/// <summary>
/// Maximum velocity assuming TWR is BaseMaxVelocityTWR.
/// Mono - velocity that 2x slowdown will be applied at. (4x at 2x this, 8x at 3x this, etc.)
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseMaxLinearVelocity = 75f; // Mono
public float BaseMaxLinearVelocity = 80f;
public const float MaxAngularVelocity = 4f;
@@ -76,35 +76,23 @@ namespace Content.Server.Shuttles.Components
public float DampingModifier;
// <Mono>
/// <summary>
/// Limit to max velocity set by a shuttle console.
/// </summary>
[DataField]
public float SetMaxVelocity = 140f;
/// <summary>
/// At what Thrust-Weight-Ratio should this ship have the base max velocity as its maximum velocity.
/// </summary>
[DataField]
public float BaseMaxVelocityTWR = 8f;
/// <summary>
/// How much should TWR affect max velocity.
/// </summary>
[DataField]
public float MaxVelocityScalingExponent = 0.25f; // 16x thrust = 2x max speed
/// <summary>
/// Don't allow max velocity to go beyond this value.
/// </summary>
[DataField]
public float UpperMaxVelocity = 140f; // we ball
/// <summary>
/// Thrust acceleration vector last update.
/// </summar>
[DataField]
public Vector2 LastThrust = Vector2.Zero;
/// <summary>
/// Multiplier to angular thrust. Set depending on pilot.
/// </summary
[ViewVariables]
public float AngularMultiplier = 1f;
/// <summary>
/// Multiplier to linear thrust. Set depending on pilot.
/// </summary
[ViewVariables]
public float AccelerationMultiplier = 1f;
// </Mono>
}
}
@@ -457,18 +457,20 @@ namespace Content.Server.Shuttles.Systems
return;
}
var shuttleUid = Transform(console.Value).GridUid;
if (!CanShuttleDock(shuttleUid))
if (!TryGetEntity(args.DockEntity, out var ourDock) ||
!TryGetEntity(args.TargetDockEntity, out var targetDock) ||
!TryComp(ourDock, out DockingComponent? ourDockComp) ||
!TryComp(targetDock, out DockingComponent? targetDockComp))
{
_popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
return;
}
if (!TryGetEntity(args.DockEntity, out var ourDock) ||
!TryGetEntity(args.TargetDockEntity, out var targetDock) ||
!TryComp(ourDock, out DockingComponent? ourDockComp) ||
!TryComp(targetDock, out DockingComponent? targetDockComp))
var shuttleUid = Transform(console.Value).GridUid;
var otherShuttleUid = Transform(targetDock.Value).GridUid; // Mono
// Mono - check both grids
if (!CanShuttleDock(shuttleUid) || !CanShuttleDock(otherShuttleUid))
{
_popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
return;
@@ -14,6 +14,7 @@ namespace Content.Server.Shuttles.Systems;
public sealed class RadarConsoleSystem : SharedRadarConsoleSystem
{
[Dependency] private readonly SharedTransformSystem _transform = default!; // Mono
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
@@ -39,14 +40,20 @@ public sealed class RadarConsoleSystem : SharedRadarConsoleSystem
protected override void UpdateState(EntityUid uid, RadarConsoleComponent component)
{
var xform = Transform(uid);
var onGrid = xform.ParentUid == xform.GridUid;
EntityCoordinates? coordinates = onGrid ? xform.Coordinates : null;
Angle? angle = onGrid ? xform.LocalRotation : null;
// Mono
var parentUid = xform.GridUid;
EntityCoordinates? coordinates = null;
Angle? angle = null;
if (component.FollowEntity)
{
coordinates = new EntityCoordinates(uid, Vector2.Zero);
angle = Angle.Zero; // Frontier: Angle.Zero<Angle.FromDegrees(180) // Mono - frontier strikes again
}
else if (parentUid is { } parent)
{
coordinates = _transform.WithEntityId(xform.Coordinates, parent);
angle = _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(parent);
}
if (_uiSystem.HasUi(uid, RadarConsoleUiKey.Key))
{
@@ -62,8 +69,6 @@ public sealed class RadarConsoleSystem : SharedRadarConsoleSystem
state = _console.GetNavState(uid, docks);
}
state.RotateWithEntity = !component.FollowEntity;
// Frontier: ghost radar restrictions
if (component.MaxIffRange != null)
state.MaxIffRange = component.MaxIffRange.Value;
@@ -202,7 +202,6 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
{
DockingInterfaceState? dockState = null;
UpdateState(uid, ref dockState);
_shuttle.NfSetPowered(uid, component, args.Powered); // Frontier
// Handle job slots when power changes
HandleJobSlotsOnPowerChange(uid, component, args.Powered);
@@ -557,9 +556,7 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
angle,
docks,
_shuttle.NfGetInertiaDampeningMode(entity), // Frontier: inertia dampening
portNames,
entity.Comp1.Pannable, // Mono
entity.Comp1.RelativePanning); // Mono
portNames);
}
/// <summary>
@@ -225,16 +225,19 @@ public sealed partial class ShuttleSystem
var inelasticVel = totalInertia / (ourMass + otherMass);
// Mono Edit - partial credit to https://github.com/Sector-Crescent/Hullrot/pull/692
//ShipShieldedComp is removed when shields are broken, only reduces energy delivered when shields are active. ShipShieldsSystem ln 256.
//ShipShieldedComp is removed when shields are broken, reduces both energies when shields are active. ShipShieldsSystem ln 256.
float shieldFactor = 1f;
if (TryComp<ShipShieldedComponent>(args.OurEntity, out var ShipShieldedComponent) //Our ship collision resistance
&& TryComp<ShipShieldEmitterComponent>(ShipShieldedComponent.Source, out var ShipShieldEmitterComponent)
)
toUsEnergy *= ShipShieldEmitterComponent.CollisionResistanceMultiplier;
shieldFactor *= ShipShieldEmitterComponent.CollisionResistanceMultiplier;
if (TryComp<ShipShieldedComponent>(args.OtherEntity, out var OtherShipShieldedComponent) //Other ship collision resistance
&& TryComp<ShipShieldEmitterComponent>(OtherShipShieldedComponent.Source, out var OtherShipShieldEmitterComponent)
)
toOtherEnergy *= OtherShipShieldEmitterComponent.CollisionResistanceMultiplier;
shieldFactor *= OtherShipShieldEmitterComponent.CollisionResistanceMultiplier;
toUsEnergy *= shieldFactor;
toOtherEnergy *= shieldFactor;
// Mono Edit end
DoGridImpact((args.OurEntity, ourGrid, ourXform, ourBody), args.OurFixture, inelasticVel, ourVelocity, ourTile, ourTiles, toUsEnergy);
@@ -1,5 +1,6 @@
using Content.Server.Inventory;
using Content.Server.Radio.Components;
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
using Content.Shared.Inventory;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
@@ -28,6 +29,23 @@ public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
if (TryComp(ent, out ActiveRadioComponent? activeRadio))
activeRadio.Channels = [.. radioChannels];
// Corvax-Next-AiRemoteControl-Start
if (TryComp(ent, out AiRemoteControllerComponent? aiRemoteComp))
{
if (TryComp(aiRemoteComp.AiHolder, out IntrinsicRadioTransmitterComponent? stationAiTransmitter) && transmitter != null)
{
aiRemoteComp.PreviouslyTransmitterChannels = [.. radioChannels];
transmitter.Channels = [.. stationAiTransmitter.Channels];
}
if (TryComp(aiRemoteComp.AiHolder, out ActiveRadioComponent? stationAiActiveRadio) && activeRadio != null)
{
aiRemoteComp.PreviouslyActiveRadioChannels = [.. radioChannels];
activeRadio.Channels = [.. stationAiActiveRadio.Channels];
}
}
// Corvax-Next-AiRemoteControl-End
// Borg transponder for the robotics console
if (TryComp(ent, out BorgTransponderComponent? transponder))
{
@@ -6,7 +6,9 @@ using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Emag.Systems;
using Robust.Shared.Player;
using Robust.Shared.Utility;
using Content.Shared._CorvaxNext.Silicons.Borgs.Components; // Corvax-Next-AiRemoteControl
namespace Content.Server.Silicons.Borgs;
@@ -47,7 +49,8 @@ public sealed partial class BorgSystem
charge,
chassis.ModuleCount,
hasBrain,
canDisable);
canDisable,
HasComp<AiRemoteControllerComponent>(uid)); // Corvax-Next-AiRemoteControl
var payload = new NetworkPayload()
{
@@ -5,6 +5,7 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Hands.Systems;
using Content.Server.PowerCell;
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
using Content.Shared.UserInterface;
using Content.Shared.Access;
using Content.Shared.Access.Components;
@@ -25,6 +26,8 @@ using Content.Shared.PowerCell.Components;
using Content.Shared.Roles;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Silicons.StationAi;
using Content.Shared.StationAi;
using Content.Shared.Throwing;
using Content.Shared.Whitelist;
using Content.Shared.Wires;
@@ -144,6 +147,21 @@ public sealed partial class BorgSystem : SharedBorgSystem
args.Handled = true;
UpdateUI(uid, component);
}
// Corvax-Next-AiRemoteControl-Start
if (component.BrainEntity == null && HasComp<AiRemoteBrainComponent>(used) &&
_whitelistSystem.IsWhitelistPassOrNull(component.BrainWhitelist, used))
{
EnsureComp<AiRemoteControllerComponent>(uid);
_container.Insert(used, component.BrainContainer);
_adminLog.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(args.User):player} installed ai remote brain {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
args.Handled = true;
BorgActivate(uid, component);
UpdateUI(uid, component);
}
// Corvax-Next-AiRemoteControl-End
}
/// <summary>
@@ -178,6 +196,15 @@ public sealed partial class BorgSystem : SharedBorgSystem
{
_mind.TransferTo(mindId, args.Entity, mind: mind);
}
// Corvax-Next-AiRemoteControl-Start
if (HasComp<AiRemoteBrainComponent>(args.Entity))
{
BorgDeactivate(uid, component);
RemComp<AiRemoteControllerComponent>(uid);
RemComp<StationAiVisionComponent>(uid);
}
// Corvax-Next-AiRemoteControl-End
}
private void OnMindAdded(EntityUid uid, BorgChassisComponent component, MindAddedMessage args)
@@ -4,6 +4,7 @@ using Content.Server.Chat.Managers;
using Content.Server.Radio.Components;
using Content.Server.Roles;
using Content.Server.Station.Systems;
using Content.Shared._CorvaxNext.Silicons.Borgs.Components; // Corvax-Next-AiRemoteControl
using Content.Shared.Administration;
using Content.Shared.Chat;
using Content.Shared.Emag.Systems;
@@ -13,6 +14,9 @@ using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Silicons.StationAi; // Corvax-Next-AiRemoteControl
using Content.Shared.Tag; // Corvax-Next-AiRemoteControl
using Content.Shared.Wires;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
@@ -33,6 +37,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly TagSystem _tagSystem = default!; // Corvax-Next-AiRemoteControl
[Dependency] private readonly IMapManager _map = default!; // Mono - Law update is grid-only now.
/// <inheritdoc/>
@@ -63,6 +68,12 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
if (!TryComp<ActorComponent>(uid, out var actor))
return;
// Corvax-Next-AiRemoteControl-Start
if (HasComp<AiRemoteControllerComponent>(uid)
|| _tagSystem.HasTag(uid, "StationAi")) // skip a law's notification for remotable and AI
return;
// Corvax-Next-AiRemoteControl-End
var msg = Loc.GetString("laws-notify");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.FromHex("#5ed7aa"));
@@ -155,6 +166,11 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
if (component.Lawset == null)
component.Lawset = GetLawset(component.Laws);
// Corvax-Next-AiRemoteControl-Start
if (HasComp<AiRemoteControllerComponent>(uid)) // You can't emag controllable entities
return;
// Corvax-Next-AiRemoteControl-End
// Show the silicon has been subverted.
component.Subverted = true;
@@ -281,7 +297,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
/// <summary>
/// Set the laws of a silicon entity while notifying the player.
/// </summary>
public void SetLaws(List<SiliconLaw> newLaws, EntityUid target, SoundSpecifier? cue = null)
public void SetLaws(List<SiliconLaw> newLaws, EntityUid target, SoundSpecifier? cue = null, bool silent = false) // AiRemoteControl - silent
{
if (!TryComp<SiliconLawProviderComponent>(target, out var component))
return;
@@ -290,7 +306,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
component.Lawset = new SiliconLawset();
component.Lawset.Laws = newLaws;
NotifyLawsChanged(target, cue);
if (!silent) // AiRemoteControl - silent
NotifyLawsChanged(target, cue);
}
protected override void OnUpdaterInsert(Entity<SiliconLawUpdaterComponent> ent, ref EntInsertedIntoContainerMessage args)
@@ -312,8 +329,16 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
continue;
SetLaws(lawset, update, provider.LawUploadSound);
// Mono edit end
// Corvax-Next-AiRemoteControl-Start
if (TryComp<StationAiHeldComponent>(update, out var stationAiHeldComp)
&& stationAiHeldComp.CurrentConnectedEntity != null
&& HasComp<SiliconLawProviderComponent>(stationAiHeldComp.CurrentConnectedEntity))
{
SetLaws(lawset, stationAiHeldComp.CurrentConnectedEntity.Value, provider.LawUploadSound);
}
// Corvax-Next-AiRemoteControl-End
}
// Mono edit end
}
}
@@ -346,11 +346,11 @@ namespace Content.Server.VendingMachines
if (TryComp<MarketModifierComponent>(component.Owner, out var modifier))
price *= modifier.Mod;
var totalPrice = (int) price;
var totalPrice = component.RequiresCash ? (int) price : 0;
// If any price has a vendor price, explicitly use its value - higher OR lower, over others.
var priceVend = _pricing.GetEstimatedVendPrice(proto);
if (priceVend > 0.0) // if vending price exists, overwrite it.
if (priceVend > 0.0 && component.RequiresCash) // if vending price exists, overwrite it.
totalPrice = (int) priceVend;
if (IsAuthorized(uid, sender, component))
@@ -91,20 +91,19 @@ public sealed partial class GunSystem : SharedGunSystem
}
}
var fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem);
var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem);
var fromMap = TransformSystem.ToMapCoordinates(fromCoordinates);
var toMap = TransformSystem.ToMapCoordinates(toCoordinates).Position;
var mapDirection = toMap - fromMap.Position;
var mapAngle = mapDirection.ToAngle();
// If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
var fromEnt = MapManager.TryFindGridAt(fromMap, out var gridUid, out var grid)
? fromCoordinates.WithEntityId(gridUid, EntityManager)
: new EntityCoordinates(MapManager.GetMapEntityId(fromMap.MapId), fromMap.Position);
? _transform.WithEntityId(fromCoordinates, gridUid)
: _transform.ToCoordinates(fromMap);
// get gun's local velocity
// get gun's fromEnt-relative velocity
var gunVelocity = Vector2.Zero;
if (_physQuery.TryComp(gunUid, out var gunBody))
gunVelocity = gunBody.LinearVelocity;
gunVelocity = Physics.GetMapLinearVelocity(gunUid) - Physics.GetMapLinearVelocity(fromEnt);
// I must be high because this was getting tripped even when true.
// DebugTools.Assert(direction != Vector2.Zero);
@@ -165,7 +164,8 @@ public sealed partial class GunSystem : SharedGunSystem
CreateAndFireProjectiles(ent.Value, newAmmo, offset);
break;
case HitscanAmmoComponent:
case HitscanAmmoComponent hitscanammo:
if (ent == null)
break;
@@ -179,10 +179,13 @@ public sealed partial class GunSystem : SharedGunSystem
};
RaiseLocalEvent(ent.Value, ref hitscanEv);
Del(ent);
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
// Mono start
Spawn(hitscanammo.CasingPrototype, fromEnt);
Del(ent);
// Mono end
break;
default:
throw new ArgumentOutOfRangeException();
}
@@ -235,7 +238,7 @@ public sealed partial class GunSystem : SharedGunSystem
}
// Do a throw
if (!HasComp<ProjectileComponent>(uid))
if (!TryComp(uid, out ProjectileComponent? projectileComp))
{
RemoveShootable(uid);
// TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack.
@@ -251,6 +254,7 @@ public sealed partial class GunSystem : SharedGunSystem
predicted.ClientEnt = user;
}
projectileComp.Damage *= gun.DamageModifier;
ShootProjectile(uid, mapDirection, gunVelocity, gunUid, user, gun.ProjectileSpeedModified, offset); // Mono - add offset
if (HasComp<FireControllableComponent>(gunUid))
{
@@ -0,0 +1,211 @@
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
// SPDX-FileCopyrightText: 2025 ImHoks <142083149+ImHoks@users.noreply.github.com>
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
using Content.Server.Radio.Components;
using Content.Server.Silicons.Laws;
using Content.Shared._CorvaxNext.Silicons.Borgs;
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
using Content.Shared.Actions;
using Content.Shared.Mind;
using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Silicons.StationAi;
using Content.Shared.StationAi;
using Content.Shared.Verbs;
using Robust.Shared.Map; // Mono
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using System.Threading.Tasks.Dataflow;
namespace Content.Server._CorvaxNext.Silicons.Borgs;
public sealed class AiRemoteControlSystem : SharedAiRemoteControlSystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SiliconLawSystem _lawSystem = default!;
[Dependency] private readonly SharedStationAiSystem _stationAiSystem = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IMapManager _map = default!; // Mono
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AiRemoteControllerComponent, ReturnMindIntoAiEvent>(OnReturnMindIntoAi);
SubscribeLocalEvent<AiRemoteControllerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AiRemoteControllerComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<AiRemoteControllerComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
SubscribeLocalEvent<StationAiHeldComponent, AiRemoteControllerComponent.RemoteDeviceActionMessage>(OnUiRemoteAction);
SubscribeLocalEvent<StationAiHeldComponent, ToggleRemoteDevicesScreenEvent>(OnToggleRemoteDevicesScreen);
}
private void OnMapInit(Entity<AiRemoteControllerComponent> entity, ref MapInitEvent args)
{
var visionComp = EnsureComp<StationAiVisionComponent>(entity.Owner);
EntityUid? actionEnt = null;
_actions.AddAction(entity.Owner, ref actionEnt, entity.Comp.BackToAiAction);
if (actionEnt != null)
entity.Comp.BackToAiActionEntity = actionEnt.Value;
}
private void OnShutdown(Entity<AiRemoteControllerComponent> entity, ref ComponentShutdown args)
{
_actions.RemoveAction(entity.Owner, entity.Comp.BackToAiActionEntity);
var backArgs = new ReturnMindIntoAiEvent();
backArgs.Performer = entity;
if (TryComp(entity, out IntrinsicRadioTransmitterComponent? transmitter)
&& entity.Comp.PreviouslyTransmitterChannels != null)
transmitter.Channels = [.. entity.Comp.PreviouslyTransmitterChannels];
if (TryComp(entity, out ActiveRadioComponent? activeRadio)
&& entity.Comp.PreviouslyActiveRadioChannels != null)
activeRadio.Channels = [.. entity.Comp.PreviouslyActiveRadioChannels];
ReturnMindIntoAi(entity);
}
private void OnGetVerbs(Entity<AiRemoteControllerComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
{
var user = args.User;
if (!TryComp<StationAiHeldComponent>(user, out var stationAiHeldComp))
return;
var verb = new AlternativeVerb
{
Text = Loc.GetString("ai-remote-control"),
Act = () => AiTakeControl(user, entity)
};
args.Verbs.Add(verb);
}
private void OnReturnMindIntoAi(Entity<AiRemoteControllerComponent> entity, ref ReturnMindIntoAiEvent args) =>
ReturnMindIntoAi(entity);
public void AiTakeControl(EntityUid ai, EntityUid entity)
{
if (!_mind.TryGetMind(ai, out var mindId, out var mind))
return;
if (_mind.TryGetMind(entity, out _, out _))
return;
if (!TryComp<StationAiHeldComponent>(ai, out var stationAiHeldComp))
return;
if (!TryComp<AiRemoteControllerComponent>(entity, out var aiRemoteComp))
return;
if (!_map.TryFindGridAt(Transform(ai).MapPosition, out var grid, out var _) || Transform(entity).GridUid != grid)
return; // Mono no controlling borgs outside the ai's grid.
if (TryComp(entity, out IntrinsicRadioTransmitterComponent? transmitter))
{
aiRemoteComp.PreviouslyTransmitterChannels = [.. transmitter.Channels];
if (TryComp(ai, out IntrinsicRadioTransmitterComponent? stationAiTransmitter))
transmitter.Channels = [.. stationAiTransmitter.Channels];
}
if (TryComp(entity, out ActiveRadioComponent? activeRadio))
{
aiRemoteComp.PreviouslyActiveRadioChannels = [.. activeRadio.Channels];
if (TryComp(ai, out ActiveRadioComponent? stationAiActiveRadio))
activeRadio.Channels = [.. stationAiActiveRadio.Channels];
}
_mind.ControlMob(ai, entity);
aiRemoteComp.AiHolder = ai;
aiRemoteComp.LinkedMind = mindId;
stationAiHeldComp.CurrentConnectedEntity = entity;
if (!_stationAiSystem.TryGetCore(ai, out var stationAiCore))
return;
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore, false);
RewriteLaws(ai, entity);
}
private void OnToggleRemoteDevicesScreen(EntityUid uid, StationAiHeldComponent component, ToggleRemoteDevicesScreenEvent args)
{
if (args.Handled || !TryComp<ActorComponent>(uid, out var actor))
return;
args.Handled = true;
_userInterface.TryToggleUi(uid, RemoteDeviceUiKey.Key, actor.PlayerSession);
var aiGrid = Transform(uid).GridUid; // Mono
var query = EntityManager.EntityQueryEnumerator<AiRemoteControllerComponent>();
var remoteDevices = new List<RemoteDevicesData>();
while (query.MoveNext(out var queryUid, out var comp))
{
if (Transform(queryUid).GridUid != aiGrid)
continue;
var data = new RemoteDevicesData
{
NetEntityUid = GetNetEntity(queryUid),
DisplayName = Comp<MetaDataComponent>(queryUid).EntityName
};
remoteDevices.Add(data);
}
var state = new RemoteDevicesBuiState(remoteDevices);
_userInterface.SetUiState(uid, RemoteDeviceUiKey.Key, state);
}
private void OnUiRemoteAction(EntityUid uid, StationAiHeldComponent component, AiRemoteControllerComponent.RemoteDeviceActionMessage msg)
{
if (msg.RemoteAction == null)
return;
var target = GetEntity(msg.RemoteAction?.Target);
if (!HasComp<AiRemoteControllerComponent>(target))
return;
switch (msg.RemoteAction?.ActionType)
{
case RemoteDeviceActionEvent.RemoteDeviceActionType.MoveToDevice:
if (!_stationAiSystem.TryGetCore(uid, out var stationAiCore)
|| stationAiCore.Comp?.RemoteEntity == null)
return;
_xformSystem.SetCoordinates(stationAiCore.Comp.RemoteEntity.Value, Transform(target.Value).Coordinates);
break;
case RemoteDeviceActionEvent.RemoteDeviceActionType.TakeControl:
AiTakeControl(uid, target.Value);
break;
}
}
private void RewriteLaws(EntityUid from, EntityUid to)
{
if (!TryComp<SiliconLawProviderComponent>(from, out var fromLawsComp))
return;
if (!TryComp<SiliconLawProviderComponent>(to, out var toLawsComp))
return;
if (fromLawsComp.Lawset == null)
return;
var fromLaws = _lawSystem.GetLaws(from);
_lawSystem.SetLaws(fromLaws.Laws, to, silent: true);
}
}
@@ -29,6 +29,7 @@ public sealed class JobWhitelistsCommand : LocalizedCommands
{
shell.WriteLine(Loc.GetString("cmd-ban-invalid-arguments"));
shell.WriteLine(Help);
return;
}
var located = await _locator.LookupIdByNameOrIdAsync(args[0]);
@@ -1,6 +1,8 @@
using Content.Server.Atmos.Rotting;
using Content.Server.Body.Components;
using Content.Server.DoAfter;
using Content.Server.EUI;
using Content.Server.Ghost;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Atmos.Rotting;
@@ -9,9 +11,11 @@ using Content.Shared.DoAfter;
using Content.Shared.Inventory;
using Content.Shared.Medical.CPR;
using Content.Shared.Mind;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Traits.Assorted;
using Content.Shared.Verbs;
using Robust.Server.Audio;
using Robust.Shared.Audio;
@@ -32,6 +36,8 @@ public sealed class CPRSystem : EntitySystem
[Dependency] private readonly RottingSystem _rottingSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!; // Mono
[Dependency] private readonly EuiManager _euiManager = default!; // Mono
public override void Initialize()
{
@@ -103,26 +109,41 @@ public sealed class CPRSystem : EntitySystem
private void OnCPRDoAfter(Entity<CPRTrainingComponent> performer, ref CPRDoAfterEvent args)
{
if (args.Cancelled || args.Handled || !args.Target.HasValue)
if (args.Cancelled || args.Handled || args.Target is not { } target) // Mono: replaces args.Target with target, makes target not nullable
{
performer.Comp.CPRPlayingStream = _audio.Stop(performer.Comp.CPRPlayingStream);
return;
}
if (!performer.Comp.CPRHealing.Empty)
_damageable.TryChangeDamage(args.Target, performer.Comp.CPRHealing, true, origin: performer);
_damageable.TryChangeDamage(target, performer.Comp.CPRHealing, true, origin: performer);
if (performer.Comp.RotReductionMultiplier > 0)
_rottingSystem.ReduceAccumulator(
(EntityUid)args.Target, performer.Comp.DoAfterDuration * performer.Comp.RotReductionMultiplier);
if (_robustRandom.Prob(performer.Comp.ResuscitationChance)
&& _mobThreshold.TryGetThresholdForState((EntityUid)args.Target, MobState.Dead, out var threshold)
&& TryComp<DamageableComponent>(args.Target, out var damageableComponent)
&& TryComp<MobStateComponent>(args.Target, out var state)
&& _mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var threshold)
&& TryComp<DamageableComponent>(target, out var damageableComponent)
&& !HasComp<UnrevivableComponent>(target) // Mono: Checks unreviveability
&& TryComp<MobStateComponent>(target, out var state)
&& damageableComponent.TotalDamage < threshold)
{
_mobStateSystem.ChangeMobState(args.Target.Value, MobState.Critical, state, performer);
// Mono Edit: Informs the ghost they've been revived.
if (_mind.TryGetMind(target, out _, out var mind) &&
mind.Session is { } playerSession)
{
// notify them they're being revived.
if (mind.CurrentEntity != target)
{
_euiManager.OpenEui(new ReturnToBodyEui(mind, _mind), playerSession);
}
}
}
var isAlive = _mobStateSystem.IsAlive(args.Target.Value);
args.Repeat = !isAlive;
if (isAlive)
@@ -0,0 +1,51 @@
using System.Linq;
using Content.Server._Mono.Grid;
using Content.Server.Administration;
using Content.Shared._Mono.Grid;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.Server._Mono.Administration.Commands;
/// <summary>
/// Command that allows you to apply grid modifiers to existing grids
/// </summary>
[AdminCommand(AdminFlags.Admin)]
public sealed class AddGridModifier : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entManager = default!;
public string Command => "addgridmodifier";
public string Description => "Applies grid modification to chosen grid.";
public string Help => $"Usage: {Command} <gridUid> <modification...>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 2)
shell.WriteLine($"Not enough arguments.\n{Help}");
if (!NetEntity.TryParse(args[0], out var uidNet) || !_entManager.TryGetEntity(uidNet, out var uid))
{
shell.WriteLine($"Invalid entity id.");
return;
}
if (!_entManager.TryGetComponent(uid, out MapGridComponent? map))
{
shell.WriteLine($"Entity is not a grid.");
return;
}
List<ProtoId<GridModificationPrototype>> modifiers = [];
var gridMod = _entManager.System<GridModifierSystem>();
foreach (var mod in args.Skip(1))
{
modifiers.Add(mod);
}
gridMod.ModifyGrid(uid.Value, modifiers);
}
}
@@ -0,0 +1,208 @@
using System.Linq;
using Content.Server.Administration.Managers;
using Content.Shared._Mono.Company;
using Content.Shared.Administration;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.TypeParsers;
using Content.Server.Administration;
namespace Content.Server._Mono.Company;
[ToolshedCommand(Name = "company"), AnyCommand]
public sealed class CompanyCommand : ToolshedCommand
{
[Dependency] private readonly CompanyManager _company = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly IAdminManager _admin = default!;
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
[CommandImplementation("addmember")]
public async void Add(
[CommandInvocationContext] IInvocationContext ctx,
[CommandArgument] string username,
[CommandArgument] ProtoId<CompanyPrototype> company)
{
if (!_prototypes.TryIndex(company, out var companyPrototype))
{
ctx.ReportError(new NotAValidPrototype(company, nameof(CompanyPrototype)));
return;
}
if (ctx.Session == null
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist)
&& !_company.IsOwner(ctx.Session, company)) // owner can add members
{
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
return;
}
var lookup = await _playerLocator.LookupIdByNameAsync(username);
if (lookup == null)
{
ctx.WriteLine(Loc.GetString("cmd-company-player-not-found", ("player", username)));
return;
}
if (_company.IsMember(lookup.UserId, company))
{
ctx.WriteLine(Loc.GetString("cmd-company-memberadd-already-whitelisted",
("player", username),
("companyId", company.Id),
("companyName", companyPrototype.Name)));
return;
}
_company.AddMember(lookup.UserId, company);
ctx.WriteLine(Loc.GetString("cmd-company-memberadd-added",
("player", username),
("companyId", company.Id),
("companyName", companyPrototype.Name)));
}
[CommandImplementation("playercompanies")]
public async void GetPlayerWhitelist(
[CommandInvocationContext] IInvocationContext ctx,
[CommandArgument] string username)
{
if (ctx.Session == null
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist)
&& ctx.Session.Name != username) // allow looking at own companies
{
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
return;
}
var lookup = await _playerLocator.LookupIdByNameAsync(username);
if (lookup == null)
{
ctx.WriteLine(Loc.GetString("cmd-company-player-not-found", ("player", username)));
return;
}
var whitelists = _company.GetPlayerCompanies(lookup.UserId);
ctx.WriteLine(Loc.GetString("cmd-company-playercompanies-whitelisted-for",
("player", username),
("companies", string.Join(", ", whitelists))));
}
[CommandImplementation("members")]
public async void GetCompanyWhitelist(
[CommandInvocationContext] IInvocationContext ctx,
[CommandArgument] ProtoId<CompanyPrototype> company)
{
if (!_prototypes.TryIndex(company, out var companyPrototype))
{
ctx.ReportError(new NotAValidPrototype(company, nameof(CompanyPrototype)));
return;
}
if (ctx.Session == null
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist)
&& !_company.IsMember(ctx.Session.UserId, company)) // members can see other members
{
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
return;
}
var whitelisted = _company.GetCompanyMembers(company);
var members = whitelisted.Where(w => !w.Owner).Select(m => m.LastSeenUserName);
var owners = whitelisted.Where(w => w.Owner).Select(m => m.LastSeenUserName);
ctx.WriteLine(Loc.GetString("cmd-company-members-whitelisted-for",
("company", company),
("players", string.Join(", ", members)),
("owners", string.Join(", ", owners))));
}
[CommandImplementation("setowner")]
public async void SetOwner(
[CommandInvocationContext] IInvocationContext ctx,
[CommandArgument] ProtoId<CompanyPrototype> company,
[CommandArgument] string username,
[CommandArgument] bool owner
)
{
if (!_prototypes.TryIndex(company, out var companyPrototype))
{
ctx.ReportError(new NotAValidPrototype(company, nameof(CompanyPrototype)));
return;
}
// only admins can set owner
if (ctx.Session == null
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist))
{
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
return;
}
// check if we're trying to owner a non-member
var lookup = await _playerLocator.LookupIdByNameAsync(username);
if (lookup == null)
{
ctx.WriteLine(Loc.GetString("cmd-company-player-not-found", ("player", username)));
return;
}
if (!_company.IsMember(lookup.UserId, company))
{
ctx.WriteLine(Loc.GetString("cmd-company-was-not-whitelisted",
("player", username),
("companyId", company.Id),
("companyName", companyPrototype.Name)));
return;
}
_company.SetOwner(company, lookup.UserId, owner);
ctx.WriteLine(Loc.GetString("cmd-company-setowner-success", ("player", username), ("status", owner)));
}
[CommandImplementation("removemember")]
public async void Remove(
[CommandInvocationContext] IInvocationContext ctx,
[CommandArgument] string username,
[CommandArgument] ProtoId<CompanyPrototype> company)
{
if (!_prototypes.TryIndex(company, out var companyPrototype))
{
ctx.ReportError(new NotAValidPrototype(company, nameof(CompanyPrototype)));
return;
}
if (ctx.Session == null
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist)
&& !_company.IsOwner(ctx.Session, company)) // owner can remove members
{
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
return;
}
var lookup = await _playerLocator.LookupIdByNameAsync(username);
if (lookup == null)
{
ctx.WriteLine(Loc.GetString("cmd-company-player-not-found", ("player", username)));
return;
}
if (!_company.IsMember(lookup.UserId, company))
{
ctx.WriteLine(Loc.GetString("cmd-company-was-not-whitelisted",
("player", username),
("companyId", company.Id),
("companyName", companyPrototype.Name)));
return;
}
await _company.RemoveMember(lookup.UserId, company);
ctx.WriteLine(Loc.GetString("cmd-company-memberremove-removed",
("player", username),
("companyId", company.Id),
("companyName", companyPrototype.Name)));
}
}
@@ -0,0 +1,177 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Shared._Mono.CCVar;
using Content.Shared._Mono.Company;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server._Mono.Company;
public sealed class CompanyManager
{
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ILogManager _log = default!;
private ISawmill _sawmill = default!;
private readonly Dictionary<ProtoId<CompanyPrototype>, HashSet<CompanyMemberRecord>> _companies = new();
public void Initialize()
{
_sawmill = _log.GetSawmill("company.manager");
_net.RegisterNetMessage<MsgCompanyWhitelist>();
_net.Connected += OnConnected;
_ = LoadCompaniesData();
}
private async Task LoadCompaniesData()
{
var sw = new Stopwatch();
sw.Start();
var members = await _db.GetAllCompanyMembers();
foreach (var proto in _proto.EnumeratePrototypes<CompanyPrototype>())
{
_companies.Add(proto.ID, new());
_companies[proto.ID].UnionWith(members.Where(m => m.Company == proto.ID).ToHashSet());
}
_sawmill.Info($"All company members data loaded in {sw.Elapsed.TotalSeconds:.2}s");
}
private void OnConnected(object? sender, NetChannelArgs e)
{
SendCompanyWhitelist(e.Channel);
}
private async Task<HashSet<CompanyMemberRecord>> QueryCompanyMembers(ProtoId<CompanyPrototype> company)
{
return (await _db.GetCompanyMembers(company)).ToHashSet();
}
public HashSet<CompanyMemberRecord> GetCompanyMembers(ProtoId<CompanyPrototype> company)
{
if (!_companies.TryGetValue(company, out var members))
return new();
return members;
}
public HashSet<CompanyMemberRecord> GetAllCompanyMembers()
{
return _companies.Values.SelectMany(v => v).ToHashSet();
}
public CompanyMemberRecord? GetCompanyMember(ProtoId<CompanyPrototype> company, NetUserId player)
{
if (!_companies.TryGetValue(company, out var members))
return null;
return members.FirstOrNull(m => m.PlayerUserId == player);
}
public async void AddMember(NetUserId player, ProtoId<CompanyPrototype> company)
{
await _db.AddCompanyMember(player, company);
var member = await _db.GetCompanyMember(company, player);
if (member != null)
_companies[company].Add(member.Value);
if (_player.TryGetSessionById(player, out var session))
SendCompanyWhitelist(session.Channel);
}
public bool IsAllowed(ICommonSession session, ProtoId<CompanyPrototype> company)
{
if (!_config.GetCVar(MonoCVars.CompanyWhitelist))
return true;
if (!_proto.TryIndex(company, out var companyPrototype) ||
!companyPrototype.Whitelisted)
{
return true;
}
return IsMember(session.UserId, company);
}
public bool IsOwner(ICommonSession session, ProtoId<CompanyPrototype> company)
{
if (!_companies.TryGetValue(company, out var members))
return false;
var member = members.FirstOrNull(m => m.PlayerUserId == session.UserId);
return member != null && member.Value.Owner;
}
public bool SetOwner(ProtoId<CompanyPrototype> company, NetUserId userId, bool owner)
{
var cached = GetCompanyMember(company, userId);
if (cached is not { } member)
return false;
if (owner == member.Owner)
return true;
_db.SetCompanyOwner(company, userId, owner);
_companies[company].RemoveWhere(w => w.PlayerUserId == userId);
member.Owner = owner; // company member is struct so we got a copy here
_companies[company].Add(member);
return true;
}
public bool IsMember(NetUserId player, ProtoId<CompanyPrototype> company)
{
var member = GetCompanyMember(company, player);
return member != null;
}
public async Task RemoveMember(NetUserId player, ProtoId<CompanyPrototype> company)
{
_companies[company].RemoveWhere(w => w.PlayerUserId == player);
await _db.RemoveCompanyMember(player, company);
if (_player.TryGetSessionById(player, out var session))
SendCompanyWhitelist(session.Channel);
}
public HashSet<ProtoId<CompanyPrototype>> GetPlayerCompanies(NetUserId player)
{
var res = new HashSet<ProtoId<CompanyPrototype>>();
foreach (var (key, members) in _companies)
{
var member = members.FirstOrNull(m => m.PlayerUserId == player);
if (member != null)
res.Add(key);
}
return res;
}
public void SendCompanyWhitelist(INetChannel player)
{
var msg = new MsgCompanyWhitelist
{
Whitelist = GetPlayerCompanies(player.UserId)
};
_net.ServerSendMessage(msg, player);
}
}
@@ -0,0 +1,12 @@
using Content.Shared._Mono.Company;
using Robust.Shared.Prototypes;
namespace Content.Server._Mono.Company;
public struct CompanyMemberRecord
{
public Guid PlayerUserId;
public string LastSeenUserName;
public bool Owner;
public ProtoId<CompanyPrototype> Company;
}
@@ -5,7 +5,6 @@ using Content.Shared.GameTicking;
using Content.Shared.Inventory;
using Content.Shared.PDA;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
@@ -19,10 +18,9 @@ namespace Content.Server._Mono.Company;
public sealed class CompanySystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedJobSystem _job = default!;
[Dependency] private readonly SharedIdCardSystem _idCardSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly CompanyManager _manager = default!;
// Dictionary to store original company preferences for players
private readonly Dictionary<string, string> _playerOriginalCompanies = new();
@@ -74,10 +72,9 @@ public sealed class CompanySystem : EntitySystem
// or if their preference is "None"
if (string.IsNullOrEmpty(profileCompany))
{
// Check for company login whitelists
foreach (var companyProto in _prototypeManager.EnumeratePrototypes<CompanyPrototype>())
{
if (companyProto.Logins.Contains(args.Player.Name))
if (_manager.IsAllowed(args.Player, companyProto))
{
companyComp.CompanyName = companyProto.ID;
loginFound = true;
@@ -9,7 +9,6 @@ using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using System;
namespace Content.Server._Mono.Detection;
@@ -21,18 +20,24 @@ public sealed class ThermalSignatureSystem : EntitySystem
[Dependency] private readonly SharedPowerReceiverSystem _power = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private float _updateInterval = 0.5f;
private float _updateAccumulator = 0f;
private EntityQuery<MapGridComponent> _gridQuery;
private const float UpdateIntervalSeconds = 1f;
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(UpdateIntervalSeconds);
private TimeSpan _nextUpdateTime;
private const float HeatChangeThreshold = 1.02f;
private List<Entity<ThermalSignatureComponent>> _gridQueue = new();
private EntityQuery<ThermalSignatureComponent> _sigQuery;
private EntityQuery<GunComponent> _gunQuery;
private Dictionary<EntityUid, ThermalSignatureComponent> _gridCompMap = new();
private EntityQuery<MapGridComponent> _mapGridQuery;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialized);
// some of this could also be handled in shared but there's no point since PVS is a thing
SubscribeLocalEvent<MachineThermalSignatureComponent, GetThermalSignatureEvent>(OnMachineGetSignature);
SubscribeLocalEvent<PassiveThermalSignatureComponent, GetThermalSignatureEvent>(OnPassiveGetSignature);
@@ -42,9 +47,14 @@ public sealed class ThermalSignatureSystem : EntitySystem
SubscribeLocalEvent<ThrusterComponent, GetThermalSignatureEvent>(OnThrusterGetSignature);
SubscribeLocalEvent<FTLDriveComponent, GetThermalSignatureEvent>(OnFTLGetSignature);
_gridQuery = GetEntityQuery<MapGridComponent>();
_sigQuery = GetEntityQuery<ThermalSignatureComponent>();
_gunQuery = GetEntityQuery<GunComponent>();
_mapGridQuery = GetEntityQuery<MapGridComponent>();
}
private void OnGridInitialized(GridInitializeEvent args)
{
EnsureComp<ThermalSignatureComponent>(args.EntityUid);
}
private void OnGunShot(Entity<ThermalSignatureComponent> ent, ref GunShotEvent args)
@@ -87,48 +97,51 @@ public sealed class ThermalSignatureSystem : EntitySystem
public override void Update(float frameTime)
{
_updateAccumulator += frameTime;
if (_updateAccumulator < _updateInterval)
if (_timing.CurTime < _nextUpdateTime)
return;
_updateAccumulator -= _updateInterval;
var interval = _updateInterval;
_nextUpdateTime = _timing.CurTime + UpdateInterval;
_gridCompMap.Clear();
var gridQuery = EntityQueryEnumerator<MapGridComponent>();
while (gridQuery.MoveNext(out var uid, out _))
var gridQuery = EntityQueryEnumerator<MapGridComponent, ThermalSignatureComponent>();
while (gridQuery.MoveNext(out _, out _, out var gridSigComp))
{
if (!_sigQuery.TryComp(uid, out var sigComp))
sigComp = EnsureComp<ThermalSignatureComponent>(uid);
sigComp.TotalHeat = 0f;
_gridCompMap.Add(uid, sigComp);
gridSigComp.TotalHeat = 0f;
}
_gridQueue.Clear();
var query = EntityQueryEnumerator<ThermalSignatureComponent>();
while (query.MoveNext(out var uid, out var sigComp))
{
var ev = new GetThermalSignatureEvent(interval);
var ev = new GetThermalSignatureEvent();
RaiseLocalEvent(uid, ref ev);
sigComp.StoredHeat += ev.Signature * interval;
sigComp.StoredHeat *= MathF.Pow(sigComp.HeatDissipation, interval);
if (_gridCompMap.ContainsKey(uid))
sigComp.StoredHeat += ev.Signature * UpdateIntervalSeconds;
sigComp.StoredHeat *= MathF.Pow(sigComp.HeatDissipation, UpdateIntervalSeconds);
if (_mapGridQuery.HasComp(uid))
{
sigComp.TotalHeat += sigComp.StoredHeat;
_gridQueue.Add((uid, sigComp));
continue;
}
else
{
var xform = Transform(uid);
sigComp.TotalHeat = sigComp.StoredHeat;
if (xform.GridUid != null && _gridCompMap.TryGetValue(xform.GridUid.Value, out var gridSig))
if (xform.GridUid != null && _sigQuery.TryGetComponent(xform.GridUid.Value, out var gridSig))
gridSig.TotalHeat += sigComp.StoredHeat;
}
}
foreach (var (uid, sigComp) in _gridCompMap)
{
Dirty(uid, sigComp); // sync to client
foreach (var ent in _gridQueue) {
ent.Comp.TotalHeat += ent.Comp.StoredHeat;
// don't sync it if it didn't change heat much since last time, we don't need to sync 500 cold asteroids every system update
if (ent.Comp.TotalHeat <= ent.Comp.LastUpdateHeat * HeatChangeThreshold
&& ent.Comp.TotalHeat >= ent.Comp.LastUpdateHeat / HeatChangeThreshold)
continue;
ent.Comp.LastUpdateHeat = ent.Comp.TotalHeat;
Dirty(ent);
}
}
}
@@ -0,0 +1,11 @@
using Content.Shared._Mono.Grid;
using Robust.Shared.Prototypes;
namespace Content.Server._Mono.Grid;
[RegisterComponent]
public sealed partial class GridModifierComponent : Component
{
[DataField]
public List<ProtoId<GridModificationPrototype>> Modifications = [];
}
@@ -0,0 +1,56 @@
using Content.Shared._Mono.Grid;
using Content.Shared._Mono.ShipRepair;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.Server._Mono.Grid;
/// <summary>
/// This handles grid modification on initialization.
/// </summary>
public sealed class GridModifierSystem : SharedGridModifierSystem
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly SharedShipRepairSystem _repair = default!;
private List<EntityUid> _snapQueue = [];
public override void Initialize()
{
SubscribeLocalEvent<GridModifierComponent, MapInitEvent>(OnInit);
}
public override void Update(float frameTime)
{
foreach (var uid in _snapQueue)
{
_repair.GenerateRepairData(uid);
}
_snapQueue.Clear();
}
private void OnInit(EntityUid uid, GridModifierComponent component, MapInitEvent args)
{
ModifyGrid(uid, component.Modifications);
}
public void ModifyGrid(EntityUid uid, List<ProtoId<GridModificationPrototype>> modifiers)
{
if (!HasComp<MapGridComponent>(uid))
return;
foreach (var modProto in modifiers)
{
if (!_protoMan.TryIndex(modProto, out var mod))
continue;
foreach (var modifier in mod.Modifiers)
{
modifier.Modify(uid, EntityManager, _factory);
}
}
_snapQueue.Add(uid);
}
}
@@ -57,12 +57,6 @@ public sealed partial class ShipFireGunsOperator : HTNOperator, IHtnConditionalS
[DataField]
public bool RequirePowered = true;
/// <summary>
/// Stop targeting if beyond this range.
/// </summary>
[DataField]
public float MaxTargetingRange = 2000f;
private const string TargetingCancelToken = "ShipTargetingCancelToken";
public override void Initialize(IEntitySystemManager sysManager)
@@ -122,7 +116,7 @@ public sealed partial class ShipFireGunsOperator : HTNOperator, IHtnConditionalS
if (comp == null)
return HTNOperatorStatus.Finished;
if (target.EntityId == EntityUid.Invalid || !xform.Coordinates.TryDistance(_entManager, target, out var distance) || distance > MaxTargetingRange)
if (target.EntityId == EntityUid.Invalid)
return HTNOperatorStatus.Finished;
if (ShutdownState == HTNPlanState.PlanFinished)
@@ -10,5 +10,12 @@ public sealed partial class ShipNpcTargetComponent : Component
public bool NeedPower = false;
[DataField]
public bool NeedGrid = true;
public NpcTargetGridMode NeedGrid = NpcTargetGridMode.OnGrid;
}
public enum NpcTargetGridMode
{
OnGrid,
Either,
NoGrid
}
@@ -52,7 +52,7 @@ public sealed partial class ShipSteererComponent : Component
/// Try to evade collisions this far into the future even if stationary.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseEvasionTime = 10f;
public float BaseEvasionTime = 4f;
/// <summary>
/// How unwilling we are to use brake to adjust our velocity. Higher means less willing.
@@ -88,7 +88,7 @@ public sealed partial class ShipSteererComponent : Component
/// How much to enlarge grid search bounds for collision evasion.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float GridSearchBuffer = 192f;
public float GridSearchBuffer = 312f;
/// <summary>
/// How much to enlarge grid search forward distance for collision evasion.
@@ -1,3 +1,4 @@
using Content.Server._Mono.Projectiles.TargetSeeking;
using Content.Server.Physics.Controllers;
using Content.Server.Shuttles.Components;
using Content.Shared._Mono;
@@ -19,6 +20,7 @@ public sealed partial class ShipSteeringSystem : EntitySystem
[Dependency] private readonly MoverController _mover = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TargetSeekingSystem _seeking = default!;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<ProjectileGridPhaseComponent> _phaseQuery;
@@ -227,11 +229,12 @@ public sealed partial class ShipSteeringSystem : EntitySystem
var avoidanceVec = CalculateAvoidanceVector(ctx, config, brakeCtx, navVec);
// use avoidance vector if available or proceed with thrust as normal
var wasNav = navVec;
var wishInputVec = avoidanceVec ?? navVec;
var rotWish = wishInputVec;
if (avoidanceVec != null && config.AvoidanceNoRotate)
rotWish = CalculateNavigationVector(ctx, brakeCtx);
rotWish = wasNav;
// process angular input
var rotControl = CalculateRotationControl(ctx, config, rotWish, ref rotationCompensation);
@@ -251,7 +254,7 @@ public sealed partial class ShipSteeringSystem : EntitySystem
{
// check our brake thrust
var brakeVec = GetGoodThrustVector((-ctx.ShipNorthAngle).RotateVec(-ctx.ShipBody.LinearVelocity), ctx.Shuttle);
var brakeAccelVec = _mover.GetDirectionAccel(brakeVec, ctx.Shuttle, ctx.ShipBody);
var brakeAccelVec = _mover.GetDirectionAccel(brakeVec, ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
var brakeAccel = brakeAccelVec.Length();
var linVelLenSq = ctx.ShipBody.LinearVelocity.LengthSquared();
@@ -344,35 +347,30 @@ public sealed partial class ShipSteeringSystem : EntitySystem
var simTime = brake.BrakeAccel == 0f ? 10f : 2f * ctx.ShipBody.LinearVelocity.Length() / brake.BrakeAccel;
simTime += config.BaseEvasionTime;
var forwardAccelVec = _mover.GetDirectionAccel(new Vector2(0f, 1f), ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
var forwardAccelDir = NormalizedOrZero(forwardAccelVec);
var forwardAccel = forwardAccelVec.Length();
_sectors.Clear();
for (var i = 0; i < config.EvasionSectorCount; i++)
{
var angle = Angle.FromDegrees(360f * i / (float)config.EvasionSectorCount);
var dir = angle.ToVec();
var rotated = (-ctx.ShipNorthAngle).RotateVec(dir);
var dirAccel = _mover.GetDirectionAccel(rotated, ctx.Shuttle, ctx.ShipBody).Length();
// var dirMax = _mover.ObtainMaxVel(rotated, ctx.Shuttle, ctx.ShipBody).Length();
// if it's zero use a very rough approximation using our forward thrust
if (dirAccel == 0f)
{
var upVec = new Vector2(0f, 1f);
var penalty = 0.5f * (Vector2.Dot(upVec, rotated) + 1f);
dirAccel = _mover.GetDirectionAccel(upVec, ctx.Shuttle, ctx.ShipBody).Length() * penalty;
// dirMax = _mover.ObtainMaxVel(upVec, ctx.Shuttle, ctx.ShipBody).Length();
var dirAccel = _mover.GetWorldDirectionAccel(dir, ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
if (dirAccel.LengthSquared() == 0f) {
dirAccel = dir * forwardAccel * (Vector2.Dot(dir, forwardAccelDir) + 1) * 0.5f;
}
for (var depth = 1; depth <= config.EvasionSectorDepth; depth++)
{
if (i % depth == 0)
_sectors.Add(new(dir, dirAccel / depth, 1f / depth));
_sectors.Add(new(dirAccel / depth, 1f / depth));
}
}
// set scale to -1 to mark it as the wish-sector
var wishRotated = (-ctx.ShipNorthAngle).RotateVec(wishDir);
var wishDirThrust = _mover.GetDirectionAccel(wishRotated, ctx.Shuttle, ctx.ShipBody).Length();
// var wishDirMaxVel = _mover.ObtainMaxVel(wishRotated, ctx.Shuttle, ctx.ShipBody).Length();
_sectors.Add(new(wishDir, wishDirThrust, -1f));
var wishDirThrust = _mover.GetWorldDirectionAccel(wishDir, ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
_sectors.Add(new(wishDirThrust, -1f));
foreach (var obstacle in _avoidEnts)
{
@@ -384,65 +382,88 @@ public sealed partial class ShipSteeringSystem : EntitySystem
var relVel = shipVel - obsVel;
var toObsVec = obsPos - shipPos;
var toObsDir = toObsVec.Normalized();
// TODO: narrowphase avoidance if we overlap
var obsDistance = MathF.Max(toObsVec.Length() - sumRadius, 1f);
var obsAccel = Vector2.Zero;
if (_shuttleQuery.TryComp(obstacle.Ent, out var obsShuttle))
obsAccel = obsShuttle.LastThrust;
// Distances to bounding planes
var obsDistanceFront = MathF.Max(toObsVec.Length() - sumRadius, 1f);
var obsDistanceCenter = toObsVec.Length();
// get time-to-collide with the accel of each sector
//
// r = obsDistance
// d = sumRadius
// p = vt + at^2 / 2
// solve for: dot(p, toObsDir) = r
// condition for no hit: abs(dot(p, toObsVec.rotate(90))) > d
// p = (x, y)
// toObsDir = (u, v)
// ux + vy = r
// x = v_x*t + a_x * t^2 / 2
// y = v_y*t + a_y * t^2 / 2
// u(v_x*t + 0.5*a_x*t^2) + v(v_y*t + 0.5*a_y*t^2) = r
// t^2 * (0.5*(u*a_x + v*a_y)) + t * (u*v_x + v*v_y) - r = 0
// k = 0.5 * u*a_x + v*a_y = 0.5 * dot(toObsDir, a)
// l = u*v_x + v*v_y = dot(toObsDir, vel)
// m = -r
// t = (-l + sqrt(l^2 - 4km)) / (2k)
// if 4km > l^2, no hit
// if t<0, no hit
//
// https://www.desmos.com/calculator/foyraxlzs7 graphed version
var l = Vector2.Dot(toObsDir, relVel);
for (var i = 0; i < _sectors.Count; i++)
{
var sector = _sectors[i];
var aDir = sector.Sector;
var accel = aDir * sector.Accel - obsAccel; // account for relative accel
var accel = sector.Accel;
var k = 0.5f * Vector2.Dot(toObsDir, accel);
var m = -obsDistance;
float t;
if (k*k < l*l / 1024f)
t = l != 0f ? -m / l : -1f;
// 1. Solve crossing the Front Tangent Plane (Entering the obstacle bounds)
float t_f;
if (k * k < l * l / 1024f)
t_f = l != 0f ? obsDistanceFront / l : -1f;
else
t = 4*k*m > l*l || k == 0f ? -1f : ((-l + MathF.Sqrt(l*l - 4*k*m)) * 0.5f / k);
if (t < 0f || t > simTime)
{
var desc_f = l * l + 4f * k * obsDistanceFront;
t_f = desc_f < 0f || k == 0f ? -1f : ((-l + MathF.Sqrt(desc_f)) * 0.5f / k);
}
if (t_f < 0f || t_f > simTime)
continue;
t = MathF.Max(0f, t - ctx.FrameTime);
// 2. Resolve longitudinal exit/stop trajectory
Vector2 p_end;
if (k * k < l * l / 1024f)
{
float t_c = l != 0f ? obsDistanceCenter / l : -1f;
if (t_c < 0f) t_c = t_f; // Failsafe bounds
p_end = relVel * t_c + 0.5f * accel * t_c * t_c;
}
else
{
var desc_c = l * l + 4f * k * obsDistanceCenter;
if (desc_c < 0f)
{
// The ship stops longitudinally inside the front half of the obstacle
var t_stop = -l / (2f * k);
p_end = relVel * t_stop + 0.5f * accel * t_stop * t_stop;
}
else
{
var t_c = ((-l + MathF.Sqrt(desc_c)) * 0.5f / k);
if (t_c < 0f) t_c = t_f; // Failsafe bounds
p_end = relVel * t_c + 0.5f * accel * t_c * t_c;
}
}
var endAt = relVel*t + 0.5f*accel*t*t;
var proj = MathF.Abs(Vector2.Dot(endAt, new Vector2(-toObsDir.Y, toObsDir.X)));
// Log.Info($"Avoid dir {aDir} time {t}, proj {proj} (k l m {k} {l} {m}) accel {accel}");
if (proj > sumRadius)
// 3. Line-segment to Circle-Center intersection.
// Represents exact path traveled while navigating across the physical dimension of the obstacle.
var p_start = relVel * t_f + 0.5f * accel * t_f * t_f;
var seg = p_end - p_start;
var l2 = seg.LengthSquared();
var hits = false;
if (l2 == 0f)
{
if ((p_start - toObsVec).LengthSquared() <= sumRadius * sumRadius)
hits = true;
}
else
{
// Find closest point on the segment to the obstacle's actual center point
var t_seg = Math.Clamp(Vector2.Dot(toObsVec - p_start, seg) / l2, 0f, 1f);
var proj = p_start + seg * t_seg;
if ((proj - toObsVec).LengthSquared() <= sumRadius * sumRadius)
hits = true;
}
if (!hits)
continue;
var t = MathF.Max(0f, t_f - ctx.FrameTime);
var ctime = sector.ImpactTime;
if ((ctime == null || ctime > t) && (!sector.Priority || obstacle.IsGrid))
{
var priority = obstacle.IsGrid || sector.Priority;
_sectors[i] = new(sector.Sector, sector.Accel, sector.Scale, t, priority);
_sectors[i] = new(sector.Accel, sector.Scale, t, priority);
}
}
}
@@ -457,7 +478,7 @@ public sealed partial class ShipSteeringSystem : EntitySystem
var sector = _sectors[i];
if (sector.ImpactTime == null)
{
var toWishSq = (wishDir - sector.Sector).LengthSquared();
var toWishSq = (wishDir - NormalizedOrZero(sector.Accel) * sector.Scale).LengthSquared();
if (toWishSq < closestDistance)
{
closestDistance = toWishSq;
@@ -480,7 +501,7 @@ public sealed partial class ShipSteeringSystem : EntitySystem
if (chosen.Scale == -1f)
return null;
return chosen.Sector * chosen.Scale;
return NormalizedOrZero(chosen.Accel) * chosen.Scale;
}
// navigation for if we aren't avoiding a collision
@@ -501,21 +522,19 @@ public sealed partial class ShipSteeringSystem : EntitySystem
if (brake.LeftoverBrakePath > destDistance)
return -linVelDir;
// mirror linVelDir in relation to toTargetDir
var adjustVec = -(linVelDir - toDestDir * Vector2.Dot(linVelDir, toDestDir));
var adjustDir = NormalizedOrZero(adjustVec);
var wishThrustDir = toDestDir + 2f * adjustVec;
var wishThrustVec = _mover.GetDirectionAccel((-ctx.ShipNorthAngle).RotateVec(wishThrustDir), ctx.Shuttle, ctx.ShipBody);
var adjustAccel = Vector2.Dot(adjustDir, wishThrustVec);
var maxAdjust = Vector2.Dot(-adjustDir, relVel);
adjustVec *= adjustAccel == 0f ? 0f : float.Clamp(maxAdjust / (adjustAccel * ctx.FrameTime), 0f, 1f);
var accelEstVec = _mover.GetWorldDirectionAccel(toDestVec, ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
var accelEst = accelEstVec.Length();
// fallback to forward accel
if (accelEst == 0f)
{
var forwardAccelVec = _mover.GetDirectionAccel(new Vector2(0f, 1f), ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
accelEst = forwardAccelVec.Length();
}
// takes target relative to us
var wishAngle = _seeking.CalculateAdvancedTracking(toDestVec, -relVel, accelEst);
// do not yet process whether we can actually accelerate well in that direction
return toDestDir + 2f * adjustVec;
return wishAngle.ToWorldVec();
}
private readonly record struct RotationResult(float RotationInput, float WishAngleVel);
@@ -714,5 +733,5 @@ public sealed partial class ShipSteeringSystem : EntitySystem
private readonly record struct ObstacleCandidate(Entity<TransformComponent, PhysicsComponent> Ent, Vector2 Pos, float Radius, bool IsGrid);
private record struct EvadeCandidate(Vector2 Sector, float Accel, float Scale, float? ImpactTime = null, bool Priority = false);
private record struct EvadeCandidate(Vector2 Accel, float Scale, float? ImpactTime = null, bool Priority = false);
}
@@ -21,6 +21,12 @@ public sealed partial class ShipTargetingComponent : Component
[DataField]
public float LeadingAccuracy = 1f;
/// <summary>
/// How good to lead offgrid targets.
/// </summary>
[DataField]
public float OffgridLeadingAccuracy = 1f; // be more accurate since an offgrid target is presumably maneurable and small
/// <summary>
/// Velocity we're currently estimating for imperfect target leading.
/// </summary>

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