* Additional Ionstorm Law Updates (#34197) * Added a couple entires, removed the references to IRL countries, and fixed a grammar mistake on "Telecomunications Equipment" * Fixed another awkward grammar situation * Commented out a bunch of law elements I felt weren't good for discussion, added some new ones to help fill in the missing areas * Reverted pylons, and added more entries to help fill out the lists more * reverted all deletions * implemented feedback, also realized the IonStormActions section was rather short, so I added a bunch there * realized I didn't remove the extra resources line, also added more things, and fixed one grammatical error * Removed Portugal again, added more Musts since that list was the new shortest one. * Automatic changelog update * Add option to disable bwoink sound. (#33782) * Add option to disable bwoink sound. * Now it's working only with active admin status. * No bwoink, only "notification sound" * Moar changes * Another one * Automatic changelog update * Pride Scarves (#34448) * More scarfs (#216) * Sprites + Colored Scarf Implimentation Doesn't include into quickthreads or other clothing vendors * File Paths are important * Metas, items exist, winterdrobe I'm iffy on them being in the winterdrobe but the other option is quickthreads so idk * File Path Fix * Typo fix * Resolved merge conflict * Moved scarves around * Moved scarf textures out of the _CD directory * Removed final CD folder * Removed extra scarfs * Removed extra scarfs, again * Replaced ClothingNeckBase with ClothingScarfBase --------- Co-authored-by: PursuitInAshes <91865152+PursuitInAshes@users.noreply.github.com> Co-authored-by: TakoDragon <69509841+BackeTako@users.noreply.github.com> * Automatic changelog update * Replace the djstation intercoms with freelance intercoms (#34478) * Add Freelance intercom prototype, and replace the djstation ruin with them instead of three master keys * removed a new line * Update nix flake for .NET 9 (#34480) * Automatic changelog update * Holopad networking rework (#34112) * Initial commit * Finalizing main changes * Addressed reviews * Fixed a few issues * Switched to using global overrides * Removed unnecessary references * Make GasMixture enumerable I noticed that enumerating gases is frequently done in an annoying way with Enum.GetValues. So I made it better. Now GasMixture is IEnumerable<(Gas gas, float moles)> and it just works. * Improve canister admin logs. 1. Now clearly says "opened"/"closed" when changing the release valve. 2. Clearly says whether the valve was opened while a canister was inserted or not. 3. When a tank is ejected, logs if the valve is open and the ejection started spilling into the environment. Fixes #34488 * Optimize & clean up RadiationSystem (#34459) * Optimize & clean up RadiationSystem * comments * Update Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> --------- Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> * Update engine to v240.0.1 (#34497) * Various Locale Typo Fixes (and spaces) (#34483) Random spelling error and FTL linting (+PowersinkSystem because there was an misspelt locale for that) * Space lizard plushie can now be worn on your head (#33809) * space weh can now be work on top of head * space weh can now be worn on top of head * trim out unnecessary comps * Automatic changelog update * Adds bullet collision to wall mounted cameras (#34500) * Automatic changelog update * Change MaskComponent to accommodate sprites namings (#33451) Co-authored-by: Winkarst <74284083+Winkarst-cpu@users.noreply.github.co> * Automatic changelog update * Add a 10u vial of plasma to the chemical locker (#33871) Add 10u plasma to the chemistry locker so the chemists stop stealing tables. * Automatic changelog update * Rarer Highcaps (#34469) * Removed highcap from seclite * Changed cyborg starting battery to highcap, reduced seclite wattage to make it last as long as it used to. * Gave cyborgs back their medcap * Rounded seclite wattage down to 0.5 * Automatic changelog update * New dry fire sound (#34447) * new empty.ogg * source to tg commit * Automatic changelog update * CentComm Map Updates (#34475) * Bandage fix denied animations playing on devices without them * CentComm blast door prototype * CentComm button * CentComm window shutters * CC Updates * Save as grid * Remove an extra detective figurine I like them better in the interrogation room * Remove paramed locker, let pumps shut off * Fix wrong HOP locker prototype * Automatic changelog update * Cog power setup fix (#34188) * many changes * contentingregrationtests * serialized invalid removed * blank * "Changes and fixes as suggested" * blank * blank * added desk bells * engi rework rework rework * added gate to content integration * tweaks * aaa * bbb * added holopads * ccc * Update default.yml * hotfix * aaa * bbb * many many tweaks and fixes * aaa * decals and maints * aaa * bbb * ccc * cog power setup was bad * made it artsy --------- Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com> * Add Airlocks with Bar and Kitchen access (#33821) * Add kitchen access to Bar-Cafeteria airlocks on Cog * Fix merge conflict * Remove mapping changes * Add Glass and Maints versions * Fixed minor spelling mistake in Noir Trenchcoat description. (#34519) Update coats.yml * Update Credits (#34507) Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> * Make storage implant drop items on gibbing (#33493) * Make storage implant drop items on gib/removal * Better way * Fix error * Forgotten trash * Cleanup * Unused var * Update Content.Server/Implants/ImplantedSystem.cs Co-authored-by: 0x6273 <0x40@keemail.me> --------- Co-authored-by: Winkarst <74284083+Winkarst-cpu@users.noreply.github.co> Co-authored-by: 0x6273 <0x40@keemail.me> * Automatic changelog update * Fix `emergency_elkridge` being saved as a map (#34496) Save `emergency_elkridge` as a grid * Update engine to v240.1.0 (#34524) * Fixes some mobs not being able to honk/weh (#33777) * fixes some mobs not being able to honk/weh * addresss review * Automatic changelog update * Welding gas mask toggleable with action (#32691) welding mask removable * Automatic changelog update * Update engine to v240.1.1 (#34527) * Automatic changelog update * Plasma Dirt Fix (#34534) did the dirt thing * Plasma station population tweak (#34549) * Plasma Station initial commit * Map fixes 1 Expanded science's SMES array Added advanced SMES Redone stamped documents with custom stamps Expanded atmospherics with more storage tanks Added status displays Add missing beacons to solars Replaced the passive gates in science with valves Removed protolathe in engineering Added guitar to CE office Replaced throngler plushie with weh cloak Add a lattice tile outside the atmos burn chamber and storange tanks Added atmos network monitor in bridge * Add cargo and emergency shuttle * Updated maps * Add plasma to map testing list * Map fixes 2 Reworked pipenets to not go under walls Redid salvage and disposals Reworked the bar to include a new bar extension facing the pool Replaced arrivals cryo with an arcade Replaced the toilets in the service plaza with cryo Removed the cryo in dorms Added more details to hallways Redid tools room to include a front desk for the janitor closet Reconnected sci to power roundstart Removed some unideal spawns Expanded the TEG airlock to be 2x3 instead of 1x3 Reduced the size of the SMES bank from 10 to 6 Disabled the plasma miners (downstreams or admins can re-enable them) Replaced illegal maint items * Fixes a 6 pack destroying the universe Ok maybe cracking a cold one with the boys wasn't a great idea. * Map fixes 3 * Quick research assistant fix * Map fixes 4 * Map fixes 5 * webedit go brrrt * Map fixes 6 * Map fixes 7 * Map fixes 8 * Fixes non-existent object It's amazing this game runs at all * Map fixes 9 * update pools * Map fixes 10 * forgot to clear my multitool I love mapping I love mapping I love mapping I love mapping I love mapping * Tweaked player counts * Update population caps Removed population cap of 60 players to make plasma into a highpop map - it's that easy! --------- Co-authored-by: jbox1 <40789662+jbox144@users.noreply.github.com> Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com> * Automatic changelog update * Job contraband rework (#33385) * contraband system rework to allow restriction by job, not just department * Fixing detective trenchcoat inheritance * removing unnecessary using declarations * trying to fix testing error by re-adding diagnostics using declaration * removing unecessary dependency, making allowedJobs nullable * Adding all of slarti's requested changes except for the hacky job icon method fix * removing accidental whitespace * choosing to use the non-localized version because we're comparing the string against the AllowedJobs field, and the contraband classes that fill that field are written in english * removing unneeded using dec, fixing nesting logic problem * didn't remove the old nesting, doing that now * using localized job title and localizing the allowed jobs string, removing usages of JobTitle field. Also networked the _jobTitle field instead. * rewrite some stuff * fixes * fix energy pen --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * Electrified doors/windoors now spark, new tips to deal with doors without access or when electrified (#34502) * new tips to open doors (throwing PDA/ID, dragging body) * electrified door sprite for players * tooltip to reset AI electrified doors * windoor electrified sprite * highsec electrified visual * increase tip dataset to 138 * corrected square bracket convention in this commit * removed door corpse tip from prior commit * Automatic changelog update * Blueprint double emergency tank (#34232) * blueprint * NitrogenTank * description * Automatic changelog update * Add system to kick people if they connect to multiple servers at once. (#34563) * Automatic changelog update * bagel update (#34572) * bagel update * Decal fault tolerance * Also fix writes * fix mv cable, decal issues, yar * fix a floor tile and some other shit --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * remove tropico from devmap (#34585) * Update wizden config to disallow multiple connections to multiple wizden servers (#34584) * Give the chef access to cloth boxes (#34403) * Give the chef access to fiber bags * Update Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * lecter visual update (#34589) * Automatic changelog update * Box Station - Update (#34605) * Various changes * Implemented a bunch of changes Emisse wanted, finished up all my atmos changes I wanted to make as well * Added reporter to prototype, and fixed dirt * Resolved outstanding issues * Fixed a floating camera * pluralize the job name in the contra description (#34559) * pluralize the job name in the contra description * pluralization specific to contraband descriptions * Automatic changelog update * Feature/make radial menu great again (#32653) * it works! kinda * so it works now * minor cleanup * central button now is useful too * more cleanup * minor cleanup * more cleanup * refactor: migrated code from toolbox (as it was rejected as too specific) * feat: moved border drawing for radial menu into RadialMenuTextureButton. Radial menu position setting into was moved to OverrideArrange to not being called on every frame * refactor: major reworks! * renamed DrawBagleSector to DrawAnnulusSector * Remove strange indexing * Regularize math * refactor: re-orienting segment elements to be Y-mirrored * refactor: extracted radial menu radius multiplier property, changed color pallet for radial menu button * refactor: removed icon backgrounds on textures used in current radial menu buttons with sectors, RadialContainer Radius renamed and now actually changed control radius. * refactor: in RadialMenuTextureButtonWithSector all sector colors are converted to and from sRGB in property getter-setters * refactor: renamed srgb to include Srgb suffix so devs gonna see that its srgb clearly * fix: enabled any functional keys pressed when pushing radial menu buttons * fix: radial menu sector now scales with UIScale * fix: accept only one event when clicking on radial menu ContextualButton * fix: now radial menu buttons accepts only click/alt-click, now clicks outside menu closes menu always --------- Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> * Automatic changelog update * Return Drozd full-auto and semi-auto firing modes (#34604) return drozd full auto and firerate, arrange available modes in alphabetical order * Automatic changelog update * added missing allowed department to the restricted severity (#34558) * added missing allow job to the base restricted severity * no need to make a list * no more linq in ContrabandTest * less nesting in ContrabandTest * Automatic changelog update * C4 Helmet (#34076) * worn bomb * Update meta.json * Update meta.json * Automatic changelog update * Make radioactive material radioactive (#34436) * Make radioactive material radioactive * Increase the slopes of item uranium No free power for engineering * Glowing uranium * Revert "Increase the slopes of item uranium" This reverts commit 2acbda26 * Nerf Wall Rocks * Automatic changelog update * Small fixes for Meta station (#34613) * Fixed a few minor things, removed salvage's shuttle parts (sorry), added more equipment to armory. * Added back salvage shuttle equipment, now in a crate * Fix * replace all instances of "department-{id}" with department.name (#34607) replace all instances of "department-{id}" with department.name" * Renaming sexy mime and clown mask (#34258) * Renamed sexy clown and mime mask and removed lusty xenomorph poster. * removing odd file changes. not sure why these were edited at all. * gaaaahhh * removed some of the old templates * Renaming sexy mime and clown instances. * removed unwanted gitignore edit * Renaming sexy clown and sexy mime mask and Removing lusty exomorph poster. * Remove lusty xenomorph poster. * reverted changes to maps files, actually. Other contrib is learning, appologies for excessive commits * requested changes * lusty migration removal * dirty hanging whitespace * correct date format comment --------- Co-authored-by: NathanielJ14 <nathanielalbert1202@gmail.com> * Automatic changelog update * Moved Cyborg Recharging Circuit Board from Lathe to Circuit Imprinter (#34612) Moved Cyborg Recharging Cirbuit Board from Lathe to Circuit Imprinter * Automatic changelog update * Replace starter borg brain with Positronic (#34614) THE FLESH IS WEAK * Automatic changelog update * Elkridge Depot fixes and changes (#34539) * fixed grav gen room, gave atmos room to branch off, replaced AME controller crate with AME controller * added rust to maints, expanded atmos, expanded bar, expanded cap bedroom, fixed some tiles under doors, removed some gamer loot * removed interrogator lamp and some more gamer loot * fixed some more tiles under doors, replaced intercomall with intercomcommand in bridge and vault * added clothing drobes to arrivals, added bin to HoP, more stuff i forgot * i forgot to move the file from /bin to /resources... * delete radstorm locale (#34630) * display the current version in the changelog window (#34556) * just works first try * introduce more magic numbers into the codebase * idiot doesnt know the difference between AND/OR * Update Credits (#34644) Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> * New solar sprites, new solar panel upgrades, and some solar panel fixes. (#29224) * New solar sprites, new solar panel upgrades, and some solar panel fixes. This adds and changes a few things for solar panels! * New sprites for all solar panels and all related states. * Move from xform.WorldRotation to _xformSystem.SetWorldRotation within the solar code. * Few random fixes that Rider suggested as warnings. * Solar Tracker Electronics was using what looks like to be the airlock controller electronics, so that's now updated to something a bit more realistic. It also uses the engineering circuit sprite instead of the generic * New Solar Panels! Adds Plasma and Uranium Glass solar panels. These can be constructed by adding the respective glass to the panel. Plasma is a slight increase of power and health, and uranium is double the power and health of glass. Thus giving engineers something to update if they want to use solar panels and possibly giving small outposts a way to make a bit more power without a large and complex power setup. * Add in solar sprites that were not in the meta file. * Updated sprites based on feedback. * Fix the rotation of the solar panel sprites. * Automatic changelog update * Added Unused HoS's Flask to HoS locker (#34658) added hos flask to locker spawn * Automatic changelog update * Fland Fix (#34670) Update fland.yml * Reroute Meta station power, engineering cosmetic changes, minor fixes (#34669) * reroute meta power plus cosmetic fixes * fix a lot of other things * make changes from conflict * Amber Station Changes (#34656) * various changes * Increased player limit * slight modification * Followed proper yaml formatting, increased lawyers * Box Station Changes (#34655) * Various changes * Implemented a bunch of changes Emisse wanted, finished up all my atmos changes I wanted to make as well * Added reporter to prototype, and fixed dirt * Resolved outstanding issues * Fixed a floating camera * Added cameras to TEG, and some minor changes * Fix mv cable crate typo (#34673) fix mv cable crate typo * Plasma station patch 1 (#34602) * Plasma Station initial commit * Map fixes 1 Expanded science's SMES array Added advanced SMES Redone stamped documents with custom stamps Expanded atmospherics with more storage tanks Added status displays Add missing beacons to solars Replaced the passive gates in science with valves Removed protolathe in engineering Added guitar to CE office Replaced throngler plushie with weh cloak Add a lattice tile outside the atmos burn chamber and storange tanks Added atmos network monitor in bridge * Add cargo and emergency shuttle * Updated maps * Add plasma to map testing list * Map fixes 2 Reworked pipenets to not go under walls Redid salvage and disposals Reworked the bar to include a new bar extension facing the pool Replaced arrivals cryo with an arcade Replaced the toilets in the service plaza with cryo Removed the cryo in dorms Added more details to hallways Redid tools room to include a front desk for the janitor closet Reconnected sci to power roundstart Removed some unideal spawns Expanded the TEG airlock to be 2x3 instead of 1x3 Reduced the size of the SMES bank from 10 to 6 Disabled the plasma miners (downstreams or admins can re-enable them) Replaced illegal maint items * Fixes a 6 pack destroying the universe Ok maybe cracking a cold one with the boys wasn't a great idea. * Map fixes 3 * Quick research assistant fix * Map fixes 4 * Map fixes 5 * webedit go brrrt * Map fixes 6 * Map fixes 7 * Map fixes 8 * Fixes non-existent object It's amazing this game runs at all * Map fixes 9 * update pools * Map fixes 10 * forgot to clear my multitool I love mapping I love mapping I love mapping I love mapping I love mapping * Tweaked player counts * Update population caps Removed population cap of 60 players to make plasma into a highpop map - it's that easy! * Map fixes 11 * Map fixes 12 * Map fixes 13 * Map fixes 14 Now it's personal * Map fixes 15 --------- Co-authored-by: jbox1 <40789662+jbox144@users.noreply.github.com> Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com> * Automatic changelog update * add a chem dispenser to the nukie planet (#34674) * add a chem dispenser to the nukie planet * begone test fail * Adds better description to pneumatic valve and build menu description (#32655) * adds description to pneumatic valve and build menu description * Change one atmosphere to kPa * Automatic changelog update * fixrotations - Modified Targetted Entities (#34638) * Fix match box (#34632) FixMatchBox Now you can't put anything but matches in a box of matches. Co-authored-by: Helm4142 <Helm4142@users.noreply.github.com> * Automatic changelog update * Hi-viz vest now actually hi-viz (#34087) * 27-12-2024-light-hi-viz * 28-12-2024-meta-fix * Storage UI V2 (#33045) * Automatic changelog update * Added Pain Numbness Trait (#34538) * added pain-numbness component and system * added numb as a trait that pulls the pain numbness component * removed new event as mob threshold event as already being fired * checked for MobThresholdsComponent first before running VerifyThresholds * refacted force say to using LocalizedDatasetPrototype and added numb messages * added severity check alert * added comment for BeforeForceSayEvent * removed space formatting * changed Cancelled to CancelUpdate, fixed spacing and added two more damage-force-say-numb * changed prefix damage-force-say-numb to 5 (whoops) * Automatic changelog update * Update submodule to 241.0.0 (#34678) * Add puddle drawdepth (#32369) Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * Revert "Fix match box" (#34681) Revert "Fix match box (#34632)" This reverts commit88456a4a8c. * update .editorconfig (#34677) * Make crew monitor update blips at consistent rates (#32555) * Added the ability for pAIs and station maps to be stored in engineering belts (#33048) Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com> * Automatic changelog update * Engineering guidebook megaupdate v2 (#33062) Significantly updates the Engineering guidebook (more explicitly the Atmos section) to have a lot more relevant and useful information. Right now engineering has been getting update after update with no real change to the relevant guidebook entry. This has lead to a lot of out of date information and bad practices being prevalent in the guidebook, something that pains me to read. * Automatic changelog update * Omega: fix cryo pipe (#34663) fixed the freezer pipe in cryo room * Add more escape pods on Packed (#34628) * add escape pods north and south of station * move south escape pod a bit to the east * Storage sidebar fix (#34680) * Add conditional camera offset based on cursor - Hristov Rework, Part 1 (#31626) * Automatic changelog update * Amber Station Stuff (#34686) * Removed salv upper docking airlock, gave south west solars some uranium glass. * Routed HV wire through maints more, gave disposal blast doors a lever, added beacons and signs to escape pods * Fake mindshield componentry and Implanter (#34079) * Fake Mindshield (With some bad sprites) - Add FakeMindshield System and Component - Add FakeMindsheildImplantSystem and Component - modify ShowMindShieldIconsSystem to check for FakeMindshields - add all supporting yaml for the Implants, action and uplink - add loc file stuff - add unfinished sprites * Cleanup, add to thief toolbox, remove metagame - Move Implant sameness check to AFTER the implant DoAfter to prevent instant identification of Deception Implants - cleanup the systems and components - add the fake mindshield to the Thief toolbox * part 1 of fixing the folder problem * Make the fakemindshield sprite folder lowercase * CR - Move ImplantCheck into shared, cleanup - Moved ImplantCheck and eventsubscription into Shared - Remove Client/Server extensions of FakeMindshieldImplantSystem and FakeMindShieldSystem and make shared Sealed - make OnToggleMindshield Private, use the event! * CR - Cleanup extra lines, fix some Prototype - cleaned up extra liens in ImplanterSystem and SharedFakeMindshieldSystem from when i was developing - Uplink catalog no longer lists the implant in 2 spots, only implants now, also uses the On state action icon - added a comment about why it's reraising the action event rather than directly interacting with the FakeMindshield Component * Fake Mindshield CR: - Added a comment about IsEnabled - moved OnFakeMindShieldToggle to Entity<> from Uid, Comp - fixed some formatting in uplink_catalog * CR - Add a bit more comment * Automatic changelog update * reworking the chunked telecomms salv ruin (#34529) * aaa * bbb * ccc * ddd * switched to grid * pain --------- Co-authored-by: TytosB <duanlaintytos@yahoo.com> * loop update (#34688) loop loot gone * bagel update (#34690) * bagel update * fixe * move tesla gen * Fix airsensors not having a nitrogen threshold (#34689) * Buff frezon to acceptable values, pending a frezon rework (#34049) buff frezon * Automatic changelog update * Stun baton precise attack thrust animation (#34693) use weaponarcthrust instead of wide swing on single attack * Automatic changelog update * Add disposal units to marathon chapelroid (#34709) add disposal units to marathon chapelroid * Oasis: add some emergency O2/N2 lockers (#34715) Added O2, N2, and Fire lockers to Oasis station as per discussions in the issue thread Co-authored-by: Orange-Winds <sebastian.pelka@hotmail.com> * Removes radioactive suppermatter from Plasma Station (#34726) Removes radioactive suppermatter Literally 1984 can't have shit on SS14 e.t.c. e.t.c. Co-authored-by: jbox1 <40789662+jbox144@users.noreply.github.com> * New salvage ruin: the ruined prison ship (#34651) * aaa * little tweaks * changed to grid and added to protos * ccc * we dont talk about it * ddd --------- Co-authored-by: TytosB <dunalintytos@yahoo.com> * Buff savlage vault-medium-1's loot (#34732) * aaa * little tweaks * changed to grid and added to protos * ccc * we dont talk about it * ddd * aaa --------- Co-authored-by: TytosB <dunalintytos@yahoo.com> * drozd visual update (#34574) * drozd visual update * fix wielded sprotes * update sprotes * Automatic changelog update * Nuke Timer MinimumTime (#34734) * Implemented the thing * Made requested changes * Automatic changelog update * Fix thrust animation rotation (#34713) * Fix thrust animation rotation * directly update sprite instead * Automatic changelog update * Fix vulture spawning additional salvage debris (#34593) fix vulture spawning additional salvage debris * Nukie briefing fix (#34737) payloadpayloadpayloadpayloadpayloadpayload * Sentient medibot now can inject (#32110) medibot player injection Co-authored-by: YourUsername <you@example.com> * Criminal Records Computer Better UX + Filtering (#32352) * First pass at new Criminal Records Computer need buttons to highlight. * Filter status tabs/buttons now activate correctly via UpdateState * Removed unneeded Directives * Fix typo + undo VSCode changes * Implement Emo Feedback Loc NA and use inject deps Cannot use inject deps on sprite system. * try to undo vscode launch.json change * Added requests + Filter dropdown list + jobs Fixed maintainer fix requests, Added Job to announcement channel output Removed toggle buttons in-place of a dropdown list * Fixed missed merge conflict + fixed an bug with filterstatus not showing on re-open ui * Update criminal-records.ftl Fixed lint error. whoops. * Update Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs typo Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * impliment chromiumboy feedback hopefully this will do it.... --------- Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Added some more borg names (#32502) added a handful of new names * Automatic changelog update * Update ScalingViewport for Engine PR (#28786) * Update submodule to 242.0.0 (#34739) * fix stop bleeding popup (#34729) * fix stop bleeding popup * add identity * Fix RoleTimeRequirement localization (#34735) * fix job restriction localization * Update Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs * Update Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * reworking chem and adding psychology to loop (#34749) * aaa * little tweaks * changed to grid and added to protos * ccc * we dont talk about it * ddd * aaa * psychology real --------- Co-authored-by: TytosB <dunalintytos@yahoo.com> * Fix shuttle console angular velocity (#34748) * Syndicate and CentComm Radio Implanters (#33533) * Add Syndicate Radio Implant * Fix description grammar * remove unused var * Update - Small fixes * Un mess with imports * Remove unused tag * Correction * Clear lists instead of remove * Update 0 check * Add Centcomm implant * Correct centcom channel * Correct params * No more crying * Update Content.Shared/Implants/Components/RadioImplantComponent.cs * Update Content.Server/Implants/RadioImplantSystem.cs * Update Content.Server/Implants/RadioImplantSystem.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * New Highpop map: Convex recreational complex (#33346) * start ig * command work +AI * finished bridge, added signs to departments * engi partly done * finish engi + atmos except for cams * added med yippie * carGODO * nerd department finished * se(x)c finish * courtroom + service power * man im tweaking out * service propa done * hallways * added cams everywhere and named all the doors :pain: * arrivals maints + exterior catwalk * more maints * voxob * draft done * holopadpilled + gaspipesensor based * added convex name everywhere it needed to be * nvm i guess we dont do oxford comma * redid ALL THE HGOLOPADS FAAAAAAAAAAAAAAAAAAAAA * Fix conflicts? * ok let me try something * this works right? * atmos updated + jani lights * uniform hallway tiles +roundstart TEG * fixed some hidden atmos stuff * Update map_attributions.txt * :finnadie: no map review ... * the fog is coming * maints theater redone * light dirt and the murder of most trims * 💔 * decal based and tileoverlay pilled * updated arrivals/library * bungus * yvgh * 6 hours later... * finished for real? * Automatic changelog update * Update cigarette.yml (#34756) simple change to one of Dan's Smokes that fixes a typo. * Seperate EMAG into EMAG and Authentication Disruptor (#34337) * Automatic changelog update * Astro Asteroid Sand (#34463) * Added astro asteroid sand * fixed the stack name * adjusted tile sprites for asteroid and ice * Adjusted meta.json * Realized the other tile sprites all have solid lines on their perimeters * Update Resources/Prototypes/Stacks/floor_tile_stacks.yml that works Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> * Update tiles.yml * Update Resources/Locale/en-US/tiles/tiles.ftl Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> * adjusted the snow sprite * Update meta.json --------- Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> * Automatic changelog update * The Goliath Hardsuit (#34721) * Entities for hardsuit and helmet, graph and crafting recipe; modifies goliath hide drop rate * Added hardsuit sprites, corrected some crafting YAML * fixed crafting recipe, adjusted resource requirements * Added durathread material req --------- Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com> * Automatic changelog update * make RefreshOverlay default to the player session (#32354) * Loop station minor tweaks (#34758) * small fixes * eee * Juice that makes you go boom (#34730) * Juice that makes you go boom * moved explosive juice to fun yml - fixed ExplosionReactionEffect.cs not having TileBreakScale parameter - made Drazil plushie major contraband (they are evil!!!!!) * removed JASON!!!! JASOOON!!! JASON!!! * don't do commits at 1am * Update fun.yml fix ident * no more bullying the server (only 1 explosion) * Automatic changelog update * Adjust inventory size of ginormous scrap (#33454) Adjusts the size of ginormous scrap to fit in bags of holding * Automatic changelog update * After getting banned, you now have to re-read the rules! (#33270) * first commit * opps * Reset cooldown instead * Added ccvar * Not replicated! * More robust Particle Accelerator menu (retry) (#34037) Particle Acceleratir fixed Co-authored-by: VideoKompany <135313844+VlaDOS1408@users.noreply.github.com> * Serializable emag flags (#34766) * serializable idk * guh * Fix Double Muzzle Flash (#33981) * Pass user to effects to properly fix double muzzle flash. * Use gun when user is null. * Update MuzzleFlashEvent.cs * Update MuzzleFlashEvent.cs * Update GunSystem.cs * Update SharedGunSystem.cs * Update MuzzleFlashEvent.cs * Update SharedGunSystem.cs * Update SharedGunSystem.cs * Update Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> --------- Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> * Automatic changelog update * fix BankClientComponent never updating (#33123) * fix * oh right * Add history tab to bounty console (#33932) * Add struct for holding historical data on cargo bounties * Add localisation strings for bounty history * Add new XAML entry for display bounty history * Expand cargo bounty menu to include tabs * Ensure station databases hold historical bounty data * Add to the bounty history when removing one from active * Feed bounty history into cargo's bounty system * Move tab title setting to constructor * Remove redundant access specifications * Remove un-needed override * Fixup BountyHistoryEntry backing code * Fix formatting in CargoBountyMenu * Reformat BountyHistoryData * Rework TryRemoveBounty to use new Entity type * Add Enum for showing bounty results * Rework look and feel of History tab * Add visible text when no bounties have been completed yet * Remove control * Swap default to null * Reverse ordering of bounties so last entry comes first * Remove redundant Visible * Move enum docs into the enum * Automatic changelog update * Minor shotgun changes and comments for future changes (#33512) * kammerer ammo, firerate and comment * bulldog description * enforcer description and comment * DB and sawn-off firerate parity, sawn-off comment * description changes * reduce kammerer firerate again * Automatic changelog update * Fixed: Ore now correctly drops the right amount of ore (#34557) Fixed bug * Automatic changelog update * Implement mrp rule changes pursuant to community votes (#34772) * #34771 Fix door system assuming all door layer states are in single RSIs (#34775) * #34771 Fix door system assuming all door layer states are in single RSIs * Delta confirmed monarch of sloggery * Do a dirty, rotten web edit Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * HOTFIX update staging submodule to 242.0.1 (#34833) * HOTFIX revert lecter visual update #34589 (#34826) * Allowed windows to be properly clicked on (#34751) * Fix hitting through directional windows (and more!) (#34793) * Revert "Add taped logo back for 10th anniversary" (#34831) * Hristov & .60 changes - Hristov Rework, Part 2 (#31662) * Initial commit * Updated values to reflect new resistances * Review fixes * Review fixes * LINQ BEGONETH * revert engine * Revert "Storage sidebar fix (#34680)" This reverts commit3e091c4dfa. * Revert "Storage UI V2 (#33045)" This reverts commitfd25dac720. * Revert "Update ScalingViewport for Engine PR (#28786)" This reverts commit3ad83378bb. * Update physical.yml * Update CP14SharpeningSystem.cs --------- Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com> Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: c4llv07e <igor@c4llv07e.xyz> Co-authored-by: PursuitInAshes <91865152+PursuitInAshes@users.noreply.github.com> Co-authored-by: TakoDragon <69509841+BackeTako@users.noreply.github.com> Co-authored-by: Minemoder5000 <minemoder50000@gmail.com> Co-authored-by: Tobias Berger <toby@tobot.dev> Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> Co-authored-by: Partmedia <kevinz5000@gmail.com> Co-authored-by: Mono <182929384+Monotheonist@users.noreply.github.com> Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com> Co-authored-by: Myra <vasilis@pikachu.systems> Co-authored-by: Winkarst <74284083+Winkarst-cpu@users.noreply.github.com> Co-authored-by: Winkarst <74284083+Winkarst-cpu@users.noreply.github.co> Co-authored-by: Nox <nebulousnox38@gmail.com> Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com> Co-authored-by: War Pigeon <54217755+minus1over12@users.noreply.github.com> Co-authored-by: TytosB <54259736+TytosB@users.noreply.github.com> Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com> Co-authored-by: Alfred Baumann <93665570+CheesePlated@users.noreply.github.com> Co-authored-by: Tezzaide <ewankayne@hotmail.co.uk> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: 0x6273 <0x40@keemail.me> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: compilatron <40789662+Compilatron144@users.noreply.github.com> Co-authored-by: jbox1 <40789662+jbox144@users.noreply.github.com> Co-authored-by: John <35928781+sporkyz@users.noreply.github.com> Co-authored-by: Nim <128169402+Nimfar11@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com> Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> Co-authored-by: Ignaz "Ian" Kraft <ignaz.k@live.de> Co-authored-by: Fildrance <fildrance@gmail.com> Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> Co-authored-by: JustinWinningham <justinmwinningham@gmail.com> Co-authored-by: NathanielJ14 <nathanielalbert1202@gmail.com> Co-authored-by: Velken <8467292+Velken@users.noreply.github.com> Co-authored-by: Deerstop <edainturner@gmail.com> Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com> Co-authored-by: Milon <milonpl.git@proton.me> Co-authored-by: CaasGit <87243814+CaasGit@users.noreply.github.com> Co-authored-by: Coolsurf6 <coolsurf24@yahoo.com.au> Co-authored-by: SlimSlam <73899110+SlimmSlamm@users.noreply.github.com> Co-authored-by: Dinner <180707738+DinnerCalzone@users.noreply.github.com> Co-authored-by: Helm4142 <158806576+Helm4142@users.noreply.github.com> Co-authored-by: Helm4142 <Helm4142@users.noreply.github.com> Co-authored-by: kosticia <kosticia46@gmail.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: August Sun <45527070+august-sun@users.noreply.github.com> Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com> Co-authored-by: NakataRin <45946146+NakataRin@users.noreply.github.com> Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Co-authored-by: Zachary Higgs <compgeek223@gmail.com> Co-authored-by: TytosB <duanlaintytos@yahoo.com> Co-authored-by: Orange-Winds <orange.wind@outlook.com> Co-authored-by: Orange-Winds <sebastian.pelka@hotmail.com> Co-authored-by: TytosB <dunalintytos@yahoo.com> Co-authored-by: themias <89101928+themias@users.noreply.github.com> Co-authored-by: godisdeadLOL <169250097+godisdeadLOL@users.noreply.github.com> Co-authored-by: YourUsername <you@example.com> Co-authored-by: James Simonson <jamessimo89@gmail.com> Co-authored-by: darkdan <145926356+Starbuckss14@users.noreply.github.com> Co-authored-by: Preston Smith <92108534+thetolbean@users.noreply.github.com> Co-authored-by: DR-DOCTOR-EVIL-EVIL <hudsonirwin11@icloud.com> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: SpaceRox1244 <138547931+SpaceRox1244@users.noreply.github.com> Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Co-authored-by: VideoKompany <135313844+VlaDOS1408@users.noreply.github.com> Co-authored-by: sleepyyapril <123355664+sleepyyapril@users.noreply.github.com> Co-authored-by: BarryNorfolk <barrynorfolkman@protonmail.com> Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Co-authored-by: Hannah Giovanna Dawson <karakkaraz@gmail.com> Co-authored-by: pubbi <63283968+impubbi@users.noreply.github.com>
1304 lines
49 KiB
C#
1304 lines
49 KiB
C#
using System.Collections.Immutable;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Content.Server.Administration.Logs;
|
|
using Content.Shared.Administration.Logs;
|
|
using Content.Shared.CCVar;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Preferences;
|
|
using Content.Shared.Roles;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Npgsql;
|
|
using Prometheus;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.ContentPack;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Prototypes;
|
|
using LogLevel = Robust.Shared.Log.LogLevel;
|
|
using MSLogLevel = Microsoft.Extensions.Logging.LogLevel;
|
|
|
|
namespace Content.Server.Database
|
|
{
|
|
public interface IServerDbManager
|
|
{
|
|
void Init();
|
|
|
|
void Shutdown();
|
|
|
|
#region Preferences
|
|
Task<PlayerPreferences> InitPrefsAsync(
|
|
NetUserId userId,
|
|
ICharacterProfile defaultProfile,
|
|
CancellationToken cancel);
|
|
|
|
Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index);
|
|
|
|
Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile? profile, int slot);
|
|
|
|
Task SaveAdminOOCColorAsync(NetUserId userId, Color color);
|
|
|
|
// Single method for two operations for transaction.
|
|
Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot);
|
|
Task<PlayerPreferences?> GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel);
|
|
#endregion
|
|
|
|
#region User Ids
|
|
// Username assignment (for guest accounts, so they persist GUID)
|
|
Task AssignUserIdAsync(string name, NetUserId userId);
|
|
Task<NetUserId?> GetAssignedUserIdAsync(string name);
|
|
#endregion
|
|
|
|
#region Bans
|
|
/// <summary>
|
|
/// Looks up a ban by id.
|
|
/// This will return a pardoned ban as well.
|
|
/// </summary>
|
|
/// <param name="id">The ban id to look for.</param>
|
|
/// <returns>The ban with the given id or null if none exist.</returns>
|
|
Task<ServerBanDef?> GetServerBanAsync(int id);
|
|
|
|
/// <summary>
|
|
/// Looks up an user's most recent received un-pardoned ban.
|
|
/// This will NOT return a pardoned ban.
|
|
/// One of <see cref="address"/> or <see cref="userId"/> need to not be null.
|
|
/// </summary>
|
|
/// <param name="address">The ip address of the user.</param>
|
|
/// <param name="userId">The id of the user.</param>
|
|
/// <param name="hwId">The legacy HWID of the user.</param>
|
|
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
|
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
|
|
Task<ServerBanDef?> GetServerBanAsync(
|
|
IPAddress? address,
|
|
NetUserId? userId,
|
|
ImmutableArray<byte>? hwId,
|
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds);
|
|
|
|
/// <summary>
|
|
/// Looks up an user's ban history.
|
|
/// One of <see cref="address"/> or <see cref="userId"/> need to not be null.
|
|
/// </summary>
|
|
/// <param name="address">The ip address of the user.</param>
|
|
/// <param name="userId">The id of the user.</param>
|
|
/// <param name="hwId">The legacy HWId of the user.</param>
|
|
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
|
/// <param name="includeUnbanned">If true, bans that have been expired or pardoned are also included.</param>
|
|
/// <returns>The user's ban history.</returns>
|
|
Task<List<ServerBanDef>> GetServerBansAsync(
|
|
IPAddress? address,
|
|
NetUserId? userId,
|
|
ImmutableArray<byte>? hwId,
|
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
bool includeUnbanned=true);
|
|
|
|
Task AddServerBanAsync(ServerBanDef serverBan);
|
|
Task AddServerUnbanAsync(ServerUnbanDef serverBan);
|
|
|
|
public Task EditServerBan(
|
|
int id,
|
|
string reason,
|
|
NoteSeverity severity,
|
|
DateTimeOffset? expiration,
|
|
Guid editedBy,
|
|
DateTimeOffset editedAt);
|
|
|
|
/// <summary>
|
|
/// Update ban exemption information for a player.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Database rows are automatically created and removed when appropriate.
|
|
/// </remarks>
|
|
/// <param name="userId">The user to update</param>
|
|
/// <param name="flags">The new ban exemption flags.</param>
|
|
Task UpdateBanExemption(NetUserId userId, ServerBanExemptFlags flags);
|
|
|
|
/// <summary>
|
|
/// Get current ban exemption flags for a user
|
|
/// </summary>
|
|
/// <returns><see cref="ServerBanExemptFlags.None"/> if the user is not exempt from any bans.</returns>
|
|
Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId, CancellationToken cancel = default);
|
|
|
|
#endregion
|
|
|
|
#region Role Bans
|
|
/// <summary>
|
|
/// Looks up a role ban by id.
|
|
/// This will return a pardoned role ban as well.
|
|
/// </summary>
|
|
/// <param name="id">The role ban id to look for.</param>
|
|
/// <returns>The role ban with the given id or null if none exist.</returns>
|
|
Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id);
|
|
|
|
/// <summary>
|
|
/// Looks up an user's role ban history.
|
|
/// This will return pardoned role bans based on the <see cref="includeUnbanned"/> bool.
|
|
/// Requires one of <see cref="address"/>, <see cref="userId"/>, or <see cref="hwId"/> to not be null.
|
|
/// </summary>
|
|
/// <param name="address">The IP address of the user.</param>
|
|
/// <param name="userId">The NetUserId of the user.</param>
|
|
/// <param name="hwId">The Hardware Id of the user.</param>
|
|
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
|
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
|
|
/// <returns>The user's role ban history.</returns>
|
|
Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
|
|
IPAddress? address,
|
|
NetUserId? userId,
|
|
ImmutableArray<byte>? hwId,
|
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
bool includeUnbanned = true);
|
|
|
|
Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan);
|
|
Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverBan);
|
|
|
|
public Task EditServerRoleBan(
|
|
int id,
|
|
string reason,
|
|
NoteSeverity severity,
|
|
DateTimeOffset? expiration,
|
|
Guid editedBy,
|
|
DateTimeOffset editedAt);
|
|
#endregion
|
|
|
|
#region Playtime
|
|
|
|
/// <summary>
|
|
/// Look up a player's role timers.
|
|
/// </summary>
|
|
/// <param name="player">The player to get the role timer information from.</param>
|
|
/// <param name="cancel"></param>
|
|
/// <returns>All role timers belonging to the player.</returns>
|
|
Task<List<PlayTime>> GetPlayTimes(Guid player, CancellationToken cancel = default);
|
|
|
|
/// <summary>
|
|
/// Update play time information in bulk.
|
|
/// </summary>
|
|
/// <param name="updates">The list of all updates to apply to the database.</param>
|
|
Task UpdatePlayTimes(IReadOnlyCollection<PlayTimeUpdate> updates);
|
|
|
|
#endregion
|
|
|
|
#region Player Records
|
|
Task UpdatePlayerRecordAsync(
|
|
NetUserId userId,
|
|
string userName,
|
|
IPAddress address,
|
|
ImmutableTypedHwid? hwId);
|
|
Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel = default);
|
|
Task<PlayerRecord?> GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel = default);
|
|
#endregion
|
|
|
|
#region Connection Logs
|
|
/// <returns>ID of newly inserted connection log row.</returns>
|
|
Task<int> AddConnectionLogAsync(
|
|
NetUserId userId,
|
|
string userName,
|
|
IPAddress address,
|
|
ImmutableTypedHwid? hwId,
|
|
float trust,
|
|
ConnectionDenyReason? denied,
|
|
int serverId);
|
|
|
|
Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans);
|
|
|
|
#endregion
|
|
|
|
#region Admin Ranks
|
|
Task<Admin?> GetAdminDataForAsync(NetUserId userId, CancellationToken cancel = default);
|
|
Task<AdminRank?> GetAdminRankAsync(int id, CancellationToken cancel = default);
|
|
|
|
Task<((Admin, string? lastUserName)[] admins, AdminRank[])> GetAllAdminAndRanksAsync(
|
|
CancellationToken cancel = default);
|
|
|
|
Task RemoveAdminAsync(NetUserId userId, CancellationToken cancel = default);
|
|
Task AddAdminAsync(Admin admin, CancellationToken cancel = default);
|
|
Task UpdateAdminAsync(Admin admin, CancellationToken cancel = default);
|
|
|
|
/// <summary>
|
|
/// Update whether an admin has voluntarily deadminned.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This does nothing if the player is not an admin.
|
|
/// </remarks>
|
|
/// <param name="userId">The user ID of the admin.</param>
|
|
/// <param name="deadminned">Whether the admin is deadminned or not.</param>
|
|
Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel = default);
|
|
|
|
Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default);
|
|
Task AddAdminRankAsync(AdminRank rank, CancellationToken cancel = default);
|
|
Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel = default);
|
|
#endregion
|
|
|
|
#region Rounds
|
|
|
|
Task<int> AddNewRound(Server server, params Guid[] playerIds);
|
|
Task<Round> GetRound(int id);
|
|
Task AddRoundPlayers(int id, params Guid[] playerIds);
|
|
|
|
#endregion
|
|
|
|
#region Admin Logs
|
|
|
|
Task<Server> AddOrGetServer(string serverName);
|
|
Task AddAdminLogs(List<AdminLog> logs);
|
|
IAsyncEnumerable<string> GetAdminLogMessages(LogFilter? filter = null);
|
|
IAsyncEnumerable<SharedAdminLog> GetAdminLogs(LogFilter? filter = null);
|
|
IAsyncEnumerable<JsonDocument> GetAdminLogsJson(LogFilter? filter = null);
|
|
Task<int> CountAdminLogs(int round);
|
|
|
|
#endregion
|
|
|
|
#region Whitelist
|
|
|
|
Task<bool> GetWhitelistStatusAsync(NetUserId player);
|
|
|
|
Task AddToWhitelistAsync(NetUserId player);
|
|
|
|
Task RemoveFromWhitelistAsync(NetUserId player);
|
|
|
|
#endregion
|
|
|
|
#region Blacklist
|
|
|
|
Task<bool> GetBlacklistStatusAsync(NetUserId player);
|
|
|
|
Task AddToBlacklistAsync(NetUserId player);
|
|
|
|
Task RemoveFromBlacklistAsync(NetUserId player);
|
|
|
|
#endregion
|
|
|
|
#region Uploaded Resources Logs
|
|
|
|
Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data);
|
|
|
|
Task PurgeUploadedResourceLogAsync(int days);
|
|
|
|
#endregion
|
|
|
|
#region Rules
|
|
|
|
Task<DateTimeOffset?> GetLastReadRules(NetUserId player);
|
|
Task SetLastReadRules(NetUserId player, DateTimeOffset? time);
|
|
|
|
#endregion
|
|
|
|
#region Admin Notes
|
|
|
|
Task<int> AddAdminNote(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, NoteSeverity severity, bool secret, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime);
|
|
Task<int> AddAdminWatchlist(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime);
|
|
Task<int> AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime);
|
|
Task<AdminNoteRecord?> GetAdminNote(int id);
|
|
Task<AdminWatchlistRecord?> GetAdminWatchlist(int id);
|
|
Task<AdminMessageRecord?> GetAdminMessage(int id);
|
|
Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id);
|
|
Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id);
|
|
Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player);
|
|
Task<List<IAdminRemarksRecord>> GetVisibleAdminNotes(Guid player);
|
|
Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player);
|
|
Task<List<AdminMessageRecord>> GetMessages(Guid player);
|
|
Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime);
|
|
Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime);
|
|
Task EditAdminMessage(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime);
|
|
Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
|
Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
|
Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
|
Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
|
Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
|
|
|
/// <summary>
|
|
/// Mark an admin message as being seen by the target player.
|
|
/// </summary>
|
|
/// <param name="id">The database ID of the admin message.</param>
|
|
/// <param name="dismissedToo">
|
|
/// If true, the message is "permanently dismissed" and will not be shown to the player again when they join.
|
|
/// </param>
|
|
Task MarkMessageAsSeen(int id, bool dismissedToo);
|
|
|
|
#endregion
|
|
|
|
#region Job Whitelists
|
|
|
|
Task AddJobWhitelist(Guid player, ProtoId<JobPrototype> job);
|
|
|
|
|
|
Task<List<string>> GetJobWhitelists(Guid player, CancellationToken cancel = default);
|
|
Task<bool> IsJobWhitelisted(Guid player, ProtoId<JobPrototype> job);
|
|
|
|
Task<bool> RemoveJobWhitelist(Guid player, ProtoId<JobPrototype> job);
|
|
|
|
#endregion
|
|
|
|
#region IPintel
|
|
|
|
Task<bool> UpsertIPIntelCache(DateTime time, IPAddress ip, float score);
|
|
Task<IPIntelCache?> GetIPIntelCache(IPAddress ip);
|
|
Task<bool> CleanIPIntelCache(TimeSpan range);
|
|
|
|
#endregion
|
|
|
|
#region DB Notifications
|
|
|
|
void SubscribeToNotifications(Action<DatabaseNotification> handler);
|
|
|
|
/// <summary>
|
|
/// Inject a notification as if it was created by the database. This is intended for testing.
|
|
/// </summary>
|
|
/// <param name="notification">The notification to trigger</param>
|
|
void InjectTestNotification(DatabaseNotification notification);
|
|
|
|
/// <summary>
|
|
/// Send a notification to all other servers connected to the same database.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The local server will receive the sent notification itself again.
|
|
/// </remarks>
|
|
/// <param name="notification">The notification to send.</param>
|
|
Task SendNotification(DatabaseNotification notification);
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a notification sent between servers via the database layer.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Database notifications are a simple system to broadcast messages to an entire server group
|
|
/// backed by the same database. For example, this is used to notify all servers of new ban records.
|
|
/// </para>
|
|
/// <para>
|
|
/// They are currently implemented by the PostgreSQL <c>NOTIFY</c> and <c>LISTEN</c> commands.
|
|
/// </para>
|
|
/// </remarks>
|
|
public struct DatabaseNotification
|
|
{
|
|
/// <summary>
|
|
/// The channel for the notification. This can be used to differentiate notifications for different purposes.
|
|
/// </summary>
|
|
public required string Channel { get; set; }
|
|
|
|
/// <summary>
|
|
/// The actual contents of the notification. Optional.
|
|
/// </summary>
|
|
public string? Payload { get; set; }
|
|
}
|
|
|
|
public sealed class ServerDbManager : IServerDbManager
|
|
{
|
|
public static readonly Counter DbReadOpsMetric = Metrics.CreateCounter(
|
|
"db_read_ops",
|
|
"Amount of read operations processed by the database manager.");
|
|
|
|
public static readonly Counter DbWriteOpsMetric = Metrics.CreateCounter(
|
|
"db_write_ops",
|
|
"Amount of write operations processed by the database manager.");
|
|
|
|
public static readonly Gauge DbActiveOps = Metrics.CreateGauge(
|
|
"db_executing_ops",
|
|
"Amount of active database operations. Note that some operations may be waiting for a database connection.");
|
|
|
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
[Dependency] private readonly IResourceManager _res = default!;
|
|
[Dependency] private readonly ILogManager _logMgr = default!;
|
|
|
|
private ServerDbBase _db = default!;
|
|
private LoggingProvider _msLogProvider = default!;
|
|
private ILoggerFactory _msLoggerFactory = default!;
|
|
|
|
private bool _synchronous;
|
|
// When running in integration tests, we'll use a single in-memory SQLite database connection.
|
|
// This is that connection, close it when we shut down.
|
|
private SqliteConnection? _sqliteInMemoryConnection;
|
|
|
|
private readonly List<Action<DatabaseNotification>> _notificationHandlers = [];
|
|
|
|
public void Init()
|
|
{
|
|
_msLogProvider = new LoggingProvider(_logMgr);
|
|
_msLoggerFactory = LoggerFactory.Create(builder =>
|
|
{
|
|
builder.AddProvider(_msLogProvider);
|
|
});
|
|
|
|
_synchronous = _cfg.GetCVar(CCVars.DatabaseSynchronous);
|
|
|
|
var engine = _cfg.GetCVar(CCVars.DatabaseEngine).ToLower();
|
|
var opsLog = _logMgr.GetSawmill("db.op");
|
|
var notifyLog = _logMgr.GetSawmill("db.notify");
|
|
switch (engine)
|
|
{
|
|
case "sqlite":
|
|
SetupSqlite(out var contextFunc, out var inMemory);
|
|
_db = new ServerDbSqlite(contextFunc, inMemory, _cfg, _synchronous, opsLog);
|
|
break;
|
|
case "postgres":
|
|
var (pgOptions, conString) = CreatePostgresOptions();
|
|
_db = new ServerDbPostgres(pgOptions, conString, _cfg, opsLog, notifyLog);
|
|
break;
|
|
default:
|
|
throw new InvalidDataException($"Unknown database engine {engine}.");
|
|
}
|
|
|
|
_db.OnNotificationReceived += HandleDatabaseNotification;
|
|
}
|
|
|
|
public void Shutdown()
|
|
{
|
|
_db.OnNotificationReceived -= HandleDatabaseNotification;
|
|
|
|
_sqliteInMemoryConnection?.Dispose();
|
|
_db.Shutdown();
|
|
}
|
|
|
|
public Task<PlayerPreferences> InitPrefsAsync(
|
|
NetUserId userId,
|
|
ICharacterProfile defaultProfile,
|
|
CancellationToken cancel)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.InitPrefsAsync(userId, defaultProfile));
|
|
}
|
|
|
|
public Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.SaveSelectedCharacterIndexAsync(userId, index));
|
|
}
|
|
|
|
public Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile? profile, int slot)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.SaveCharacterSlotAsync(userId, profile, slot));
|
|
}
|
|
|
|
public Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.DeleteSlotAndSetSelectedIndex(userId, deleteSlot, newSlot));
|
|
}
|
|
|
|
public Task SaveAdminOOCColorAsync(NetUserId userId, Color color)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.SaveAdminOOCColorAsync(userId, color));
|
|
}
|
|
|
|
public Task<PlayerPreferences?> GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetPlayerPreferencesAsync(userId, cancel));
|
|
}
|
|
|
|
public Task AssignUserIdAsync(string name, NetUserId userId)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AssignUserIdAsync(name, userId));
|
|
}
|
|
|
|
public Task<NetUserId?> GetAssignedUserIdAsync(string name)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAssignedUserIdAsync(name));
|
|
}
|
|
|
|
public Task<ServerBanDef?> GetServerBanAsync(int id)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetServerBanAsync(id));
|
|
}
|
|
|
|
public Task<ServerBanDef?> GetServerBanAsync(
|
|
IPAddress? address,
|
|
NetUserId? userId,
|
|
ImmutableArray<byte>? hwId,
|
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId, modernHWIds));
|
|
}
|
|
|
|
public Task<List<ServerBanDef>> GetServerBansAsync(
|
|
IPAddress? address,
|
|
NetUserId? userId,
|
|
ImmutableArray<byte>? hwId,
|
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
bool includeUnbanned=true)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
|
|
}
|
|
|
|
public Task AddServerBanAsync(ServerBanDef serverBan)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddServerBanAsync(serverBan));
|
|
}
|
|
|
|
public Task AddServerUnbanAsync(ServerUnbanDef serverUnban)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddServerUnbanAsync(serverUnban));
|
|
}
|
|
|
|
public Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.EditServerBan(id, reason, severity, expiration, editedBy, editedAt));
|
|
}
|
|
|
|
public Task UpdateBanExemption(NetUserId userId, ServerBanExemptFlags flags)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.UpdateBanExemption(userId, flags));
|
|
}
|
|
|
|
public Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId, CancellationToken cancel = default)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetBanExemption(userId, cancel));
|
|
}
|
|
|
|
#region Role Ban
|
|
public Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetServerRoleBanAsync(id));
|
|
}
|
|
|
|
public Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
|
|
IPAddress? address,
|
|
NetUserId? userId,
|
|
ImmutableArray<byte>? hwId,
|
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
bool includeUnbanned = true)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
|
|
}
|
|
|
|
public Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddServerRoleBanAsync(serverRoleBan));
|
|
}
|
|
|
|
public Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddServerRoleUnbanAsync(serverRoleUnban));
|
|
}
|
|
|
|
public Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.EditServerRoleBan(id, reason, severity, expiration, editedBy, editedAt));
|
|
}
|
|
#endregion
|
|
|
|
#region Playtime
|
|
|
|
public Task<List<PlayTime>> GetPlayTimes(Guid player, CancellationToken cancel)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetPlayTimes(player, cancel));
|
|
}
|
|
|
|
public Task UpdatePlayTimes(IReadOnlyCollection<PlayTimeUpdate> updates)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.UpdatePlayTimes(updates));
|
|
}
|
|
|
|
#endregion
|
|
|
|
public Task UpdatePlayerRecordAsync(
|
|
NetUserId userId,
|
|
string userName,
|
|
IPAddress address,
|
|
ImmutableTypedHwid? hwId)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.UpdatePlayerRecord(userId, userName, address, hwId));
|
|
}
|
|
|
|
public Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel = default)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetPlayerRecordByUserName(userName, cancel));
|
|
}
|
|
|
|
public Task<PlayerRecord?> GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel = default)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetPlayerRecordByUserId(userId, cancel));
|
|
}
|
|
|
|
public Task<int> AddConnectionLogAsync(
|
|
NetUserId userId,
|
|
string userName,
|
|
IPAddress address,
|
|
ImmutableTypedHwid? hwId,
|
|
float trust,
|
|
ConnectionDenyReason? denied,
|
|
int serverId)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, trust, denied, serverId));
|
|
}
|
|
|
|
public Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddServerBanHitsAsync(connection, bans));
|
|
}
|
|
|
|
public Task<Admin?> GetAdminDataForAsync(NetUserId userId, CancellationToken cancel = default)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAdminDataForAsync(userId, cancel));
|
|
}
|
|
|
|
public Task<AdminRank?> GetAdminRankAsync(int id, CancellationToken cancel = default)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAdminRankDataForAsync(id, cancel));
|
|
}
|
|
|
|
public Task<((Admin, string? lastUserName)[] admins, AdminRank[])> GetAllAdminAndRanksAsync(
|
|
CancellationToken cancel = default)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAllAdminAndRanksAsync(cancel));
|
|
}
|
|
|
|
public Task RemoveAdminAsync(NetUserId userId, CancellationToken cancel = default)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.RemoveAdminAsync(userId, cancel));
|
|
}
|
|
|
|
public Task AddAdminAsync(Admin admin, CancellationToken cancel = default)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddAdminAsync(admin, cancel));
|
|
}
|
|
|
|
public Task UpdateAdminAsync(Admin admin, CancellationToken cancel = default)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.UpdateAdminAsync(admin, cancel));
|
|
}
|
|
|
|
public Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel = default)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.UpdateAdminDeadminnedAsync(userId, deadminned, cancel));
|
|
}
|
|
|
|
public Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.RemoveAdminRankAsync(rankId, cancel));
|
|
}
|
|
|
|
public Task AddAdminRankAsync(AdminRank rank, CancellationToken cancel = default)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddAdminRankAsync(rank, cancel));
|
|
}
|
|
|
|
public Task<int> AddNewRound(Server server, params Guid[] playerIds)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddNewRound(server, playerIds));
|
|
}
|
|
|
|
public Task<Round> GetRound(int id)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetRound(id));
|
|
}
|
|
|
|
public Task AddRoundPlayers(int id, params Guid[] playerIds)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddRoundPlayers(id, playerIds));
|
|
}
|
|
|
|
public Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel = default)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.UpdateAdminRankAsync(rank, cancel));
|
|
}
|
|
|
|
public async Task<Server> AddOrGetServer(string serverName)
|
|
{
|
|
var (server, existed) = await RunDbCommand(() => _db.AddOrGetServer(serverName));
|
|
if (existed)
|
|
DbReadOpsMetric.Inc();
|
|
else
|
|
DbWriteOpsMetric.Inc();
|
|
|
|
return server;
|
|
}
|
|
|
|
public Task AddAdminLogs(List<AdminLog> logs)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddAdminLogs(logs));
|
|
}
|
|
|
|
public IAsyncEnumerable<string> GetAdminLogMessages(LogFilter? filter = null)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAdminLogMessages(filter));
|
|
}
|
|
|
|
public IAsyncEnumerable<SharedAdminLog> GetAdminLogs(LogFilter? filter = null)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAdminLogs(filter));
|
|
}
|
|
|
|
public IAsyncEnumerable<JsonDocument> GetAdminLogsJson(LogFilter? filter = null)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAdminLogsJson(filter));
|
|
}
|
|
|
|
public Task<int> CountAdminLogs(int round)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.CountAdminLogs(round));
|
|
}
|
|
|
|
public Task<bool> GetWhitelistStatusAsync(NetUserId player)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetWhitelistStatusAsync(player));
|
|
}
|
|
|
|
public Task AddToWhitelistAsync(NetUserId player)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddToWhitelistAsync(player));
|
|
}
|
|
|
|
public Task RemoveFromWhitelistAsync(NetUserId player)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.RemoveFromWhitelistAsync(player));
|
|
}
|
|
|
|
public Task<bool> GetBlacklistStatusAsync(NetUserId player)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetBlacklistStatusAsync(player));
|
|
}
|
|
|
|
public Task AddToBlacklistAsync(NetUserId player)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddToBlacklistAsync(player));
|
|
}
|
|
|
|
public Task RemoveFromBlacklistAsync(NetUserId player)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.RemoveFromBlacklistAsync(player));
|
|
}
|
|
|
|
public Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddUploadedResourceLogAsync(user, date, path, data));
|
|
}
|
|
|
|
public Task PurgeUploadedResourceLogAsync(int days)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.PurgeUploadedResourceLogAsync(days));
|
|
}
|
|
|
|
public Task<DateTimeOffset?> GetLastReadRules(NetUserId player)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetLastReadRules(player));
|
|
}
|
|
|
|
public Task SetLastReadRules(NetUserId player, DateTimeOffset? time)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.SetLastReadRules(player, time));
|
|
}
|
|
|
|
public Task<int> AddAdminNote(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, NoteSeverity severity, bool secret, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
var note = new AdminNote
|
|
{
|
|
RoundId = roundId,
|
|
CreatedById = createdBy,
|
|
LastEditedById = createdBy,
|
|
PlayerUserId = player,
|
|
PlaytimeAtNote = playtimeAtNote,
|
|
Message = message,
|
|
Severity = severity,
|
|
Secret = secret,
|
|
CreatedAt = createdAt.UtcDateTime,
|
|
LastEditedAt = createdAt.UtcDateTime,
|
|
ExpirationTime = expiryTime?.UtcDateTime
|
|
};
|
|
|
|
return RunDbCommand(() => _db.AddAdminNote(note));
|
|
}
|
|
|
|
public Task<int> AddAdminWatchlist(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
var note = new AdminWatchlist
|
|
{
|
|
RoundId = roundId,
|
|
CreatedById = createdBy,
|
|
LastEditedById = createdBy,
|
|
PlayerUserId = player,
|
|
PlaytimeAtNote = playtimeAtNote,
|
|
Message = message,
|
|
CreatedAt = createdAt.UtcDateTime,
|
|
LastEditedAt = createdAt.UtcDateTime,
|
|
ExpirationTime = expiryTime?.UtcDateTime
|
|
};
|
|
|
|
return RunDbCommand(() => _db.AddAdminWatchlist(note));
|
|
}
|
|
|
|
public Task<int> AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
var note = new AdminMessage
|
|
{
|
|
RoundId = roundId,
|
|
CreatedById = createdBy,
|
|
LastEditedById = createdBy,
|
|
PlayerUserId = player,
|
|
PlaytimeAtNote = playtimeAtNote,
|
|
Message = message,
|
|
CreatedAt = createdAt.UtcDateTime,
|
|
LastEditedAt = createdAt.UtcDateTime,
|
|
ExpirationTime = expiryTime?.UtcDateTime
|
|
};
|
|
|
|
return RunDbCommand(() => _db.AddAdminMessage(note));
|
|
}
|
|
|
|
public Task<AdminNoteRecord?> GetAdminNote(int id)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAdminNote(id));
|
|
}
|
|
public Task<AdminWatchlistRecord?> GetAdminWatchlist(int id)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAdminWatchlist(id));
|
|
}
|
|
public Task<AdminMessageRecord?> GetAdminMessage(int id)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAdminMessage(id));
|
|
}
|
|
|
|
public Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetServerBanAsNoteAsync(id));
|
|
}
|
|
|
|
public Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id));
|
|
}
|
|
|
|
public Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetAllAdminRemarks(player));
|
|
}
|
|
|
|
public Task<List<IAdminRemarksRecord>> GetVisibleAdminNotes(Guid player)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetVisibleAdminRemarks(player));
|
|
}
|
|
|
|
public Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetActiveWatchlists(player));
|
|
}
|
|
|
|
public Task<List<AdminMessageRecord>> GetMessages(Guid player)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetMessages(player));
|
|
}
|
|
public Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.EditAdminNote(id, message, severity, secret, editedBy, editedAt, expiryTime));
|
|
}
|
|
|
|
public Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.EditAdminWatchlist(id, message, editedBy, editedAt, expiryTime));
|
|
}
|
|
|
|
public Task EditAdminMessage(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.EditAdminMessage(id, message, editedBy, editedAt, expiryTime));
|
|
}
|
|
|
|
public Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.DeleteAdminNote(id, deletedBy, deletedAt));
|
|
}
|
|
|
|
public Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.DeleteAdminWatchlist(id, deletedBy, deletedAt));
|
|
}
|
|
|
|
public Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.DeleteAdminMessage(id, deletedBy, deletedAt));
|
|
}
|
|
|
|
public Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.HideServerBanFromNotes(id, deletedBy, deletedAt));
|
|
}
|
|
|
|
public Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt));
|
|
}
|
|
|
|
public Task MarkMessageAsSeen(int id, bool dismissedToo)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.MarkMessageAsSeen(id, dismissedToo));
|
|
}
|
|
|
|
public Task AddJobWhitelist(Guid player, ProtoId<JobPrototype> job)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.AddJobWhitelist(player, job));
|
|
}
|
|
|
|
public Task<List<string>> GetJobWhitelists(Guid player, CancellationToken cancel = default)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.GetJobWhitelists(player, cancel));
|
|
}
|
|
|
|
public Task<bool> IsJobWhitelisted(Guid player, ProtoId<JobPrototype> job)
|
|
{
|
|
DbReadOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.IsJobWhitelisted(player, job));
|
|
}
|
|
|
|
public Task<bool> RemoveJobWhitelist(Guid player, ProtoId<JobPrototype> job)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.RemoveJobWhitelist(player, job));
|
|
}
|
|
|
|
public Task<bool> UpsertIPIntelCache(DateTime time, IPAddress ip, float score)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.UpsertIPIntelCache(time, ip, score));
|
|
}
|
|
|
|
public Task<IPIntelCache?> GetIPIntelCache(IPAddress ip)
|
|
{
|
|
return RunDbCommand(() => _db.GetIPIntelCache(ip));
|
|
}
|
|
|
|
public Task<bool> CleanIPIntelCache(TimeSpan range)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.CleanIPIntelCache(range));
|
|
}
|
|
|
|
public void SubscribeToNotifications(Action<DatabaseNotification> handler)
|
|
{
|
|
lock (_notificationHandlers)
|
|
{
|
|
_notificationHandlers.Add(handler);
|
|
}
|
|
}
|
|
|
|
public void InjectTestNotification(DatabaseNotification notification)
|
|
{
|
|
HandleDatabaseNotification(notification);
|
|
}
|
|
|
|
public Task SendNotification(DatabaseNotification notification)
|
|
{
|
|
DbWriteOpsMetric.Inc();
|
|
return RunDbCommand(() => _db.SendNotification(notification));
|
|
}
|
|
|
|
private async void HandleDatabaseNotification(DatabaseNotification notification)
|
|
{
|
|
lock (_notificationHandlers)
|
|
{
|
|
foreach (var handler in _notificationHandlers)
|
|
{
|
|
handler(notification);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wrapper functions to run DB commands from the thread pool.
|
|
// This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread.
|
|
// For SQLite, this will also enable read parallelization (within limits).
|
|
//
|
|
// If we're configured to be synchronous (for integration tests) we shouldn't thread pool it,
|
|
// as that would make things very random and undeterministic.
|
|
// That only works on SQLite though, since SQLite is internally synchronous anyways.
|
|
|
|
private async Task<T> RunDbCommand<T>(Func<Task<T>> command)
|
|
{
|
|
using var _ = DbActiveOps.TrackInProgress();
|
|
|
|
if (_synchronous)
|
|
return await RunDbCommandCoreSync(command);
|
|
|
|
return await Task.Run(command);
|
|
}
|
|
|
|
private async Task RunDbCommand(Func<Task> command)
|
|
{
|
|
using var _ = DbActiveOps.TrackInProgress();
|
|
|
|
if (_synchronous)
|
|
{
|
|
await RunDbCommandCoreSync(command);
|
|
return;
|
|
}
|
|
|
|
await Task.Run(command);
|
|
}
|
|
|
|
private static T RunDbCommandCoreSync<T>(Func<T> command) where T : IAsyncResult
|
|
{
|
|
var task = command();
|
|
if (!task.IsCompleted)
|
|
{
|
|
// We can't just do BlockWaitOnTask here, because that could cause deadlocks.
|
|
// This flag is only intended for integration tests. If we trip this, it's a bug.
|
|
throw new InvalidOperationException(
|
|
"Database task is running asynchronously. " +
|
|
"This should be impossible when the database is set to synchronous.");
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
private IAsyncEnumerable<T> RunDbCommand<T>(Func<IAsyncEnumerable<T>> command)
|
|
{
|
|
var enumerable = command();
|
|
if (_synchronous)
|
|
return new SyncAsyncEnumerable<T>(enumerable);
|
|
|
|
return enumerable;
|
|
}
|
|
|
|
private (DbContextOptions<PostgresServerDbContext> options, string connectionString) CreatePostgresOptions()
|
|
{
|
|
var host = _cfg.GetCVar(CCVars.DatabasePgHost);
|
|
var port = _cfg.GetCVar(CCVars.DatabasePgPort);
|
|
var db = _cfg.GetCVar(CCVars.DatabasePgDatabase);
|
|
var user = _cfg.GetCVar(CCVars.DatabasePgUsername);
|
|
var pass = _cfg.GetCVar(CCVars.DatabasePgPassword);
|
|
|
|
var builder = new DbContextOptionsBuilder<PostgresServerDbContext>();
|
|
var connectionString = new NpgsqlConnectionStringBuilder
|
|
{
|
|
Host = host,
|
|
Port = port,
|
|
Database = db,
|
|
Username = user,
|
|
Password = pass
|
|
}.ConnectionString;
|
|
|
|
Logger.DebugS("db.manager", $"Using Postgres \"{host}:{port}/{db}\"");
|
|
|
|
builder.UseNpgsql(connectionString);
|
|
SetupLogging(builder);
|
|
return (builder.Options, connectionString);
|
|
}
|
|
|
|
private void SetupSqlite(out Func<DbContextOptions<SqliteServerDbContext>> contextFunc, out bool inMemory)
|
|
{
|
|
#if USE_SYSTEM_SQLITE
|
|
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
|
|
#endif
|
|
|
|
// Can't re-use the SqliteConnection across multiple threads, so we have to make it every time.
|
|
|
|
Func<SqliteConnection> getConnection;
|
|
|
|
var configPreferencesDbPath = _cfg.GetCVar(CCVars.DatabaseSqliteDbPath);
|
|
inMemory = _res.UserData.RootDir == null;
|
|
|
|
if (!inMemory)
|
|
{
|
|
var finalPreferencesDbPath = Path.Combine(_res.UserData.RootDir!, configPreferencesDbPath);
|
|
Logger.DebugS("db.manager", $"Using SQLite DB \"{finalPreferencesDbPath}\"");
|
|
getConnection = () => new SqliteConnection($"Data Source={finalPreferencesDbPath}");
|
|
}
|
|
else
|
|
{
|
|
Logger.DebugS("db.manager", "Using in-memory SQLite DB");
|
|
_sqliteInMemoryConnection = new SqliteConnection("Data Source=:memory:");
|
|
// When using an in-memory DB we have to open it manually
|
|
// so EFCore doesn't open, close and wipe it every operation.
|
|
_sqliteInMemoryConnection.Open();
|
|
getConnection = () => _sqliteInMemoryConnection;
|
|
}
|
|
|
|
contextFunc = () =>
|
|
{
|
|
var builder = new DbContextOptionsBuilder<SqliteServerDbContext>();
|
|
builder.UseSqlite(getConnection());
|
|
SetupLogging(builder);
|
|
return builder.Options;
|
|
};
|
|
}
|
|
|
|
private void SetupLogging(DbContextOptionsBuilder builder)
|
|
{
|
|
builder.UseLoggerFactory(_msLoggerFactory);
|
|
}
|
|
|
|
private sealed class LoggingProvider : ILoggerProvider
|
|
{
|
|
private readonly ILogManager _logManager;
|
|
|
|
public LoggingProvider(ILogManager logManager)
|
|
{
|
|
_logManager = logManager;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
public ILogger CreateLogger(string categoryName)
|
|
{
|
|
return new MSLogger(_logManager.GetSawmill("db.ef"));
|
|
}
|
|
}
|
|
|
|
private sealed class MSLogger : ILogger
|
|
{
|
|
private readonly ISawmill _sawmill;
|
|
|
|
public MSLogger(ISawmill sawmill)
|
|
{
|
|
_sawmill = sawmill;
|
|
}
|
|
|
|
public void Log<TState>(MSLogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
|
Func<TState, Exception?, string> formatter)
|
|
{
|
|
var lvl = logLevel switch
|
|
{
|
|
MSLogLevel.Trace => LogLevel.Debug,
|
|
MSLogLevel.Debug => LogLevel.Debug,
|
|
// EFCore feels the need to log individual DB commands as "Information" so I'm slapping debug on it.
|
|
MSLogLevel.Information => LogLevel.Debug,
|
|
MSLogLevel.Warning => LogLevel.Warning,
|
|
MSLogLevel.Error => LogLevel.Error,
|
|
MSLogLevel.Critical => LogLevel.Fatal,
|
|
MSLogLevel.None => LogLevel.Debug,
|
|
_ => LogLevel.Debug
|
|
};
|
|
|
|
_sawmill.Log(lvl, formatter(state, exception));
|
|
}
|
|
|
|
public bool IsEnabled(MSLogLevel logLevel)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
|
|
{
|
|
// TODO: this
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed record PlayTimeUpdate(NetUserId User, string Tracker, TimeSpan Time);
|
|
|
|
internal sealed class SyncAsyncEnumerable<T> : IAsyncEnumerable<T>
|
|
{
|
|
private readonly IAsyncEnumerable<T> _enumerable;
|
|
|
|
public SyncAsyncEnumerable(IAsyncEnumerable<T> enumerable)
|
|
{
|
|
_enumerable = enumerable;
|
|
}
|
|
|
|
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
|
{
|
|
return new Enumerator(_enumerable.GetAsyncEnumerator(cancellationToken));
|
|
}
|
|
|
|
private sealed class Enumerator : IAsyncEnumerator<T>
|
|
{
|
|
private readonly IAsyncEnumerator<T> _enumerator;
|
|
|
|
public Enumerator(IAsyncEnumerator<T> enumerator)
|
|
{
|
|
_enumerator = enumerator;
|
|
}
|
|
|
|
public ValueTask DisposeAsync()
|
|
{
|
|
var task = _enumerator.DisposeAsync();
|
|
if (!task.IsCompleted)
|
|
throw new InvalidOperationException("DisposeAsync did not complete synchronously.");
|
|
|
|
return task;
|
|
}
|
|
|
|
public ValueTask<bool> MoveNextAsync()
|
|
{
|
|
var task = _enumerator.MoveNextAsync();
|
|
if (!task.IsCompleted)
|
|
throw new InvalidOperationException("MoveNextAsync did not complete synchronously.");
|
|
|
|
return task;
|
|
}
|
|
|
|
public T Current => _enumerator.Current;
|
|
}
|
|
}
|
|
}
|