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 01/86] 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 03a70cb7fe..78a27a9d0f 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 e3f972cfda..f03670b673 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 0d273c324e..647a5b2a7b 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 02/86] 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 37c85e07a3..5a7dfbb3bf 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 7e4087b20a..bd9b2415e2 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 03/86] 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 10773d6de8..65310b67f9 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 955ddfd2e3..2f8a7cf10e 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 d1d530ae81..00bf7fc2af 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 15878a4017..a6320dc7b3 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 0f012cefc9..3f8a731cbb 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 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 Date: Fri, 27 Sep 2024 20:11:04 +0200 Subject: [PATCH 04/86] 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 2f8a7cf10e..4c0359b37e 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 00bf7fc2af..818847f244 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 05/86] 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 77d2645c4c..71ab4d3716 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 0000000000..88e750e621 --- /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 b694a8cc2f..18d459cd89 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 a6320dc7b3..ff2b3f9ff2 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 dc44915f53..efebc8e272 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 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 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 373b9ce9a9..38a75827d5 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 06/86] 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 38a75827d5..6ec63992f1 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 07/86] 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 0000000000..1acfe2e389 --- /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 0000000000..762397b5d4 --- /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 805549439b..6d1834c576 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 @@ public sealed class IonStormRule : StationEventSystem 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 0000000000..ae9b49a49c --- /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 71ab4d3716..3a8e720038 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 88e750e621..96a33ae6b1 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 4c0359b37e..dcbef820ce 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 efebc8e272..72ab8b104d 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 08/86] 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 dcbef820ce..73f7b518fb 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 09/86] 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 3a8e720038..3f9422f250 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 73f7b518fb..f345229ad4 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 10/86] 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 ff2b3f9ff2..df024c55a1 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 11/86] 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 df024c55a1..70e533fa44 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 12/86] 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 6ec63992f1..f5c2001828 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 13/86] 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 762397b5d4..4b1b4da033 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -29,7 +29,7 @@ public sealed class StartIonStormedSystem : EntitySystem 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 14/86] 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 f345229ad4..2fc04a6aa9 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 15/86] Cleaned up a bit of the Derelict Cyborg code. --- .../Silicons/Laws/IonStormSystem.cs | 178 +++++++++--------- .../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, 93 insertions(+), 111 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 1acfe2e389..87df0d8cf2 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)) + // 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) { - 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(); + if (law.Order < baseOrder) + baseOrder = law.Order; } - // 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; + _robustRandom.Shuffle(laws.Laws); + // change order based on shuffled position 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].Order = baseOrder + i; } + } - //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()}"); + // 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); + } - // laws unique to this silicon, dont use station laws anymore - EnsureComp(ent); - var ev = new IonStormLawsEvent(laws); - RaiseLocalEvent(ent, ref ev); + // 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(); + } + } + + // 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); } // 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 4b1b4da033..f7b65e1a04 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -6,7 +6,7 @@ using Content.Shared.Silicons.Laws; 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 @@ public sealed class StartIonStormedSystem : EntitySystem 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 ae9b49a49c..4157bee9f2 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 0000000000..87e500d3e6 --- /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 96a33ae6b1..0000000000 --- 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 2fc04a6aa9..f94436dc33 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 72ab8b104d..36ded039ec 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 16/86] 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 87df0d8cf2..cf31a9e19f 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -9,7 +9,6 @@ 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; @@ -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 f7b65e1a04..887bc051dd 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -31,7 +31,7 @@ public sealed class StartIonStormedSystem : EntitySystem 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 6d1834c576..05d079fb3d 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -4,6 +4,7 @@ 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; namespace Content.Server.StationEvents.Events; @@ -21,6 +22,10 @@ public sealed class IonStormRule : StationEventSystem 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 17/86] 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 cf31a9e19f..65c12c3d8e 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -85,7 +85,7 @@ public sealed class IonStormSystem : EntitySystem 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 sealed class IonStormSystem : EntitySystem 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 887bc051dd..c80c2a7751 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 18/86] 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 c80c2a7751..546b3b2777 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -19,21 +19,22 @@ public sealed class StartIonStormedSystem : EntitySystem 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 19/86] 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 65c12c3d8e..bcf002dae7 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; 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 05d079fb3d..26f6d3263f 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 20/86] 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 bcf002dae7..b8d8f432e7 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 21/86] 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 f94436dc33..7449fe5669 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 36ded039ec..be44b50d61 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 22/86] 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 b8d8f432e7..637155228c 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 546b3b2777..d679b558b3 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -31,7 +31,7 @@ public sealed class StartIonStormedSystem : EntitySystem 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 26f6d3263f..9b67f25608 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -24,7 +24,7 @@ public sealed class IonStormRule : StationEventSystem 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 23/86] 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 637155228c..b3c36a2273 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -78,11 +78,8 @@ public sealed class IonStormSystem : EntitySystem var lawset = lawsets.Pick(_robustRandom); laws = _siliconLaw.GetLawset(lawset); } - else - { - // clone it so not modifying stations lawset - laws = laws.Clone(); - } + // clone it so not modifying stations lawset + laws = laws.Clone(); // shuffle them all if (_robustRandom.Prob(target.ShuffleChance)) From 9dc90a258e8e57fd5e7e76a07e9535f48b34b23f Mon Sep 17 00:00:00 2001 From: The Canned One Date: Thu, 3 Oct 2024 12:43:51 +0200 Subject: [PATCH 24/86] 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 be44b50d61..9b5426c269 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 25/86] 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 70e533fa44..97cd7c2a9d 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 26/86] 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 3f9422f250..f584a4b35f 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 27/86] 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 87e500d3e6..0000000000 --- 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 9b5426c269..763557e6c9 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 28/86] 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 b3c36a2273..f8bc8ca8c3 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 d679b558b3..ee2ce7b9ed 100644 --- a/Content.Server/Silicons/Laws/StartIonStormedSystem.cs +++ b/Content.Server/Silicons/Laws/StartIonStormedSystem.cs @@ -24,14 +24,12 @@ public sealed class StartIonStormedSystem : EntitySystem { 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 9b67f25608..e7c2d563ba 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -24,7 +24,7 @@ public sealed class IonStormRule : StationEventSystem 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 97cd7c2a9d..22f49c93ea 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 29/86] 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 4157bee9f2..e73d17b87b 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 30/86] 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 6b7df52a6e..27ed6702d6 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -51,6 +51,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem SubscribeLocalEvent(OnEmagLawsAdded); SubscribeLocalEvent(OnEmagMindAdded); SubscribeLocalEvent(OnEmagMindRemoved); + SubscribeLocalEvent(OnStartIonStormedMindAdded); + SubscribeLocalEvent(OnStartIonStormedMindRemoved); } private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args) @@ -184,6 +186,31 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem _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 e73d17b87b..d57241c857 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 31/86] 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 d57241c857..75d7412166 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 32/86] 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 53436a920a..72c1af29b2 100644 --- a/Content.Shared/Damage/Systems/DamageExamineSystem.cs +++ b/Content.Shared/Damage/Systems/DamageExamineSystem.cs @@ -61,6 +61,15 @@ public sealed class DamageExamineSystem : EntitySystem } 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 974b8fa965..848adeb31a 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 33/86] 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 c96a1522d2..55adfb7ba6 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 34/86] 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 72c1af29b2..fe97a40d1b 100644 --- a/Content.Shared/Damage/Systems/DamageExamineSystem.cs +++ b/Content.Shared/Damage/Systems/DamageExamineSystem.cs @@ -61,15 +61,12 @@ public sealed class DamageExamineSystem : EntitySystem } 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 35/86] 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 27ce392cd5..3a67ffb9cd 100644 --- a/Content.Server.Database/SnakeCaseNaming.cs +++ b/Content.Server.Database/SnakeCaseNaming.cs @@ -82,7 +82,7 @@ namespace Content.Server.Database } } - public class SnakeCaseConvention : + public partial class SnakeCaseConvention : IEntityTypeAddedConvention, IEntityTypeAnnotationChangedConvention, IPropertyAddedConvention, @@ -99,22 +99,27 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database } } } + + [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 36/86] 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 588d62e560..3c7322d473 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 @@ public sealed partial class BanPanel : DefaultWindow 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 @@ public sealed partial class BanPanel : DefaultWindow 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 4db9eabf5c..9e57cd4b0e 100644 --- a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs +++ b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs @@ -32,9 +32,9 @@ namespace Content.IntegrationTests.Tests.Commands // 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 @@ namespace Content.IntegrationTests.Tests.Commands // 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 @@ namespace Content.IntegrationTests.Tests.Commands // 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 @@ namespace Content.IntegrationTests.Tests.Commands 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 @@ namespace Content.IntegrationTests.Tests.Commands 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 @@ namespace Content.IntegrationTests.Tests.Commands 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 @@ namespace Content.IntegrationTests.Tests.Commands 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 0000000000..155d6a163f --- /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 0000000000..c70a5ffaa5 --- /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 0000000000..dc1b4a0eeb --- /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 0000000000..debb36aacc --- /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 1f64f6e51a..7544438631 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -512,20 +512,6 @@ namespace Content.Server.Database.Migrations.Postgres 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 @@ namespace Content.Server.Database.Migrations.Postgres 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 @@ namespace Content.Server.Database.Migrations.Postgres .HasColumnType("smallint") .HasColumnName("denied"); - b.Property("HWId") - .HasColumnType("bytea") - .HasColumnName("hwid"); - b.Property("ServerId") .ValueGeneratedOnAdd() .HasColumnType("integer") @@ -603,6 +598,10 @@ namespace Content.Server.Database.Migrations.Postgres .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 @@ namespace Content.Server.Database.Migrations.Postgres .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 @@ namespace Content.Server.Database.Migrations.Postgres .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 @@ namespace Content.Server.Database.Migrations.Postgres .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 @@ namespace Content.Server.Database.Migrations.Postgres .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 @@ namespace Content.Server.Database.Migrations.Postgres 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 @@ namespace Content.Server.Database.Migrations.Postgres .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 @@ namespace Content.Server.Database.Migrations.Postgres .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 0000000000..56a9fe0a05 --- /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 0000000000..97b5dafd03 --- /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 0000000000..bd4e20a464 --- /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 0000000000..3a7fd784e1 --- /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 02d4416302..c63127874c 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -483,19 +483,6 @@ namespace Content.Server.Database.Migrations.Sqlite 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 @@ namespace Content.Server.Database.Migrations.Sqlite 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 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("INTEGER") .HasColumnName("denied"); - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - b.Property("ServerId") .ValueGeneratedOnAdd() .HasColumnType("INTEGER") @@ -569,6 +565,10 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("TEXT") .HasColumnName("time"); + b.Property("Trust") + .HasColumnType("REAL") + .HasColumnName("trust"); + b.Property("UserId") .HasColumnType("TEXT") .HasColumnName("user_id"); @@ -675,10 +675,6 @@ namespace Content.Server.Database.Migrations.Sqlite .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 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("TEXT") .HasColumnName("expiration_time"); - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - b.Property("Hidden") .HasColumnType("INTEGER") .HasColumnName("hidden"); @@ -1124,10 +1116,6 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("TEXT") .HasColumnName("expiration_time"); - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - b.Property("Hidden") .HasColumnType("INTEGER") .HasColumnName("hidden"); @@ -1559,6 +1547,34 @@ namespace Content.Server.Database.Migrations.Sqlite .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 @@ namespace Content.Server.Database.Migrations.Sqlite 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 @@ namespace Content.Server.Database.Migrations.Sqlite .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 @@ namespace Content.Server.Database.Migrations.Sqlite .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 00b3cfea03..9190475b15 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 @@ namespace Content.Server.Database .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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database /// /// 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database /// 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 8ddc7459d7..2ca126bf16 100644 --- a/Content.Server/Administration/BanList/BanListEui.cs +++ b/Content.Server/Administration/BanList/BanListEui.cs @@ -54,7 +54,7 @@ public sealed class BanListEui : BaseEui 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 @@ public sealed class BanListEui : BaseEui ? (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 @@ public sealed class BanListEui : BaseEui 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 @@ public sealed class BanListEui : BaseEui ? (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 e746e9c725..3eedad3ed5 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.Server.EUI; 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 sealed class BanPanelEui : BaseEui 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 sealed class BanPanelEui : BaseEui } } - 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 sealed class BanPanelEui : BaseEui 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 a5bc97dce3..2f7093ae1d 100644 --- a/Content.Server/Administration/Commands/BanListCommand.cs +++ b/Content.Server/Administration/Commands/BanListCommand.cs @@ -38,7 +38,7 @@ public sealed class BanListCommand : LocalizedCommands 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 30bb3073ad..8244ded3b2 100644 --- a/Content.Server/Administration/Commands/RoleBanListCommand.cs +++ b/Content.Server/Administration/Commands/RoleBanListCommand.cs @@ -48,7 +48,7 @@ public sealed class RoleBanListCommand : IConsoleCommand 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 1cdfb82224..2e21710e51 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -65,7 +65,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit 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 sealed partial class BanManager : IBanManager, IPostInjectInit } #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 sealed partial class BanManager : IBanManager, IPostInjectInit 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 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit 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 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit #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 c11e310a82..fc192cc306 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 64a85f19ad..25cc771468 100644 --- a/Content.Server/Administration/PlayerLocator.cs +++ b/Content.Server/Administration/PlayerLocator.cs @@ -5,16 +5,42 @@ using System.Net.Http.Headers; 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 @@ namespace Content.Server.Administration { // 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 @@ namespace Content.Server.Administration } 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 4c0df80601..6c30488886 100644 --- a/Content.Server/Administration/PlayerPanelEui.cs +++ b/Content.Server/Administration/PlayerPanelEui.cs @@ -173,11 +173,11 @@ public sealed class PlayerPanelEui : BaseEui { _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 7a47755db9..4358b7e387 100644 --- a/Content.Server/Administration/Systems/BwoinkSystem.cs +++ b/Content.Server/Administration/Systems/BwoinkSystem.cs @@ -172,7 +172,7 @@ namespace Content.Server.Administration.Systems } // 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 2c1f9fb36f..e4c7cf0be2 100644 --- a/Content.Server/Connection/ConnectionManager.cs +++ b/Content.Server/Connection/ConnectionManager.cs @@ -111,11 +111,14 @@ namespace Content.Server.Connection 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 @@ namespace Content.Server.Connection } 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 @@ namespace Content.Server.Connection 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 0000000000..a409f79a75 --- /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 e58e5b0b5f..f477ccd822 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 class BanMatcher 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 static class BanMatcher 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 c0d81147bb..30fba3434b 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 09a960e9a6..a09f9e959c 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 c85b774e38..723092bdc4 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -388,12 +388,14 @@ namespace Content.Server.Database /// /// 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 @@ namespace Content.Server.Database /// /// 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 @@ namespace Content.Server.Database /// 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 @@ namespace Content.Server.Database NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId) + ImmutableTypedHwid? hwId) { await using var db = await GetDb(); @@ -603,7 +609,7 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database player.LastSeenUserName, new DateTimeOffset(NormalizeDatabaseTime(player.LastSeenTime)), player.LastSeenAddress, - player.LastSeenHWId?.ToImmutableArray()); + player.LastSeenHWId); } #endregion @@ -658,11 +664,11 @@ namespace Content.Server.Database /* * 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 216b1ec159..be32b43595 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -69,12 +69,14 @@ namespace Content.Server.Database /// /// 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 @@ namespace Content.Server.Database /// /// 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 @@ namespace Content.Server.Database /// 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId, + ImmutableTypedHwid? hwId, + float trust, ConnectionDenyReason? denied, int serverId); @@ -480,20 +487,22 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId) + ImmutableTypedHwid? hwId) { DbWriteOpsMetric.Inc(); return RunDbCommand(() => _db.UpdatePlayerRecord(userId, userName, address, hwId)); @@ -604,12 +614,13 @@ namespace Content.Server.Database 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 7d131f70dc..c034670837 100644 --- a/Content.Server/Database/ServerDbPostgres.cs +++ b/Content.Server/Database/ServerDbPostgres.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, DbGuardImpl db, bool includeUnbanned, ServerBanExemptFlags? exemptFlags, @@ -134,16 +139,11 @@ namespace Content.Server.Database { 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId, + ImmutableTypedHwid? hwId, + float trust, ConnectionDenyReason? denied, int serverId) { @@ -488,9 +511,10 @@ namespace Content.Server.Database 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 af4bc2cf8d..6ec90c3332 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -9,6 +9,7 @@ using Content.Server.Administration.Logs; 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned) { var exempt = await GetBanExemptionCore(db, userId); @@ -119,6 +123,7 @@ namespace Content.Server.Database UserId = userId, ExemptFlags = exempt ?? default, HWId = hwId, + ModernHWIds = modernHWIds, IsNewPlayer = newPlayer, }; @@ -161,7 +166,7 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database IPAddress? address, NetUserId? userId, ImmutableArray? hwId, + ImmutableArray>? modernHWIds, bool includeUnbanned) { await using var db = await GetDbImpl(); @@ -214,7 +220,7 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database 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 @@ namespace Content.Server.Database NetUserId userId, string userName, IPAddress address, - ImmutableArray hwId, + ImmutableTypedHwid? hwId, + float trust, ConnectionDenyReason? denied, int serverId) { @@ -424,9 +452,10 @@ namespace Content.Server.Database 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 f615d5da4d..dda3a82237 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 sealed class 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 0000000000..6e4a7763b3 --- /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 dd10068e5d..74c340566b 100644 --- a/Content.Shared/Administration/BanPanelEuiState.cs +++ b/Content.Shared/Administration/BanPanelEuiState.cs @@ -25,7 +25,7 @@ public static class BanPanelEuiStateMsg { 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 static class BanPanelEuiStateMsg 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 37/86] 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 f584a4b35f..77d2645c4c 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 65310b67f9..46959705c2 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 18d459cd89..a614fb5963 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 7449fe5669..9022384352 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 818847f244..04d629279e 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 22f49c93ea..71b8a92d1c 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 763557e6c9..364a412887 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 3f8a731cbb..8fd528575e 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 zcmeHtWmKEn)^32}-r^3$-6h4{f>X565Q2LWT#M5dhZcw8MO&;;v}mEYJ4H*eBE{vV z-TUmb&$!xG4Z ze1B{2z61b}zxOpThU?Jz{6N^69=SU$lSGj2|8qZBAW52fJWm5dC*}4Qv>3gmxb#u;fyd>s0a#>NwGGxoEs=chlP zA1!ojxZ+BQ9nB@I4SJJt4$CiP@$J^gO2r|%6UVO}*7{iV?x-DI*q-FKNaGK8FW&~S zm5ZUC9S(t9Z@RS)7H5N}+vd;vGd^i=Js|fWJ{Z>CI<9$uca)JOeM{n5ySWyVLmqJ0 z-}~t5YHRajgUgxuY8$0QVDs%|x?3}o-pxix7U!5psOf6U>1n7PDNME_@RS! zaW9}tY<7FLk_8u6CYHEzx? zKC^#Um}+KUhn5p~HUiO>Ivd+@>m)pfbR{U$K_~3lbkfi8gocEVxodcH>zhr^aJVXK zk2uQ$z4zRA;@a*$Ii4P-&u5Q?Z+ANSr;j;ea{;lBC<$Pj_1==7(E)RxZH?;B_SPjX zb~$59uwp?N7f!}Lc!W#iP1U;tUOlvN{jfW>wlsLqVaAoVlM!YQnzD7~ zxY)9%G5usNi>qVtmDi+*=E9DN+Kydbi`MJLjX4O#DZ%OfSs=FhN|1lI7w*h!k?SSh zX@c)Kb&Ck!T&zA5nb|aFv%tYsAv4;go$s^C9;wzsCv6!y{Oj+t78F|f@_Vu28gpmI z%#kv)Z_$H}?O1s8Nu$SR<<;^o)Zc%9X?BLI!N_{FJDCu6-+006Ov)h$Va7VKO3q+D zo*W`oAw6LwfM_+lEF2xBb&Oq$G1UNs+E0*JAY`+&7e#Clzig`;534 z%bB1bvK$ZJJN5Zq)^mnPTIGOn8yj=ZJS2Vl&<@Um9H0fGw~aav<$Zi!Gx*uXj~argqJ>0B$a`R@Ao^_TqN07$b?53sd5>UjqojFwvcnHj zs!h9fqE&|QH56s$US}w`lXz-U@G7&v)HqM~e|)vuCEHOPP0w*j))F4L8pd@V+fqD1 zWnLuWinhk$wrh*_zMA+wd->P&q!0z-ncZS^C>k^CE~#g7yfS@R8of=qxAnaB`otC0 z!%7qrP%mtT>HAv|Vz6KD!z|h>yQs1#fEBjC*OgqKNXF&q!@$E_%)Zl;h^*Pi-`2)R z-5gaG7NLx_x%%c$%{p;+eHLHWG<-atH8V>?q^CgMP@m(%s**a1d*WWhb7aD zcGs9SE94Ho_z=h4TJ=ygDu=WE9J=%v|ExT8g!v?fO_=;?`y1WItUuyEB0jE98^M-@ zGryp%*^@zDijllE>x3%~{W7C6kYxLMAy}NIXDzK8Jxo-TZ)RJOFJf00 zMQBe+GKRJ*KW&SSCEgbH)}NNv#NjK`aYj%t>xh5nyBc6%o%y^A{?dUoffFiDt1`dI=Yc3tP``F$@u-Ptcr8DZA_7>|5eikwHNQG*NfaNoSrbX$7~aY&|I z&aAeRc9X!tMj30k!P5?R-qR^~+1`2`u(vRpdlTa$XGarhFdo}mx8{BdXF7{E%dhXa zbt2CJxW=w~~K+uG8lp@tq3H>l`NUg=aC zeTeqB_&(M@8ysp->e&Z>rHOCY9=;u-NCI=^vdA!RlH3?!_Ks0yhdEi%O)#}uDlvf> z9kd09<}(H2jd0DnWnH$OCwo5)vnTXoY;>ZTaeJ8XQ@JG}GSIT>6!T@Pu>qdCO-H*0 zxB1k&_J|ybjl;u%Vt9X@?A6jD*P_8Ba$^t=ZveFE`X@O?Pd`z)p=N z%|F}~PNbs_#99*zx~61zwM{thVu=cOs{kfq@1K$|4Zgxe6WNf@%tPa*r(5hBh!Rm~ zASN7giOFnOqs*N$>Fcjz#y*HF#6QyERZCE8#QA_t(my%U3KP=P4}Qp?Xa`wnUrV9& zUSG7|sHL5j=`!XfOy9G(QkQsd@}PcrXg<* z4jD|0b+5`Fe(I_?-n>5Q0)Z$#U?a$BQll(5OCLnFqPL{Ku-rAhw0VH{6NV^{!3C5_ahXcHVfNwJFr3 zi?OwxiDrm^IXRo^%9J#z7xlBWXap9*G$4up( zW#D);yWqntg|B-g7$BzH8NqPO`bFJC-euV3>Rbe;9jbm!`Ab$?ujnar9QHeaVO@TX z789wSwElMP5}KQrC0QbOXa>XmcDi?#YhZn^{&H+}%#AC9NH!nN2krhRX=6>hZ+&h0 zDjyZRdq9O( zMsvN7SWjt>Nd>X@4;=VUd4bL&SU#I`6n4cPJQJrWiu03I%KgSfS&=4I+^3YoqCm#@ z&V4xv6++%jcrU#;H`z_ZnG5%HXi$6oteAO`I_fLy1<5K|_?Kho8x8DlH-}9grMg1( z9NH_-L6dQOE*J9KTsbG9xMv>~x;YuRHVt*)7wiesSdujD`}eJ;?8gVR%C_c2zmbvPdFN%{$1TQg1w zbg!eV21xPX6bgJ$-EOL`dmT-gB8|22WIC8V(RQI5S1dQa0H|ll`gwCga-R9;#0#G_ zwYnDC0EV%W07F@H*Td}hZ*e?d<1Behwx_ENsYJ}~`3HF)u$No3JD7gmxv z%}Z5%+<`g*G8Vb#wQNUKWop5{cY2cC&H??#+A@69lTTGNk-3=c?aL)aHSDoVCDDd! zboivEKuz4q3lv*2YxWdv`U6ecCeF2BTzov|u3NWlrq;{zgIU=!7Qv3|WP>nR^;AuIi^sq5WR~$0Lqe34<(#FIWl{9=_%9 zy{Po5g&e$1Uk|ttC zvXyP0e*KUP3i=7fyI87zx|Ko;#h(n4!**^}_1*4prjFt5UalSEi%CIqjJ4Z)PY z5O-MAPjV=fO(y3k`i&!P$CdM7%aMYl*K{;)@q<=Bqu*#GjAVxjnp?*%=Q~x^ey+4< zEx&5_VR15~M!QoWS{=&;E7Vjp^wvB&UdB50Xf}i2GUI_+eUJR;MI0Nm_WV?41lmlH zt3+;4ZbL*;@|zh2UPdAmp8vb2VS(6Kcnm4C7uN zZ6w#Yp>gL) zeD8>?p;EK^lInRE8ILpPs=s{lSRwYR5N7m-^W>S#3s7P6WsEh;7=c-MTMuuK*p!&e zg^>9Sbn}fMb{N;LnW7fdjx$|rSIV$hY9p@x57tbs}@_(e&)JUeIDODS6H zWX%-cQa10t!>xCx_jESgy(Sy-_DS069kuS2?RPW9`caOtrj4Mo)+RKq%tYP zmnSA{=9m)7l4rE;YRhdk{U4?S(>x=DE)09!8=wQ9@AoMf->-|fW&bY9{>tuKe|f>7r38m`ni{9b(igi zmC$@wnO_{u8nj{|G_rd}*!oQb{Tijmdc3#fs6xfNGhT!f;lj1P zdXq(~i*;629U9;C=HSbffkiq((z$3VTU0-SNLEySqej<^hnOuqY_>5%JG~Z>B)t6H z5ApQ|_FdybE&I5FVt_y=Z9SYd&nJk-kG@u3!@od6cg`5Jo_S=Qj2X&`Eq{7hA$x1O z98a6$`P#3pv&Z5P-ucD$k<+KVxAc`l#7mq08KKq2=Aw%ZN;($~TxOiCwW``|mx~iU zkwFC#$lE{Q$dCxbT&w}#l(B(>M76yYAB=wbHS1$ow*St8(fP+D%%33xg^Aj9X`~`Y zg{|x0AM)djb+$P9z_o7l_Yo__Hg6(w9WLbq69OJ5iQJ3-66-mzT2I{`l_gYaVe4cU zbYRrnl77Pq&&oDShmqZ15uyl6a+ok)?m6<~j5Fu`8AH{ZUf~13d@9vBUuwgt4fIHQ zJ!UXNUkJ!6;wemi0rxGMjk}mWOebqI<1)NKqc=R+(LZxI&RhZxtQK>5A+p^eWMU<& z>ZG5oJp5wOyU=P>2JBleL_KWCF}Ge-IA_7;1#36~sjUKTFd>6|X=F6a^mIHjxStqzI`z56L&ZaYu-HWbC`NUq zym_pZG^Pz9Cd$|Ua2`YYL-tWFn+oqU*UV1J=T^@O*+XApjCu*D<%RPFjqNKJuAPy^ z8WS|VGpn|RT9R%vIik;zX=Z;^ROGI1* z*8P14qjo$J?R^4`7sf&f)0;|KA~7awch#P()MThfN;d6f|7lHOm@;%@!8s#W-H{_B0S zD(XC_msqr6Q+7orjD_|(=mjR=yQAz60XqKagTWs;;~_G5n3{~KeT@}#GCKyTj}~`p z#u;_h4_9uMtvb`tUM95>NOqzB)I*SJ@7lWrva+@py&bI%+LG)XK&^UXGebP~M0*R* z*(`;%562D*zZcHT95Y;`Y;$!!qgHz+mnGFpkz$t+LW;R*Ils(4;fWXK>dxyJ_SIF< zWR7*1CBycGJL~S1xt0X&mFa?9A0HrEH2kvGYBLR#-XIX$HYB-yCB8k+y>m)}N~k5x z1GRWMo0?ww7O=DxpN&Sf4cF+oM$`Nv(~Zr)&Ae^<>AdEV-2~lr#p8>C==E{!>%jU6 zouU2KgNk>#>Vo|;cVRnaZHob4ib>Pp-qQy|pCZ}Y#R}*lYmNGP$20SMgKJG0U#?C% zRP<9fCR1KYue)tOstTSz2e5fr>THa|xMLy5`0$HFMsPNkjHd5c*_{ zb*1?*;B@)+g0wh)n<}SE)C+uDlD6olIYsl#{QY!~!roat@HK18{CTD0-DdN`78`+6 z@H3m^QmM!Fs9ph(A4gDr%DoyO4Vfo<)*k4PJsS39T0E5P9oa|;SAnVCIKeT#01Cf6 z=fUmyW^C&)(YFYbpInBs(ol!n+t=IDwY3DT&%M#huwKX%tFq=-z>JISMN8Yo~3`met z?bIO&*VZpI2Z|C$hz4QzGX$Y%LgJ>cNdWb6=8*JljVL^Gz$Y=@rj)NKJ_8?Q(Ka!fSqm8;bQQ z5*(f8%hVDdVjHliPe1u2+1BUm#zabe$vRk@IUCvpCa!v*?P?0#3RvamQo}ivID9Qf z&K$Vjw0lIo^`;Yb7>_z_g2w%|F?apDg`RTjk}mx&Vx>uT%f6;^x24tc)Ut9P?b}Z} z7G_$otSO>ZV?|W8sCeCOjQ(n(!Y2>A6=Q}VbhjwIIn1v#EAja~zT~5lCaz)Lg)Y~K z;3hqA(PPJex6YjQViC=tQ;E#qO4sl8V6OyWRMw`p>0#PPt5wz2_7RVK#5Xr@5cVPb zuz+%z1OFUP^I2Pioh0~7=(31r#0wW*)dX|QUOPeQ8!1n92@X|wTB|ziNOwR`KfJe` zy?Gr7Fmmz8e9?@*h&*TUv`3z&7=tu{5Lahju(hidl-JwY4SC)I07%MsyMZB&P&k7X z)Yjfbis|5E8xw=QwG@+)2#6o#rU13GSM~LP>icRLKztn`64p#I(m0adKqP=O6b@$a zc6M@s0llS|e&Yg>?O)w|Obow8;EqyE#vmOA1y>I!gD|f!FF%ixx4owzlQa&4q=&T) zP)||$PYC3d6q6ks?gr%J^YZfI^%CNB^|0j=kdTnz;}_%;6y!ll@W6ar;9zea7udsJ z5Px7OLSYaOdpEefs|&*~Ot6(J0xreGgdAu1mwnD|Akbg%F0emYK=Q%o4R+%b;N|CY zcINxL1`Mv`i3Iu6p#M<=W`I1F;?sk|ToE1+sFEkt1^)2w5Y~{t^xY61PQTr;hVVh1 zpw3897;;vD|Cmxm4W#pz#xDwN?Va6zYaz-04@tPa&A-X|58Hl?{C4N>h9K4d!u=2F zfBF7f7%2q;0To>#h+pQZDM~T@njdKG3bD5a{%%5qL?94BQ3)Qfu%I=Mu(h=qj|4}zWf^CF(g!%bJcqFWB zpgc%jL2C&SK~Z5a|KA|AJ?xQD33mFsRllIDkx*c;sDy}>FqB7J0%-$sb|i|h2tSV) zR6rOi1hx^g783srWeou;yLvc-k;`fC47P>xxw+W>9{5E#P)C@d-@C@3N!C@k`Cp~p}U7%~=rp$hQx z3JLxm`L!@Wq&rAz!M`FE3Gllg=?zf90}6(_dKkF6I!Q78GJ)Zj=3m1gWI|bk;b28D z9Et?x7Ze8aO8^DM3BAuT$D*s+!;tg){O>16*qnHU&;rv(rU`NIVm*b{2~+fF3bA43p3u!}7eSwH>^*njog{|~_c zl@OK?frtw5h}-ZB@dyh*C3wUI!9qL|68u8K!Zt{5ZT|4;FLaoz4crUt0hO~w@`&UL z8KA$pVqpJssW|@A8ZSHOuXR9D#=|ec^A}~vHR1cyS-xKt<6quN^8H_YNd6Z1TZuvH z{n3XkUC2tv_g5+WlP{#L|C^6L*WrJ23I>LMCi$=U{fDl9==!f1_^*WjiLQU>`mY%H zuY~`JuK#az;rx3u1$9CG0`fv`m8fnC&ym|K3@Z&4#orq)z=gucAY=>MP1OVj01$Nj zdZDCn5l|yLG2v<;CCqQw)L1wSFcS(>q~e8|qMU*E++N1ABefA(uT1QMi&zY&dLPas z?TVm2Qk)`X<=l92cUto#Sq`*7nrp)IcPFmwS0A{qX5JL*5ix40^Ao$2-4AV0j(=Pd z%XC$t$L7Ef*#^ea8U{oiET+A4a$Ha2p8qasl0WKyAO#CtfDKKLBxRozut{x4J5t8Q z*7oE-thhTvoOq#O@!xe^?e6ZQ)&l6HeHX;&Xziu^Pd*GE;A?OPEBe0>3*Bz1aD0Vwju^PdAPr=0a{;kE0JL&|C4pn(D=fc|2%>wiIw?eHMEep+t^-2n+&w_Y~up%gEqe_Mgf8Gb=L-GNN7)%s~ozsK# zA7~#9XQNd=HfECsc2QTg_0-#))1#^HuzdQ;Uu0k3GBpHjPWD^AYd4U@X+Qd&?$NQ> z5)gQK5q*b)71{Z%i9i;Zh4IwJ1|!%>bZ2K1t%C@pi@7rY=^<)I{M^Y$@;hby3G90c zmD$@s$AI5;4Y zB&J{cJe9iV%T0!sYs~%DQMa3?mB8IeI-ur_WS^duO95l`fYl%&_CdS3y?u7{$ypx&r+a@&$}!b zO&>^NlEv0x&bxK}k}Yv~>niu8yhC=+DLcXi<7YHl|t)20Rf?%ofL)&hGAdbUiZm{sW-l;)2L}~^89Y_ zP3-m^8NZmA?RYb{)XTRaM4!m=Hgv_2fwaHW`gU+bs3~E574uo%gF|h+XV2+_KFQqP zmR?F$@s&tAlBN~6X$HAmRR}$4ByBr<6oWFo<#Hb@;&roNp)fD*n?AWnRf~*fK_z5B zUoL=#pXKD7vzf}~=`g$+C^dLJyfwdMJ9(mOUqgLJeP@hL5g{i`B_-G}u-WAL9uT&;?&;}?V8fxHqH@UEk%3_;q_zQEn{Q2xq_C#10bBE}!f6Jf#GfD7HJ=Z{ z?p_TiQ-nN?GUo1yTdtxr&EMh?aD-X8dB4_3QsV`&HcETx*A={Yn}_w_`Sa)5NB5BV zh-|cL@IIsICVj3{IlO4FLrFvDxftdSMVYc6xo8*~w-dID0cv=;2wi zOKoVX6!L?lsuzA5%bMro$v=F5%#2yvR9RD7yKYG~A`F^bO7TeBwtiLA* zK26?dE!dB~01s*ML@C2=%IfiE9RJ+KNbV2i&9zV03qw#HbIxhs|CODBLF2-s!WCTP zZlIl7n-DHB8;)er345Jsgk=hY59-l9Q+8*!jUMjcL3ICU6p-!~(w(GPLK#$x*nDK| zKvG?1RORvY6MFYb;rvoM2dqGvtAv|6EGx%U~zGA zA&ca?hXl1xUWV9c^v36}kqgZTd;pT)h7DprJvadIj@C$E%ochiJan!TLSHbTYZU$Uos8qXlAm<-pwlizXDmzBbjAv=l4l 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 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: Thu, 14 Nov 2024 15:01:04 +0100 Subject: [PATCH 38/86] 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 69a700fdeb..a180b698fa 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 a614fb5963..b694a8cc2f 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 26e0ce4c79..0db92ac941 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 c381723499..6a8f1e5abb 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 cef834c0b6..e787ef59f0 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 32af0835c7..fafb6bd428 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 8fd528575e..0f012cefc9 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 39/86] 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 fafb6bd428..08218acced 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 40/86] progress --- .../Silicons/Laws/SiliconLawSystem.cs | 76 ++++++++----------- .../Silicons/Laws/StartIonStormedSystem.cs | 38 ---------- .../Components/EmagSiliconLawComponent.cs | 7 -- .../Components/SiliconLawProviderComponent.cs | 6 ++ .../Components/StartIonStormedComponent.cs | 24 ------ 5 files changed, 39 insertions(+), 112 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 c2aa20f401..a4a7ee528f 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.Player; 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 sealed class SiliconLawSystem : SharedSiliconLawSystem 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem // 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem 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) + private void EnsureSubvertedSiliconRole(EntityUid uid) { - if (HasComp(uid)) - EnsureEmaggedRole(uid, component); - } - - private void OnEmagMindRemoved(EntityUid uid, EmagSiliconLawComponent component, MindRemovedMessage args) - { - 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) + private void RemoveSubvertedSiliconRole(EntityUid uid) { - 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 _)) + 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 ee2ce7b9ed..0000000000 --- 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 1b5338a7f7..5fe867ae29 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 4885bd0265..d78e539aa9 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 75d7412166..0000000000 --- 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 41/86] 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 a4a7ee528f..9b3e279b75 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -75,14 +75,14 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem { 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem } - 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 42/86] 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 f8bc8ca8c3..7587dc4155 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 43/86] 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 7587dc4155..97ad2d8f9e 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 44/86] 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 97ad2d8f9e..7587dc4155 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 45/86] 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 7587dc4155..6017a36fc0 100644 --- a/Content.Server/Silicons/Laws/IonStormSystem.cs +++ b/Content.Server/Silicons/Laws/IonStormSystem.cs @@ -228,7 +228,7 @@ public sealed class IonStormSystem : EntitySystem 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 @@ public sealed class IonStormSystem : EntitySystem 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 9b3e279b75..3bf0297c40 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -69,18 +69,29 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem 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 0b4e0d1ad2..feb56f475a 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 46/86] 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 ea2628e5cb..180b849958 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 a0c0262794..be0c5e49b5 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 sealed class SingularityGeneratorSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(HandleParticleCollide); + SubscribeLocalEvent(OnEmagged); var vvHandle = _vvm.GetTypeHandler(); vvHandle.AddPath(nameof(SingularityGeneratorComponent.Power), (_, comp) => comp.Power, SetPower); @@ -100,11 +114,33 @@ public sealed class SingularityGeneratorSystem : EntitySystem /// 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 @@ public sealed class SingularityGeneratorSystem : EntitySystem 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 0000000000..f3a2254c38 --- /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 647eae2772..45a40bf0fa 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 d45e6c58ea..bdd90f2f16 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 47/86] 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 f3a2254c38..d2a04f9cbc 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 48/86] 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 180b849958..8b9b4e7446 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 49/86] 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 3bf0297c40..8341e3185c 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem 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 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem 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 50/86] 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 378feb1cf8..977be3f4d6 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 51/86] 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 8b9b4e7446..c8feeb5d5d 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 52/86] 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 2b059ca40e..28bab5d4c7 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 c4e2eb9d0250aebed6e7f7048f93820701b4b22d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 20 Nov 2024 01:17:45 +0100 Subject: [PATCH 53/86] .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 2307f36a5d..3249fcc95d 100644 --- a/Content.Server/Announcements/AnnounceCommand.cs +++ b/Content.Server/Announcements/AnnounceCommand.cs @@ -28,7 +28,8 @@ namespace Content.Server.Announcements } 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 830368baa3..edc273b3c1 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 ee8eaa9ad1..1504894b4a 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 9979755f99..91c42e651c 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs @@ -19,7 +19,7 @@ public sealed partial class PathfindingSystem 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 f7bcd019f5..11ac93876e 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs @@ -84,6 +84,6 @@ public sealed partial class PathfindingSystem 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 f7b9475cb5..c346d08536 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 bdc62a6bb3..d3416949d5 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -56,6 +56,7 @@ public sealed class StationAiVisionSystem : EntitySystem EntManager = EntityManager, Maps = _maps, System = this, + VisibleTiles = _singleTiles, }; } @@ -278,7 +279,7 @@ public sealed class StationAiVisionSystem : EntitySystem /// private record struct SeedJob() : IRobustJob { - public StationAiVisionSystem System; + public required StationAiVisionSystem System; public Entity Grid; public Box2 ExpandedBounds; @@ -293,14 +294,14 @@ public sealed class StationAiVisionSystem : EntitySystem { 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 54/86] 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 6341042bf8..26748e8226 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 a5d4e5f70a..3fa03ea2e3 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 bc5377fe81..fadaf2f01e 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 38597adb1e..a035196849 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 55/86] 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 7dcd42e06f..666d104517 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 @@ public sealed class GatewayGeneratorSystem : EntitySystem 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 d031418476..a9d8314f57 100644 --- a/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs +++ b/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs @@ -28,7 +28,7 @@ public sealed partial class SalvageSystem 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 525fe01a1f..31c6b73253 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -104,7 +104,9 @@ public sealed class SpawnSalvageMissionJob : Job 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 430c9c8df2..18959dd7f3 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 5ad94699be..de0593b26f 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -208,7 +208,7 @@ public sealed partial class ShuttleSystem 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 054c42f934..6e8c1a9e20 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -8,6 +8,7 @@ using Content.Server.Station.Systems; 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 0c56f4f556..12d0a26449 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 0000000000..17cae851b9 --- /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 03667f67a7..e8d741e6a9 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 effb391404..5d95e7cbe0 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 56/86] 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 b33d4b00be..b2f63bb596 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 57/86] 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 97c9d8b795..1e61ad9838 100644 --- a/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs +++ b/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs @@ -307,12 +307,6 @@ public sealed class StorageUIController : UIController, IOnSystemChanged(OnInteractWithItem); SubscribeAllEvent(OnSetItemLocation); SubscribeAllEvent(OnInsertItemIntoLocation); - SubscribeAllEvent(OnRemoveItem); SubscribeAllEvent(OnSaveItemLocation); SubscribeLocalEvent(OnReclaimed); @@ -639,19 +638,6 @@ public abstract class SharedStorageSystem : EntitySystem 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 d2c607e57f..5683ae95a7 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -169,20 +169,6 @@ namespace Content.Shared.Storage } } - [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 58/86] 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 b2f63bb596..a578659a00 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 59/86] 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 0000000000..79be86a005 --- /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 60/86] 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 a96780fc3e..0000000000 --- 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 756a223e74..cb7f8af822 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 899db4c37c..0000000000 --- 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 15ce05708e..6adf5942a4 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 5fb81aa6d4..82d43cf7d8 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 8940df2b65..a09986ebd8 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 61/86] 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 a578659a00..0eaa287ba9 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 62/86] 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 be0c5e49b5..cfca86bf4a 100644 --- a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs @@ -117,27 +117,31 @@ public sealed class SingularityGeneratorSystem : EntitySystem 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 @@ public sealed class SingularityGeneratorSystem : EntitySystem }, 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 63/86] 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 cfca86bf4a..95722449b8 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 @@ using Robust.Shared.Timing; 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 sealed class SingularityGeneratorSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(HandleParticleCollide); - SubscribeLocalEvent(OnEmagged); var vvHandle = _vvm.GetTypeHandler(); vvHandle.AddPath(nameof(SingularityGeneratorComponent.Power), (_, comp) => comp.Power, SetPower); @@ -138,7 +133,7 @@ public sealed class SingularityGeneratorSystem : EntitySystem 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 @@ public sealed class SingularityGeneratorSystem : EntitySystem 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 c8feeb5d5d..3643ed31a6 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 0000000000..8830cb0624 --- /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 64/86] 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 47edec135d..110ae80626 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 b673c55e0f..0000000000 --- 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 d5bbaac12c..f6752a67f6 100644 --- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs @@ -78,7 +78,7 @@ public sealed class ItemToggleSystem : EntitySystem args.Verbs.Add(new ActivationVerb() { - Text = !ent.Comp.Activated ? Loc.GetString("item-toggle-activate") : Loc.GetString("item-toggle-deactivate"), + Text = !ent.Comp.Activated ? Loc.GetString(ent.Comp.VerbToggleOn) : Loc.GetString(ent.Comp.VerbToggleOff), Act = () => { 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 858cd9bc11..0000000000 --- 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 4113a74862..0000000000 --- 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 f641879391..de1fcd7d4e 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 ab8084c91c..6610ae87cc 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 b0c586fc75..4b4200fed8 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 65/86] 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 | 106 +++++++++++++++--- .../construction/ui/construction-menu.ftl | 1 + 4 files changed, 116 insertions(+), 21 deletions(-) diff --git a/Content.Client/Construction/UI/ConstructionMenu.xaml b/Content.Client/Construction/UI/ConstructionMenu.xaml index 6e4438cf6f..a934967a53 100644 --- a/Content.Client/Construction/UI/ConstructionMenu.xaml +++ b/Content.Client/Construction/UI/ConstructionMenu.xaml @@ -1,15 +1,20 @@ - + + + + - -