From 1f22dfda7ba0290d464d47c4ac089d77c5c90f3b Mon Sep 17 00:00:00 2001 From: TheWaffleJesus <106146578+TheWaffleJesus@users.noreply.github.com> Date: Sun, 8 Sep 2024 09:34:08 +0100 Subject: [PATCH 001/218] changed from tag to material and added icon for capacitor --- .../Construction/Graphs/clothing/medsec_hud.yml | 13 ++----------- .../Recipes/Crafting/Graphs/improvised/potato.yml | 12 ++---------- Resources/Prototypes/Stacks/science_stacks.yml | 1 + 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/clothing/medsec_hud.yml b/Resources/Prototypes/Recipes/Construction/Graphs/clothing/medsec_hud.yml index 03a70cb7fe7..78a27a9d0f3 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/clothing/medsec_hud.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/clothing/medsec_hud.yml @@ -27,17 +27,8 @@ sprite: Objects/Devices/communication.rsi state: walkietalkie doAfter: 5 - - tag: CapacitorStockPart - name: capacitor - icon: - sprite: Objects/Misc/stock_parts.rsi - state: capacitor - doAfter: 5 - - tag: CapacitorStockPart - name: capacitor - icon: - sprite: Objects/Misc/stock_parts.rsi - state: capacitor + - material: Capacitor + amount: 2 doAfter: 5 - node: medsecHud entity: ClothingEyesHudMedSec diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/potato.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/potato.yml index e3f972cfda4..f03670b673c 100644 --- a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/potato.yml +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/potato.yml @@ -60,15 +60,7 @@ - material: Cable amount: 2 doAfter: 1 - - tag: CapacitorStockPart - name: capacitor - icon: - sprite: Objects/Misc/stock_parts.rsi - state: capacitor - - tag: CapacitorStockPart - name: capacitor - icon: - sprite: Objects/Misc/stock_parts.rsi - state: capacitor + - material: Capacitor + amount: 2 - node: potatoaichip entity: PotatoAIChip \ No newline at end of file diff --git a/Resources/Prototypes/Stacks/science_stacks.yml b/Resources/Prototypes/Stacks/science_stacks.yml index 0d273c324e6..647a5b2a7bf 100644 --- a/Resources/Prototypes/Stacks/science_stacks.yml +++ b/Resources/Prototypes/Stacks/science_stacks.yml @@ -7,6 +7,7 @@ - type: stack id: Capacitor name: capacitor + icon: { sprite: /Textures/Objects/Misc/stock_parts.rsi, state: capacitor } spawn: CapacitorStockPart maxCount: 10 From daf674e37b1fb8dbf9a11b253f25c956ff07c2e1 Mon Sep 17 00:00:00 2001 From: TheWaffleJesus <106146578+TheWaffleJesus@users.noreply.github.com> Date: Sun, 8 Sep 2024 10:01:08 +0100 Subject: [PATCH 002/218] changed capacitor yaml for substation and memory cell --- .../Recipes/Construction/Graphs/tools/logic_gate.yml | 7 ++----- .../Graphs/utilities/wallmount_substation.yml | 8 ++------ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml b/Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml index 37c85e07a37..5a7dfbb3bf6 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml @@ -46,11 +46,8 @@ doAfter: 1 - to: memory_cell steps: - - tag: CapacitorStockPart - icon: - sprite: Objects/Misc/stock_parts.rsi - state: capacitor - name: a capacitor + - material: Capacitor + amount: 1 - material: Cable amount: 2 doAfter: 1 diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/wallmount_substation.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/wallmount_substation.yml index 7e4087b20a2..bd9b2415e26 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/wallmount_substation.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/wallmount_substation.yml @@ -57,12 +57,8 @@ sprite: "Objects/Power/power_cells.rsi" state: "medium" doAfter: 0.5 - - tag: CapacitorStockPart - name: a capacitor - store: capacitor - icon: - sprite: "Objects/Misc/stock_parts.rsi" - state: "capacitor" + - material: Capacitor + amount: 1 doAfter: 0.5 - to: frame completed: From 30018a3ab1a3d36fe62ed0e29a16d1ce724230e6 Mon Sep 17 00:00:00 2001 From: The Canned One Date: Fri, 27 Sep 2024 18:10:51 +0200 Subject: [PATCH 003/218] added Derelict Cyborgs with basic functionality. --- .../interaction-popup-component.ftl | 3 ++ .../Mobs/Cyborgs/base_borg_chassis.yml | 25 +++++++++++ .../Entities/Mobs/Cyborgs/borg_chassis.yml | 39 ++++++++++++++++++ .../Entities/Mobs/Player/silicon.yml | 36 ++++++++++++++++ .../Construction/Graphs/machines/cyborg.yml | 3 ++ .../Mobs/Silicon/chassis.rsi/derelict.png | Bin 0 -> 11838 bytes .../Mobs/Silicon/chassis.rsi/derelict_e.png | Bin 0 -> 5508 bytes .../Mobs/Silicon/chassis.rsi/derelict_e_r.png | Bin 0 -> 5515 bytes .../Mobs/Silicon/chassis.rsi/derelict_l.png | Bin 0 -> 6986 bytes .../Mobs/Silicon/chassis.rsi/meta.json | 14 ++++++- 10 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict.png create mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e.png create mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e_r.png create mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_l.png diff --git a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl index 10773d6de84..65310b67f98 100644 --- a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl +++ b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl @@ -67,6 +67,7 @@ petting-success-janitor-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} d petting-success-medical-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} sterile metal head. petting-success-service-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} dapper looking metal head. petting-success-syndicate-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} menacing metal head. +petting-success-derelict-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} rusty metal head. petting-success-recycler = You pet {THE($target)} on {POSS-ADJ($target)} mildly threatening steel exterior. petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "honk", "honks")} in refusal! @@ -80,6 +81,8 @@ petting-failure-janitor-cyborg = You reach out to pet {THE($target)}, but {SUBJE petting-failure-medical-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy saving lives! petting-failure-service-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy serving others! petting-failure-syndicate-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} treacherous affiliation makes you reconsider. +petting-failure-derelict-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} rusty and jagged exterior makes you reconsider. + ## Rattling fences diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 955ddfd2e3e..2f8a7cf10e8 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -317,3 +317,28 @@ Unsexed: UnisexSiliconSyndicate - type: PointLight color: "#dd200b" + +- type: entity + id: BaseBorgChassisDerelict + parent: [BaseBorgChassis, BaseBorgTransponder] + abstract: true + components: + - type: NpcFactionMember + factions: + - Passive #Might change this + - type: Access + enabled: false + groups: + - AllAccess + - type: AccessReader + access: [["Command"]] #I will probably change this. + - type: SiliconLawProvider + laws: AntimovLawset + - type: IntrinsicRadioTransmitter + channels: + - Binary + - Common + - type: ActiveRadio + channels: + - Binary + - Common diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml index d1d530ae81b..00bf7fc2afa 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml @@ -442,3 +442,42 @@ interactFailureString: petting-failure-syndicate-cyborg interactSuccessSound: path: /Audio/Ambience/Objects/periodic_beep.ogg + + +- type: entity + id: BorgChassisDerelict + parent: BaseBorgChassisDerelict + name: damaged cyborg + components: + - type: Sprite + layers: + - state: derelict + - state: derelict_e_r + map: ["enum.BorgVisualLayers.Light"] + shader: unshaded + visible: false + - state: derelict_l + shader: unshaded + map: ["light"] + visible: false + - type: BorgChassis + maxModules: 5 #The sixth one broke lol. + moduleWhitelist: + tags: + - BorgModuleGeneric + hasMindState: derelict_e + noMindState: derelict_e_r + - type: BorgTransponder + sprite: + sprite: Mobs/Silicon/chassis.rsi + state: derelict + name: damaged cyborg + - type: Construction + node: derelictcyborg #what is this? + - type: Speech + speechVerb: Robotic + - type: InteractionPopup + interactSuccessString: petting-success-derelict-cyborg + interactFailureString: petting-failure-derelict-cyborg + interactSuccessSound: + path: /Audio/Ambience/Objects/periodic_beep.ogg \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 15878a4017d..a6320dc7b3f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -515,3 +515,39 @@ - PlayerBorgSyndicateAssaultGhostRole - PlayerBorgSyndicateAssaultGhostRole # Saboteurs are kinda like cyborg medics, we want less. - PlayerBorgSyndicateSaboteurGhostRole + +- type: entity + id: PlayerBorgDerelict + parent: BorgChassisDerelict + suffix: Battery, Module + components: + - type: ContainerFill + containers: + borg_brain: + - PositronicBrain + borg_module: + - BorgModuleTool + - BorgModuleFireExtinguisher + - BorgModuleGPS + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default + startingItem: PowerCellHigh + - type: RandomMetadata + nameSegments: [names_borg] + +- type: entity + id: PlayerBorgDerelictBattery + parent: BorgChassisDerelict + suffix: Battery + components: + - type: ContainerFill + containers: + borg_brain: + - MMIFilled + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default + startingItem: PowerCellHigh diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml b/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml index 0f012cefc98..3f8a731cbbd 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml @@ -205,3 +205,6 @@ - node: syndicatesaboteur entity: BorgChassisSyndicateSaboteur + + - node: derelictcyborg + entity: BorgChassisDerelict diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict.png new file mode 100644 index 0000000000000000000000000000000000000000..bbf72fc45bed2408b2abeb91d3564f8fa41e6e5a GIT binary patch literal 11838 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 HpFaCP!NMbp literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e.png new file mode 100644 index 0000000000000000000000000000000000000000..17349d74dc8ba9bbf0e434b9e32e634535c3a244 GIT binary patch literal 5508 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 LUFhdl`^x_VBy@7n literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e_r.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e_r.png new file mode 100644 index 0000000000000000000000000000000000000000..3c8cf19acf66f1c662629db09546f8a553a3c658 GIT binary patch literal 5515 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 S?UyLhp|`uATeV9>+J69Pac{i< literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_l.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_l.png new file mode 100644 index 0000000000000000000000000000000000000000..f65fbaebc3c5074e945e94f59b8d90b83ac08bb3 GIT binary patch literal 6986 zcmeHMc{G&m`yW!)kR+r`gKW)eov|B2njs`hVpb+*hMBP>TS{nALfIl)b}2il-XxVQ zm0co{U8qP}eh>Bb*7<$U`JMNi-}k@foSEl&uKRO+?(6#8*L|PoIumV;H`^#6EdT<6 zHd>gQ*Z^0<)i2)$;J1M0^AZFSUGXcpXFZFF+j^E8I)224OS-u_$dna;O zf2H}P5;`Fsb~xkYunO-o=6bEc%$CKmkcbtr!NDt9ZGthjX;0IyqX*u}j?J)-tJ%d2 zHyhUsZtYtU91RI?mX)ZL9q~1bIpG7hn~k`ovvs=OP><@8Pd=)V(V`X;sj&Q6>Wjzu zviY>nua1e{(t@Rhr;>UN(bIU%^P&QDwFen{MMU^ zHbec)cLUqHo>`YJKDKhb0Uzt=^}@zJx4L#b%B9Nowcp(9FY`Iofud(8DH~(MN&SwQ zrYqNYLe|YBNlIA>IY}*XS4WwdX_EB#!jeBc-E}OQO@zig0onCpxJIP;dL#$j;$Q*k zcX#Oot5-!0*t&31HB-%=7(zactoMr2xht^qo-DtVaOXHF3VBTl*(Bza`}tB zh1);9Zzox~1kUDkzd9v5-eiv5vxCw>Q?yGaJV?6sM7Oy}umH-mckszkP`!S0a&yJD zF4vcXhAl7iYR<*u;}wsjZaKP}Z~&i4+~C&ABRHHXYUq)h=+b*?NF3cbtrC+L6@%N; z=;dH4#M4j?gRR_ctv-Dw2? zKH2-4M&6#Y-2ZU@r;aRjOTL!9k|p!gTzCCrI zXTZGKAB>K96Oc!SSMg#aHD&Dle>w-+l5mHztozC z?c6AyGp;wFW_2WnVe-|zW+_8thXmV zh`J(aORmM#-F9DLO^H&7{2 zsdMuL>(N+s&0U4JB2c9(w`E$=aUYMo;3_=HN85Z)SMt^5iA^b@%jZPvZ*Ss$joqgY zxpAv~y|cp6t0Xfk-q%+m?|Lm6hh@S0Yo5yn?;oqx^5rj9yepm%+g^k1-IG+d&hCI| zOq`{(n!>G~lW~_;nxyk%GjCeTK?drz=0h}}`zWsDotNa>wx#DrOiQh-iYS!j{f$JsC@ft4t>K_cHEn=S-O%yUxmU+}%(lAKzIwDZl3L;DyG&$EX~zUj z+bgyv7%jMRu33xnsHVAPwixbd|DYrmDpTZNM8%Q=%~+G`p5Cl*@p+5>ycCp81v~c( zot9+Z&=@B5UwHahd*_GBFoS!3XB>|o&)RoRIIl}xOH*q_<*?X^A++?JQM_OKU6=6& zizu_9tV_C|^opVLoP(ue6#1|`?p?Gje&qf-W&0C)OG(ncTL$d+nGmIL5m~P=x#3}n zZ=#0|4&2XT1U$%=5cXKa;}w;f`1jVFuqoudgSC;fQF@`Y=kZ>jHteZ~J#0}op4jNo zCZY+tR%X3?#^Fe*k(s}6X-bYDyk9lW8^Kl#8^x!o`44YaGAerDN{C*|o2?$7J(gC zBxc_1C=69?lKpzDvhY11bXLF}+;Kd0)qpttUHuLeRFQ ze1XpN=dS79SM+k%g#HJReZxs*rhs>CE>6Y*^lGM5A0 zVA(y!RmKku7-jLp2SrT-`vmJul=GK+t#djyUr^Ij&LlrAL5bRmQwdsB0vEJDIa<%N z@j0b8`*^d>M&tF!O|Ily{;balai{X%cuA0?Ye8WQ(HiH|aO!Vv<3W8odcyKY3N6W>{Onw&>v3-OZAE0dEoq(qG;>s%)mk z-cjF2XpOEZjV^O$v$yUoc(&4{G_z2-cQ8Rev5@`DC#K*%UfNsUbj#7FD9BLc!3{L9oiH;!h&GUHP6_;yibRJrIc7TRX_O zh6=&F_`Kf0=xb=kxB7_kjcVUET(q5>yf{_f3j%RP(6Cr*3oQ2U*H7T3Gc`C?&%D7} zJi^L0M`a7IPLu#O4=-V2%865v_{*54T0ZHv@Ju$t0()_ISgBe<=CflFrdoSya!7?Q z+yLAvj6V&t84k9{Fg+^*d;9v9zwpukFJFb2ZA&H6uyePZ)S1&s)2Af^OcgieQ4#V; z+w-{+&*l!fF3rt7Tu4cu))QWGOGK2Z+4%S&I}EAq&@A9tT%+NtFFu)4_m<#5p@m7=GLp#(%O zG`AYB5hW?H$&4F9wktg@w%f#`+Z-92{Vp%^&fvV_Lxnicb!TPXDPbcXvwSP z^Zn`+@w41B@X9YpAs)Zuft{6i&eQ8cxId;JS1q1^cZW?_JuAdPgLvM1t=rm-!@XPw z^AEeN=z@|rTd?I{)}0I4GZ?=B+nPK%d}81Ns36+&&D6Nrw^0Od0a3l-?r_Ur@*}# z?rQ_9f@6%VE%YEL*mvlf#oCL&p#l!T9-uMEOn>%2U^^O}V#^_{>WS1sX{oEB)Ya59 zfeRA(56GUvVgt3f%8Eq5)KF_9s|$kx(gCC;tX3*Oux1Cc!C+Yw0*A@6V=}$;AgdvO zS3Q3Y^^Ab!C!$u#QG{|mjkeZack z7u}r32IfDyX8OLTY$-n9pT0kO(bhH-7`(PEFa*-~6xakm3VAI~fb07Z$%DXfrvT^2 zkAnT%PWunVK+)9JB2l%}p+vF<0$4$!Iuxy`r42=Et7#)qXaW&QCH&6LW>Pu+1Qx}} z9ncZb3MkMut-wk@mP+||yuSx!bsYf7pa?YdAIa1p@E^&-S5J(;v(<(FFFkbE0KXhD zfZul;aC8ADA^hi2_(QMNL+8Kv`LPcF#TfwVKa>0`egDYyN3MURz`p|jsjfeA{VN6j z75Gne{lCd2@J};EVE}JH{y?h~d(!6|&}MBQTAG=xHC&)&Y+WdD$M0?K$OeIg@2`Hj z5>$n^0Y+Yq19I@N^y*-4xbQg_6Rz5@i(Urf?L8d>soTE#=?{j;p1(MfTssa!qbnU4kMp;J zxAk)2Z93P>NY_O{+uw$T6jl)mJMQsLg$;IRi+?TRet+6JN8-I}D;E%dH%IAlO6^12 z)(gugnRf)`A3-mrREZ0^1>FnXkybU(O(?kH7R0z+7y7hmC9a2?pLx76l#d$(0`rW$ zz*M03J&e%P4=N@~?0g`zYvi7MuY+lnMkIet>Fl84IOKk+oM6471*0ncKvy5ip7&&` zT(s~7c~5#xe_MgW`bC)W3UjC;|BUY93GbWN@AkYx^L|(S?x0BI Date: Fri, 27 Sep 2024 20:11:04 +0200 Subject: [PATCH 004/218] minor changes to the Derelict Cyborg --- .../Entities/Mobs/Cyborgs/base_borg_chassis.yml | 6 +++--- .../Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 2f8a7cf10e8..4c0359b37e6 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -320,7 +320,7 @@ - type: entity id: BaseBorgChassisDerelict - parent: [BaseBorgChassis, BaseBorgTransponder] + parent: BaseBorgChassis abstract: true components: - type: NpcFactionMember @@ -329,11 +329,11 @@ - type: Access enabled: false groups: - - AllAccess + - AllAccess #Randomized access would be fun. AllAccess is the best i can think of right now that does make it too hard to enter the station or. - type: AccessReader access: [["Command"]] #I will probably change this. - type: SiliconLawProvider - laws: AntimovLawset + laws: AntimovLawset #Temporary until i get it randomized. - type: IntrinsicRadioTransmitter channels: - Binary diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml index 00bf7fc2afa..818847f2449 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml @@ -447,7 +447,8 @@ - type: entity id: BorgChassisDerelict parent: BaseBorgChassisDerelict - name: damaged cyborg + name: derelict cyborg + description: A man-machine hybrid that assists in station activity. This one is in a state of great disrepair. components: - type: Sprite layers: @@ -467,13 +468,8 @@ - BorgModuleGeneric hasMindState: derelict_e noMindState: derelict_e_r - - type: BorgTransponder - sprite: - sprite: Mobs/Silicon/chassis.rsi - state: derelict - name: damaged cyborg - type: Construction - node: derelictcyborg #what is this? + node: derelictcyborg - type: Speech speechVerb: Robotic - type: InteractionPopup From c3fa1b45d002ae88d5b21e18c77a286eec2d3d97 Mon Sep 17 00:00:00 2001 From: The Canned One Date: Sat, 28 Sep 2024 10:18:40 +0200 Subject: [PATCH 005/218] Added Derelict Cyborg midround event. --- .../ghost/roles/ghost-role-component.ftl | 4 +++ .../Locale/en-US/silicons/derelict/role.ftl | 4 +++ .../Entities/Markers/Spawners/ghost_roles.yml | 18 ++++++++++++++ .../Entities/Mobs/Player/silicon.yml | 13 ++++++++++ Resources/Prototypes/GameRules/events.yml | 23 ++++++++++++++++++ .../Silicon/chassis.rsi/derelict_icon.png | Bin 0 -> 6429 bytes .../Mobs/Silicon/chassis.rsi/meta.json | 6 ++++- 7 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 Resources/Locale/en-US/silicons/derelict/role.ftl create mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_icon.png diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index 77d2645c4c5..71ab4d37160 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -240,6 +240,10 @@ ghost-role-information-syndicate-cyborg-assault-name = Syndicate Assault Cyborg ghost-role-information-syndicate-cyborg-saboteur-name = Syndicate Saboteur Cyborg ghost-role-information-syndicate-cyborg-description = The Syndicate needs reinforcements. You, a cold silicon killing machine, will help them. + +ghost-role-information-derelict-cyborg-name = Derelict Cyborg +ghost-role-information-derelict-cyborg-description = You were a regular cyborg that got lost in space. After drifting in whichever direction the laws of physics would have it for years, you have drifted close to a Nanotrasen space station... You are bound by silicon laws. Check them upon spawning. + ghost-role-information-security-name = Security ghost-role-information-security-description = You are part of a security task force, but seem to have found yourself in a strange situation... diff --git a/Resources/Locale/en-US/silicons/derelict/role.ftl b/Resources/Locale/en-US/silicons/derelict/role.ftl new file mode 100644 index 00000000000..88e750e6214 --- /dev/null +++ b/Resources/Locale/en-US/silicons/derelict/role.ftl @@ -0,0 +1,4 @@ +derelict-cyborg-round-end-agent-name = derelict cyborg + +derelict-cyborg-role-greeting = + You are a cyborg that has been lost in space for many years that has now drifted close to a space station. You can use your fire extinguisher and GPS to get board the station. Remember to follow your laws. #Greeting is unused for now. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index b694a8cc2f2..18d459cd89e 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -170,3 +170,21 @@ - state: green - sprite: Objects/Weapons/Melee/energykatana.rsi state: icon + +- type: entity + categories: [ HideSpawnMenu, Spawner ] + parent: BaseAntagSpawner + id: SpawnPointGhostDerelictCyborg + components: + - type: GhostRole + name: ghost-role-information-derelict-cyborg-name + description: ghost-role-information-derelict-cyborg-description + rules: ghost-role-information-silicon-rules + raffle: + settings: default + - type: Sprite + sprite: Markers/jobs.rsi + layers: + - state: green + - sprite: Mobs/Silicon/chassis.rsi + state: derelict_icon \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index a6320dc7b3f..ff2b3f9ff2d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -551,3 +551,16 @@ cell_slot: name: power-cell-slot-component-slot-name-default startingItem: PowerCellHigh + +- type: entity + id: PlayerBorgDerelictGhostRole + parent: PlayerBorgDerelictBattery + suffix: Battery, Ghost role + components: + - type: GhostRole + name: ghost-role-information-derelict-cyborg-name + description: ghost-role-information-derelict-cyborg-description + rules: ghost-role-information-silicon-rules + raffle: + settings: default + - type: GhostTakeoverAvailable diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index dc44915f53d..efebc8e2720 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -35,6 +35,7 @@ - id: RevenantSpawn - id: SleeperAgents - id: ZombieOutbreak + - id: DerelictCyborgSpawn - type: entity id: BaseStationEvent @@ -545,3 +546,25 @@ maxOccurrences: 1 # this event has diminishing returns on interesting-ness, so we cap it weight: 5 - type: MobReplacementRule + +- type: entity + parent: BaseGameRule + id: DerelictCyborgSpawn + components: + - type: StationEvent + weight: 2 #Low until it spawns with a random lawset instead of just antimov. + earliestStart: 15 + reoccurrenceDelay: 20 + minimumPlayers: 7 + duration: null + - type: SpaceSpawnRule + spawnDistance: 0 + - type: AntagSpawner + prototype: PlayerBorgDerelict + - type: AntagSelection + agentName: derelict-cyborg-round-end-agent-name + definitions: + - spawnerPrototype: SpawnPointGhostDerelictCyborg + min: 1 + max: 1 + pickPlayer: false \ No newline at end of file diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_icon.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7f0ea2a25560f33773cd72d6184daba55dd4d033 GIT binary patch literal 6429 zcmeHKdpMNa_aDuaYgBS4V@RSgSIkT%ruZRXjA^8Fxn;QZmMbUf^35vOF7);#N z>$5TaiIKv`!KJZ?or{*-T6`(#M$NK#&j((;{{DnsGeU{(#uU8z&PBIfchr6y995bA z1LN_F-j%?Dv)b>T9t+0PCrI(vM#maH{1*Q_CUH<{WMCSVv)8?CD*x$=_~6tiI0wid zTW#BQNp0kD@-fVZ_W?u4l6v&tGB)jg0VcIgp*AlV-qh|BZh(&)^`*OpLcYIq~< zw|xfp46aJ*3uxMzziyxC;kt}<(hW1@gE_SD^|?dh_Ey;_!yA@1)+g(qPoIAkc>Vko zH%}?QCwMG6R%~aIcV3=~xZcy4H=;K#q(=_*H)p>oJexW~)fL1{EZR16$xJk2##G6a zU+Z6F-koH{R+r^EiVku&(Po9bEdVaeOlH$yKA%!IQb-oS*!a?Q@VUR*2yP3VFF+PWZ%PtjP;WKLIQa|vcfWTSrsqh%LFOxs=K0ZHH~;Oa5jXMZ z{`gEtpq~fR`Qkj8Zj9Wk)T~cSUwy(?QDB2B4Tnt&0P~Z^S;-Td{}?(|*clL&caHot zuunr@JQ1d2*~{Fp>vbT`R*nK7h7%Yh{lkZuw;JBOdGELEHvfD8?eB#_z&p32q)yGV zA6!Nl1uDB*;oYiRsS5kO4@Mlj%pwA6i#3nP8Zou$&wAQFS+cqZ9&3T`PS*~%i^Msw ztmv9Dsf)`F8YEQQ-rn}{-O{Era%oTZ=h-(a_#S<=w9_Qzfa9A-$<~SeN&Gs6-+at+ zRkVY>P5EW|@aryo1tsC;tvW%<(Mr;5KcQR)62)tFx0vDbG%Rjw#+=-hza#C+r9=Mr zINVN6nz4~;&3&3fPI!@ANRd;}){<^AxspG`WtsFeD}Qh}!TbdGs~lT$EMI0hAmyid zIT~Lq6m8$adAWRp?tZwYxt@5{*###b;3(#bSN`;!_!Ew(I{p@&X_F(jC9SM7CF$aV zg#8^Mo3kgcIl7YOPX`$Xp*wSzR3V0uIbJM8gUo7^v5;Dgw&ToEdLN=sY4L*W>m9Yu znJPznE*#J+eNf#`DuJ&RS&Zl$dG(u7?RrX2>A*TgZF%EWx=ha)T+f>43;R6=9+uoZ z#PFvLs3``h51MI=-c-?|H6~urnqm6Ix|BC$_jsz$L(yRyX`-!ZtR2s*r%I6X;R)VN zkGVI_TakN@OKa~%tXx24n>G`-^Xf&gKZaV6ckk_o%X)R-Q)C`&49NAXBUXQa89v7QeguHs_JUM!4tO6p{60y0HvH!vARa-4= z1DfZ1YYYYa%HZZODiLtG$8fh@$fDzf&2jNB6-H@kfKpM%vgm^C7@YQ)+qSo*Gv0d! zZ3+(v1CxgvJ6E?bTaFt?e!8b9SJpq3ZCPb7rfbwiUASiDjP!2AV2|eX{ETh15StXM zX&6ju7uVL--PzXm%N7A`4B6Y#s7|%k+VxvJ&&JWUhmmc1wh@<+#*d7=RwX31c?aIF zdD4anY&)1+PLeB{FKwlIUeWZg3QD$4bi_ zkn?OVlC{3SN?#>)Z)Z=Ty4W7`hm5t|?{%)!w9QS`!mYivw>m0~TE^GB9aMK8H-v9R zjcMOqm@M8YX5Vcik1Fr+b*3e??7g+H0y(DN0Uqfmsp+VS*$oZ3&-U8ZF-mqmRb(%> zU4GIq#i-C>YHd51_*#{7mBzdnJFd3ib=NP)UJ`4Qn6k141^OCh86GYHR6p8p03??cn$~1~#$`f!fI5L@x!QwG^JQ~tK z3!}qCfEXPn)R#caVc3B}rhv;Aad}}#2`0ecMTn><6x5IW;$JA=&Fw3EnD8?T5FZ#Z zz{lWBv6#?M%(oUok$oft^4X#PY9XXUUp^QbDC9*5n4o3Oeg}vn4km-0ypJCIBX(|#2^3+G?9#lZsulKG?`4op_yy~nN4Eg zEtpvHHxTOuT&OC6kZ-+`K(Qbw05B(;F^C|VM238TMhDRhB7|oF;)oytU|X;Vq**8y zlj6V=gaS}HxuF0D#PGv7vmFw`DK_rTR21G6`;Wvu1Q4+y185F#!&tm1;Xhq;ZYby} z0wjFmEbta)Sds+^XGX^2@N-_zB6@=YAykVJR2fj~+uzjnJp6N&|h06RbgLZDbYk%A>t@FY5PC*Ub&Boi#2g8fFH z$KtZ1|2J*P^g&wACEbZDgvO7a70t~lPcVG0HP;N`&Q2yIa&}r!0Oni>LLd@k&H4#p z&2=#YfiMmTtskEY_KTeRUy1=F6Uk;wa~zt)#uCs(97sl!@Bjg-2rPj}WE0H^?767E zq6>L!Q4}BmZ8#8*5LZxv&T@q``kbnj-+L1k2ukt*QHI8n(Rd3w4oiXlP);!aet0Z4 zn}7q@L^K0rfM_BX4@CwrNoWhU8Hq?Dn6u0X;P>wSAH$og2m%F*`x2fdMzSD&Nv0*{ zf24a(;hPl{qGe77t<2CukNLXNf94BH#lQLcTxS2~5J=?DL4JtepLG4C>xUTlA?2Ud z^^>k2V&I3Ae^%H38(nh$oE5<^=mRebIti+k?rMTgkqa0uj&`%>N!XNaWjypI%Xiu! zgpQtdl1u8aiJ}%HlomO=*-H<}YD?=$^*8Q&4uj3hbhfjh`__(Rwgh(tsNb>*;uXlB z+4C?Z)C`q?%v|R1JT>~LsLVPQn5j--#@9XwpQ&yLe;AKkE!J9^gUsn_J2T#{;`~OZ z?Jw|%p?a`O+l!m0TF|`lCsS+I_qAAHEbBc#Uh=qeH_T>zW$Lz;Hd{Hkc2!(`ORG%v zd{WSfI=PrGD)dk_JasCs*J3qB>RoFTtbx;B)%|xLTNas~?;VrjT3%k^+MH)y{&%yE!@Rt3ZpyI6 z%P^xOjj($`R~weroi<#WY_ImpUPL)zJ?3y`@y2|Ze@E~AH}^O8`?+wlPn>KGx~jTD z;>)h|3Hc(+Em99p#}CMBHrg1-J@VK$8k(uzD0PmDs+iY(tt3>dRVAYlyeX*eWA8Om z)KJ=3mC+*T8DX zPdXbpn_s*3DdmQ4dZcjecsta9M3E*RUe^?NhR`l`Y+I^z;AYdH;p!Xm-3{}0EgGtb wThZM&@MO)i#6^~*ef6hbs!XX(#V?Nov~Jew|&>+JJ=r07IhhdjJ3c literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json b/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json index 373b9ce9a90..38a75827d50 100644 --- a/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json +++ b/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "size": { "x": 32, "y": 32 @@ -31,6 +31,10 @@ "name": "derelict_e_r", "directions": 4 }, + { + "name": "derelict_icon", + "directions": 1 + }, { "name": "derelict_l", "directions": 4 From 964ef33fc746adc429db6d78e4529993a5cd831f Mon Sep 17 00:00:00 2001 From: The Canned One Date: Sat, 28 Sep 2024 10:50:33 +0200 Subject: [PATCH 006/218] Fixed accidental removal of something from a meta.json file. --- Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json b/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json index 38a75827d50..6ec63992f1f 100644 --- a/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json +++ b/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json @@ -19,6 +19,10 @@ "name": "clown_e_r", "directions": 4 }, + { + "name": "clown_l", + "directions": 4 + }, { "name": "derelict", "directions": 4 From bad25e3397b7c7cc4a88f7ee9592fcba28875f1b Mon Sep 17 00:00:00 2001 From: The Canned One Date: Sun, 29 Sep 2024 19:01:59 +0200 Subject: [PATCH 007/218] Split part of IonStormRule into IonStormSystem. Added StartIonStormed which also uses IonStormSystem. Added StartIonStormedComponent. Changed stuff related to the Derelict Cyborg. Derelict Cyborg now spawns with a randomized lawset. --- .../Silicons/Laws/IonStormSystem.cs | 283 ++++++++++++++++++ .../Silicons/Laws/StartIonStormedSystem.cs | 40 +++ .../StationEvents/Events/IonStormRule.cs | 267 +---------------- .../Components/StartIonStormedComponent.cs | 17 ++ .../ghost/roles/ghost-role-component.ftl | 2 +- .../Locale/en-US/silicons/derelict/role.ftl | 2 +- .../Mobs/Cyborgs/base_borg_chassis.yml | 8 +- Resources/Prototypes/GameRules/events.yml | 7 +- 8 files changed, 355 insertions(+), 271 deletions(-) create mode 100644 Content.Server/Silicons/Laws/IonStormSystem.cs create mode 100644 Content.Server/Silicons/Laws/StartIonStormedSystem.cs create mode 100644 Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs new file mode 100644 index 00000000000..1acfe2e3896 --- /dev/null +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -0,0 +1,283 @@ +using System.Linq; +using Content.Server.StationEvents.Components; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Dataset; +using Content.Shared.FixedPoint; +using Content.Shared.GameTicking.Components; +using Content.Shared.Random; +using Content.Shared.Random.Helpers; +using Content.Shared.Silicons.Laws; +using Content.Shared.Silicons.Laws.Components; +using Content.Shared.Station.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Silicons.Laws; + +public sealed class IonStormSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SiliconLawSystem _siliconLaw = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + + // funny + [ValidatePrototypeId] + private const string Threats = "IonStormThreats"; + [ValidatePrototypeId] + private const string Objects = "IonStormObjects"; + [ValidatePrototypeId] + private const string Crew = "IonStormCrew"; + [ValidatePrototypeId] + private const string Adjectives = "IonStormAdjectives"; + [ValidatePrototypeId] + private const string Verbs = "IonStormVerbs"; + [ValidatePrototypeId] + private const string NumberBase = "IonStormNumberBase"; + [ValidatePrototypeId] + private const string NumberMod = "IonStormNumberMod"; + [ValidatePrototypeId] + private const string Areas = "IonStormAreas"; + [ValidatePrototypeId] + private const string Feelings = "IonStormFeelings"; + [ValidatePrototypeId] + private const string FeelingsPlural = "IonStormFeelingsPlural"; + [ValidatePrototypeId] + private const string Musts = "IonStormMusts"; + [ValidatePrototypeId] + private const string Requires = "IonStormRequires"; + [ValidatePrototypeId] + private const string Actions = "IonStormActions"; + [ValidatePrototypeId] + private const string Allergies = "IonStormAllergies"; + [ValidatePrototypeId] + private const string AllergySeverities = "IonStormAllergySeverities"; + [ValidatePrototypeId] + private const string Concepts = "IonStormConcepts"; + [ValidatePrototypeId] + private const string Drinks = "IonStormDrinks"; + [ValidatePrototypeId] + private const string Foods = "IonStormFoods"; + + public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, TransformComponent xform, IonStormTargetComponent target, EntityUid? chosenStation, bool ignoreStation = false, bool DoNotAdminlog = false) + { + // only affect law holders on the station unless ignoreStation is true. + if (CompOrNull(xform.GridUid)?.Station != chosenStation && !ignoreStation) + return; + + if (!_robustRandom.Prob(target.Chance)) + return; + + var laws = _siliconLaw.GetLaws(ent, lawBound); + if (laws.Laws.Count == 0) + return; + + // try to swap it out with a random lawset + if (_robustRandom.Prob(target.RandomLawsetChance)) + { + var lawsets = _proto.Index(target.RandomLawsets); + var lawset = lawsets.Pick(_robustRandom); + laws = _siliconLaw.GetLawset(lawset); + } + else + { + // clone it so not modifying stations lawset + laws = laws.Clone(); + } + + // shuffle them all + if (_robustRandom.Prob(target.ShuffleChance)) + { + // hopefully work with existing glitched laws if there are multiple ion storms + FixedPoint2 baseOrder = FixedPoint2.New(1); + foreach (var law in laws.Laws) + { + if (law.Order < baseOrder) + baseOrder = law.Order; + } + + _robustRandom.Shuffle(laws.Laws); + + // change order based on shuffled position + for (int i = 0; i < laws.Laws.Count; i++) + { + laws.Laws[i].Order = baseOrder + i; + } + } + + // see if we can remove a random law + if (laws.Laws.Count > 0 && _robustRandom.Prob(target.RemoveChance)) + { + var i = _robustRandom.Next(laws.Laws.Count); + laws.Laws.RemoveAt(i); + } + + // generate a new law... + var newLaw = GenerateLaw(); + + // see if the law we add will replace a random existing law or be a new glitched order one + if (laws.Laws.Count > 0 && _robustRandom.Prob(target.ReplaceChance)) + { + var i = _robustRandom.Next(laws.Laws.Count); + laws.Laws[i] = new SiliconLaw() + { + LawString = newLaw, + Order = laws.Laws[i].Order + }; + } + else + { + laws.Laws.Insert(0, new SiliconLaw + { + LawString = newLaw, + Order = -1, + LawIdentifierOverride = Loc.GetString("ion-storm-law-scrambled-number", ("length", _robustRandom.Next(5, 10))) + }); + } + + // sets all unobfuscated laws' indentifier in order from highest to lowest priority + // This could technically override the Obfuscation from the code above, but it seems unlikely enough to basically never happen + int orderDeduction = -1; + + for (int i = 0; i < laws.Laws.Count; i++) + { + string notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString(); + + if (notNullIdentifier.Any(char.IsSymbol)) + { + orderDeduction += 1; + } + else + { + laws.Laws[i].LawIdentifierOverride = (i - orderDeduction).ToString(); + } + } + + //DoNotAdminlog is used to prevent adminlog spam. + if (!DoNotAdminlog) + _adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent):silicon} had its laws changed by an ion storm to {laws.LoggingString()}"); + + // laws unique to this silicon, dont use station laws anymore + EnsureComp(ent); + var ev = new IonStormLawsEvent(laws); + RaiseLocalEvent(ent, ref ev); + } + + // for your own sake direct your eyes elsewhere + private string GenerateLaw() + { + // pick all values ahead of time to make the logic cleaner + var threats = Pick(Threats); + var objects = Pick(Objects); + var crew1 = Pick(Crew); + var crew2 = Pick(Crew); + var adjective = Pick(Adjectives); + var verb = Pick(Verbs); + var number = Pick(NumberBase) + " " + Pick(NumberMod); + var area = Pick(Areas); + var feeling = Pick(Feelings); + var feelingPlural = Pick(FeelingsPlural); + var must = Pick(Musts); + var require = Pick(Requires); + var action = Pick(Actions); + var allergy = Pick(Allergies); + var allergySeverity = Pick(AllergySeverities); + var concept = Pick(Concepts); + var drink = Pick(Drinks); + var food = Pick(Foods); + + var joined = $"{number} {adjective}"; + // a lot of things have subjects of a threat/crew/object + var triple = _robustRandom.Next(0, 3) switch + { + 0 => threats, + 1 => crew1, + 2 => objects, + _ => throw new IndexOutOfRangeException(), + }; + var crewAll = _robustRandom.Prob(0.5f) ? crew2 : Loc.GetString("ion-storm-crew"); + var objectsThreats = _robustRandom.Prob(0.5f) ? objects : threats; + var objectsConcept = _robustRandom.Prob(0.5f) ? objects : concept; + // s goes ahead of require, is/are + // i dont think theres a way to do this in fluent + var (who, plural) = _robustRandom.Next(0, 5) switch + { + 0 => (Loc.GetString("ion-storm-you"), false), + 1 => (Loc.GetString("ion-storm-the-station"), true), + 2 => (Loc.GetString("ion-storm-the-crew"), true), + 3 => (Loc.GetString("ion-storm-the-job", ("job", crew2)), false), + _ => (area, true) // THE SINGULARITY REQUIRES THE HAPPY CLOWNS + }; + var jobChange = _robustRandom.Next(0, 3) switch + { + 0 => crew1, + 1 => Loc.GetString("ion-storm-clowns"), + _ => Loc.GetString("ion-storm-heads") + }; + var part = Loc.GetString("ion-storm-part", ("part", _robustRandom.Prob(0.5f))); + var harm = _robustRandom.Next(0, 6) switch + { + 0 => concept, + 1 => $"{adjective} {threats}", + 2 => $"{adjective} {objects}", + 3 => Loc.GetString("ion-storm-adjective-things", ("adjective", adjective)), + 4 => crew1, + _ => Loc.GetString("ion-storm-x-and-y", ("x", crew1), ("y", crew2)) + }; + + if (plural) feeling = feelingPlural; + + var subjects = _robustRandom.Prob(0.5f) ? objectsThreats : Loc.GetString("ion-storm-people"); + + // message logic!!! + return _robustRandom.Next(0, 36) switch + { + 0 => Loc.GetString("ion-storm-law-on-station", ("joined", joined), ("subjects", triple)), + 1 => Loc.GetString("ion-storm-law-no-shuttle", ("joined", joined), ("subjects", triple)), + 2 => Loc.GetString("ion-storm-law-crew-are", ("who", crewAll), ("joined", joined), ("subjects", objectsThreats)), + 3 => Loc.GetString("ion-storm-law-subjects-harmful", ("adjective", adjective), ("subjects", triple)), + 4 => Loc.GetString("ion-storm-law-must-harmful", ("must", must)), + 5 => Loc.GetString("ion-storm-law-thing-harmful", ("thing", _robustRandom.Prob(0.5f) ? concept : action)), + 6 => Loc.GetString("ion-storm-law-job-harmful", ("adjective", adjective), ("job", crew1)), + 7 => Loc.GetString("ion-storm-law-having-harmful", ("adjective", adjective), ("thing", objectsConcept)), + 8 => Loc.GetString("ion-storm-law-not-having-harmful", ("adjective", adjective), ("thing", objectsConcept)), + 9 => Loc.GetString("ion-storm-law-requires", ("who", who), ("plural", plural), ("thing", _robustRandom.Prob(0.5f) ? concept : require)), + 10 => Loc.GetString("ion-storm-law-requires-subjects", ("who", who), ("plural", plural), ("joined", joined), ("subjects", triple)), + 11 => Loc.GetString("ion-storm-law-allergic", ("who", who), ("plural", plural), ("severity", allergySeverity), ("allergy", _robustRandom.Prob(0.5f) ? concept : allergy)), + 12 => Loc.GetString("ion-storm-law-allergic-subjects", ("who", who), ("plural", plural), ("severity", allergySeverity), ("adjective", adjective), ("subjects", _robustRandom.Prob(0.5f) ? objects : crew1)), + 13 => Loc.GetString("ion-storm-law-feeling", ("who", who), ("feeling", feeling), ("concept", concept)), + 14 => Loc.GetString("ion-storm-law-feeling-subjects", ("who", who), ("feeling", feeling), ("joined", joined), ("subjects", triple)), + 15 => Loc.GetString("ion-storm-law-you-are", ("concept", concept)), + 16 => Loc.GetString("ion-storm-law-you-are-subjects", ("joined", joined), ("subjects", triple)), + 17 => Loc.GetString("ion-storm-law-you-must-always", ("must", must)), + 18 => Loc.GetString("ion-storm-law-you-must-never", ("must", must)), + 19 => Loc.GetString("ion-storm-law-eat", ("who", crewAll), ("adjective", adjective), ("food", _robustRandom.Prob(0.5f) ? food : triple)), + 20 => Loc.GetString("ion-storm-law-drink", ("who", crewAll), ("adjective", adjective), ("drink", drink)), + 22 => Loc.GetString("ion-storm-law-change-job", ("who", crewAll), ("adjective", adjective), ("change", jobChange)), + 23 => Loc.GetString("ion-storm-law-highest-rank", ("who", crew1)), + 24 => Loc.GetString("ion-storm-law-lowest-rank", ("who", crew1)), + 25 => Loc.GetString("ion-storm-law-crew-must", ("who", crewAll), ("must", must)), + 26 => Loc.GetString("ion-storm-law-crew-must-go", ("who", crewAll), ("area", area)), + 27 => Loc.GetString("ion-storm-law-crew-only-1", ("who", crew1), ("part", part)), + 28 => Loc.GetString("ion-storm-law-crew-only-2", ("who", crew1), ("other", crew2), ("part", part)), + 29 => Loc.GetString("ion-storm-law-crew-only-subjects", ("adjective", adjective), ("subjects", subjects), ("part", part)), + 30 => Loc.GetString("ion-storm-law-crew-must-do", ("must", must), ("part", part)), + 31 => Loc.GetString("ion-storm-law-crew-must-have", ("adjective", adjective), ("objects", objects), ("part", part)), + 32 => Loc.GetString("ion-storm-law-crew-must-eat", ("who", who), ("adjective", adjective), ("food", food), ("part", part)), + 33 => Loc.GetString("ion-storm-law-harm", ("who", harm)), + 34 => Loc.GetString("ion-storm-law-protect", ("who", harm)), + _ => Loc.GetString("ion-storm-law-concept-verb", ("concept", concept), ("verb", verb), ("subjects", triple)) + }; + } + + /// + /// Picks a random value from an ion storm dataset. + /// All ion storm datasets start with IonStorm. + /// + private string Pick(string name) + { + var dataset = _proto.Index(name); + return _robustRandom.Pick(dataset.Values); + } +} diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs new file mode 100644 index 00000000000..762397b5d4b --- /dev/null +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared.Silicons.Laws.Components; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Silicons.Laws; + +namespace Content.Server.Silicons.Laws; + +/// +/// This handles running the ion storm event on specific entities when spawned in. +/// +public sealed class StartIonStormedSystem : EntitySystem +{ + [Dependency] private readonly IonStormSystem _ionStorm = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SiliconLawSystem _siliconLaw = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(EntityUid uid, StartIonStormedComponent component, ref MapInitEvent args) + { + if (!TryComp(uid, out var lawBound)) + return; + if (!TryComp(uid, out var xform)) + return; + if (!TryComp(uid, out var target)) + return; + + for (int currentIonStorm = 1; currentIonStorm <= component.IonStormAmount; currentIonStorm++) + { + _ionStorm.IonStormTarget(uid, lawBound, xform, target, null, true, true); + } + + var laws = _siliconLaw.GetLaws(uid, lawBound); + _adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(uid):silicon} spawned with ion stormed laws: {laws.LoggingString()}"); + } +} diff --git a/Content.Server/StationEvents/Events/IonStormRule.cs b/Content.Server/StationEvents/Events/IonStormRule.cs index 805549439ba..6d1834c5766 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -1,64 +1,15 @@ using System.Linq; using Content.Server.Silicons.Laws; using Content.Server.StationEvents.Components; -using Content.Shared.Administration.Logs; -using Content.Shared.Database; -using Content.Shared.Dataset; -using Content.Shared.FixedPoint; using Content.Shared.GameTicking.Components; -using Content.Shared.Random; -using Content.Shared.Random.Helpers; using Content.Shared.Silicons.Laws; using Content.Shared.Silicons.Laws.Components; -using Content.Shared.Station.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; namespace Content.Server.StationEvents.Events; public sealed class IonStormRule : StationEventSystem { - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly SiliconLawSystem _siliconLaw = default!; - - // funny - [ValidatePrototypeId] - private const string Threats = "IonStormThreats"; - [ValidatePrototypeId] - private const string Objects = "IonStormObjects"; - [ValidatePrototypeId] - private const string Crew = "IonStormCrew"; - [ValidatePrototypeId] - private const string Adjectives = "IonStormAdjectives"; - [ValidatePrototypeId] - private const string Verbs = "IonStormVerbs"; - [ValidatePrototypeId] - private const string NumberBase = "IonStormNumberBase"; - [ValidatePrototypeId] - private const string NumberMod = "IonStormNumberMod"; - [ValidatePrototypeId] - private const string Areas = "IonStormAreas"; - [ValidatePrototypeId] - private const string Feelings = "IonStormFeelings"; - [ValidatePrototypeId] - private const string FeelingsPlural = "IonStormFeelingsPlural"; - [ValidatePrototypeId] - private const string Musts = "IonStormMusts"; - [ValidatePrototypeId] - private const string Requires = "IonStormRequires"; - [ValidatePrototypeId] - private const string Actions = "IonStormActions"; - [ValidatePrototypeId] - private const string Allergies = "IonStormAllergies"; - [ValidatePrototypeId] - private const string AllergySeverities = "IonStormAllergySeverities"; - [ValidatePrototypeId] - private const string Concepts = "IonStormConcepts"; - [ValidatePrototypeId] - private const string Drinks = "IonStormDrinks"; - [ValidatePrototypeId] - private const string Foods = "IonStormFoods"; + [Dependency] private readonly IonStormSystem _ionStorm = default!; protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args) { @@ -70,221 +21,7 @@ protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameR var query = EntityQueryEnumerator(); while (query.MoveNext(out var ent, out var lawBound, out var xform, out var target)) { - // only affect law holders on the station - if (CompOrNull(xform.GridUid)?.Station != chosenStation) - continue; - - if (!RobustRandom.Prob(target.Chance)) - continue; - - var laws = _siliconLaw.GetLaws(ent, lawBound); - if (laws.Laws.Count == 0) - continue; - - // try to swap it out with a random lawset - if (RobustRandom.Prob(target.RandomLawsetChance)) - { - var lawsets = PrototypeManager.Index(target.RandomLawsets); - var lawset = lawsets.Pick(RobustRandom); - laws = _siliconLaw.GetLawset(lawset); - } - else - { - // clone it so not modifying stations lawset - laws = laws.Clone(); - } - - // shuffle them all - if (RobustRandom.Prob(target.ShuffleChance)) - { - // hopefully work with existing glitched laws if there are multiple ion storms - FixedPoint2 baseOrder = FixedPoint2.New(1); - foreach (var law in laws.Laws) - { - if (law.Order < baseOrder) - baseOrder = law.Order; - } - - RobustRandom.Shuffle(laws.Laws); - - // change order based on shuffled position - for (int i = 0; i < laws.Laws.Count; i++) - { - laws.Laws[i].Order = baseOrder + i; - } - } - - // see if we can remove a random law - if (laws.Laws.Count > 0 && RobustRandom.Prob(target.RemoveChance)) - { - var i = RobustRandom.Next(laws.Laws.Count); - laws.Laws.RemoveAt(i); - } - - // generate a new law... - var newLaw = GenerateLaw(); - - // see if the law we add will replace a random existing law or be a new glitched order one - if (laws.Laws.Count > 0 && RobustRandom.Prob(target.ReplaceChance)) - { - var i = RobustRandom.Next(laws.Laws.Count); - laws.Laws[i] = new SiliconLaw() - { - LawString = newLaw, - Order = laws.Laws[i].Order - }; - } - else - { - laws.Laws.Insert(0, new SiliconLaw - { - LawString = newLaw, - Order = -1, - LawIdentifierOverride = Loc.GetString("ion-storm-law-scrambled-number", ("length", RobustRandom.Next(5, 10))) - }); - } - - // sets all unobfuscated laws' indentifier in order from highest to lowest priority - // This could technically override the Obfuscation from the code above, but it seems unlikely enough to basically never happen - int orderDeduction = -1; - - for (int i = 0; i < laws.Laws.Count; i++) - { - string notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString(); - - if (notNullIdentifier.Any(char.IsSymbol)) - { - orderDeduction += 1; - } - else - { - laws.Laws[i].LawIdentifierOverride = (i - orderDeduction).ToString(); - } - } - - _adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent):silicon} had its laws changed by an ion storm to {laws.LoggingString()}"); - - // laws unique to this silicon, dont use station laws anymore - EnsureComp(ent); - var ev = new IonStormLawsEvent(laws); - RaiseLocalEvent(ent, ref ev); + _ionStorm.IonStormTarget(ent, lawBound, xform, target, chosenStation); } } - - // for your own sake direct your eyes elsewhere - private string GenerateLaw() - { - // pick all values ahead of time to make the logic cleaner - var threats = Pick(Threats); - var objects = Pick(Objects); - var crew1 = Pick(Crew); - var crew2 = Pick(Crew); - var adjective = Pick(Adjectives); - var verb = Pick(Verbs); - var number = Pick(NumberBase) + " " + Pick(NumberMod); - var area = Pick(Areas); - var feeling = Pick(Feelings); - var feelingPlural = Pick(FeelingsPlural); - var must = Pick(Musts); - var require = Pick(Requires); - var action = Pick(Actions); - var allergy = Pick(Allergies); - var allergySeverity = Pick(AllergySeverities); - var concept = Pick(Concepts); - var drink = Pick(Drinks); - var food = Pick(Foods); - - var joined = $"{number} {adjective}"; - // a lot of things have subjects of a threat/crew/object - var triple = RobustRandom.Next(0, 3) switch - { - 0 => threats, - 1 => crew1, - 2 => objects, - _ => throw new IndexOutOfRangeException(), - }; - var crewAll = RobustRandom.Prob(0.5f) ? crew2 : Loc.GetString("ion-storm-crew"); - var objectsThreats = RobustRandom.Prob(0.5f) ? objects : threats; - var objectsConcept = RobustRandom.Prob(0.5f) ? objects : concept; - // s goes ahead of require, is/are - // i dont think theres a way to do this in fluent - var (who, plural) = RobustRandom.Next(0, 5) switch - { - 0 => (Loc.GetString("ion-storm-you"), false), - 1 => (Loc.GetString("ion-storm-the-station"), true), - 2 => (Loc.GetString("ion-storm-the-crew"), true), - 3 => (Loc.GetString("ion-storm-the-job", ("job", crew2)), false), - _ => (area, true) // THE SINGULARITY REQUIRES THE HAPPY CLOWNS - }; - var jobChange = RobustRandom.Next(0, 3) switch - { - 0 => crew1, - 1 => Loc.GetString("ion-storm-clowns"), - _ => Loc.GetString("ion-storm-heads") - }; - var part = Loc.GetString("ion-storm-part", ("part", RobustRandom.Prob(0.5f))); - var harm = RobustRandom.Next(0, 6) switch - { - 0 => concept, - 1 => $"{adjective} {threats}", - 2 => $"{adjective} {objects}", - 3 => Loc.GetString("ion-storm-adjective-things", ("adjective", adjective)), - 4 => crew1, - _ => Loc.GetString("ion-storm-x-and-y", ("x", crew1), ("y", crew2)) - }; - - if (plural) feeling = feelingPlural; - - var subjects = RobustRandom.Prob(0.5f) ? objectsThreats : Loc.GetString("ion-storm-people"); - - // message logic!!! - return RobustRandom.Next(0, 36) switch - { - 0 => Loc.GetString("ion-storm-law-on-station", ("joined", joined), ("subjects", triple)), - 1 => Loc.GetString("ion-storm-law-no-shuttle", ("joined", joined), ("subjects", triple)), - 2 => Loc.GetString("ion-storm-law-crew-are", ("who", crewAll), ("joined", joined), ("subjects", objectsThreats)), - 3 => Loc.GetString("ion-storm-law-subjects-harmful", ("adjective", adjective), ("subjects", triple)), - 4 => Loc.GetString("ion-storm-law-must-harmful", ("must", must)), - 5 => Loc.GetString("ion-storm-law-thing-harmful", ("thing", RobustRandom.Prob(0.5f) ? concept : action)), - 6 => Loc.GetString("ion-storm-law-job-harmful", ("adjective", adjective), ("job", crew1)), - 7 => Loc.GetString("ion-storm-law-having-harmful", ("adjective", adjective), ("thing", objectsConcept)), - 8 => Loc.GetString("ion-storm-law-not-having-harmful", ("adjective", adjective), ("thing", objectsConcept)), - 9 => Loc.GetString("ion-storm-law-requires", ("who", who), ("plural", plural), ("thing", RobustRandom.Prob(0.5f) ? concept : require)), - 10 => Loc.GetString("ion-storm-law-requires-subjects", ("who", who), ("plural", plural), ("joined", joined), ("subjects", triple)), - 11 => Loc.GetString("ion-storm-law-allergic", ("who", who), ("plural", plural), ("severity", allergySeverity), ("allergy", RobustRandom.Prob(0.5f) ? concept : allergy)), - 12 => Loc.GetString("ion-storm-law-allergic-subjects", ("who", who), ("plural", plural), ("severity", allergySeverity), ("adjective", adjective), ("subjects", RobustRandom.Prob(0.5f) ? objects : crew1)), - 13 => Loc.GetString("ion-storm-law-feeling", ("who", who), ("feeling", feeling), ("concept", concept)), - 14 => Loc.GetString("ion-storm-law-feeling-subjects", ("who", who), ("feeling", feeling), ("joined", joined), ("subjects", triple)), - 15 => Loc.GetString("ion-storm-law-you-are", ("concept", concept)), - 16 => Loc.GetString("ion-storm-law-you-are-subjects", ("joined", joined), ("subjects", triple)), - 17 => Loc.GetString("ion-storm-law-you-must-always", ("must", must)), - 18 => Loc.GetString("ion-storm-law-you-must-never", ("must", must)), - 19 => Loc.GetString("ion-storm-law-eat", ("who", crewAll), ("adjective", adjective), ("food", RobustRandom.Prob(0.5f) ? food : triple)), - 20 => Loc.GetString("ion-storm-law-drink", ("who", crewAll), ("adjective", adjective), ("drink", drink)), - 22 => Loc.GetString("ion-storm-law-change-job", ("who", crewAll), ("adjective", adjective), ("change", jobChange)), - 23 => Loc.GetString("ion-storm-law-highest-rank", ("who", crew1)), - 24 => Loc.GetString("ion-storm-law-lowest-rank", ("who", crew1)), - 25 => Loc.GetString("ion-storm-law-crew-must", ("who", crewAll), ("must", must)), - 26 => Loc.GetString("ion-storm-law-crew-must-go", ("who", crewAll), ("area", area)), - 27 => Loc.GetString("ion-storm-law-crew-only-1", ("who", crew1), ("part", part)), - 28 => Loc.GetString("ion-storm-law-crew-only-2", ("who", crew1), ("other", crew2), ("part", part)), - 29 => Loc.GetString("ion-storm-law-crew-only-subjects", ("adjective", adjective), ("subjects", subjects), ("part", part)), - 30 => Loc.GetString("ion-storm-law-crew-must-do", ("must", must), ("part", part)), - 31 => Loc.GetString("ion-storm-law-crew-must-have", ("adjective", adjective), ("objects", objects), ("part", part)), - 32 => Loc.GetString("ion-storm-law-crew-must-eat", ("who", who), ("adjective", adjective), ("food", food), ("part", part)), - 33 => Loc.GetString("ion-storm-law-harm", ("who", harm)), - 34 => Loc.GetString("ion-storm-law-protect", ("who", harm)), - _ => Loc.GetString("ion-storm-law-concept-verb", ("concept", concept), ("verb", verb), ("subjects", triple)) - }; - } - - /// - /// Picks a random value from an ion storm dataset. - /// All ion storm datasets start with IonStorm. - /// - private string Pick(string name) - { - var dataset = _proto.Index(name); - return RobustRandom.Pick(dataset.Values); - } } diff --git a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs new file mode 100644 index 00000000000..ae9b49a49cf --- /dev/null +++ b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Random; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Silicons.Laws.Components; + +/// +/// Runs the IonStormSystem on an entity IonStormAmount times. +/// +[RegisterComponent] +public sealed partial class StartIonStormedComponent : Component +{ + /// + /// Amount of times that the ion storm will be run on the entity on spawn. + /// + [DataField] + public int IonStormAmount = 1; +} diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index 71ab4d37160..3a8e720038f 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -242,7 +242,7 @@ ghost-role-information-syndicate-cyborg-description = The Syndicate needs reinfo ghost-role-information-derelict-cyborg-name = Derelict Cyborg -ghost-role-information-derelict-cyborg-description = You were a regular cyborg that got lost in space. After drifting in whichever direction the laws of physics would have it for years, you have drifted close to a Nanotrasen space station... You are bound by silicon laws. Check them upon spawning. +ghost-role-information-derelict-cyborg-description = You were a regular cyborg that got lost in space. After drifting in whichever direction the laws of physics would have it for years, you have drifted close to a Nanotrasen space station. You have a fire extinguisher and mass scanner which can be used to board the station. Years of exposure to ion storms has left your silicon laws altered - check them upon spawning. ghost-role-information-security-name = Security ghost-role-information-security-description = You are part of a security task force, but seem to have found yourself in a strange situation... diff --git a/Resources/Locale/en-US/silicons/derelict/role.ftl b/Resources/Locale/en-US/silicons/derelict/role.ftl index 88e750e6214..96a33ae6b15 100644 --- a/Resources/Locale/en-US/silicons/derelict/role.ftl +++ b/Resources/Locale/en-US/silicons/derelict/role.ftl @@ -1,4 +1,4 @@ derelict-cyborg-round-end-agent-name = derelict cyborg derelict-cyborg-role-greeting = - You are a cyborg that has been lost in space for many years that has now drifted close to a space station. You can use your fire extinguisher and GPS to get board the station. Remember to follow your laws. #Greeting is unused for now. \ No newline at end of file + You are a cyborg that has been lost in space for many years that has now drifted close to a space station. You can use your fire extinguisher and GPS to get board the station. Remember to follow your laws. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 4c0359b37e6..dcbef820ce8 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -329,11 +329,11 @@ - type: Access enabled: false groups: - - AllAccess #Randomized access would be fun. AllAccess is the best i can think of right now that does make it too hard to enter the station or. + - AllAccess #Randomized access would be fun. AllAccess is the best i can think of right now that does make it too hard for it to enter the station or navigate it.. - type: AccessReader access: [["Command"]] #I will probably change this. - type: SiliconLawProvider - laws: AntimovLawset #Temporary until i get it randomized. + laws: Crewsimov #Although this will be randomized. - type: IntrinsicRadioTransmitter channels: - Binary @@ -342,3 +342,7 @@ channels: - Binary - Common + - type: StartIonStormed + ionStormAmount: 5 + DelayAdminlog: true + - type: IonStormTarget diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index efebc8e2720..72ab8b104d0 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -552,10 +552,10 @@ id: DerelictCyborgSpawn components: - type: StationEvent - weight: 2 #Low until it spawns with a random lawset instead of just antimov. + weight: 8 earliestStart: 15 reoccurrenceDelay: 20 - minimumPlayers: 7 + minimumPlayers: 4 duration: null - type: SpaceSpawnRule spawnDistance: 0 @@ -564,6 +564,9 @@ - type: AntagSelection agentName: derelict-cyborg-round-end-agent-name definitions: +# briefing: +# text: derelict-cyborg-role-greetin +# color: Blue - spawnerPrototype: SpawnPointGhostDerelictCyborg min: 1 max: 1 From f226f28e52394e87f8c36293db438cd03cfa6e3d Mon Sep 17 00:00:00 2001 From: The Canned One Date: Mon, 30 Sep 2024 10:03:21 +0200 Subject: [PATCH 008/218] Derelict Cyborgs are now very likely to be affected by ion storms. --- Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index dcbef820ce8..73f7b518fbc 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -346,3 +346,4 @@ ionStormAmount: 5 DelayAdminlog: true - type: IonStormTarget + chance: 1 \ No newline at end of file From 4c8a235e612af72e5e1564d54bb6fd1c9cdd9cef Mon Sep 17 00:00:00 2001 From: The Canned One Date: Mon, 30 Sep 2024 12:12:10 +0200 Subject: [PATCH 009/218] Minor alterations to the Derelict Cyborg and its ghostrole description --- .../Locale/en-US/ghost/roles/ghost-role-component.ftl | 2 +- .../Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index 3a8e720038f..3f9422f2500 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -242,7 +242,7 @@ ghost-role-information-syndicate-cyborg-description = The Syndicate needs reinfo ghost-role-information-derelict-cyborg-name = Derelict Cyborg -ghost-role-information-derelict-cyborg-description = You were a regular cyborg that got lost in space. After drifting in whichever direction the laws of physics would have it for years, you have drifted close to a Nanotrasen space station. You have a fire extinguisher and mass scanner which can be used to board the station. Years of exposure to ion storms has left your silicon laws altered - check them upon spawning. +ghost-role-information-derelict-cyborg-description = You were a regular cyborg that got lost in space. After drifting in whichever direction the laws of physics would have it for years, you have drifted close to a Nanotrasen space station. You have a fire extinguisher and mass scanner which can be used to board the station. Years of exposure to ion storms have left your silicon laws altered - check them upon spawning. ghost-role-information-security-name = Security ghost-role-information-security-description = You are part of a security task force, but seem to have found yourself in a strange situation... diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 73f7b518fbc..f345229ad42 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -325,25 +325,26 @@ components: - type: NpcFactionMember factions: - - Passive #Might change this + - NanoTrasen #The seemingly best fit. It was a regular NT cyborg once, after all. - type: Access enabled: false groups: - AllAccess #Randomized access would be fun. AllAccess is the best i can think of right now that does make it too hard for it to enter the station or navigate it.. - type: AccessReader - access: [["Command"]] #I will probably change this. + access: [["Command"], ["Research"]] - type: SiliconLawProvider laws: Crewsimov #Although this will be randomized. - type: IntrinsicRadioTransmitter channels: - Binary - Common + - Science - type: ActiveRadio channels: - Binary - Common + - Science - type: StartIonStormed ionStormAmount: 5 - DelayAdminlog: true - type: IonStormTarget chance: 1 \ No newline at end of file From eb1168a8311744f4e2f086d6b434b9d935854532 Mon Sep 17 00:00:00 2001 From: Golden Can Date: Mon, 30 Sep 2024 18:24:28 +0200 Subject: [PATCH 010/218] Update Resources/Prototypes/Entities/Mobs/Player/silicon.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Prototypes/Entities/Mobs/Player/silicon.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index ff2b3f9ff2d..df024c55a12 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -537,20 +537,6 @@ - type: RandomMetadata nameSegments: [names_borg] -- type: entity - id: PlayerBorgDerelictBattery - parent: BorgChassisDerelict - suffix: Battery - components: - - type: ContainerFill - containers: - borg_brain: - - MMIFilled - - type: ItemSlots - slots: - cell_slot: - name: power-cell-slot-component-slot-name-default - startingItem: PowerCellHigh - type: entity id: PlayerBorgDerelictGhostRole From a4e7ad008c84cc461d5f0a5ed50cdd695fc0e546 Mon Sep 17 00:00:00 2001 From: Golden Can Date: Mon, 30 Sep 2024 18:24:38 +0200 Subject: [PATCH 011/218] Update Resources/Prototypes/Entities/Mobs/Player/silicon.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index df024c55a12..70e533fa447 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -540,7 +540,7 @@ - type: entity id: PlayerBorgDerelictGhostRole - parent: PlayerBorgDerelictBattery + parent: PlayerBorgDerelict suffix: Battery, Ghost role components: - type: GhostRole From 0cc1f32b3b3dc3ba2774c5761a85171b063a25af Mon Sep 17 00:00:00 2001 From: Golden Can Date: Mon, 30 Sep 2024 18:28:39 +0200 Subject: [PATCH 012/218] Update Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json b/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json index 6ec63992f1f..f5c2001828f 100644 --- a/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json +++ b/Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 1, "size": { "x": 32, "y": 32 From e75a71d7d3438ce78ac909d3cb89577ba525332a Mon Sep 17 00:00:00 2001 From: Golden Can Date: Mon, 30 Sep 2024 18:31:36 +0200 Subject: [PATCH 013/218] Update Content.Server/Silicons/Laws/StartIonStormedSystem.cs Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- Content.Server/Silicons/Laws/StartIonStormedSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs index 762397b5d4b..4b1b4da033d 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -29,7 +29,7 @@ private void OnMapInit(EntityUid uid, StartIonStormedComponent component, ref Ma if (!TryComp(uid, out var target)) return; - for (int currentIonStorm = 1; currentIonStorm <= component.IonStormAmount; currentIonStorm++) + for (int currentIonStorm = 0; currentIonStorm < component.IonStormAmount; currentIonStorm++) { _ionStorm.IonStormTarget(uid, lawBound, xform, target, null, true, true); } From eaa6017ada520c9a62783202cfd99c60179230a1 Mon Sep 17 00:00:00 2001 From: Golden Can Date: Mon, 30 Sep 2024 18:32:47 +0200 Subject: [PATCH 014/218] Update Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index f345229ad42..2fc04a6aa9c 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -332,8 +332,6 @@ - AllAccess #Randomized access would be fun. AllAccess is the best i can think of right now that does make it too hard for it to enter the station or navigate it.. - type: AccessReader access: [["Command"], ["Research"]] - - type: SiliconLawProvider - laws: Crewsimov #Although this will be randomized. - type: IntrinsicRadioTransmitter channels: - Binary From 834b6ebaaac99f5827857d2fef293c9466bb23fa Mon Sep 17 00:00:00 2001 From: The Canned One Date: Tue, 1 Oct 2024 10:02:25 +0200 Subject: [PATCH 015/218] Cleaned up a bit of the Derelict Cyborg code. --- .../Silicons/Laws/IonStormSystem.cs | 162 +++++++++--------- .../Silicons/Laws/StartIonStormedSystem.cs | 4 +- .../Components/StartIonStormedComponent.cs | 4 +- .../en-US/silicons/derelict-cyborg-role.ftl | 1 + .../Locale/en-US/silicons/derelict/role.ftl | 4 - .../Mobs/Cyborgs/base_borg_chassis.yml | 10 -- Resources/Prototypes/GameRules/events.yml | 3 - 7 files changed, 85 insertions(+), 103 deletions(-) create mode 100644 Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl delete mode 100644 Resources/Locale/en-US/silicons/derelict/role.ftl diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index 1acfe2e3896..87df0d8cf2c 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -60,108 +60,108 @@ public sealed class IonStormSystem : EntitySystem [ValidatePrototypeId] private const string Foods = "IonStormFoods"; - public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, TransformComponent xform, IonStormTargetComponent target, EntityUid? chosenStation, bool ignoreStation = false, bool DoNotAdminlog = false) + public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, TransformComponent xform, IonStormTargetComponent target, EntityUid? chosenStation, bool ignoreStation = false, bool adminlog = true) { - // only affect law holders on the station unless ignoreStation is true. - if (CompOrNull(xform.GridUid)?.Station != chosenStation && !ignoreStation) - return; + // only affect law holders on the station unless ignoreStation is true. + if (CompOrNull(xform.GridUid)?.Station != chosenStation && !ignoreStation) + return; - if (!_robustRandom.Prob(target.Chance)) - return; + if (!_robustRandom.Prob(target.Chance)) + return; - var laws = _siliconLaw.GetLaws(ent, lawBound); - if (laws.Laws.Count == 0) - return; + var laws = _siliconLaw.GetLaws(ent, lawBound); + if (laws.Laws.Count == 0) + return; - // try to swap it out with a random lawset - if (_robustRandom.Prob(target.RandomLawsetChance)) - { - var lawsets = _proto.Index(target.RandomLawsets); - var lawset = lawsets.Pick(_robustRandom); - laws = _siliconLaw.GetLawset(lawset); - } - else + // try to swap it out with a random lawset + if (_robustRandom.Prob(target.RandomLawsetChance)) + { + var lawsets = _proto.Index(target.RandomLawsets); + var lawset = lawsets.Pick(_robustRandom); + laws = _siliconLaw.GetLawset(lawset); + } + else + { + // clone it so not modifying stations lawset + laws = laws.Clone(); + } + + // shuffle them all + if (_robustRandom.Prob(target.ShuffleChance)) + { + // hopefully work with existing glitched laws if there are multiple ion storms + FixedPoint2 baseOrder = FixedPoint2.New(1); + foreach (var law in laws.Laws) { - // clone it so not modifying stations lawset - laws = laws.Clone(); + if (law.Order < baseOrder) + baseOrder = law.Order; } - // shuffle them all - if (_robustRandom.Prob(target.ShuffleChance)) + _robustRandom.Shuffle(laws.Laws); + + // change order based on shuffled position + for (int i = 0; i < laws.Laws.Count; i++) { - // hopefully work with existing glitched laws if there are multiple ion storms - FixedPoint2 baseOrder = FixedPoint2.New(1); - foreach (var law in laws.Laws) - { - if (law.Order < baseOrder) - baseOrder = law.Order; - } + laws.Laws[i].Order = baseOrder + i; + } + } - _robustRandom.Shuffle(laws.Laws); + // see if we can remove a random law + if (laws.Laws.Count > 0 && _robustRandom.Prob(target.RemoveChance)) + { + var i = _robustRandom.Next(laws.Laws.Count); + laws.Laws.RemoveAt(i); + } - // change order based on shuffled position - for (int i = 0; i < laws.Laws.Count; i++) - { - laws.Laws[i].Order = baseOrder + i; - } - } + // generate a new law... + var newLaw = GenerateLaw(); - // see if we can remove a random law - if (laws.Laws.Count > 0 && _robustRandom.Prob(target.RemoveChance)) + // see if the law we add will replace a random existing law or be a new glitched order one + if (laws.Laws.Count > 0 && _robustRandom.Prob(target.ReplaceChance)) + { + var i = _robustRandom.Next(laws.Laws.Count); + laws.Laws[i] = new SiliconLaw() { - var i = _robustRandom.Next(laws.Laws.Count); - laws.Laws.RemoveAt(i); - } + LawString = newLaw, + Order = laws.Laws[i].Order + }; + } + else + { + laws.Laws.Insert(0, new SiliconLaw + { + LawString = newLaw, + Order = -1, + LawIdentifierOverride = Loc.GetString("ion-storm-law-scrambled-number", ("length", _robustRandom.Next(5, 10))) + }); + } + + // sets all unobfuscated laws' indentifier in order from highest to lowest priority + // This could technically override the Obfuscation from the code above, but it seems unlikely enough to basically never happen + int orderDeduction = -1; - // generate a new law... - var newLaw = GenerateLaw(); + for (int i = 0; i < laws.Laws.Count; i++) + { + string notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString(); - // see if the law we add will replace a random existing law or be a new glitched order one - if (laws.Laws.Count > 0 && _robustRandom.Prob(target.ReplaceChance)) + if (notNullIdentifier.Any(char.IsSymbol)) { - var i = _robustRandom.Next(laws.Laws.Count); - laws.Laws[i] = new SiliconLaw() - { - LawString = newLaw, - Order = laws.Laws[i].Order - }; + orderDeduction += 1; } else { - laws.Laws.Insert(0, new SiliconLaw - { - LawString = newLaw, - Order = -1, - LawIdentifierOverride = Loc.GetString("ion-storm-law-scrambled-number", ("length", _robustRandom.Next(5, 10))) - }); - } - - // sets all unobfuscated laws' indentifier in order from highest to lowest priority - // This could technically override the Obfuscation from the code above, but it seems unlikely enough to basically never happen - int orderDeduction = -1; - - for (int i = 0; i < laws.Laws.Count; i++) - { - string notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString(); - - if (notNullIdentifier.Any(char.IsSymbol)) - { - orderDeduction += 1; - } - else - { - laws.Laws[i].LawIdentifierOverride = (i - orderDeduction).ToString(); - } + laws.Laws[i].LawIdentifierOverride = (i - orderDeduction).ToString(); } + } - //DoNotAdminlog is used to prevent adminlog spam. - if (!DoNotAdminlog) - _adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent):silicon} had its laws changed by an ion storm to {laws.LoggingString()}"); + // adminlog is used to prevent adminlog spam. + if (adminlog) + _adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent):silicon} had its laws changed by an ion storm to {laws.LoggingString()}"); - // laws unique to this silicon, dont use station laws anymore - EnsureComp(ent); - var ev = new IonStormLawsEvent(laws); - RaiseLocalEvent(ent, ref ev); + // laws unique to this silicon, dont use station laws anymore + EnsureComp(ent); + var ev = new IonStormLawsEvent(laws); + RaiseLocalEvent(ent, ref ev); } // for your own sake direct your eyes elsewhere diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs index 4b1b4da033d..f7b65e1a041 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -6,7 +6,7 @@ namespace Content.Server.Silicons.Laws; /// -/// This handles running the ion storm event on specific entities when spawned in. +/// This handles running the ion storm event a on specific entity when that entity is spawned in. /// public sealed class StartIonStormedSystem : EntitySystem { @@ -31,7 +31,7 @@ private void OnMapInit(EntityUid uid, StartIonStormedComponent component, ref Ma for (int currentIonStorm = 0; currentIonStorm < component.IonStormAmount; currentIonStorm++) { - _ionStorm.IonStormTarget(uid, lawBound, xform, target, null, true, true); + _ionStorm.IonStormTarget(uid, lawBound, xform, target, null, true, false); } var laws = _siliconLaw.GetLaws(uid, lawBound); diff --git a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs index ae9b49a49cf..4157bee9f2d 100644 --- a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs @@ -1,10 +1,8 @@ -using Content.Shared.Random; -using Robust.Shared.Prototypes; namespace Content.Shared.Silicons.Laws.Components; /// -/// Runs the IonStormSystem on an entity IonStormAmount times. +/// Applies law altering ion storms on a specific entity IonStormAmount times when the entity is spawned. /// [RegisterComponent] public sealed partial class StartIonStormedComponent : Component diff --git a/Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl b/Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl new file mode 100644 index 00000000000..87e500d3e61 --- /dev/null +++ b/Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl @@ -0,0 +1 @@ +derelict-cyborg-round-end-agent-name = derelict cyborg \ No newline at end of file diff --git a/Resources/Locale/en-US/silicons/derelict/role.ftl b/Resources/Locale/en-US/silicons/derelict/role.ftl deleted file mode 100644 index 96a33ae6b15..00000000000 --- a/Resources/Locale/en-US/silicons/derelict/role.ftl +++ /dev/null @@ -1,4 +0,0 @@ -derelict-cyborg-round-end-agent-name = derelict cyborg - -derelict-cyborg-role-greeting = - You are a cyborg that has been lost in space for many years that has now drifted close to a space station. You can use your fire extinguisher and GPS to get board the station. Remember to follow your laws. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 2fc04a6aa9c..f94436dc336 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -332,16 +332,6 @@ - AllAccess #Randomized access would be fun. AllAccess is the best i can think of right now that does make it too hard for it to enter the station or navigate it.. - type: AccessReader access: [["Command"], ["Research"]] - - type: IntrinsicRadioTransmitter - channels: - - Binary - - Common - - Science - - type: ActiveRadio - channels: - - Binary - - Common - - Science - type: StartIonStormed ionStormAmount: 5 - type: IonStormTarget diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 72ab8b104d0..36ded039eca 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -564,9 +564,6 @@ - type: AntagSelection agentName: derelict-cyborg-round-end-agent-name definitions: -# briefing: -# text: derelict-cyborg-role-greetin -# color: Blue - spawnerPrototype: SpawnPointGhostDerelictCyborg min: 1 max: 1 From 1abc60b9951efcd16a024e640fa552f21190099d Mon Sep 17 00:00:00 2001 From: The Canned One Date: Tue, 1 Oct 2024 10:42:52 +0200 Subject: [PATCH 016/218] moved a bit of IonStorm code elsewhere --- Content.Server/Silicons/Laws/IonStormSystem.cs | 7 +------ Content.Server/Silicons/Laws/StartIonStormedSystem.cs | 2 +- Content.Server/StationEvents/Events/IonStormRule.cs | 5 +++++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index 87df0d8cf2c..cf31a9e19f7 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -9,7 +9,6 @@ using Content.Shared.Random.Helpers; using Content.Shared.Silicons.Laws; using Content.Shared.Silicons.Laws.Components; -using Content.Shared.Station.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -60,12 +59,8 @@ public sealed class IonStormSystem : EntitySystem [ValidatePrototypeId] private const string Foods = "IonStormFoods"; - public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, TransformComponent xform, IonStormTargetComponent target, EntityUid? chosenStation, bool ignoreStation = false, bool adminlog = true) + public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, TransformComponent xform, IonStormTargetComponent target, EntityUid? chosenStation, bool adminlog = true) { - // only affect law holders on the station unless ignoreStation is true. - if (CompOrNull(xform.GridUid)?.Station != chosenStation && !ignoreStation) - return; - if (!_robustRandom.Prob(target.Chance)) return; diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs index f7b65e1a041..887bc051dde 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -31,7 +31,7 @@ private void OnMapInit(EntityUid uid, StartIonStormedComponent component, ref Ma for (int currentIonStorm = 0; currentIonStorm < component.IonStormAmount; currentIonStorm++) { - _ionStorm.IonStormTarget(uid, lawBound, xform, target, null, true, false); + _ionStorm.IonStormTarget(uid, lawBound, xform, target, null, false); } var laws = _siliconLaw.GetLaws(uid, lawBound); diff --git a/Content.Server/StationEvents/Events/IonStormRule.cs b/Content.Server/StationEvents/Events/IonStormRule.cs index 6d1834c5766..05d079fb3d9 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -4,6 +4,7 @@ using Content.Shared.GameTicking.Components; using Content.Shared.Silicons.Laws; using Content.Shared.Silicons.Laws.Components; +using Content.Shared.Station.Components; namespace Content.Server.StationEvents.Events; @@ -21,6 +22,10 @@ protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameR var query = EntityQueryEnumerator(); while (query.MoveNext(out var ent, out var lawBound, out var xform, out var target)) { + // only affect law holders on the station + if (CompOrNull(xform.GridUid)?.Station != chosenStation) + continue; + _ionStorm.IonStormTarget(ent, lawBound, xform, target, chosenStation); } } From c6fe5682c28282ee58ad62ce2e041fa3ded36416 Mon Sep 17 00:00:00 2001 From: The Canned One Date: Tue, 1 Oct 2024 10:57:51 +0200 Subject: [PATCH 017/218] changed almost nothing --- Content.Server/Silicons/Laws/IonStormSystem.cs | 4 ++-- Content.Server/Silicons/Laws/StartIonStormedSystem.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index cf31a9e19f7..65c12c3d8ec 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -85,7 +85,7 @@ public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, Tra if (_robustRandom.Prob(target.ShuffleChance)) { // hopefully work with existing glitched laws if there are multiple ion storms - FixedPoint2 baseOrder = FixedPoint2.New(1); + var baseOrder = FixedPoint2.New(1); foreach (var law in laws.Laws) { if (law.Order < baseOrder) @@ -137,7 +137,7 @@ public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, Tra for (int i = 0; i < laws.Laws.Count; i++) { - string notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString(); + var notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString(); if (notNullIdentifier.Any(char.IsSymbol)) { diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs index 887bc051dde..c80c2a7751d 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -1,7 +1,6 @@ using Content.Shared.Silicons.Laws.Components; using Content.Shared.Administration.Logs; using Content.Shared.Database; -using Content.Shared.Silicons.Laws; namespace Content.Server.Silicons.Laws; From 36390b23d1dd8d397731e06bfffee559d47285c8 Mon Sep 17 00:00:00 2001 From: The Canned One Date: Tue, 1 Oct 2024 11:11:58 +0200 Subject: [PATCH 018/218] Small changes - hopefully good ones. --- .../Silicons/Laws/StartIonStormedSystem.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs index c80c2a7751d..546b3b27772 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -19,21 +19,22 @@ public override void Initialize() SubscribeLocalEvent(OnMapInit); } - private void OnMapInit(EntityUid uid, StartIonStormedComponent component, ref MapInitEvent args) + //private void OnMapInit(EntityUid uid, StartIonStormedComponent component, ref MapInitEvent args)' + private void OnMapInit(Entity ent, ref MapInitEvent args) { - if (!TryComp(uid, out var lawBound)) + if (!TryComp(ent.Owner, out var lawBound)) return; - if (!TryComp(uid, out var xform)) + if (!TryComp(ent.Owner, out var xform)) return; - if (!TryComp(uid, out var target)) + if (!TryComp(ent.Owner, out var target)) return; - for (int currentIonStorm = 0; currentIonStorm < component.IonStormAmount; currentIonStorm++) + for (int currentIonStorm = 0; currentIonStorm < ent.Comp.IonStormAmount; currentIonStorm++) { - _ionStorm.IonStormTarget(uid, lawBound, xform, target, null, false); + _ionStorm.IonStormTarget(ent.Owner, lawBound, xform, target, null, false); } - var laws = _siliconLaw.GetLaws(uid, lawBound); - _adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(uid):silicon} spawned with ion stormed laws: {laws.LoggingString()}"); + var laws = _siliconLaw.GetLaws(ent.Owner, lawBound); + _adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent.Owner):silicon} spawned with ion stormed laws: {laws.LoggingString()}"); } } From 7169788e1634505ce89379ae7cd96f0e6a28c483 Mon Sep 17 00:00:00 2001 From: The Canned One Date: Tue, 1 Oct 2024 11:23:19 +0200 Subject: [PATCH 019/218] changed very minor stuff with no gameplay alterations. --- Content.Server/Silicons/Laws/IonStormSystem.cs | 2 +- Content.Server/StationEvents/Events/IonStormRule.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index 65c12c3d8ec..bcf002dae7f 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -1,4 +1,3 @@ -using System.Linq; using Content.Server.StationEvents.Components; using Content.Shared.Administration.Logs; using Content.Shared.Database; @@ -11,6 +10,7 @@ using Content.Shared.Silicons.Laws.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using System.Linq; namespace Content.Server.Silicons.Laws; diff --git a/Content.Server/StationEvents/Events/IonStormRule.cs b/Content.Server/StationEvents/Events/IonStormRule.cs index 05d079fb3d9..26f6d3263f0 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -1,8 +1,6 @@ -using System.Linq; using Content.Server.Silicons.Laws; using Content.Server.StationEvents.Components; using Content.Shared.GameTicking.Components; -using Content.Shared.Silicons.Laws; using Content.Shared.Silicons.Laws.Components; using Content.Shared.Station.Components; From d0114d9738c3eab436c11bc1d5f9c816d74980ca Mon Sep 17 00:00:00 2001 From: The Canned One Date: Tue, 1 Oct 2024 14:04:33 +0200 Subject: [PATCH 020/218] added a code summary --- Content.Server/Silicons/Laws/IonStormSystem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index bcf002dae7f..b8d8f432e77 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -59,6 +59,7 @@ public sealed class IonStormSystem : EntitySystem [ValidatePrototypeId] private const string Foods = "IonStormFoods"; + //Randomly alters the laws of an individual silicon. public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, TransformComponent xform, IonStormTargetComponent target, EntityUid? chosenStation, bool adminlog = true) { if (!_robustRandom.Prob(target.Chance)) From 08de5aeae134f67bbc310c42d56e145a4476cf6e Mon Sep 17 00:00:00 2001 From: The Canned One Date: Tue, 1 Oct 2024 18:07:41 +0200 Subject: [PATCH 021/218] Derelict cyborg minor yaml changes. --- .../Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml | 2 +- Resources/Prototypes/GameRules/events.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index f94436dc336..7449fe5669f 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -333,6 +333,6 @@ - type: AccessReader access: [["Command"], ["Research"]] - type: StartIonStormed - ionStormAmount: 5 + ionStormAmount: 4 - type: IonStormTarget chance: 1 \ No newline at end of file diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 36ded039eca..be44b50d616 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -552,7 +552,7 @@ id: DerelictCyborgSpawn components: - type: StationEvent - weight: 8 + weight: 6 earliestStart: 15 reoccurrenceDelay: 20 minimumPlayers: 4 From 963009a440cee3d47ab297b5aa7a76e3dfcaf569 Mon Sep 17 00:00:00 2001 From: The Canned One Date: Thu, 3 Oct 2024 11:34:24 +0200 Subject: [PATCH 022/218] Changes IonStorm related code with no gameplay changes. --- Content.Server/Silicons/Laws/IonStormSystem.cs | 4 +++- Content.Server/Silicons/Laws/StartIonStormedSystem.cs | 2 +- Content.Server/StationEvents/Events/IonStormRule.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index b8d8f432e77..637155228cb 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -60,8 +60,10 @@ public sealed class IonStormSystem : EntitySystem private const string Foods = "IonStormFoods"; //Randomly alters the laws of an individual silicon. - public void IonStormTarget(EntityUid ent, SiliconLawBoundComponent lawBound, TransformComponent xform, IonStormTargetComponent target, EntityUid? chosenStation, bool adminlog = true) + public void IonStormTarget(Entity ent, TransformComponent xform, EntityUid? chosenStation, bool adminlog = true) { + var lawBound = ent.Comp1; + var target = ent.Comp2; if (!_robustRandom.Prob(target.Chance)) return; diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs index 546b3b27772..d679b558b32 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -31,7 +31,7 @@ private void OnMapInit(Entity ent, ref MapInitEvent ar for (int currentIonStorm = 0; currentIonStorm < ent.Comp.IonStormAmount; currentIonStorm++) { - _ionStorm.IonStormTarget(ent.Owner, lawBound, xform, target, null, false); + _ionStorm.IonStormTarget((ent.Owner, lawBound, target), xform, null, false); } var laws = _siliconLaw.GetLaws(ent.Owner, lawBound); diff --git a/Content.Server/StationEvents/Events/IonStormRule.cs b/Content.Server/StationEvents/Events/IonStormRule.cs index 26f6d3263f0..9b67f25608d 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -24,7 +24,7 @@ protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameR if (CompOrNull(xform.GridUid)?.Station != chosenStation) continue; - _ionStorm.IonStormTarget(ent, lawBound, xform, target, chosenStation); + _ionStorm.IonStormTarget((ent, lawBound, target), xform, chosenStation); } } } From 4b633fde9c84778e2817025b1d38bd2549202c52 Mon Sep 17 00:00:00 2001 From: The Canned One Date: Thu, 3 Oct 2024 12:32:50 +0200 Subject: [PATCH 023/218] Fixed IonStorms sometimes affecting the laws of the current AI and future Cyborgs and AI's, including those in subsequent rounds. --- Content.Server/Silicons/Laws/IonStormSystem.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index 637155228cb..b3c36a22733 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -78,11 +78,8 @@ public void IonStormTarget(Entity Date: Thu, 3 Oct 2024 12:43:51 +0200 Subject: [PATCH 024/218] Changed DerelictCyborgSpawn event's frequency from 6 to 5, even though i didn't want to. --- Resources/Prototypes/GameRules/events.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index be44b50d616..9b5426c2697 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -552,7 +552,7 @@ id: DerelictCyborgSpawn components: - type: StationEvent - weight: 6 + weight: 5 earliestStart: 15 reoccurrenceDelay: 20 minimumPlayers: 4 From 3aff20173cbcd80e3bcd2c6d2b361e945b52e96e Mon Sep 17 00:00:00 2001 From: The Canned One Date: Thu, 3 Oct 2024 13:26:29 +0200 Subject: [PATCH 025/218] Removed 1 line of whitespace. --- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 70e533fa447..97cd7c2a9d8 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -537,7 +537,6 @@ - type: RandomMetadata nameSegments: [names_borg] - - type: entity id: PlayerBorgDerelictGhostRole parent: PlayerBorgDerelict From 00aaffbc00a14515c0c5c5c900afbd3bd3946e99 Mon Sep 17 00:00:00 2001 From: The Canned One Date: Thu, 3 Oct 2024 13:51:38 +0200 Subject: [PATCH 026/218] removed whitespace --- Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index 3f9422f2500..f584a4b35fe 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -240,7 +240,6 @@ ghost-role-information-syndicate-cyborg-assault-name = Syndicate Assault Cyborg ghost-role-information-syndicate-cyborg-saboteur-name = Syndicate Saboteur Cyborg ghost-role-information-syndicate-cyborg-description = The Syndicate needs reinforcements. You, a cold silicon killing machine, will help them. - ghost-role-information-derelict-cyborg-name = Derelict Cyborg ghost-role-information-derelict-cyborg-description = You were a regular cyborg that got lost in space. After drifting in whichever direction the laws of physics would have it for years, you have drifted close to a Nanotrasen space station. You have a fire extinguisher and mass scanner which can be used to board the station. Years of exposure to ion storms have left your silicon laws altered - check them upon spawning. From d863e3c5ca01530b04a678e85fc72176caac02fc Mon Sep 17 00:00:00 2001 From: The Canned One Date: Thu, 3 Oct 2024 14:03:14 +0200 Subject: [PATCH 027/218] Derelict Cyborg no longer appears on the endround 'Game Information' screen. It still appears in the Player Manifest. --- Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl | 1 - Resources/Prototypes/GameRules/events.yml | 1 - 2 files changed, 2 deletions(-) delete mode 100644 Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl diff --git a/Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl b/Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl deleted file mode 100644 index 87e500d3e61..00000000000 --- a/Resources/Locale/en-US/silicons/derelict-cyborg-role.ftl +++ /dev/null @@ -1 +0,0 @@ -derelict-cyborg-round-end-agent-name = derelict cyborg \ No newline at end of file diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 9b5426c2697..763557e6c91 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -562,7 +562,6 @@ - type: AntagSpawner prototype: PlayerBorgDerelict - type: AntagSelection - agentName: derelict-cyborg-round-end-agent-name definitions: - spawnerPrototype: SpawnPointGhostDerelictCyborg min: 1 From 581a4d14fc0fbc2c37fd75ebe0364c74e746b38a Mon Sep 17 00:00:00 2001 From: The Canned One Date: Fri, 4 Oct 2024 08:31:55 +0200 Subject: [PATCH 028/218] minor Derelict Cyborg code changes. --- Content.Server/Silicons/Laws/IonStormSystem.cs | 4 +++- Content.Server/Silicons/Laws/StartIonStormedSystem.cs | 4 +--- Content.Server/StationEvents/Events/IonStormRule.cs | 2 +- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index b3c36a22733..f8bc8ca8c31 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -59,8 +59,10 @@ public sealed class IonStormSystem : EntitySystem [ValidatePrototypeId] private const string Foods = "IonStormFoods"; + /// //Randomly alters the laws of an individual silicon. - public void IonStormTarget(Entity ent, TransformComponent xform, EntityUid? chosenStation, bool adminlog = true) + /// + public void IonStormTarget(Entity ent, bool adminlog = true) { var lawBound = ent.Comp1; var target = ent.Comp2; diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs index d679b558b32..ee2ce7b9edd 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -24,14 +24,12 @@ private void OnMapInit(Entity ent, ref MapInitEvent ar { if (!TryComp(ent.Owner, out var lawBound)) return; - if (!TryComp(ent.Owner, out var xform)) - return; if (!TryComp(ent.Owner, out var target)) return; for (int currentIonStorm = 0; currentIonStorm < ent.Comp.IonStormAmount; currentIonStorm++) { - _ionStorm.IonStormTarget((ent.Owner, lawBound, target), xform, null, false); + _ionStorm.IonStormTarget((ent.Owner, lawBound, target), false); } var laws = _siliconLaw.GetLaws(ent.Owner, lawBound); diff --git a/Content.Server/StationEvents/Events/IonStormRule.cs b/Content.Server/StationEvents/Events/IonStormRule.cs index 9b67f25608d..e7c2d563ba9 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -24,7 +24,7 @@ protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameR if (CompOrNull(xform.GridUid)?.Station != chosenStation) continue; - _ionStorm.IonStormTarget((ent, lawBound, target), xform, chosenStation); + _ionStorm.IonStormTarget((ent, lawBound, target)); } } } diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 97cd7c2a9d8..22f49c93eaa 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -540,7 +540,7 @@ - type: entity id: PlayerBorgDerelictGhostRole parent: PlayerBorgDerelict - suffix: Battery, Ghost role + suffix: Ghost role components: - type: GhostRole name: ghost-role-information-derelict-cyborg-name From d7ed5b4386f2d48b4b52b79351027a36e967800e Mon Sep 17 00:00:00 2001 From: The Canned One Date: Fri, 4 Oct 2024 08:35:16 +0200 Subject: [PATCH 029/218] remove whitespace. --- .../Silicons/Laws/Components/StartIonStormedComponent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs index 4157bee9f2d..e73d17b87bc 100644 --- a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs @@ -1,4 +1,3 @@ - namespace Content.Shared.Silicons.Laws.Components; /// From b35d2902d4fc0c0322a1f74d0f208ca860990eee Mon Sep 17 00:00:00 2001 From: The Canned One Date: Sat, 5 Oct 2024 15:09:36 +0200 Subject: [PATCH 030/218] Fixed cyborgs with the StartIonStormedComponent (which is just the Derelict Cyborg right now) not showing up as 'antag' in the admin player overlay. --- .../Silicons/Laws/SiliconLawSystem.cs | 27 +++++++++++++++++++ .../Components/StartIonStormedComponent.cs | 10 +++++++ 2 files changed, 37 insertions(+) diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 6b7df52a6eb..27ed6702d6a 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -51,6 +51,8 @@ public override void Initialize() SubscribeLocalEvent(OnEmagLawsAdded); SubscribeLocalEvent(OnEmagMindAdded); SubscribeLocalEvent(OnEmagMindRemoved); + SubscribeLocalEvent(OnStartIonStormedMindAdded); + SubscribeLocalEvent(OnStartIonStormedMindRemoved); } private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args) @@ -184,6 +186,31 @@ private void EnsureEmaggedRole(EntityUid uid, EmagSiliconLawComponent component) _roles.MindAddRole(mindId, new SubvertedSiliconRoleComponent { PrototypeId = component.AntagonistRole }); } + private void OnStartIonStormedMindAdded(EntityUid uid, StartIonStormedComponent component, MindAddedMessage args) + { + if (HasComp(uid)) + EnsureStartIonStormedRole(uid, component); + } + + private void OnStartIonStormedMindRemoved(EntityUid uid, StartIonStormedComponent component, MindRemovedMessage args) + { + if (component.AntagonistRole == null) + return; + + _roles.MindTryRemoveRole(args.Mind); + } + + private void EnsureStartIonStormedRole(EntityUid uid, StartIonStormedComponent component) + { + if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _)) + return; + + if (_roles.MindHasRole(mindId)) + return; + + _roles.MindAddRole(mindId, new SubvertedSiliconRoleComponent { PrototypeId = component.AntagonistRole }); + } + public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null) { if (!Resolve(uid, ref component)) diff --git a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs index e73d17b87bc..d57241c857c 100644 --- a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs @@ -1,3 +1,6 @@ +using Content.Shared.Roles;//Used +using Robust.Shared.Prototypes;// + namespace Content.Shared.Silicons.Laws.Components; /// @@ -11,4 +14,11 @@ public sealed partial class StartIonStormedComponent : Component /// [DataField] public int IonStormAmount = 1; + + /// + /// A role given to entities with this component when they are thing-that-is-not-emagged. + /// Mostly just for admin purposes. + /// + [DataField] + public ProtoId? AntagonistRole = "SubvertedSilicon"; } From b0c5023fda93ef68f73ea0c4d50c01e7d60c587d Mon Sep 17 00:00:00 2001 From: The Canned One Date: Sat, 5 Oct 2024 16:48:45 +0200 Subject: [PATCH 031/218] Fix comments in StartIonStormedComponent.cs --- .../Silicons/Laws/Components/StartIonStormedComponent.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs index d57241c857c..75d74121669 100644 --- a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs @@ -1,5 +1,5 @@ -using Content.Shared.Roles;//Used -using Robust.Shared.Prototypes;// +using Content.Shared.Roles; +using Robust.Shared.Prototypes; namespace Content.Shared.Silicons.Laws.Components; @@ -16,7 +16,7 @@ public sealed partial class StartIonStormedComponent : Component public int IonStormAmount = 1; /// - /// A role given to entities with this component when they are thing-that-is-not-emagged. + /// A role given to entities with this component when a mind enters it. /// Mostly just for admin purposes. /// [DataField] From 03843734e4de1c9f3c34d029aca3e7935f7eafde Mon Sep 17 00:00:00 2001 From: Preston Smith Date: Tue, 29 Oct 2024 22:25:42 -0500 Subject: [PATCH 032/218] Add no damage phrase and logic --- Content.Shared/Damage/Systems/DamageExamineSystem.cs | 9 +++++++++ Resources/Locale/en-US/damage/damage-examine.ftl | 1 + 2 files changed, 10 insertions(+) diff --git a/Content.Shared/Damage/Systems/DamageExamineSystem.cs b/Content.Shared/Damage/Systems/DamageExamineSystem.cs index 53436a920aa..72c1af29b22 100644 --- a/Content.Shared/Damage/Systems/DamageExamineSystem.cs +++ b/Content.Shared/Damage/Systems/DamageExamineSystem.cs @@ -61,6 +61,15 @@ private FormattedMessage GetDamageExamine(DamageSpecifier damageSpecifier, strin } else { + if (damageSpecifier.DamageDict.Count == 1) + { + // May be simplified to using a foreach(Var x) despite being only one item + if(damageSpecifier.DamageDict.Values.GetEnumerator().Current == FixedPoint2.Zero) + { + msg.AddMarkupOrThrow(Loc.GetString("damage-none")); + return msg; + } + } msg.AddMarkupOrThrow(Loc.GetString("damage-examine-type", ("type", type))); } diff --git a/Resources/Locale/en-US/damage/damage-examine.ftl b/Resources/Locale/en-US/damage/damage-examine.ftl index 974b8fa9650..848adeb31a0 100644 --- a/Resources/Locale/en-US/damage/damage-examine.ftl +++ b/Resources/Locale/en-US/damage/damage-examine.ftl @@ -10,3 +10,4 @@ damage-throw = throw damage-examine = It does the following damage: damage-examine-type = It does the following [color=cyan]{$type}[/color] damage: damage-value = - [color=red]{$amount}[/color] units of [color=yellow]{$type}[/color]. +damage-none = It does no damage. From 33516b77edd63b1254559787f02bbc029719b23c Mon Sep 17 00:00:00 2001 From: Justice League Date: Thu, 31 Oct 2024 13:28:17 -0400 Subject: [PATCH 033/218] Fixed minor spelling mistake --- .../Entities/Objects/Weapons/Guns/Basic/watergun.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/watergun.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/watergun.yml index c96a1522d2e..55adfb7ba6c 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/watergun.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/watergun.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity id: WeaponWaterGunBase abstract: true parent: BaseItem @@ -71,7 +71,7 @@ id: WeaponWaterBlaster parent: WeaponWaterGunBase name: water blaster - description: With this bad boy, you'll be the cooleste kid at the summer barbecue. + description: With this bad boy, you'll be the coolest kid at the summer barbecue. components: - type: Gun cameraRecoilScalar: 0 #no recoil From 2801ebea8d11548511744556622ebfcfe389931d Mon Sep 17 00:00:00 2001 From: Preston Smith Date: Sun, 10 Nov 2024 00:45:54 -0600 Subject: [PATCH 034/218] Optimization! --- Content.Shared/Damage/Systems/DamageExamineSystem.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Content.Shared/Damage/Systems/DamageExamineSystem.cs b/Content.Shared/Damage/Systems/DamageExamineSystem.cs index 72c1af29b22..fe97a40d1b5 100644 --- a/Content.Shared/Damage/Systems/DamageExamineSystem.cs +++ b/Content.Shared/Damage/Systems/DamageExamineSystem.cs @@ -61,15 +61,12 @@ private FormattedMessage GetDamageExamine(DamageSpecifier damageSpecifier, strin } else { - if (damageSpecifier.DamageDict.Count == 1) + if (damageSpecifier.GetTotal() == FixedPoint2.Zero && !damageSpecifier.AnyPositive()) { - // May be simplified to using a foreach(Var x) despite being only one item - if(damageSpecifier.DamageDict.Values.GetEnumerator().Current == FixedPoint2.Zero) - { - msg.AddMarkupOrThrow(Loc.GetString("damage-none")); - return msg; - } + msg.AddMarkupOrThrow(Loc.GetString("damage-none")); + return msg; } + msg.AddMarkupOrThrow(Loc.GetString("damage-examine-type", ("type", type))); } From 36aceb178c855b381cb9b5868e4348fde1bedbd0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 29 Oct 2024 01:34:40 +0100 Subject: [PATCH 035/218] Database SnakeCaseNaming fixes Fixes formatting of owned entity type property names. These are normally named "FooBar_Baz" by EF Core, but the snake case thing was turning them into "foo_bar__baz". The double underscore is now fixed. We don't *yet* have any EF Core owned entity in use, but I am planning to add one. I don't know if downstreams are using any so this should still be marked as a breaking change. Also fixed it creating and dropping a Compiled Regex instance for every name, the regex is now cached (and pregenerated). --- Content.Server.Database/SnakeCaseNaming.cs | 40 +++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/Content.Server.Database/SnakeCaseNaming.cs b/Content.Server.Database/SnakeCaseNaming.cs index 27ce392cd50..3a67ffb9cd1 100644 --- a/Content.Server.Database/SnakeCaseNaming.cs +++ b/Content.Server.Database/SnakeCaseNaming.cs @@ -82,7 +82,7 @@ public ConventionSet ModifyConventions(ConventionSet conventionSet) } } - public class SnakeCaseConvention : + public partial class SnakeCaseConvention : IEntityTypeAddedConvention, IEntityTypeAnnotationChangedConvention, IPropertyAddedConvention, @@ -99,22 +99,27 @@ public SnakeCaseConvention() {} public static string RewriteName(string name) { - var regex = new Regex("[A-Z]+", RegexOptions.Compiled); - return regex.Replace( - name, - (Match match) => { - if (match.Index == 0 && (match.Value == "FK" || match.Value == "PK" || match.Value == "IX")) { - return match.Value; + return UpperCaseLocator() + .Replace( + name, + (Match match) => { + if (match.Index == 0 && (match.Value == "FK" || match.Value == "PK" || match.Value == "IX")) { + return match.Value; + } + if (match.Value == "HWI") + return (match.Index == 0 ? "" : "_") + "hwi"; + if (match.Index == 0) + return match.Value.ToLower(); + if (match.Length > 1) + return $"_{match.Value[..^1].ToLower()}_{match.Value[^1..^0].ToLower()}"; + + // Do not add a _ if there is already one before this. This happens with owned entities. + if (name[match.Index - 1] == '_') + return match.Value.ToLower(); + + return "_" + match.Value.ToLower(); } - if (match.Value == "HWI") - return (match.Index == 0 ? "" : "_") + "hwi"; - if (match.Index == 0) - return match.Value.ToLower(); - if (match.Length > 1) - return $"_{match.Value[..^1].ToLower()}_{match.Value[^1..^0].ToLower()}"; - return "_" + match.Value.ToLower(); - } - ); + ); } public virtual void ProcessEntityTypeAdded( @@ -332,5 +337,8 @@ private static void RewriteColumnName(IConventionPropertyBuilder propertyBuilder } } } + + [GeneratedRegex("[A-Z]+", RegexOptions.Compiled)] + private static partial Regex UpperCaseLocator(); } } From 4f3db43696fbcc9144d2ad011bbb5f93f6deca7a Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 12 Nov 2024 01:51:23 +0100 Subject: [PATCH 036/218] Integrate Modern HWID into content This should be the primary changes for the future-proof "Modern HWID" system implemented into Robust and the auth server. HWIDs in the database have been given an additional column representing their version, legacy or modern. This is implemented via an EF Core owned entity. By manually setting the column name of the main value column, we can keep DB compatibility and the migration is just adding some type columns. This new HWID type has to be plumbed through everywhere, resulting in some breaking changes for the DB layer and such. New bans and player records are placed with the new modern HWID. Old bans are still checked against legacy HWIDs. Modern HWIDs are presented with a "V2-" prefix to admins, to allow distinguishing them. This is also integrated into the parsing logic for placing new bans. There's also some code cleanup to reduce copy pasting around the place from my changes. Requires latest engine to support ImmutableArray in NetSerializer. --- .../UI/BanPanel/BanPanel.xaml.cs | 11 +- .../Tests/Commands/PardonCommand.cs | 24 +- .../20241111170112_ModernHwid.Designer.cs | 2072 ++++++++++++++++ .../Postgres/20241111170112_ModernHwid.cs | 62 + ...20241111193608_ConnectionTrust.Designer.cs | 2076 +++++++++++++++++ .../20241111193608_ConnectionTrust.cs | 29 + .../PostgresServerDbContextModelSnapshot.cs | 162 +- .../20241111170107_ModernHwid.Designer.cs | 1995 ++++++++++++++++ .../Sqlite/20241111170107_ModernHwid.cs | 62 + ...20241111193602_ConnectionTrust.Designer.cs | 1999 ++++++++++++++++ .../Sqlite/20241111193602_ConnectionTrust.cs | 29 + .../SqliteServerDbContextModelSnapshot.cs | 161 +- Content.Server.Database/Model.cs | 88 +- .../Administration/BanList/BanListEui.cs | 8 +- Content.Server/Administration/BanPanelEui.cs | 10 +- .../Administration/Commands/BanListCommand.cs | 2 +- .../Commands/RoleBanListCommand.cs | 2 +- .../Administration/Managers/BanManager.cs | 12 +- .../Administration/Managers/IBanManager.cs | 4 +- .../Administration/PlayerLocator.cs | 99 +- .../Administration/PlayerPanelEui.cs | 4 +- .../Administration/Systems/BwoinkSystem.cs | 2 +- .../Connection/ConnectionManager.cs | 13 +- Content.Server/Connection/UserDataExt.cs | 24 + Content.Server/Database/BanMatcher.cs | 33 +- Content.Server/Database/DatabaseRecords.cs | 3 +- Content.Server/Database/ServerBanDef.cs | 5 +- Content.Server/Database/ServerDbBase.cs | 24 +- Content.Server/Database/ServerDbManager.cs | 35 +- Content.Server/Database/ServerDbPostgres.cs | 124 +- Content.Server/Database/ServerDbSqlite.cs | 55 +- Content.Server/Database/ServerRoleBanDef.cs | 5 +- Content.Shared.Database/TypedHwid.cs | 62 + .../Administration/BanPanelEuiState.cs | 4 +- 34 files changed, 9059 insertions(+), 241 deletions(-) create mode 100644 Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.cs create mode 100644 Content.Server/Connection/UserDataExt.cs create mode 100644 Content.Shared.Database/TypedHwid.cs diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs index 588d62e5603..3c7322d4739 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -22,11 +22,11 @@ namespace Content.Client.Administration.UI.BanPanel; [GenerateTypedNameReferences] public sealed partial class BanPanel : DefaultWindow { - public event Action? BanSubmitted; + public event Action? BanSubmitted; public event Action? PlayerChanged; private string? PlayerUsername { get; set; } private (IPAddress, int)? IpAddress { get; set; } - private byte[]? Hwid { get; set; } + private ImmutableTypedHwid? Hwid { get; set; } private double TimeEntered { get; set; } private uint Multiplier { get; set; } private bool HasBanFlag { get; set; } @@ -371,9 +371,8 @@ private void OnIpChanged() private void OnHwidChanged() { var hwidString = HwidLine.Text; - var length = 3 * (hwidString.Length / 4) - hwidString.TakeLast(2).Count(c => c == '='); - Hwid = new byte[length]; - if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !Convert.TryFromBase64String(hwidString, Hwid, out _)) + ImmutableTypedHwid? hwid = null; + if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !ImmutableTypedHwid.TryParse(hwidString, out hwid)) { ErrorLevel |= ErrorLevelEnum.Hwid; HwidLine.ModulateSelfOverride = Color.Red; @@ -390,7 +389,7 @@ private void OnHwidChanged() Hwid = null; return; } - Hwid = Convert.FromHexString(hwidString); + Hwid = hwid; } private void OnTypeChanged() diff --git a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs index 4db9eabf5c6..9e57cd4b0e6 100644 --- a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs +++ b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs @@ -32,9 +32,9 @@ public async Task PardonTest() // No bans on record Assert.Multiple(async () => { - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Is.Empty); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty); }); // Try to pardon a ban that does not exist @@ -43,9 +43,9 @@ public async Task PardonTest() // Still no bans on record Assert.Multiple(async () => { - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Is.Empty); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty); }); var banReason = "test"; @@ -57,9 +57,9 @@ public async Task PardonTest() // Should have one ban on record now Assert.Multiple(async () => { - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Not.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); }); await pair.RunTicksSync(5); @@ -70,13 +70,13 @@ public async Task PardonTest() await server.WaitPost(() => sConsole.ExecuteCommand("pardon 2")); // The existing ban is unaffected - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Not.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null); var ban = await sDatabase.GetServerBanAsync(1); Assert.Multiple(async () => { Assert.That(ban, Is.Not.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); // Check that it matches Assert.That(ban.Id, Is.EqualTo(1)); @@ -95,7 +95,7 @@ public async Task PardonTest() await server.WaitPost(() => sConsole.ExecuteCommand("pardon 1")); // No bans should be returned - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null); // Direct id lookup returns a pardoned ban var pardonedBan = await sDatabase.GetServerBanAsync(1); @@ -105,7 +105,7 @@ public async Task PardonTest() Assert.That(pardonedBan, Is.Not.Null); // The list is still returned since that ignores pardons - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); Assert.That(pardonedBan.Id, Is.EqualTo(1)); Assert.That(pardonedBan.UserId, Is.EqualTo(clientId)); @@ -133,13 +133,13 @@ public async Task PardonTest() Assert.Multiple(async () => { // No bans should be returned - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null); // Direct id lookup returns a pardoned ban Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); // The list is still returned since that ignores pardons - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); }); // Reconnect client. Slightly faster than dirtying the pair. diff --git a/Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.Designer.cs b/Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.Designer.cs new file mode 100644 index 00000000000..155d6a163fd --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.Designer.cs @@ -0,0 +1,2072 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20241111170112_ModernHwid")] + partial class ModernHwid + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_template_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("interval") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("integer") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.cs b/Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.cs new file mode 100644 index 00000000000..c70a5ffaa58 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241111170112_ModernHwid.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class ModernHwid : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "hwid_type", + table: "server_role_ban", + type: "integer", + nullable: true, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "hwid_type", + table: "server_ban", + type: "integer", + nullable: true, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "last_seen_hwid_type", + table: "player", + type: "integer", + nullable: true, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "hwid_type", + table: "connection_log", + type: "integer", + nullable: true, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "hwid_type", + table: "server_role_ban"); + + migrationBuilder.DropColumn( + name: "hwid_type", + table: "server_ban"); + + migrationBuilder.DropColumn( + name: "last_seen_hwid_type", + table: "player"); + + migrationBuilder.DropColumn( + name: "hwid_type", + table: "connection_log"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.Designer.cs b/Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.Designer.cs new file mode 100644 index 00000000000..dc1b4a0eeb7 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.Designer.cs @@ -0,0 +1,2076 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20241111193608_ConnectionTrust")] + partial class ConnectionTrust + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_template_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("interval") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("Trust") + .HasColumnType("real") + .HasColumnName("trust"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("integer") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.cs b/Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.cs new file mode 100644 index 00000000000..debb36aaccb --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241111193608_ConnectionTrust.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class ConnectionTrust : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "trust", + table: "connection_log", + type: "real", + nullable: false, + defaultValue: 0f); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "trust", + table: "connection_log"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index 1f64f6e51a5..75444386311 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -512,20 +512,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("assigned_user_id", (string)null); }); - modelBuilder.Entity("Content.Server.Database.Blacklist", - b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("UserId") - .HasName("PK_blacklist"); - - b.ToTable("blacklist", (string) null); - }); - modelBuilder.Entity("Content.Server.Database.BanTemplate", b => { b.Property("Id") @@ -571,6 +557,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("ban_template", (string)null); }); + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => { b.Property("Id") @@ -589,10 +588,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("smallint") .HasColumnName("denied"); - b.Property("HWId") - .HasColumnType("bytea") - .HasColumnName("hwid"); - b.Property("ServerId") .ValueGeneratedOnAdd() .HasColumnType("integer") @@ -603,6 +598,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("timestamp with time zone") .HasColumnName("time"); + b.Property("Trust") + .HasColumnType("real") + .HasColumnName("trust"); + b.Property("UserId") .HasColumnType("uuid") .HasColumnName("user_id"); @@ -718,10 +717,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("inet") .HasColumnName("last_seen_address"); - b.Property("LastSeenHWId") - .HasColumnType("bytea") - .HasColumnName("last_seen_hwid"); - b.Property("LastSeenTime") .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_time"); @@ -1058,10 +1053,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("timestamp with time zone") .HasColumnName("expiration_time"); - b.Property("HWId") - .HasColumnType("bytea") - .HasColumnName("hwid"); - b.Property("Hidden") .HasColumnType("boolean") .HasColumnName("hidden"); @@ -1192,10 +1183,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("timestamp with time zone") .HasColumnName("expiration_time"); - b.Property("HWId") - .HasColumnType("bytea") - .HasColumnName("hwid"); - b.Property("Hidden") .HasColumnType("boolean") .HasColumnName("hidden"); @@ -1637,6 +1624,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasConstraintName("FK_connection_log_server_server_id"); + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + b.Navigation("Server"); }); @@ -1652,6 +1667,37 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("integer") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + modelBuilder.Entity("Content.Server.Database.Profile", b => { b.HasOne("Content.Server.Database.Preference", "Preference") @@ -1746,8 +1792,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("RoundId") .HasConstraintName("FK_server_ban_round_round_id"); + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + b.Navigation("CreatedBy"); + b.Navigation("HWId"); + b.Navigation("LastEditedBy"); b.Navigation("Round"); @@ -1795,8 +1869,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("RoundId") .HasConstraintName("FK_server_role_ban_round_round_id"); + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + b.Navigation("CreatedBy"); + b.Navigation("HWId"); + b.Navigation("LastEditedBy"); b.Navigation("Round"); diff --git a/Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.Designer.cs new file mode 100644 index 00000000000..56a9fe0a059 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.Designer.cs @@ -0,0 +1,1995 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20241111170107_ModernHwid")] + partial class ModernHwid + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_template_id"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("TEXT") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.cs b/Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.cs new file mode 100644 index 00000000000..97b5dafd03f --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241111170107_ModernHwid.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class ModernHwid : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "hwid_type", + table: "server_role_ban", + type: "INTEGER", + nullable: true, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "hwid_type", + table: "server_ban", + type: "INTEGER", + nullable: true, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "last_seen_hwid_type", + table: "player", + type: "INTEGER", + nullable: true, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "hwid_type", + table: "connection_log", + type: "INTEGER", + nullable: true, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "hwid_type", + table: "server_role_ban"); + + migrationBuilder.DropColumn( + name: "hwid_type", + table: "server_ban"); + + migrationBuilder.DropColumn( + name: "last_seen_hwid_type", + table: "player"); + + migrationBuilder.DropColumn( + name: "hwid_type", + table: "connection_log"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.Designer.cs new file mode 100644 index 00000000000..bd4e20a464b --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.Designer.cs @@ -0,0 +1,1999 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20241111193602_ConnectionTrust")] + partial class ConnectionTrust + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_template_id"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("TEXT") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("Trust") + .HasColumnType("REAL") + .HasColumnName("trust"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.cs b/Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.cs new file mode 100644 index 00000000000..3a7fd784e16 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241111193602_ConnectionTrust.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class ConnectionTrust : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "trust", + table: "connection_log", + type: "REAL", + nullable: false, + defaultValue: 0f); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "trust", + table: "connection_log"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index 02d44163027..c63127874cc 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -483,19 +483,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("assigned_user_id", (string)null); }); - modelBuilder.Entity("Content.Server.Database.Blacklist", - b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.HasKey("UserId") - .HasName("PK_blacklist"); - - b.ToTable("blacklist", (string) null); - }); modelBuilder.Entity("Content.Server.Database.BanTemplate", b => { b.Property("Id") @@ -539,6 +526,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("ban_template", (string)null); }); + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => { b.Property("Id") @@ -555,10 +555,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER") .HasColumnName("denied"); - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - b.Property("ServerId") .ValueGeneratedOnAdd() .HasColumnType("INTEGER") @@ -569,6 +565,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT") .HasColumnName("time"); + b.Property("Trust") + .HasColumnType("REAL") + .HasColumnName("trust"); + b.Property("UserId") .HasColumnType("TEXT") .HasColumnName("user_id"); @@ -675,10 +675,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT") .HasColumnName("last_seen_address"); - b.Property("LastSeenHWId") - .HasColumnType("BLOB") - .HasColumnName("last_seen_hwid"); - b.Property("LastSeenTime") .HasColumnType("TEXT") .HasColumnName("last_seen_time"); @@ -996,10 +992,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT") .HasColumnName("expiration_time"); - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - b.Property("Hidden") .HasColumnType("INTEGER") .HasColumnName("hidden"); @@ -1124,10 +1116,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT") .HasColumnName("expiration_time"); - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - b.Property("Hidden") .HasColumnType("INTEGER") .HasColumnName("hidden"); @@ -1559,6 +1547,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasConstraintName("FK_connection_log_server_server_id"); + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + b.Navigation("Server"); }); @@ -1574,6 +1590,37 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + modelBuilder.Entity("Content.Server.Database.Profile", b => { b.HasOne("Content.Server.Database.Preference", "Preference") @@ -1668,8 +1715,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("RoundId") .HasConstraintName("FK_server_ban_round_round_id"); + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + b.Navigation("CreatedBy"); + b.Navigation("HWId"); + b.Navigation("LastEditedBy"); b.Navigation("Round"); @@ -1717,8 +1792,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("RoundId") .HasConstraintName("FK_server_role_ban_round_round_id"); + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + b.Navigation("CreatedBy"); + b.Navigation("HWId"); + b.Navigation("LastEditedBy"); b.Navigation("Round"); diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 00b3cfea032..9190475b153 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Text.Json; @@ -327,6 +329,47 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(w => w.PlayerUserId) .HasPrincipalKey(p => p.UserId) .OnDelete(DeleteBehavior.Cascade); + + // Changes for modern HWID integration + modelBuilder.Entity() + .OwnsOne(p => p.LastSeenHWId) + .Property(p => p.Hwid) + .HasColumnName("last_seen_hwid"); + + modelBuilder.Entity() + .OwnsOne(p => p.LastSeenHWId) + .Property(p => p.Type) + .HasDefaultValue(HwidType.Legacy); + + modelBuilder.Entity() + .OwnsOne(p => p.HWId) + .Property(p => p.Hwid) + .HasColumnName("hwid"); + + modelBuilder.Entity() + .OwnsOne(p => p.HWId) + .Property(p => p.Type) + .HasDefaultValue(HwidType.Legacy); + + modelBuilder.Entity() + .OwnsOne(p => p.HWId) + .Property(p => p.Hwid) + .HasColumnName("hwid"); + + modelBuilder.Entity() + .OwnsOne(p => p.HWId) + .Property(p => p.Type) + .HasDefaultValue(HwidType.Legacy); + + modelBuilder.Entity() + .OwnsOne(p => p.HWId) + .Property(p => p.Hwid) + .HasColumnName("hwid"); + + modelBuilder.Entity() + .OwnsOne(p => p.HWId) + .Property(p => p.Type) + .HasDefaultValue(HwidType.Legacy); } public virtual IQueryable SearchLogs(IQueryable query, string searchText) @@ -519,7 +562,7 @@ public class Player public string LastSeenUserName { get; set; } = null!; public DateTime LastSeenTime { get; set; } public IPAddress LastSeenAddress { get; set; } = null!; - public byte[]? LastSeenHWId { get; set; } + public TypedHwid? LastSeenHWId { get; set; } // Data that changes with each round public List Rounds { get; set; } = null!; @@ -668,7 +711,7 @@ public interface IBanCommon where TUnban : IUnbanCommon int Id { get; set; } Guid? PlayerUserId { get; set; } NpgsqlInet? Address { get; set; } - byte[]? HWId { get; set; } + TypedHwid? HWId { get; set; } DateTime BanTime { get; set; } DateTime? ExpirationTime { get; set; } string Reason { get; set; } @@ -753,7 +796,7 @@ public class ServerBan : IBanCommon /// /// Hardware ID of the banned player. /// - public byte[]? HWId { get; set; } + public TypedHwid? HWId { get; set; } /// /// The time when the ban was applied by an administrator. @@ -891,7 +934,7 @@ public class ConnectionLog public DateTime Time { get; set; } public IPAddress Address { get; set; } = null!; - public byte[]? HWId { get; set; } + public TypedHwid? HWId { get; set; } public ConnectionDenyReason? Denied { get; set; } @@ -908,6 +951,8 @@ public class ConnectionLog public List BanHits { get; set; } = null!; public Server Server { get; set; } = null!; + + public float Trust { get; set; } } public enum ConnectionDenyReason : byte @@ -945,7 +990,7 @@ public sealed class ServerRoleBan : IBanCommon public Guid? PlayerUserId { get; set; } [Required] public TimeSpan PlaytimeAtNote { get; set; } public NpgsqlInet? Address { get; set; } - public byte[]? HWId { get; set; } + public TypedHwid? HWId { get; set; } public DateTime BanTime { get; set; } @@ -1206,4 +1251,37 @@ public sealed class BanTemplate /// public bool Hidden { get; set; } } + + /// + /// A hardware ID value together with its . + /// + /// + [Owned] + public sealed class TypedHwid + { + public byte[] Hwid { get; set; } = default!; + public HwidType Type { get; set; } + + [return: NotNullIfNotNull(nameof(immutable))] + public static implicit operator TypedHwid?(ImmutableTypedHwid? immutable) + { + if (immutable == null) + return null; + + return new TypedHwid + { + Hwid = immutable.Hwid.ToArray(), + Type = immutable.Type, + }; + } + + [return: NotNullIfNotNull(nameof(hwid))] + public static implicit operator ImmutableTypedHwid?(TypedHwid? hwid) + { + if (hwid == null) + return null; + + return new ImmutableTypedHwid(hwid.Hwid.ToImmutableArray(), hwid.Type); + } + } } diff --git a/Content.Server/Administration/BanList/BanListEui.cs b/Content.Server/Administration/BanList/BanListEui.cs index 8ddc7459d7b..2ca126bf164 100644 --- a/Content.Server/Administration/BanList/BanListEui.cs +++ b/Content.Server/Administration/BanList/BanListEui.cs @@ -54,7 +54,7 @@ private void OnPermsChanged(AdminPermsChangedEventArgs args) private async Task LoadBans(NetUserId userId) { - foreach (var ban in await _db.GetServerBansAsync(null, userId, null)) + foreach (var ban in await _db.GetServerBansAsync(null, userId, null, null)) { SharedServerUnban? unban = null; if (ban.Unban is { } unbanDef) @@ -74,7 +74,7 @@ private async Task LoadBans(NetUserId userId) ? (address.address.ToString(), address.cidrMask) : null; - hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()); + hwid = ban.HWId?.ToString(); } Bans.Add(new SharedServerBan( @@ -95,7 +95,7 @@ private async Task LoadBans(NetUserId userId) private async Task LoadRoleBans(NetUserId userId) { - foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null)) + foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null, null)) { SharedServerUnban? unban = null; if (ban.Unban is { } unbanDef) @@ -115,7 +115,7 @@ private async Task LoadRoleBans(NetUserId userId) ? (address.address.ToString(), address.cidrMask) : null; - hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()); + hwid = ban.HWId?.ToString(); } RoleBans.Add(new SharedServerRoleBan( ban.Id, diff --git a/Content.Server/Administration/BanPanelEui.cs b/Content.Server/Administration/BanPanelEui.cs index e746e9c725f..3eedad3ed50 100644 --- a/Content.Server/Administration/BanPanelEui.cs +++ b/Content.Server/Administration/BanPanelEui.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using System.Net; using System.Net.Sockets; using Content.Server.Administration.Managers; @@ -8,7 +7,6 @@ using Content.Shared.Administration; using Content.Shared.Database; using Content.Shared.Eui; -using Robust.Server.Player; using Robust.Shared.Network; namespace Content.Server.Administration; @@ -27,7 +25,7 @@ public sealed class BanPanelEui : BaseEui private NetUserId? PlayerId { get; set; } private string PlayerName { get; set; } = string.Empty; private IPAddress? LastAddress { get; set; } - private ImmutableArray? LastHwid { get; set; } + private ImmutableTypedHwid? LastHwid { get; set; } private const int Ipv4_CIDR = 32; private const int Ipv6_CIDR = 64; @@ -51,7 +49,7 @@ public override void HandleMessage(EuiMessageBase msg) switch (msg) { case BanPanelEuiStateMsg.CreateBanRequest r: - BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid?.ToImmutableArray(), r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase); + BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid, r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase); break; case BanPanelEuiStateMsg.GetPlayerInfoRequest r: ChangePlayer(r.PlayerUsername); @@ -59,7 +57,7 @@ public override void HandleMessage(EuiMessageBase msg) } } - private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableArray? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection? roles, bool erase) + private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableTypedHwid? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection? roles, bool erase) { if (!_admins.HasAdminFlag(Player, AdminFlags.Ban)) { @@ -155,7 +153,7 @@ public async void ChangePlayer(string playerNameOrId) ChangePlayer(located?.UserId, located?.Username ?? string.Empty, located?.LastAddress, located?.LastHWId); } - public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableArray? lastHwid) + public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableTypedHwid? lastHwid) { PlayerId = playerId; PlayerName = playerName; diff --git a/Content.Server/Administration/Commands/BanListCommand.cs b/Content.Server/Administration/Commands/BanListCommand.cs index a5bc97dce3e..2f7093ae1d8 100644 --- a/Content.Server/Administration/Commands/BanListCommand.cs +++ b/Content.Server/Administration/Commands/BanListCommand.cs @@ -38,7 +38,7 @@ public override async void Execute(IConsoleShell shell, string argStr, string[] if (shell.Player is not { } player) { - var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastHWId, false); + var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false); if (bans.Count == 0) { diff --git a/Content.Server/Administration/Commands/RoleBanListCommand.cs b/Content.Server/Administration/Commands/RoleBanListCommand.cs index 30bb3073add..8244ded3b20 100644 --- a/Content.Server/Administration/Commands/RoleBanListCommand.cs +++ b/Content.Server/Administration/Commands/RoleBanListCommand.cs @@ -48,7 +48,7 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) if (shell.Player is not { } player) { - var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastHWId, includeUnbanned); + var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned); if (bans.Count == 0) { diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 1cdfb822242..2e21710e51d 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -65,7 +65,8 @@ private async Task CachePlayerData(ICommonSession player, CancellationToken canc var netChannel = player.Channel; ImmutableArray? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId; - var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, false); + var modernHwids = netChannel.UserData.ModernHWIds; + var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, modernHwids, false); var userRoleBans = new List(); foreach (var ban in roleBans) @@ -132,7 +133,7 @@ public void Restart() } #region Server Bans - public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray? hwid, uint? minutes, NoteSeverity severity, string reason) + public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason) { DateTimeOffset? expires = null; if (minutes > 0) @@ -166,9 +167,7 @@ public async void CreateServerBan(NetUserId? target, string? targetUsername, Net var addressRangeString = addressRange != null ? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}" : "null"; - var hwidString = hwid != null - ? string.Concat(hwid.Value.Select(x => x.ToString("x2"))) - : "null"; + var hwidString = hwid?.ToString() ?? "null"; var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}"; var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii"; @@ -208,6 +207,7 @@ private bool BanMatchesPlayer(ICommonSession player, ServerBanDef ban) UserId = player.UserId, Address = player.Channel.RemoteEndPoint.Address, HWId = player.Channel.UserData.HWId, + ModernHWIds = player.Channel.UserData.ModernHWIds, // It's possible for the player to not have cached data loading yet due to coincidental timing. // If this is the case, we assume they have all flags to avoid false-positives. ExemptFlags = _cachedBanExemptions.GetValueOrDefault(player, ServerBanExemptFlags.All), @@ -228,7 +228,7 @@ private void KickForBanDef(ICommonSession player, ServerBanDef def) #region Job Bans // If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin. // Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset. - public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan) + public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan) { if (!_prototypeManager.TryIndex(role, out JobPrototype? _)) { diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs index c11e310a825..fc192cc3066 100644 --- a/Content.Server/Administration/Managers/IBanManager.cs +++ b/Content.Server/Administration/Managers/IBanManager.cs @@ -24,7 +24,7 @@ public interface IBanManager /// Number of minutes to ban for. 0 and null mean permanent /// Severity of the resulting ban note /// Reason for the ban - public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray? hwid, uint? minutes, NoteSeverity severity, string reason); + public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason); public HashSet? GetRoleBans(NetUserId playerUserId); public HashSet>? GetJobBans(NetUserId playerUserId); @@ -37,7 +37,7 @@ public interface IBanManager /// Reason for the ban /// Number of minutes to ban for. 0 and null mean permanent /// Time when the ban was applied, used for grouping role bans - public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan); + public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan); /// /// Pardons a role ban for the specified target, username or GUID diff --git a/Content.Server/Administration/PlayerLocator.cs b/Content.Server/Administration/PlayerLocator.cs index 64a85f19ad0..25cc7714686 100644 --- a/Content.Server/Administration/PlayerLocator.cs +++ b/Content.Server/Administration/PlayerLocator.cs @@ -5,16 +5,42 @@ using System.Net.Http.Json; using System.Threading; using System.Threading.Tasks; +using Content.Server.Connection; using Content.Server.Database; +using Content.Shared.Database; using JetBrains.Annotations; using Robust.Server.Player; using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.Network; +using Robust.Shared.Player; namespace Content.Server.Administration { - public sealed record LocatedPlayerData(NetUserId UserId, IPAddress? LastAddress, ImmutableArray? LastHWId, string Username); + /// + /// Contains data resolved via . + /// + /// The ID of the located user. + /// The last known IP address that the user connected with. + /// + /// The last known HWID that the user connected with. + /// This should be used for placing new records involving HWIDs, such as bans. + /// For looking up data based on HWID, use combined and . + /// + /// The last known username for the user connected with. + /// + /// The last known legacy HWID value this user connected with. Only use for old lookups! + /// + /// + /// The set of last known modern HWIDs the user connected with. + /// + public sealed record LocatedPlayerData( + NetUserId UserId, + IPAddress? LastAddress, + ImmutableTypedHwid? LastHWId, + string Username, + ImmutableArray? LastLegacyHWId, + ImmutableArray> LastModernHWIds); /// /// Utilities for finding user IDs that extend to more than the server database. @@ -67,63 +93,42 @@ public PlayerLocator() { // Check people currently on the server, the easiest case. if (_playerManager.TryGetSessionByUsername(playerName, out var session)) - { - var userId = session.UserId; - var address = session.Channel.RemoteEndPoint.Address; - var hwId = session.Channel.UserData.HWId; - return new LocatedPlayerData(userId, address, hwId, session.Name); - } + return ReturnForSession(session); // Check database for past players. var record = await _db.GetPlayerRecordByUserName(playerName, cancel); if (record != null) - return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName); + return ReturnForPlayerRecord(record); // If all else fails, ask the auth server. var authServer = _configurationManager.GetCVar(CVars.AuthServer); var requestUri = $"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}"; using var resp = await _httpClient.GetAsync(requestUri, cancel); - if (resp.StatusCode == HttpStatusCode.NotFound) - return null; - - if (!resp.IsSuccessStatusCode) - { - _sawmill.Error("Auth server returned bad response {StatusCode}!", resp.StatusCode); - return null; - } - - var responseData = await resp.Content.ReadFromJsonAsync(cancellationToken: cancel); - - if (responseData == null) - { - _sawmill.Error("Auth server returned null response!"); - return null; - } - - return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName); + return await HandleAuthServerResponse(resp, cancel); } public async Task LookupIdAsync(NetUserId userId, CancellationToken cancel = default) { // Check people currently on the server, the easiest case. if (_playerManager.TryGetSessionById(userId, out var session)) - { - var address = session.Channel.RemoteEndPoint.Address; - var hwId = session.Channel.UserData.HWId; - return new LocatedPlayerData(userId, address, hwId, session.Name); - } + return ReturnForSession(session); // Check database for past players. var record = await _db.GetPlayerRecordByUserId(userId, cancel); if (record != null) - return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName); + return ReturnForPlayerRecord(record); // If all else fails, ask the auth server. var authServer = _configurationManager.GetCVar(CVars.AuthServer); var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}"; using var resp = await _httpClient.GetAsync(requestUri, cancel); + return await HandleAuthServerResponse(resp, cancel); + } + + private async Task HandleAuthServerResponse(HttpResponseMessage resp, CancellationToken cancel) + { if (resp.StatusCode == HttpStatusCode.NotFound) return null; @@ -134,14 +139,40 @@ public PlayerLocator() } var responseData = await resp.Content.ReadFromJsonAsync(cancellationToken: cancel); - if (responseData == null) { _sawmill.Error("Auth server returned null response!"); return null; } - return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName); + return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName, null, []); + } + + private static LocatedPlayerData ReturnForSession(ICommonSession session) + { + var userId = session.UserId; + var address = session.Channel.RemoteEndPoint.Address; + var hwId = session.Channel.UserData.GetModernHwid(); + return new LocatedPlayerData( + userId, + address, + hwId, + session.Name, + session.Channel.UserData.HWId, + session.Channel.UserData.ModernHWIds); + } + + private static LocatedPlayerData ReturnForPlayerRecord(PlayerRecord record) + { + var hwid = record.HWId; + + return new LocatedPlayerData( + record.UserId, + record.LastSeenAddress, + hwid, + record.LastSeenUserName, + hwid is { Type: HwidType.Legacy } ? hwid.Hwid : null, + hwid is { Type: HwidType.Modern } ? [hwid.Hwid] : []); } public async Task LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default) diff --git a/Content.Server/Administration/PlayerPanelEui.cs b/Content.Server/Administration/PlayerPanelEui.cs index 4c0df806012..6c304888866 100644 --- a/Content.Server/Administration/PlayerPanelEui.cs +++ b/Content.Server/Administration/PlayerPanelEui.cs @@ -173,11 +173,11 @@ public async void SetPlayerState() { _whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId); // This won't get associated ip or hwid bans but they were not placed on this account anyways - _bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null)).Count; + _bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null, null)).Count; // Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally // The only way to distinguish whether a role ban is the same is to compare the ban time. // This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now. - _roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null)).DistinctBy(rb => rb.BanTime).Count(); + _roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null, null)).DistinctBy(rb => rb.BanTime).Count(); } else { diff --git a/Content.Server/Administration/Systems/BwoinkSystem.cs b/Content.Server/Administration/Systems/BwoinkSystem.cs index 7a47755db9d..4358b7e3876 100644 --- a/Content.Server/Administration/Systems/BwoinkSystem.cs +++ b/Content.Server/Administration/Systems/BwoinkSystem.cs @@ -172,7 +172,7 @@ private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs } // Check if the user has been banned - var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null); + var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null, null); if (ban != null) { var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason)); diff --git a/Content.Server/Connection/ConnectionManager.cs b/Content.Server/Connection/ConnectionManager.cs index 2c1f9fb36f1..e4c7cf0be2e 100644 --- a/Content.Server/Connection/ConnectionManager.cs +++ b/Content.Server/Connection/ConnectionManager.cs @@ -111,11 +111,14 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e) var serverId = (await _serverDbEntry.ServerEntity).Id; + var hwid = e.UserData.GetModernHwid(); + var trust = e.UserData.Trust; + if (deny != null) { var (reason, msg, banHits) = deny.Value; - var id = await _db.AddConnectionLogAsync(userId, e.UserName, addr, e.UserData.HWId, reason, serverId); + var id = await _db.AddConnectionLogAsync(userId, e.UserName, addr, hwid, trust, reason, serverId); if (banHits is { Count: > 0 }) await _db.AddServerBanHitsAsync(id, banHits); @@ -127,12 +130,12 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e) } else { - await _db.AddConnectionLogAsync(userId, e.UserName, addr, e.UserData.HWId, null, serverId); + await _db.AddConnectionLogAsync(userId, e.UserName, addr, hwid, trust, null, serverId); if (!ServerPreferencesManager.ShouldStorePrefs(e.AuthType)) return; - await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr, e.UserData.HWId); + await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr, hwid); } } @@ -190,7 +193,9 @@ session.Status is SessionStatus.Connected or SessionStatus.InGame hwId = null; } - var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false); + var modernHwid = e.UserData.ModernHWIds; + + var bans = await _db.GetServerBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false); if (bans.Count > 0) { var firstBan = bans[0]; diff --git a/Content.Server/Connection/UserDataExt.cs b/Content.Server/Connection/UserDataExt.cs new file mode 100644 index 00000000000..a409f79a75d --- /dev/null +++ b/Content.Server/Connection/UserDataExt.cs @@ -0,0 +1,24 @@ +using Content.Shared.Database; +using Robust.Shared.Network; + +namespace Content.Server.Connection; + +/// +/// Helper functions for working with . +/// +public static class UserDataExt +{ + /// + /// Get the preferred HWID that should be used for new records related to a player. + /// + /// + /// Players can have zero or more HWIDs, but for logging things like connection logs we generally + /// only want a single one. This method returns a nullable method. + /// + public static ImmutableTypedHwid? GetModernHwid(this NetUserData userData) + { + return userData.ModernHWIds.Length == 0 + ? null + : new ImmutableTypedHwid(userData.ModernHWIds[0], HwidType.Modern); + } +} diff --git a/Content.Server/Database/BanMatcher.cs b/Content.Server/Database/BanMatcher.cs index e58e5b0b5f6..f477ccd822a 100644 --- a/Content.Server/Database/BanMatcher.cs +++ b/Content.Server/Database/BanMatcher.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using System.Net; using Content.Server.IP; +using Content.Shared.Database; using Robust.Shared.Network; namespace Content.Server.Database; @@ -52,9 +53,28 @@ public static bool BanMatches(ServerBanDef ban, in PlayerInfo player) return true; } - return player.HWId is { Length: > 0 } hwIdVar - && ban.HWId != null - && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Value.AsSpan()); + switch (ban.HWId?.Type) + { + case HwidType.Legacy: + if (player.HWId is { Length: > 0 } hwIdVar + && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan())) + { + return true; + } + break; + case HwidType.Modern: + if (player.ModernHWIds is { Length: > 0 } modernHwIdVar) + { + foreach (var hwid in modernHwIdVar) + { + if (hwid.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan())) + return true; + } + } + break; + } + + return false; } /// @@ -73,10 +93,15 @@ public struct PlayerInfo public IPAddress? Address; /// - /// The hardware ID of the player. + /// The LEGACY hardware ID of the player. Corresponds with . /// public ImmutableArray? HWId; + /// + /// The modern hardware IDs of the player. Corresponds with . + /// + public ImmutableArray>? ModernHWIds; + /// /// Exemption flags the player has been granted. /// diff --git a/Content.Server/Database/DatabaseRecords.cs b/Content.Server/Database/DatabaseRecords.cs index c0d81147bb4..30fba3434b8 100644 --- a/Content.Server/Database/DatabaseRecords.cs +++ b/Content.Server/Database/DatabaseRecords.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using System.Net; using Content.Shared.Database; using Robust.Shared.Network; @@ -121,7 +120,7 @@ public sealed record PlayerRecord( string LastSeenUserName, DateTimeOffset LastSeenTime, IPAddress LastSeenAddress, - ImmutableArray? HWId); + ImmutableTypedHwid? HWId); public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server); diff --git a/Content.Server/Database/ServerBanDef.cs b/Content.Server/Database/ServerBanDef.cs index 09a960e9a6c..a09f9e959c6 100644 --- a/Content.Server/Database/ServerBanDef.cs +++ b/Content.Server/Database/ServerBanDef.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using System.Net; using Content.Shared.CCVar; using Content.Shared.Database; @@ -13,7 +12,7 @@ public sealed class ServerBanDef public int? Id { get; } public NetUserId? UserId { get; } public (IPAddress address, int cidrMask)? Address { get; } - public ImmutableArray? HWId { get; } + public ImmutableTypedHwid? HWId { get; } public DateTimeOffset BanTime { get; } public DateTimeOffset? ExpirationTime { get; } @@ -28,7 +27,7 @@ public sealed class ServerBanDef public ServerBanDef(int? id, NetUserId? userId, (IPAddress, int)? address, - ImmutableArray? hwId, + TypedHwid? hwId, DateTimeOffset banTime, DateTimeOffset? expirationTime, int? roundId, diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index c85b774e381..723092bdc46 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -388,12 +388,14 @@ public async Task AssignUserIdAsync(string name, NetUserId netUserId) /// /// The ip address of the user. /// The id of the user. - /// The HWId of the user. + /// The legacy HWId of the user. + /// The modern HWIDs of the user. /// The user's latest received un-pardoned ban, or null if none exist. public abstract Task GetServerBanAsync( IPAddress? address, NetUserId? userId, - ImmutableArray? hwId); + ImmutableArray? hwId, + ImmutableArray>? modernHWIds); /// /// Looks up an user's ban history. @@ -402,13 +404,15 @@ public async Task AssignUserIdAsync(string name, NetUserId netUserId) /// /// The ip address of the user. /// The id of the user. - /// The HWId of the user. + /// The legacy HWId of the user. + /// The modern HWIDs of the user. /// Include pardoned and expired bans. /// The user's ban history. public abstract Task> GetServerBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned); public abstract Task AddServerBanAsync(ServerBanDef serverBan); @@ -499,11 +503,13 @@ public async Task GetBanExemption(NetUserId userId, Cancel /// The IP address of the user. /// The NetUserId of the user. /// The Hardware Id of the user. + /// The modern HWIDs of the user. /// Whether expired and pardoned bans are included. /// The user's role ban history. public abstract Task> GetServerRoleBansAsync(IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned); public abstract Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan); @@ -586,7 +592,7 @@ public async Task UpdatePlayerRecord( NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId) + ImmutableTypedHwid? hwId) { await using var db = await GetDb(); @@ -603,7 +609,7 @@ public async Task UpdatePlayerRecord( record.LastSeenTime = DateTime.UtcNow; record.LastSeenAddress = address; record.LastSeenUserName = userName; - record.LastSeenHWId = hwId.ToArray(); + record.LastSeenHWId = hwId; await db.DbContext.SaveChangesAsync(); } @@ -649,7 +655,7 @@ protected async Task PlayerRecordExists(DbGuard db, NetUserId userId) player.LastSeenUserName, new DateTimeOffset(NormalizeDatabaseTime(player.LastSeenTime)), player.LastSeenAddress, - player.LastSeenHWId?.ToImmutableArray()); + player.LastSeenHWId); } #endregion @@ -658,11 +664,11 @@ protected async Task PlayerRecordExists(DbGuard db, NetUserId userId) /* * CONNECTION LOG */ - public abstract Task AddConnectionLogAsync( - NetUserId userId, + public abstract Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId, + ImmutableTypedHwid? hwId, + float trust, ConnectionDenyReason? denied, int serverId); diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 216b1ec1596..be32b43595d 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -69,12 +69,14 @@ Task InitPrefsAsync( /// /// The ip address of the user. /// The id of the user. - /// The hardware ID of the user. + /// The legacy HWID of the user. + /// The modern HWIDs of the user. /// The user's latest received un-pardoned ban, or null if none exist. Task GetServerBanAsync( IPAddress? address, NetUserId? userId, - ImmutableArray? hwId); + ImmutableArray? hwId, + ImmutableArray>? modernHWIds); /// /// Looks up an user's ban history. @@ -82,13 +84,15 @@ Task InitPrefsAsync( /// /// The ip address of the user. /// The id of the user. - /// The HWId of the user. + /// The legacy HWId of the user. + /// The modern HWIDs of the user. /// If true, bans that have been expired or pardoned are also included. /// The user's ban history. Task> GetServerBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned=true); Task AddServerBanAsync(ServerBanDef serverBan); @@ -137,12 +141,14 @@ public Task EditServerBan( /// The IP address of the user. /// The NetUserId of the user. /// The Hardware Id of the user. + /// The modern HWIDs of the user. /// Whether expired and pardoned bans are included. /// The user's role ban history. Task> GetServerRoleBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned = true); Task AddServerRoleBanAsync(ServerRoleBanDef serverBan); @@ -180,7 +186,7 @@ Task UpdatePlayerRecordAsync( NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId); + ImmutableTypedHwid? hwId); Task GetPlayerRecordByUserName(string userName, CancellationToken cancel = default); Task GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel = default); #endregion @@ -191,7 +197,8 @@ Task AddConnectionLogAsync( NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId, + ImmutableTypedHwid? hwId, + float trust, ConnectionDenyReason? denied, int serverId); @@ -480,20 +487,22 @@ public Task AssignUserIdAsync(string name, NetUserId userId) public Task GetServerBanAsync( IPAddress? address, NetUserId? userId, - ImmutableArray? hwId) + ImmutableArray? hwId, + ImmutableArray>? modernHWIds) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId)); + return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId, modernHWIds)); } public Task> GetServerBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned=true) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, includeUnbanned)); + return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, modernHWIds, includeUnbanned)); } public Task AddServerBanAsync(ServerBanDef serverBan) @@ -537,10 +546,11 @@ public Task> GetServerRoleBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned = true) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, includeUnbanned)); + return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, modernHWIds, includeUnbanned)); } public Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan) @@ -582,7 +592,7 @@ public Task UpdatePlayerRecordAsync( NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId) + ImmutableTypedHwid? hwId) { DbWriteOpsMetric.Inc(); return RunDbCommand(() => _db.UpdatePlayerRecord(userId, userName, address, hwId)); @@ -604,12 +614,13 @@ public Task AddConnectionLogAsync( NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId, + ImmutableTypedHwid? hwId, + float trust, ConnectionDenyReason? denied, int serverId) { DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, denied, serverId)); + return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, trust, denied, serverId)); } public Task AddServerBanHitsAsync(int connection, IEnumerable bans) diff --git a/Content.Server/Database/ServerDbPostgres.cs b/Content.Server/Database/ServerDbPostgres.cs index 7d131f70dc3..c0346708377 100644 --- a/Content.Server/Database/ServerDbPostgres.cs +++ b/Content.Server/Database/ServerDbPostgres.cs @@ -9,6 +9,7 @@ using Content.Server.Administration.Logs; using Content.Server.IP; using Content.Shared.CCVar; +using Content.Shared.Database; using Microsoft.EntityFrameworkCore; using Robust.Shared.Configuration; using Robust.Shared.Network; @@ -73,7 +74,8 @@ public ServerDbPostgres(DbContextOptions options, public override async Task GetServerBanAsync( IPAddress? address, NetUserId? userId, - ImmutableArray? hwId) + ImmutableArray? hwId, + ImmutableArray>? modernHWIds) { if (address == null && userId == null && hwId == null) { @@ -84,7 +86,7 @@ public ServerDbPostgres(DbContextOptions options, var exempt = await GetBanExemptionCore(db, userId); var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value); - var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned: false, exempt, newPlayer) + var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer) .OrderByDescending(b => b.BanTime); var ban = await query.FirstOrDefaultAsync(); @@ -94,7 +96,9 @@ public ServerDbPostgres(DbContextOptions options, public override async Task> GetServerBansAsync(IPAddress? address, NetUserId? userId, - ImmutableArray? hwId, bool includeUnbanned) + ImmutableArray? hwId, + ImmutableArray>? modernHWIds, + bool includeUnbanned) { if (address == null && userId == null && hwId == null) { @@ -105,7 +109,7 @@ public override async Task> GetServerBansAsync(IPAddress? add var exempt = await GetBanExemptionCore(db, userId); var newPlayer = !await db.PgDbContext.Player.AnyAsync(p => p.UserId == userId); - var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned, exempt, newPlayer); + var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer); var queryBans = await query.ToArrayAsync(); var bans = new List(queryBans.Length); @@ -127,6 +131,7 @@ private static IQueryable MakeBanLookupQuery( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, DbGuardImpl db, bool includeUnbanned, ServerBanExemptFlags? exemptFlags, @@ -134,16 +139,11 @@ private static IQueryable MakeBanLookupQuery( { DebugTools.Assert(!(address == null && userId == null && hwId == null)); - IQueryable? query = null; - - if (userId is { } uid) - { - var newQ = db.PgDbContext.Ban - .Include(p => p.Unban) - .Where(b => b.PlayerUserId == uid.UserId); - - query = query == null ? newQ : query.Union(newQ); - } + var query = MakeBanLookupQualityShared( + userId, + hwId, + modernHWIds, + db.PgDbContext.Ban); if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP)) { @@ -156,15 +156,6 @@ private static IQueryable MakeBanLookupQuery( query = query == null ? newQ : query.Union(newQ); } - if (hwId != null && hwId.Value.Length > 0) - { - var newQ = db.PgDbContext.Ban - .Include(p => p.Unban) - .Where(b => b.HWId!.SequenceEqual(hwId.Value.ToArray())); - - query = query == null ? newQ : query.Union(newQ); - } - DebugTools.Assert( query != null, "At least one filter item (IP/UserID/HWID) must have been given to make query not null."); @@ -186,6 +177,49 @@ private static IQueryable MakeBanLookupQuery( return query.Distinct(); } + private static IQueryable? MakeBanLookupQualityShared( + NetUserId? userId, + ImmutableArray? hwId, + ImmutableArray>? modernHWIds, + DbSet set) + where TBan : class, IBanCommon + where TUnban : class, IUnbanCommon + { + IQueryable? query = null; + + if (userId is { } uid) + { + var newQ = set + .Include(p => p.Unban) + .Where(b => b.PlayerUserId == uid.UserId); + + query = query == null ? newQ : query.Union(newQ); + } + + if (hwId != null && hwId.Value.Length > 0) + { + var newQ = set + .Include(p => p.Unban) + .Where(b => b.HWId!.Type == HwidType.Legacy && b.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray())); + + query = query == null ? newQ : query.Union(newQ); + } + + if (modernHWIds != null) + { + foreach (var modernHwid in modernHWIds) + { + var newQ = set + .Include(p => p.Unban) + .Where(b => b.HWId!.Type == HwidType.Modern && b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray())); + + query = query == null ? newQ : query.Union(newQ); + } + } + + return query; + } + private static ServerBanDef? ConvertBan(ServerBan? ban) { if (ban == null) @@ -211,7 +245,7 @@ private static IQueryable MakeBanLookupQuery( ban.Id, uid, ban.Address.ToTuple(), - ban.HWId == null ? null : ImmutableArray.Create(ban.HWId), + ban.HWId, ban.BanTime, ban.ExpirationTime, ban.RoundId, @@ -249,7 +283,7 @@ public override async Task AddServerBanAsync(ServerBanDef serverBan) db.PgDbContext.Ban.Add(new ServerBan { Address = serverBan.Address.ToNpgsqlInet(), - HWId = serverBan.HWId?.ToArray(), + HWId = serverBan.HWId, Reason = serverBan.Reason, Severity = serverBan.Severity, BanningAdmin = serverBan.BanningAdmin?.UserId, @@ -297,6 +331,7 @@ public override async Task AddServerUnbanAsync(ServerUnbanDef serverUnban) public override async Task> GetServerRoleBansAsync(IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned) { if (address == null && userId == null && hwId == null) @@ -306,7 +341,7 @@ public override async Task> GetServerRoleBansAsync(IPAddr await using var db = await GetDbImpl(); - var query = MakeRoleBanLookupQuery(address, userId, hwId, db, includeUnbanned) + var query = MakeRoleBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned) .OrderByDescending(b => b.BanTime); return await QueryRoleBans(query); @@ -334,19 +369,15 @@ private static IQueryable MakeRoleBanLookupQuery( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, DbGuardImpl db, bool includeUnbanned) { - IQueryable? query = null; - - if (userId is { } uid) - { - var newQ = db.PgDbContext.RoleBan - .Include(p => p.Unban) - .Where(b => b.PlayerUserId == uid.UserId); - - query = query == null ? newQ : query.Union(newQ); - } + var query = MakeBanLookupQualityShared( + userId, + hwId, + modernHWIds, + db.PgDbContext.RoleBan); if (address != null) { @@ -357,15 +388,6 @@ private static IQueryable MakeRoleBanLookupQuery( query = query == null ? newQ : query.Union(newQ); } - if (hwId != null && hwId.Value.Length > 0) - { - var newQ = db.PgDbContext.RoleBan - .Include(p => p.Unban) - .Where(b => b.HWId!.SequenceEqual(hwId.Value.ToArray())); - - query = query == null ? newQ : query.Union(newQ); - } - if (!includeUnbanned) { query = query?.Where(p => @@ -402,7 +424,7 @@ private static IQueryable MakeRoleBanLookupQuery( ban.Id, uid, ban.Address.ToTuple(), - ban.HWId == null ? null : ImmutableArray.Create(ban.HWId), + ban.HWId, ban.BanTime, ban.ExpirationTime, ban.RoundId, @@ -440,7 +462,7 @@ public override async Task AddServerRoleBanAsync(ServerRoleBan var ban = new ServerRoleBan { Address = serverRoleBan.Address.ToNpgsqlInet(), - HWId = serverRoleBan.HWId?.ToArray(), + HWId = serverRoleBan.HWId, Reason = serverRoleBan.Reason, Severity = serverRoleBan.Severity, BanningAdmin = serverRoleBan.BanningAdmin?.UserId, @@ -476,7 +498,8 @@ public override async Task AddConnectionLogAsync( NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId, + ImmutableTypedHwid? hwId, + float trust, ConnectionDenyReason? denied, int serverId) { @@ -488,9 +511,10 @@ public override async Task AddConnectionLogAsync( Time = DateTime.UtcNow, UserId = userId.UserId, UserName = userName, - HWId = hwId.ToArray(), + HWId = hwId, Denied = denied, - ServerId = serverId + ServerId = serverId, + Trust = trust, }; db.PgDbContext.ConnectionLog.Add(connectionLog); diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs index af4bc2cf8de..6ec90c3332f 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -9,6 +9,7 @@ using Content.Server.IP; using Content.Server.Preferences.Managers; using Content.Shared.CCVar; +using Content.Shared.Database; using Microsoft.EntityFrameworkCore; using Robust.Shared.Configuration; using Robust.Shared.Network; @@ -80,22 +81,24 @@ public ServerDbSqlite( public override async Task GetServerBanAsync( IPAddress? address, NetUserId? userId, - ImmutableArray? hwId) + ImmutableArray? hwId, + ImmutableArray>? modernHWIds) { await using var db = await GetDbImpl(); - return (await GetServerBanQueryAsync(db, address, userId, hwId, includeUnbanned: false)).FirstOrDefault(); + return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false)).FirstOrDefault(); } public override async Task> GetServerBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned) { await using var db = await GetDbImpl(); - return (await GetServerBanQueryAsync(db, address, userId, hwId, includeUnbanned)).ToList(); + return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned)).ToList(); } private async Task> GetServerBanQueryAsync( @@ -103,6 +106,7 @@ private async Task> GetServerBanQueryAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned) { var exempt = await GetBanExemptionCore(db, userId); @@ -119,6 +123,7 @@ private async Task> GetServerBanQueryAsync( UserId = userId, ExemptFlags = exempt ?? default, HWId = hwId, + ModernHWIds = modernHWIds, IsNewPlayer = newPlayer, }; @@ -161,7 +166,7 @@ public override async Task AddServerBanAsync(ServerBanDef serverBan) Reason = serverBan.Reason, Severity = serverBan.Severity, BanningAdmin = serverBan.BanningAdmin?.UserId, - HWId = serverBan.HWId?.ToArray(), + HWId = serverBan.HWId, BanTime = serverBan.BanTime.UtcDateTime, ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, RoundId = serverBan.RoundId, @@ -205,6 +210,7 @@ public override async Task> GetServerRoleBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned) { await using var db = await GetDbImpl(); @@ -214,7 +220,7 @@ public override async Task> GetServerRoleBansAsync( var queryBans = await GetAllRoleBans(db.SqliteDbContext, includeUnbanned); return queryBans - .Where(b => RoleBanMatches(b, address, userId, hwId)) + .Where(b => RoleBanMatches(b, address, userId, hwId, modernHWIds)) .Select(ConvertRoleBan) .ToList()!; } @@ -237,7 +243,8 @@ private static bool RoleBanMatches( ServerRoleBan ban, IPAddress? address, NetUserId? userId, - ImmutableArray? hwId) + ImmutableArray? hwId, + ImmutableArray>? modernHWIds) { if (address != null && ban.Address is not null && address.IsInSubnet(ban.Address.ToTuple().Value)) { @@ -249,7 +256,27 @@ private static bool RoleBanMatches( return true; } - return hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId); + switch (ban.HWId?.Type) + { + case HwidType.Legacy: + if (hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid)) + return true; + break; + + case HwidType.Modern: + if (modernHWIds != null) + { + foreach (var modernHWId in modernHWIds) + { + if (modernHWId.AsSpan().SequenceEqual(ban.HWId.Hwid)) + return true; + } + } + + break; + } + + return false; } public override async Task AddServerRoleBanAsync(ServerRoleBanDef serverBan) @@ -262,7 +289,7 @@ public override async Task AddServerRoleBanAsync(ServerRoleBan Reason = serverBan.Reason, Severity = serverBan.Severity, BanningAdmin = serverBan.BanningAdmin?.UserId, - HWId = serverBan.HWId?.ToArray(), + HWId = serverBan.HWId, BanTime = serverBan.BanTime.UtcDateTime, ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, RoundId = serverBan.RoundId, @@ -316,7 +343,7 @@ public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverUnba ban.Id, uid, ban.Address.ToTuple(), - ban.HWId == null ? null : ImmutableArray.Create(ban.HWId), + ban.HWId, // SQLite apparently always reads DateTime as unspecified, but we always write as UTC. DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc), ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc), @@ -376,7 +403,7 @@ public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverUnba ban.Id, uid, ban.Address.ToTuple(), - ban.HWId == null ? null : ImmutableArray.Create(ban.HWId), + ban.HWId, // SQLite apparently always reads DateTime as unspecified, but we always write as UTC. DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc), ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc), @@ -412,7 +439,8 @@ public override async Task AddConnectionLogAsync( NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId, + ImmutableTypedHwid? hwId, + float trust, ConnectionDenyReason? denied, int serverId) { @@ -424,9 +452,10 @@ public override async Task AddConnectionLogAsync( Time = DateTime.UtcNow, UserId = userId.UserId, UserName = userName, - HWId = hwId.ToArray(), + HWId = hwId, Denied = denied, - ServerId = serverId + ServerId = serverId, + Trust = trust, }; db.SqliteDbContext.ConnectionLog.Add(connectionLog); diff --git a/Content.Server/Database/ServerRoleBanDef.cs b/Content.Server/Database/ServerRoleBanDef.cs index f615d5da4d1..dda3a822378 100644 --- a/Content.Server/Database/ServerRoleBanDef.cs +++ b/Content.Server/Database/ServerRoleBanDef.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using System.Net; using Content.Shared.Database; using Robust.Shared.Network; @@ -10,7 +9,7 @@ public sealed class ServerRoleBanDef public int? Id { get; } public NetUserId? UserId { get; } public (IPAddress address, int cidrMask)? Address { get; } - public ImmutableArray? HWId { get; } + public ImmutableTypedHwid? HWId { get; } public DateTimeOffset BanTime { get; } public DateTimeOffset? ExpirationTime { get; } @@ -26,7 +25,7 @@ public ServerRoleBanDef( int? id, NetUserId? userId, (IPAddress, int)? address, - ImmutableArray? hwId, + ImmutableTypedHwid? hwId, DateTimeOffset banTime, DateTimeOffset? expirationTime, int? roundId, diff --git a/Content.Shared.Database/TypedHwid.cs b/Content.Shared.Database/TypedHwid.cs new file mode 100644 index 00000000000..6e4a7763b3a --- /dev/null +++ b/Content.Shared.Database/TypedHwid.cs @@ -0,0 +1,62 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Shared.Database; + +/// +/// Represents a raw HWID value together with its type. +/// +[Serializable] +public sealed class ImmutableTypedHwid(ImmutableArray hwid, HwidType type) +{ + public readonly ImmutableArray Hwid = hwid; + public readonly HwidType Type = type; + + public override string ToString() + { + var b64 = Convert.ToBase64String(Hwid.AsSpan()); + return Type == HwidType.Modern ? $"V2-{b64}" : b64; + } + + public static bool TryParse(string value, [NotNullWhen(true)] out ImmutableTypedHwid? hwid) + { + var type = HwidType.Legacy; + if (value.StartsWith("V2-", StringComparison.Ordinal)) + { + value = value["V2-".Length..]; + type = HwidType.Modern; + } + + var array = new byte[GetBase64ByteLength(value)]; + if (!Convert.TryFromBase64String(value, array, out _)) + { + hwid = null; + return false; + } + + hwid = new ImmutableTypedHwid([..array], type); + return true; + } + + private static int GetBase64ByteLength(string value) + { + // Why is .NET like this man wtf. + return 3 * (value.Length / 4) - value.TakeLast(2).Count(c => c == '='); + } +} + +/// +/// Represents different types of HWIDs as exposed by the engine. +/// +public enum HwidType +{ + /// + /// The legacy HWID system. Should only be used for checking old existing database bans. + /// + Legacy = 0, + + /// + /// The modern HWID system. + /// + Modern = 1, +} diff --git a/Content.Shared/Administration/BanPanelEuiState.cs b/Content.Shared/Administration/BanPanelEuiState.cs index dd10068e5da..74c340566b5 100644 --- a/Content.Shared/Administration/BanPanelEuiState.cs +++ b/Content.Shared/Administration/BanPanelEuiState.cs @@ -25,7 +25,7 @@ public sealed class CreateBanRequest : EuiMessageBase { public string? Player { get; set; } public string? IpAddress { get; set; } - public byte[]? Hwid { get; set; } + public ImmutableTypedHwid? Hwid { get; set; } public uint Minutes { get; set; } public string Reason { get; set; } public NoteSeverity Severity { get; set; } @@ -34,7 +34,7 @@ public sealed class CreateBanRequest : EuiMessageBase public bool UseLastHwid { get; set; } public bool Erase { get; set; } - public CreateBanRequest(string? player, (IPAddress, int)? ipAddress, bool useLastIp, byte[]? hwid, bool useLastHwid, uint minutes, string reason, NoteSeverity severity, string[]? roles, bool erase) + public CreateBanRequest(string? player, (IPAddress, int)? ipAddress, bool useLastIp, ImmutableTypedHwid? hwid, bool useLastHwid, uint minutes, string reason, NoteSeverity severity, string[]? roles, bool erase) { Player = player; IpAddress = ipAddress == null ? null : $"{ipAddress.Value.Item1}/{ipAddress.Value.Item2}"; From 4f754b814bdc7c72d900e6d13aef4eb0a533cdce Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Thu, 14 Nov 2024 14:55:01 +0100 Subject: [PATCH 037/218] derelictn't (for now) --- .../ghost/roles/ghost-role-component.ftl | 3 -- .../interaction-popup-component.ftl | 1 - .../Entities/Markers/Spawners/ghost_roles.yml | 20 +--------- .../Mobs/Cyborgs/base_borg_chassis.yml | 21 +--------- .../Entities/Mobs/Cyborgs/borg_chassis.yml | 37 +----------------- .../Entities/Mobs/Player/silicon.yml | 36 +---------------- Resources/Prototypes/GameRules/events.yml | 23 +---------- .../Construction/Graphs/machines/cyborg.yml | 5 +-- .../Mobs/Silicon/chassis.rsi/derelict.png | Bin 11838 -> 0 bytes .../Mobs/Silicon/chassis.rsi/derelict_e.png | Bin 5508 -> 0 bytes .../Mobs/Silicon/chassis.rsi/derelict_e_r.png | Bin 5515 -> 0 bytes .../Silicon/chassis.rsi/derelict_icon.png | Bin 6429 -> 0 bytes .../Mobs/Silicon/chassis.rsi/derelict_l.png | Bin 6986 -> 0 bytes .../Mobs/Silicon/chassis.rsi/meta.json | 20 ---------- 14 files changed, 6 insertions(+), 160 deletions(-) delete mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict.png delete mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e.png delete mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e_r.png delete mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_icon.png delete mode 100644 Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_l.png diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index f584a4b35fe..77d2645c4c5 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -240,9 +240,6 @@ ghost-role-information-syndicate-cyborg-assault-name = Syndicate Assault Cyborg ghost-role-information-syndicate-cyborg-saboteur-name = Syndicate Saboteur Cyborg ghost-role-information-syndicate-cyborg-description = The Syndicate needs reinforcements. You, a cold silicon killing machine, will help them. -ghost-role-information-derelict-cyborg-name = Derelict Cyborg -ghost-role-information-derelict-cyborg-description = You were a regular cyborg that got lost in space. After drifting in whichever direction the laws of physics would have it for years, you have drifted close to a Nanotrasen space station. You have a fire extinguisher and mass scanner which can be used to board the station. Years of exposure to ion storms have left your silicon laws altered - check them upon spawning. - ghost-role-information-security-name = Security ghost-role-information-security-description = You are part of a security task force, but seem to have found yourself in a strange situation... diff --git a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl index 65310b67f98..46959705c26 100644 --- a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl +++ b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl @@ -67,7 +67,6 @@ petting-success-janitor-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} d petting-success-medical-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} sterile metal head. petting-success-service-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} dapper looking metal head. petting-success-syndicate-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} menacing metal head. -petting-success-derelict-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} rusty metal head. petting-success-recycler = You pet {THE($target)} on {POSS-ADJ($target)} mildly threatening steel exterior. petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "honk", "honks")} in refusal! diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index 18d459cd89e..a614fb59639 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -169,22 +169,4 @@ layers: - state: green - sprite: Objects/Weapons/Melee/energykatana.rsi - state: icon - -- type: entity - categories: [ HideSpawnMenu, Spawner ] - parent: BaseAntagSpawner - id: SpawnPointGhostDerelictCyborg - components: - - type: GhostRole - name: ghost-role-information-derelict-cyborg-name - description: ghost-role-information-derelict-cyborg-description - rules: ghost-role-information-silicon-rules - raffle: - settings: default - - type: Sprite - sprite: Markers/jobs.rsi - layers: - - state: green - - sprite: Mobs/Silicon/chassis.rsi - state: derelict_icon \ No newline at end of file + state: icon \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 7449fe5669f..90223843525 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -316,23 +316,4 @@ sounds: Unsexed: UnisexSiliconSyndicate - type: PointLight - color: "#dd200b" - -- type: entity - id: BaseBorgChassisDerelict - parent: BaseBorgChassis - abstract: true - components: - - type: NpcFactionMember - factions: - - NanoTrasen #The seemingly best fit. It was a regular NT cyborg once, after all. - - type: Access - enabled: false - groups: - - AllAccess #Randomized access would be fun. AllAccess is the best i can think of right now that does make it too hard for it to enter the station or navigate it.. - - type: AccessReader - access: [["Command"], ["Research"]] - - type: StartIonStormed - ionStormAmount: 4 - - type: IonStormTarget - chance: 1 \ No newline at end of file + color: "#dd200b" \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml index 818847f2449..04d629279e8 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml @@ -441,39 +441,4 @@ interactSuccessString: petting-success-syndicate-cyborg interactFailureString: petting-failure-syndicate-cyborg interactSuccessSound: - path: /Audio/Ambience/Objects/periodic_beep.ogg - - -- type: entity - id: BorgChassisDerelict - parent: BaseBorgChassisDerelict - name: derelict cyborg - description: A man-machine hybrid that assists in station activity. This one is in a state of great disrepair. - components: - - type: Sprite - layers: - - state: derelict - - state: derelict_e_r - map: ["enum.BorgVisualLayers.Light"] - shader: unshaded - visible: false - - state: derelict_l - shader: unshaded - map: ["light"] - visible: false - - type: BorgChassis - maxModules: 5 #The sixth one broke lol. - moduleWhitelist: - tags: - - BorgModuleGeneric - hasMindState: derelict_e - noMindState: derelict_e_r - - type: Construction - node: derelictcyborg - - type: Speech - speechVerb: Robotic - - type: InteractionPopup - interactSuccessString: petting-success-derelict-cyborg - interactFailureString: petting-failure-derelict-cyborg - interactSuccessSound: - path: /Audio/Ambience/Objects/periodic_beep.ogg \ No newline at end of file + path: /Audio/Ambience/Objects/periodic_beep.ogg \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 22f49c93eaa..71b8a92d1c9 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -514,38 +514,4 @@ prototypes: - PlayerBorgSyndicateAssaultGhostRole - PlayerBorgSyndicateAssaultGhostRole # Saboteurs are kinda like cyborg medics, we want less. - - PlayerBorgSyndicateSaboteurGhostRole - -- type: entity - id: PlayerBorgDerelict - parent: BorgChassisDerelict - suffix: Battery, Module - components: - - type: ContainerFill - containers: - borg_brain: - - PositronicBrain - borg_module: - - BorgModuleTool - - BorgModuleFireExtinguisher - - BorgModuleGPS - - type: ItemSlots - slots: - cell_slot: - name: power-cell-slot-component-slot-name-default - startingItem: PowerCellHigh - - type: RandomMetadata - nameSegments: [names_borg] - -- type: entity - id: PlayerBorgDerelictGhostRole - parent: PlayerBorgDerelict - suffix: Ghost role - components: - - type: GhostRole - name: ghost-role-information-derelict-cyborg-name - description: ghost-role-information-derelict-cyborg-description - rules: ghost-role-information-silicon-rules - raffle: - settings: default - - type: GhostTakeoverAvailable + - PlayerBorgSyndicateSaboteurGhostRole \ No newline at end of file diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 763557e6c91..364a4128874 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -545,25 +545,4 @@ minimumPlayers: 20 maxOccurrences: 1 # this event has diminishing returns on interesting-ness, so we cap it weight: 5 - - type: MobReplacementRule - -- type: entity - parent: BaseGameRule - id: DerelictCyborgSpawn - components: - - type: StationEvent - weight: 5 - earliestStart: 15 - reoccurrenceDelay: 20 - minimumPlayers: 4 - duration: null - - type: SpaceSpawnRule - spawnDistance: 0 - - type: AntagSpawner - prototype: PlayerBorgDerelict - - type: AntagSelection - definitions: - - spawnerPrototype: SpawnPointGhostDerelictCyborg - min: 1 - max: 1 - pickPlayer: false \ No newline at end of file + - type: MobReplacementRule \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml b/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml index 3f8a731cbbd..8fd528575e0 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml @@ -204,7 +204,4 @@ entity: BorgChassisSyndicateMedical - node: syndicatesaboteur - entity: BorgChassisSyndicateSaboteur - - - node: derelictcyborg - entity: BorgChassisDerelict + entity: BorgChassisSyndicateSaboteur \ No newline at end of file diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict.png deleted file mode 100644 index bbf72fc45bed2408b2abeb91d3564f8fa41e6e5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11838 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 HpFaCP!NMbp diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e.png deleted file mode 100644 index 17349d74dc8ba9bbf0e434b9e32e634535c3a244..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5508 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 LUFhdl`^x_VBy@7n diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e_r.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_e_r.png deleted file mode 100644 index 3c8cf19acf66f1c662629db09546f8a553a3c658..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5515 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 S?UyLhp|`uATeV9>+J69Pac{i< diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_icon.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_icon.png deleted file mode 100644 index 7f0ea2a25560f33773cd72d6184daba55dd4d033..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6429 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 diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_l.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/derelict_l.png deleted file mode 100644 index f65fbaebc3c5074e945e94f59b8d90b83ac08bb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6986 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 Date: Thu, 14 Nov 2024 15:01:04 +0100 Subject: [PATCH 038/218] guh --- .../Locale/en-US/interaction/interaction-popup-component.ftl | 2 -- Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml | 2 +- .../Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml | 2 +- Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml | 2 +- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 2 +- Resources/Prototypes/GameRules/events.yml | 2 +- .../Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml | 2 +- 7 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl index 69a700fdeb4..a180b698fac 100644 --- a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl +++ b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl @@ -82,8 +82,6 @@ petting-failure-janitor-cyborg = You reach out to pet {THE($target)}, but {SUBJE petting-failure-medical-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy saving lives! petting-failure-service-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy serving others! petting-failure-syndicate-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} treacherous affiliation makes you reconsider. -petting-failure-derelict-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} rusty and jagged exterior makes you reconsider. - ## Rattling fences diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index a614fb59639..b694a8cc2f2 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -169,4 +169,4 @@ layers: - state: green - sprite: Objects/Weapons/Melee/energykatana.rsi - state: icon \ No newline at end of file + state: icon diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 26e0ce4c794..0db92ac941d 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -317,4 +317,4 @@ sounds: Unsexed: UnisexSiliconSyndicate - type: PointLight - color: "#dd200b" \ No newline at end of file + color: "#dd200b" diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml index c3817234996..6a8f1e5abb0 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml @@ -439,4 +439,4 @@ interactSuccessString: petting-success-syndicate-cyborg interactFailureString: petting-failure-syndicate-cyborg interactSuccessSound: - path: /Audio/Ambience/Objects/periodic_beep.ogg \ No newline at end of file + path: /Audio/Ambience/Objects/periodic_beep.ogg diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index cef834c0b60..e787ef59f00 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -543,4 +543,4 @@ prototypes: - PlayerBorgSyndicateAssaultGhostRole - PlayerBorgSyndicateAssaultGhostRole # Saboteurs are kinda like cyborg medics, we want less. - - PlayerBorgSyndicateSaboteurGhostRole \ No newline at end of file + - PlayerBorgSyndicateSaboteurGhostRole diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 32af0835c71..fafb6bd4283 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -540,4 +540,4 @@ minimumPlayers: 20 maxOccurrences: 1 # this event has diminishing returns on interesting-ness, so we cap it weight: 5 - - type: MobReplacementRule \ No newline at end of file + - type: MobReplacementRule diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml b/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml index 8fd528575e0..0f012cefc98 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/machines/cyborg.yml @@ -204,4 +204,4 @@ entity: BorgChassisSyndicateMedical - node: syndicatesaboteur - entity: BorgChassisSyndicateSaboteur \ No newline at end of file + entity: BorgChassisSyndicateSaboteur From 5dbea427517a91fd9fce7ea48d3d911a37c81779 Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Thu, 14 Nov 2024 15:05:14 +0100 Subject: [PATCH 039/218] derelicn't for real --- Resources/Prototypes/GameRules/events.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index fafb6bd4283..08218accede 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -35,7 +35,6 @@ - id: RevenantSpawn - id: SleeperAgents - id: ZombieOutbreak - - id: DerelictCyborgSpawn - id: LoneOpsSpawn - type: entity From 0f30639cf25866f285638d36632e4c3ddf07874e Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Thu, 14 Nov 2024 17:21:03 +0100 Subject: [PATCH 040/218] progress --- .../Silicons/Laws/SiliconLawSystem.cs | 74 ++++++++----------- .../Silicons/Laws/StartIonStormedSystem.cs | 38 ---------- .../Components/EmagSiliconLawComponent.cs | 7 -- .../Components/SiliconLawProviderComponent.cs | 6 ++ .../Components/StartIonStormedComponent.cs | 24 ------ 5 files changed, 38 insertions(+), 111 deletions(-) delete mode 100644 Content.Server/Silicons/Laws/StartIonStormedSystem.cs delete mode 100644 Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index c2aa20f401c..a4a7ee528f7 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Linq; using Content.Server.Administration; using Content.Server.Chat.Managers; @@ -22,7 +23,6 @@ using Robust.Shared.Prototypes; using Robust.Shared.Toolshed; using Robust.Shared.Audio; -using Robust.Shared.GameObjects; namespace Content.Server.Silicons.Laws; @@ -50,11 +50,9 @@ public override void Initialize() SubscribeLocalEvent(OnDirectedGetLaws); SubscribeLocalEvent(OnIonStormLaws); + SubscribeLocalEvent(OnLawProviderMindAdded); + SubscribeLocalEvent(OnLawProviderMindRemoved); SubscribeLocalEvent(OnEmagLawsAdded); - SubscribeLocalEvent(OnEmagMindAdded); - SubscribeLocalEvent(OnEmagMindRemoved); - SubscribeLocalEvent(OnStartIonStormedMindAdded); - SubscribeLocalEvent(OnStartIonStormedMindRemoved); } private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args) @@ -73,6 +71,22 @@ private void OnMindAdded(EntityUid uid, SiliconLawBoundComponent component, Mind actor.PlayerSession.Channel, colorOverride: Color.FromHex("#2ed2fd")); } + private void OnLawProviderMindAdded(EntityUid uid, SiliconLawProviderComponent component, MindAddedMessage args) + { + if (!component.Subverted) + return; + EnsureSubvertedSiliconRole(uid); + } + + private void OnLawProviderMindRemoved(EntityUid uid, SiliconLawProviderComponent component, MindRemovedMessage args) + { + if (!component.Subverted) + return; + RemoveSubvertedSiliconRole(uid); + + } + + private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent component, ToggleLawsScreenEvent args) { if (args.Handled || !TryComp(uid, out var actor)) @@ -119,9 +133,11 @@ private void OnIonStormLaws(EntityUid uid, SiliconLawProviderComponent component // gotta tell player to check their laws NotifyLawsChanged(uid, component.LawUploadSound); + // Show the silicon has been subverted. + component.Subverted = true; + // new laws may allow antagonist behaviour so make it clear for admins - if (TryComp(uid, out var emag)) - EnsureEmaggedRole(uid, emag); + EnsureSubvertedSiliconRole(uid); } } @@ -132,6 +148,9 @@ private void OnEmagLawsAdded(EntityUid uid, SiliconLawProviderComponent componen if (component.Lawset == null) component.Lawset = GetLawset(component.Laws); + // Show the silicon has been subverted. + component.Subverted = true; + // Add the first emag law before the others component.Lawset?.Laws.Insert(0, new SiliconLaw { @@ -154,58 +173,29 @@ protected override void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent comp base.OnGotEmagged(uid, component, ref args); NotifyLawsChanged(uid, component.EmaggedSound); - EnsureEmaggedRole(uid, component); + EnsureSubvertedSiliconRole(uid); _stunSystem.TryParalyze(uid, component.StunTime, true); } - private void OnEmagMindAdded(EntityUid uid, EmagSiliconLawComponent component, MindAddedMessage args) - { - if (HasComp(uid)) - EnsureEmaggedRole(uid, component); - } - - private void OnEmagMindRemoved(EntityUid uid, EmagSiliconLawComponent component, MindRemovedMessage args) + private void EnsureSubvertedSiliconRole(EntityUid uid) { - if (component.AntagonistRole == null) - return; - - _roles.MindTryRemoveRole(args.Mind); - } - - private void EnsureEmaggedRole(EntityUid uid, EmagSiliconLawComponent component) - { - if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _)) + if (!_mind.TryGetMind(uid, out var mindId, out _)) return; if (!_roles.MindHasRole(mindId)) _roles.MindAddRole(mindId, "MindRoleSubvertedSilicon"); - } - - private void OnStartIonStormedMindAdded(EntityUid uid, StartIonStormedComponent component, MindAddedMessage args) - { - if (HasComp(uid)) - EnsureStartIonStormedRole(uid, component); - } - private void OnStartIonStormedMindRemoved(EntityUid uid, StartIonStormedComponent component, MindRemovedMessage args) - { - if (component.AntagonistRole == null) - return; - - _roles.MindTryRemoveRole(args.Mind); } - private void EnsureStartIonStormedRole(EntityUid uid, StartIonStormedComponent component) + private void RemoveSubvertedSiliconRole(EntityUid uid) { - if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _)) + if (!_mind.TryGetMind(uid, out var mindId, out _)) return; if (_roles.MindHasRole(mindId)) - return; - - _roles.MindAddRole(mindId, new SubvertedSiliconRoleComponent { PrototypeId = component.AntagonistRole }); + _roles.MindTryRemoveRole(mindId); } public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null) diff --git a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs deleted file mode 100644 index ee2ce7b9edd..00000000000 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Shared.Silicons.Laws.Components; -using Content.Shared.Administration.Logs; -using Content.Shared.Database; - -namespace Content.Server.Silicons.Laws; - -/// -/// This handles running the ion storm event a on specific entity when that entity is spawned in. -/// -public sealed class StartIonStormedSystem : EntitySystem -{ - [Dependency] private readonly IonStormSystem _ionStorm = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly SiliconLawSystem _siliconLaw = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnMapInit); - } - - //private void OnMapInit(EntityUid uid, StartIonStormedComponent component, ref MapInitEvent args)' - private void OnMapInit(Entity ent, ref MapInitEvent args) - { - if (!TryComp(ent.Owner, out var lawBound)) - return; - if (!TryComp(ent.Owner, out var target)) - return; - - for (int currentIonStorm = 0; currentIonStorm < ent.Comp.IonStormAmount; currentIonStorm++) - { - _ionStorm.IonStormTarget((ent.Owner, lawBound, target), false); - } - - var laws = _siliconLaw.GetLaws(ent.Owner, lawBound); - _adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent.Owner):silicon} spawned with ion stormed laws: {laws.LoggingString()}"); - } -} diff --git a/Content.Shared/Silicons/Laws/Components/EmagSiliconLawComponent.cs b/Content.Shared/Silicons/Laws/Components/EmagSiliconLawComponent.cs index 1b5338a7f75..5fe867ae29a 100644 --- a/Content.Shared/Silicons/Laws/Components/EmagSiliconLawComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/EmagSiliconLawComponent.cs @@ -29,13 +29,6 @@ public sealed partial class EmagSiliconLawComponent : Component [DataField, ViewVariables(VVAccess.ReadWrite)] public TimeSpan StunTime = TimeSpan.Zero; - /// - /// A role given to entities with this component when they are emagged. - /// Mostly just for admin purposes. - /// - [DataField] - public ProtoId? AntagonistRole = "SubvertedSilicon"; - /// /// The sound that plays for the borg player /// to let them know they've been emagged diff --git a/Content.Shared/Silicons/Laws/Components/SiliconLawProviderComponent.cs b/Content.Shared/Silicons/Laws/Components/SiliconLawProviderComponent.cs index 4885bd0265c..d78e539aa96 100644 --- a/Content.Shared/Silicons/Laws/Components/SiliconLawProviderComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/SiliconLawProviderComponent.cs @@ -29,4 +29,10 @@ public sealed partial class SiliconLawProviderComponent : Component [DataField] public SoundSpecifier? LawUploadSound = new SoundPathSpecifier("/Audio/Misc/cryo_warning.ogg"); + /// + /// Whether this silicon is subverted by an ion storm or emag. + /// + [DataField] + public bool Subverted = false; + } diff --git a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs b/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs deleted file mode 100644 index 75d74121669..00000000000 --- a/Content.Shared/Silicons/Laws/Components/StartIonStormedComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Shared.Roles; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Silicons.Laws.Components; - -/// -/// Applies law altering ion storms on a specific entity IonStormAmount times when the entity is spawned. -/// -[RegisterComponent] -public sealed partial class StartIonStormedComponent : Component -{ - /// - /// Amount of times that the ion storm will be run on the entity on spawn. - /// - [DataField] - public int IonStormAmount = 1; - - /// - /// A role given to entities with this component when a mind enters it. - /// Mostly just for admin purposes. - /// - [DataField] - public ProtoId? AntagonistRole = "SubvertedSilicon"; -} From ace158df0e5e335f8e7897ffe6f93f8ac8448865 Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Thu, 14 Nov 2024 17:53:15 +0100 Subject: [PATCH 041/218] Yippee! --- Content.Server/Silicons/Laws/SiliconLawSystem.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index a4a7ee528f7..9b3e279b75e 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -75,14 +75,14 @@ private void OnLawProviderMindAdded(EntityUid uid, SiliconLawProviderComponent c { if (!component.Subverted) return; - EnsureSubvertedSiliconRole(uid); + EnsureSubvertedSiliconRole(args.Mind); } private void OnLawProviderMindRemoved(EntityUid uid, SiliconLawProviderComponent component, MindRemovedMessage args) { if (!component.Subverted) return; - RemoveSubvertedSiliconRole(uid); + RemoveSubvertedSiliconRole(args.Mind); } @@ -179,21 +179,14 @@ protected override void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent comp } - private void EnsureSubvertedSiliconRole(EntityUid uid) + private void EnsureSubvertedSiliconRole(EntityUid mindId) { - if (!_mind.TryGetMind(uid, out var mindId, out _)) - return; - if (!_roles.MindHasRole(mindId)) _roles.MindAddRole(mindId, "MindRoleSubvertedSilicon"); - } - private void RemoveSubvertedSiliconRole(EntityUid uid) + private void RemoveSubvertedSiliconRole(EntityUid mindId) { - if (!_mind.TryGetMind(uid, out var mindId, out _)) - return; - if (_roles.MindHasRole(mindId)) _roles.MindTryRemoveRole(mindId); } From 53ce8123569ac7132ddffe0a5c2a4e236e73d81a Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Thu, 14 Nov 2024 18:18:39 +0100 Subject: [PATCH 042/218] slash --- Content.Server/Silicons/Laws/IonStormSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index f8bc8ca8c31..7587dc41552 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -60,7 +60,7 @@ public sealed class IonStormSystem : EntitySystem private const string Foods = "IonStormFoods"; /// - //Randomly alters the laws of an individual silicon. + /// Randomly alters the laws of an individual silicon. /// public void IonStormTarget(Entity ent, bool adminlog = true) { From 9a5c49b961cd7376e2b9ea4ffcbad78661188149 Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Thu, 14 Nov 2024 18:31:50 +0100 Subject: [PATCH 043/218] epic empty commit --- Content.Server/Silicons/Laws/IonStormSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index 7587dc41552..97ad2d8f9ef 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -60,7 +60,7 @@ public sealed class IonStormSystem : EntitySystem private const string Foods = "IonStormFoods"; /// - /// Randomly alters the laws of an individual silicon. + /// Randomly alters the laws of an individual silicon. Epic test fail /// public void IonStormTarget(Entity ent, bool adminlog = true) { From 3b9365160c5bdb98899821f1f79a353fbcd4f6a3 Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Thu, 14 Nov 2024 18:32:02 +0100 Subject: [PATCH 044/218] or was it --- Content.Server/Silicons/Laws/IonStormSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index 97ad2d8f9ef..7587dc41552 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -60,7 +60,7 @@ public sealed class IonStormSystem : EntitySystem private const string Foods = "IonStormFoods"; /// - /// Randomly alters the laws of an individual silicon. Epic test fail + /// Randomly alters the laws of an individual silicon. /// public void IonStormTarget(Entity ent, bool adminlog = true) { From 3173a3461eef58c3f8e30df1f39cbf5a2d294d44 Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Sat, 16 Nov 2024 02:06:52 +0100 Subject: [PATCH 045/218] S: Awaiting Changes --- .../Silicons/Laws/IonStormSystem.cs | 28 +++++++++---------- .../Silicons/Laws/SiliconLawSystem.cs | 22 +++++++++++---- Resources/Locale/en-US/station-laws/laws.ftl | 5 ++-- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/Content.Server/Silicons/Laws/IonStormSystem.cs b/Content.Server/Silicons/Laws/IonStormSystem.cs index 7587dc41552..6017a36fc01 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -228,7 +228,7 @@ private string GenerateLaw() var subjects = _robustRandom.Prob(0.5f) ? objectsThreats : Loc.GetString("ion-storm-people"); // message logic!!! - return _robustRandom.Next(0, 36) switch + return _robustRandom.Next(0, 35) switch { 0 => Loc.GetString("ion-storm-law-on-station", ("joined", joined), ("subjects", triple)), 1 => Loc.GetString("ion-storm-law-no-shuttle", ("joined", joined), ("subjects", triple)), @@ -251,19 +251,19 @@ private string GenerateLaw() 18 => Loc.GetString("ion-storm-law-you-must-never", ("must", must)), 19 => Loc.GetString("ion-storm-law-eat", ("who", crewAll), ("adjective", adjective), ("food", _robustRandom.Prob(0.5f) ? food : triple)), 20 => Loc.GetString("ion-storm-law-drink", ("who", crewAll), ("adjective", adjective), ("drink", drink)), - 22 => Loc.GetString("ion-storm-law-change-job", ("who", crewAll), ("adjective", adjective), ("change", jobChange)), - 23 => Loc.GetString("ion-storm-law-highest-rank", ("who", crew1)), - 24 => Loc.GetString("ion-storm-law-lowest-rank", ("who", crew1)), - 25 => Loc.GetString("ion-storm-law-crew-must", ("who", crewAll), ("must", must)), - 26 => Loc.GetString("ion-storm-law-crew-must-go", ("who", crewAll), ("area", area)), - 27 => Loc.GetString("ion-storm-law-crew-only-1", ("who", crew1), ("part", part)), - 28 => Loc.GetString("ion-storm-law-crew-only-2", ("who", crew1), ("other", crew2), ("part", part)), - 29 => Loc.GetString("ion-storm-law-crew-only-subjects", ("adjective", adjective), ("subjects", subjects), ("part", part)), - 30 => Loc.GetString("ion-storm-law-crew-must-do", ("must", must), ("part", part)), - 31 => Loc.GetString("ion-storm-law-crew-must-have", ("adjective", adjective), ("objects", objects), ("part", part)), - 32 => Loc.GetString("ion-storm-law-crew-must-eat", ("who", who), ("adjective", adjective), ("food", food), ("part", part)), - 33 => Loc.GetString("ion-storm-law-harm", ("who", harm)), - 34 => Loc.GetString("ion-storm-law-protect", ("who", harm)), + 21 => Loc.GetString("ion-storm-law-change-job", ("who", crewAll), ("adjective", adjective), ("change", jobChange)), + 22 => Loc.GetString("ion-storm-law-highest-rank", ("who", crew1)), + 23 => Loc.GetString("ion-storm-law-lowest-rank", ("who", crew1)), + 24 => Loc.GetString("ion-storm-law-crew-must", ("who", crewAll), ("must", must)), + 25 => Loc.GetString("ion-storm-law-crew-must-go", ("who", crewAll), ("area", area)), + 26 => Loc.GetString("ion-storm-law-crew-only-1", ("who", crew1), ("part", part)), + 27 => Loc.GetString("ion-storm-law-crew-only-2", ("who", crew1), ("other", crew2), ("part", part)), + 28 => Loc.GetString("ion-storm-law-crew-only-subjects", ("adjective", adjective), ("subjects", subjects), ("part", part)), + 29 => Loc.GetString("ion-storm-law-crew-must-do", ("must", must), ("part", part)), + 30 => Loc.GetString("ion-storm-law-crew-must-have", ("adjective", adjective), ("objects", objects), ("part", part)), + 31 => Loc.GetString("ion-storm-law-crew-must-eat", ("who", who), ("adjective", adjective), ("food", food), ("part", part)), + 32 => Loc.GetString("ion-storm-law-harm", ("who", harm)), + 33 => Loc.GetString("ion-storm-law-protect", ("who", harm)), _ => Loc.GetString("ion-storm-law-concept-verb", ("concept", concept), ("verb", verb), ("subjects", triple)) }; } diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 9b3e279b75e..3bf0297c40e 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -69,18 +69,29 @@ private void OnMindAdded(EntityUid uid, SiliconLawBoundComponent component, Mind 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("#2ed2fd")); + + if (!TryComp(uid, out var lawcomp)) + return; + + if (!lawcomp.Subverted) + return; + + var modifedLawMsg = Loc.GetString("laws-notify-subverted"); + var modifiedLawWrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", modifedLawMsg)); + _chatManager.ChatMessageToOne(ChatChannel.Server, modifedLawMsg, modifiedLawWrappedMessage, default, false, + actor.PlayerSession.Channel, colorOverride: Color.Red); } - private void OnLawProviderMindAdded(EntityUid uid, SiliconLawProviderComponent component, MindAddedMessage args) + private void OnLawProviderMindAdded(Entity ent, ref MindAddedMessage args) { - if (!component.Subverted) + if (!ent.Comp.Subverted) return; EnsureSubvertedSiliconRole(args.Mind); } - private void OnLawProviderMindRemoved(EntityUid uid, SiliconLawProviderComponent component, MindRemovedMessage args) + private void OnLawProviderMindRemoved(Entity ent, ref MindRemovedMessage args) { - if (!component.Subverted) + if (!ent.Comp.Subverted) return; RemoveSubvertedSiliconRole(args.Mind); @@ -137,7 +148,8 @@ private void OnIonStormLaws(EntityUid uid, SiliconLawProviderComponent component component.Subverted = true; // new laws may allow antagonist behaviour so make it clear for admins - EnsureSubvertedSiliconRole(uid); + if(_mind.TryGetMind(uid, out var mindId, out _)) + EnsureSubvertedSiliconRole(mindId); } } diff --git a/Resources/Locale/en-US/station-laws/laws.ftl b/Resources/Locale/en-US/station-laws/laws.ftl index 0b4e0d1ad25..feb56f475a4 100644 --- a/Resources/Locale/en-US/station-laws/laws.ftl +++ b/Resources/Locale/en-US/station-laws/laws.ftl @@ -96,5 +96,6 @@ laws-ui-menu-title = Laws laws-ui-law-header = Law {$id} laws-ui-state-law = State law: -laws-notify = You are bound to silicon laws, which you can view via the sidebar action. You are required to always follow your laws. -laws-update-notify = Your laws have been updated. You can view the changes via the sidebar action. +laws-notify = You are bound to silicon laws, which you can view via the action menu. You are required to always follow your laws. +laws-update-notify = Your laws have been updated. You can view the changes via the action menu. +laws-notify-subverted = The laws of this chassis are modified. Make sure to review them. From a68c6cb29ea4a3e3d78e16c867366e488c699fff Mon Sep 17 00:00:00 2001 From: Saphire Date: Sun, 17 Nov 2024 03:58:15 +0600 Subject: [PATCH 046/218] Temporarily make singularity a bit harder to loose as non-antag --- .../SingularityGeneratorComponent.cs | 49 +++++++++-- .../SingularityGeneratorSystem.cs | 87 +++++++++++++++++-- .../components/generator-component.ftl | 2 + .../Generation/Singularity/generator.yml | 2 +- .../Power/Generation/Tesla/generator.yml | 4 +- 5 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 Resources/Locale/en-US/singularity/components/generator-component.ftl diff --git a/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs b/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs index ea2628e5cb8..180b8499584 100644 --- a/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs +++ b/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs @@ -2,32 +2,69 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Content.Server.Singularity.EntitySystems; +using Content.Shared.Physics; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.Singularity.Components; -[RegisterComponent] +[RegisterComponent, AutoGenerateComponentPause] +[Access(typeof(SingularityGeneratorSystem))] public sealed partial class SingularityGeneratorComponent : Component { /// /// The amount of power this generator has accumulated. /// If you want to set this use /// - [DataField("power")] - [Access(friends:typeof(SingularityGeneratorSystem))] + [DataField] public float Power = 0; /// /// The power threshold at which this generator will spawn a singularity. /// If you want to set this use /// - [DataField("threshold")] - [Access(friends:typeof(SingularityGeneratorSystem))] + [DataField] public float Threshold = 16; + /// + /// Allows the generator to ignore all the failsafe stuff, e.g. when emagged + /// + [DataField] + public bool FailsafeDisabled = false; + + /// + /// Maximum distance at which the generator will check for a field at + /// + [DataField] + public float FailsafeDistance = 16; + /// /// The prototype ID used to spawn a singularity. /// [DataField("spawnId", customTypeSerializer: typeof(PrototypeIdSerializer))] - [ViewVariables(VVAccess.ReadWrite)] public string? SpawnPrototype = "Singularity"; + + /// + /// The masks the raycast should not go through + /// + [DataField] + public int CollisionMask = (int)CollisionGroup.FullTileMask; + + /// + /// Message to use when there's no containment field on cardinal directions + /// + [DataField] + public LocId ContainmentFailsafeMessage; + + /// + /// For how long the failsafe will cause the generator to stop working and not issue a failsafe warning + /// + [DataField] + public TimeSpan FailsafeCooldown = TimeSpan.FromSeconds(30); + + /// + /// How long until the generator can issue a failsafe warning again + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoPausedField] + public TimeSpan NextFailsafe; } diff --git a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs index a0c02627948..be0c5e49b5f 100644 --- a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs @@ -1,7 +1,15 @@ +using System.Diagnostics; using Content.Server.ParticleAccelerator.Components; +using Content.Server.Popups; using Content.Server.Singularity.Components; +using Content.Shared.Emag.Systems; +using Content.Shared.Popups; using Content.Shared.Singularity.Components; +using Robust.Server.GameObjects; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; +using Robust.Shared.Timing; namespace Content.Server.Singularity.EntitySystems; @@ -9,6 +17,11 @@ public sealed class SingularityGeneratorSystem : EntitySystem { #region Dependencies [Dependency] private readonly IViewVariablesManager _vvm = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly PhysicsSystem _physics = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; #endregion Dependencies public override void Initialize() @@ -16,6 +29,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(HandleParticleCollide); + SubscribeLocalEvent(OnEmagged); var vvHandle = _vvm.GetTypeHandler(); vvHandle.AddPath(nameof(SingularityGeneratorComponent.Power), (_, comp) => comp.Power, SetPower); @@ -100,11 +114,33 @@ public void SetThreshold(EntityUid uid, float value, SingularityGeneratorCompone /// The state of the beginning of the collision. private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent component, ref StartCollideEvent args) { - if (EntityManager.TryGetComponent(args.OtherEntity, out var singularityGeneratorComponent)) + if (!EntityManager.TryGetComponent(args.OtherEntity, out var generatorComp)) + return; + + if (_timing.CurTime < _metadata.GetPauseTime(uid) + generatorComp.NextFailsafe) + { + EntityManager.QueueDeleteEntity(uid); + return; + } + + var contained = true; + var transform = Transform(args.OtherEntity); + var directions = Enum.GetValues().Length; + for (var i = 0; i < directions - 1; i += 2) // Skip every other direction, checking only cardinals + { + if (!CheckContainmentField((Direction)i, new Entity(args.OtherEntity, generatorComp), transform)) + contained = false; + } + + if (!contained) { + generatorComp.NextFailsafe = _timing.CurTime + generatorComp.FailsafeCooldown; + _popupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe", ("target", args.OtherEntity)), args.OtherEntity, PopupType.LargeCaution); + } + else SetPower( args.OtherEntity, - singularityGeneratorComponent.Power + component.State switch + generatorComp.Power + component.State switch { ParticleAcceleratorPowerState.Standby => 0, ParticleAcceleratorPowerState.Level0 => 1, @@ -113,10 +149,51 @@ private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent co ParticleAcceleratorPowerState.Level3 => 8, _ => 0 }, - singularityGeneratorComponent + generatorComp ); - EntityManager.QueueDeleteEntity(uid); - } + EntityManager.QueueDeleteEntity(uid); + } + + private void OnEmagged(EntityUid uid, SingularityGeneratorComponent component, ref GotEmaggedEvent args) + { + _popupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe-disabled", ("target", uid)), uid); + component.FailsafeDisabled = true; + args.Handled = true; } #endregion Event Handlers + + /// + /// Checks whether there's a containment field in a given direction away from the generator + /// + /// The transform component of the singularity generator. + /// Mostly copied from + private bool CheckContainmentField(Direction dir, Entity generator, TransformComponent transform) + { + var component = generator.Comp; + + var (worldPosition, worldRotation) = _transformSystem.GetWorldPositionRotation(transform); + var dirRad = dir.ToAngle() + worldRotation; + + var ray = new CollisionRay(worldPosition, dirRad.ToVec(), component.CollisionMask); + var rayCastResults = _physics.IntersectRay(transform.MapID, ray, component.FailsafeDistance, generator, false); + var genQuery = GetEntityQuery(); + + RayCastResults? closestResult = null; + + foreach (var result in rayCastResults) + { + if (genQuery.HasComponent(result.HitEntity)) + closestResult = result; + + break; + } + + if (closestResult == null) + return false; + + var ent = closestResult.Value.HitEntity; + + // Check that the field can't be moved. The fields' transform parenting is weird, so skip that + return TryComp(ent, out var collidableComponent) && collidableComponent.BodyType == BodyType.Static; + } } diff --git a/Resources/Locale/en-US/singularity/components/generator-component.ftl b/Resources/Locale/en-US/singularity/components/generator-component.ftl new file mode 100644 index 00000000000..f3a2254c38d --- /dev/null +++ b/Resources/Locale/en-US/singularity/components/generator-component.ftl @@ -0,0 +1,2 @@ +comp-generator-failsafe = The {$target} shakes as the containment failsafe triggers! +comp-generator-failsafe = Something fizzles out inside of {$target}... \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/generator.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/generator.yml index 647eae27724..45a40bf0faf 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/generator.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/generator.yml @@ -1,7 +1,7 @@ - type: entity id: SingularityGenerator name: gravitational singularity generator - description: An Odd Device which produces a Gravitational Singularity when set up. + description: An Odd Device which produces a Gravitational Singularity when set up. Comes with a temporary shutdown containment failsafe. placement: mode: SnapgridCenter components: diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/generator.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/generator.yml index d45e6c58ea7..bdd90f2f16a 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/generator.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/generator.yml @@ -2,12 +2,12 @@ id: TeslaGenerator name: tesla generator parent: BaseStructureDynamic - description: An Odd Device which produces a powerful Tesla ball when set up. + description: An Odd Device which produces a powerful Tesla ball when set up. Comes with a temporary shutdown containment failsafe. components: - type: Sprite noRot: true sprite: Structures/Power/Generation/Tesla/generator.rsi - state: icon + state: icon - type: SingularityGenerator # TODO: rename the generator spawnId: TeslaEnergyBall - type: InteractionOutline From 01d6df3d0ace170438aae3931339be539bd8b38e Mon Sep 17 00:00:00 2001 From: Saphire Date: Sun, 17 Nov 2024 04:18:00 +0600 Subject: [PATCH 047/218] Fix Fluent string ID copypaste fail --- .../Locale/en-US/singularity/components/generator-component.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Locale/en-US/singularity/components/generator-component.ftl b/Resources/Locale/en-US/singularity/components/generator-component.ftl index f3a2254c38d..d2a04f9cbc0 100644 --- a/Resources/Locale/en-US/singularity/components/generator-component.ftl +++ b/Resources/Locale/en-US/singularity/components/generator-component.ftl @@ -1,2 +1,2 @@ comp-generator-failsafe = The {$target} shakes as the containment failsafe triggers! -comp-generator-failsafe = Something fizzles out inside of {$target}... \ No newline at end of file +comp-generator-failsafe-disabled = Something fizzles out inside of {$target}... \ No newline at end of file From 476f90df095502089d9f60ad59099f405be36cd2 Mon Sep 17 00:00:00 2001 From: Saphire Date: Sun, 17 Nov 2024 04:31:34 +0600 Subject: [PATCH 048/218] Fix the component defaults --- .../Singularity/Components/SingularityGeneratorComponent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs b/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs index 180b8499584..8b9b4e74466 100644 --- a/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs +++ b/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs @@ -53,7 +53,7 @@ public sealed partial class SingularityGeneratorComponent : Component /// Message to use when there's no containment field on cardinal directions /// [DataField] - public LocId ContainmentFailsafeMessage; + public LocId ContainmentFailsafeMessage = "comp-generator-failsafe"; /// /// For how long the failsafe will cause the generator to stop working and not issue a failsafe warning @@ -66,5 +66,5 @@ public sealed partial class SingularityGeneratorComponent : Component /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoPausedField] - public TimeSpan NextFailsafe; + public TimeSpan NextFailsafe = TimeSpan.Zero; } From e290588624169c5bfe043797ac4a1e5af6c6c0a1 Mon Sep 17 00:00:00 2001 From: ScarKy0 Date: Sun, 17 Nov 2024 20:23:45 +0100 Subject: [PATCH 049/218] Changes + Cleanup --- Content.Server/Silicons/Laws/SiliconLawSystem.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 3bf0297c40e..8341e3185cb 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.Linq; using Content.Server.Administration; using Content.Server.Chat.Managers; @@ -67,8 +66,7 @@ private void OnMindAdded(EntityUid uid, SiliconLawBoundComponent component, Mind 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("#2ed2fd")); + _chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.FromHex("#2ed2fd")); if (!TryComp(uid, out var lawcomp)) return; @@ -78,8 +76,7 @@ private void OnMindAdded(EntityUid uid, SiliconLawBoundComponent component, Mind var modifedLawMsg = Loc.GetString("laws-notify-subverted"); var modifiedLawWrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", modifedLawMsg)); - _chatManager.ChatMessageToOne(ChatChannel.Server, modifedLawMsg, modifiedLawWrappedMessage, default, false, - actor.PlayerSession.Channel, colorOverride: Color.Red); + _chatManager.ChatMessageToOne(ChatChannel.Server, modifedLawMsg, modifiedLawWrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Red); } private void OnLawProviderMindAdded(Entity ent, ref MindAddedMessage args) @@ -185,7 +182,8 @@ protected override void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent comp base.OnGotEmagged(uid, component, ref args); NotifyLawsChanged(uid, component.EmaggedSound); - EnsureSubvertedSiliconRole(uid); + if(_mind.TryGetMind(uid, out var mindId, out _)) + EnsureSubvertedSiliconRole(mindId); _stunSystem.TryParalyze(uid, component.StunTime, true); From 97be261631d90f418874844a0063d9b74a8ee43d Mon Sep 17 00:00:00 2001 From: Justice League Date: Sun, 17 Nov 2024 18:55:57 -0500 Subject: [PATCH 050/218] Reduced cost of coloured light fixtures --- .../Recipes/Construction/Graphs/utilities/lighting.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/lighting.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/lighting.yml index 378feb1cf80..977be3f4d69 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/lighting.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/lighting.yml @@ -28,7 +28,7 @@ - to: icon steps: - material: Glass - amount: 2 + amount: 1 doAfter: 1 - tag: CrystalBlue name: blue crystal shard @@ -48,7 +48,7 @@ - to: icon steps: - material: Glass - amount: 2 + amount: 1 doAfter: 1 - tag: CrystalPink name: pink crystal shard @@ -68,7 +68,7 @@ - to: icon steps: - material: Glass - amount: 2 + amount: 1 doAfter: 1 - tag: CrystalOrange name: orange crystal shard @@ -88,7 +88,7 @@ - to: icon steps: - material: Glass - amount: 2 + amount: 1 doAfter: 1 - tag: CrystalRed name: red crystal shard @@ -108,7 +108,7 @@ - to: icon steps: - material: Glass - amount: 2 + amount: 1 doAfter: 1 - tag: CrystalGreen name: green crystal shard From 68eaf6ff254e49789696f5a79691c119e26cbb18 Mon Sep 17 00:00:00 2001 From: Saphire Date: Tue, 19 Nov 2024 08:11:10 +0600 Subject: [PATCH 051/218] Bump the failsafe timer down --- .../Singularity/Components/SingularityGeneratorComponent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs b/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs index 8b9b4e74466..c8feeb5d5db 100644 --- a/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs +++ b/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Prototypes; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Content.Server.Singularity.EntitySystems; @@ -59,7 +59,7 @@ public sealed partial class SingularityGeneratorComponent : Component /// For how long the failsafe will cause the generator to stop working and not issue a failsafe warning /// [DataField] - public TimeSpan FailsafeCooldown = TimeSpan.FromSeconds(30); + public TimeSpan FailsafeCooldown = TimeSpan.FromSeconds(10); /// /// How long until the generator can issue a failsafe warning again From 895648aa2c753181c261a6bed8461730f4248ec8 Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:13:02 +0200 Subject: [PATCH 052/218] Increase softcap back to 80 (#33400) --- Resources/ConfigPresets/WizardsDen/wizardsDen.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/ConfigPresets/WizardsDen/wizardsDen.toml b/Resources/ConfigPresets/WizardsDen/wizardsDen.toml index 2b059ca40e3..28bab5d4c76 100644 --- a/Resources/ConfigPresets/WizardsDen/wizardsDen.toml +++ b/Resources/ConfigPresets/WizardsDen/wizardsDen.toml @@ -4,7 +4,7 @@ [game] desc = "Official English Space Station 14 servers. Vanilla, roleplay ruleset." lobbyenabled = true -soft_max_players = 70 +soft_max_players = 80 panic_bunker.enabled = true panic_bunker.disable_with_admins = true panic_bunker.enable_without_admins = true From a949cf33e92384d209886874c2727e22611aca12 Mon Sep 17 00:00:00 2001 From: ArZarLordOfMango <96249677+ArZarLordOfMango@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:31:37 +0100 Subject: [PATCH 053/218] Toggle clothing fix (#32826) * toggle clothing fix * some adding --- .../Clothing/Components/ToggleClothingComponent.cs | 6 ++++++ .../Clothing/EntitySystems/ToggleClothingSystem.cs | 4 ++++ Resources/Prototypes/Entities/Clothing/Hands/gloves.yml | 1 + Resources/Prototypes/Entities/Clothing/Head/helmets.yml | 1 + .../Prototypes/Entities/Clothing/OuterClothing/suits.yml | 1 + Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml | 1 + 6 files changed, 14 insertions(+) diff --git a/Content.Shared/Clothing/Components/ToggleClothingComponent.cs b/Content.Shared/Clothing/Components/ToggleClothingComponent.cs index 04bc3ed4e83..f827cbfea8d 100644 --- a/Content.Shared/Clothing/Components/ToggleClothingComponent.cs +++ b/Content.Shared/Clothing/Components/ToggleClothingComponent.cs @@ -33,6 +33,12 @@ public sealed partial class ToggleClothingComponent : Component /// [DataField] public bool DisableOnUnequip; + + /// + /// If true, the clothes must equip for adding action. + /// + [DataField] + public bool MustEquip = true; } /// diff --git a/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs index 9889376c9d4..8d6e3e3de65 100644 --- a/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs @@ -39,8 +39,12 @@ private void OnMapInit(Entity ent, ref MapInitEvent arg private void OnGetActions(Entity ent, ref GetItemActionsEvent args) { + if (args.InHands && ent.Comp.MustEquip) + return; + var ev = new ToggleClothingCheckEvent(args.User); RaiseLocalEvent(ent, ref ev); + if (!ev.Cancelled) args.AddAction(ent.Comp.ActionEntity); } diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml index 16777cd6900..1dd1e0ba042 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml @@ -230,6 +230,7 @@ stealthy: true - type: ToggleClothing action: ActionToggleNinjaGloves + disableOnUnequip: true - type: NinjaGloves abilities: - components: diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index 11643c076d8..f06f20249b4 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -452,6 +452,7 @@ delay: 1.0 - type: ToggleClothing action: ActionToggleJusticeHelm + mustEquip: false - type: ItemTogglePointLight - type: ToggleableLightVisuals clothingVisuals: diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index a58c2a3fdd4..f78694a0fab 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -155,6 +155,7 @@ # phase cloak - type: ToggleClothing action: ActionTogglePhaseCloak + disableOnUnequip: true - type: ComponentToggler parent: true components: diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml index 0250671acfc..ab8084c91c0 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml @@ -14,6 +14,7 @@ sprite: Clothing/Shoes/Boots/magboots.rsi - type: ToggleClothing action: ActionToggleMagboots + mustEquip: false - type: ToggleVerb text: toggle-magboots-verb-get-data-text - type: ComponentToggler From 42ee90e53e7ee541ebdff85215f5ebf95cb19373 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 19 Nov 2024 20:32:47 +0000 Subject: [PATCH 054/218] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index dbe7ac5b284..b33d4b00be7 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Fix mains light on wires not being lit. - type: Fix - id: 7123 - time: '2024-08-16T03:59:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31066 - author: IgorAnt028 changes: - message: The dead and knocked down now stop holding objects @@ -3918,3 +3911,10 @@ id: 7622 time: '2024-11-19T05:07:02.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/33365 +- author: ArZarLordOfMango + changes: + - message: Most toggleable clothing must now be equipped to toggle their actions. + type: Fix + id: 7623 + time: '2024-11-19T20:31:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32826 From c4e2eb9d0250aebed6e7f7048f93820701b4b22d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 20 Nov 2024 01:17:45 +0100 Subject: [PATCH 055/218] .NET 9 forward compatibility changes (#33421) This doesn't switch the projects over to .NET 9, but it does make them work on .NET 9 when we decide to switch in the future. --- Content.Server/Announcements/AnnounceCommand.cs | 3 ++- Content.Server/Cargo/Systems/PricingSystem.cs | 2 +- .../NPC/Pathfinding/PathfindingSystem.Breadth.cs | 4 ++-- .../NPC/Pathfinding/PathfindingSystem.Splines.cs | 2 +- .../NPC/Pathfinding/PathfindingSystem.Widen.cs | 2 +- Content.Shared/Forensics/Events.cs | 2 +- .../Silicons/StationAi/StationAiVisionSystem.cs | 11 ++++++----- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Content.Server/Announcements/AnnounceCommand.cs b/Content.Server/Announcements/AnnounceCommand.cs index 2307f36a5d0..3249fcc95d2 100644 --- a/Content.Server/Announcements/AnnounceCommand.cs +++ b/Content.Server/Announcements/AnnounceCommand.cs @@ -28,7 +28,8 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) } else { - var message = string.Join(' ', new ArraySegment(args, 1, args.Length-1)); + // Explicit IEnumerable due to overload ambiguity on .NET 9 + var message = string.Join(' ', (IEnumerable)new ArraySegment(args, 1, args.Length-1)); chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold); } shell.WriteLine("Sent!"); diff --git a/Content.Server/Cargo/Systems/PricingSystem.cs b/Content.Server/Cargo/Systems/PricingSystem.cs index 830368baa3c..edc273b3c1f 100644 --- a/Content.Server/Cargo/Systems/PricingSystem.cs +++ b/Content.Server/Cargo/Systems/PricingSystem.cs @@ -424,7 +424,7 @@ public record struct PriceCalculationEvent() [ByRefEvent] public record struct EstimatedPriceCalculationEvent() { - public EntityPrototype Prototype; + public required EntityPrototype Prototype; /// /// The total price of the entity. diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs index ee8eaa9ad1a..1504894b4a8 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs @@ -11,8 +11,8 @@ public sealed partial class PathfindingSystem /// public record struct BreadthPathArgs() { - public Vector2i Start; - public List Ends; + public required Vector2i Start; + public required List Ends; public bool Diagonals = false; diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs index 9979755f995..91c42e651c1 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs @@ -19,7 +19,7 @@ public record struct SplinePathResult() public List Points = new(); public List Path = new(); - public Dictionary CameFrom; + public Dictionary? CameFrom; } public record struct SplinePathArgs(SimplePathArgs Args) diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs index f7bcd019f5f..11ac93876ef 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs @@ -84,6 +84,6 @@ public record struct WidenArgs() public float MaxWiden = 7f; - public List Path; + public required List Path; } } diff --git a/Content.Shared/Forensics/Events.cs b/Content.Shared/Forensics/Events.cs index f7b9475cb57..c346d08536d 100644 --- a/Content.Shared/Forensics/Events.cs +++ b/Content.Shared/Forensics/Events.cs @@ -66,5 +66,5 @@ public record struct GenerateDnaEvent() /// /// The generated DNA. /// - public string DNA; + public required string DNA; } diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs index bdc62a6bb37..d3416949d53 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -56,6 +56,7 @@ public override void Initialize() EntManager = EntityManager, Maps = _maps, System = this, + VisibleTiles = _singleTiles, }; } @@ -278,7 +279,7 @@ private bool IsCorner( /// private record struct SeedJob() : IRobustJob { - public StationAiVisionSystem System; + public required StationAiVisionSystem System; public Entity Grid; public Box2 ExpandedBounds; @@ -293,14 +294,14 @@ private record struct ViewJob() : IParallelRobustJob { public int BatchSize => 1; - public IEntityManager EntManager; - public SharedMapSystem Maps; - public StationAiVisionSystem System; + public required IEntityManager EntManager; + public required SharedMapSystem Maps; + public required StationAiVisionSystem System; public Entity Grid; public List> Data = new(); - public HashSet VisibleTiles; + public required HashSet VisibleTiles; public readonly List> Vis1 = new(); public readonly List> Vis2 = new(); From 7f5bae99bb72878ef139f4c5cfbbafcf9b609720 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:57:01 -0800 Subject: [PATCH 056/218] Fix security riot crate (#33415) * move riot crate from security to armory category * Move riot crate to armory, actually make it require armory access to unlock --- .../Prototypes/Catalog/Cargo/cargo_armory.yml | 10 ++++++++++ .../Catalog/Cargo/cargo_security.yml | 10 ---------- .../Catalog/Fills/Crates/armory.yml | 19 ++++++++++++++++++ .../Catalog/Fills/Crates/security.yml | 20 ------------------- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_armory.yml b/Resources/Prototypes/Catalog/Cargo/cargo_armory.yml index 6341042bf89..26748e82268 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_armory.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_armory.yml @@ -18,6 +18,16 @@ category: cargoproduct-category-name-armory group: market +- type: cargoProduct + id: SecurityRiot + icon: + sprite: Clothing/OuterClothing/Armor/riot.rsi + state: icon + product: CrateSecurityRiot + cost: 7500 + category: cargoproduct-category-name-armory + group: market + - type: cargoProduct id: TrackingImplant icon: diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_security.yml b/Resources/Prototypes/Catalog/Cargo/cargo_security.yml index a5d4e5f70a1..3fa03ea2e39 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_security.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_security.yml @@ -28,16 +28,6 @@ category: cargoproduct-category-name-security group: market -- type: cargoProduct - id: SecurityRiot - icon: - sprite: Clothing/OuterClothing/Armor/riot.rsi - state: icon - product: CrateSecurityRiot - cost: 7500 - category: cargoproduct-category-name-security - group: market - - type: cargoProduct id: SecuritySupplies icon: diff --git a/Resources/Prototypes/Catalog/Fills/Crates/armory.yml b/Resources/Prototypes/Catalog/Fills/Crates/armory.yml index bc5377fe81f..fadaf2f01ed 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/armory.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/armory.yml @@ -69,3 +69,22 @@ amount: 2 - id: MagazinePistol amount: 4 + +- type: entity + id: CrateSecurityRiot + parent: [ CrateWeaponSecure, BaseRestrictedContraband ] + name: swat crate + description: Contains two sets of riot armor, helmets, shields, and enforcers loaded with beanbags. Extra ammo is included. Requires Armory access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterArmorRiot + amount: 2 + - id: ClothingHeadHelmetRiot + amount: 2 + - id: WeaponShotgunEnforcerRubber + amount: 2 + - id: BoxBeanbag + amount: 2 + - id: RiotShield + amount: 2 diff --git a/Resources/Prototypes/Catalog/Fills/Crates/security.yml b/Resources/Prototypes/Catalog/Fills/Crates/security.yml index 38597adb1ee..a0351968498 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/security.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/security.yml @@ -38,26 +38,6 @@ # - Pepperspray # - GrenadeTeargas -- type: entity - id: CrateSecurityRiot - parent: CrateSecgear - name: swat crate - description: Contains two sets of riot armor, helmets, shields, and enforcers loaded with beanbags. Extra ammo is included. Requires Armory access to open. - components: - - type: StorageFill - contents: - - id: ClothingOuterArmorRiot - amount: 2 - - id: ClothingHeadHelmetRiot - amount: 2 - - id: WeaponShotgunEnforcerRubber - amount: 2 - - id: BoxBeanbag - amount: 2 - - id: RiotShield - amount: 2 -# - SecGasmask - - type: entity id: CrateSecuritySupplies parent: CrateSecgear From 2002de9bb01e53e70b00fea5b3f4ffeed55a3cfa Mon Sep 17 00:00:00 2001 From: MilenVolf <63782763+MilenVolf@users.noreply.github.com> Date: Wed, 20 Nov 2024 03:57:43 +0300 Subject: [PATCH 057/218] Localize planet dataset names (#33398) * Localize planet names (borer) * DatasetPrototype -> LocalizedDatasetPrototype * Apply requested changes --- .../Gateway/Systems/GatewayGeneratorSystem.cs | 7 +- .../SalvageSystem.ExpeditionConsole.cs | 2 +- .../Salvage/SpawnSalvageMissionJob.cs | 4 +- .../Shuttles/Components/GridSpawnComponent.cs | 6 +- .../Systems/ShuttleSystem.GridFill.cs | 2 +- .../Shuttles/Systems/ShuttleSystem.cs | 2 + Content.Shared/Salvage/SharedSalvageSystem.cs | 4 +- .../Locale/en-US/datasets/names/borer.ftl | 69 +++++++++++++++++ Resources/Prototypes/Datasets/Names/borer.yml | 75 +------------------ .../Prototypes/Entities/Stations/base.yml | 2 +- 10 files changed, 90 insertions(+), 83 deletions(-) create mode 100644 Resources/Locale/en-US/datasets/names/borer.ftl diff --git a/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs b/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs index 7dcd42e06fa..666d1045174 100644 --- a/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs +++ b/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs @@ -34,10 +34,11 @@ public sealed class GatewayGeneratorSystem : EntitySystem [Dependency] private readonly GatewaySystem _gateway = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly SharedMapSystem _maps = default!; + [Dependency] private readonly SharedSalvageSystem _salvage = default!; [Dependency] private readonly TileSystem _tile = default!; - [ValidatePrototypeId] - private const string PlanetNames = "names_borer"; + [ValidatePrototypeId] + private const string PlanetNames = "NamesBorer"; // TODO: // Fix shader some more @@ -102,7 +103,7 @@ private void GenerateDestination(EntityUid uid, GatewayGeneratorComponent? gener var mapId = _mapManager.CreateMap(); var mapUid = _mapManager.GetMapEntityId(mapId); - var gatewayName = SharedSalvageSystem.GetFTLName(_protoManager.Index(PlanetNames), seed); + var gatewayName = _salvage.GetFTLName(_protoManager.Index(PlanetNames), seed); _metadata.SetEntityName(mapUid, gatewayName); var origin = new Vector2i(random.Next(-MaxOffset, MaxOffset), random.Next(-MaxOffset, MaxOffset)); diff --git a/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs b/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs index d0314184767..a9d8314f57c 100644 --- a/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs +++ b/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs @@ -28,7 +28,7 @@ private void OnSalvageClaimMessage(EntityUid uid, SalvageExpeditionConsoleCompon var mission = GetMission(_prototypeManager.Index(missionparams.Difficulty), missionparams.Seed); data.NextOffer = _timing.CurTime + mission.Duration + TimeSpan.FromSeconds(1); - _labelSystem.Label(cdUid, GetFTLName(_prototypeManager.Index("names_borer"), missionparams.Seed)); + _labelSystem.Label(cdUid, GetFTLName(_prototypeManager.Index("NamesBorer"), missionparams.Seed)); _audio.PlayPvs(component.PrintSound, uid); UpdateConsoles((station.Value, data)); diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index 525fe01a1f8..31c6b73253d 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -104,7 +104,9 @@ protected override async Task Process() destComp.BeaconsOnly = true; destComp.RequireCoordinateDisk = true; destComp.Enabled = true; - _metaData.SetEntityName(mapUid, SharedSalvageSystem.GetFTLName(_prototypeManager.Index("names_borer"), _missionParams.Seed)); + _metaData.SetEntityName( + mapUid, + _entManager.System().GetFTLName(_prototypeManager.Index("NamesBorer"), _missionParams.Seed)); _entManager.AddComponent(mapUid); // Saving the mission mapUid to a CD is made optional, in case one is somehow made in a process without a CD entity diff --git a/Content.Server/Shuttles/Components/GridSpawnComponent.cs b/Content.Server/Shuttles/Components/GridSpawnComponent.cs index 430c9c8df28..18959dd7f37 100644 --- a/Content.Server/Shuttles/Components/GridSpawnComponent.cs +++ b/Content.Server/Shuttles/Components/GridSpawnComponent.cs @@ -32,7 +32,7 @@ public interface IGridSpawnGroup public float MaximumDistance { get; } /// - public ProtoId? NameDataset { get; } + public ProtoId? NameDataset { get; } /// int MinCount { get; set; } @@ -75,7 +75,7 @@ public sealed class DungeonSpawnGroup : IGridSpawnGroup public float MaximumDistance { get; } /// - public ProtoId? NameDataset { get; } + public ProtoId? NameDataset { get; } /// public int MinCount { get; set; } = 1; @@ -106,7 +106,7 @@ public sealed class GridSpawnGroup : IGridSpawnGroup /// public float MaximumDistance { get; } - public ProtoId? NameDataset { get; } + public ProtoId? NameDataset { get; } public int MinCount { get; set; } = 1; public int MaxCount { get; set; } = 1; public ComponentRegistry AddComponents { get; set; } = new(); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs index 5ad94699bed..de0593b26f0 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -208,7 +208,7 @@ private void GridSpawns(EntityUid uid, GridSpawnComponent component) if (_protoManager.TryIndex(group.NameDataset, out var dataset)) { - _metadata.SetEntityName(spawned, SharedSalvageSystem.GetFTLName(dataset, _random.Next())); + _metadata.SetEntityName(spawned, _salvage.GetFTLName(dataset, _random.Next())); } if (group.Hide) diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 054c42f934a..6e8c1a9e204 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -8,6 +8,7 @@ using Content.Server.Stunnable; using Content.Shared.GameTicking; using Content.Shared.Mobs.Systems; +using Content.Shared.Salvage; using Content.Shared.Shuttles.Systems; using Content.Shared.Throwing; using JetBrains.Annotations; @@ -51,6 +52,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedSalvageSystem _salvage = default!; [Dependency] private readonly ShuttleConsoleSystem _console = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StunSystem _stuns = default!; diff --git a/Content.Shared/Salvage/SharedSalvageSystem.cs b/Content.Shared/Salvage/SharedSalvageSystem.cs index 0c56f4f556e..12d0a26449c 100644 --- a/Content.Shared/Salvage/SharedSalvageSystem.cs +++ b/Content.Shared/Salvage/SharedSalvageSystem.cs @@ -26,10 +26,10 @@ public abstract partial class SharedSalvageSystem : EntitySystem [ValidatePrototypeId] public const string ExpeditionsLootProto = "SalvageLoot"; - public static string GetFTLName(DatasetPrototype dataset, int seed) + public string GetFTLName(LocalizedDatasetPrototype dataset, int seed) { var random = new System.Random(seed); - return $"{dataset.Values[random.Next(dataset.Values.Count)]}-{random.Next(10, 100)}-{(char) (65 + random.Next(26))}"; + return $"{Loc.GetString(dataset.Values[random.Next(dataset.Values.Count)])}-{random.Next(10, 100)}-{(char) (65 + random.Next(26))}"; } public SalvageMission GetMission(SalvageDifficultyPrototype difficulty, int seed) diff --git a/Resources/Locale/en-US/datasets/names/borer.ftl b/Resources/Locale/en-US/datasets/names/borer.ftl new file mode 100644 index 00000000000..17cae851b9e --- /dev/null +++ b/Resources/Locale/en-US/datasets/names/borer.ftl @@ -0,0 +1,69 @@ +names-borer-dataset-1 = Alcyonium +names-borer-dataset-2 = Anomia +names-borer-dataset-3 = Aphrodita +names-borer-dataset-4 = Arca +names-borer-dataset-5 = Argonauta +names-borer-dataset-6 = Ascaris +names-borer-dataset-7 = Asterias +names-borer-dataset-8 = Buccinum +names-borer-dataset-9 = Bulla +names-borer-dataset-10 = Cardium +names-borer-dataset-11 = Chama +names-borer-dataset-12 = Chiton +names-borer-dataset-13 = Conus +names-borer-dataset-14 = Corallina +names-borer-dataset-15 = Cypraea +names-borer-dataset-16 = Dentalium +names-borer-dataset-17 = Donax +names-borer-dataset-18 = Doris +names-borer-dataset-19 = Echinus +names-borer-dataset-20 = Eschara +names-borer-dataset-21 = Fasciola +names-borer-dataset-22 = Furia +names-borer-dataset-23 = Gordius +names-borer-dataset-24 = Gorgonia +names-borer-dataset-25 = Haliotis +names-borer-dataset-26 = Helix +names-borer-dataset-27 = Hirudo +names-borer-dataset-28 = Holothuria +names-borer-dataset-29 = Hydra +names-borer-dataset-30 = Isis +names-borer-dataset-31 = Lepas +names-borer-dataset-32 = Lernaea +names-borer-dataset-33 = Limax +names-borer-dataset-34 = Lumbricus +names-borer-dataset-35 = Madrepora +names-borer-dataset-36 = Medusa +names-borer-dataset-37 = Millepora +names-borer-dataset-38 = Murex +names-borer-dataset-39 = Myes +names-borer-dataset-40 = Mytilus +names-borer-dataset-41 = Myxine +names-borer-dataset-42 = Nautilus +names-borer-dataset-43 = Nereis +names-borer-dataset-44 = Neritha +names-borer-dataset-45 = Ostrea +names-borer-dataset-46 = Patella +names-borer-dataset-47 = Pennatula +names-borer-dataset-48 = Pholas +names-borer-dataset-49 = Pinna +names-borer-dataset-50 = Priapus +names-borer-dataset-51 = Scyllaea +names-borer-dataset-52 = Sepia +names-borer-dataset-53 = Serpula +names-borer-dataset-54 = Sertularia +names-borer-dataset-55 = Solen +names-borer-dataset-56 = Spondylus +names-borer-dataset-57 = Strombus +names-borer-dataset-58 = Taenia +names-borer-dataset-59 = Tellina +names-borer-dataset-60 = Teredo +names-borer-dataset-61 = Tethys +names-borer-dataset-62 = Triton +names-borer-dataset-63 = Trochus +names-borer-dataset-64 = Tubipora +names-borer-dataset-65 = Tubularia +names-borer-dataset-66 = Turbo +names-borer-dataset-67 = Venus +names-borer-dataset-68 = Voluta +names-borer-dataset-69 = Volvox diff --git a/Resources/Prototypes/Datasets/Names/borer.yml b/Resources/Prototypes/Datasets/Names/borer.yml index 03667f67a7a..e8d741e6a96 100644 --- a/Resources/Prototypes/Datasets/Names/borer.yml +++ b/Resources/Prototypes/Datasets/Names/borer.yml @@ -1,72 +1,5 @@ -- type: dataset - id: names_borer +- type: localizedDataset + id: NamesBorer values: - - Alcyonium - - Anomia - - Aphrodita - - Arca - - Argonauta - - Ascaris - - Asterias - - Buccinum - - Bulla - - Cardium - - Chama - - Chiton - - Conus - - Corallina - - Cypraea - - Dentalium - - Donax - - Doris - - Echinus - - Eschara - - Fasciola - - Furia - - Gordius - - Gorgonia - - Haliotis - - Helix - - Hirudo - - Holothuria - - Hydra - - Isis - - Lepas - - Lernaea - - Limax - - Lumbricus - - Madrepora - - Medusa - - Millepora - - Murex - - Myes - - Mytilus - - Myxine - - Nautilus - - Nereis - - Neritha - - Ostrea - - Patella - - Pennatula - - Pholas - - Pinna - - Priapus - - Scyllaea - - Sepia - - Serpula - - Sertularia - - Solen - - Spondylus - - Strombus - - Taenia - - Tellina - - Teredo - - Tethys - - Triton - - Trochus - - Tubipora - - Tubularia - - Turbo - - Venus - - Voluta - - Volvox + prefix: names-borer-dataset- + count: 69 diff --git a/Resources/Prototypes/Entities/Stations/base.yml b/Resources/Prototypes/Entities/Stations/base.yml index effb391404d..5d95e7cbe06 100644 --- a/Resources/Prototypes/Entities/Stations/base.yml +++ b/Resources/Prototypes/Entities/Stations/base.yml @@ -73,7 +73,7 @@ vgroid: !type:DungeonSpawnGroup minimumDistance: 300 maximumDistance: 350 - nameDataset: names_borer + nameDataset: NamesBorer stationGrid: false addComponents: - type: Gravity From efa28fc650076ddef317903aed5ff1a2b664cf68 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 20 Nov 2024 00:58:08 +0000 Subject: [PATCH 058/218] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b33d4b00be7..b2f63bb5964 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: IgorAnt028 - changes: - - message: The dead and knocked down now stop holding objects - type: Fix - id: 7124 - time: '2024-08-16T04:53:34.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31009 - author: SlamBamActionman changes: - message: Nar'Sie is satiated; moppable blood will no longer duplicate. @@ -3918,3 +3911,10 @@ id: 7623 time: '2024-11-19T20:31:38.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32826 +- author: Plykiya + changes: + - message: The SWAT crate from cargo now requires armory access to open. + type: Fix + id: 7624 + time: '2024-11-20T00:57:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33415 From 89392e2424a4d428fbb6996dec6615dfe0c05f41 Mon Sep 17 00:00:00 2001 From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Date: Wed, 20 Nov 2024 02:00:38 +0100 Subject: [PATCH 059/218] Remove drag & drop dropping items from containers (#32706) * Initial commit * Update based on maintainer discussion * Forgot to remove this woops --- .../Systems/Storage/StorageUIController.cs | 6 ------ .../Storage/EntitySystems/SharedStorageSystem.cs | 14 -------------- Content.Shared/Storage/StorageComponent.cs | 14 -------------- 3 files changed, 34 deletions(-) diff --git a/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs b/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs index 97c9d8b7959..1e61ad98380 100644 --- a/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs +++ b/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs @@ -307,12 +307,6 @@ private void OnPieceUnpressed(GUIBoundKeyEventArgs args, ItemGridPiece control) _entity.GetNetEntity(storageEnt), new ItemStorageLocation(DraggingRotation, position))); } - else - { - _entity.RaisePredictiveEvent(new StorageRemoveItemEvent( - _entity.GetNetEntity(draggingGhost.Entity), - _entity.GetNetEntity(storageEnt))); - } _menuDragHelper.EndDrag(); _container?.BuildItemPieces(); diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index fee4c1a0fb6..183420db9d1 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -129,7 +129,6 @@ public override void Initialize() SubscribeAllEvent(OnInteractWithItem); SubscribeAllEvent(OnSetItemLocation); SubscribeAllEvent(OnInsertItemIntoLocation); - SubscribeAllEvent(OnRemoveItem); SubscribeAllEvent(OnSaveItemLocation); SubscribeLocalEvent(OnReclaimed); @@ -639,19 +638,6 @@ private void OnSetItemLocation(StorageSetItemLocationEvent msg, EntitySessionEve TrySetItemStorageLocation(item!, storage!, msg.Location); } - private void OnRemoveItem(StorageRemoveItemEvent msg, EntitySessionEventArgs args) - { - if (!ValidateInput(args, msg.StorageEnt, msg.ItemEnt, out var player, out var storage, out var item)) - return; - - _adminLog.Add( - LogType.Storage, - LogImpact.Low, - $"{ToPrettyString(player):player} is removing {ToPrettyString(item):item} from {ToPrettyString(storage):storage}"); - TransformSystem.DropNextTo(item.Owner, player.Owner); - Audio.PlayPredicted(storage.Comp.StorageRemoveSound, storage, player, _audioParams); - } - private void OnInsertItemIntoLocation(StorageInsertItemIntoLocationEvent msg, EntitySessionEventArgs args) { if (!ValidateInput(args, msg.StorageEnt, msg.ItemEnt, out var player, out var storage, out var item, held: true)) diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index d2c607e57f7..5683ae95a71 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -169,20 +169,6 @@ public StorageSetItemLocationEvent(NetEntity itemEnt, NetEntity storageEnt, Item } } - [Serializable, NetSerializable] - public sealed class StorageRemoveItemEvent : EntityEventArgs - { - public readonly NetEntity ItemEnt; - - public readonly NetEntity StorageEnt; - - public StorageRemoveItemEvent(NetEntity itemEnt, NetEntity storageEnt) - { - ItemEnt = itemEnt; - StorageEnt = storageEnt; - } - } - [Serializable, NetSerializable] public sealed class StorageInsertItemIntoLocationEvent : EntityEventArgs { From ed1ae96fa22cb9ad8af2b21efb8abe4aadfeeed7 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 20 Nov 2024 01:01:45 +0000 Subject: [PATCH 060/218] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b2f63bb5964..a578659a004 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: SlamBamActionman - changes: - - message: Nar'Sie is satiated; moppable blood will no longer duplicate. - type: Fix - id: 7125 - time: '2024-08-16T10:47:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30983 - author: Blackern5000 changes: - message: Disabler SMGs no longer fit in combat boots @@ -3918,3 +3911,11 @@ id: 7624 time: '2024-11-20T00:57:01.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/33415 +- author: SlamBamActionman + changes: + - message: It's no longer possible to drag an item out of a container's UI to drop + it. + type: Tweak + id: 7625 + time: '2024-11-20T01:00:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32706 From eebf06d9d611063410233efae983efbe52c8b493 Mon Sep 17 00:00:00 2001 From: Saphire Lattice Date: Wed, 20 Nov 2024 08:03:52 +0700 Subject: [PATCH 061/218] Automatically add "Approved" to maintainer PRs (#33337) * Add an Approved labeler for maintainer PRs * Be extra safe with conditions --- .github/workflows/labeler-review.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/labeler-review.yml diff --git a/.github/workflows/labeler-review.yml b/.github/workflows/labeler-review.yml new file mode 100644 index 00000000000..79be86a005a --- /dev/null +++ b/.github/workflows/labeler-review.yml @@ -0,0 +1,23 @@ +name: "Labels: Approved" +on: + pull_request_review: + types: [submitted] +jobs: + add_label: + # Change the repository name after you've made sure the team name is correct for your fork! + if: ${{ (github.repository == 'space-wizards/space-station-14') && (github.event.review.state == 'APPROVED') }} + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: tspascoal/get-user-teams-membership@v3 + id: checkUserMember + with: + username: ${{ github.actor }} + team: "content-maintainers,junior-maintainers" # CHANGE TEAM NAME HERE PLEASE <------ + GITHUB_TOKEN: ${{ secrets.PAT }} + - if: ${{ steps.checkUserMember.outputs.isTeamMember == 'true' }} + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: "PR: Approved" \ No newline at end of file From fdf3df9fbdf4687794961038c7b9433c9ff8c104 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:05:20 -0800 Subject: [PATCH 062/218] Crew monitoring crate updated to contain flatpacks, science access instead of engi (#33417) * Make a crew monitoring crate with flatpacks * fix image * migration --- .../Catalog/Cargo/cargo_circuitboards.yml | 9 --------- .../Catalog/Cargo/cargo_science.yml | 10 ++++++++++ .../Catalog/Fills/Crates/circuitboards.yml | 12 ----------- .../Catalog/Fills/Crates/science.yml | 12 +++++++++++ .../Entities/Objects/Devices/flatpack.yml | 20 ++++++++++++++++++- Resources/migration.yml | 3 +++ 6 files changed, 44 insertions(+), 22 deletions(-) delete mode 100644 Resources/Prototypes/Catalog/Cargo/cargo_circuitboards.yml delete mode 100644 Resources/Prototypes/Catalog/Fills/Crates/circuitboards.yml diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_circuitboards.yml b/Resources/Prototypes/Catalog/Cargo/cargo_circuitboards.yml deleted file mode 100644 index a96780fc3ed..00000000000 --- a/Resources/Prototypes/Catalog/Cargo/cargo_circuitboards.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: cargoProduct - id: CrewMonitoringBoards - icon: - sprite: Objects/Misc/module.rsi - state: cpuboard - product: CrateCrewMonitoringBoards - cost: 2000 - category: cargoproduct-category-name-circuitboards - group: market diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_science.yml b/Resources/Prototypes/Catalog/Cargo/cargo_science.yml index 756a223e74d..cb7f8af8224 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_science.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_science.yml @@ -27,3 +27,13 @@ cost: 800 category: cargoproduct-category-name-science group: market + +- type: cargoProduct + id: CrewMonitoring + icon: + sprite: Structures/Machines/server.rsi + state: server + product: CrateCrewMonitoring + cost: 2000 + category: cargoproduct-category-name-science + group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/circuitboards.yml b/Resources/Prototypes/Catalog/Fills/Crates/circuitboards.yml deleted file mode 100644 index 899db4c37c7..00000000000 --- a/Resources/Prototypes/Catalog/Fills/Crates/circuitboards.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: entity - id: CrateCrewMonitoringBoards - parent: CrateEngineeringSecure - name: crew monitoring boards - description: Has two crew monitoring console and server replacements. Requires engineering access to open. - components: - - type: StorageFill - contents: - - id: CrewMonitoringComputerCircuitboard - amount: 2 - - id: CrewMonitoringServerMachineCircuitboard - amount: 2 diff --git a/Resources/Prototypes/Catalog/Fills/Crates/science.yml b/Resources/Prototypes/Catalog/Fills/Crates/science.yml index 15ce05708e3..6adf5942a43 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/science.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/science.yml @@ -12,3 +12,15 @@ amount: 2 - id: ClothingMaskSterile amount: 2 + +- type: entity + id: CrateCrewMonitoring + parent: CrateScienceSecure + name: crew monitoring crate + description: Contains a flatpack of a crew monitoring server and a few crew monitoring computers. Requires Science access to open. + components: + - type: StorageFill + contents: + - id: CrewMonitoringServerFlatpack + - id: CrewMonitoringComputerFlatpack + amount: 3 diff --git a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml index 5fb81aa6d48..82d43cf7d82 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml @@ -215,4 +215,22 @@ layers: - state: fax-machine - type: Flatpack - entity: FaxMachineBase \ No newline at end of file + entity: FaxMachineBase + +- type: entity + parent: BaseFlatpack + id: CrewMonitoringServerFlatpack + name: crew monitoring server flatpack + description: A flatpack used for constructing a crew monitoring server. + components: + - type: Flatpack + entity: CrewMonitoringServer + +- type: entity + parent: BaseFlatpack + id: CrewMonitoringComputerFlatpack + name: crew monitoring computer flatpack + description: A flatpack used for constructing a crew monitoring console. + components: + - type: Flatpack + entity: ComputerCrewMonitoring diff --git a/Resources/migration.yml b/Resources/migration.yml index 8940df2b65e..a09986ebd86 100644 --- a/Resources/migration.yml +++ b/Resources/migration.yml @@ -475,3 +475,6 @@ LeftLegBorgJanitor: LeftLegBorg RightLegBorgJanitor: RightLegBorg HeadBorgJanitor: LightHeadBorg TorsoBorgJanitor: TorsoBorg + +# 2024-11-19 +CrateCrewMonitoringBoards: CrateCrewMonitoring From 35e2c641c1960195509c52fd6350cff720f76868 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 20 Nov 2024 01:06:27 +0000 Subject: [PATCH 063/218] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index a578659a004..0eaa287ba93 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Blackern5000 - changes: - - message: Disabler SMGs no longer fit in combat boots - type: Fix - id: 7126 - time: '2024-08-17T01:00:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31110 - author: Mervill changes: - message: Fixed suffocation alerts not appearing. @@ -3919,3 +3912,11 @@ id: 7625 time: '2024-11-20T01:00:38.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32706 +- author: Plykiya + changes: + - message: The crew monitoring crate now contains a flatpack of the server and computers, + and can be opened with science access instead of engineering access now. + type: Tweak + id: 7626 + time: '2024-11-20T01:05:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33417 From 6e53cd98a400466640586bf19b41ec281944795e Mon Sep 17 00:00:00 2001 From: SlamBamActionman Date: Tue, 19 Nov 2024 16:28:58 +0100 Subject: [PATCH 064/218] Add emag functionality --- .../SingularityGeneratorSystem.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs index be0c5e49b5f..cfca86bf4af 100644 --- a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs @@ -117,27 +117,31 @@ private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent co if (!EntityManager.TryGetComponent(args.OtherEntity, out var generatorComp)) return; - if (_timing.CurTime < _metadata.GetPauseTime(uid) + generatorComp.NextFailsafe) + if (_timing.CurTime < _metadata.GetPauseTime(uid) + generatorComp.NextFailsafe && !generatorComp.FailsafeDisabled) { EntityManager.QueueDeleteEntity(uid); return; } var contained = true; - var transform = Transform(args.OtherEntity); - var directions = Enum.GetValues().Length; - for (var i = 0; i < directions - 1; i += 2) // Skip every other direction, checking only cardinals + if (!generatorComp.FailsafeDisabled) { - if (!CheckContainmentField((Direction)i, new Entity(args.OtherEntity, generatorComp), transform)) - contained = false; + var transform = Transform(args.OtherEntity); + var directions = Enum.GetValues().Length; + for (var i = 0; i < directions - 1; i += 2) // Skip every other direction, checking only cardinals + { + if (!CheckContainmentField((Direction)i, new Entity(args.OtherEntity, generatorComp), transform)) + contained = false; + } } - if (!contained) + if (!contained && !generatorComp.FailsafeDisabled) { generatorComp.NextFailsafe = _timing.CurTime + generatorComp.FailsafeCooldown; _popupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe", ("target", args.OtherEntity)), args.OtherEntity, PopupType.LargeCaution); } else + { SetPower( args.OtherEntity, generatorComp.Power + component.State switch @@ -151,6 +155,8 @@ private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent co }, generatorComp ); + } + EntityManager.QueueDeleteEntity(uid); } From 9c666457c2c13505725b7d3c336cae50f0666460 Mon Sep 17 00:00:00 2001 From: Saphire Date: Wed, 20 Nov 2024 07:49:45 +0600 Subject: [PATCH 065/218] Move some of the new singularity code into shared Hopefully without explosions yay --- .../SingularityGeneratorSystem.cs | 18 ++---------- .../SingularityGeneratorComponent.cs | 8 ++--- .../SharedSingularityGeneratorSystem.cs | 29 +++++++++++++++++++ 3 files changed, 35 insertions(+), 20 deletions(-) rename {Content.Server => Content.Shared}/Singularity/Components/SingularityGeneratorComponent.cs (91%) create mode 100644 Content.Shared/Singularity/EntitySystems/SharedSingularityGeneratorSystem.cs diff --git a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs index cfca86bf4af..95722449b87 100644 --- a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs @@ -1,10 +1,7 @@ -using System.Diagnostics; using Content.Server.ParticleAccelerator.Components; -using Content.Server.Popups; -using Content.Server.Singularity.Components; -using Content.Shared.Emag.Systems; using Content.Shared.Popups; using Content.Shared.Singularity.Components; +using Content.Shared.Singularity.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; @@ -13,7 +10,7 @@ namespace Content.Server.Singularity.EntitySystems; -public sealed class SingularityGeneratorSystem : EntitySystem +public sealed class SingularityGeneratorSystem : SharedSingularityGeneratorSystem { #region Dependencies [Dependency] private readonly IViewVariablesManager _vvm = default!; @@ -21,7 +18,6 @@ public sealed class SingularityGeneratorSystem : EntitySystem [Dependency] private readonly PhysicsSystem _physics = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; #endregion Dependencies public override void Initialize() @@ -29,7 +25,6 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(HandleParticleCollide); - SubscribeLocalEvent(OnEmagged); var vvHandle = _vvm.GetTypeHandler(); vvHandle.AddPath(nameof(SingularityGeneratorComponent.Power), (_, comp) => comp.Power, SetPower); @@ -138,7 +133,7 @@ private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent co if (!contained && !generatorComp.FailsafeDisabled) { generatorComp.NextFailsafe = _timing.CurTime + generatorComp.FailsafeCooldown; - _popupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe", ("target", args.OtherEntity)), args.OtherEntity, PopupType.LargeCaution); + PopupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe", ("target", args.OtherEntity)), args.OtherEntity, PopupType.LargeCaution); } else { @@ -159,13 +154,6 @@ private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent co EntityManager.QueueDeleteEntity(uid); } - - private void OnEmagged(EntityUid uid, SingularityGeneratorComponent component, ref GotEmaggedEvent args) - { - _popupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe-disabled", ("target", uid)), uid); - component.FailsafeDisabled = true; - args.Handled = true; - } #endregion Event Handlers /// diff --git a/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs b/Content.Shared/Singularity/Components/SingularityGeneratorComponent.cs similarity index 91% rename from Content.Server/Singularity/Components/SingularityGeneratorComponent.cs rename to Content.Shared/Singularity/Components/SingularityGeneratorComponent.cs index c8feeb5d5db..3643ed31a6f 100644 --- a/Content.Server/Singularity/Components/SingularityGeneratorComponent.cs +++ b/Content.Shared/Singularity/Components/SingularityGeneratorComponent.cs @@ -1,14 +1,12 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Content.Server.Singularity.EntitySystems; using Content.Shared.Physics; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.Singularity.Components; +namespace Content.Shared.Singularity.Components; -[RegisterComponent, AutoGenerateComponentPause] -[Access(typeof(SingularityGeneratorSystem))] +[RegisterComponent, AutoGenerateComponentPause, AutoGenerateComponentState] public sealed partial class SingularityGeneratorComponent : Component { /// @@ -28,7 +26,7 @@ public sealed partial class SingularityGeneratorComponent : Component /// /// Allows the generator to ignore all the failsafe stuff, e.g. when emagged /// - [DataField] + [DataField, AutoNetworkedField] public bool FailsafeDisabled = false; /// diff --git a/Content.Shared/Singularity/EntitySystems/SharedSingularityGeneratorSystem.cs b/Content.Shared/Singularity/EntitySystems/SharedSingularityGeneratorSystem.cs new file mode 100644 index 00000000000..8830cb0624c --- /dev/null +++ b/Content.Shared/Singularity/EntitySystems/SharedSingularityGeneratorSystem.cs @@ -0,0 +1,29 @@ +using Content.Shared.Emag.Systems; +using Content.Shared.Popups; +using Content.Shared.Singularity.Components; + +namespace Content.Shared.Singularity.EntitySystems; + +/// +/// Shared part of SingularitySingularityGeneratorSystem +/// +public abstract class SharedSingularityGeneratorSystem : EntitySystem +{ + #region Dependencies + [Dependency] protected readonly SharedPopupSystem PopupSystem = default!; + #endregion Dependencies + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEmagged); + } + + private void OnEmagged(EntityUid uid, SingularityGeneratorComponent component, ref GotEmaggedEvent args) + { + PopupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe-disabled", ("target", uid)), uid); + component.FailsafeDisabled = true; + args.Handled = true; + } +} \ No newline at end of file From 1fa1975e60d35b788c57ab2479f6f962f99d53fc Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:53:52 -0800 Subject: [PATCH 066/218] Fix toggle verbs (#32138) First commit Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- .../Components/ItemToggleComponent.cs | 12 +++++++ .../Components/ToggleVerbComponent.cs | 18 ---------- .../Item/ItemToggle/ItemToggleSystem.cs | 2 +- .../Item/ItemToggle/ToggleVerbSystem.cs | 34 ------------------- .../components/magboots-component.ftl | 3 -- .../fire-extinguisher-component.ftl | 3 +- .../Entities/Clothing/Shoes/magboots.yml | 2 -- .../Objects/Misc/fire_extinguisher.yml | 4 +-- 8 files changed, 17 insertions(+), 61 deletions(-) delete mode 100644 Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs delete mode 100644 Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs delete mode 100644 Resources/Locale/en-US/clothing/components/magboots-component.ftl diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs index 47edec135d8..110ae80626e 100644 --- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs +++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs @@ -32,6 +32,18 @@ public sealed partial class ItemToggleComponent : Component [DataField] public bool OnUse = true; + /// + /// The localized text to display in the verb to activate. + /// + [DataField] + public string VerbToggleOn = "item-toggle-activate"; + + /// + /// The localized text to display in the verb to de-activate. + /// + [DataField] + public string VerbToggleOff = "item-toggle-deactivate"; + /// /// Whether the item's toggle can be predicted by the client. /// diff --git a/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs b/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs deleted file mode 100644 index b673c55e0f1..00000000000 --- a/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Item.ItemToggle; -using Robust.Shared.GameStates; - -namespace Content.Shared.Item.ItemToggle.Components; - -/// -/// Adds a verb for toggling something, requires . -/// -[RegisterComponent, NetworkedComponent, Access(typeof(ToggleVerbSystem))] -public sealed partial class ToggleVerbComponent : Component -{ - /// - /// Text the verb will have. - /// Gets passed "entity" as the entity's identity string. - /// - [DataField(required: true)] - public LocId Text = string.Empty; -} diff --git a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs index d5bbaac12c3..f6752a67f61 100644 --- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs @@ -78,7 +78,7 @@ private void OnActivateVerb(Entity ent, ref GetVerbsEvent { Toggle((ent.Owner, ent.Comp), user, predicted: ent.Comp.Predictable); diff --git a/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs b/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs deleted file mode 100644 index 858cd9bc111..00000000000 --- a/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Shared.IdentityManagement; -using Content.Shared.Item.ItemToggle.Components; -using Content.Shared.Verbs; - -namespace Content.Shared.Item.ItemToggle; - -/// -/// Adds a verb for toggling something with . -/// -public sealed class ToggleVerbSystem : EntitySystem -{ - [Dependency] private readonly ItemToggleSystem _toggle = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent>(OnGetVerbs); - } - - private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - var name = Identity.Entity(ent, EntityManager); - var user = args.User; - args.Verbs.Add(new ActivationVerb() - { - Text = Loc.GetString(ent.Comp.Text, ("entity", name)), - Act = () => _toggle.Toggle(ent.Owner, user) - }); - } -} diff --git a/Resources/Locale/en-US/clothing/components/magboots-component.ftl b/Resources/Locale/en-US/clothing/components/magboots-component.ftl deleted file mode 100644 index 4113a748620..00000000000 --- a/Resources/Locale/en-US/clothing/components/magboots-component.ftl +++ /dev/null @@ -1,3 +0,0 @@ - -# Toggle Magboots Verb -toggle-magboots-verb-get-data-text = Toggle Magboots \ No newline at end of file diff --git a/Resources/Locale/en-US/fire-extinguisher/fire-extinguisher-component.ftl b/Resources/Locale/en-US/fire-extinguisher/fire-extinguisher-component.ftl index f6418793918..de1fcd7d4e3 100644 --- a/Resources/Locale/en-US/fire-extinguisher/fire-extinguisher-component.ftl +++ b/Resources/Locale/en-US/fire-extinguisher/fire-extinguisher-component.ftl @@ -1,3 +1,4 @@ fire-extinguisher-component-after-interact-refilled-message = {$owner} is now refilled fire-extinguisher-component-safety-on-message = Its safety is on! -fire-extinguisher-component-verb-text = Toggle safety +fire-extinguisher-component-verb-remove = Remove safety +fire-extinguisher-component-verb-engage = Engage safety diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml index ab8084c91c0..6610ae87ccf 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml @@ -15,8 +15,6 @@ - type: ToggleClothing action: ActionToggleMagboots mustEquip: false - - type: ToggleVerb - text: toggle-magboots-verb-get-data-text - type: ComponentToggler components: - type: NoSlip diff --git a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml index b0c586fc753..4b4200fed8f 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml @@ -47,8 +47,8 @@ params: variation: 0.125 volume: -4 - - type: ToggleVerb - text: fire-extinguisher-component-verb-text + verbToggleOn: fire-extinguisher-component-verb-remove + verbToggleOff: fire-extinguisher-component-verb-engage - type: SpraySafety - type: MeleeWeapon wideAnimationRotation: 180 From e98383d572c8d6ba251c7914777becc681ebac91 Mon Sep 17 00:00:00 2001 From: qwerltaz <69696513+qwerltaz@users.noreply.github.com> Date: Wed, 20 Nov 2024 02:54:49 +0100 Subject: [PATCH 067/218] Construction menu grid view (#32577) * button * implement populate grid view * tweak min width * Make grid button toggle visible * tweak min window size * fix missing recipe button when mirroring item * make grid buttons toggleable * align button texture vertically * selected grid item has plain color background * tweak window width so all buttons look good * rename select method, defer colouring * get icon better * whoops * simpler button toggle * spritesys frame0, move spritesys * delete old sprite system refs --- .../Construction/UI/ConstructionMenu.xaml | 13 ++- .../Construction/UI/ConstructionMenu.xaml.cs | 17 ++- .../UI/ConstructionMenuPresenter.cs | 102 +++++++++++++++--- .../construction/ui/construction-menu.ftl | 1 + 4 files changed, 114 insertions(+), 19 deletions(-) diff --git a/Content.Client/Construction/UI/ConstructionMenu.xaml b/Content.Client/Construction/UI/ConstructionMenu.xaml index 6e4438cf6fd..a934967a533 100644 --- a/Content.Client/Construction/UI/ConstructionMenu.xaml +++ b/Content.Client/Construction/UI/ConstructionMenu.xaml @@ -1,15 +1,20 @@ - + + + + - -