From 1f6d6e41f6ab578b79e129c1a1abe530b1cfb828 Mon Sep 17 00:00:00 2001 From: Colin-Tel <113523727+Colin-Tel@users.noreply.github.com> Date: Fri, 31 May 2024 18:15:41 -0500 Subject: [PATCH 001/235] Update micro.yml (#1274) * Update micro.yml Added southeast solar array, making it easier to obtain power, it's even got another hardsuit! * Update micro.yml added empty light outside the new solars, also gave engie front desk some spare light tubes * Update micro.yml made evac default, as it fits the station better and is more likely to dock at the alternate evac location * Update micro.yml new arrivals (not arrivals) * Update micro.yml added hydro public table * Update micro.yml more hydro changes * Add chef, other map updates adds one chef role just because double-checked firelocks in medbay added little table in maints by epi --- Resources/Maps/micro.yml | 1990 +++++++++++++++++++-------- Resources/Prototypes/Maps/micro.yml | 3 +- 2 files changed, 1421 insertions(+), 572 deletions(-) diff --git a/Resources/Maps/micro.yml b/Resources/Maps/micro.yml index 156159fffe5..acb48bad1d1 100644 --- a/Resources/Maps/micro.yml +++ b/Resources/Maps/micro.yml @@ -74,75 +74,75 @@ entities: chunks: 0,0: ind: 0,0 - tiles: FAAAAAAAFAAAAAAADAAAAAADBAAAAAAABgAAAAAABgAAAAABBQAAAAAABgAAAAADDAAAAAACeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAABgAAAAAABQAAAAAABQAAAAADFAAAAAAAFAAAAAAADAAAAAAAegAAAAAABQAAAAADBQAAAAADBgAAAAACBQAAAAAADAAAAAADeQAAAAAAeQAAAAAAAAAAAAAAegAAAAAABQAAAAACBgAAAAADBQAAAAABDAAAAAABDAAAAAAADAAAAAADegAAAAAADAAAAAAADAAAAAACDAAAAAABDAAAAAADegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAABQAAAAADBQAAAAACBgAAAAABBAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAABgAAAAABDAAAAAAAegAAAAAADAAAAAADBgAAAAABDAAAAAADBgAAAAAADAAAAAACegAAAAAAeQAAAAAAeQAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAACegAAAAAABQAAAAABDAAAAAACegAAAAAADAAAAAABBgAAAAABDAAAAAAABgAAAAACDAAAAAABegAAAAAAeQAAAAAAAAAAAAAAegAAAAAABgAAAAAABQAAAAACBQAAAAAAegAAAAAABgAAAAADDAAAAAAAegAAAAAADAAAAAACDAAAAAADDAAAAAAADAAAAAABegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAABgAAAAACBQAAAAADegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAegAAAAAABQAAAAADBQAAAAAAAwAAAAAAegAAAAAABQAAAAABBQAAAAAAegAAAAAABAAAAAAABAAAAAAABAAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAABQAAAAACBQAAAAADegAAAAAAAQAAAAAABgAAAAACBQAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAABgAAAAAABQAAAAAAegAAAAAAegAAAAAABgAAAAABBQAAAAADegAAAAAAegAAAAAAAQAAAAABAQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBgAAAAACBQAAAAABegAAAAAAegAAAAAABgAAAAADBQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAABAwAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAABBQAAAAAABQAAAAADBAAAAAAADAAAAAADDAAAAAADDAAAAAAADAAAAAACBAAAAAAABQAAAAADBQAAAAADBQAAAAACegAAAAAAegAAAAAABQAAAAABBQAAAAADBQAAAAACBQAAAAAABQAAAAABBAAAAAAADAAAAAACDAAAAAABDAAAAAAADAAAAAABBAAAAAAABQAAAAAABgAAAAAABQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAAgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAACegAAAAAADAAAAAAAegAAAAAABQAAAAADBgAAAAABegAAAAAAegAAAAAAegAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAegAAAAAAegAAAAAABgAAAAAAegAAAAAADAAAAAAAegAAAAAABQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAA + tiles: FAAAAAAAFAAAAAAADAAAAAADBAAAAAAABgAAAAAABgAAAAABBQAAAAAABgAAAAADDAAAAAACFQAAAAAAeQAAAAAAeQAAAAAAegAAAAAABgAAAAAABQAAAAAABQAAAAADFAAAAAAAFAAAAAAADAAAAAAAegAAAAAABQAAAAADBQAAAAADBgAAAAACBQAAAAAADAAAAAADFQAAAAAAeQAAAAAAAAAAAAAAFQAAAAAABQAAAAACBgAAAAADBQAAAAABDAAAAAABDAAAAAAADAAAAAADegAAAAAADAAAAAAADAAAAAACDAAAAAABDAAAAAADegAAAAAAFQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAABQAAAAADBQAAAAACBgAAAAABBAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAABgAAAAABDAAAAAAAFQAAAAAADAAAAAADBgAAAAABDAAAAAADBgAAAAAADAAAAAACegAAAAAAeQAAAAAAeQAAAAAAFQAAAAAABQAAAAABBQAAAAACBQAAAAACegAAAAAABQAAAAABDAAAAAACFQAAAAAADAAAAAABBgAAAAABDAAAAAAABgAAAAACDAAAAAABegAAAAAAeQAAAAAAAAAAAAAAFQAAAAAABgAAAAAABQAAAAACBQAAAAAAegAAAAAABgAAAAADDAAAAAAAegAAAAAADAAAAAACDAAAAAADDAAAAAAADAAAAAABegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAABgAAAAACBQAAAAADegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAFQAAAAAABQAAAAADBQAAAAAAAwAAAAAAegAAAAAABQAAAAABBQAAAAAAegAAAAAABAAAAAAABAAAAAAABAAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAABQAAAAACBQAAAAADegAAAAAAAQAAAAAABgAAAAACBQAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAABgAAAAAABQAAAAAAegAAAAAAegAAAAAABgAAAAABBQAAAAADegAAAAAAegAAAAAAAQAAAAABAQAAAAACegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAABQAAAAACBgAAAAACBQAAAAABegAAAAAAegAAAAAABgAAAAADBQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAABAwAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAABBQAAAAAABQAAAAADBAAAAAAADAAAAAADDAAAAAADDAAAAAAADAAAAAACBAAAAAAABQAAAAADBQAAAAADBQAAAAACegAAAAAAegAAAAAABQAAAAABBQAAAAADBQAAAAACBQAAAAAABQAAAAABBAAAAAAADAAAAAACDAAAAAABDAAAAAAADAAAAAABBAAAAAAABQAAAAAABgAAAAAABQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAAgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAACegAAAAAADAAAAAAAegAAAAAABQAAAAADBgAAAAABegAAAAAAegAAAAAAegAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAegAAAAAAegAAAAAABgAAAAAAegAAAAAADAAAAAAAFQAAAAAABQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAA version: 6 0,-1: ind: 0,-1 - tiles: DgAAAAABDgAAAAACEgAAAAAAEgAAAAABEgAAAAACEgAAAAADEgAAAAAADgAAAAABDgAAAAAADgAAAAAADgAAAAACegAAAAAABQAAAAACBQAAAAACBQAAAAABegAAAAAAFgAAAAAAFgAAAAAAEgAAAAABEgAAAAADEgAAAAADEgAAAAABEgAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAFgAAAAAABAAAAAAABQAAAAADBgAAAAABBQAAAAACegAAAAAABAAAAAAAegAAAAAAEgAAAAACEgAAAAADEgAAAAACEgAAAAACEgAAAAACegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAAABgAAAAACBQAAAAACegAAAAAAFgAAAAAADgAAAAACDgAAAAAADgAAAAAADgAAAAAAFgAAAAAAegAAAAAAegAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAegAAAAAABQAAAAACBQAAAAACBQAAAAABegAAAAAAFgAAAAAADgAAAAABDgAAAAADDgAAAAABDgAAAAADFgAAAAAAegAAAAAADgAAAAAADgAAAAABDgAAAAADFgAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAADgAAAAABegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAADgAAAAADDgAAAAAADgAAAAADFgAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAADegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAAABQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBgAAAAADBQAAAAAABAAAAAAABQAAAAADBQAAAAACBQAAAAABBgAAAAABBQAAAAADBQAAAAADBQAAAAAABgAAAAACBQAAAAAABQAAAAACBQAAAAAAegAAAAAABQAAAAABBgAAAAACBQAAAAACegAAAAAABQAAAAAABQAAAAACBQAAAAACBgAAAAADBQAAAAADBQAAAAADBQAAAAAABgAAAAADBQAAAAADBQAAAAACBQAAAAAABAAAAAAABQAAAAABBQAAAAAABQAAAAADegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAAAegAAAAAADAAAAAACBgAAAAAADAAAAAAAegAAAAAAGgAAAAAAGgAAAAAAGgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBgAAAAABBQAAAAADegAAAAAADAAAAAAABgAAAAACDAAAAAABBAAAAAAADAAAAAADegAAAAAAegAAAAAAGgAAAAAAegAAAAAAeQAAAAAAAAAAAAAAegAAAAAABQAAAAABBgAAAAACBQAAAAAAegAAAAAADAAAAAADDAAAAAAADAAAAAACegAAAAAAFQAAAAAAFQAAAAAADAAAAAABGgAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAADegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAFAAAAAAADAAAAAADDAAAAAADegAAAAAADAAAAAAADAAAAAADDAAAAAADDAAAAAADegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAABQAAAAAABQAAAAADBgAAAAABFAAAAAAAFAAAAAAADAAAAAACegAAAAAABQAAAAABBQAAAAAABgAAAAABBQAAAAAADAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAegAAAAAABQAAAAADBgAAAAACBQAAAAAB + tiles: DgAAAAABDgAAAAACEgAAAAAAEgAAAAABEgAAAAACEgAAAAADEgAAAAAADgAAAAABDgAAAAAADgAAAAAADgAAAAACFQAAAAAABQAAAAACBQAAAAACBQAAAAABegAAAAAAFgAAAAAAFgAAAAAAEgAAAAABEgAAAAADEgAAAAADEgAAAAABEgAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAFgAAAAAABAAAAAAABQAAAAADBgAAAAABBQAAAAACegAAAAAABAAAAAAAegAAAAAAEgAAAAACEgAAAAADEgAAAAACEgAAAAACEgAAAAACegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAAABgAAAAACBQAAAAACFQAAAAAAFgAAAAAADgAAAAACDgAAAAAADgAAAAAADgAAAAAAFgAAAAAAegAAAAAAegAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAegAAAAAABQAAAAACBQAAAAACBQAAAAABFQAAAAAAFgAAAAAADgAAAAABDgAAAAADDgAAAAABDgAAAAADFgAAAAAAegAAAAAADgAAAAAADgAAAAABDgAAAAADFgAAAAAAegAAAAAAFQAAAAAABAAAAAAAFQAAAAAAegAAAAAADgAAAAABegAAAAAAFQAAAAAABAAAAAAABAAAAAAAFQAAAAAAegAAAAAADgAAAAADDgAAAAAADgAAAAADFgAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAADegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAAABQAAAAADegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAABQAAAAACBgAAAAADBQAAAAAABAAAAAAABQAAAAADBQAAAAACBQAAAAABBgAAAAABBQAAAAADBQAAAAADBQAAAAAABgAAAAACBQAAAAAABQAAAAACBQAAAAAAFQAAAAAABQAAAAABBgAAAAACBQAAAAACegAAAAAABQAAAAAABQAAAAACBQAAAAACBgAAAAADBQAAAAADBQAAAAADBQAAAAAABgAAAAADBQAAAAADBQAAAAACBQAAAAAABAAAAAAABQAAAAABBQAAAAAABQAAAAADFQAAAAAAFQAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAAAFQAAAAAABQAAAAADBQAAAAADBQAAAAAAFQAAAAAADAAAAAACBgAAAAAADAAAAAAAFQAAAAAAGgAAAAAAGgAAAAAAGgAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAABQAAAAADBgAAAAABBQAAAAADFQAAAAAADAAAAAAABgAAAAACDAAAAAABBAAAAAAADAAAAAADegAAAAAAegAAAAAAGgAAAAAAegAAAAAAeQAAAAAAAAAAAAAAFQAAAAAABQAAAAABBgAAAAACBQAAAAAAegAAAAAADAAAAAADDAAAAAAADAAAAAACegAAAAAAFQAAAAAAFQAAAAAADAAAAAABGgAAAAAAegAAAAAAeQAAAAAAeQAAAAAAFQAAAAAABQAAAAABBQAAAAACBQAAAAADegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAAegAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAFAAAAAAADAAAAAADDAAAAAADegAAAAAADAAAAAAADAAAAAADDAAAAAADDAAAAAADegAAAAAAFQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAABQAAAAAABQAAAAADBgAAAAABFAAAAAAAFAAAAAAADAAAAAACFQAAAAAABQAAAAABBQAAAAAABgAAAAABBQAAAAAADAAAAAAAFQAAAAAAeQAAAAAAAAAAAAAAFQAAAAAABQAAAAADBgAAAAACBQAAAAAB version: 6 -1,0: ind: -1,0 - tiles: egAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAADAQAAAAACAQAAAAACAQAAAAADAQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAFAAAAAAAFAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAFAAAAAAAFAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAACDAAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAEgAAAAADEgAAAAACEgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAADAAAAAADBgAAAAADEgAAAAADEgAAAAACEgAAAAAAEgAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAADAAAAAAABQAAAAABEgAAAAAAEgAAAAADEgAAAAAAEgAAAAADegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAACBQAAAAAABQAAAAACBQAAAAAAegAAAAAADAAAAAABBgAAAAADEgAAAAADEgAAAAACEgAAAAADEgAAAAACegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAABBQAAAAAABQAAAAADBQAAAAAABQAAAAABegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABQAAAAAABQAAAAACegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAABBQAAAAAABQAAAAACBgAAAAAABQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAABBQAAAAABBAAAAAAABQAAAAAABgAAAAABBQAAAAAABQAAAAAABgAAAAACBQAAAAAABgAAAAACBQAAAAADBQAAAAAABgAAAAAABgAAAAABBQAAAAACBQAAAAAABgAAAAADBgAAAAABegAAAAAABQAAAAAABgAAAAABBQAAAAACBQAAAAAABQAAAAABBgAAAAADBQAAAAABBQAAAAACBQAAAAADBQAAAAADBQAAAAADBQAAAAAABQAAAAAABQAAAAAABQAAAAACBAAAAAAABQAAAAADBgAAAAACegAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAABCQAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAABegAAAAAACgAAAAAABgAAAAAACgAAAAAABgAAAAABCgAAAAAACgAAAAAACgAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAAABAAAAAAABQAAAAACBgAAAAAABQAAAAACegAAAAAACgAAAAAABgAAAAADCgAAAAAABgAAAAACCgAAAAAACgAAAAAABgAAAAAACgAAAAAACgAAAAAAegAAAAAABgAAAAAABAAAAAAABQAAAAACBgAAAAADBQAAAAACegAAAAAACgAAAAAABgAAAAABCgAAAAAABgAAAAAACgAAAAAACgAAAAAACgAAAAAABgAAAAADCgAAAAAAegAAAAAA + tiles: egAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAADAQAAAAACAQAAAAACAQAAAAADAQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAFAAAAAAAFAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAFAAAAAAAFAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAACDAAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAEgAAAAADEgAAAAACEgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAADAAAAAADBgAAAAADEgAAAAADEgAAAAACEgAAAAAAEgAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAADAAAAAAABQAAAAABEgAAAAAAEgAAAAADEgAAAAAAEgAAAAADegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAACBQAAAAAABQAAAAACBQAAAAAAegAAAAAADAAAAAABBgAAAAADEgAAAAADEgAAAAACEgAAAAADEgAAAAACegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAABBQAAAAAABQAAAAADBQAAAAAABQAAAAABegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAAegAAAAAAAwAAAAAAegAAAAAABQAAAAAABQAAAAACegAAAAAAegAAAAAABAAAAAAAFQAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAABBQAAAAAABQAAAAACBgAAAAAABQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAABBQAAAAABBAAAAAAABQAAAAAABgAAAAABBQAAAAAABQAAAAAABgAAAAACBQAAAAAABgAAAAACBQAAAAADBQAAAAAABgAAAAAABgAAAAABBQAAAAACBQAAAAAABgAAAAADBgAAAAABFQAAAAAABQAAAAAABgAAAAABBQAAAAACBQAAAAAABQAAAAABBgAAAAADBQAAAAABBQAAAAACBQAAAAADBQAAAAADBQAAAAADBQAAAAAABQAAAAAABQAAAAAABQAAAAACBAAAAAAABQAAAAADBgAAAAACegAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAABCQAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAABegAAAAAACgAAAAAABgAAAAAACgAAAAAABgAAAAABCgAAAAAACgAAAAAACgAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAAABAAAAAAABQAAAAACBgAAAAAABQAAAAACFQAAAAAACgAAAAAABgAAAAADCgAAAAAABgAAAAACCgAAAAAACgAAAAAABgAAAAAACgAAAAAACgAAAAAAegAAAAAABgAAAAAABAAAAAAABQAAAAACBgAAAAADBQAAAAACBAAAAAAACgAAAAAABgAAAAABCgAAAAAABgAAAAAACgAAAAAACgAAAAAACgAAAAAABgAAAAADCgAAAAAAegAAAAAA version: 6 -1,-1: ind: -1,-1 - tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFgAAAAAADgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAFgAAAAAAFgAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAAABQAAAAADBQAAAAADegAAAAAABQAAAAACBQAAAAAABQAAAAACBQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAFgAAAAAAFgAAAAAABQAAAAADBQAAAAAABQAAAAAABQAAAAACBQAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAABBQAAAAABBQAAAAADegAAAAAAegAAAAAAAwAAAAAAFgAAAAAAFgAAAAAAegAAAAAABAAAAAAAegAAAAAABQAAAAABBQAAAAABegAAAAAABQAAAAAABQAAAAACBQAAAAADBQAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAADgAAAAABDgAAAAADBQAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBgAAAAABBQAAAAABBAAAAAAABQAAAAAABQAAAAACBgAAAAABBQAAAAAABQAAAAABBQAAAAADBgAAAAABBQAAAAADBQAAAAAABQAAAAADBgAAAAACBQAAAAABBQAAAAADBgAAAAADBQAAAAAABAAAAAAABQAAAAADBQAAAAADBgAAAAACBQAAAAABBQAAAAACBQAAAAADBgAAAAAABQAAAAADBQAAAAACBQAAAAADBgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAADBQAAAAADBQAAAAABBQAAAAABBQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAADAAAAAABDAAAAAABegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAAQAAAAAAAQAAAAABAQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAADAAAAAAADAAAAAACegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAADDAAAAAABegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFAAAAAAAFAAAAAAAegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFAAAAAAAFAAAAAAA + tiles: egAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFgAAAAAADgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAFgAAAAAAFgAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAABQAAAAABBQAAAAACBQAAAAAABQAAAAADBQAAAAADegAAAAAABQAAAAACBQAAAAAABQAAAAACBQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAFgAAAAAAFgAAAAAABQAAAAADBQAAAAAABQAAAAAABQAAAAACBQAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAABBQAAAAABBQAAAAADegAAAAAAegAAAAAAAwAAAAAAFgAAAAAAFgAAAAAAFQAAAAAABAAAAAAAegAAAAAABQAAAAABBQAAAAABegAAAAAABQAAAAAABQAAAAACBQAAAAADBQAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAADgAAAAABDgAAAAADBQAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAABAAAAAAAFQAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBgAAAAABBQAAAAABBAAAAAAABQAAAAAABQAAAAACBgAAAAABBQAAAAAABQAAAAABBQAAAAADBgAAAAABBQAAAAADBQAAAAAABQAAAAADBgAAAAACBQAAAAABBQAAAAADBgAAAAADBQAAAAAABAAAAAAABQAAAAADBQAAAAADBgAAAAACBQAAAAABBQAAAAACBQAAAAADBgAAAAAABQAAAAADBQAAAAACBQAAAAADBgAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAADBQAAAAADBQAAAAABBQAAAAABBQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAADAAAAAABDAAAAAABegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAAQAAAAAAAQAAAAABAQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAADAAAAAAADAAAAAACegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAADDAAAAAABegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFAAAAAAAFAAAAAAAegAAAAAAGwAAAAAAHAAAAAAAHAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFAAAAAAAFAAAAAAA version: 6 -1,1: ind: -1,1 - tiles: CQAAAAAAegAAAAAABQAAAAADBQAAAAAABQAAAAACegAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAABgAAAAADCgAAAAAACgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADegAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAABgAAAAAACgAAAAAAAgAAAAAADgAAAAABDgAAAAADegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAACgAAAAAACgAAAAAACgAAAAAAegAAAAAABgAAAAAADgAAAAABegAAAAAABQAAAAADBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAABegAAAAAAegAAAAAABgAAAAAABQAAAAADBQAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAADgAAAAADBAAAAAAABQAAAAACBQAAAAADBgAAAAAABQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAADwAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAADBQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAAABQAAAAACBQAAAAACBQAAAAACegAAAAAAAQAAAAADAQAAAAADegAAAAAAegAAAAAAegAAAAAAAwAAAAAACwAAAAAACwAAAAAABQAAAAACBQAAAAABBQAAAAAABgAAAAADBQAAAAAABQAAAAADBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAABQAAAAABBQAAAAADBgAAAAAABQAAAAAABQAAAAAABQAAAAADBQAAAAAABgAAAAAABQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAAegAAAAAABQAAAAABBQAAAAADBQAAAAACBQAAAAACBQAAAAABBQAAAAABBQAAAAADBgAAAAADBQAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAADBQAAAAACBQAAAAACegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABQAAAAACBgAAAAACBgAAAAAABQAAAAABBgAAAAADBQAAAAACegAAAAAABQAAAAAABQAAAAADBQAAAAACegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABgAAAAADBQAAAAACBQAAAAADBQAAAAACegAAAAAABQAAAAADBgAAAAACBQAAAAACBQAAAAACBQAAAAADBQAAAAADegAAAAAAegAAAAAAFQAAAAAABQAAAAABBQAAAAAABQAAAAABBgAAAAAABQAAAAADBQAAAAADegAAAAAABQAAAAAABQAAAAABBgAAAAADBQAAAAADBQAAAAACBQAAAAACBgAAAAAABgAAAAAA + tiles: CQAAAAAAegAAAAAABQAAAAADBQAAAAAABQAAAAACBAAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAABgAAAAADCgAAAAAACgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADegAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAACgAAAAAABgAAAAAACgAAAAAAAgAAAAAADgAAAAABDgAAAAADegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAACgAAAAAACgAAAAAACgAAAAAAegAAAAAABgAAAAAADgAAAAABegAAAAAABQAAAAADBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAABegAAAAAAegAAAAAABgAAAAAABQAAAAADBQAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAADgAAAAADBAAAAAAABQAAAAACBQAAAAADBgAAAAAABQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAADwAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAADBQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAAABQAAAAACBQAAAAACBQAAAAACegAAAAAAAQAAAAADAQAAAAADegAAAAAAegAAAAAAegAAAAAAAwAAAAAACwAAAAAACwAAAAAABQAAAAACBQAAAAABBQAAAAAABgAAAAADBQAAAAAABQAAAAADBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAABQAAAAABBQAAAAADBgAAAAAABQAAAAAABQAAAAAABQAAAAADBQAAAAAABgAAAAAABQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAAegAAAAAABQAAAAABBQAAAAADBQAAAAACBQAAAAACBQAAAAABBQAAAAABBQAAAAADBgAAAAADBQAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAABQAAAAABBQAAAAAABQAAAAADBQAAAAACBQAAAAACegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABQAAAAACBgAAAAACBgAAAAAABQAAAAABBgAAAAADBQAAAAACegAAAAAABQAAAAAABQAAAAADBQAAAAACegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABgAAAAADBQAAAAACBQAAAAADBQAAAAACFQAAAAAABQAAAAADBgAAAAACBQAAAAACBQAAAAACBQAAAAADBQAAAAADegAAAAAAegAAAAAAFQAAAAAABQAAAAABBQAAAAAABQAAAAABBgAAAAAABQAAAAADBQAAAAADFQAAAAAABQAAAAAABQAAAAABBgAAAAADBQAAAAADBQAAAAACBQAAAAACBgAAAAAABgAAAAAA version: 6 0,1: ind: 0,1 - tiles: AgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAegAAAAAAegAAAAAABgAAAAABBgAAAAAABgAAAAACegAAAAAABQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAegAAAAAABQAAAAACFwAAAAAAFwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAAgAAAAAAegAAAAAAegAAAAAABQAAAAABFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAABAAAAAAABQAAAAABBgAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAAegAAAAAABQAAAAADFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAABAAAAAAABQAAAAADBQAAAAAABQAAAAADAwAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAABAAAAAAABQAAAAABFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAegAAAAAAegAAAAAABgAAAAACBQAAAAACegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAABAAAAAAABQAAAAACFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAegAAAAAABQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAABAAAAAAABQAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAegAAAAAABgAAAAADBQAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAAegAAAAAABQAAAAACFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAegAAAAAABQAAAAADBQAAAAABegAAAAAADAAAAAABDAAAAAAACwAAAAAACwAAAAAACwAAAAAABAAAAAAABQAAAAACFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAegAAAAAABgAAAAACBQAAAAABegAAAAAADAAAAAABDAAAAAAACwAAAAAACwAAAAAACwAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAACFwAAAAAAFwAAAAAAFwAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAADAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAABQAAAAACBQAAAAABegAAAAAADAAAAAAADAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAACBQAAAAAABgAAAAACBQAAAAADBAAAAAAADAAAAAADDQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABQAAAAAABQAAAAACBQAAAAADBgAAAAACBQAAAAAABQAAAAABegAAAAAADAAAAAADDAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAABBQAAAAADBQAAAAAABQAAAAADBgAAAAAABQAAAAACBgAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAABBAAAAAAABQAAAAADBgAAAAAABgAAAAAABQAAAAAABQAAAAAABQAAAAABBQAAAAACBQAAAAADBQAAAAABegAAAAAAeQAAAAAAeQAAAAAA + tiles: AgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAegAAAAAAegAAAAAABgAAAAABBgAAAAAABgAAAAACFQAAAAAABQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAegAAAAAABQAAAAACFwAAAAAAFwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAAgAAAAAAFQAAAAAAegAAAAAABQAAAAABFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAABAAAAAAABQAAAAABBgAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAAegAAAAAABQAAAAADFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAABAAAAAAABQAAAAADBQAAAAAABQAAAAADAwAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAABAAAAAAABQAAAAABFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAegAAAAAAegAAAAAABgAAAAACBQAAAAACegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAABAAAAAAABQAAAAACFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFQAAAAAABQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAABAAAAAAABQAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFQAAAAAABgAAAAADBQAAAAAAegAAAAAAegAAAAAAegAAAAAACwAAAAAACwAAAAAACwAAAAAAegAAAAAABQAAAAACFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFQAAAAAABQAAAAADBQAAAAABegAAAAAADAAAAAABDAAAAAAACwAAAAAACwAAAAAACwAAAAAABAAAAAAABQAAAAACFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFwAAAAAAFQAAAAAABgAAAAACBQAAAAABegAAAAAADAAAAAABDAAAAAAACwAAAAAACwAAAAAACwAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAACFwAAAAAAFwAAAAAAFwAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAADAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAFQAAAAAABAAAAAAABAAAAAAAegAAAAAABQAAAAACBQAAAAABFQAAAAAADAAAAAAADAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAACBQAAAAAABgAAAAACBQAAAAADBAAAAAAADAAAAAADDQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABQAAAAAABQAAAAACBQAAAAADBgAAAAACBQAAAAAABQAAAAABegAAAAAADAAAAAADDAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAABBQAAAAADBQAAAAAABQAAAAADBgAAAAAABQAAAAACBgAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAABBAAAAAAABQAAAAADBgAAAAAABgAAAAAABQAAAAAABQAAAAAABQAAAAABBQAAAAACBQAAAAADBQAAAAABegAAAAAAeQAAAAAAeQAAAAAA version: 6 1,0: ind: 1,0 - tiles: BQAAAAABBgAAAAADegAAAAAABQAAAAACBgAAAAADBQAAAAABBQAAAAACBQAAAAACBQAAAAAABQAAAAAABQAAAAAABQAAAAACBgAAAAABBQAAAAAABQAAAAABBgAAAAAABgAAAAACBQAAAAABBAAAAAAABQAAAAAABQAAAAACBgAAAAACBQAAAAACBQAAAAABBgAAAAACBQAAAAADBQAAAAAABgAAAAACBQAAAAACBQAAAAAABQAAAAADBQAAAAABBQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAABegAAAAAAAwAAAAAAegAAAAAAegAAAAAAAwAAAAAABQAAAAABBQAAAAADBQAAAAAABgAAAAACBQAAAAADBQAAAAACBgAAAAADBQAAAAABegAAAAAAegAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAABBgAAAAADBQAAAAAABQAAAAADBgAAAAACBQAAAAAABQAAAAABBgAAAAABBAAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABgAAAAACBQAAAAACBQAAAAAABgAAAAAABQAAAAAABQAAAAADBgAAAAABBQAAAAADegAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAABAAAAAAAegAAAAAABgAAAAAABQAAAAAABQAAAAAAegAAAAAABQAAAAACegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBQAAAAAABQAAAAADBQAAAAABegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAADBQAAAAADBQAAAAACBQAAAAACBQAAAAADBQAAAAACBQAAAAABBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABQAAAAABBQAAAAADBQAAAAACBQAAAAABBQAAAAACBQAAAAADBQAAAAAABQAAAAAABQAAAAADBQAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAACBQAAAAACBQAAAAAABQAAAAAABQAAAAAABQAAAAACBQAAAAACBQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDwAAAAAADwAAAAABDwAAAAACegAAAAAABQAAAAADBQAAAAABBQAAAAABegAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAegAAAAAADgAAAAABDwAAAAACDwAAAAABDwAAAAACBAAAAAAABQAAAAAABQAAAAACBQAAAAABegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAADgAAAAAADgAAAAABDgAAAAAADgAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAABegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: BQAAAAABBgAAAAADFQAAAAAABQAAAAACBgAAAAADBQAAAAABBQAAAAACBQAAAAACBQAAAAAABQAAAAAABQAAAAAABQAAAAACBgAAAAABBQAAAAAABQAAAAABBgAAAAAABgAAAAACBQAAAAABBAAAAAAABQAAAAAABQAAAAACBgAAAAACBQAAAAACBQAAAAABBgAAAAACBQAAAAADBQAAAAAABgAAAAACBQAAAAACBQAAAAAABQAAAAADBQAAAAABBQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAABegAAAAAAAwAAAAAAegAAAAAAegAAAAAAAwAAAAAABQAAAAABBQAAAAADBQAAAAAABgAAAAACBQAAAAADBQAAAAACBgAAAAADBQAAAAABegAAAAAAegAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAABBgAAAAADBQAAAAAABQAAAAADBgAAAAACBQAAAAAABQAAAAABBgAAAAABBAAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABgAAAAACBQAAAAACBQAAAAAABgAAAAAABQAAAAAABQAAAAADBgAAAAABBQAAAAADFQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAABAAAAAAAegAAAAAABgAAAAAABQAAAAAABQAAAAAAFQAAAAAABQAAAAACegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBQAAAAAABQAAAAADBQAAAAABegAAAAAABAAAAAAABAAAAAAAFQAAAAAAegAAAAAAegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAADBQAAAAADBQAAAAACBQAAAAACBQAAAAADBQAAAAACBQAAAAABBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABQAAAAABBQAAAAADBQAAAAACBQAAAAABBQAAAAACBQAAAAADBQAAAAAABQAAAAAABQAAAAADBQAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAACBQAAAAACBQAAAAAABQAAAAAABQAAAAAABQAAAAACBQAAAAACBQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDwAAAAAADwAAAAABDwAAAAACegAAAAAABQAAAAADBQAAAAABBQAAAAABegAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAegAAAAAADgAAAAABDwAAAAACDwAAAAABDwAAAAACBAAAAAAABQAAAAAABQAAAAACBQAAAAABFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAADgAAAAAADgAAAAABDgAAAAAADgAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAABFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 1,-1: ind: 1,-1 - tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAACDAAAAAADDAAAAAAADAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAAwAAAAAADAAAAAABDAAAAAABDAAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAACDAAAAAAABgAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAABBgAAAAABDAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAADAAAAAADDAAAAAADDAAAAAAAegAAAAAADgAAAAAADgAAAAAADgAAAAADegAAAAAAegAAAAAAegAAAAAAHQAAAAACHQAAAAACHQAAAAABHQAAAAABHQAAAAACHQAAAAAADAAAAAABDAAAAAADDAAAAAACBAAAAAAADgAAAAACDgAAAAADDgAAAAABDgAAAAAAegAAAAAABQAAAAADHQAAAAABBgAAAAADHQAAAAABBgAAAAADHQAAAAABHQAAAAAABgAAAAACBgAAAAABDAAAAAACegAAAAAADgAAAAAADgAAAAABDgAAAAAADgAAAAADegAAAAAABQAAAAADHQAAAAABHQAAAAACBgAAAAACHQAAAAAAHQAAAAACHQAAAAADDAAAAAADDAAAAAADDAAAAAADegAAAAAADgAAAAAADgAAAAACDgAAAAABDgAAAAACegAAAAAAegAAAAAAHQAAAAADHQAAAAAAHQAAAAADHQAAAAABHQAAAAAAHQAAAAABBgAAAAACBgAAAAADDAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACHQAAAAADBgAAAAADHQAAAAABBgAAAAADHQAAAAACHQAAAAACDAAAAAADDAAAAAADDAAAAAACegAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAegAAAAAABQAAAAADHQAAAAADHQAAAAADBgAAAAADHQAAAAABHQAAAAADHQAAAAAAHQAAAAAAHQAAAAABHQAAAAACegAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAAABAAAAAAABQAAAAADHQAAAAABHQAAAAAAHQAAAAACHQAAAAAAHQAAAAACBgAAAAACHQAAAAACBgAAAAACHQAAAAAAegAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAegAAAAAABQAAAAABHQAAAAABHQAAAAAAHQAAAAAAHQAAAAADHQAAAAACHQAAAAACBgAAAAACHQAAAAADBgAAAAAABAAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAegAAAAAABQAAAAAABAAAAAAABAAAAAAAegAAAAAAHQAAAAAAHQAAAAACHQAAAAADHQAAAAABHQAAAAACHQAAAAADegAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAACBgAAAAAABQAAAAAABAAAAAAABQAAAAACBQAAAAADBgAAAAAABQAAAAACBQAAAAAABgAAAAADBQAAAAACBQAAAAADBgAAAAAABQAAAAABBQAAAAADBQAAAAADBQAAAAAA + tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAACDAAAAAADDAAAAAAADAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAAwAAAAAADAAAAAABDAAAAAABDAAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAACDAAAAAAABgAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAABBgAAAAABDAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAADAAAAAADDAAAAAADDAAAAAAAegAAAAAADgAAAAAADgAAAAAADgAAAAADegAAAAAAegAAAAAAegAAAAAAHQAAAAACHQAAAAACHQAAAAABHQAAAAABHQAAAAACHQAAAAAADAAAAAABDAAAAAADDAAAAAACBAAAAAAADgAAAAACDgAAAAADDgAAAAABDgAAAAAAegAAAAAABQAAAAADHQAAAAABBgAAAAADHQAAAAABBgAAAAADHQAAAAABHQAAAAAABgAAAAACBgAAAAABDAAAAAACFQAAAAAADgAAAAAADgAAAAABDgAAAAAADgAAAAADegAAAAAABQAAAAADHQAAAAABHQAAAAACBgAAAAACHQAAAAAAHQAAAAACHQAAAAADDAAAAAADDAAAAAADDAAAAAADFQAAAAAADgAAAAAADgAAAAACDgAAAAABDgAAAAACegAAAAAAegAAAAAAHQAAAAADHQAAAAAAHQAAAAADHQAAAAABHQAAAAAAHQAAAAABBgAAAAACBgAAAAADDAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACHQAAAAADBgAAAAADHQAAAAABBgAAAAADHQAAAAACHQAAAAACDAAAAAADDAAAAAADDAAAAAACegAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAFQAAAAAABQAAAAADHQAAAAADHQAAAAADBgAAAAADHQAAAAABHQAAAAADHQAAAAAAHQAAAAAAHQAAAAABHQAAAAACegAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAAABAAAAAAABQAAAAADHQAAAAABHQAAAAAAHQAAAAACHQAAAAAAHQAAAAACBgAAAAACHQAAAAACBgAAAAACHQAAAAAAFQAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAegAAAAAABQAAAAABHQAAAAABHQAAAAAAHQAAAAAAHQAAAAADHQAAAAACHQAAAAACBgAAAAACHQAAAAADBgAAAAAABAAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAegAAAAAABQAAAAAABAAAAAAABAAAAAAAegAAAAAAHQAAAAAAHQAAAAACHQAAAAADHQAAAAABHQAAAAACHQAAAAADFQAAAAAAHgAAAAAAHgAAAAAAHgAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAABAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAACBgAAAAAABQAAAAAABAAAAAAABQAAAAACBQAAAAADBgAAAAAABQAAAAACBQAAAAAABgAAAAADBQAAAAACBQAAAAADBgAAAAAABQAAAAABBQAAAAADBQAAAAADBQAAAAAA version: 6 1,1: ind: 1,1 - tiles: egAAAAAAEAAAAAAAEAAAAAAAEAAAAAAAEAAAAAAABQAAAAACBQAAAAADBQAAAAABBQAAAAABegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAEAAAAAAAEAAAAAAAEAAAAAAAEAAAAAAABQAAAAADBQAAAAADBQAAAAADBQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABQAAAAADBQAAAAACBQAAAAABBQAAAAAABQAAAAABBQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAEAAAAAAAEAAAAAAAEAAAAAAAEAAAAAAABQAAAAAABQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAEAAAAAAAEAAAAAAAEAAAAAAAEAAAAAAABQAAAAACBQAAAAACBQAAAAABBwAAAAAAegAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAACBQAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAAABQAAAAACBwAAAAAAegAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBQAAAAACBQAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAADBQAAAAADBQAAAAADegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAADAAAAAAADAAAAAADegAAAAAABQAAAAABBQAAAAACBQAAAAACBQAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAACDAAAAAACDAAAAAAAegAAAAAABAAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAABDAAAAAACDAAAAAACDAAAAAAADAAAAAABDAAAAAAADAAAAAACDAAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAAADAAAAAABDQAAAAABDQAAAAADDAAAAAAADQAAAAADDQAAAAACDAAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAADDAAAAAACDAAAAAACDAAAAAADDAAAAAAADAAAAAADDAAAAAADDAAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAABwAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: egAAAAAAEAAAAAAAEAAAAAAAEAAAAAAAEAAAAAAABQAAAAACBQAAAAADBQAAAAABBQAAAAABFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAEAAAAAAAEAAAAAAAEAAAAAAAEAAAAAAABQAAAAADBQAAAAADBQAAAAADBQAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABQAAAAADBQAAAAACBQAAAAABBQAAAAAABQAAAAABBQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAEAAAAAAAEAAAAAAAEAAAAAAAEAAAAAAABQAAAAAABQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAEAAAAAAAEAAAAAAAEAAAAAAAEAAAAAAABQAAAAACBQAAAAACBQAAAAABBwAAAAAAegAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAACBQAAAAAAFQAAAAAAegAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAAABQAAAAACBwAAAAAAegAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBQAAAAACBQAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAADBQAAAAADBQAAAAADFQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAADAAAAAAADAAAAAADegAAAAAABQAAAAABBQAAAAACBQAAAAACBQAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAACDAAAAAACDAAAAAAAegAAAAAABAAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAABDAAAAAACDAAAAAACDAAAAAAADAAAAAABDAAAAAAADAAAAAACDAAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAAADAAAAAABDQAAAAABDQAAAAADDAAAAAAADQAAAAADDQAAAAACDAAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAADDAAAAAACDAAAAAACDAAAAAADDAAAAAAADAAAAAADDAAAAAADDAAAAAACegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAABwAAAAAABwAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 0,-2: ind: 0,-2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAADAwAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABQAAAAAADgAAAAAADgAAAAADEgAAAAADEgAAAAADEgAAAAABEgAAAAADEgAAAAABegAAAAAAIgAAAAAEDgAAAAABIgAAAAACegAAAAAABQAAAAACBQAAAAACBQAAAAADBQAAAAADDgAAAAADDgAAAAADEgAAAAAAEgAAAAABEgAAAAABEgAAAAAAEgAAAAADBAAAAAAADgAAAAABDgAAAAADIgAAAAAEegAAAAAABQAAAAADBgAAAAADBQAAAAAABQAAAAACDgAAAAACDgAAAAADEgAAAAACEgAAAAAAEgAAAAACEgAAAAABEgAAAAADegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAADBgAAAAABBQAAAAADBQAAAAABDgAAAAABDgAAAAACEgAAAAACEgAAAAAAEgAAAAACEgAAAAADEgAAAAADDgAAAAACDgAAAAADDgAAAAACDgAAAAABBAAAAAAABQAAAAABBQAAAAACBQAAAAACBQAAAAAB + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAADAwAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABQAAAAAADgAAAAAADgAAAAADEgAAAAADEgAAAAADEgAAAAABEgAAAAADEgAAAAABegAAAAAAIgAAAAAEDgAAAAABIgAAAAACegAAAAAABQAAAAACBQAAAAACBQAAAAADBQAAAAADDgAAAAADDgAAAAADEgAAAAAAEgAAAAABEgAAAAABEgAAAAAAEgAAAAADBAAAAAAADgAAAAABDgAAAAADIgAAAAAEegAAAAAABQAAAAADBgAAAAADBQAAAAAABQAAAAACDgAAAAACDgAAAAADEgAAAAACEgAAAAAAEgAAAAACEgAAAAABEgAAAAADegAAAAAAFQAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAADBgAAAAABBQAAAAADBQAAAAABDgAAAAABDgAAAAACEgAAAAACEgAAAAAAEgAAAAACEgAAAAADEgAAAAADDgAAAAACDgAAAAADDgAAAAACDgAAAAABBAAAAAAABQAAAAABBQAAAAACBQAAAAACBQAAAAAB version: 6 1,-2: ind: 1,-2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAACBQAAAAADBgAAAAACBQAAAAAABgAAAAADBQAAAAACBgAAAAABegAAAAAADAAAAAABDAAAAAABDAAAAAABegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAABQAAAAABBgAAAAABBQAAAAACBQAAAAABBQAAAAADBQAAAAAABQAAAAABBAAAAAAADAAAAAAABgAAAAABBgAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAACBQAAAAABBgAAAAABBQAAAAADBQAAAAADBQAAAAACBgAAAAABegAAAAAADAAAAAACDAAAAAAADAAAAAADDAAAAAAABQAAAAADDQAAAAADBQAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAADBgAAAAACBQAAAAACBgAAAAABBQAAAAACBAAAAAAADAAAAAACBgAAAAAABgAAAAAADAAAAAACBQAAAAAABQAAAAACBQAAAAABegAAAAAABQAAAAACBQAAAAABBgAAAAABBQAAAAAABgAAAAADBQAAAAABegAAAAAAegAAAAAADAAAAAABDAAAAAAADAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAABBQAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAADAAAAAACDAAAAAABBgAAAAABBgAAAAABDAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAFQAAAAAAegAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAFQAAAAAAegAAAAAAFQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAFQAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAACBQAAAAADBgAAAAACBQAAAAAABgAAAAADBQAAAAACBgAAAAABegAAAAAADAAAAAABDAAAAAABDAAAAAABegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAABQAAAAABBgAAAAABBQAAAAACBQAAAAABBQAAAAADBQAAAAAABQAAAAABBAAAAAAADAAAAAAABgAAAAABBgAAAAABegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAABgAAAAACBQAAAAABBgAAAAABBQAAAAADBQAAAAADBQAAAAACBgAAAAABFQAAAAAADAAAAAACDAAAAAAADAAAAAADDAAAAAAABQAAAAADDQAAAAADBQAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAADBgAAAAACBQAAAAACBgAAAAABBQAAAAACBAAAAAAADAAAAAACBgAAAAAABgAAAAAADAAAAAACBQAAAAAABQAAAAACBQAAAAABegAAAAAABQAAAAACBQAAAAABBgAAAAABBQAAAAAABgAAAAADBQAAAAABegAAAAAAegAAAAAADAAAAAABDAAAAAAADAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAABBQAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAADAAAAAACDAAAAAABBgAAAAABBgAAAAABDAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 -1,-2: ind: -1,-2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAIgAAAAABIgAAAAACIgAAAAAAIgAAAAAGegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACIgAAAAABDgAAAAABIgAAAAAFegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAACDgAAAAAAIgAAAAACegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAACDgAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACFgAAAAAAFgAAAAAAFgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAABFgAAAAAAFgAAAAAAFgAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAIgAAAAABIgAAAAACIgAAAAAAIgAAAAAGegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACIgAAAAABDgAAAAABIgAAAAAFegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAACDgAAAAAAIgAAAAACegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAACDgAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAADgAAAAACFgAAAAAAFgAAAAAAFgAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAABFgAAAAAAFgAAAAAAFgAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAA version: 6 -2,-2: ind: -2,-2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAAABQAAAAACBQAAAAAABQAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAAABQAAAAADBQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABAAAAAAABQAAAAADBQAAAAABBQAAAAABBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABegAAAAAABQAAAAADBQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAAABQAAAAACBQAAAAAABQAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABAAAAAAAegAAAAAAegAAAAAAFQAAAAAABQAAAAACBQAAAAAABQAAAAADBQAAAAABBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABAAAAAAABQAAAAADBQAAAAABBQAAAAABBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAFQAAAAAABQAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAADegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABegAAAAAABQAAAAADBQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 -2,-1: ind: -2,-1 - tiles: egAAAAAAegAAAAAABQAAAAAABQAAAAABBQAAAAADBQAAAAABBQAAAAACBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAACBQAAAAADBQAAAAAABQAAAAACegAAAAAABQAAAAAABQAAAAACBQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAABQAAAAAABAAAAAAABQAAAAABBQAAAAABBQAAAAADBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABQAAAAACBQAAAAAABQAAAAABBQAAAAAABQAAAAACBQAAAAADegAAAAAABQAAAAABBQAAAAACBQAAAAACBQAAAAABBQAAAAABBQAAAAADBQAAAAACegAAAAAAegAAAAAAegAAAAAABQAAAAACBgAAAAACBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAACDgAAAAACDgAAAAACBAAAAAAABQAAAAADBQAAAAAABgAAAAADBQAAAAABBAAAAAAABQAAAAABBgAAAAADBQAAAAACBgAAAAADBQAAAAADegAAAAAAegAAAAAAegAAAAAADgAAAAADDgAAAAABegAAAAAABQAAAAABBgAAAAADBQAAAAACBQAAAAAAegAAAAAABQAAAAACBQAAAAADBgAAAAACBQAAAAAABQAAAAADBAAAAAAABQAAAAACBQAAAAADDgAAAAAADgAAAAADegAAAAAAegAAAAAABQAAAAAABgAAAAAABQAAAAACBAAAAAAABQAAAAACBgAAAAAABQAAAAAABgAAAAAABQAAAAABBAAAAAAABgAAAAADBQAAAAACDgAAAAADDgAAAAABEgAAAAACegAAAAAABgAAAAAABQAAAAABBQAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAADBQAAAAADegAAAAAAegAAAAAABQAAAAABBgAAAAACDgAAAAAADgAAAAABEgAAAAADegAAAAAABQAAAAADBgAAAAACBQAAAAACegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAAABgAAAAABBQAAAAAABQAAAAACBQAAAAADBgAAAAACBQAAAAABBQAAAAABegAAAAAAegAAAAAAIAAAAAADBQAAAAACBQAAAAABBQAAAAACIAAAAAABegAAAAAABQAAAAADBQAAAAACBgAAAAABBQAAAAAABQAAAAABBQAAAAACBgAAAAADBQAAAAAAegAAAAAAegAAAAAAIAAAAAADBgAAAAADBgAAAAAABgAAAAACIAAAAAACegAAAAAABQAAAAACBQAAAAADBQAAAAABegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIAAAAAADBQAAAAADBQAAAAABBQAAAAABBQAAAAACBAAAAAAABQAAAAADBQAAAAADBQAAAAADegAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAegAAAAAAegAAAAAAIAAAAAAABQAAAAABBQAAAAACBQAAAAABBQAAAAADegAAAAAABQAAAAABBgAAAAADBgAAAAAAegAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAJwAAAAACegAAAAAAIAAAAAADBgAAAAABBgAAAAADBgAAAAACBQAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAADegAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAA + tiles: egAAAAAAegAAAAAABQAAAAAABQAAAAABBQAAAAADBQAAAAABBQAAAAACBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAACBQAAAAADBQAAAAAABQAAAAACFQAAAAAABQAAAAAABQAAAAACBQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAABQAAAAAABAAAAAAABQAAAAABBQAAAAABBQAAAAADBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABQAAAAACBQAAAAAABQAAAAABBQAAAAAABQAAAAACBQAAAAADFQAAAAAABQAAAAABBQAAAAACBQAAAAACBQAAAAABBQAAAAABBQAAAAADBQAAAAACegAAAAAAegAAAAAAegAAAAAABQAAAAACBgAAAAACBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAFQAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAACDgAAAAACDgAAAAACBAAAAAAABQAAAAADBQAAAAAABgAAAAADBQAAAAABBAAAAAAABQAAAAABBgAAAAADBQAAAAACBgAAAAADBQAAAAADegAAAAAAegAAAAAAFQAAAAAADgAAAAADDgAAAAABFQAAAAAABQAAAAABBgAAAAADBQAAAAACBQAAAAAAFQAAAAAABQAAAAACBQAAAAADBgAAAAACBQAAAAAABQAAAAADBAAAAAAABQAAAAACBQAAAAADDgAAAAAADgAAAAADegAAAAAAegAAAAAABQAAAAAABgAAAAAABQAAAAACBAAAAAAABQAAAAACBgAAAAAABQAAAAAABgAAAAAABQAAAAABBAAAAAAABgAAAAADBQAAAAACDgAAAAADDgAAAAABEgAAAAACegAAAAAABgAAAAAABQAAAAABBQAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAADBQAAAAADegAAAAAAegAAAAAABQAAAAABBgAAAAACDgAAAAAADgAAAAABEgAAAAADegAAAAAABQAAAAADBgAAAAACBQAAAAACegAAAAAAFQAAAAAABAAAAAAABAAAAAAAFQAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAABQAAAAAABgAAAAABBQAAAAAABQAAAAACBQAAAAADBgAAAAACBQAAAAABBQAAAAABegAAAAAAegAAAAAAIAAAAAADBQAAAAACBQAAAAABBQAAAAACIAAAAAABFQAAAAAABQAAAAADBQAAAAACBgAAAAABBQAAAAAABQAAAAABBQAAAAACBgAAAAADBQAAAAAAegAAAAAAegAAAAAAIAAAAAADBgAAAAADBgAAAAAABgAAAAACIAAAAAACFQAAAAAABQAAAAACBQAAAAADBQAAAAABegAAAAAAegAAAAAABAAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAIAAAAAADBQAAAAADBQAAAAABBQAAAAABBQAAAAACBAAAAAAABQAAAAADBQAAAAADBQAAAAADegAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAegAAAAAAegAAAAAAIAAAAAAABQAAAAABBQAAAAACBQAAAAABBQAAAAADFQAAAAAABQAAAAABBgAAAAADBgAAAAAAegAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAJwAAAAACegAAAAAAIAAAAAADBgAAAAABBgAAAAADBgAAAAACBQAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAADegAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAA version: 6 -2,0: ind: -2,0 - tiles: JwAAAAACegAAAAAAIAAAAAACBQAAAAADIAAAAAADIAAAAAACIAAAAAACegAAAAAABQAAAAACBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAJwAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAJwAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABQAAAAADBgAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBgAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAABBgAAAAAABAAAAAAABQAAAAABBQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAABegAAAAAABgAAAAADCAAAAAABCAAAAAAABAAAAAAABQAAAAACBQAAAAADBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAABgAAAAABCAAAAAADegAAAAAACAAAAAAACAAAAAAACAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAACAAAAAADCAAAAAADBAAAAAAACAAAAAAABgAAAAACBgAAAAABCAAAAAADCAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAADegAAAAAACAAAAAABCAAAAAACegAAAAAABgAAAAACCAAAAAACCAAAAAACCAAAAAAACAAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAADegAAAAAACAAAAAABCAAAAAACCAAAAAABBgAAAAABCAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAAABQAAAAAABAAAAAAAegAAAAAAegAAAAAACAAAAAABBgAAAAABBgAAAAACCAAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAACBQAAAAACBQAAAAABBQAAAAADBgAAAAACBgAAAAABCAAAAAACCAAAAAAABAAAAAAABgAAAAABCAAAAAAACAAAAAACCAAAAAAABAAAAAAABQAAAAAABQAAAAAABgAAAAADBgAAAAADBQAAAAABBQAAAAAABQAAAAADBQAAAAACBgAAAAABCAAAAAADBAAAAAAACAAAAAAACAAAAAAACAAAAAABBgAAAAAABAAAAAAABQAAAAAABQAAAAAABQAAAAACBQAAAAACBQAAAAACegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAABegAAAAAACAAAAAABBgAAAAADBgAAAAAACAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACQAAAAAACQAAAAAACAAAAAABCAAAAAAAegAAAAAABgAAAAADCAAAAAACCAAAAAACegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAAACAAAAAAABgAAAAAACAAAAAABegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAADwAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABgAAAAAACAAAAAAA + tiles: JwAAAAACegAAAAAAIAAAAAACBQAAAAADIAAAAAADIAAAAAACIAAAAAACegAAAAAABQAAAAACBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAJwAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAJwAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABQAAAAADBgAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAADBgAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAABBgAAAAAABAAAAAAABQAAAAABBQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAABegAAAAAABgAAAAADCAAAAAABCAAAAAAABAAAAAAABQAAAAACBQAAAAADBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAABgAAAAABCAAAAAADegAAAAAACAAAAAAACAAAAAAACAAAAAADFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAACAAAAAADCAAAAAADBAAAAAAACAAAAAAABgAAAAACBgAAAAABCAAAAAADCAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAADegAAAAAACAAAAAABCAAAAAACegAAAAAABgAAAAACCAAAAAACCAAAAAACCAAAAAAACAAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAADegAAAAAACAAAAAABCAAAAAACCAAAAAABBgAAAAABCAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAAABQAAAAAABAAAAAAAegAAAAAAegAAAAAACAAAAAABBgAAAAABBgAAAAACCAAAAAAAegAAAAAAegAAAAAABQAAAAACBQAAAAACBQAAAAACBQAAAAABBQAAAAADBgAAAAACBgAAAAABCAAAAAACCAAAAAAABAAAAAAABgAAAAABCAAAAAAACAAAAAACCAAAAAAABAAAAAAABQAAAAAABQAAAAAABgAAAAADBgAAAAADBQAAAAABBQAAAAAABQAAAAADBQAAAAACBgAAAAABCAAAAAADBAAAAAAACAAAAAAACAAAAAAACAAAAAABBgAAAAAABAAAAAAABQAAAAAABQAAAAAABQAAAAACBQAAAAACBQAAAAACegAAAAAAFQAAAAAAFQAAAAAACAAAAAADCAAAAAABFQAAAAAACAAAAAABBgAAAAADBgAAAAAACAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACQAAAAAACQAAAAAACAAAAAABCAAAAAAAegAAAAAABgAAAAADCAAAAAACCAAAAAACegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAAACAAAAAAABgAAAAAACAAAAAABegAAAAAAegAAAAAABAAAAAAAFQAAAAAAegAAAAAADwAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABgAAAAAACAAAAAAA version: 6 -2,1: ind: -2,1 - tiles: CAAAAAADCAAAAAADBAAAAAAACAAAAAAACAAAAAAACAAAAAADegAAAAAADAAAAAABDAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAACQAAAAAACQAAAAAABAAAAAAABAAAAAAAegAAAAAABgAAAAAACAAAAAADCAAAAAACBAAAAAAADAAAAAAABgAAAAADDAAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABgAAAAACCAAAAAABCAAAAAACCAAAAAAABgAAAAACCAAAAAADegAAAAAADAAAAAACDAAAAAACDAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAABCAAAAAADCAAAAAABCAAAAAACCAAAAAABCAAAAAACCAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAADBgAAAAADBgAAAAAACAAAAAACCAAAAAAACAAAAAAABgAAAAABCAAAAAABCAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAADgAAAAAABgAAAAAACAAAAAADBgAAAAABCAAAAAACCAAAAAADCAAAAAACBgAAAAADCAAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAGAAAAAACDgAAAAAACAAAAAACCAAAAAADCAAAAAABCAAAAAACCAAAAAABCAAAAAACCAAAAAACegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAIAAAAAABBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADegAAAAAACAAAAAABCAAAAAADCAAAAAAACAAAAAACCAAAAAADegAAAAAAIAAAAAABBQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAAACAAAAAABCAAAAAAACAAAAAABBAAAAAAABQAAAAACBQAAAAACIAAAAAADIAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAABAAAAAAACAAAAAABCAAAAAABCAAAAAAACAAAAAACCAAAAAADegAAAAAABQAAAAACBQAAAAABIAAAAAACIAAAAAABegAAAAAAegAAAAAAAwAAAAAAegAAAAAACAAAAAACegAAAAAACAAAAAABCAAAAAADCAAAAAAACAAAAAADegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAACBQAAAAAABAAAAAAABQAAAAABBgAAAAADBQAAAAABCAAAAAAAegAAAAAACAAAAAAACAAAAAACCAAAAAAACAAAAAADegAAAAAAIAAAAAABBQAAAAACBQAAAAACBQAAAAABBQAAAAAAegAAAAAABQAAAAADBQAAAAACBQAAAAABCAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAIAAAAAABBQAAAAADBQAAAAADIAAAAAACIAAAAAAAegAAAAAABQAAAAABBgAAAAACBQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: CAAAAAADCAAAAAADBAAAAAAACAAAAAAACAAAAAAACAAAAAADegAAAAAADAAAAAABDAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAACQAAAAAACQAAAAAABAAAAAAABAAAAAAAegAAAAAABgAAAAAACAAAAAADCAAAAAACBAAAAAAADAAAAAAABgAAAAADDAAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABgAAAAACCAAAAAABCAAAAAACCAAAAAAABgAAAAACCAAAAAADFQAAAAAADAAAAAACDAAAAAACDAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAABCAAAAAADCAAAAAABCAAAAAACCAAAAAABCAAAAAACCAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAADBgAAAAADBgAAAAAACAAAAAACCAAAAAAACAAAAAAABgAAAAABCAAAAAABCAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAADgAAAAAABgAAAAAACAAAAAADBgAAAAABCAAAAAACCAAAAAADCAAAAAACBgAAAAADCAAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAGAAAAAACDgAAAAAACAAAAAACCAAAAAADCAAAAAABCAAAAAACCAAAAAABCAAAAAACCAAAAAACegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADwAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAIAAAAAABBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADegAAAAAACAAAAAABCAAAAAADCAAAAAAACAAAAAACCAAAAAADegAAAAAAIAAAAAABBQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAFQAAAAAACAAAAAABCAAAAAAACAAAAAABCAAAAAAACAAAAAABBAAAAAAABQAAAAACBQAAAAACIAAAAAADIAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAABAAAAAAACAAAAAABCAAAAAABCAAAAAAACAAAAAACCAAAAAADegAAAAAABQAAAAACBQAAAAABIAAAAAACIAAAAAABegAAAAAAegAAAAAAAwAAAAAAegAAAAAACAAAAAACFQAAAAAACAAAAAABCAAAAAADCAAAAAAACAAAAAADegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAACBQAAAAAABAAAAAAABQAAAAABBgAAAAADBQAAAAABCAAAAAAAegAAAAAACAAAAAAACAAAAAACCAAAAAAACAAAAAADegAAAAAAIAAAAAABBQAAAAACBQAAAAACBQAAAAABBQAAAAAAFQAAAAAABQAAAAADBQAAAAACBQAAAAABCAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAIAAAAAABBQAAAAADBQAAAAADIAAAAAACIAAAAAAAFQAAAAAABQAAAAABBgAAAAACBQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 2,0: ind: 2,0 - tiles: BQAAAAAABQAAAAAAegAAAAAADAAAAAAADAAAAAADDAAAAAADegAAAAAADAAAAAADDAAAAAAADAAAAAACDAAAAAADDAAAAAABegAAAAAABQAAAAADBQAAAAAAegAAAAAABgAAAAAABQAAAAACegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAADAAAAAACDAAAAAADDAAAAAADDAAAAAACDQAAAAACBwAAAAAABQAAAAABBQAAAAAAegAAAAAABQAAAAACBQAAAAABegAAAAAADAAAAAAADAAAAAADDAAAAAABBAAAAAAADAAAAAACDAAAAAAADAAAAAACDAAAAAAADAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABegAAAAAADAAAAAABDAAAAAAADAAAAAABBAAAAAAADAAAAAABDAAAAAABDAAAAAAADAAAAAACDQAAAAABegAAAAAABQAAAAACBQAAAAAAegAAAAAABgAAAAADBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAADDAAAAAACBwAAAAAABQAAAAADBQAAAAACegAAAAAABgAAAAAABQAAAAABBQAAAAABegAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAegAAAAAADAAAAAACDQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAABBQAAAAADBAAAAAAADgAAAAAADgAAAAADDgAAAAABDgAAAAABDgAAAAACBAAAAAAADAAAAAAADAAAAAADDAAAAAAADAAAAAABAwAAAAAAegAAAAAABAAAAAAABAAAAAAABQAAAAABegAAAAAAegAAAAAADgAAAAACDgAAAAACDgAAAAAADgAAAAAAegAAAAAADAAAAAAADQAAAAADDQAAAAABDAAAAAACegAAAAAAegAAAAAABQAAAAADBgAAAAAABQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAADgAAAAADDgAAAAABegAAAAAADAAAAAADDAAAAAABDAAAAAABDAAAAAADDAAAAAADBAAAAAAABQAAAAADBQAAAAABBgAAAAADBQAAAAADBQAAAAADBQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAADAAAAAACDAAAAAACDAAAAAAABAAAAAAABQAAAAAABQAAAAACBQAAAAABBQAAAAABBQAAAAABBQAAAAAAegAAAAAAFAAAAAAAFAAAAAAAFAAAAAAADAAAAAACegAAAAAADAAAAAACDAAAAAAADAAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAADegAAAAAAFAAAAAAAFAAAAAAAFAAAAAAADAAAAAABegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAegAAAAAABQAAAAACBgAAAAAAegAAAAAADAAAAAADDAAAAAADDAAAAAAADAAAAAACegAAAAAAIAAAAAABDAAAAAADDAAAAAACDAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABQAAAAACBgAAAAACegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAIAAAAAACDAAAAAADDAAAAAACDAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABQAAAAAABQAAAAABBQAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIAAAAAABIAAAAAABDAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAA + tiles: BQAAAAAABQAAAAAAFQAAAAAADAAAAAAADAAAAAADDAAAAAADegAAAAAADAAAAAADDAAAAAAADAAAAAACDAAAAAADDAAAAAABFQAAAAAABQAAAAADBQAAAAAAegAAAAAABgAAAAAABQAAAAACegAAAAAABAAAAAAABAAAAAAAFQAAAAAAegAAAAAADAAAAAACDAAAAAADDAAAAAADDAAAAAACDQAAAAACBwAAAAAABQAAAAABBQAAAAAAegAAAAAABQAAAAACBQAAAAABegAAAAAADAAAAAAADAAAAAADDAAAAAABBAAAAAAADAAAAAACDAAAAAAADAAAAAACDAAAAAAADAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAABegAAAAAADAAAAAABDAAAAAAADAAAAAABBAAAAAAADAAAAAABDAAAAAABDAAAAAAADAAAAAACDQAAAAABFQAAAAAABQAAAAACBQAAAAAAegAAAAAABgAAAAADBQAAAAABegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADAAAAAADDAAAAAACBwAAAAAABQAAAAADBQAAAAACegAAAAAABgAAAAAABQAAAAABBQAAAAABegAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAFgAAAAAAFQAAAAAADAAAAAACDQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAABBQAAAAADBAAAAAAADgAAAAAADgAAAAADDgAAAAABDgAAAAABDgAAAAACBAAAAAAADAAAAAAADAAAAAADDAAAAAAADAAAAAABAwAAAAAAegAAAAAABAAAAAAABAAAAAAABQAAAAABegAAAAAAegAAAAAADgAAAAACDgAAAAACDgAAAAAADgAAAAAAFQAAAAAADAAAAAAADQAAAAADDQAAAAABDAAAAAACegAAAAAAegAAAAAABQAAAAADBgAAAAAABQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAADgAAAAADDgAAAAABegAAAAAADAAAAAADDAAAAAABDAAAAAABDAAAAAADDAAAAAADBAAAAAAABQAAAAADBQAAAAABBgAAAAADBQAAAAADBQAAAAADBQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAADAAAAAACDAAAAAACDAAAAAAABAAAAAAABQAAAAAABQAAAAACBQAAAAABBQAAAAABBQAAAAABBQAAAAAAegAAAAAAFAAAAAAAFAAAAAAAFAAAAAAADAAAAAACegAAAAAADAAAAAACDAAAAAAADAAAAAACegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAABQAAAAADBQAAAAADegAAAAAAFAAAAAAAFAAAAAAAFAAAAAAADAAAAAABegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAFQAAAAAABQAAAAACBgAAAAAAegAAAAAADAAAAAADDAAAAAADDAAAAAAADAAAAAACegAAAAAAIAAAAAABDAAAAAADDAAAAAACDAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAABQAAAAACBgAAAAACegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAIAAAAAACDAAAAAADDAAAAAACDAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAABQAAAAAABQAAAAABBQAAAAADAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIAAAAAABIAAAAAABDAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABQAAAAADBQAAAAADBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAA version: 6 2,-1: ind: 2,-1 - tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAADIgAAAAAGDgAAAAACIgAAAAAGegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAADIgAAAAAFDgAAAAAADgAAAAABDgAAAAADegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAABIgAAAAAEIgAAAAACIgAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAADAAAAAADDAAAAAADDAAAAAADDAAAAAAADAAAAAACegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAAACAAAAAADCAAAAAACegAAAAAADAAAAAABDQAAAAAADAAAAAACDQAAAAAADAAAAAABegAAAAAAAwAAAAAABQAAAAABBQAAAAAABQAAAAADegAAAAAACAAAAAABDQAAAAAADQAAAAADCAAAAAADBAAAAAAADAAAAAABDQAAAAABDAAAAAADDQAAAAAADAAAAAACegAAAAAAegAAAAAABQAAAAABBgAAAAACBQAAAAACBAAAAAAACAAAAAABCAAAAAADCAAAAAABCAAAAAADegAAAAAADAAAAAABDQAAAAADDAAAAAACDQAAAAADDAAAAAABAwAAAAAAegAAAAAABgAAAAACBQAAAAACBQAAAAABegAAAAAACAAAAAADDQAAAAACDQAAAAACCAAAAAAAegAAAAAADAAAAAACDAAAAAADDAAAAAADDAAAAAACDAAAAAABegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAADegAAAAAACAAAAAABCAAAAAAACAAAAAADCAAAAAACegAAAAAADAAAAAADDAAAAAACDAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAADBQAAAAACegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAEwAAAAAAEwAAAAAAEwAAAAAABgAAAAACBQAAAAABegAAAAAABQAAAAABBQAAAAAADAAAAAAAegAAAAAADAAAAAACDAAAAAACDAAAAAAADAAAAAAADQAAAAADBAAAAAAAIQAAAAAADQAAAAACIQAAAAAABQAAAAACBQAAAAAABAAAAAAABQAAAAADDQAAAAACDAAAAAAABAAAAAAADAAAAAACDQAAAAABDAAAAAABDAAAAAADDAAAAAADegAAAAAAEwAAAAAAEwAAAAAAEwAAAAAABgAAAAAABQAAAAAABAAAAAAABQAAAAABDQAAAAABDAAAAAABBAAAAAAADAAAAAABDQAAAAAADAAAAAAADAAAAAAADQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAADIgAAAAAGDgAAAAACIgAAAAAGIgAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAADIgAAAAAFDgAAAAAADgAAAAABDgAAAAADegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAABIgAAAAAEIgAAAAACIgAAAAADegAAAAAAFQAAAAAAAwAAAAAAegAAAAAABQAAAAABBQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACBQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAABegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAADAAAAAADDAAAAAADDAAAAAADDAAAAAAADAAAAAACegAAAAAAegAAAAAABAAAAAAAFQAAAAAAFQAAAAAAegAAAAAACAAAAAABCAAAAAAACAAAAAADCAAAAAACegAAAAAADAAAAAABDQAAAAAADAAAAAACDQAAAAAADAAAAAABegAAAAAAAwAAAAAABQAAAAABBQAAAAAABQAAAAADegAAAAAACAAAAAABDQAAAAAADQAAAAADCAAAAAADBAAAAAAADAAAAAABDQAAAAABDAAAAAADDQAAAAAADAAAAAACegAAAAAAegAAAAAABQAAAAABBgAAAAACBQAAAAACBAAAAAAACAAAAAABCAAAAAADCAAAAAABCAAAAAADegAAAAAADAAAAAABDQAAAAADDAAAAAACDQAAAAADDAAAAAABAwAAAAAAegAAAAAABgAAAAACBQAAAAACBQAAAAABFQAAAAAACAAAAAADDQAAAAACDQAAAAACCAAAAAAAegAAAAAADAAAAAACDAAAAAADDAAAAAADDAAAAAACDAAAAAABegAAAAAAegAAAAAABQAAAAABBQAAAAABBQAAAAADFQAAAAAACAAAAAABCAAAAAAACAAAAAADCAAAAAACegAAAAAADAAAAAADDAAAAAACDAAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAADBQAAAAACegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAAegAAAAAAEwAAAAAAEwAAAAAAEwAAAAAABgAAAAACBQAAAAABFQAAAAAABQAAAAABBQAAAAAADAAAAAAAFQAAAAAADAAAAAACDAAAAAACDAAAAAAADAAAAAAADQAAAAADBAAAAAAAIQAAAAAADQAAAAACIQAAAAAABQAAAAACBQAAAAAABAAAAAAABQAAAAADDQAAAAACDAAAAAAABAAAAAAADAAAAAACDQAAAAABDAAAAAABDAAAAAADDAAAAAADFQAAAAAAEwAAAAAAEwAAAAAAEwAAAAAABgAAAAAABQAAAAAABAAAAAAABQAAAAABDQAAAAABDAAAAAABFQAAAAAADAAAAAABDQAAAAAADAAAAAAADAAAAAAADQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 -4,-1: ind: -4,-1 @@ -150,55 +150,55 @@ entities: version: 6 1,2: ind: 1,2 - tiles: eQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAABwAAAAAABwAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAABwAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 0,2: ind: 0,2 - tiles: BQAAAAAABQAAAAABBQAAAAABBAAAAAAABQAAAAAABQAAAAADBQAAAAACBQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAABAAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAADDgAAAAACDgAAAAADDgAAAAADegAAAAAABgAAAAABBQAAAAABBgAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAADBgAAAAACDgAAAAAADgAAAAABegAAAAAABgAAAAADBQAAAAABBgAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAADDgAAAAACBgAAAAAABgAAAAACegAAAAAABgAAAAAABQAAAAABBgAAAAABegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAABgAAAAAADgAAAAABDgAAAAAAegAAAAAABQAAAAADBgAAAAABBQAAAAADBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAACDgAAAAABDgAAAAAADgAAAAABegAAAAAABQAAAAABBgAAAAADBQAAAAADegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABgAAAAABBQAAAAABBgAAAAABegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBgAAAAABBQAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBgAAAAACBQAAAAABegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAAAABegAAAAAAegAAAAAAegAAAAAAAwAAAAAABgAAAAADBQAAAAACBgAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAACBQAAAAADBgAAAAABegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAABBQAAAAADBgAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: BQAAAAAABQAAAAABBQAAAAABBAAAAAAABQAAAAAABQAAAAADBQAAAAACBQAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAABAAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAABAAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAADDgAAAAACDgAAAAADDgAAAAADegAAAAAABgAAAAABBQAAAAABBgAAAAACegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAADBgAAAAACDgAAAAAADgAAAAABFQAAAAAABgAAAAADBQAAAAABBgAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAADDgAAAAACBgAAAAAABgAAAAACFQAAAAAABgAAAAAABQAAAAABBgAAAAABFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAABgAAAAAADgAAAAABDgAAAAAAFQAAAAAABQAAAAADBgAAAAABBQAAAAADegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAACDgAAAAABDgAAAAAADgAAAAABegAAAAAABQAAAAABBgAAAAADBQAAAAADBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAABgAAAAABBQAAAAABBgAAAAABFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBgAAAAABBQAAAAACBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACBgAAAAACBQAAAAABegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAAAABegAAAAAAegAAAAAAegAAAAAAAwAAAAAABgAAAAADBQAAAAACBgAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAACBQAAAAADBgAAAAABFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABgAAAAABBQAAAAADBgAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 -1,2: ind: -1,2 - tiles: FQAAAAAABQAAAAADBgAAAAACBQAAAAABBgAAAAADBgAAAAACBQAAAAADegAAAAAABQAAAAAABQAAAAAABQAAAAACBQAAAAACBQAAAAAABQAAAAAABQAAAAAABQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAADBQAAAAADBQAAAAADegAAAAAABQAAAAADBQAAAAADBgAAAAACegAAAAAABgAAAAACBQAAAAAABgAAAAAAegAAAAAADgAAAAACDgAAAAACDgAAAAADBgAAAAADBQAAAAACBQAAAAACBQAAAAACBQAAAAACBQAAAAABBgAAAAADBQAAAAADBAAAAAAABgAAAAACBQAAAAABBgAAAAABegAAAAAABgAAAAADDgAAAAADDgAAAAAABQAAAAABBgAAAAACBQAAAAACBQAAAAABBQAAAAADBgAAAAABBQAAAAADBQAAAAACegAAAAAABQAAAAACBgAAAAADBQAAAAAABAAAAAAADgAAAAACBgAAAAABBgAAAAADBgAAAAAABQAAAAADBQAAAAAABQAAAAABBQAAAAACBQAAAAACBgAAAAADBQAAAAACBAAAAAAABgAAAAADBQAAAAACBgAAAAADegAAAAAABgAAAAADDgAAAAAADgAAAAAABQAAAAABBQAAAAACegAAAAAABQAAAAABBQAAAAADBQAAAAADBQAAAAABBgAAAAADegAAAAAABgAAAAADBQAAAAAABgAAAAACegAAAAAADgAAAAAADgAAAAAADgAAAAABAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAACegAAAAAABQAAAAACBgAAAAADBQAAAAAABQAAAAAABQAAAAABBgAAAAAABQAAAAAABQAAAAAAegAAAAAAEwAAAAAAEwAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAABBAAAAAAABQAAAAAABQAAAAABBgAAAAAABQAAAAADBgAAAAADBQAAAAADBQAAAAABBQAAAAACBAAAAAAADgAAAAABDgAAAAADegAAAAAABgAAAAAACAAAAAAACAAAAAADegAAAAAABQAAAAADBQAAAAABBQAAAAABBQAAAAABBQAAAAACBQAAAAAABQAAAAACBQAAAAADegAAAAAAEgAAAAAAEgAAAAAACAAAAAADBgAAAAADCAAAAAADCAAAAAACegAAAAAABQAAAAADBgAAAAADBQAAAAABBQAAAAABBgAAAAACBQAAAAAABQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAACAAAAAABBgAAAAACCAAAAAACCAAAAAADegAAAAAABQAAAAADBQAAAAADBgAAAAACegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAACBgAAAAABBgAAAAACBgAAAAABegAAAAAAFQAAAAAAegAAAAAABQAAAAACegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAABQAAAAABegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: FQAAAAAABQAAAAADBgAAAAACBQAAAAABBgAAAAADBgAAAAACBQAAAAADegAAAAAABQAAAAAABQAAAAAABQAAAAACBQAAAAACBQAAAAAABQAAAAAABQAAAAAABQAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAADBQAAAAADBQAAAAADegAAAAAABQAAAAADBQAAAAADBgAAAAACFQAAAAAABgAAAAACBQAAAAAABgAAAAAAegAAAAAADgAAAAACDgAAAAACDgAAAAADBgAAAAADBQAAAAACBQAAAAACBQAAAAACBQAAAAACBQAAAAABBgAAAAADBQAAAAADBAAAAAAABgAAAAACBQAAAAABBgAAAAABFQAAAAAABgAAAAADDgAAAAADDgAAAAAABQAAAAABBgAAAAACBQAAAAACBQAAAAABBQAAAAADBgAAAAABBQAAAAADBQAAAAACFQAAAAAABQAAAAACBgAAAAADBQAAAAAABAAAAAAADgAAAAACBgAAAAABBgAAAAADBgAAAAAABQAAAAADBQAAAAAABQAAAAABBQAAAAACBQAAAAACBgAAAAADBQAAAAACBAAAAAAABgAAAAADBQAAAAACBgAAAAADFQAAAAAABgAAAAADDgAAAAAADgAAAAAABQAAAAABBQAAAAACegAAAAAABQAAAAABBQAAAAADBQAAAAADBQAAAAABBgAAAAADegAAAAAABgAAAAADBQAAAAAABgAAAAACegAAAAAADgAAAAAADgAAAAAADgAAAAABAwAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAACegAAAAAABQAAAAACBgAAAAADBQAAAAAABQAAAAAABQAAAAABBgAAAAAABQAAAAAABQAAAAAAegAAAAAAEwAAAAAAEwAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAABBAAAAAAABQAAAAAABQAAAAABBgAAAAAABQAAAAADBgAAAAADBQAAAAADBQAAAAABBQAAAAACBAAAAAAADgAAAAABDgAAAAADegAAAAAABgAAAAAACAAAAAAACAAAAAADegAAAAAABQAAAAADBQAAAAABBQAAAAABBQAAAAABBQAAAAACBQAAAAAABQAAAAACBQAAAAADegAAAAAAEgAAAAAAEgAAAAAACAAAAAADBgAAAAADCAAAAAADCAAAAAACegAAAAAABQAAAAADBgAAAAADBQAAAAABBQAAAAABBgAAAAACBQAAAAAABQAAAAAABQAAAAABegAAAAAAegAAAAAAegAAAAAACAAAAAABBgAAAAACCAAAAAACCAAAAAADegAAAAAABQAAAAADBQAAAAADBgAAAAACegAAAAAAFQAAAAAABAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAACBgAAAAABBgAAAAACBgAAAAABegAAAAAAFQAAAAAAegAAAAAABQAAAAACFQAAAAAAAQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAABQAAAAABFQAAAAAAAQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 -2,2: ind: -2,2 - tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIwAAAAABIwAAAAABIwAAAAADIwAAAAACIwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAIgAAAAACIgAAAAAFIgAAAAAGIgAAAAAGIgAAAAAADgAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAJAAAAAAAJAAAAAAAJAAAAAAAegAAAAAAegAAAAAADgAAAAABDgAAAAABDgAAAAADIgAAAAAEDgAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAJAAAAAAAJAAAAAAAJAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIwAAAAACIwAAAAACIwAAAAABegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIwAAAAADegAAAAAAIwAAAAACIwAAAAACegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAAADgAAAAAADgAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAAADgAAAAAADgAAAAADDgAAAAADegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAADDgAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIwAAAAABIwAAAAABIwAAAAADIwAAAAACIwAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAIgAAAAACIgAAAAAFIgAAAAAGIgAAAAAGIgAAAAAADgAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAJAAAAAAAJAAAAAAAJAAAAAAAegAAAAAAFQAAAAAADgAAAAABDgAAAAABDgAAAAADIgAAAAAEDgAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAJAAAAAAAJAAAAAAAJAAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIwAAAAACIwAAAAACIwAAAAABegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAIwAAAAADegAAAAAAIwAAAAACIwAAAAACegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAABAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAAADgAAAAAADgAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAAADgAAAAAADgAAAAADDgAAAAADegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACDgAAAAADDgAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 -3,1: ind: -3,1 - tiles: AAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAACAAAAAAACAAAAAADCAAAAAAACAAAAAABBAAAAAAACAAAAAADCAAAAAABCAAAAAAAegAAAAAACAAAAAADAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAABCAAAAAAACAAAAAACBAAAAAAACAAAAAABBgAAAAABCAAAAAACegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAAABgAAAAAACAAAAAADCAAAAAADAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAABCAAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAAACAAAAAACCAAAAAABAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAACBgAAAAABCAAAAAAABAAAAAAABgAAAAAACAAAAAAACAAAAAACCAAAAAACAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAADCAAAAAAAegAAAAAACAAAAAACBgAAAAAACAAAAAACCAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABBgAAAAABCAAAAAACegAAAAAACAAAAAAACAAAAAABCAAAAAABCAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAACAAAAAADCAAAAAADCAAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAACegAAAAAACAAAAAAACAAAAAAAegAAAAAACAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAADegAAAAAACAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAADCAAAAAADCAAAAAACCAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAADCAAAAAABCAAAAAABCAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAACCAAAAAAACAAAAAACCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAACAAAAAADCAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAQAAAAACAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: AAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAACAAAAAAACAAAAAADCAAAAAAACAAAAAABBAAAAAAACAAAAAADCAAAAAABCAAAAAAAegAAAAAACAAAAAADAAAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAABCAAAAAAACAAAAAACBAAAAAAACAAAAAABBgAAAAABCAAAAAACegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAAFQAAAAAAegAAAAAACAAAAAABCAAAAAAABgAAAAAACAAAAAADCAAAAAADAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAABCAAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAAACAAAAAACCAAAAAABAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAACBgAAAAABCAAAAAAABAAAAAAABgAAAAAACAAAAAAACAAAAAACCAAAAAACAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAADCAAAAAAAFQAAAAAACAAAAAACBgAAAAAACAAAAAACCAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABBgAAAAABCAAAAAACegAAAAAACAAAAAAACAAAAAABCAAAAAABCAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAACAAAAAADCAAAAAADCAAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAACegAAAAAACAAAAAAACAAAAAAAegAAAAAACAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAADegAAAAAACAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAADCAAAAAADCAAAAAACCAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAADCAAAAAABCAAAAAABCAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAACCAAAAAAACAAAAAACCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAACAAAAAADCAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAAQAAAAACAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 -3,0: ind: -3,0 - tiles: AAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAKAAAAAADKAAAAAAAKAAAAAADKAAAAAABAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAABwAAAAAAegAAAAAABAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAKAAAAAAAKAAAAAADKAAAAAABKAAAAAADAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAFQAAAAAAegAAAAAABAAAAAAABAAAAAAABAAAAAAABAAAAAAABAAAAAAAegAAAAAAKAAAAAABKAAAAAADJwAAAAACJwAAAAACAAAAAAAAAAAAAAAAegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAJwAAAAABJwAAAAAAJwAAAAAAegAAAAAAAAAAAAAAeQAAAAAAegAAAAAAAQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAegAAAAAAAQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAACAAAAAADAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAQAAAAACAQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAAABgAAAAAABgAAAAABAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAACCAAAAAADCAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAACBgAAAAADBgAAAAADBgAAAAACAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAACCAAAAAAACAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAADAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAACAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAADCAAAAAABCAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAACAAAAAABCAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAAACAAAAAABCAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAACCAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAAACAAAAAACCAAAAAADegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACAAAAAAA + tiles: AAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAABAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAKAAAAAADKAAAAAAAKAAAAAADKAAAAAABAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAABwAAAAAAegAAAAAABAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAKAAAAAAAKAAAAAADKAAAAAABKAAAAAADAAAAAAAAAAAAAAAAeQAAAAAAFQAAAAAAFQAAAAAAegAAAAAABAAAAAAABAAAAAAABAAAAAAABAAAAAAABAAAAAAAegAAAAAAKAAAAAABKAAAAAADJwAAAAACJwAAAAACAAAAAAAAAAAAAAAAegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAJwAAAAABJwAAAAAAJwAAAAAAegAAAAAAAAAAAAAAeQAAAAAAFQAAAAAAAQAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAFQAAAAAAAQAAAAABegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAACAAAAAADAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAQAAAAACAQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAAABgAAAAAABgAAAAABAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAACCAAAAAADCAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAACBgAAAAADBgAAAAADBgAAAAACAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAACCAAAAAAACAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAADAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAADCAAAAAACAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAAACAAAAAADCAAAAAABCAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAACAAAAAABCAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAAACAAAAAABCAAAAAADegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAACCAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAACAAAAAABCAAAAAAACAAAAAACCAAAAAADegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAACAAAAAAA version: 6 -3,-1: ind: -3,-1 - tiles: eQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAABAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAADAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAABAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAACAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAAAegAAAAAADgAAAAADAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAKAAAAAADKAAAAAAAKAAAAAADKAAAAAAD + tiles: eQAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAAAAQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAAQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAABAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAADgAAAAADAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAADgAAAAABAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAADgAAAAACAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAAQAAAAAAegAAAAAADgAAAAADAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAABwAAAAAAegAAAAAABwAAAAAAFQAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAFQAAAAAABAAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAKAAAAAADKAAAAAAAKAAAAAADKAAAAAAD version: 6 2,1: ind: 2,1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAJQAAAAAAJQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAFQAAAAAABwAAAAAAFQAAAAAAJQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAABwAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAFQAAAAAABwAAAAAAFQAAAAAAJQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAABwAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAJQAAAAAAJQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAFQAAAAAABwAAAAAAFQAAAAAAJQAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAABwAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAFQAAAAAABwAAAAAAFQAAAAAAJQAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAABwAAAAAA version: 6 3,0: ind: 3,0 - tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAACgAAAAAACgAAAAAAegAAAAAABQAAAAAABQAAAAADBQAAAAACBQAAAAACegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAADBwAAAAAABQAAAAABDQAAAAABBQAAAAAABQAAAAACegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAABBQAAAAACDQAAAAACBQAAAAABBwAAAAAABQAAAAADBQAAAAABDQAAAAADBQAAAAABegAAAAAAeQAAAAAAeQAAAAAADAAAAAADDwAAAAABBQAAAAAABwAAAAAABQAAAAADDQAAAAAABQAAAAABBQAAAAADegAAAAAABQAAAAACBQAAAAABBQAAAAACBQAAAAABegAAAAAAeQAAAAAAeQAAAAAADAAAAAADDwAAAAABBQAAAAABBwAAAAAABQAAAAABBQAAAAAABQAAAAABBQAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAACegAAAAAABQAAAAACBQAAAAAABQAAAAABBQAAAAADegAAAAAAeQAAAAAAeQAAAAAADAAAAAACDAAAAAAADAAAAAABegAAAAAAeQAAAAAAegAAAAAADQAAAAAABQAAAAAABwAAAAAABQAAAAABDQAAAAAADQAAAAABBQAAAAACegAAAAAAeQAAAAAAeQAAAAAADAAAAAADDAAAAAADDAAAAAADegAAAAAAeQAAAAAAegAAAAAABQAAAAACDQAAAAACegAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAACegAAAAAAeQAAAAAAeQAAAAAADAAAAAAADAAAAAAADAAAAAACegAAAAAAeQAAAAAAegAAAAAABQAAAAACBQAAAAACBQAAAAADegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAADgAAAAAADgAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAABDQAAAAADBQAAAAAAegAAAAAAAgAAAAAAAgAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABQAAAAADBQAAAAABBQAAAAAABQAAAAACBQAAAAAABwAAAAAAAgAAAAAAAgAAAAAAegAAAAAAeQAAAAAAeQAAAAAA + tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAFQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAACgAAAAAACgAAAAAAFQAAAAAABQAAAAAABQAAAAADBQAAAAACBQAAAAACegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAADBwAAAAAABQAAAAABDQAAAAABBQAAAAAABQAAAAACFQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAABQAAAAABBQAAAAACDQAAAAACBQAAAAABBwAAAAAABQAAAAADBQAAAAABDQAAAAADBQAAAAABFQAAAAAAeQAAAAAAeQAAAAAADAAAAAADDwAAAAABBQAAAAAABwAAAAAABQAAAAADDQAAAAAABQAAAAABBQAAAAADegAAAAAABQAAAAACBQAAAAABBQAAAAACBQAAAAABegAAAAAAeQAAAAAAeQAAAAAADAAAAAADDwAAAAABBQAAAAABBwAAAAAABQAAAAABBQAAAAAABQAAAAABBQAAAAAAegAAAAAAFQAAAAAAFQAAAAAABwAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAFQAAAAAABAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAACegAAAAAABQAAAAACBQAAAAAABQAAAAABBQAAAAADegAAAAAAeQAAAAAAeQAAAAAADAAAAAACDAAAAAAADAAAAAABFQAAAAAAeQAAAAAAFQAAAAAADQAAAAAABQAAAAAABwAAAAAABQAAAAABDQAAAAAADQAAAAABBQAAAAACFQAAAAAAeQAAAAAAeQAAAAAADAAAAAADDAAAAAADDAAAAAADFQAAAAAAeQAAAAAAFQAAAAAABQAAAAACDQAAAAACegAAAAAAegAAAAAABQAAAAAABQAAAAAABQAAAAACegAAAAAAeQAAAAAAeQAAAAAADAAAAAAADAAAAAAADAAAAAACegAAAAAAeQAAAAAAFQAAAAAABQAAAAACBQAAAAACBQAAAAADegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAADgAAAAAADgAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAABDQAAAAADBQAAAAAAegAAAAAAAgAAAAAAAgAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAABQAAAAADBQAAAAABBQAAAAAABQAAAAACBQAAAAAABwAAAAAAAgAAAAAAAgAAAAAAegAAAAAAeQAAAAAAeQAAAAAA version: 6 3,1: ind: 3,1 - tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACDQAAAAAABQAAAAACBQAAAAACBQAAAAACegAAAAAAAgAAAAAAAgAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAADBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAAAAJQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAAAAJgAAAAAAJgAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAACDQAAAAAABQAAAAACBQAAAAACBQAAAAACegAAAAAAAgAAAAAAAgAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABQAAAAADBQAAAAABBQAAAAADBQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAAAAJQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAAAAJgAAAAAAJgAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 3,-1: ind: 3,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAAAAAEwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAABIQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAEwAAAAAAEwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAJAAAAAAAJAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAA + tiles: BAAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAAAAAEwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABAAAAAAABAAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAABIQAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAEwAAAAAAEwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAJAAAAAAAJAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAA version: 6 -3,-2: ind: -3,-2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAACAQAAAAACegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAEQAAAAACEQAAAAADEQAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAEQAAAAABegAAAAAAEQAAAAABEAAAAAAABAAAAAAAEAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAEQAAAAADEQAAAAADEQAAAAAAegAAAAAABAAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAAQAAAAACAQAAAAACFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAEQAAAAACEQAAAAADEQAAAAAAegAAAAAABAAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAEQAAAAABegAAAAAAEQAAAAABEAAAAAAABAAAAAAAEAAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAEQAAAAADEQAAAAADEQAAAAAAegAAAAAABAAAAAAAegAAAAAABwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 -4,-2: ind: -4,-2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAFQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAFQAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAFQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAA version: 6 -4,0: ind: -4,0 @@ -206,15 +206,15 @@ entities: version: 6 -3,2: ind: -3,2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAIgAAAAADegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAIgAAAAABDgAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAIgAAAAADegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAIgAAAAABDgAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 -1,3: ind: -1,3 - tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAegAAAAAAAQAAAAACegAAAAAAegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAAQAAAAACAQAAAAABegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: egAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAegAAAAAAAQAAAAACegAAAAAAegAAAAAAAQAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAFQAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAAQAAAAACAQAAAAABegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 -2,3: ind: -2,3 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 2,2: ind: 2,2 @@ -234,7 +234,7 @@ entities: version: 6 2,-2: ind: 2,-2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAAwAAAAAAegAAAAAAeQAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAFQAAAAAAFQAAAAAA version: 6 0,3: ind: 0,3 @@ -244,6 +244,10 @@ entities: ind: -3,-3 tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 + 3,-2: + ind: 3,-2 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAegAAAAAAeQAAAAAAeQAAAAAAegAAAAAAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 - type: Broadphase - type: Physics bodyStatus: InAir @@ -302,7 +306,7 @@ entities: decals: 525: 24,16 527: 24,14 - 746: 24,15 + 743: 24,15 - node: color: '#FFFFFFFF' id: BotRightGreyscale @@ -405,7 +409,7 @@ entities: color: '#FFFFFFFF' id: BrickTileSteelInnerNw decals: - 748: 7,25 + 745: 7,25 - node: color: '#FFFFFFFF' id: BrickTileSteelLineN @@ -422,7 +426,7 @@ entities: 68: 5,22 69: 5,23 70: 5,24 - 747: 7,26 + 744: 7,26 - node: color: '#FFFFFFFF' id: Caution @@ -477,7 +481,7 @@ entities: 572: -36,21 573: -31,20 574: -31,19 - 783: -31,22 + 780: -31,22 - node: color: '#8C347F96' id: CheckerNESW @@ -495,7 +499,7 @@ entities: 506: 24,16 507: 24,17 526: 24,14 - 745: 24,15 + 742: 24,15 - node: color: '#D381C996' id: CheckerNESW @@ -526,19 +530,19 @@ entities: 650: 37,-1 651: 37,-2 652: 37,-3 - 656: 41,-3 - 657: 40,-3 - 658: 39,-3 - 679: 50,11 - 680: 50,12 - 681: 50,13 - 682: 48,11 - 785: 41,0 - 786: 40,0 - 787: 39,0 - 788: 41,1 - 789: 40,1 - 791: 39,1 + 653: 41,-3 + 654: 40,-3 + 655: 39,-3 + 676: 50,11 + 677: 50,12 + 678: 50,13 + 679: 48,11 + 782: 41,0 + 783: 40,0 + 784: 39,0 + 785: 41,1 + 786: 40,1 + 787: 39,1 - node: color: '#EFB34196' id: CheckerNESW @@ -587,7 +591,7 @@ entities: 252: 16,29 253: 18,29 254: 20,29 - 763: 19,29 + 760: 19,29 - node: color: '#A4610696' id: CheckerNWSE @@ -617,28 +621,28 @@ entities: color: '#DE3A3A96' id: CheckerNWSE decals: - 673: 44,9 - 674: 44,10 - 686: 41,-5 - 687: 41,-6 - 688: 41,-7 - 689: 41,-9 - 690: 41,-10 - 691: 45,-8 - 692: 45,-9 - 693: 45,-10 - 694: 45,-6 - 695: 44,-6 - 696: 43,-6 - 697: 43,-5 + 670: 44,9 + 671: 44,10 + 683: 41,-5 + 684: 41,-6 + 685: 41,-7 + 686: 41,-9 + 687: 41,-10 + 688: 45,-8 + 689: 45,-9 + 690: 45,-10 + 691: 45,-6 + 692: 44,-6 + 693: 43,-6 + 694: 43,-5 - node: color: '#DE3A3ACC' id: CheckerNWSE decals: - 703: 39,-5 - 704: 39,-6 - 705: 39,-7 - 706: 39,-9 + 700: 39,-5 + 701: 39,-6 + 702: 39,-7 + 703: 39,-9 - node: color: '#EFB34196' id: CheckerNWSE @@ -654,12 +658,12 @@ entities: color: '#835432FF' id: Delivery decals: - 762: -25,28 + 759: -25,28 - node: color: '#A4610696' id: Delivery decals: - 784: -22,26 + 781: -22,26 - node: color: '#FFFFFFFF' id: Delivery @@ -668,34 +672,34 @@ entities: 357: -28,-15 358: -27,-15 359: -26,-15 - 780: -37,-23 - 781: -40,-20 - 782: -37,-17 + 777: -37,-23 + 778: -40,-20 + 779: -37,-17 - node: cleanable: True color: '#FFFFFFFF' id: Delivery decals: - 761: 20,29 + 758: 20,29 - node: color: '#FFFFFFFF' id: DeliveryGreyscale decals: 5: 20,21 - 766: -40,-22 - 767: -40,-21 - 768: -40,-19 - 769: -40,-18 - 770: -39,-17 - 771: -38,-17 - 772: -36,-17 - 773: -35,-17 - 774: -34,-18 - 775: -34,-22 - 776: -35,-23 - 777: -36,-23 - 778: -38,-23 - 779: -39,-23 + 763: -40,-22 + 764: -40,-21 + 765: -40,-19 + 766: -40,-18 + 767: -39,-17 + 768: -38,-17 + 769: -36,-17 + 770: -35,-17 + 771: -34,-18 + 772: -34,-22 + 773: -35,-23 + 774: -36,-23 + 775: -38,-23 + 776: -39,-23 - node: color: '#3EB38896' id: FullTileOverlayGreyscale @@ -714,10 +718,10 @@ entities: 205: -37,26 206: -37,27 207: -37,28 - 730: -38,24 - 731: -39,24 - 732: -41,20 - 733: -41,19 + 727: -38,24 + 728: -39,24 + 729: -41,20 + 730: -41,19 - node: color: '#8C347F96' id: FullTileOverlayGreyscale @@ -792,7 +796,7 @@ entities: 484: -23,12 485: -22,12 575: -21,12 - 728: -39,23 + 725: -39,23 - node: color: '#8C347F96' id: HalfTileOverlayGreyscale @@ -860,10 +864,10 @@ entities: 640: 39,3 641: 37,3 642: 36,3 - 664: 45,-2 - 665: 46,-2 - 666: 47,-2 - 671: 43,8 + 661: 45,-2 + 662: 46,-2 + 663: 47,-2 + 668: 43,8 - node: color: '#334E6DC8' id: HalfTileOverlayGreyscale180 @@ -877,8 +881,8 @@ entities: 495: 6,-2 496: 7,-2 591: 11,10 - 714: 2,-10 - 715: 5,-10 + 711: 2,-10 + 712: 5,-10 - node: color: '#52B4E996' id: HalfTileOverlayGreyscale180 @@ -893,7 +897,7 @@ entities: 194: -41,13 482: -22,10 483: -21,10 - 737: -39,19 + 734: -39,19 - node: color: '#8C347F96' id: HalfTileOverlayGreyscale180 @@ -922,8 +926,8 @@ entities: 560: 28,-1 561: 17,-2 562: 16,-2 - 756: 8,28 - 757: 9,28 + 753: 8,28 + 754: 9,28 - node: color: '#A4610696' id: HalfTileOverlayGreyscale180 @@ -952,13 +956,13 @@ entities: 630: 57,10 631: 58,10 632: 59,10 - 659: 45,-4 - 660: 46,-4 - 661: 47,-4 - 662: 48,-4 - 663: 49,-4 - 742: 35,8 - 743: 34,8 + 656: 45,-4 + 657: 46,-4 + 658: 47,-4 + 659: 48,-4 + 660: 49,-4 + 739: 35,8 + 740: 34,8 - node: color: '#EFB34196' id: HalfTileOverlayGreyscale180 @@ -990,11 +994,11 @@ entities: 590: 12,8 592: 11,15 593: 11,16 - 710: 12,-19 - 711: 12,-17 - 712: 12,-15 - 713: 12,-13 - 718: 12,-20 + 707: 12,-19 + 708: 12,-17 + 709: 12,-15 + 710: 12,-13 + 715: 12,-20 - node: color: '#52B4E996' id: HalfTileOverlayGreyscale270 @@ -1023,9 +1027,9 @@ entities: 475: -25,3 476: -25,4 569: -19,29 - 734: -40,22 - 735: -40,21 - 736: -40,20 + 731: -40,22 + 732: -40,21 + 733: -40,20 - node: color: '#9FED5896' id: HalfTileOverlayGreyscale270 @@ -1036,9 +1040,9 @@ entities: 426: 11,18 427: 11,22 428: 11,24 - 707: 31,-5 - 708: 31,-3 - 709: 31,-4 + 704: 31,-5 + 705: 31,-3 + 706: 31,-4 - node: color: '#A4610696' id: HalfTileOverlayGreyscale270 @@ -1080,21 +1084,21 @@ entities: 645: 35,-1 646: 35,-2 647: 35,-3 - 667: 42,4 - 668: 42,5 - 669: 42,6 - 670: 42,7 - 721: 48,9 - 722: 48,8 + 664: 42,4 + 665: 42,5 + 666: 42,6 + 667: 42,7 + 718: 48,9 + 719: 48,8 - node: color: '#DE3A3ACC' id: HalfTileOverlayGreyscale270 decals: - 698: 36,-5 - 699: 36,-6 - 700: 36,-7 - 701: 36,-8 - 702: 36,-9 + 695: 36,-5 + 696: 36,-6 + 697: 36,-7 + 698: 36,-8 + 699: 36,-9 - node: color: '#EFB34196' id: HalfTileOverlayGreyscale270 @@ -1159,9 +1163,9 @@ entities: 215: -32,28 216: -32,27 217: -32,26 - 725: -38,20 - 726: -38,21 - 727: -38,22 + 722: -38,20 + 723: -38,21 + 724: -38,22 - node: color: '#9FED5896' id: HalfTileOverlayGreyscale90 @@ -1221,13 +1225,13 @@ entities: 294: 34,6 295: 38,14 633: 60,11 - 719: 33,2 - 720: 33,3 - 723: 50,9 - 724: 50,8 - 740: 37,11 - 741: 37,10 - 744: 37,9 + 716: 33,2 + 717: 33,3 + 720: 50,9 + 721: 50,8 + 737: 37,11 + 738: 37,10 + 741: 37,9 - node: angle: -1.5707963267948966 rad color: '#FFFFFFFF' @@ -1238,10 +1242,10 @@ entities: color: '#DE3A3A96' id: MonoOverlay decals: - 675: 44,12 - 676: 44,13 - 677: 45,14 - 678: 46,14 + 672: 44,12 + 673: 44,13 + 674: 45,14 + 675: 46,14 - node: color: '#334E6DC8' id: QuarterTileOverlayGreyscale @@ -1262,7 +1266,7 @@ entities: id: QuarterTileOverlayGreyscale decals: 619: 54,9 - 685: 42,3 + 682: 42,3 - node: color: '#FA7500CC' id: QuarterTileOverlayGreyscale @@ -1273,7 +1277,7 @@ entities: id: QuarterTileOverlayGreyscale180 decals: 455: 1,-2 - 716: 4,-10 + 713: 4,-10 - node: color: '#52B4E996' id: QuarterTileOverlayGreyscale180 @@ -1288,7 +1292,7 @@ entities: color: '#334E6DC8' id: QuarterTileOverlayGreyscale270 decals: - 717: 3,-10 + 714: 3,-10 - node: color: '#52B4E996' id: QuarterTileOverlayGreyscale270 @@ -1303,8 +1307,8 @@ entities: color: '#9FED5896' id: QuarterTileOverlayGreyscale270 decals: - 758: 10,28 - 759: 7,29 + 755: 10,28 + 756: 7,29 - node: color: '#DE3A3A96' id: QuarterTileOverlayGreyscale270 @@ -1375,7 +1379,7 @@ entities: 219: -34,29 479: -25,5 480: -24,12 - 738: -40,23 + 735: -40,23 - node: color: '#9FED5896' id: ThreeQuarterTileOverlayGreyscale @@ -1388,7 +1392,7 @@ entities: 607: 52,9 617: 53,17 648: 35,3 - 672: 42,8 + 669: 42,8 - node: color: '#FA7500CC' id: ThreeQuarterTileOverlayGreyscale @@ -1429,7 +1433,7 @@ entities: 201: -42,13 481: -24,11 487: -23,10 - 739: -40,19 + 736: -40,19 - node: color: '#8C347F96' id: ThreeQuarterTileOverlayGreyscale270 @@ -1439,7 +1443,7 @@ entities: color: '#9FED5896' id: ThreeQuarterTileOverlayGreyscale270 decals: - 755: 7,28 + 752: 7,28 - node: color: '#D381C996' id: ThreeQuarterTileOverlayGreyscale270 @@ -1472,7 +1476,7 @@ entities: decals: 146: -28,28 220: -32,29 - 729: -38,23 + 726: -38,23 - node: color: '#9FED5896' id: ThreeQuarterTileOverlayGreyscale90 @@ -1487,7 +1491,7 @@ entities: color: '#FFFFFFFF' id: WarnBox decals: - 752: -43,-14 + 749: -43,-14 - node: color: '#FFFFFFFF' id: WarnCornerNE @@ -1498,7 +1502,7 @@ entities: id: WarnCornerNW decals: 125: -21,37 - 750: -37,-13 + 747: -37,-13 - node: color: '#FFFFFFFF' id: WarnCornerSE @@ -1506,13 +1510,13 @@ entities: 23: -45,-12 31: -11,45 123: -19,36 - 749: -36,-14 + 746: -36,-14 - node: color: '#FFFFFFFF' id: WarnCornerSW decals: 122: -21,36 - 753: -42,-24 + 750: -42,-24 - node: color: '#FFFFFFFF' id: WarnCornerSmallNE @@ -1533,6 +1537,7 @@ entities: id: WarnCornerSmallSW decals: 13: -36,-19 + 790: 47,-15 - node: color: '#FFFFFFFF' id: WarnEndE @@ -1544,27 +1549,29 @@ entities: id: WarnEndW decals: 601: 4,-4 - 751: -35,-7 + 748: -35,-7 - node: color: '#FFFFFFFF' id: WarnLineE decals: 17: -38,-20 32: -11,46 - 764: -7,45 - 765: -7,46 + 761: -7,45 + 762: -7,46 - node: color: '#FFFFFFFF' id: WarnLineN decals: 19: -37,-19 124: -20,36 - 754: -41,-24 + 751: -41,-24 + 789: 46,-15 - node: color: '#FFFFFFFF' id: WarnLineS decals: 18: -36,-20 + 788: 47,-16 - node: color: '#FFFFFFFF' id: WarnLineW @@ -1636,8 +1643,8 @@ entities: 116: 38,5 117: 37,5 118: 36,5 - 683: 49,13 - 684: 48,13 + 680: 49,13 + 681: 48,13 - node: color: '#FFFFFFFF' id: WoodTrimThinLineS @@ -1671,7 +1678,7 @@ entities: color: '#B02E26FF' id: splatter decals: - 760: -0.9152527,40.04415 + 757: -0.9152527,40.04415 - type: GridAtmosphere version: 2 data: @@ -1714,8 +1721,8 @@ entities: 1: 1 0: 63244 2,0: - 0: 17 - 2: 20078 + 0: 529 + 2: 19532 2,1: 2: 22054 2,2: @@ -1724,8 +1731,8 @@ entities: 2,3: 0: 48127 2,-1: - 0: 4096 - 2: 28230 + 0: 4608 + 2: 19526 2,4: 0: 63675 3,1: @@ -2036,22 +2043,26 @@ entities: 8,-1: 0: 65459 4,8: - 2: 52431 + 2: 3279 + 0: 49152 5,5: 0: 65535 5,6: - 0: 63231 + 0: 63487 5,7: 0: 26367 5,8: - 0: 102 - 2: 65280 + 0: 30566 + 2: 34816 6,5: 0: 1831 + 6,7: + 2: 4096 + 6,8: + 2: 3 0,-7: 2: 256 - 4: 16384 - 0: 32768 + 0: 49152 0,-6: 0: 3871 -1,-6: @@ -2102,8 +2113,7 @@ entities: -3,-7: 2: 3328 -3,-6: - 0: 4087 - 4: 8 + 0: 4095 -2,-6: 0: 28528 -2,-7: @@ -2136,8 +2146,7 @@ entities: -7,-6: 0: 65520 -7,-5: - 0: 65279 - 4: 256 + 0: 65535 -7,-4: 0: 65535 -6,-7: @@ -2147,8 +2156,8 @@ entities: 0: 57294 -6,-5: 0: 4369 - 5: 192 - 6: 49152 + 4: 192 + 5: 49152 -5,-7: 2: 560 -9,-4: @@ -2254,7 +2263,7 @@ entities: -5,8: 0: 63239 9,0: - 0: 65439 + 0: 65435 9,1: 0: 61424 9,2: @@ -2274,9 +2283,7 @@ entities: 10,3: 0: 63271 10,-1: - 0: 57300 - 7: 32 - 4: 8192 + 0: 65524 10,4: 0: 140 2: 21264 @@ -2309,7 +2316,7 @@ entities: 9,-5: 2: 9730 10,-4: - 0: 20471 + 0: 20479 10,-3: 0: 61071 10,-2: @@ -2318,15 +2325,12 @@ entities: 0: 48015 11,-2: 0: 3067 - 11,-1: - 0: 3838 - 11,-5: - 2: 28944 11,-4: - 2: 130 - 0: 24576 + 0: 25838 + 11,-1: + 0: 3822 12,-4: - 2: 3856 + 0: 497 12,-3: 0: 305 12,-2: @@ -2397,13 +2401,11 @@ entities: 2: 1860 0: 63624 4,9: - 2: 52428 + 0: 204 + 2: 35840 5,9: - 2: 65535 - 6,8: - 2: 4352 - 6,9: - 2: 4369 + 0: 55 + 2: 32712 0,9: 0: 40959 -1,9: @@ -2667,7 +2669,7 @@ entities: 16,1: 2: 4369 16,2: - 2: 4369 + 2: 4353 16,3: 2: 4369 12,8: @@ -2682,17 +2684,23 @@ entities: 13,8: 2: 4383 14,5: - 2: 255 + 2: 191 15,5: 2: 255 16,4: 2: 4369 16,5: - 2: 17 + 2: 1 + 12,-5: + 2: 49696 + 0: 15 13,-4: - 2: 36625 + 0: 819 + 2: 35840 13,-1: 0: 509 + 13,-5: + 0: 13107 13,-2: 2: 2280 13,-3: @@ -2753,10 +2761,30 @@ entities: 2: 3 9,-6: 2: 12288 + 10,-7: + 2: 49152 + 10,-6: + 2: 36044 + 11,-7: + 2: 53248 + 11,-6: + 2: 36317 10,-5: + 0: 8 2: 2048 + 11,-5: + 0: 15 + 2: 272 + 12,-7: + 2: 53248 + 12,-6: + 2: 36317 1,12: 2: 16 + 13,-7: + 2: 4096 + 13,-6: + 2: 273 uniqueMixes: - volume: 2500 temperature: 293.15 @@ -2818,21 +2846,6 @@ entities: - 0 - 0 - 0 - - volume: 2500 - temperature: 293.14975 - moles: - - 20.078888 - - 75.53487 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - volume: 2500 temperature: 293.15 moles: @@ -2863,21 +2876,6 @@ entities: - 0 - 0 - 0 - - volume: 2500 - temperature: 293.14948 - moles: - - 18.472576 - - 69.49208 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 chunkSize: 4 - type: GasTileOverlay - type: RadiationGridResistance @@ -2924,17 +2922,20 @@ entities: parent: 2 - type: DeviceList devices: - - 9800 - 9801 + - 9800 - 9802 + - 9804 + - 9803 + - 6631 + - 6048 - 9790 + - 976 - 9792 + - 9794 + - 9810 - 9796 - 9797 - - 976 - - 6048 - - 6631 - - 9810 - uid: 3196 components: - type: Transform @@ -3100,6 +3101,8 @@ entities: - 5372 - 5373 - 6496 + - 3935 + - 818 - uid: 9742 components: - type: Transform @@ -3449,6 +3452,8 @@ entities: - 9845 - 9838 - 9837 + - 3935 + - 818 - uid: 9840 components: - type: Transform @@ -3760,6 +3765,7 @@ entities: - 6183 - 9912 - 1987 + - 12187 - uid: 9925 components: - type: Transform @@ -3870,13 +3876,6 @@ entities: - type: Transform pos: -16.5,17.5 parent: 2 -- proto: AirlockArmoryLocked - entities: - - uid: 9970 - components: - - type: Transform - pos: 44.5,-2.5 - parent: 2 - proto: AirlockAtmosphericsGlassLocked entities: - uid: 9985 @@ -4121,6 +4120,11 @@ entities: - type: Transform pos: -28.5,-19.5 parent: 2 + - uid: 12146 + components: + - type: Transform + pos: 46.5,-13.5 + parent: 2 - proto: AirlockEngineeringLocked entities: - uid: 1898 @@ -4133,6 +4137,11 @@ entities: - type: Transform pos: -32.5,-19.5 parent: 2 + - uid: 3245 + components: + - type: Transform + pos: 35.5,-16.5 + parent: 2 - uid: 9993 components: - type: Transform @@ -4188,11 +4197,6 @@ entities: - type: Transform pos: 14.5,7.5 parent: 2 - - uid: 10134 - components: - - type: Transform - pos: 35.5,-16.5 - parent: 2 - uid: 10135 components: - type: Transform @@ -4288,6 +4292,16 @@ entities: - type: Transform pos: -39.5,-3.5 parent: 2 + - uid: 12158 + components: + - type: Transform + pos: 49.5,-14.5 + parent: 2 + - uid: 12159 + components: + - type: Transform + pos: 51.5,-14.5 + parent: 2 - proto: AirlockExternalGlassLocked entities: - uid: 3148 @@ -4342,20 +4356,6 @@ entities: - type: Transform pos: 22.5,33.5 parent: 2 -- proto: AirlockExternalGlassShuttleArrivals - entities: - - uid: 480 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,37.5 - parent: 2 - - uid: 2649 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,44.5 - parent: 2 - proto: AirlockExternalGlassShuttleEmergencyLocked entities: - uid: 105 @@ -4394,6 +4394,12 @@ entities: parent: 2 - proto: AirlockExternalGlassShuttleLocked entities: + - uid: 480 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,38.5 + parent: 2 - uid: 734 components: - type: Transform @@ -4406,6 +4412,12 @@ entities: rot: 1.5707963267948966 rad pos: 26.5,22.5 parent: 2 + - uid: 11835 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,40.5 + parent: 2 - proto: AirlockExternalLocked entities: - uid: 10193 @@ -4432,6 +4444,11 @@ entities: parent: 2 - proto: AirlockGlass entities: + - uid: 361 + components: + - type: Transform + pos: -2.5,11.5 + parent: 2 - uid: 10004 components: - type: Transform @@ -4607,11 +4624,6 @@ entities: - type: Transform pos: -14.5,14.5 parent: 2 - - uid: 10039 - components: - - type: Transform - pos: -2.5,11.5 - parent: 2 - uid: 10040 components: - type: Transform @@ -6024,15 +6036,10 @@ entities: parent: 2 - proto: ArrivalsShuttleTimer entities: - - uid: 11814 - components: - - type: Transform - pos: 8.5,33.5 - parent: 2 - - uid: 11815 + - uid: 2658 components: - type: Transform - pos: 8.5,43.5 + pos: 4.5,39.5 parent: 2 - proto: ArtifactAnalyzerMachineCircuitboard entities: @@ -6054,15 +6061,10 @@ entities: rot: 3.141592653589793 rad pos: 26.5,22.5 parent: 2 - - uid: 851 - components: - - type: Transform - pos: 8.5,44.5 - parent: 2 - - uid: 2645 + - uid: 2655 components: - type: Transform - pos: 8.5,37.5 + pos: 8.5,40.5 parent: 2 - uid: 3930 components: @@ -6074,6 +6076,11 @@ entities: - type: Transform pos: -26.5,43.5 parent: 2 + - uid: 11815 + components: + - type: Transform + pos: 8.5,38.5 + parent: 2 - proto: AtmosFixBlockerMarker entities: - uid: 5138 @@ -7014,6 +7021,13 @@ entities: - type: Transform pos: -8.634189,5.4278917 parent: 2 +- proto: BoxMouthSwab + entities: + - uid: 420 + components: + - type: Transform + pos: -5.8234816,17.993004 + parent: 2 - proto: BoxShotgunSlug entities: - uid: 315 @@ -7119,6 +7133,11 @@ entities: - type: Transform pos: 52.5,-1.5 parent: 2 + - uid: 477 + components: + - type: Transform + pos: 23.5,37.5 + parent: 2 - uid: 604 components: - type: Transform @@ -7134,6 +7153,11 @@ entities: - type: Transform pos: 15.5,-4.5 parent: 2 + - uid: 2646 + components: + - type: Transform + pos: 23.5,36.5 + parent: 2 - uid: 6608 components: - type: Transform @@ -14634,6 +14658,16 @@ entities: - type: Transform pos: 21.5,36.5 parent: 2 + - uid: 10708 + components: + - type: Transform + pos: 23.5,38.5 + parent: 2 + - uid: 10709 + components: + - type: Transform + pos: 22.5,38.5 + parent: 2 - uid: 10716 components: - type: Transform @@ -14689,21 +14723,6 @@ entities: - type: Transform pos: 19.5,7.5 parent: 2 - - uid: 11835 - components: - - type: Transform - pos: 20.5,35.5 - parent: 2 - - uid: 11840 - components: - - type: Transform - pos: 19.5,35.5 - parent: 2 - - uid: 11841 - components: - - type: Transform - pos: 19.5,36.5 - parent: 2 - uid: 11898 components: - type: Transform @@ -14754,6 +14773,61 @@ entities: - type: Transform pos: -6.5,45.5 parent: 2 + - uid: 12105 + components: + - type: Transform + pos: 46.5,-12.5 + parent: 2 + - uid: 12106 + components: + - type: Transform + pos: 46.5,-13.5 + parent: 2 + - uid: 12107 + components: + - type: Transform + pos: 46.5,-14.5 + parent: 2 + - uid: 12108 + components: + - type: Transform + pos: 45.5,-14.5 + parent: 2 + - uid: 12109 + components: + - type: Transform + pos: 47.5,-14.5 + parent: 2 + - uid: 12110 + components: + - type: Transform + pos: 48.5,-14.5 + parent: 2 + - uid: 12111 + components: + - type: Transform + pos: 49.5,-14.5 + parent: 2 + - uid: 12112 + components: + - type: Transform + pos: 50.5,-14.5 + parent: 2 + - uid: 12113 + components: + - type: Transform + pos: 51.5,-14.5 + parent: 2 + - uid: 12114 + components: + - type: Transform + pos: 52.5,-14.5 + parent: 2 + - uid: 12115 + components: + - type: Transform + pos: 52.5,-13.5 + parent: 2 - proto: CableApcStack10 entities: - uid: 2874 @@ -18463,6 +18537,146 @@ entities: - type: Transform pos: 50.5,31.5 parent: 2 + - uid: 12060 + components: + - type: Transform + pos: 45.5,-15.5 + parent: 2 + - uid: 12061 + components: + - type: Transform + pos: 45.5,-14.5 + parent: 2 + - uid: 12062 + components: + - type: Transform + pos: 46.5,-15.5 + parent: 2 + - uid: 12063 + components: + - type: Transform + pos: 47.5,-15.5 + parent: 2 + - uid: 12064 + components: + - type: Transform + pos: 45.5,-13.5 + parent: 2 + - uid: 12065 + components: + - type: Transform + pos: 45.5,-12.5 + parent: 2 + - uid: 12073 + components: + - type: Transform + pos: 48.5,-15.5 + parent: 2 + - uid: 12074 + components: + - type: Transform + pos: 48.5,-14.5 + parent: 2 + - uid: 12075 + components: + - type: Transform + pos: 49.5,-14.5 + parent: 2 + - uid: 12076 + components: + - type: Transform + pos: 50.5,-14.5 + parent: 2 + - uid: 12077 + components: + - type: Transform + pos: 51.5,-14.5 + parent: 2 + - uid: 12100 + components: + - type: Transform + pos: 52.5,-14.5 + parent: 2 + - uid: 12127 + components: + - type: Transform + pos: 52.5,-24.5 + parent: 2 + - uid: 12128 + components: + - type: Transform + pos: 52.5,-23.5 + parent: 2 + - uid: 12129 + components: + - type: Transform + pos: 52.5,-22.5 + parent: 2 + - uid: 12130 + components: + - type: Transform + pos: 52.5,-21.5 + parent: 2 + - uid: 12131 + components: + - type: Transform + pos: 50.5,-24.5 + parent: 2 + - uid: 12132 + components: + - type: Transform + pos: 50.5,-23.5 + parent: 2 + - uid: 12133 + components: + - type: Transform + pos: 50.5,-22.5 + parent: 2 + - uid: 12134 + components: + - type: Transform + pos: 50.5,-21.5 + parent: 2 + - uid: 12135 + components: + - type: Transform + pos: 51.5,-21.5 + parent: 2 + - uid: 12136 + components: + - type: Transform + pos: 51.5,-20.5 + parent: 2 + - uid: 12137 + components: + - type: Transform + pos: 51.5,-19.5 + parent: 2 + - uid: 12138 + components: + - type: Transform + pos: 52.5,-19.5 + parent: 2 + - uid: 12139 + components: + - type: Transform + pos: 52.5,-18.5 + parent: 2 + - uid: 12140 + components: + - type: Transform + pos: 52.5,-17.5 + parent: 2 + - uid: 12141 + components: + - type: Transform + pos: 52.5,-16.5 + parent: 2 + - uid: 12142 + components: + - type: Transform + pos: 52.5,-15.5 + parent: 2 - proto: CableMV entities: - uid: 368 @@ -21770,6 +21984,12 @@ entities: - type: Transform pos: -48.5,-18.5 parent: 2 + - uid: 12059 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 46.5,-15.5 + parent: 2 - proto: CandyBowl entities: - uid: 2226 @@ -22417,6 +22637,21 @@ entities: - type: Transform pos: -38.5,-3.5 parent: 2 + - uid: 2287 + components: + - type: Transform + pos: 22.5,37.5 + parent: 2 + - uid: 2650 + components: + - type: Transform + pos: 22.5,35.5 + parent: 2 + - uid: 2652 + components: + - type: Transform + pos: 22.5,34.5 + parent: 2 - uid: 2846 components: - type: Transform @@ -22567,21 +22802,6 @@ entities: - type: Transform pos: 21.5,34.5 parent: 2 - - uid: 3829 - components: - - type: Transform - pos: 21.5,35.5 - parent: 2 - - uid: 3830 - components: - - type: Transform - pos: 21.5,36.5 - parent: 2 - - uid: 3831 - components: - - type: Transform - pos: 21.5,37.5 - parent: 2 - uid: 3887 components: - type: Transform @@ -23602,7 +23822,7 @@ entities: - uid: 4621 components: - type: Transform - pos: 19.5,35.5 + pos: 22.5,36.5 parent: 2 - uid: 4675 components: @@ -25115,30 +25335,105 @@ entities: - type: Transform pos: 15.5,7.5 parent: 2 - - uid: 11836 + - uid: 12039 components: - type: Transform - pos: 19.5,36.5 + pos: -24.5,-17.5 parent: 2 - - uid: 11837 + - uid: 12041 components: - type: Transform - pos: 19.5,37.5 + pos: -25.5,-17.5 parent: 2 - - uid: 11839 + - uid: 12072 components: - type: Transform - pos: 20.5,35.5 + pos: 45.5,-14.5 parent: 2 - - uid: 12039 + - uid: 12079 components: - type: Transform - pos: -24.5,-17.5 + pos: 52.5,-14.5 parent: 2 - - uid: 12041 + - uid: 12080 components: - type: Transform - pos: -25.5,-17.5 + pos: 52.5,-15.5 + parent: 2 + - uid: 12081 + components: + - type: Transform + pos: 52.5,-16.5 + parent: 2 + - uid: 12082 + components: + - type: Transform + pos: 52.5,-17.5 + parent: 2 + - uid: 12083 + components: + - type: Transform + pos: 52.5,-18.5 + parent: 2 + - uid: 12084 + components: + - type: Transform + pos: 52.5,-19.5 + parent: 2 + - uid: 12085 + components: + - type: Transform + pos: 51.5,-19.5 + parent: 2 + - uid: 12086 + components: + - type: Transform + pos: 50.5,-19.5 + parent: 2 + - uid: 12087 + components: + - type: Transform + pos: 49.5,-19.5 + parent: 2 + - uid: 12088 + components: + - type: Transform + pos: 48.5,-19.5 + parent: 2 + - uid: 12089 + components: + - type: Transform + pos: 47.5,-19.5 + parent: 2 + - uid: 12090 + components: + - type: Transform + pos: 46.5,-19.5 + parent: 2 + - uid: 12091 + components: + - type: Transform + pos: 45.5,-19.5 + parent: 2 + - uid: 12092 + components: + - type: Transform + pos: 44.5,-19.5 + parent: 2 + - uid: 12093 + components: + - type: Transform + pos: 43.5,-19.5 + parent: 2 + - uid: 12103 + components: + - type: Transform + pos: 50.5,-14.5 + parent: 2 + - uid: 12104 + components: + - type: Transform + pos: 48.5,-14.5 parent: 2 - proto: Chair entities: @@ -25552,6 +25847,12 @@ entities: rot: -1.5707963267948966 rad pos: -1.7598586,45.796513 parent: 2 + - uid: 11628 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -20.580261,45.91752 + parent: 2 - proto: ChairFoldingSpawnFolded entities: - uid: 3760 @@ -25861,6 +26162,25 @@ entities: - type: Transform pos: -21.970404,42.530983 parent: 2 + - uid: 12174 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 47.651226,-15.31665 + parent: 2 + - uid: 12192 + components: + - type: Transform + pos: -20.642761,47.47068 + parent: 2 +- proto: ChairWeb + entities: + - uid: 12191 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -19.642761,46.501255 + parent: 2 - proto: ChairWood entities: - uid: 14 @@ -25964,6 +26284,13 @@ entities: - type: Transform pos: 26.890896,-10.669161 parent: 2 +- proto: CheapLighter + entities: + - uid: 12195 + components: + - type: Transform + pos: -20.754343,46.464325 + parent: 2 - proto: CheapRollerBed entities: - uid: 12035 @@ -25971,6 +26298,13 @@ entities: - type: Transform pos: 15.5,27.5 parent: 2 +- proto: CheckerBoard + entities: + - uid: 12193 + components: + - type: Transform + pos: -20.504343,46.63111 + parent: 2 - proto: ChemDispenser entities: - uid: 2309 @@ -26017,6 +26351,30 @@ entities: rot: 1.5707963267948966 rad pos: 3.5,34.5 parent: 2 +- proto: CigaretteSpent + entities: + - uid: 12196 + components: + - type: Transform + pos: -19.535593,46.016098 + parent: 2 + - uid: 12197 + components: + - type: Transform + pos: -18.858511,47.38163 + parent: 2 + - uid: 12198 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -21.462677,47.183575 + parent: 2 + - uid: 12199 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -21.139761,45.82847 + parent: 2 - proto: CigarSpent entities: - uid: 2125 @@ -26024,6 +26382,14 @@ entities: - type: Transform pos: -25.22141,-18.481884 parent: 2 +- proto: CigPackBlue + entities: + - uid: 12194 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -20.264761,46.464325 + parent: 2 - proto: CigPackGreen entities: - uid: 3734 @@ -26342,6 +26708,13 @@ entities: - type: Transform pos: -10.5,28.5 parent: 2 +- proto: ClosetWallMaintenanceFilledRandom + entities: + - uid: 12102 + components: + - type: Transform + pos: 47.5,-13.5 + parent: 2 - proto: ClosetWallOrange entities: - uid: 2354 @@ -26835,11 +27208,11 @@ entities: rot: 3.141592653589793 rad pos: 7.5,-1.5 parent: 2 - - uid: 11611 + - uid: 2647 components: - type: Transform - rot: 1.5707963267948966 rad - pos: 18.5,37.5 + rot: -1.5707963267948966 rad + pos: 23.5,35.5 parent: 2 - proto: ComputerResearchAndDevelopment entities: @@ -26884,6 +27257,12 @@ entities: rot: -1.5707963267948966 rad pos: -23.5,-19.5 parent: 2 + - uid: 12101 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 48.5,-15.5 + parent: 2 - proto: ComputerStationRecords entities: - uid: 99 @@ -27071,15 +27450,15 @@ entities: parent: 2 - proto: CrateArtifactContainer entities: - - uid: 10935 + - uid: 3829 components: - type: Transform - pos: -7.5,41.5 + pos: 24.5,37.5 parent: 2 - - uid: 11834 + - uid: 10935 components: - type: Transform - pos: 23.5,36.5 + pos: -7.5,41.5 parent: 2 - proto: CrateEmptySpawner entities: @@ -27211,18 +27590,6 @@ entities: - type: Transform pos: -13.5,51.5 parent: 2 -- proto: CrateEngineeringSingularityCollector - entities: - - uid: 12037 - components: - - type: Transform - pos: -32.5,-27.5 - parent: 2 - - uid: 12038 - components: - - type: Transform - pos: -33.5,-27.5 - parent: 2 - proto: CrateEngineeringSingularityEmitter entities: - uid: 2794 @@ -27237,6 +27604,11 @@ entities: - type: Transform pos: -46.5,-19.5 parent: 2 + - uid: 12145 + components: + - type: Transform + pos: 45.5,-14.5 + parent: 2 - proto: CrateFilledSpawner entities: - uid: 10689 @@ -27361,6 +27733,11 @@ entities: parent: 2 - proto: CrateHydroponicsTools entities: + - uid: 917 + components: + - type: Transform + pos: -8.5,17.5 + parent: 2 - uid: 3489 components: - type: Transform @@ -28030,11 +28407,6 @@ entities: parent: 2 - proto: DiseaseSwab entities: - - uid: 917 - components: - - type: Transform - pos: -5.896539,17.71439 - parent: 2 - uid: 4571 components: - type: Transform @@ -32162,6 +32534,11 @@ entities: - type: Transform pos: -23.5,-15.5 parent: 2 + - uid: 2186 + components: + - type: Transform + pos: 38.5,1.5 + parent: 2 - uid: 2209 components: - type: Transform @@ -32182,11 +32559,6 @@ entities: - type: Transform pos: 19.5,-11.5 parent: 2 - - uid: 5195 - components: - - type: Transform - pos: 44.5,-0.5 - parent: 2 - uid: 5196 components: - type: Transform @@ -32483,6 +32855,8 @@ entities: - 9841 - 9842 - 9843 + - 3935 + - 818 - uid: 11733 components: - type: Transform @@ -32619,8 +32993,9 @@ entities: - 9794 - 9792 - 9790 - - 9801 - 9800 + - 9802 + - 9801 - uid: 11742 components: - type: Transform @@ -32777,6 +33152,7 @@ entities: deviceLists: - 9793 - 11741 + - 3121 - uid: 9795 components: - type: Transform @@ -32925,6 +33301,28 @@ entities: - 9742 - proto: FirelockEdge entities: + - uid: 818 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -10.5,15.5 + parent: 2 + - type: DeviceNetwork + deviceLists: + - 11732 + - 9835 + - 9741 + - uid: 3935 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -10.5,16.5 + parent: 2 + - type: DeviceNetwork + deviceLists: + - 11732 + - 9835 + - 9741 - uid: 4130 components: - type: Transform @@ -33011,6 +33409,7 @@ entities: deviceLists: - 9827 - 11741 + - 3121 - uid: 9804 components: - type: Transform @@ -33021,6 +33420,7 @@ entities: deviceLists: - 9827 - 11741 + - 3121 - uid: 9805 components: - type: Transform @@ -33641,8 +34041,8 @@ entities: deviceLists: - 4393 - 11731 - - 11741 - 3121 + - 11741 - uid: 9801 components: - type: Transform @@ -33651,8 +34051,8 @@ entities: - type: DeviceNetwork deviceLists: - 9863 - - 11741 - 3121 + - 11741 - uid: 9802 components: - type: Transform @@ -33662,6 +34062,7 @@ entities: deviceLists: - 9827 - 3121 + - 11741 - uid: 9825 components: - type: Transform @@ -34157,6 +34558,16 @@ entities: - type: Transform pos: -13.730139,-11.50376 parent: 2 + - uid: 12038 + components: + - type: Transform + pos: -53.447044,-15.223393 + parent: 2 + - uid: 12118 + components: + - type: Transform + pos: 48.30665,-19.05083 + parent: 2 - proto: FloodlightBroken entities: - uid: 4573 @@ -36092,14 +36503,6 @@ entities: parent: 2 - type: AtmosPipeColor color: '#FF0066FF' - - uid: 9937 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 43.5,-11.5 - parent: 2 - - type: AtmosPipeColor - color: '#6666FFFF' - uid: 9938 components: - type: Transform @@ -36176,6 +36579,29 @@ entities: parent: 2 - type: AtmosPipeColor color: '#FF0066FF' + - uid: 12169 + components: + - type: Transform + pos: 46.5,-11.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12185 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -31.5,-13.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12186 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -31.5,-14.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' - proto: GasPipeFourway entities: - uid: 1662 @@ -46261,6 +46687,76 @@ entities: parent: 2 - type: AtmosPipeColor color: '#FF0066FF' + - uid: 12170 + components: + - type: Transform + pos: 46.5,-12.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12171 + components: + - type: Transform + pos: 46.5,-13.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12172 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 45.5,-11.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12173 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 44.5,-11.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12180 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -26.5,-13.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12181 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -27.5,-13.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12182 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -28.5,-13.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12183 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -29.5,-13.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12184 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -30.5,-13.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' - proto: GasPipeTJunction entities: - uid: 542 @@ -46533,6 +47029,14 @@ entities: parent: 2 - type: AtmosPipeColor color: '#6666FFFF' + - uid: 1910 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 43.5,-11.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' - uid: 2091 components: - type: Transform @@ -49836,6 +50340,25 @@ entities: - 9890 - type: AtmosPipeColor color: '#6666FFFF' + - uid: 12168 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 46.5,-14.5 + parent: 2 + - type: AtmosPipeColor + color: '#6666FFFF' + - uid: 12187 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -32.5,-14.5 + parent: 2 + - type: DeviceNetwork + deviceLists: + - 9918 + - type: AtmosPipeColor + color: '#6666FFFF' - proto: GasVentScrubber entities: - uid: 838 @@ -51283,6 +51806,11 @@ entities: rot: 1.5707963267948966 rad pos: -32.5,3.5 parent: 2 + - uid: 3830 + components: + - type: Transform + pos: 22.5,39.5 + parent: 2 - uid: 3987 components: - type: Transform @@ -51353,23 +51881,21 @@ entities: - type: Transform pos: -40.5,9.5 parent: 2 - - uid: 10708 + - uid: 10710 components: - type: Transform rot: 3.141592653589793 rad - pos: 19.5,39.5 + pos: 25.5,32.5 parent: 2 - - uid: 10709 + - uid: 12155 components: - type: Transform - rot: 3.141592653589793 rad - pos: 23.5,38.5 + pos: 47.5,-24.5 parent: 2 - - uid: 10710 + - uid: 12161 components: - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,32.5 + pos: 43.5,-24.5 parent: 2 - proto: GlassBoxLaserFilled entities: @@ -51569,11 +52095,6 @@ entities: - type: Transform pos: -4.5,12.5 parent: 2 - - uid: 361 - components: - - type: Transform - pos: -10.5,15.5 - parent: 2 - uid: 362 components: - type: Transform @@ -51741,6 +52262,11 @@ entities: - type: Transform pos: 5.5,-10.5 parent: 2 + - uid: 851 + components: + - type: Transform + pos: 8.5,42.5 + parent: 2 - uid: 861 components: - type: Transform @@ -52437,25 +52963,15 @@ entities: - type: Transform pos: 2.5,33.5 parent: 2 - - uid: 2654 - components: - - type: Transform - pos: 8.5,42.5 - parent: 2 - - uid: 2655 - components: - - type: Transform - pos: 8.5,41.5 - parent: 2 - - uid: 2656 + - uid: 2649 components: - type: Transform - pos: 8.5,40.5 + pos: 8.5,39.5 parent: 2 - uid: 2657 components: - type: Transform - pos: 8.5,39.5 + pos: 8.5,43.5 parent: 2 - uid: 2691 components: @@ -53097,11 +53613,6 @@ entities: rot: 1.5707963267948966 rad pos: 43.5,-3.5 parent: 2 - - uid: 3934 - components: - - type: Transform - pos: -10.5,16.5 - parent: 2 - uid: 3999 components: - type: Transform @@ -53393,16 +53904,51 @@ entities: - type: Transform pos: -21.5,40.5 parent: 2 + - uid: 10291 + components: + - type: Transform + pos: 8.5,36.5 + parent: 2 - uid: 10294 components: - type: Transform pos: -25.5,6.5 parent: 2 + - uid: 11557 + components: + - type: Transform + pos: 45.5,-13.5 + parent: 2 - uid: 11698 components: - type: Transform pos: -32.5,23.5 parent: 2 + - uid: 12051 + components: + - type: Transform + pos: 48.5,-16.5 + parent: 2 + - uid: 12054 + components: + - type: Transform + pos: 47.5,-16.5 + parent: 2 + - uid: 12055 + components: + - type: Transform + pos: 46.5,-16.5 + parent: 2 + - uid: 12067 + components: + - type: Transform + pos: 51.5,-15.5 + parent: 2 + - uid: 12068 + components: + - type: Transform + pos: 50.5,-15.5 + parent: 2 - proto: GrilleDiagonal entities: - uid: 66 @@ -53629,6 +54175,13 @@ entities: rot: -1.5707963267948966 rad pos: -41.5,-8.5 parent: 2 +- proto: HighSecArmoryLocked + entities: + - uid: 12147 + components: + - type: Transform + pos: 44.5,-2.5 + parent: 2 - proto: HighSecCommandLocked entities: - uid: 2541 @@ -53699,6 +54252,11 @@ entities: parent: 2 - proto: HydroponicsToolMiniHoe entities: + - uid: 902 + components: + - type: Transform + pos: -6.9293127,13.324524 + parent: 2 - uid: 4563 components: - type: Transform @@ -54208,6 +54766,18 @@ entities: - type: Transform pos: -12.150006,36.889446 parent: 2 +- proto: LightTube + entities: + - uid: 12189 + components: + - type: Transform + pos: -24.601343,-8.484417 + parent: 2 + - uid: 12190 + components: + - type: Transform + pos: -24.476343,-8.546917 + parent: 2 - proto: LiquidNitrogenCanister entities: - uid: 1820 @@ -54263,11 +54833,6 @@ entities: - proto: LockerBotanistFilled entities: - uid: 901 - components: - - type: Transform - pos: -1.5,14.5 - parent: 2 - - uid: 902 components: - type: Transform pos: -1.5,15.5 @@ -54817,6 +55382,31 @@ entities: - type: Transform pos: 51.5,23.5 parent: 2 + - uid: 12156 + components: + - type: Transform + pos: 49.5,-17.5 + parent: 2 + - uid: 12162 + components: + - type: Transform + pos: -27.5,-28.5 + parent: 2 + - uid: 12163 + components: + - type: Transform + pos: -42.5,-29.5 + parent: 2 + - uid: 12165 + components: + - type: Transform + pos: 7.5,-25.5 + parent: 2 + - uid: 12166 + components: + - type: Transform + pos: 57.5,19.5 + parent: 2 - proto: MaintenancePlantSpawner entities: - uid: 11514 @@ -54831,6 +55421,11 @@ entities: parent: 2 - proto: MaintenanceToolSpawner entities: + - uid: 5195 + components: + - type: Transform + pos: 43.5,-17.5 + parent: 2 - uid: 11522 components: - type: Transform @@ -54943,11 +55538,6 @@ entities: - type: Transform pos: 54.5,2.5 parent: 2 - - uid: 11578 - components: - - type: Transform - pos: 48.5,-14.5 - parent: 2 - uid: 11579 components: - type: Transform @@ -55034,6 +55624,11 @@ entities: - type: Transform pos: 47.5,35.5 parent: 2 + - uid: 12164 + components: + - type: Transform + pos: -46.5,-27.5 + parent: 2 - proto: Matchbox entities: - uid: 3735 @@ -55404,10 +55999,10 @@ entities: parent: 2 - proto: OreBox entities: - - uid: 11838 + - uid: 285 components: - type: Transform - pos: 18.5,35.5 + pos: 20.5,36.5 parent: 2 - proto: OreProcessor entities: @@ -55428,11 +56023,6 @@ entities: - type: Transform pos: -18.5,-17.5 parent: 2 - - uid: 5262 - components: - - type: Transform - pos: 46.5,-12.5 - parent: 2 - uid: 5263 components: - type: Transform @@ -55448,6 +56038,11 @@ entities: - type: Transform pos: 17.5,29.5 parent: 2 + - uid: 12050 + components: + - type: Transform + pos: 48.5,-11.5 + parent: 2 - proto: OxygenTankFilled entities: - uid: 11548 @@ -55625,7 +56220,7 @@ entities: BODY: - POWER - Electricity on Micro station is provided by uranium and plasma generators in Engineering, a solar array attached to Engineering, and a one-core AME that staff may choose to set up. The station's supermatter is currently pending arrival from the thirteenth sector and should be due to arrive within a month. + POWER - Electricity on Micro station is provided by uranium and plasma generators in Engineering, a solar array attached to Engineering, a solar array near the southeast evac docks, and a one-core AME that staff may choose to set up. The station's supermatter is currently pending arrival from the thirteenth sector and should be due to arrive within a month. The station is dependent on Plasma and Uranium for fuel, unless new power generation means are acquired. Please coordinate with the Logistics department to facilitate this. @@ -56308,12 +56903,6 @@ entities: rot: -1.5707963267948966 rad pos: 3.5,34.5 parent: 2 - - uid: 10291 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 7.5,43.5 - parent: 2 - uid: 10292 components: - type: Transform @@ -56907,6 +57496,12 @@ entities: rot: 3.141592653589793 rad pos: 19.5,27.5 parent: 2 + - uid: 11814 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,43.5 + parent: 2 - uid: 11950 components: - type: Transform @@ -56923,6 +57518,12 @@ entities: - type: Transform pos: -27.5,22.5 parent: 2 + - uid: 12117 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 45.5,-14.5 + parent: 2 - proto: PoweredlightBlue entities: - uid: 10442 @@ -56991,6 +57592,11 @@ entities: rot: -1.5707963267948966 rad pos: 41.5,30.5 parent: 2 + - uid: 12116 + components: + - type: Transform + pos: 50.5,-14.5 + parent: 2 - proto: PoweredLightColoredFrostyBlue entities: - uid: 10441 @@ -57006,12 +57612,24 @@ entities: - type: Transform pos: 40.5,-13.5 parent: 2 +- proto: PoweredlightEmpty + entities: + - uid: 12188 + components: + - type: Transform + pos: 49.5,-17.5 + parent: 2 - proto: PoweredLightPostSmall entities: - - uid: 2287 + - uid: 3831 components: - type: Transform - pos: 18.5,36.5 + pos: 20.5,39.5 + parent: 2 + - uid: 11611 + components: + - type: Transform + pos: 24.5,39.5 parent: 2 - proto: PoweredSmallLight entities: @@ -57811,6 +58429,11 @@ entities: - type: Transform pos: -29.5,-2.5 parent: 2 + - uid: 12143 + components: + - type: Transform + pos: 45.5,-12.5 + parent: 2 - proto: RadiationCollectorNoTank entities: - uid: 1633 @@ -57818,11 +58441,6 @@ entities: - type: Transform pos: -34.5,-16.5 parent: 2 - - uid: 1637 - components: - - type: Transform - pos: -39.5,-18.5 - parent: 2 - uid: 1639 components: - type: Transform @@ -57837,18 +58455,6 @@ entities: parent: 2 - proto: Railing entities: - - uid: 285 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,36.5 - parent: 2 - - uid: 477 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,37.5 - parent: 2 - uid: 595 components: - type: Transform @@ -57876,40 +58482,60 @@ entities: - type: Transform pos: 2.5,-12.5 parent: 2 - - uid: 2646 + - uid: 3030 components: - type: Transform rot: -1.5707963267948966 rad - pos: 18.5,38.5 + pos: 7.5,-14.5 parent: 2 - - uid: 2650 + - uid: 3032 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,35.5 + pos: 3.5,-12.5 parent: 2 - - uid: 2652 + - uid: 3034 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,34.5 + rot: 1.5707963267948966 rad + pos: 1.5,-14.5 parent: 2 - - uid: 3030 + - uid: 12094 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 7.5,-14.5 + rot: 1.5707963267948966 rad + pos: 53.5,-14.5 parent: 2 - - uid: 3032 + - uid: 12095 components: - type: Transform - pos: 3.5,-12.5 + rot: 1.5707963267948966 rad + pos: 53.5,-15.5 parent: 2 - - uid: 3034 + - uid: 12096 components: - type: Transform rot: 1.5707963267948966 rad - pos: 1.5,-14.5 + pos: 53.5,-16.5 + parent: 2 + - uid: 12097 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 53.5,-17.5 + parent: 2 + - uid: 12098 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 53.5,-18.5 + parent: 2 +- proto: RailingCorner + entities: + - uid: 12099 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 53.5,-13.5 parent: 2 - proto: RailingCornerSmall entities: @@ -58079,8 +58705,18 @@ entities: - type: Transform pos: 3.5,19.5 parent: 2 + - uid: 12037 + components: + - type: Transform + pos: -11.5,-20.5 + parent: 2 - proto: RandomPosterAny entities: + - uid: 1637 + components: + - type: Transform + pos: -7.5,-17.5 + parent: 2 - uid: 11666 components: - type: Transform @@ -58186,6 +58822,11 @@ entities: - type: Transform pos: -33.5,-1.5 parent: 2 + - uid: 12175 + components: + - type: Transform + pos: -20.5,-24.5 + parent: 2 - proto: RandomPosterContraband entities: - uid: 11674 @@ -58435,6 +59076,26 @@ entities: - type: Transform pos: 1.5,27.5 parent: 2 + - uid: 12176 + components: + - type: Transform + pos: -49.5,-19.5 + parent: 2 + - uid: 12177 + components: + - type: Transform + pos: -33.5,-11.5 + parent: 2 + - uid: 12178 + components: + - type: Transform + pos: -30.5,-27.5 + parent: 2 + - uid: 12179 + components: + - type: Transform + pos: -27.5,-23.5 + parent: 2 - proto: RandomProduce entities: - uid: 10723 @@ -58474,10 +59135,10 @@ entities: - type: Transform pos: 58.5,15.5 parent: 2 - - uid: 11628 + - uid: 11840 components: - type: Transform - pos: -20.5,45.5 + pos: -16.5,47.5 parent: 2 - proto: RandomSpawner entities: @@ -58749,7 +59410,7 @@ entities: - uid: 11441 components: - type: Transform - pos: -20.5,46.5 + pos: -19.5,45.5 parent: 2 - uid: 11442 components: @@ -58911,11 +59572,6 @@ entities: - type: Transform pos: 43.5,-14.5 parent: 2 - - uid: 11474 - components: - - type: Transform - pos: 48.5,-11.5 - parent: 2 - uid: 11475 components: - type: Transform @@ -59031,11 +59687,6 @@ entities: - type: Transform pos: -46.5,16.5 parent: 2 - - uid: 11557 - components: - - type: Transform - pos: 46.5,-16.5 - parent: 2 - uid: 11558 components: - type: Transform @@ -59091,6 +59742,31 @@ entities: - type: Transform pos: 55.5,31.5 parent: 2 + - uid: 12148 + components: + - type: Transform + pos: 42.5,-24.5 + parent: 2 + - uid: 12152 + components: + - type: Transform + pos: 51.5,-16.5 + parent: 2 + - uid: 12153 + components: + - type: Transform + pos: 43.5,-22.5 + parent: 2 + - uid: 12154 + components: + - type: Transform + pos: 43.5,-23.5 + parent: 2 + - uid: 12160 + components: + - type: Transform + pos: 48.5,-24.5 + parent: 2 - proto: RandomVending entities: - uid: 1354 @@ -59565,6 +60241,11 @@ entities: - type: Transform pos: 8.5,10.5 parent: 2 + - uid: 366 + components: + - type: Transform + pos: 8.5,39.5 + parent: 2 - uid: 417 components: - type: Transform @@ -59945,25 +60626,20 @@ entities: - type: Transform pos: 9.5,-9.5 parent: 2 - - uid: 2658 - components: - - type: Transform - pos: 8.5,42.5 - parent: 2 - - uid: 2659 + - uid: 2637 components: - type: Transform - pos: 8.5,41.5 + pos: 8.5,43.5 parent: 2 - - uid: 2660 + - uid: 2653 components: - type: Transform - pos: 8.5,40.5 + pos: 8.5,36.5 parent: 2 - - uid: 2661 + - uid: 2659 components: - type: Transform - pos: 8.5,39.5 + pos: 8.5,42.5 parent: 2 - uid: 2662 components: @@ -60567,6 +61243,36 @@ entities: - type: Transform pos: 25.5,15.5 parent: 2 + - uid: 11474 + components: + - type: Transform + pos: 45.5,-13.5 + parent: 2 + - uid: 12056 + components: + - type: Transform + pos: 47.5,-16.5 + parent: 2 + - uid: 12057 + components: + - type: Transform + pos: 46.5,-16.5 + parent: 2 + - uid: 12069 + components: + - type: Transform + pos: 51.5,-15.5 + parent: 2 + - uid: 12070 + components: + - type: Transform + pos: 50.5,-15.5 + parent: 2 + - uid: 12071 + components: + - type: Transform + pos: 48.5,-16.5 + parent: 2 - proto: ReinforcedWindowDiagonal entities: - uid: 63 @@ -60691,10 +61397,11 @@ entities: chance: 0.5 - proto: SalvageLocator entities: - - uid: 2647 + - uid: 11834 components: - type: Transform - pos: 21.5,37.5 + rot: 3.141592653589793 rad + pos: 22.5,37.5 parent: 2 - proto: Saw entities: @@ -60845,6 +61552,11 @@ entities: - type: Transform pos: 24.5,15.5 parent: 2 + - uid: 12144 + components: + - type: Transform + pos: 45.5,-12.5 + parent: 2 - proto: SheetPlasma entities: - uid: 822 @@ -61482,6 +62194,13 @@ entities: - type: Transform pos: 26.5,-1.5 parent: 2 +- proto: SignArmory + entities: + - uid: 4581 + components: + - type: Transform + pos: 44.5,-0.5 + parent: 2 - proto: SignAtmos entities: - uid: 3027 @@ -61860,6 +62579,35 @@ entities: rot: -1.5707963267948966 rad pos: -45.5,-4.5 parent: 2 + - uid: 11838 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 38.5,-12.5 + parent: 2 + - uid: 11839 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 31.5,-16.5 + parent: 2 + - uid: 12149 + components: + - type: Transform + pos: 47.5,-12.5 + parent: 2 + - uid: 12150 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 22.5,-14.5 + parent: 2 + - uid: 12151 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 31.5,-14.5 + parent: 2 - proto: SignDisposalSpace entities: - uid: 343 @@ -61951,6 +62699,11 @@ entities: - type: Transform pos: -45.5,-21.5 parent: 2 + - uid: 12157 + components: + - type: Transform + pos: 49.5,-13.5 + parent: 2 - proto: SignGravity entities: - uid: 4647 @@ -62100,6 +62853,16 @@ entities: parent: 2 - proto: SignShipDock entities: + - uid: 2645 + components: + - type: Transform + pos: 8.5,37.5 + parent: 2 + - uid: 2661 + components: + - type: Transform + pos: 8.5,41.5 + parent: 2 - uid: 4666 components: - type: Transform @@ -62234,6 +62997,11 @@ entities: - type: Transform pos: -48.5,-19.5 parent: 2 + - uid: 12058 + components: + - type: Transform + pos: 45.5,-15.5 + parent: 2 - proto: SodaDispenser entities: - uid: 1169 @@ -62409,6 +63177,46 @@ entities: - type: Transform pos: -59.5,-15.5 parent: 2 + - uid: 12119 + components: + - type: Transform + pos: 52.5,-21.5 + parent: 2 + - uid: 12120 + components: + - type: Transform + pos: 52.5,-22.5 + parent: 2 + - uid: 12121 + components: + - type: Transform + pos: 52.5,-23.5 + parent: 2 + - uid: 12122 + components: + - type: Transform + pos: 52.5,-24.5 + parent: 2 + - uid: 12123 + components: + - type: Transform + pos: 50.5,-21.5 + parent: 2 + - uid: 12124 + components: + - type: Transform + pos: 50.5,-22.5 + parent: 2 + - uid: 12125 + components: + - type: Transform + pos: 50.5,-23.5 + parent: 2 + - uid: 12126 + components: + - type: Transform + pos: 50.5,-24.5 + parent: 2 - proto: SolarTracker entities: - uid: 5156 @@ -63553,6 +64361,11 @@ entities: - type: Transform pos: -29.5,-1.5 parent: 2 + - uid: 12078 + components: + - type: Transform + pos: 48.5,-13.5 + parent: 2 - proto: SuitStorageEVA entities: - uid: 39 @@ -64850,6 +65663,11 @@ entities: - type: Transform pos: 41.5,-14.5 parent: 2 + - uid: 11841 + components: + - type: Transform + pos: -20.5,46.5 + parent: 2 - proto: TableCounterMetal entities: - uid: 176 @@ -65471,11 +66289,21 @@ entities: - type: Transform pos: 40.5,0.5 parent: 2 + - uid: 3934 + components: + - type: Transform + pos: -10.5,16.5 + parent: 2 - uid: 5221 components: - type: Transform pos: -12.5,51.5 parent: 2 + - uid: 10039 + components: + - type: Transform + pos: -10.5,15.5 + parent: 2 - proto: TableReinforcedGlass entities: - uid: 1265 @@ -66242,6 +67070,13 @@ entities: - type: Transform pos: 2.5,24.5 parent: 2 +- proto: VendingMachineDonut + entities: + - uid: 12167 + components: + - type: Transform + pos: 37.5,0.5 + parent: 2 - proto: VendingMachineEngiDrobe entities: - uid: 1936 @@ -66270,10 +67105,10 @@ entities: parent: 2 - proto: VendingMachineHydrobe entities: - - uid: 905 + - uid: 448 components: - type: Transform - pos: -1.5,16.5 + pos: -1.5,14.5 parent: 2 - proto: VendingMachineJaniDrobe entities: @@ -66310,13 +67145,6 @@ entities: - type: Transform pos: -15.5,22.5 parent: 2 -- proto: VendingMachineNutri - entities: - - uid: 420 - components: - - type: Transform - pos: -9.5,17.5 - parent: 2 - proto: VendingMachinePride entities: - uid: 10231 @@ -66380,15 +67208,13 @@ entities: - type: Transform pos: 44.5,-5.5 parent: 2 -- proto: VendingMachineSeeds +- proto: VendingMachineSeedsUnlocked entities: - - uid: 448 + - uid: 905 components: - type: Transform - pos: -8.5,17.5 + pos: -9.5,17.5 parent: 2 -- proto: VendingMachineSeedsUnlocked - entities: - uid: 3404 components: - type: Transform @@ -66894,11 +67720,6 @@ entities: - type: Transform pos: -42.5,-1.5 parent: 2 - - uid: 366 - components: - - type: Transform - pos: 8.5,36.5 - parent: 2 - uid: 385 components: - type: Transform @@ -68170,11 +68991,6 @@ entities: - type: Transform pos: 6.5,-24.5 parent: 2 - - uid: 1910 - components: - - type: Transform - pos: 44.5,-14.5 - parent: 2 - uid: 1920 components: - type: Transform @@ -68386,11 +69202,6 @@ entities: - type: Transform pos: -36.5,-28.5 parent: 2 - - uid: 2186 - components: - - type: Transform - pos: 45.5,-13.5 - parent: 2 - uid: 2188 components: - type: Transform @@ -68830,11 +69641,6 @@ entities: - type: Transform pos: -16.5,44.5 parent: 2 - - uid: 2637 - components: - - type: Transform - pos: 8.5,43.5 - parent: 2 - uid: 2643 components: - type: Transform @@ -68850,10 +69656,20 @@ entities: - type: Transform pos: 5.5,45.5 parent: 2 - - uid: 2653 + - uid: 2654 components: - type: Transform - pos: 8.5,38.5 + pos: 8.5,41.5 + parent: 2 + - uid: 2656 + components: + - type: Transform + pos: 8.5,44.5 + parent: 2 + - uid: 2660 + components: + - type: Transform + pos: 8.5,37.5 parent: 2 - uid: 2716 components: @@ -69522,16 +70338,6 @@ entities: - type: Transform pos: 48.5,0.5 parent: 2 - - uid: 3244 - components: - - type: Transform - pos: 47.5,-13.5 - parent: 2 - - uid: 3245 - components: - - type: Transform - pos: 46.5,-13.5 - parent: 2 - uid: 3250 components: - type: Transform @@ -69917,11 +70723,6 @@ entities: - type: Transform pos: 44.5,32.5 parent: 2 - - uid: 3556 - components: - - type: Transform - pos: 47.5,-12.5 - parent: 2 - uid: 3557 components: - type: Transform @@ -69932,11 +70733,6 @@ entities: - type: Transform pos: 47.5,33.5 parent: 2 - - uid: 3559 - components: - - type: Transform - pos: 48.5,-12.5 - parent: 2 - uid: 3560 components: - type: Transform @@ -70452,16 +71248,6 @@ entities: - type: Transform pos: -42.5,28.5 parent: 2 - - uid: 4581 - components: - - type: Transform - pos: 43.5,-15.5 - parent: 2 - - uid: 4583 - components: - - type: Transform - pos: 44.5,-13.5 - parent: 2 - uid: 4584 components: - type: Transform @@ -70477,16 +71263,6 @@ entities: - type: Transform pos: 39.5,-16.5 parent: 2 - - uid: 4587 - components: - - type: Transform - pos: 44.5,-12.5 - parent: 2 - - uid: 4588 - components: - - type: Transform - pos: 44.5,-15.5 - parent: 2 - uid: 4589 components: - type: Transform @@ -70666,6 +71442,11 @@ entities: - type: Transform pos: -10.5,51.5 parent: 2 + - uid: 5262 + components: + - type: Transform + pos: 50.5,-13.5 + parent: 2 - uid: 5737 components: - type: Transform @@ -70731,6 +71512,36 @@ entities: - type: Transform pos: 54.5,-4.5 parent: 2 + - uid: 9970 + components: + - type: Transform + pos: 49.5,-16.5 + parent: 2 + - uid: 10134 + components: + - type: Transform + pos: 44.5,-16.5 + parent: 2 + - uid: 11578 + components: + - type: Transform + pos: 45.5,-16.5 + parent: 2 + - uid: 12052 + components: + - type: Transform + pos: 49.5,-15.5 + parent: 2 + - uid: 12053 + components: + - type: Transform + pos: 49.5,-13.5 + parent: 2 + - uid: 12066 + components: + - type: Transform + pos: 51.5,-13.5 + parent: 2 - proto: WallSolid entities: - uid: 4 @@ -74042,6 +74853,11 @@ entities: - type: Transform pos: 43.5,-12.5 parent: 2 + - uid: 3244 + components: + - type: Transform + pos: 44.5,-13.5 + parent: 2 - uid: 3301 components: - type: Transform @@ -74127,6 +74943,16 @@ entities: - type: Transform pos: 57.5,12.5 parent: 2 + - uid: 3556 + components: + - type: Transform + pos: 47.5,-13.5 + parent: 2 + - uid: 3559 + components: + - type: Transform + pos: 47.5,-12.5 + parent: 2 - uid: 3722 components: - type: Transform @@ -74197,6 +75023,21 @@ entities: - type: Transform pos: 48.5,-8.5 parent: 2 + - uid: 4583 + components: + - type: Transform + pos: 48.5,-12.5 + parent: 2 + - uid: 4587 + components: + - type: Transform + pos: 44.5,-14.5 + parent: 2 + - uid: 4588 + components: + - type: Transform + pos: 44.5,-15.5 + parent: 2 - uid: 4766 components: - type: Transform @@ -74232,6 +75073,11 @@ entities: - type: Transform pos: 6.5,28.5 parent: 2 + - uid: 9937 + components: + - type: Transform + pos: 44.5,-12.5 + parent: 2 - uid: 10632 components: - type: Transform @@ -74860,6 +75706,20 @@ entities: rot: 1.5707963267948966 rad pos: 41.5,-1.5 parent: 2 +- proto: WindoorServiceLocked + entities: + - uid: 11836 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -10.5,15.5 + parent: 2 + - uid: 11837 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -10.5,16.5 + parent: 2 - proto: Window entities: - uid: 143 @@ -74948,11 +75808,6 @@ entities: - type: Transform pos: -10.5,14.5 parent: 2 - - uid: 818 - components: - - type: Transform - pos: -10.5,15.5 - parent: 2 - uid: 819 components: - type: Transform @@ -75177,11 +76032,6 @@ entities: - type: Transform pos: -24.5,-1.5 parent: 2 - - uid: 3935 - components: - - type: Transform - pos: -10.5,16.5 - parent: 2 - uid: 3937 components: - type: Transform diff --git a/Resources/Prototypes/Maps/micro.yml b/Resources/Prototypes/Maps/micro.yml index 5e408057e46..b03bf96e37e 100644 --- a/Resources/Prototypes/Maps/micro.yml +++ b/Resources/Prototypes/Maps/micro.yml @@ -8,8 +8,6 @@ Micro: stationProto: StandardNanotrasenStation components: - - type: StationEmergencyShuttle - emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_Lox.yml - type: StationNameSetup mapNameTemplate: '{0} Micro "Snail" Station {1}' nameGenerator: @@ -25,6 +23,7 @@ Bartender: [ 1, 1 ] Botanist: [ 1, 1 ] Clown: [ 1, 1 ] + Chef: [ 1, 1 ] Lawyer: [ 1, 1 ] Reporter: [ 1, 1 ] Musician: [ 1, 1 ] From 93469accd3b4e02844a7068e550722491b969920 Mon Sep 17 00:00:00 2001 From: DeltaV-Bot <135767721+DeltaV-Bot@users.noreply.github.com> Date: Fri, 31 May 2024 23:16:09 +0000 Subject: [PATCH 002/235] Automatic changelog update --- Resources/Changelog/DeltaVChangelog.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index ad0aa8c5aca..774e4f23bb7 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -2512,3 +2512,11 @@ id: 369 time: '2024-05-29T04:10:40.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/1251 +- author: Colin-Tel + changes: + - message: Micro now has a second solar array in the southeast of the station, and + its arrivals should function as an alternate evac. + type: Tweak + id: 370 + time: '2024-05-31T23:15:41.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/1274 From 7c7872a89fa30e9461bc72f231696d0d38244b57 Mon Sep 17 00:00:00 2001 From: double_b <40827162+benjamin-burges@users.noreply.github.com> Date: Sat, 18 May 2024 14:23:16 +0000 Subject: [PATCH 003/235] Resolves Bible summon message being sent to all users (#28104) * Changed PopupEntity overload used to ensure message is only sent to user * Updated uid for PopupEntity call * Updating _popupSystem.PopupEntity call in AttemptSummon --- Content.Server/Bible/BibleSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index c845b17230a..0c60e40dacb 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -241,7 +241,7 @@ private void AttemptSummon(Entity ent, EntityUid user, Tran // If this is going to use a ghost role mob spawner, attach it to the bible. if (HasComp(familiar)) { - _popupSystem.PopupEntity(Loc.GetString("bible-summon-requested"), user, PopupType.Medium); + _popupSystem.PopupEntity(Loc.GetString("bible-summon-requested"), user, user, PopupType.Medium); _transform.SetParent(familiar, uid); } component.AlreadySummoned = true; From a8407c7d3a0052a0ab5b9145efe49d273724a30a Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 18 May 2024 14:24:24 +0000 Subject: [PATCH 004/235] 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 6ad80313b34..9ba439bc51f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Pod launches will now be offset slightly. - type: Add - id: 6102 - time: '2024-03-06T01:44:26.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25855 - author: Brandon-Huu changes: - message: The Syndicate is now supplying barber scissors in the Hristov bundle @@ -3865,3 +3858,11 @@ id: 6601 time: '2024-05-17T19:46:45.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28083 +- author: benjamin-burges + changes: + - message: Bible summon messages are now only visible to the chaplain using the + Bible. + type: Tweak + id: 6602 + time: '2024-05-18T14:23:17.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28104 From e0820b8b132a6e8f17c1529fb0f82a13c1ee1f5e Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Sat, 18 May 2024 19:17:46 +0300 Subject: [PATCH 005/235] Nuke spaceshroom ore (#28110) nuke spaceshroom ore --- .../Components/GatherableComponent.cs | 18 +++++++++---- .../Gatherable/GatherableSystem.Projectile.cs | 14 +++++----- Content.Server/Gatherable/GatherableSystem.cs | 27 ++++++++----------- .../Entities/Objects/Misc/spaceshroom.yml | 15 +++++++---- .../Entities/Structures/Walls/asteroid.yml | 2 +- Resources/Prototypes/ore.yml | 4 --- 6 files changed, 41 insertions(+), 39 deletions(-) diff --git a/Content.Server/Gatherable/Components/GatherableComponent.cs b/Content.Server/Gatherable/Components/GatherableComponent.cs index f1d0c6ef74d..5480f4404fc 100644 --- a/Content.Server/Gatherable/Components/GatherableComponent.cs +++ b/Content.Server/Gatherable/Components/GatherableComponent.cs @@ -1,4 +1,6 @@ +using Content.Shared.EntityList; using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; namespace Content.Server.Gatherable.Components; @@ -10,7 +12,7 @@ public sealed partial class GatherableComponent : Component /// Whitelist for specifying the kind of tools can be used on a resource /// Supports multiple tags. /// - [DataField("whitelist", required: true)] + [DataField(required: true)] public EntityWhitelist? ToolWhitelist; /// @@ -18,14 +20,20 @@ public sealed partial class GatherableComponent : Component /// (Tag1, Tag2, LootTableID1, LootTableID2 are placeholders for example) /// -------------------- /// useMappedLoot: true - /// whitelist: + /// toolWhitelist: /// tags: /// - Tag1 /// - Tag2 - /// mappedLoot: + /// loot: /// Tag1: LootTableID1 /// Tag2: LootTableID2 /// - [DataField("loot")] - public Dictionary? MappedLoot = new(); + [DataField] + public Dictionary>? Loot = new(); + + /// + /// Random shift of the appearing entity during gathering + /// + [DataField] + public float GatherOffset = 0.3f; } diff --git a/Content.Server/Gatherable/GatherableSystem.Projectile.cs b/Content.Server/Gatherable/GatherableSystem.Projectile.cs index f773ca2dbb9..3ab8872fd7d 100644 --- a/Content.Server/Gatherable/GatherableSystem.Projectile.cs +++ b/Content.Server/Gatherable/GatherableSystem.Projectile.cs @@ -1,7 +1,5 @@ using Content.Server.Gatherable.Components; -using Content.Server.Projectiles; using Content.Shared.Projectiles; -using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Physics.Events; namespace Content.Server.Gatherable; @@ -13,20 +11,20 @@ private void InitializeProjectile() SubscribeLocalEvent(OnProjectileCollide); } - private void OnProjectileCollide(EntityUid uid, GatheringProjectileComponent component, ref StartCollideEvent args) + private void OnProjectileCollide(Entity gathering, ref StartCollideEvent args) { if (!args.OtherFixture.Hard || args.OurFixtureId != SharedProjectileSystem.ProjectileFixture || - component.Amount <= 0 || + gathering.Comp.Amount <= 0 || !TryComp(args.OtherEntity, out var gatherable)) { return; } - Gather(args.OtherEntity, uid, gatherable); - component.Amount--; + Gather(args.OtherEntity, gathering, gatherable); + gathering.Comp.Amount--; - if (component.Amount <= 0) - QueueDel(uid); + if (gathering.Comp.Amount <= 0) + QueueDel(gathering); } } diff --git a/Content.Server/Gatherable/GatherableSystem.cs b/Content.Server/Gatherable/GatherableSystem.cs index 11295bb3a35..44e60cb102a 100644 --- a/Content.Server/Gatherable/GatherableSystem.cs +++ b/Content.Server/Gatherable/GatherableSystem.cs @@ -1,11 +1,9 @@ using Content.Server.Destructible; using Content.Server.Gatherable.Components; -using Content.Shared.EntityList; using Content.Shared.Interaction; using Content.Shared.Tag; using Content.Shared.Weapons.Melee.Events; using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -14,7 +12,7 @@ namespace Content.Server.Gatherable; public sealed partial class GatherableSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly DestructibleSystem _destructible = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -30,20 +28,20 @@ public override void Initialize() InitializeProjectile(); } - private void OnAttacked(EntityUid uid, GatherableComponent component, AttackedEvent args) + private void OnAttacked(Entity gatherable, ref AttackedEvent args) { - if (component.ToolWhitelist?.IsValid(args.Used, EntityManager) != true) + if (gatherable.Comp.ToolWhitelist?.IsValid(args.Used, EntityManager) != true) return; - Gather(uid, args.User, component); + Gather(gatherable, args.User); } - private void OnActivate(EntityUid uid, GatherableComponent component, ActivateInWorldEvent args) + private void OnActivate(Entity gatherable, ref ActivateInWorldEvent args) { - if (component.ToolWhitelist?.IsValid(args.User, EntityManager) != true) + if (gatherable.Comp.ToolWhitelist?.IsValid(args.User, EntityManager) != true) return; - Gather(uid, args.User, component); + Gather(gatherable, args.User); } public void Gather(EntityUid gatheredUid, EntityUid? gatherer = null, GatherableComponent? component = null) @@ -60,25 +58,22 @@ public void Gather(EntityUid gatheredUid, EntityUid? gatherer = null, Gatherable _destructible.DestroyEntity(gatheredUid); // Spawn the loot! - if (component.MappedLoot == null) + if (component.Loot == null) return; var pos = _transform.GetMapCoordinates(gatheredUid); - foreach (var (tag, table) in component.MappedLoot) + foreach (var (tag, table) in component.Loot) { if (tag != "All") { if (gatherer != null && !_tagSystem.HasTag(gatherer.Value, tag)) continue; } - var getLoot = _prototypeManager.Index(table); + var getLoot = _proto.Index(table); var spawnLoot = getLoot.GetSpawns(_random); - var spawnPos = pos.Offset(_random.NextVector2(0.3f)); + var spawnPos = pos.Offset(_random.NextVector2(component.GatherOffset)); Spawn(spawnLoot[0], spawnPos); } } } - - - diff --git a/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml b/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml index 9ec6ce0ed11..15717877157 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml @@ -19,14 +19,12 @@ !type:PhysShapeCircle radius: 0.2 - type: InteractionOutline - # TODO: Nuke this shit - - type: OreVein - oreChance: 1.0 - currentOre: SpaceShrooms - type: Gatherable - whitelist: + toolWhitelist: components: - Hands + loot: + All: SpaceshroomGather - type: Damageable damageContainer: Inorganic damageModifierSet: Wood @@ -39,6 +37,13 @@ - !type:DoActsBehavior acts: [ "Destruction" ] +- type: entityLootTable + id: SpaceshroomGather + entries: + - id: FoodSpaceshroom + amount: 1 + maxAmount: 1 + - type: entity name: spaceshroom parent: FoodProduceBase diff --git a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml index a8cb0b1cb8a..c6c3692e598 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml @@ -375,7 +375,7 @@ noRot: true - type: SoundOnGather - type: Gatherable - whitelist: + toolWhitelist: tags: - Pickaxe - type: Damageable diff --git a/Resources/Prototypes/ore.yml b/Resources/Prototypes/ore.yml index dde1516c855..84d1c667369 100644 --- a/Resources/Prototypes/ore.yml +++ b/Resources/Prototypes/ore.yml @@ -1,9 +1,5 @@ # TODO: Kill ore veins # Split it into 2 components, 1 for "spawn XYZ on destruction" and 1 for "randomly select one of these for spawn on destruction" -# You could even just use an entityspawncollection instead. -- type: ore - id: SpaceShrooms - oreEntity: FoodSpaceshroom # High yields - type: ore From e5bbb0a4bcf04856af496a0da7ddfa41f0262bac Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sat, 18 May 2024 12:20:00 -0400 Subject: [PATCH 006/235] Random book story generator refactor (#28082) * Randomized book overhaul * Fix prototype names * Improved setting paper content * Praise Ratvar --- .../Paper/PaperRandomStoryComponent.cs | 13 +- .../Paper/PaperRandomStorySystem.cs | 12 +- .../EntitySystems/StoryGeneratorSystem.cs | 54 +++++ .../Prototypes/StoryTemplatePrototype.cs | 33 +++ .../Locale/en-US/paper/story-generation.ftl | 72 +++---- .../Locale/en-US/storygen/story-template.ftl | 4 + .../Prototypes/Datasets/story_generation.yml | 204 +++++++++--------- .../Entities/Objects/Misc/books.yml | 32 +-- .../Prototypes/StoryGen/story-templates.yml | 16 ++ 9 files changed, 261 insertions(+), 179 deletions(-) create mode 100644 Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs create mode 100644 Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs create mode 100644 Resources/Locale/en-US/storygen/story-template.ftl create mode 100644 Resources/Prototypes/StoryGen/story-templates.yml diff --git a/Content.Server/Paper/PaperRandomStoryComponent.cs b/Content.Server/Paper/PaperRandomStoryComponent.cs index 7c5744f0878..b8e07f0ee81 100644 --- a/Content.Server/Paper/PaperRandomStoryComponent.cs +++ b/Content.Server/Paper/PaperRandomStoryComponent.cs @@ -1,14 +1,17 @@ +using Content.Shared.StoryGen; +using Robust.Shared.Prototypes; + namespace Content.Server.Paper; /// -/// Adds randomly generated stories to Paper component +/// Adds a randomly generated story to the content of a /// [RegisterComponent, Access(typeof(PaperRandomStorySystem))] public sealed partial class PaperRandomStoryComponent : Component { + /// + /// The ID to use for story generation. + /// [DataField] - public List? StorySegments; - - [DataField] - public string StorySeparator = " "; + public ProtoId Template; } diff --git a/Content.Server/Paper/PaperRandomStorySystem.cs b/Content.Server/Paper/PaperRandomStorySystem.cs index e7712009c2e..156718f5450 100644 --- a/Content.Server/Paper/PaperRandomStorySystem.cs +++ b/Content.Server/Paper/PaperRandomStorySystem.cs @@ -1,11 +1,11 @@ -using Content.Server.RandomMetadata; +using Content.Shared.StoryGen; namespace Content.Server.Paper; public sealed class PaperRandomStorySystem : EntitySystem { - - [Dependency] private readonly RandomMetadataSystem _randomMeta = default!; + [Dependency] private readonly StoryGeneratorSystem _storyGen = default!; + [Dependency] private readonly PaperSystem _paper = default!; public override void Initialize() { @@ -19,11 +19,9 @@ private void OnMapinit(Entity paperStory, ref MapInit if (!TryComp(paperStory, out var paper)) return; - if (paperStory.Comp.StorySegments == null) + if (!_storyGen.TryGenerateStoryFromTemplate(paperStory.Comp.Template, out var story)) return; - var story = _randomMeta.GetRandomFromSegments(paperStory.Comp.StorySegments, paperStory.Comp.StorySeparator); - - paper.Content += $"\n{story}"; + _paper.SetContent(paperStory.Owner, story, paper); } } diff --git a/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs b/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs new file mode 100644 index 00000000000..51ad85730c7 --- /dev/null +++ b/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Collections; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared.StoryGen; + +/// +/// Provides functionality to generate a story from a . +/// +public sealed partial class StoryGeneratorSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + /// + /// Tries to generate a random story using the given template, picking a random word from the referenced + /// datasets for each variable and passing them into the localization system with template. + /// If is specified, the randomizer will be seeded with it for consistent story generation; + /// otherwise the variables will be randomized. + /// Fails if the template prototype cannot be loaded. + /// + /// true if the template was loaded, otherwise false. + public bool TryGenerateStoryFromTemplate(ProtoId template, [NotNullWhen(true)] out string? story, int? seed = null) + { + // Get the story template prototype from the ID + if (!_protoMan.TryIndex(template, out var templateProto)) + { + story = null; + return false; + } + + // If given a seed, use it + if (seed != null) + _random.SetSeed(seed.Value); + + // Pick values for all of the variables in the template + var variables = new ValueList<(string, object)>(templateProto.Variables.Count); + foreach (var (name, list) in templateProto.Variables) + { + // Get the prototype for the world list dataset + if (!_protoMan.TryIndex(list, out var listProto)) + continue; // Missed one, but keep going with the rest of the story + + // Pick a random word from the dataset and localize it + var chosenWord = Loc.GetString(_random.Pick(listProto.Values)); + variables.Add((name, chosenWord)); + } + + // Pass the variables to the localization system and build the story + story = Loc.GetString(templateProto.LocId, variables.ToArray()); + return true; + } +} diff --git a/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs new file mode 100644 index 00000000000..7f6afacccc5 --- /dev/null +++ b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs @@ -0,0 +1,33 @@ +using Content.Shared.Dataset; +using Robust.Shared.Prototypes; + +namespace Content.Shared.StoryGen; + +/// +/// Prototype for a story template that can be filled in with words chosen from s. +/// +[Serializable, Prototype("storyTemplate")] +public sealed partial class StoryTemplatePrototype : IPrototype +{ + /// + /// Identifier for this prototype instance. + /// + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// Localization ID of the Fluent string that forms the structure of this story. + /// + [DataField(required: true)] + public LocId LocId { get; } = default!; + + /// + /// Dictionary containing the name of each variable to pass to the template and the ID of the + /// from which a random entry will be selected as its value. + /// For example, name: book_character will pick a random entry from the book_character + /// dataset which can then be used in the template by {$name}. + /// + [DataField] + public Dictionary> Variables { get; } = default!; +} diff --git a/Resources/Locale/en-US/paper/story-generation.ftl b/Resources/Locale/en-US/paper/story-generation.ftl index bcd1c8901e0..94ecbc3caa5 100644 --- a/Resources/Locale/en-US/paper/story-generation.ftl +++ b/Resources/Locale/en-US/paper/story-generation.ftl @@ -86,7 +86,7 @@ story-gen-book-character29 = space dragon story-gen-book-character30 = revolutionary story-gen-book-character31 = nuclear operative story-gen-book-character32 = narsie cultist -story-gen-book-character33 = ratwar cultist +story-gen-book-character33 = ratvar cultist story-gen-book-character34 = greytider story-gen-book-character35 = arachnid story-gen-book-character36 = vox @@ -98,7 +98,7 @@ story-gen-book-character40 = slime story-gen-book-character-trait1 = stupid story-gen-book-character-trait2 = smart story-gen-book-character-trait3 = funny -story-gen-book-character-trait4 = attractive +story-gen-book-character-trait4 = attractive story-gen-book-character-trait5 = charming story-gen-book-character-trait6 = nasty story-gen-book-character-trait7 = dying @@ -113,7 +113,7 @@ story-gen-book-character-trait15 = сharismatic story-gen-book-character-trait16 = stoic story-gen-book-character-trait17 = cute story-gen-book-character-trait18 = dwarven -story-gen-book-character-trait19 = beer-smelling +story-gen-book-character-trait19 = beer-smelling story-gen-book-character-trait20 = joyful story-gen-book-character-trait21 = painfully beautiful story-gen-book-character-trait22 = robotic @@ -121,20 +121,20 @@ story-gen-book-character-trait23 = holographic story-gen-book-character-trait24 = hysterically laughing story-gen-book-event1 = a zombie outbreak -story-gen-book-event2 = a nuclear explosion +story-gen-book-event2 = a nuclear explosion story-gen-book-event3 = a mass murder story-gen-book-event4 = a sudden depressurization story-gen-book-event5 = a blackout -story-gen-book-event6 = the starvation of the protagonists +story-gen-book-event6 = the protagonists nearly starving story-gen-book-event7 = a wasting illness story-gen-book-event8 = love at first sight story-gen-book-event9 = a rush of inspiration -story-gen-book-event10 = the occurrence of some mystical phenomena +story-gen-book-event10 = some mystical phenomena story-gen-book-event11 = divine intervention story-gen-book-event12 = the characters' own selfish motives story-gen-book-event13 = an unforeseen deception -story-gen-book-event14 = the resurrection of one of these characters from the dead -story-gen-book-event15 = the terrible torture of the protagonist +story-gen-book-event14 = the resurrection of one of the characters from the dead +story-gen-book-event15 = the brutal torture of the protagonists story-gen-book-event16 = the inadvertent loosing of a gravitational singularity story-gen-book-event17 = a psychic prediction of future events story-gen-book-event18 = an antimatter explosion @@ -145,31 +145,31 @@ story-gen-book-event22 = having a quarrel with a close friend story-gen-book-event23 = the sudden loss of their home in a fiery blaze story-gen-book-event24 = the loss of a PDA -story-gen-book-action1 = share in a kiss with a -story-gen-book-action2 = strangle to death a -story-gen-book-action3 = manage to blow apart a -story-gen-book-action4 = manage to win a game of chess against a -story-gen-book-action5 = narrowly lose a game of chess against a -story-gen-book-action6 = reveal the hidden secrets of a -story-gen-book-action7 = manipulate a -story-gen-book-action8 = sacrifice upon an altar a -story-gen-book-action9 = attend the wedding of a -story-gen-book-action10 = join forces to defeat their common enemy, a -story-gen-book-action11 = are forced to work together to escape a +story-gen-book-action1 = share in a kiss with +story-gen-book-action2 = strangle +story-gen-book-action3 = blow apart +story-gen-book-action4 = win a game of chess against +story-gen-book-action5 = lose a game of chess against +story-gen-book-action6 = reveal the hidden secrets of +story-gen-book-action7 = manipulate +story-gen-book-action8 = sacrifice a hamster to +story-gen-book-action9 = infiltrate the wedding of +story-gen-book-action10 = join forces to defeat their common enemy, +story-gen-book-action11 = are forced to work together to escape story-gen-book-action12 = give a valuable gift to -story-gen-book-action-trait1 = terribly -story-gen-book-action-trait2 = disgustingly +story-gen-book-action-trait1 = clumsily +story-gen-book-action-trait2 = disgustingly story-gen-book-action-trait3 = marvelously story-gen-book-action-trait4 = nicely story-gen-book-action-trait5 = weirdly story-gen-book-action-trait6 = amusingly story-gen-book-action-trait7 = fancifully story-gen-book-action-trait8 = impressively -story-gen-book-action-trait9 = irresponsibly -story-gen-book-action-trait10 = severely -story-gen-book-action-trait11 = ruthlessly -story-gen-book-action-trait12 = playfully +story-gen-book-action-trait9 = irresponsibly +story-gen-book-action-trait10 = severely +story-gen-book-action-trait11 = ruthlessly +story-gen-book-action-trait12 = playfully story-gen-book-action-trait13 = thoughtfully story-gen-book-location1 = in an underground complex @@ -178,7 +178,7 @@ story-gen-book-location3 = while trapped in outer space story-gen-book-location4 = while in a news office story-gen-book-location5 = in a hidden garden story-gen-book-location6 = in the kitchen of a local restaurant -story-gen-book-location7 = under the counter of the local sports bar +story-gen-book-location7 = under the counter of the local sports bar story-gen-book-location8 = in an ancient library story-gen-book-location9 = while deep in bowels of the space station's maintenance corridors story-gen-book-location10 = on the bridge of a starship @@ -192,7 +192,7 @@ story-gen-book-location17 = standing too close to an anomaly story-gen-book-location18 = while huddling on the evacuation shuttle story-gen-book-location19 = standing in freshly fallen snow story-gen-book-location20 = lost in the woods -story-gen-book-location21 = iin the harsh desert +story-gen-book-location21 = in the harsh desert story-gen-book-location22 = worrying about their social media networks story-gen-book-location23 = atop of a mountain story-gen-book-location24 = while driving a car @@ -207,15 +207,15 @@ story-gen-book-location32 = while trapped in a shadow dimension story-gen-book-location33 = while trying to escape a destroyed space station story-gen-book-location34 = while sandwiched between a Tesla ball and a gravitational singularity -story-gen-book-element1 = The plot -story-gen-book-element2 = The twist -story-gen-book-element3 = The climax -story-gen-book-element4 = The final act -story-gen-book-element5 = The ending -story-gen-book-element6 = The moral of the story -story-gen-book-element7 = The theme of this work -story-gen-book-element8 = The literary style -story-gen-book-element9 = The illustrations +story-gen-book-element1 = plot +story-gen-book-element2 = twist +story-gen-book-element3 = climax +story-gen-book-element4 = final act +story-gen-book-element5 = ending +story-gen-book-element6 = moral of the story +story-gen-book-element7 = theme of this work +story-gen-book-element8 = literary style +story-gen-book-element9 = artwork story-gen-book-element-trait1 = terrifying story-gen-book-element-trait2 = disgusting diff --git a/Resources/Locale/en-US/storygen/story-template.ftl b/Resources/Locale/en-US/storygen/story-template.ftl new file mode 100644 index 00000000000..b535e2fd95e --- /dev/null +++ b/Resources/Locale/en-US/storygen/story-template.ftl @@ -0,0 +1,4 @@ +story-template-generic = + This is { INDEFINITE($bookGenre) } {$bookGenre} about { INDEFINITE($char1Adj) } {$char1Adj} {$char1Type} and { INDEFINITE($char2Adj) } {$char2Adj} {$char2Type}. Due to {$event}, they {$actionTrait} {$action} { INDEFINITE($char3Type) } {$char3Type} {$location}. + + The {$element} is {$elementTrait}. diff --git a/Resources/Prototypes/Datasets/story_generation.yml b/Resources/Prototypes/Datasets/story_generation.yml index 1083a6acdb6..1a461c7596d 100644 --- a/Resources/Prototypes/Datasets/story_generation.yml +++ b/Resources/Prototypes/Datasets/story_generation.yml @@ -1,31 +1,31 @@ - type: dataset - id: book_type + id: BookTypes values: - - story-gen-book-type1 - - story-gen-book-type2 - - story-gen-book-type3 - - story-gen-book-type4 - - story-gen-book-type5 - - story-gen-book-type6 - - story-gen-book-type7 - - story-gen-book-type8 - - story-gen-book-type9 + - story-gen-book-type1 + - story-gen-book-type2 + - story-gen-book-type3 + - story-gen-book-type4 + - story-gen-book-type5 + - story-gen-book-type6 + - story-gen-book-type7 + - story-gen-book-type8 + - story-gen-book-type9 - story-gen-book-type10 - story-gen-book-type11 - story-gen-book-type12 - type: dataset - id: book_genre + id: BookGenres values: - - story-gen-book-genre1 - - story-gen-book-genre2 - - story-gen-book-genre3 - - story-gen-book-genre4 - - story-gen-book-genre5 - - story-gen-book-genre6 - - story-gen-book-genre7 - - story-gen-book-genre8 - - story-gen-book-genre9 + - story-gen-book-genre1 + - story-gen-book-genre2 + - story-gen-book-genre3 + - story-gen-book-genre4 + - story-gen-book-genre5 + - story-gen-book-genre6 + - story-gen-book-genre7 + - story-gen-book-genre8 + - story-gen-book-genre9 - story-gen-book-genre10 - story-gen-book-genre11 - story-gen-book-genre12 @@ -33,17 +33,17 @@ - story-gen-book-genre14 - type: dataset - id: book_hint_appearance + id: BookHintAppearances values: - - story-gen-book-appearance1 - - story-gen-book-appearance2 - - story-gen-book-appearance3 - - story-gen-book-appearance4 - - story-gen-book-appearance5 - - story-gen-book-appearance6 - - story-gen-book-appearance7 - - story-gen-book-appearance8 - - story-gen-book-appearance9 + - story-gen-book-appearance1 + - story-gen-book-appearance2 + - story-gen-book-appearance3 + - story-gen-book-appearance4 + - story-gen-book-appearance5 + - story-gen-book-appearance6 + - story-gen-book-appearance7 + - story-gen-book-appearance8 + - story-gen-book-appearance9 - story-gen-book-appearance10 - story-gen-book-appearance11 - story-gen-book-appearance12 @@ -64,17 +64,17 @@ - story-gen-book-appearance27 - type: dataset - id: book_character + id: BookCharacters values: - - story-gen-book-character1 - - story-gen-book-character2 - - story-gen-book-character3 - - story-gen-book-character4 - - story-gen-book-character5 - - story-gen-book-character6 - - story-gen-book-character7 - - story-gen-book-character8 - - story-gen-book-character9 + - story-gen-book-character1 + - story-gen-book-character2 + - story-gen-book-character3 + - story-gen-book-character4 + - story-gen-book-character5 + - story-gen-book-character6 + - story-gen-book-character7 + - story-gen-book-character8 + - story-gen-book-character9 - story-gen-book-character10 - story-gen-book-character11 - story-gen-book-character12 @@ -108,17 +108,17 @@ - story-gen-book-character40 - type: dataset - id: book_character_trait + id: BookCharacterTraits values: - - story-gen-book-character-trait1 - - story-gen-book-character-trait2 - - story-gen-book-character-trait3 - - story-gen-book-character-trait4 - - story-gen-book-character-trait5 - - story-gen-book-character-trait6 - - story-gen-book-character-trait7 - - story-gen-book-character-trait8 - - story-gen-book-character-trait9 + - story-gen-book-character-trait1 + - story-gen-book-character-trait2 + - story-gen-book-character-trait3 + - story-gen-book-character-trait4 + - story-gen-book-character-trait5 + - story-gen-book-character-trait6 + - story-gen-book-character-trait7 + - story-gen-book-character-trait8 + - story-gen-book-character-trait9 - story-gen-book-character-trait10 - story-gen-book-character-trait11 - story-gen-book-character-trait12 @@ -137,17 +137,17 @@ - type: dataset - id: book_event + id: BookEvents values: - - story-gen-book-event1 - - story-gen-book-event2 - - story-gen-book-event3 - - story-gen-book-event4 - - story-gen-book-event5 - - story-gen-book-event6 - - story-gen-book-event7 - - story-gen-book-event8 - - story-gen-book-event9 + - story-gen-book-event1 + - story-gen-book-event2 + - story-gen-book-event3 + - story-gen-book-event4 + - story-gen-book-event5 + - story-gen-book-event6 + - story-gen-book-event7 + - story-gen-book-event8 + - story-gen-book-event9 - story-gen-book-event10 - story-gen-book-event11 - story-gen-book-event12 @@ -165,50 +165,50 @@ - story-gen-book-event24 - type: dataset - id: book_action + id: BookActions values: - - story-gen-book-action1 - - story-gen-book-action2 - - story-gen-book-action3 - - story-gen-book-action4 - - story-gen-book-action5 - - story-gen-book-action6 - - story-gen-book-action7 - - story-gen-book-action8 - - story-gen-book-action9 + - story-gen-book-action1 + - story-gen-book-action2 + - story-gen-book-action3 + - story-gen-book-action4 + - story-gen-book-action5 + - story-gen-book-action6 + - story-gen-book-action7 + - story-gen-book-action8 + - story-gen-book-action9 - story-gen-book-action10 - story-gen-book-action11 - story-gen-book-action12 - type: dataset - id: book_action_trait + id: BookActionTraits values: - - story-gen-book-action-trait1 - - story-gen-book-action-trait2 - - story-gen-book-action-trait3 - - story-gen-book-action-trait4 - - story-gen-book-action-trait5 - - story-gen-book-action-trait6 - - story-gen-book-action-trait7 - - story-gen-book-action-trait8 - - story-gen-book-action-trait9 + - story-gen-book-action-trait1 + - story-gen-book-action-trait2 + - story-gen-book-action-trait3 + - story-gen-book-action-trait4 + - story-gen-book-action-trait5 + - story-gen-book-action-trait6 + - story-gen-book-action-trait7 + - story-gen-book-action-trait8 + - story-gen-book-action-trait9 - story-gen-book-action-trait10 - story-gen-book-action-trait11 - story-gen-book-action-trait12 - story-gen-book-action-trait13 - type: dataset - id: book_location + id: BookLocations values: - - story-gen-book-location1 - - story-gen-book-location2 - - story-gen-book-location3 - - story-gen-book-location4 - - story-gen-book-location5 - - story-gen-book-location6 - - story-gen-book-location7 - - story-gen-book-location8 - - story-gen-book-location9 + - story-gen-book-location1 + - story-gen-book-location2 + - story-gen-book-location3 + - story-gen-book-location4 + - story-gen-book-location5 + - story-gen-book-location6 + - story-gen-book-location7 + - story-gen-book-location8 + - story-gen-book-location9 - story-gen-book-location10 - story-gen-book-location11 - story-gen-book-location12 @@ -236,7 +236,7 @@ - story-gen-book-location34 - type: dataset - id: book_story_element + id: BookStoryElements values: - story-gen-book-element1 - story-gen-book-element2 @@ -249,18 +249,18 @@ - story-gen-book-element9 - type: dataset - id: book_story_element_trait + id: BookStoryElementTraits values: - - story-gen-book-element-trait1 - - story-gen-book-element-trait2 - - story-gen-book-element-trait3 - - story-gen-book-element-trait4 - - story-gen-book-element-trait5 - - story-gen-book-element-trait6 - - story-gen-book-element-trait7 - - story-gen-book-element-trait8 - - story-gen-book-element-trait9 + - story-gen-book-element-trait1 + - story-gen-book-element-trait2 + - story-gen-book-element-trait3 + - story-gen-book-element-trait4 + - story-gen-book-element-trait5 + - story-gen-book-element-trait6 + - story-gen-book-element-trait7 + - story-gen-book-element-trait8 + - story-gen-book-element-trait9 - story-gen-book-element-trait10 - story-gen-book-element-trait11 - story-gen-book-element-trait12 - - story-gen-book-element-trait13 \ No newline at end of file + - story-gen-book-element-trait13 diff --git a/Resources/Prototypes/Entities/Objects/Misc/books.yml b/Resources/Prototypes/Entities/Objects/Misc/books.yml index 25e6bb9f943..3fc90048dd5 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/books.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/books.yml @@ -361,8 +361,8 @@ components: - type: RandomMetadata nameSegments: - - book_hint_appearance - - book_type + - BookHintAppearances + - BookTypes - type: RandomSprite available: - cover: @@ -423,33 +423,7 @@ suffix: random visual, random story components: - type: PaperRandomStory - storySegments: - - "This is a " - - book_genre - - " about a " - - book_character_trait - - " " - - book_character - - " and " - - book_character_trait - - " " - - book_character - - ". Due to " - - book_event - - ", they " - - book_action_trait - - " " - - book_action - - " " - - book_character - - " " - - book_location - - ". \n\n" - - book_story_element - - " is " - - book_story_element_trait - - "." - storySeparator: "" + template: GenericStory - type: entity parent: BookBase diff --git a/Resources/Prototypes/StoryGen/story-templates.yml b/Resources/Prototypes/StoryGen/story-templates.yml new file mode 100644 index 00000000000..f05fd5afa6e --- /dev/null +++ b/Resources/Prototypes/StoryGen/story-templates.yml @@ -0,0 +1,16 @@ +- type: storyTemplate + id: GenericStory + locId: story-template-generic + variables: + bookGenre: BookGenres + char1Type: BookCharacters + char1Adj: BookCharacterTraits + char2Type: BookCharacters + char2Adj: BookCharacterTraits + event: BookEvents + action: BookActions + actionTrait: BookActionTraits + char3Type: BookCharacters + location: BookLocations + element: BookStoryElements + elementTrait: BookStoryElementTraits From 1f3687cc4134d223b3dbb70fcc013d7180a2a5c1 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Sat, 18 May 2024 22:06:36 +0300 Subject: [PATCH 007/235] Tile variantize after deconstruct (#28118) Update TileSystem.cs --- Content.Shared/Maps/TileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Maps/TileSystem.cs b/Content.Shared/Maps/TileSystem.cs index 3f7852cfe60..430a5cc10be 100644 --- a/Content.Shared/Maps/TileSystem.cs +++ b/Content.Shared/Maps/TileSystem.cs @@ -179,7 +179,7 @@ public bool DeconstructTile(TileRef tileRef) } var plating = _tileDefinitionManager[tileDef.BaseTurf]; - _maps.SetTile(gridUid, mapGrid, tileRef.GridIndices, new Tile(plating.TileId)); + _maps.SetTile(gridUid, mapGrid, tileRef.GridIndices, new Tile(plating.TileId, variant: PickVariant(tileDef))); return true; } From d9e5a8dbaee8f9e4ffbe2581c7b505ffe0a0e25c Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Sat, 18 May 2024 23:23:40 +0300 Subject: [PATCH 008/235] Revert "Tile variantize after deconstruct" (#28119) Revert "Tile variantize after deconstruct (#28118)" This reverts commit bf0529df566c323820d5b7497bc55e75992f105c. --- Content.Shared/Maps/TileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Maps/TileSystem.cs b/Content.Shared/Maps/TileSystem.cs index 430a5cc10be..3f7852cfe60 100644 --- a/Content.Shared/Maps/TileSystem.cs +++ b/Content.Shared/Maps/TileSystem.cs @@ -179,7 +179,7 @@ public bool DeconstructTile(TileRef tileRef) } var plating = _tileDefinitionManager[tileDef.BaseTurf]; - _maps.SetTile(gridUid, mapGrid, tileRef.GridIndices, new Tile(plating.TileId, variant: PickVariant(tileDef))); + _maps.SetTile(gridUid, mapGrid, tileRef.GridIndices, new Tile(plating.TileId)); return true; } From d000edce59faff0a104523822a049301a153bbb3 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sat, 18 May 2024 20:12:47 -0400 Subject: [PATCH 009/235] Fix activatable UI popup message spam (#28123) Fixed activatable UI popup message spam --- Content.Shared/UserInterface/ActivatableUISystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index 3ac8835dd02..a6d27ac5459 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -215,7 +215,7 @@ private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUICompone if (aui.SingleUser && aui.CurrentSingleUser != null && user != aui.CurrentSingleUser) { var message = Loc.GetString("machine-already-in-use", ("machine", uiEntity)); - _popupSystem.PopupEntity(message, uiEntity, user); + _popupSystem.PopupClient(message, uiEntity, user); if (_uiSystem.IsUiOpen(uiEntity, aui.Key)) return true; From 24bb967ffce73be9894de88d419ce5a11e3307b3 Mon Sep 17 00:00:00 2001 From: dffdff2423 Date: Sat, 18 May 2024 19:23:44 -0500 Subject: [PATCH 010/235] Fix Flavor Text editor not saving correctly (#28122) * Check for flavor text equality in profile. * Fix some characters being deleted from flavor text Sometimes the last few characters of the flavor text would get deleted. This fixes this issue. --- Content.Client/FlavorText/FlavorText.xaml.cs | 2 +- Content.Shared/Preferences/HumanoidCharacterProfile.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Content.Client/FlavorText/FlavorText.xaml.cs b/Content.Client/FlavorText/FlavorText.xaml.cs index ffcf653f119..91b59046a47 100644 --- a/Content.Client/FlavorText/FlavorText.xaml.cs +++ b/Content.Client/FlavorText/FlavorText.xaml.cs @@ -17,7 +17,7 @@ public FlavorText() var loc = IoCManager.Resolve(); CFlavorTextInput.Placeholder = new Rope.Leaf(loc.GetString("flavor-text-placeholder")); - CFlavorTextInput.OnKeyBindDown += _ => FlavorTextChanged(); + CFlavorTextInput.OnTextChanged += _ => FlavorTextChanged(); } public void FlavorTextChanged() diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 6a297bb1f2a..682b0d700f6 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -396,6 +396,7 @@ public bool MemberwiseEquals(ICharacterProfile maybeOther) if (!_antagPreferences.SequenceEqual(other._antagPreferences)) return false; if (!_traitPreferences.SequenceEqual(other._traitPreferences)) return false; if (!Loadouts.SequenceEqual(other.Loadouts)) return false; + if (FlavorText != other.FlavorText) return false; return Appearance.MemberwiseEquals(other.Appearance); } From 0df79c23075b5b459fbc437ea1553c2d691cbf08 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 19 May 2024 00:24:50 +0000 Subject: [PATCH 011/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 9ba439bc51f..e4b72cd303a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Brandon-Huu - changes: - - message: The Syndicate is now supplying barber scissors in the Hristov bundle - to make your disguises even balde- better. - type: Tweak - id: 6103 - time: '2024-03-06T02:03:08.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25695 - author: Tayrtahn changes: - message: Flasks can once again be put into dispensers. @@ -3866,3 +3858,12 @@ id: 6602 time: '2024-05-18T14:23:17.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28104 +- author: Aquif + changes: + - message: Changing your character's description now properly updates the save button. + type: Fix + - message: The character description editor does not discard your last edit anymore. + type: Fix + id: 6603 + time: '2024-05-19T00:23:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28122 From 198b67bef6e1118a7243f644982792c0c7dda968 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 18 May 2024 18:35:46 -0700 Subject: [PATCH 012/235] Make wielding automatically drop the item on your other hand (#27975) * Make wielding automatically drop the item on your other hand * Fix docs * Remove redundant parameter * Fix not deleting virtuals on fail * Make count freeable hands method * Add popup when dropping item --- .../Hands/Components/HandHelpers.cs | 10 ++++ .../Hands/EntitySystems/SharedHandsSystem.cs | 13 ++++- .../VirtualItem/SharedVirtualItemSystem.cs | 48 ++++++++++++++++--- Content.Shared/Wieldable/WieldableSystem.cs | 16 ++++++- .../Locale/en-US/virtual/virtual-item.ftl | 1 + 5 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 Resources/Locale/en-US/virtual/virtual-item.ftl diff --git a/Content.Shared/Hands/Components/HandHelpers.cs b/Content.Shared/Hands/Components/HandHelpers.cs index 11fff6d9c8a..aecf3a69369 100644 --- a/Content.Shared/Hands/Components/HandHelpers.cs +++ b/Content.Shared/Hands/Components/HandHelpers.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared.Hands.EntitySystems; namespace Content.Shared.Hands.Components; @@ -20,6 +21,15 @@ public static class HandHelpers /// public static int CountFreeHands(this HandsComponent component) => component.Hands.Values.Count(hand => hand.IsEmpty); + /// + /// Get the number of hands that are not currently holding anything. This is a LinQ method, not a property, so + /// cache it instead of accessing this multiple times. + /// + public static int CountFreeableHands(this Entity component, SharedHandsSystem system) + { + return system.CountFreeableHands(component); + } + /// /// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache /// it instead of accessing this multiple times. diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index fd732009e9a..e48aafeab52 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -5,7 +5,6 @@ using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Inventory.VirtualItem; -using Content.Shared.Item; using Content.Shared.Storage.EntitySystems; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; @@ -299,4 +298,16 @@ public bool TryGetHand(EntityUid handsUid, string handId, [NotNullWhen(true)] ou return hands.Hands.TryGetValue(handId, out hand); } + + public int CountFreeableHands(Entity hands) + { + var freeable = 0; + foreach (var hand in hands.Comp.Hands.Values) + { + if (hand.IsEmpty || CanDropHeld(hands, hand)) + freeable++; + } + + return freeable; + } } diff --git a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs index e45530e4582..b31cc755763 100644 --- a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs +++ b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Item; +using Content.Shared.Popups; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Prototypes; @@ -29,6 +30,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem [Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; [ValidatePrototypeId] private const string VirtualItem = "VirtualItem"; @@ -71,23 +73,53 @@ private void OnBeforeRangedInteract(Entity ent, ref Before } #region Hands + /// /// Spawns a virtual item in a empty hand /// /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity - public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user) + /// Whether or not to try and drop other items to make space + public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, bool dropOthers = false) { - return TrySpawnVirtualItemInHand(blockingEnt, user, out _); + return TrySpawnVirtualItemInHand(blockingEnt, user, out _, dropOthers); } - /// - public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem) + /// + public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false) { - if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem) || !_handsSystem.TryGetEmptyHand(user, out var hand)) + virtualItem = null; + if (!_handsSystem.TryGetEmptyHand(user, out var empty)) + { + if (!dropOthers) + return false; + + foreach (var hand in _handsSystem.EnumerateHands(user)) + { + if (hand.HeldEntity is not { } held) + continue; + + if (held == blockingEnt || HasComp(held)) + continue; + + if (!_handsSystem.TryDrop(user, hand)) + continue; + + if (!TerminatingOrDeleted(held)) + _popup.PopupClient(Loc.GetString("virtual-item-dropped-other", ("dropped", held)), user, user); + + empty = hand; + break; + } + } + + if (empty == null) + return false; + + if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem)) return false; - _handsSystem.DoPickup(user, hand, virtualItem.Value); + _handsSystem.DoPickup(user, empty, virtualItem.Value); return true; } @@ -120,6 +152,7 @@ public void DeleteInHandsMatching(EntityUid user, EntityUid matching) /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity /// The slot to which we will insert the virtual entity (could be the "shoes" slot, for example) + /// Whether or not to force an equip public bool TrySpawnVirtualItemInInventory(EntityUid blockingEnt, EntityUid user, string slot, bool force = false) { return TrySpawnVirtualItemInInventory(blockingEnt, user, slot, force, out _); @@ -140,6 +173,8 @@ public bool TrySpawnVirtualItemInInventory(EntityUid blockingEnt, EntityUid user /// that's done check if the found virtual entity is a copy of our matching entity, /// if it is, delete it /// + /// The entity that we want to delete the virtual entity from + /// The entity that made the virtual entity /// Set this param if you have the name of the slot, it avoids unnecessary queries public void DeleteInSlotMatching(EntityUid user, EntityUid matching, string? slotName = null) { @@ -178,6 +213,7 @@ public void DeleteInSlotMatching(EntityUid user, EntityUid matching, string? slo /// /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity + /// The virtual item, if spawned public bool TrySpawnVirtualItem(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem) { if (_netManager.IsClient) diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs index b765566f44a..cee6c65fa11 100644 --- a/Content.Shared/Wieldable/WieldableSystem.cs +++ b/Content.Shared/Wieldable/WieldableSystem.cs @@ -161,7 +161,7 @@ public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user return false; } - if (hands.CountFreeHands() < component.FreeHandsRequired) + if (_handsSystem.CountFreeableHands((user, hands)) < component.FreeHandsRequired) { if (!quiet) { @@ -202,9 +202,21 @@ public bool TryWield(EntityUid used, WieldableComponent component, EntityUid use if (component.WieldSound != null) _audioSystem.PlayPredicted(component.WieldSound, used, user); + var virtuals = new List(); for (var i = 0; i < component.FreeHandsRequired; i++) { - _virtualItemSystem.TrySpawnVirtualItemInHand(used, user); + if (_virtualItemSystem.TrySpawnVirtualItemInHand(used, user, out var virtualItem, true)) + { + virtuals.Add(virtualItem.Value); + continue; + } + + foreach (var existingVirtual in virtuals) + { + QueueDel(existingVirtual); + } + + return false; } if (TryComp(used, out UseDelayComponent? useDelay) diff --git a/Resources/Locale/en-US/virtual/virtual-item.ftl b/Resources/Locale/en-US/virtual/virtual-item.ftl new file mode 100644 index 00000000000..cb91f24cf7c --- /dev/null +++ b/Resources/Locale/en-US/virtual/virtual-item.ftl @@ -0,0 +1 @@ +virtual-item-dropped-other = You dropped {THE($dropped)}! From 939133e8ba7c40e0a831de14d24f461358f2233d Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 19 May 2024 01:36:52 +0000 Subject: [PATCH 013/235] 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 e4b72cd303a..9dffda89017 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Tayrtahn - changes: - - message: Flasks can once again be put into dispensers. - type: Fix - id: 6104 - time: '2024-03-06T17:19:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25883 - author: Doctor-Cpu changes: - message: Cargo palllets can no longer be ordered from cargo request terminal @@ -3867,3 +3860,11 @@ id: 6603 time: '2024-05-19T00:23:45.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28122 +- author: DrSmugleaf + changes: + - message: Attempting to dual wield something will now automatically drop the item + in your other hand. + type: Tweak + id: 6604 + time: '2024-05-19T01:35:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27975 From caa2f7e5ba4008cb67424c2e55c7cf2dec13078c Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 19 May 2024 13:54:52 +1200 Subject: [PATCH 014/235] Modify battery assert to avoid floating point errors (#28007) --- Content.Server/Power/Pow3r/BatteryRampPegSolver.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs index 5d52bde3777..0afd86679b1 100644 --- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs +++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs @@ -240,7 +240,8 @@ private void UpdateNetwork(Network network, PowerState state, float frameTime) } } - if (unmet <= 0 || totalBatterySupply <= 0) + // Return if normal supplies met all demand or there are no supplying batteries + if (unmet <= 0 || totalMaxBatterySupply <= 0) return; // Target output capacity for batteries @@ -275,8 +276,8 @@ private void UpdateNetwork(Network network, PowerState state, float frameTime) battery.SupplyRampTarget = battery.MaxEffectiveSupply * relativeTargetBatteryOutput - battery.CurrentReceiving * battery.Efficiency; - DebugTools.Assert(battery.SupplyRampTarget + battery.CurrentReceiving * battery.Efficiency <= battery.LoadingNetworkDemand - || MathHelper.CloseToPercent(battery.SupplyRampTarget + battery.CurrentReceiving * battery.Efficiency, battery.LoadingNetworkDemand, 0.001)); + DebugTools.Assert(battery.MaxEffectiveSupply * relativeTargetBatteryOutput <= battery.LoadingNetworkDemand + || MathHelper.CloseToPercent(battery.MaxEffectiveSupply * relativeTargetBatteryOutput, battery.LoadingNetworkDemand, 0.001)); } } From 92b8bc98d16ef2d049f5fa8786ca3e3a9aef8548 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 19 May 2024 13:55:10 +1200 Subject: [PATCH 015/235] Update component query benchmarks (#27967) * Add more component query benchmarks. * Rename benchmark --- Content.Benchmarks/ComponentQueryBenchmark.cs | 273 ++++++++++++++++++ Content.Benchmarks/EntityQueryBenchmark.cs | 137 --------- Content.Benchmarks/MapLoadBenchmark.cs | 2 +- Content.Benchmarks/PvsBenchmark.cs | 2 +- .../SpawnEquipDeleteBenchmark.cs | 2 +- .../PoolManager.Prototypes.cs | 5 +- Content.IntegrationTests/PoolManager.cs | 30 +- .../PoolManagerTestEventHandler.cs | 2 +- Content.MapRenderer/Program.cs | 2 +- Content.YAMLLinter/Program.cs | 2 +- 10 files changed, 300 insertions(+), 157 deletions(-) create mode 100644 Content.Benchmarks/ComponentQueryBenchmark.cs delete mode 100644 Content.Benchmarks/EntityQueryBenchmark.cs diff --git a/Content.Benchmarks/ComponentQueryBenchmark.cs b/Content.Benchmarks/ComponentQueryBenchmark.cs new file mode 100644 index 00000000000..11c7ab9d5f5 --- /dev/null +++ b/Content.Benchmarks/ComponentQueryBenchmark.cs @@ -0,0 +1,273 @@ +#nullable enable +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using Content.IntegrationTests; +using Content.IntegrationTests.Pair; +using Content.Shared.Clothing.Components; +using Content.Shared.Doors.Components; +using Content.Shared.Item; +using Robust.Server.GameObjects; +using Robust.Shared; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Random; + +namespace Content.Benchmarks; + +/// +/// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event +/// subscriptions +/// +[Virtual] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class ComponentQueryBenchmark +{ + public const string Map = "Maps/atlas.yml"; + + private TestPair _pair = default!; + private IEntityManager _entMan = default!; + private MapId _mapId = new(10); + private EntityQuery _itemQuery; + private EntityQuery _clothingQuery; + private EntityQuery _mapQuery; + private EntityUid[] _items = default!; + + [GlobalSetup] + public void Setup() + { + ProgramShared.PathOffset = "../../../../"; + PoolManager.Startup(typeof(QueryBenchSystem).Assembly); + + _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); + _entMan = _pair.Server.ResolveDependency(); + + _itemQuery = _entMan.GetEntityQuery(); + _clothingQuery = _entMan.GetEntityQuery(); + _mapQuery = _entMan.GetEntityQuery(); + + _pair.Server.ResolveDependency().SetSeed(42); + _pair.Server.WaitPost(() => + { + var success = _entMan.System().TryLoad(_mapId, Map, out _); + if (!success) + throw new Exception("Map load failed"); + _pair.Server.MapMan.DoMapInitialize(_mapId); + }).GetAwaiter().GetResult(); + + _items = new EntityUid[_entMan.Count()]; + var i = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out var uid, out _)) + { + _items[i++] = uid; + } + } + + [GlobalCleanup] + public async Task Cleanup() + { + await _pair.DisposeAsync(); + PoolManager.Shutdown(); + } + + #region TryComp + + /// + /// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("TryComp")] + public int TryComp() + { + var hashCode = 0; + foreach (var uid in _items) + { + if (_clothingQuery.TryGetComponent(uid, out var clothing)) + hashCode = HashCode.Combine(hashCode, clothing.GetHashCode()); + } + return hashCode; + } + + /// + /// Variant of that is meant to always fail to get a component. + /// + [Benchmark] + [BenchmarkCategory("TryComp")] + public int TryCompFail() + { + var hashCode = 0; + foreach (var uid in _items) + { + if (_mapQuery.TryGetComponent(uid, out var map)) + hashCode = HashCode.Combine(hashCode, map.GetHashCode()); + } + return hashCode; + } + + /// + /// Variant of that is meant to always succeed getting a component. + /// + [Benchmark] + [BenchmarkCategory("TryComp")] + public int TryCompSucceed() + { + var hashCode = 0; + foreach (var uid in _items) + { + if (_itemQuery.TryGetComponent(uid, out var item)) + hashCode = HashCode.Combine(hashCode, item.GetHashCode()); + } + return hashCode; + } + + /// + /// Variant of that uses `Resolve()` to try get the component. + /// + [Benchmark] + [BenchmarkCategory("TryComp")] + public int Resolve() + { + var hashCode = 0; + foreach (var uid in _items) + { + DoResolve(uid, ref hashCode); + } + return hashCode; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null) + { + if (_clothingQuery.Resolve(uid, ref clothing, false)) + hash = HashCode.Combine(hash, clothing.GetHashCode()); + } + + #endregion + + #region Enumeration + + [Benchmark] + [BenchmarkCategory("Item Enumerator")] + public int SingleItemEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out var item)) + { + hashCode = HashCode.Combine(hashCode, item.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Item Enumerator")] + public int DoubleItemEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out _, out var item)) + { + hashCode = HashCode.Combine(hashCode, item.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Item Enumerator")] + public int TripleItemEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out _, out _, out var xform)) + { + hashCode = HashCode.Combine(hashCode, xform.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Airlock Enumerator")] + public int SingleAirlockEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out var airlock)) + { + hashCode = HashCode.Combine(hashCode, airlock.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Airlock Enumerator")] + public int DoubleAirlockEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out _, out var door)) + { + hashCode = HashCode.Combine(hashCode, door.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Airlock Enumerator")] + public int TripleAirlockEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out _, out _, out var xform)) + { + hashCode = HashCode.Combine(hashCode, xform.GetHashCode()); + } + + return hashCode; + } + + #endregion + + [Benchmark(Baseline = true)] + [BenchmarkCategory("Events")] + public int StructEvents() + { + var ev = new QueryBenchEvent(); + foreach (var uid in _items) + { + _entMan.EventBus.RaiseLocalEvent(uid, ref ev); + } + + return ev.HashCode; + } +} + +[ByRefEvent] +public struct QueryBenchEvent +{ + public int HashCode; +} + +public sealed class QueryBenchSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnEvent); + } + + private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args) + { + args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode()); + } +} diff --git a/Content.Benchmarks/EntityQueryBenchmark.cs b/Content.Benchmarks/EntityQueryBenchmark.cs deleted file mode 100644 index cef6a5e35c5..00000000000 --- a/Content.Benchmarks/EntityQueryBenchmark.cs +++ /dev/null @@ -1,137 +0,0 @@ -#nullable enable -using System; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; -using Content.IntegrationTests; -using Content.IntegrationTests.Pair; -using Content.Shared.Clothing.Components; -using Content.Shared.Item; -using Robust.Server.GameObjects; -using Robust.Shared; -using Robust.Shared.Analyzers; -using Robust.Shared.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Random; - -namespace Content.Benchmarks; - -[Virtual] -public class EntityQueryBenchmark -{ - public const string Map = "Maps/atlas.yml"; - - private TestPair _pair = default!; - private IEntityManager _entMan = default!; - private MapId _mapId = new MapId(10); - private EntityQuery _clothingQuery; - - [GlobalSetup] - public void Setup() - { - ProgramShared.PathOffset = "../../../../"; - PoolManager.Startup(null); - - _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); - _entMan = _pair.Server.ResolveDependency(); - - _pair.Server.ResolveDependency().SetSeed(42); - _pair.Server.WaitPost(() => - { - var success = _entMan.System().TryLoad(_mapId, Map, out _); - if (!success) - throw new Exception("Map load failed"); - _pair.Server.MapMan.DoMapInitialize(_mapId); - }).GetAwaiter().GetResult(); - - _clothingQuery = _entMan.GetEntityQuery(); - - // Apparently ~40% of entities are items, and 1 in 6 of those are clothing. - /* - var entCount = _entMan.EntityCount; - var itemCount = _entMan.Count(); - var clothingCount = _entMan.Count(); - var itemRatio = (float) itemCount / entCount; - var clothingRatio = (float) clothingCount / entCount; - Console.WriteLine($"Entities: {entCount}. Items: {itemRatio:P2}. Clothing: {clothingRatio:P2}."); - */ - } - - [GlobalCleanup] - public async Task Cleanup() - { - await _pair.DisposeAsync(); - PoolManager.Shutdown(); - } - - [Benchmark] - public int HasComponent() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var _)) - { - if (_entMan.HasComponent(uid)) - hashCode = HashCode.Combine(hashCode, uid.Id); - } - - return hashCode; - } - - [Benchmark] - public int HasComponentQuery() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var _)) - { - if (_clothingQuery.HasComponent(uid)) - hashCode = HashCode.Combine(hashCode, uid.Id); - } - - return hashCode; - } - - [Benchmark] - public int TryGetComponent() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var _)) - { - if (_entMan.TryGetComponent(uid, out ClothingComponent? clothing)) - hashCode = HashCode.Combine(hashCode, clothing.GetHashCode()); - } - - return hashCode; - } - - [Benchmark] - public int TryGetComponentQuery() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var _)) - { - if (_clothingQuery.TryGetComponent(uid, out var clothing)) - hashCode = HashCode.Combine(hashCode, clothing.GetHashCode()); - } - - return hashCode; - } - - /// - /// Enumerate all entities with both an item and clothing component. - /// - [Benchmark] - public int Enumerator() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var _, out var clothing)) - { - hashCode = HashCode.Combine(hashCode, clothing.GetHashCode()); - } - - return hashCode; - } -} diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 7caa9958361..0cb1da4b401 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -26,7 +26,7 @@ public class MapLoadBenchmark public void Setup() { ProgramShared.PathOffset = "../../../../"; - PoolManager.Startup(null); + PoolManager.Startup(); _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); var server = _pair.Server; diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index c7f22bdb0cd..0b4dd907621 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -49,7 +49,7 @@ public void Setup() #if !DEBUG ProgramShared.PathOffset = "../../../../"; #endif - PoolManager.Startup(null); + PoolManager.Startup(); _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); _entMan = _pair.Server.ResolveDependency(); diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs index 8512107b69d..0638d945aa5 100644 --- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs +++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs @@ -32,7 +32,7 @@ public class SpawnEquipDeleteBenchmark public async Task SetupAsync() { ProgramShared.PathOffset = "../../../../"; - PoolManager.Startup(null); + PoolManager.Startup(); _pair = await PoolManager.GetServerClient(); var server = _pair.Server; diff --git a/Content.IntegrationTests/PoolManager.Prototypes.cs b/Content.IntegrationTests/PoolManager.Prototypes.cs index 760e8b1d372..eb7518ea155 100644 --- a/Content.IntegrationTests/PoolManager.Prototypes.cs +++ b/Content.IntegrationTests/PoolManager.Prototypes.cs @@ -15,11 +15,8 @@ public static partial class PoolManager | BindingFlags.Public | BindingFlags.DeclaredOnly; - private static void DiscoverTestPrototypes(Assembly? assembly = null) + private static void DiscoverTestPrototypes(Assembly assembly) { - assembly ??= typeof(PoolManager).Assembly; - _testPrototypes.Clear(); - foreach (var type in assembly.GetTypes()) { foreach (var field in type.GetFields(Flags)) diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index b544fe28547..25e6c7ef26f 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -42,6 +42,8 @@ public static partial class PoolManager private static bool _dead; private static Exception? _poolFailureReason; + private static HashSet _contentAssemblies = default!; + public static async Task<(RobustIntegrationTest.ServerIntegrationInstance, PoolTestLogHandler)> GenerateServer( PoolSettings poolSettings, TextWriter testOut) @@ -54,12 +56,7 @@ public static partial class PoolManager LoadConfigAndUserData = false, LoadContentResources = !poolSettings.NoLoadContent, }, - ContentAssemblies = new[] - { - typeof(Shared.Entry.EntryPoint).Assembly, - typeof(Server.Entry.EntryPoint).Assembly, - typeof(PoolManager).Assembly - } + ContentAssemblies = _contentAssemblies.ToArray() }; var logHandler = new PoolTestLogHandler("SERVER"); @@ -140,7 +137,7 @@ public static string DeathReport() { typeof(Shared.Entry.EntryPoint).Assembly, typeof(Client.Entry.EntryPoint).Assembly, - typeof(PoolManager).Assembly + typeof(PoolManager).Assembly, } }; @@ -422,13 +419,26 @@ public static async Task WaitUntil(RobustIntegrationTest.IntegrationInstance ins /// /// Initialize the pool manager. /// - /// Assembly to search for to discover extra test prototypes. - public static void Startup(Assembly? assembly) + /// Assemblies to search for to discover extra prototypes and systems. + public static void Startup(params Assembly[] extraAssemblies) { if (_initialized) throw new InvalidOperationException("Already initialized"); _initialized = true; - DiscoverTestPrototypes(assembly); + _contentAssemblies = + [ + typeof(Shared.Entry.EntryPoint).Assembly, + typeof(Server.Entry.EntryPoint).Assembly, + typeof(PoolManager).Assembly + ]; + _contentAssemblies.UnionWith(extraAssemblies); + + _testPrototypes.Clear(); + DiscoverTestPrototypes(typeof(PoolManager).Assembly); + foreach (var assembly in extraAssemblies) + { + DiscoverTestPrototypes(assembly); + } } } diff --git a/Content.IntegrationTests/PoolManagerTestEventHandler.cs b/Content.IntegrationTests/PoolManagerTestEventHandler.cs index d37dffff50a..3b26d6637fd 100644 --- a/Content.IntegrationTests/PoolManagerTestEventHandler.cs +++ b/Content.IntegrationTests/PoolManagerTestEventHandler.cs @@ -13,7 +13,7 @@ public sealed class PoolManagerTestEventHandler [OneTimeSetUp] public void Setup() { - PoolManager.Startup(typeof(PoolManagerTestEventHandler).Assembly); + PoolManager.Startup(); // If the tests seem to be stuck, we try to end it semi-nicely _ = Task.Delay(MaximumTotalTestingTimeLimit).ContinueWith(_ => { diff --git a/Content.MapRenderer/Program.cs b/Content.MapRenderer/Program.cs index 43dcff2c020..73141191089 100644 --- a/Content.MapRenderer/Program.cs +++ b/Content.MapRenderer/Program.cs @@ -29,7 +29,7 @@ internal static async Task Main(string[] args) if (!CommandLineArguments.TryParse(args, out var arguments)) return; - PoolManager.Startup(null); + PoolManager.Startup(); if (arguments.Maps.Count == 0) { Console.WriteLine("Didn't specify any maps to paint! Loading the map list..."); diff --git a/Content.YAMLLinter/Program.cs b/Content.YAMLLinter/Program.cs index 7f0b740fe8c..32078faeefb 100644 --- a/Content.YAMLLinter/Program.cs +++ b/Content.YAMLLinter/Program.cs @@ -17,7 +17,7 @@ internal static class Program { private static async Task Main(string[] _) { - PoolManager.Startup(null); + PoolManager.Startup(); var stopwatch = new Stopwatch(); stopwatch.Start(); From 98dbf7f77ef36cf1fa8b5c6ec85af63aed5f575c Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sun, 19 May 2024 16:01:54 -0400 Subject: [PATCH 016/235] Improve "points at self" messages (#28147) --- .../Locale/en-US/entity-systems/pointing/pointing-system.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Locale/en-US/entity-systems/pointing/pointing-system.ftl b/Resources/Locale/en-US/entity-systems/pointing/pointing-system.ftl index a310fd9b692..29f0fa27e25 100644 --- a/Resources/Locale/en-US/entity-systems/pointing/pointing-system.ftl +++ b/Resources/Locale/en-US/entity-systems/pointing/pointing-system.ftl @@ -3,7 +3,7 @@ pointing-system-try-point-cannot-reach = You can't reach there! pointing-system-point-at-self = You point at yourself. pointing-system-point-at-other = You point at {THE($other)}. -pointing-system-point-at-self-others = {CAPITALIZE(THE($otherName))} points at {THE($other)}. +pointing-system-point-at-self-others = {CAPITALIZE(THE($otherName))} points at {REFLEXIVE($other)}. pointing-system-point-at-other-others = {CAPITALIZE(THE($otherName))} points at {THE($other)}. pointing-system-point-at-you-other = {$otherName} points at you. pointing-system-point-at-tile = You point at the {$tileName}. From dc8b03273578dbdaf9891bad2dcf84a0be64a57b Mon Sep 17 00:00:00 2001 From: MilenVolf <63782763+MilenVolf@users.noreply.github.com> Date: Mon, 20 May 2024 01:04:55 +0300 Subject: [PATCH 017/235] Fix toilet texture & fixture (#28125) * Fix toilet texture & fixture Passangers that got stuck between wall and a toilet will thank you * Fix mask, add layer None --- .../Entities/Structures/Furniture/toilet.yml | 11 +++++++++++ .../Furniture/toilet.rsi/condisposal.png | Bin 31043 -> 2433 bytes .../toilet.rsi/disposal-charging.png | Bin 31052 -> 2414 bytes .../Furniture/toilet.rsi/disposal-flush.png | Bin 25461 -> 4017 bytes .../Furniture/toilet.rsi/disposal.png | Bin 31095 -> 2459 bytes 5 files changed, 11 insertions(+) diff --git a/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml b/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml index 2556b9ddfde..1eb5176e9e0 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml @@ -51,6 +51,17 @@ disposals: !type:Container - type: Physics bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.4 + density: 190 + mask: + - MachineMask + layer: + - None - type: Construction graph: Toilet node: toilet diff --git a/Resources/Textures/Structures/Furniture/toilet.rsi/condisposal.png b/Resources/Textures/Structures/Furniture/toilet.rsi/condisposal.png index c60cabe80b18aa8a578e2225633f58de1b405a5b..469258bd2fbe64358f85e3eaeddfd0ed15b7a8eb 100644 GIT binary patch delta 2424 zcmV-;35WK>@&SPpkRyKyNJ&INRCt{2T5W6;#~FUst&;fTFf2An&XEsLU16J8mQ}@$ zXd|&XOF)yD4@H$)iPTuB_ksj8a6ilUrK63n0_ zh=q`}#Zo4f;5g3m>l?MR0y6>S4HYqE-fl0v-QJk;x{8-syR}3jhR4(1bO{jf%Xr*6 zdQ(-_d9o8{e3yUdd-aZL`uwNQKaWSpM+#FXD2k%|ujy&S#y7sW3jiqm(Q-qXXVTM? zc7(~y0zE&#>;wT|>((uXbzNQEmNK85`P6C%Q+Vn;^7{4b6Kea)&t8G2&ck`?J%%z& zOCTqAg>Lz8YHLl~g+t>k-5bJF=aH*-RHJH3RZN|?+e?3!;6~?9bY=KuJUUNy%FxVo zC71{Rfb@)XR#5=J^nK%{lL+c6UPk@E1^TWuSZjQFLj^7#7+~s`nSe(PrSSxMUmXCm z@qh&Z?d|QHD2no?jT?}YyMlQZOy8eII5dt=&U~7ZlOVs0Pqqs~FwKB(qZ0s(eSsF? z&^T?AEINNrcBZ5sBQOzg;X>^6dsrwdD^05X;NVr$dP@SxGH~y8(=zvdx2|mCk;aAR zKQsa+-Zvhw&O9wxb|K57u%7WeAG;=bo>vhw-zKSll1ij!q|;wAGFrs-XUM(Z4Tr;F zSnqH+3};8G5e|*3eu;va0Il&n&m%1@4QXjXl`jmSy?HXTUycF(u$GM(HNPL(NS7YWLd!E z1IEU_rq7Zjp{AxrS5A^7M8XjSLqS!WWZ-|P_i%zB$k(plz@FW^X!+rzCscCEx@*^O z007F$N-fVJmbbzHAP9o&a_vM}sd`%RIdk4W5?;78CC0b49fQlYlY8^cZB{xkK@bGl zZnx9-TeqVsCs?p*!663%0XUtNoX^*T-rnArzT&zN5d;AMaOKKnT9%jhyh?_wck+ML ziELZjF{FY3K@eo8vl8EZHwgg9%Ce!T2=IQ~4*;n8`8oig^+iR+@cDX7?*Rxb3Fz(Z zg&+vX%Ch0gmCG0z89`N*`UZ#??)7#fKR+K?S*hO=*A^Gjsa;U;V?6!T53sg)VF#Fb z@E_c|b(6~ad_7G4GM@v80{Qv*@Opn^y)Xc3d^`#;f@^MRm6hvU3nd}VEv!Q zl>!yzFPREAx3prFPfOr(xvwK%cBSFNGy-~F}u72OB z+H5Jqv3Mdn&Y7?q)7mIerxZ%ct(j^@k3r5+e9zJUTi$5Q#)&Q53oM_Vz_lIF%4ZQMTvhp|-X* z#+JH+2USPp$9@SI5}-_S2#k$k)5Z-z3U7HA3nY>i7#pKyddVFGh6IpUT<6J7tX#P& zg(^NAtX#Q@MwQHBg{Dh@grI-F`sP7uwR;Ho>YE2RU1Wb5G%g=kyaNl(3uu)jO(j=gz_vb5>?% zl4>p;NQwH#8FAernq+Uyb~3<)qZo+#wr>!yDfqgq75k(6YK zlHjb>IA(nq84nl`V3m7z(1PsR<=7mKb(S6eh8PcxHq0tCN1r#&Yj>W8c1gDqR8S z+M*4r#LNJDULGpSUs8W2EFw8kq9 zHyY33aGbWGo$#@4M{(ACwgN{F1CoHT2i@<8dt zy$Q=P0npeX(UsU>goMK5(P$I^kd`*Liz>t9N`+HJg3*^Jddz>$&LSF(#>@Z~B=m34 z<=Tm8G>X~TS(PG6`2Bti4G&Qt5HBL(2qa0u(C`raet%58)_9j|XWU}|7BhfMot)ei zbQusF?>LUPYu~2tgbVLUZt8>|4 zWcm{yW|gt3C4he$7#NVh|Nh_j>AH0>!g?ea?au&`iWDo`@HxCH?5-U{t0NGbA@=wF zeq6eA8PJ3RR4pO21 z4dMXc+_`hKK;;86*OVf2O(_6ydv@>Qre-p*d0RDB7gT>>Y9<3tXC>O&j-jvbJo@_1 zBNz&r{t`7Fm|F^G&-P$!>}$($h(11F51h_QOwD9qbwLF-Z>z@COa_TmaSHp?j1au$ z`YnB)`sdu5nwrUwfAz~>U})$DzPvR6yWNgJAOHYxIx9_$n2*mVbrdF{ajS6vP~BD+ zRG_4!5PyI9>)&v(|1>8Eg39pIZGWWChE6bZO({Nl|2+WU%GbRn13>1}a}i+eztMqM~A&_+T~`0GgLT^dg0000NgyFv5-=n{=m<6)RfUp9q?IJ-CoM1)4^ z6NYq4ND66fPe&na7>6qa$A|D@7|HTH!#5A12$D4sw23BgiZ(NZnV~}5U!jqK&lbb+ylCiIAdcqN_7r z;ilp)7dtA(t_;%kAB2m~KXP|+kCZqjudIu_3E=D7PhX%V8MMV5|bXiQ5L}~ z0hS?RqXY{>u~A|S70ty&vlfyf2s(xq383MXfXE913}9(Wpkg>SiWkKgmb8$uu_763 z;VoVX#By{@G{aa}ij9tqjp9U-q@zigKAIM}7)xuf1YAa2wJTQDT*x^BU{E<0;Q}Kv zI7uReOM=GYk|N54B*~0IdPnCeq!mR%6a`xVCxA$kNX2CWekdFwNReg)LR5TQHAx_u zXEh1u5X-tqOA5>5qC~Tb$S4$&7+*<~k_MDzTvch=MOu(a6$dq=WrF4uiY0v|O>r`< z!%Pq&@4{MEz-S0OPjDKdMV9dy5Q>*Ljf7Pzg5q8^sfoD8u%Ih6DT^HM>#8LrX^JA? zG9x;%M$lpzTv26CAsH3$@Kr)cg_0THJBn7X;~%-T$DM2CTYL~ zc{kC;+Ym;gR6>&wE;1By1ESFs4x%mrU0^cd>#A8$Yk{K(9Py0P@~OB`LRhnssv=1S zcYqWH7a)QlGq@yzv;%`xuuwiq2&-}$CDJSiAL&t<5)wWZ37W`)fhHKYMPqPP zBEhJtJkR@VLpWZc6rKlDN61b#>iZ3)f#p+Ffukvw;eE9soWLMOQb=5bNn8>lOB4%R z4UITX1p6ZUDj~c|!yW)IBVKXuGBN_?RCs}xC`yxP(pNT$U@8zJvAVr>lNL(IUW=-N z5W!JkLy*%vm7H9QNToFyr~(Y0y%B-l%e)FOQM9PBzM3bAVQE5<^j(H0-6{hcIWEuw zqlkn;Gn%g(7`QS7BhdmbP%P~x%}F8%9|4&2NF-Qag->Q-LziG%0aN6}ngaaIlZlnp(10D>Mf( z0)MZmbza!Ub^GDT-7G5O*K3 zhAmr^NTh)_s%|H^q;?rq0*^rCMO-A28xUEBKf$l2Wr|`sk@wY8Rlz$_RR}~&-U&t! zVh9`v2Vm`9wNye_BT3rxO_g4kP>nhkc4Da0c`{}I^D^VGpa}h9~#`={|P`4 zO_3170Sv5L_#}y^aEXPzM^FWg^x1Vs;77A6_>UYC+}=}3MPNun94j&sPV z+(|(aS#XdAB>8GoHHxHY0fdj?fR`?#N@xJR1}-X1D-h=py7k=&JFRzS(JYKaN}2!Wprngu&F>z&IUVgdm{1xbiF3l0g+YC18Jl5iH>4~m1xi9z1o z5DzhtB0(x3OM!Sn#tDg$aY2Cev;qOBK&YUb^s{3o%Y>|`8YnLq3|wUy4e|zHFhou! z6wnSNJVO%JvoaO(BPv9iIHW`woTDN7mNlA&9~393GOtJ)S-F_Ah_R*-u=e$`lg>qZbeYY2EKGZcv{kjmi+@P$DuoE3s6 zhZknS-K0IU1L@+^EMQ1P*lZy;3#Qo>5r_nma0PLcLJMFLWU$q)bsC8MZlc6=D^wPx z6hj{m*9lQ$WLnSzJlJqK3SiZJDjdkuydqNyCqaPcnxYsozndz~s#g+*KAG}&RuVd) zflpp7tRy&VfN4^C2GT2B5uu(ES3%LxRW@)J51f@Uv!v+N!$?K18zE5UC$1JQt6*ff ztAq9T?0hx|emDH-b7b~Mwm+&nxkx#CJ)KB~U2|kg=_slnRA`>1B<7;}=>qrkjBw^i|CNOW6MV0b*-`}bHC6{TDMj9oFTQt`kP;q0u z41>TYCH+e2G{J6%*sXleZAl?k=R^S#W>7MOpq+s5T~zfW4J72`6$J{;+;apH`gunI z%&3|1`=o>YK4}30PuF?Fp7EdSlOh6&s5~X(G$(VoN-z}E7&sE=IVh571e63Azfu-D zq`Jr9N-O_6eNvW?1QCJ>*!eW5;iwE`z9E^8!|_i+Vk8-{2r45!1QKwr6t+J$8IGP) zU`+ifnUb`me;xnK?~}UXkf-k*2B*?fX?eCfo?j^ojp_dZOA3W~I7pzt$!kRNP~Bvp z8qP!23NqVJuY`IoEBO}PyDTZEzk@@QW|r|s_XQqVfwSe=qW*Mr|JTVGmYgO1>pb&w z2a1HO(vreM86M6Yz%d;?y60pKiWdZeMSJKL;| zNGL5i7gOZ2VU+&Hv+MQTPVkmLisV?9218r)a?ue<)*&dRTZ|Q6&&|+}x#$OqoMVdK zD0X1?rwugRa1V5;jp2QKCu3Y!;cb8Tj|NNy|I>hhX9k;B(#^srDrw3Fdj3faV0dr( z;qs1=p@$zX0~p?$ez?42Wa#0C%K(P=rXMcv7#Vu_;WB{Xz3GR`J4S{cez*)^cyIdQ z@{W4(cZMur}KxC~%;Z~Ec# zj*+2wV`S*zhsywl_og2%?-&_+_~9~u;l1gH%R5Gf9)7qCV0dr(;qs1= zp@$zX0~p?$ez?42Wa#0C%K(P=rXMcv7#Vu_;WB{Xz4?c51r~mGAF{y*_GQ8+^W}Vc zunl~wUx*UdF#*GdHNvnlZ(-Q|eE9tnh7BSx>}p>OGta@W>h_s^_F6Ek?8*3;sN^B1 zulE@Fde0NV`4Of!+x%ntjvA-u1-|wkg-`ERWmRBs&#pUbG>LE7pIQ|^f7Y~h6}QL5 zf3s&wcKj}NO6582|1qmnyE@0?<|j7^SiJemhP`GDZe71q^z`}(K@pK?Oz-H!=7?6x$i8$GjMQ&@+-bRP`kpovrCs!N49;du8sL++cq=Xch2U`oKmXs zs%HDXefeM6n{TeQVXaQ(P#uOR{6c?pBUj#X`{okcx@y(lfB*fN;gJg~9g7P(&+iP! z_7T|0sm(35EitKeFSkfLFnVfa*E*{g<>&u=;I+4#_UYaG$cYo*cFIfT<~QtvP@@r% z_0G0A*VtsbZ0fz^iz|Oy^y8)Y&ZYM6->=@!ng7A^+Sru6(ZL&Ut^4x!smzldx_2*U z-#PnkhX$4=FPZ<|e$=f6=hok9(PBG>J-m18y}bKpmgQcX{KA4-mGZxD8TtCkTN_#q ztA^D`T+`;es_WFtahvm3t!n#epN8Q}2K*SozLN7p-`H{Q)_**q`L2j*D;f@kb(@c5 zHhxh0aC;(kWcuo{T2{W*ZG7PVW~RJ#T}RfKHMwcW4Ha8$xV;M7xM>r)r&71| z8BEVzyU&%0*-dXdk$ZGjy^~#PU^(v`Zu%f#<)*|3SC&otXWdG7n`2nlwY`S+UEZhL zmQG#s$BvB}iA_frBCjq_y?N0V-`l=3GcSLB@BZheE~_ zuiUHe#j8~;<1VcHl_*{Mt%+IvuH=l1nRDbw*oQV@!q*ipA3b%d(E*wu$|kOvK#r)_ zmzz)W{Dca%xrwK5R*#Qe+%rFCYTYxlkCbWs`Pi(=tJ%Q0GlO4kMmLL+ZZF(H&)XMS z_r1tRCqLU|`|g9}g|o0Z6%#-2c`KHvvFG~VyQ@g6-;6G=ZK$bjm>c=k`MGdC>$CGy ze)^dinEiHE^!lKQ_uJJy^uls9GN(z!kMxO2_wS-qHb6;b2xr=$%!iIb@o^KZpZir z6Dl3M3(poaVt3}}yuHeo9nMW4daT_mPP_QV+Bbu42F(v1(Eaw~V{yqFdo5oc*=E4C znp=8(d#KqbkM>j!5vE;S)1}$SSL#lg^x=i$c(aOPP~fib8>2^$U%7Ml?$r(0w2K(V z+j^}?-MTfg;csOZFROh1_;H#DfEz<5ED7o}l6?{T;tQfd%X(KvePaJ9`L_|}u%q|x z-!Jp?!{N-i@~Z-lo;g!~+QsQJqdliSIXx7`f&I=J1d0*T*}K^ynTDSnG+f>dKlZGmYO_eL6=`ox2af9-Z)IgH$I9;=vRKil3B;h<*wbFfWdW& zc{!%esdmWp+OOwkAMD*_;i*jv>afz(uUliU1gzT^A5^CIh<{Z`nU{-S4xZaG_t)dA zI$W>*Nlw$1`)}MyIMibFqn!*k|3~{jH?3$tDKHc?=Jc5}Ri;{ouK0STcrXL}C9M%>*S(d%}fB`AE`=ZhM(cyai> z>&M^V?&TBtDiuF@!s6fVY=EmX;x6@UacxFiR@3}T7vH^bVE@9I35y489y9r^;2QP% zY?Y71CFUOw2$_{SV#MpoTi)MTrvJ+INj)oCg5$2%ZL=~Zu^QGWZ&ItJw#3QVGd`-{ zIeYhmR?7y?%2}Mep>eq}J!1cMpjxv$Y-D7;t5>gnly)GXN}u4SnZcD-FU=eMaC@it zYoP&s<@Ao5&zq_)SX>c=bLzf*mc8>LLWu*x4V4NVJ1=glY<+k8!0RW;8LP)QrYa*k zHrf@idh6E4VZ(X^wSD}}`LG|XDJfZpwIwyPha5b3C_AA1$NZ)-l|R{$G;r%LDO~MZ zwQJ9)IeSs?2fxJbrmy5aFb%@8W^InFP(HKzy3zTOj=_V0A(wyrF{t0MD=|NTDt=2& z%8u?nsbZ5Gr7tzEHU;0C^mWIe;N=I#2h=Vz?(CtL#cI{7SDCW6YV!*@Z%hPbn7CK& z)~i=gxzMG3iHZM68uMU`+N9Q`-IxDap9pG2)JX1pK5y0W(erVW#`sCXI4Xdnj+|1m%%9?)X)9d?#-VZsLn?EYC?ZYu|Y;QYb{==;01F;LY z^D2F_KI$c`!@h26sh?V&EQig>A2MA`|E+R+{=cKJ9M{?*z4 z%gS-BCWl5oxbkY5H*C%O#&1q)oCPa>`|Z+OPTvb_u<`oR>@k%a-L60F;&1P~w4v+^ zV(7~BM!Pm8jT%)7ACXk6JhrA?$nC&EReF5(D)vK*%B`}-2ZAA*Jg#Pk*IMoCRJ+-z zaB=*g8!rqWKD_3%i$Udsvtj~+J9a+*edEsfI}_`fPUUS5JHXEExOm)xQ~T2gTB^Rh rv`39bwaXk2jX3nunnBoO9zOqA&Z<4V^G5w+j^kt7$E=O+JMO;$Cx4+% diff --git a/Resources/Textures/Structures/Furniture/toilet.rsi/disposal-charging.png b/Resources/Textures/Structures/Furniture/toilet.rsi/disposal-charging.png index cb9ad3a9c75d02d9915666999f56fa89dbcd3d24..d12b97d998796952ccd3797a3060a31088996fa8 100644 GIT binary patch delta 2405 zcmV-r37YoI@&WD=kRyKyHAzH4RCt{2T5U{JR~mjUyJ^eEh+KiTL&K<9HUgFqvMDs~ zA8&_5-M09Vrpaz%7fF{pHBq-p8vn@Pt|1EB1vVB~y|OjgP4*9DQnxk%5tAk2M$rJ4 ztv1zxGVL(KbdVy8+vV;LIEQ=h+>bLKXj*-eN$x%8J?H(n?|FaEne)EDV-8OFBFl0F zfhdZRjZvVjt&Ov$G6%=8PobxS*?d1XBNRnZK79BvLf?d#1yF*|;30~l9FGyAQ9c%! z8sPf~rNLOdA&GV3()zN9Hcyuao-R*Bdu`dvtkbS2SiFbH1NaOcw@=(swRN27!1aj~XE=ED^C?6Rc!`1~hUEKK39b<3MKZ;o{=0C4U)w;>JF63EQT)@}byb+zfZU|^DE zdPBHt-E!rgN>uEqi0JcldFT?{>in554WGfI<4lJPji-Mr!MqD3rzEqI0s!Xj8!w$$ zP+Rsg>IN>;ccsEwpmy2nHtUn0V1~rXwNq7=g(EmoG=U z-@`&lNpXK%;|B+CnD$#5fGmTWgEh3R=DiwS+J@r|%g=vk08FB9G+^~SEm(FT%cHQK z@;o28CV8G$8D?orT>m7MNKQ$nzj$c0VE!)vEGqACI1Kw84u_$4BpSiMr0N$dm<^y+ zp67WaB_$y#DM{rKi8O+{&dm)D-as%gNzWBzhv|Rp?831Vr*Z7WX>@jW>Cy}i-hjK# zZMqC&ItSQnHq6Y-VD;+NsR$ZCIk2bel#3!fYZ32Do)=`5r)$Bx(czRB-@Q zc}bFV$Hs%E<`$Hc6r-uR1x?K@7#kmtpcxwm@1yOHF!TelpLeLf$Y z&T_7&ryIS!y%BRobs-`M0s!Fp^=q^(C+B$;4O#Exs}tJR){{tt0R%yioz8N6`|W=W z03a>RhJpgX^N)T2K*cXM0RZhUC@4fvPq*nk0D+|edV6~z2m;d5Y`A{?8pg)PP*I`2 z0V0ZfJYC4m%|%*T;u@d0(&CZb`$m3;fWa+y$aO3jfgX z!~l<4fJcqT-Ta12~iYfdrl6jtE(e)sXcN;bwqybmw=%Gltm7{ zi3x1mx&=t!E$>Q!WU_n{6SPgQxP!pZ03;XJai#-n*RD&Thz|#A*RG>kC9_1qE0nYba)h&PFQGz)JWM*Y!+tw{{m(8stl!;tcnEn~krk^vjvJngh zW0US7AQ%i%KrxJJXE?+UHrKZLy=E^}S+ugf&RE1kcF!TVH zDhsRSkcirVMDWp8mndOq05j!@Ks5(z=*6fOpR2r{m|z8(M!ui}B_wEXXWe>9@cN*9#@#>U4{d*p~NVK6XB1B^f5 zM`L3nie4--oR|nqas|1m$d506jj-V2Md>L zFHMddUkSIUu-^E?wD0O;$xWR(r#0N~=qi?l%%15!5>BXvVD z0B{HP@8f3YQ?PwkCD!MaVRk+RPG>n@UGHl;fiP`xSlBwbp`l&5K@T%*#^m+E*i)(6jK1Kf3FW+{M`RFsdu{_XF$(tnN<1VN?v zxz<0@XTyIWn7W}DAARsX0C1(7UX=kL_36b7u$HkN>~=fsb~^+?kYhChDT?^?)0@=j zM!bMU*?c}9hDU}lJTinU{pUEMixCVBu%tWy(1`>=kkis^DkB(yq{__%XF)+hAuW6` z;{|}$B@lTDKKkH&y!Ot!x?>FupdCOAqAXM>C@5Vt6)X{T0)(FxFD*YKg|&$4B-q>A z%L#&DTBx7|DO8AthZ0U_xn*I5mS42XEh+;Df)G=oLabOC%gk@-1L+n_8&36D8w4D8xb|JjXS-pxC+858Uzu$QK#I|jj)TbioFG63&xh%^6VuN zH9lMHK8+<9HTsg+H6}_l*2HIGEKlB<-33O(tXcX0^ZdT`L%7V`d+wdzJ?D4NJ?9QP zF}{84vVqkDF$^mk(gf)CYaZ6m=}a-3OzG+AP1EV7j?}&;QWQlKL76BDhZ=a=5W7&M#il)I*iivDWn2Kto zD*0_v2Bs#thNdbel!TIz-I)gM$RgXN^m8~JY5g2e=`#QJQ#*z^iH*(gyU6h-Cl}du znlpM3K;VJ2$d+k|LsF0_0i`(xrYb0U5IjoVqMkm@nScsr=NUvn^&$m1ZQ4`h=y#q9 z0vc?4N;CbAyO~R;+*2J6pqX2xBFX7UO>{VtEN*p;&zp8QP$W&8|Aq(;9}G z3O6ZGoC0YmN^&9#MNl+Ou<(^ga%RwNl4wkjW`ZyXan~#)Bi5nXv>|yi7@VMRl1U_a zGsT!$vb(#vp;F=IV1ugUl)MQrRx^lD6|?3@O_rP%Te8#_nNsY1!%YPj4K?$X(%j0C zB?jxnXk6+EZkz}(FVuY2V=*Z|O zD=S)CMMN`{(2BJR1RqUMkt7>#(k&b+7WD3!ch7awDnv$#EFI0zq7d1NjG!VTIF@6H zhzOCRtb$hoEJH*`2v&w-Bg7~wl8cIDtt3SdbQCQTAj7KwkrxDLz|xdJMR9BdFN#qt zX(gkhMKapTTfGX1=IE$MhOx2~8yOuP!HFbEN0QKeBrS4L)>d8xxT#xpC^pqxFmnXJ zpmHq21x93Wl0*oX1dYWdMU)9ik{N~cj?Pm=D~g0D3KjxR0L3PeipvCiQ#eGBBFzYd zsQ5T)l0Y)gY7)*NmUWAk6qd(DiDng%Q79xazKSL#4J6CBs?xGsv>=lz4wgpC1kEWF zOZqCB;$&KfnIJ^ojkT-*bqG99a2ldTmhl-7ikCQzgi$Mk;u$rmiMYnFU@J5!iyZIk zs3jz6iXz}LBf79gP+}QeQDsgc85QvGRYORHk|>cNb>)-XfE1VVQ49k05=op8S;8$^ zK!A^=3A`w)lq4&%kATo5Cx{xs;Jl)Frct6*0@rwoQYk`VWkU5;G*9z{CW<)X6wNb@ z3TU{(h)AF`m7^%1M=g;QC6OFXfKBFM4aO;o0!09c(X{BR$uRKPtUvRhK|6Ofuo?s97*;fujf<@r;Z4R9q+~ ztXWA_ktBmtK#GDJ5J8X`ToOUsVS-g)C?7S1RXL3kX%>`^%rlu{Qa%<5n#ck{6AbU- z5I|kg7+jS|AXSy;d7n9i;}uHbd7wH%c1@$c-cTAapP~vJO|cB`D~E6bgA_?2aSb|g zYltjSEG#uN;y4lbMfO!gc$J1V0ANPE;#p;61jecG0xwaNCefs?(J)${D1mz#Op~EG*~}EGs}oF047w zaDf09hav?^ke^SJ9M=kifw@PHSrM zVy(~|=m_la1w!(0NO2juq5*}Z=(E+)rJr4?Xn6|@aJ9S_!wBH;qK>l8&Q z1gN`@Si_PnN+i<28dZ-ITwJS+DuG8J@**yh$ODKh!>{01(=tV|oXGp?sjA=|sVZzo zi{A-Gu*DEKP!7P_Gis@rutuUHF)C2C$hdtz(2!zs7LbHwRRL>+X>_@h#Yd`01s@vR z-v0|g5KWP=g##E^kMc8C zSxJKshF4PUYlFa@J7(c3lCTGt2nL3#X&lbTpcb-%L|kSyAQf;RfCK)tP;gu%LE<=^ zW;qRnGO#Nm1aMYK@ZuqsATWY=7qak3IaL6a;v}4x2^cBED!9mksZg|{sk-;43f@(% zuuxGYC0XEj9K2eDGa3h6M}g;oBo?;+Dumg|KmYg|LBf6nA|9dyEWxM@ICS8*;5^Nc zz)jHSGx+_5M+%!*1cFtFK&bHe5~<)4&w(DOERuAw;COkRC_uh$cn0g@d9DsD$8gP zHvqyAIhjzvI*{-TLD;;JsSqDgVW){hNR+`j8g}2ZM$_<(;sjOZ6-k50`7_R@#*(VY zfQ3;qC}gI30^~7K79A-Y=sgeM$_!{b7?FxIB)~2x0;%f3pB6Dl)_1{!E<*}gqSC~YGczg2}2$a`MWX+9npZtuNGtyT$#WWDK!o0 zd9LPRd5Nz4)S)|V;7K01@?~a8(Q}9vMb91~Fy_au77Q!DWw^V7?aAqUHVB?*{`5Z5 z`yu-uWuM%lT(zDqB+a2Y)1_1t(HAl_&ypB(OMSdqVe#Hf?s+D{VIPE2|IAdtgiDaG zMv!;XQXR>m`RT4urz2EKNlCIPl0L_#L3Y)X{moC1YsbkG>DzUJ`^_LkS&B1KDg2l_M&k} z-0+a|Bp{J3J;$WciKJkrc{9WMnY0+0G@mB_=l!$dW6~GrnV;JzAby9zfl&DZ=S|^a?{Vh{5{h_1K*ZEII;HF zJB@BBe??Ndz*6(Ff&jQC`D4`Ok*<2)&3x+F5apRpi@S$v5LQ%Qj^{ld;_jsgF$KNl%|lcH+=kO2 z;rd&}W;kC0XB}KQe+!&JeBzoxn(JOy{hUbBK-W1C9^U#S2otG)_cS%$F5I-+A2&7L zF5I-BWhcAMX@TsLYcPc_8(QhFKD*!0?Fg^?BMZl}G;nR<%Y|FS+Xkbgu2D94LpM!7 z>B7NbM^~G|H;Qc7?P(1SH#`m9W@C6U-!(DrtMJ-C{KWxN{+}E$%$b4qio00wM8z%H zAkW{40Sxa&KV051GSu+HWdOr_(GQn*j0`pWa2dexUi8D|9V0^xKU@Ydychj&dB@05 z!w;7M4DUrhT;4G<)bPV)0K`*{BRk-@Lu%8d*B_dY9z zl|C606_Ggj^!4r|-t2KAB)7Tg?biRCy1nY@xq)xYpzx_(D`o|T^yspqYQvZo{iv*% zc{8W1Ew`;r%r|={FOJ!*POdP!{Xb`xXjk)in|X;10~T%ivR==bgId+;7&*00Y*5GD z@n^@@I+0Q7n^hNYl;1q-?)AH9*Q{j|YGzOWZp7uC!;ajVKkUekyEWgvJY*H>zGD9c zr+HwVZ;l_|zp6X-QTsPW{jj%8)UNlI-x)BdZqSOavul(cdv@tk>d4k_)iqJSY~5;R z`^?_7iBn26$ZEX*+gJayc+<@_cC6*8om7WmvA@tC-`FK@zI}6veQo8+AAIn^^l;09 z^2gc)pXYakWBUp0WM&g<4Qo_#t;@|)vPWfFy3|~?FgN$->^I(R)Vo)&BPULL+c77Z zn^&(lLiLARYM*U=u7SyP+0<+M7gzqa@W)Frok|=ya6rAcbKZx`YhaW2MTTs+wf4)~ zr_xV$=+>=_W5=xDI@Gl`e8v3t_9Ji2Kez5yvu4{c?7`hzGji^oS+?uiq?hJbFQ5B; z3(K1;Z>?`Jv@%vTZguPLDy>y7x7n1NmDTniz3YW9>HlMM_O+cq^obt(ew|0-o9u2r zWktOqFmCga^al4!9&S%0k4RmEOJjQM-gB;0)E;{4iCsr$);`&}Dz@{z!;S6-tlSuP|H`r{|E^X3w(%dE=CbPJHgsKFea;4Z z#JGd&lYW@1cI*7oq14)+A31Hi_Vtyx@^SydN@tHai*GAi3Y&NM_5QW29qWbHy}zvO z^287NcB}mQj54p!h*2wA$6i?ZD^arKI}ec7y{Jd-&dge8W*sTj>hm!f6;`oCfEou0ejQfr3g;mOZ-+rRrTaluS%cDcCEd)$g9s_wo1_iifE>NKWZv= zou32OGd?>%`KOvg zYRv=XzuP{p?)dV@euHO=8onp}bKX($%MRzp6W!PB6Q^8!Yt7riH-qPe^zU~2(Xlp( z8+tBZZfV{BTD8qRzdh9W(}#O2gbGtGuI}7;#A~%CPyFb@alCOkF*tDd_YKg)N3Y%a z?YFDzu_+fZjJNk(k-TL~T)p2*FIraN{PE*75db#^k6#kpdj$J3_Qe-O-4?a4jQrH` zQ{wN#%V0b3s`FN6(xInsRaKG--ckjT0nEj#;;E#PoJcw`~g=GGqv} zwA8bkPFiD!T|0j3;<45{-?>q$am5SQhUMiM6K!wXzaMuke167ghW+el{XW51ScyrK=Xd_~bn9}pqhX1E*(%fD6 z<&ZfocKv!htHbpwpYCk5^1zKdv4@(CdboqZ=KbjS_r?|NCkBRr#hgBKredab$cnF5 zinktxX8+{8ef(_C#7>>+Ur9*2y|mxtheu!jV#mh~M`}4vmK~SVgxpnK8wGX?6M1aY z;;lbiDiOM6GuX(nDXubBKWAdg zrS`ZG~Dr_?+XgaieaN9@UoNxMrEh#DEu(qVy;=us?Ay$^5HMhd*~~>?wbZ;88bIo$_AxZSvxA%;v6&xCgk#uKL+NnE)%x24>94N@uTmLRvT8Ixaab}>kz>$iK>a6&gWzuA62JP++Jss zQ=+q41@oDal|SvY!@8hMRjfj$)%M}+dhJX-H!o~hY3kxtC0{NV|3;&bQW*;uB)6D6 zW_JX(@W;5}v%BwmI~;p+)TkD@hxy#y<=eHbG<#Xgsgu8o%;}gN9Q0sq<<2t)y4*R>RC4OpgvJ5smckom(_4f+x`HwD+dbEB)ScT2w7JZK$ zoNtN%3J_{8!$x18|KJ~2n=D_vJ*DCG)19&o4!d{zWenT3>*aE-8*csX+UHYl3_5w^ z%+IrS?ach{;r&@}c1#QUHtu+UZYTFgJ-WDzWpjTW8n|c0lK-sjzPj|dNUY_UsY`Mm zZeO#Sh{^mUv*hT5zwKDPFY(?dBW_VO4@{gnYe#VAXYV5G@{cc-dJX$m?Y9nM2g)~z z?)*szeAPVg%gV7WCxuz=UwOUMTlOY>Vm8G$$bb>Q`)=TKA33rFK0Lm95VpEq=l#AmG2LJ$E=4Qrr;PWYX zq1af!d$y0a2mtWJnj0H9#Fnm=g@lSs6y1kscDht4*u8YWrsbznT$v-*%bU`|(H7MI zLI3N^^CUgyXb3cC>h3#8{vCl^yeA(NkOU2-Z%yn}wxG||-$JQ;*1OSS^%roXK(3ze zRrpDx$G`EaqIS*8L+Fgkpd49e63=S3e{2!wpw!~RE<;Lszb29XX+b++MKr>LQO1tl zj~{C$!ym+~lXGpZ+Q7)jNN(9~@+xw1)9Pi5dw~VH(Bm%qvv39Z_WpHX;C6-^G?|7c z-du9f%y5vbw(fC1C=1#%F*Q-w)hKQ~G9%ukWM$I)`U|_6G%u03-4YHmedoy4UTDo| zkJyA?)kb|gNlAaISAhrcS>&WS+rVvhnE2>j@-mhM0!(9m{M}-C%iaBxb{|e#4!$kJ z1ng|g6>fKbePw!b7=7@m%Yuzis1E-w8`~Xegg?;JK|1ZUqmCXIuV%pUNJn-yAnNz( zH$i3qx8W<%8OgW#aPXC%u${SV2@2fV%eJ{VA;^)6Re;03wJ7*i95uRs5T8vLjJjY->nr&>H zQw^@d_E1~AwXQj%-ruqiTr6)X+rC*ofzFup7w@}XH`EwMIm$j3+^F=Ui&t7%74N|# z8Cr`zI=P^g%-cIVb(K|Sua*qfwdju65q{I9oKk!9Tj$Q4Ak02i2{%SOA9G6v&49DB zEFzh~U_C?L{4TW2F*SoU1rmFZH zMT*M1ws|vnbTsck&*u)DmFYDpHwC?G{dHO-&I1*HxjcJdrN$CT{g~YYx@&qw^eQhp z$5D~MCK4y!YSLA8@GK*v@||9Dp4-w4`dxOsYb&6CoxW0Z8bH&240>HDLDI=v5i3FUDIt*66YpAgKaA0C9)#0TLjPCR!END_DF4uj-c|2=a z(BimEq8MD1Zy+gy15e7!@w44y@E#5FuU~9#szS<@QO^uBX!xaC>mIS`Hm}t&zjim( z?3^55<^FkSE296$kJH}`c(F)&%{nA`DWNBF6!yGk?D;^B5a}?LhF{FufPhKKX=nx9 z3%k7e@crl|h2Gf>bURJwqwpF zWEk0q4gJc8>1rrCy@SnepWj)ZF1dmCit(g<3;$H6kMyBL{!vu@9(#8K(;bWcy^u|| zygyFQJl^Kvm3bxN7+0`{-xUD4HFoJcgcpmZ*SJ%zo%>G#_rJOym0J66p<4Fj zR`sT$cRM;r9-~gH!LZXwQvs)U+u9ID@q1cU1548X!5pY=w6`~bf#J}#k6cm%noir5 zms4H3k|3WLM|N~i$rXp@5X%!-OmoGA*4*XKX#sR5c16AumoOsh*QhM1cQ)!RbBQ1Nb=7YWDod$VlJs+KQ2DVTKZcAtz@`%F4dK zFG-`}=R;ao!nl z@;2qh3MczMf6i+tBQN!);?x*nD*bsd?V)0nw_4t$h&H)?0Lz}UnAvJ)wNIH3u+m>) z)<6aeySBBSDy~%^6tcp1_&+MQHyqu%-u5pWecwg>E)&U2JS~aYq&H((EnDVXHh!Y? zC2h?xbq^3o41p~cBrx*b3Gt@4-zJ1E0E9wqz29u@wOIQvfwn<9p8{qc(3X2gi%}8b zEb9pvp`7ia5l*9IUXWUsrr2?6Jc)QFhX@=g0i#Xd$m(_;j=e0twY$sN8~hJQYdr9L z)K^VS9SUXmw9*M0f_+vp^6=6LZtlv1XFGL}J25ew$*-@7yrXDep+xEQdjY7qu)@&7 ze0beXo8C8W>dkx;Twn{hW zO``DQo&qVq*Vpe4EE;T3yK8g}I3-|e(mJtPbvuJM^zp831%8?9Ig=nFG6f_YPT~7O6ow{qLvl?QRI5^`2CIQSeuJoSihu zZ(sY3(!UUXMlZ@(@j}wLx4~s`Qyozg9nr_3tVRZ2#$#C$4^3p(1QGEeHCcU)=xDAl zfhRX(MZW|(&@6@F4G5x~VCbSV$;W`fYo8*zsaPBV|DWS{;w2oB+C|rIV_pn7>$9 zL`=3S0f6xmZLby=KFFINosLZnrHNc$zfv#I2F>g0WC9kNoRsUcK{i@?Z@wV@G;b9I z>UK(yIo)Cnu9GxN-iO8z=HtYhoK9RGO(n(|-xT;lq%N$mZYWXwO!Zf&U9%kq!uN}2 zb?7XZB-);nbHc4KH!6#>K4nF~%zA*h9~ny>(Z1MtjZaVe(3_op4M3iuRkH``#;m(} z$HlS3XS$&(Pc036U0M125kGBXq=Y3Tv^!?P2!(v`{71R4KMKau1dOnr|G3=6}PNXh_P zvah|Aav$y(lF5#;lED5-fF558fcjGm{Y4_-wze)D&*9h9R8Di22I27LGS7#69ss7h z_n6&mR>Vp92FOP5pSpuYC0HAp7YwRS%3Hbln8Rf_#7uxM@60I}Z9aN(Xj{ud=I~&2 zV#|uH3Nl?+QJQfs%A$3MV)(2Br1@6W$Z0ce6&#GZdQGwg{RC}xP zYv$QM+;$_l<|YZe)ed9Zi{6eAJY$4RB}a)s+QM%2gYp)j*h~t5Ow(y)2m%Tpc%+c3 zF6C23IIJFxSqw5sR!V%`@kILYU_k8`n=l&_6KzG!!G|g&uDUvDy1$nT|IBAg5+iOW z-UrD~(RUlCEeZe5_N0;L=c7-JY%eD|9?=$9-5m8Iiu^?4cT!c8kI5;=} z#!0OjLGvvTpa*+2P#d&yE^dSmjOMg}ZRz%jkEC(Cgn3le^#x8fv?SVJ)uIT!@-o2R zm(i|Y2 ff1#4{Uj5CQH+dP&n>BOju-tERt;eC=Gewvi)M5tPHQU7#|C~fGfWlQ> zN9O@ct)txp54Xf)rl|Gjdgr21XD~OHOo=M|b3BsK@<9x^S2TvzK_Pxb3mDj+o(SBbitPvwk^VuY*R`svR-_1hZL-QmpmB=U!z*+5MZ7a8RE#7 z9}JEAgp?#O-VDw7>15yL`YFWb-#IHpWk*$GOu0+cl~*=}wA`dcui@KWG0a)dFJlNB7K3U?E8tat}v4xrI5> z<~L)*sj0gQRC@kXtCBQh&+_n?bzHBFwcM3G4h4J#S|{TkmH2fw;33@ET)2GnkDiFc zO_<6#y^5Vz5C6Z3VC5DC-mFmZVJ0KB%Qset_k50FLQYkt+xG`~_y(?p`zQkW_Oy59^W}HX z#n#rQ$w}D#Q;A!MIZd%E(W2Y%*=i@D&0&<9h5pq5CQz-~E;6!$EB4Y^b(v|V0dpE4 z9+@xNbLB1_H!*X%oAAC?l1BFQn)L;3?Zwd~Ow|zQr{xH*=x9N(FmB%twtKTZ7O?_m zC@u3hiU-y%)`l?k8$OTK$&&5h#`RcY>6120UE1~1m?nBA`-lDcztQtf&RU|xDNjP7 z5L^q=)w%N|M;zKP$fF8}IvtNi^HYwVWAWc!3=4}upAWUw=RK6i+@^cSYw>3oOFuG` b{W1rMMMEDg#x|3{Rva+DYHi$L=$Z0=Uhm}^ literal 25461 zcmeHw2UJv77w#Y`L?sXnu?7i7jpAi)pL-EOs!_xS#u628MM4>5KtM;pk|<(}8nKgT zh<}WV#@@h!8b!s9N)#bM#0p{oL3n#8mWdht`C03&_tv;{m~+qF`<(mjy}z^fIm4JR zWKchg79CnB6bg&}UwRCKpFhICc=P7)I#=L3!jBKaz8s}dC|b7B{WDRlSk+FU@clZ_ zZ)C(spMj2oI>dn!)x6{o9TEn2D-=#H(P5krBt_WrQb1s6AG?S9((P;m#Xfc;C?C`( z%w3ug_~qnq$#?P~KVfo^z>0P*&Mlmx9YH~e6v5d>hXjXe9HaZ#)#!DE_qxkUJKLID zB7*wZx#|YA9qBW~)?FPg*-{QPBA~R&R>eACluAXadfDPAMk!H3NuUT$IFhI%iQCrw zv2$(#@0`L#*>RYMSKV;X>SH${A|lLDsf>z>a)=@v)Zqb2jAdCRiYsv(fjbaQOlSlb zjf85t)GDdh;~{B;@W8N$Ky|3CPA|u+BP06Q+35y)`KRu=Lc(4S6soDi4p3A^b74x% z0adl+Pb^fc$MuJBLYK3b(E%ulTu&g zmAzkSlRUTx$r(p+0>Lo&>xa>fDCtOHqhFJ+y-82pM=b`*G4FI4NIUeC^JcZsh2$4pxW5 zj3j5mN%^+RZ;3z0fMb`2FI4WO@i%_?AjIyp?I8L~EdU#Q+ zm%FPci8Jn$7lW!iQQQrqoRqqlgd1OtUT5;U>zEhg=EhQlCrPl3n>*%;ySdU7O`)!? zERB0H?^HmMsHZFAMdFkz>w&w`9&VHuhNCFqL9i(3c&7qZ#V{a15jcZ;(3Go+Wj!d& z3-k13F;6d**E(u9W_NqSK@<>u+>O0yV7xM4878^O{ZUheNyP)oL=S_l*!Us(;i~i^wDeu|i-x zORE|>Yfj>1L0}M`WNWaN;2lLGg2>YXMv8z(Lp=l&a1LitOy_)DEg+5Md^jnAd$AaT zvJ_gYnvnn>PG(drFX9|8@C^inz-We*Q4&!Ja-A7DLPQZ+h2tWQ3KWlu4OOioRH)3d zh(rr=of!r2aDikc2A4$|#~VCr4#RN{qY)HhvL0&)PL^eG6iAF9*oKA-A(1>O(h%qv z_(e^ib2Q5%k|fcnND~~5zMG=o&LJcqN-?mk!W8M+S@S%GA}mj%1V#YdQiD}zo)nP3YI08|`fnM+*w4s1dz!(gTqlly;>i}UI$&3^yiW0{Y2v8_f3kbvTB*L)} zNZ>mnkY59xj}mDaX9)`Y8mo`SM)GS)Vq}&A?ng;g4Lt$sf=nVJhXG%UDwV3i^hB!! zTu`ZiMNz)SjJl-^mw{9Tk)a8kB2^8gCz>H8ffF!9hCymQgy(PyRxv`NX%?u9Z>Wc; zL;{ur~Sm3(kiE<|>IrsfIcqPeHE0!HNY`RD(4Q9?qbUJ>eLG ziz*S=uYrLMt2RY3v;Z|VM0+h?<1}yx4%|Y}EFlq6LsjFvBr!Ck2!t#)F4h7;gO5PD z&!Ai#hcs583o=kB&l0+vuZC2?uNx`n6A1y>S_E%{B&rT;Qs58xskoD5iKbYRLkJaDmtRBv z3+W`ou@vOqjKnpRRAn5)2?m^xq`{W8q>9P_y$mTTK?t~tZ?I|#hZG%_U6^1UUpLyU zAr@&G)#Xgw%c`Jp%70mc%0i-G*&_M63aqh;J6YmoMuai|D%-kM28a-YB$(wXk>X_7 z9eF3I1_Z)(VC{BD&07r0dk!UGs`!Fg?P~f; zSrOxShE^ev)k+8{)4+8&WF8Vn!OnsRd+ykO|M)VBK|KPSDJ%ypL5d_Kbda|oDuTp- zn_$fM$outY3iYuBibdGm5aIDTOh7ml4Spa}5~ouOiPt*~1sl5#BJ6}=C?P<_PQ#Q? zcy^8?5LQ-k2uE;~NCFMv+<&1AZl_6_M`#N6UT~abfLRz0!7(roWN!p6!?x)=_A2XV zM!?-Tuqgs-n1s+I2|IJLih+EP6y%+Qhft8q;53wBr1b8brd}C~W8h-2n8B~NoC*B`3o=15>lz|C=eGri%W!T>U_F-ur6(H0k<~=rx>t`myK8^@w zD*{`$BtjEV%JVWozza?@BCirS8GUbS$`mHDB!nx@gF{dpD1!|h2BC3U5K-8)gAL*L zSH|f~0EHD}Lp$}l6AvFB$Nr(32o5&# z`+GR+kP3t#!U;-Lfgd5dSVG`AVW(Fm>#PaP!834y;cCu8-V}eeTVJQTesoa%_U)ke zEB0&W`I@qS3^X`A5Kb<+;MeY{w>s>_GCT|3BSE5oz*!@ZD+<-Lj0%_tZzx`XlCE}$ zx2XBYP+tb>PCR1XJesIE@$i)w&>e6|5X^5}eKoC@l1{ZZ1pd>U?+3v@#Q%Mas0mW&zZ{U&YN@&5-{m!G zIUDmbo1Q6;8( ztrLB{TVHgPVbRyS^+jJv4i62CaE6WCnu+RNeoadEafRBWbzk^ojpR&I6amK?`j_=3 zh6F}S!6Q8a;b>i>qkfEr2dfd%?|G}_sMjQMpp#dUul3YP)*9=zPr=lzT(wu>GdA$? z9_7o=_xxi~d%5H^=9^cK)RhO=>cN6X@tu#K6<@Ixby%q z+%&>v7$3b}BV2lb7;YNjGK`O2uMsXiKnypHa2du&uh$5d9w3IBMz{>)qt|PMOAipk zO(R@}@zLuw!leg@;ieHT!}#d+8sX9d#BkFHmtlPLdW~@D0b;mmgv&5Kdc8)t^Z+s3 zG{R*VAH7~9TzY^QZW`e-jE`Qg5iUJI3^$E%8OBGi*9ey$AcmVpxD4Z?*K34J4-msm zBV2~@(d#wBr3Z-NrV%c~_~`W-;nD-daMK8vVSMy@jd1A!Vz_C9%P>BAy+*k705RM& z!etmAyS96~2>mhC)$Z z4L^$%ibzzUxHDd%a9pNPv{f(iKkB7WST5=B;p!KC?cVCR;Eshc$J4v7i5>Ox7sJ+$ z@unr~)9CS?;eyjQXAK}-peCs}7so(O}J?1Qp`9zu+xA0-ne8)%gUFOeS zkk!^UV(*j%$vS-@hETm{hY4jHpS=MKhwj#qM~BYn5W&-wwCYjP+3;; zmw8juQHSo+z5fi2>XqYVn^saTMYOiH_Fd5?ENbxay^-!S+}m~NG+B;@x>Ge~&aRG%LPQY+X|Ap4GW^Qrz>a0WlV9%8nIiiXKeA9vU@pw#Ts0 z^pd0)#lfAa7uWal9X@v6n9Z)U)xl|Z%%g7}tXh)eFs7X!Qu<)Z!@`Rdy6eCAhRs+v zbm9xk`JMXj%Ux9+I&P3jhRPv1dvwAbi%ac?JBGU~%D!s_^B8;q{aN{d92ghp@(kpK zGjXbQHr9JOA6ixIaAWGj7pvs_1hrPDFzmA>Pe=DH`ZMe4iCMGNW72+UT6ufjf;o-@ z+vM)n4$8cMhVJTp`N@E!vbgN64oAZete>2{wE93>#qO%As#W&=wSPUz{C!%^{Yf4j zI!elY*ByQv$tN$fIn*YXshs;@`o8&*Kc%EJOWf(WWW-m0jDw%sw{QRX-lyeOCG(Ce zGe5T3vTW$Mp&uk(osMsAC5>s{OKy46zUN0_qQk|D7yr0i)FZQUf6wGkZTn>0=>2K( zrJ>3+Db~5G|KOe5KRj)3`O(2e|D?FjMzTG*joF zJGtCybgM&!r9VCMQ_3&4#k6!A=sFh9V)z*XQ#Acr)HtR?S$M3kj;Yt-4Y**9$F`D}< zy5M-1Zil>F`@+cf0f)U@t@3OZ^qZI)oRXpP88k;5wA!L$Gm~FVoS0sG)%D63=36db z6-&&#`n7BwHv#*6K4ZBn*d%`XBcH{K6^fuu)|W@y-}}iED<=wqR?J?rV#W8v^NaJ2 z%qeiPv23C-IrTh1s`$GnRkhmg(ZeOtm3wUBeA=9INV1>w_;$+)xjVdf-ncQ|OwsgK zli%|ufAdXqby_(wckbM<@(GIkj!CKI6E_ZadEqf8H1x*y2OV6m`Ti`Gb_GvPWlc`Y zEuCLHZZmM;!11|hFXp&EK6}=7)u8UD7RCIeO(;9|chI1NpXTi1DxX_0s|U5Ru;{sz zE7o0zF5|~_jE5UEqc^c*97fzLNE&z1@9E>^+hX=r*+P_$>2?EkNb>g0)0zziM`rQv zcGkX+S6j92YMan{p~u>$pZ~C?EO^oYY4oz++S`?QFUSfoeNa@?f8W^D?|==;wB3p~ zkJvk2z2!G&i|-TdyO($;rUu#+FArFgu=)<$^>9kA#lks#EI(P+^5&HiA-ZsN)T8s} z1GG=09esDFjnmo;Ql}j_Fjn-wJM=$gDV2Oi@}E_~?8-9g`b?4}6^ai#JI~#@I%Lb` z(Vvl_OAc(@@zwVG!EL6P<#g{nEI(;wbo)3>M9TQe)90+e&%87AkFu+6{>P6-bdUuU1V<-1(dD#koJ@!($TEfpS7_s*8i z`grX5ScU$DUc%oJ0r+-2I+4&_HbE}54(!sp<$Qd3VI>D~IR z+xRIJn^!5;ts9}amJAmP|6Y{Z?ZDV;3Dvhtw}qb=T+nu7%D|m=X;`^-r}cm{4}zzB zP~yEZwP^9er%%UD2qV`{cHeP3J~r?0kN4KE_^{KJ^v$Pk-@df}R0i-=<;Jm()RmG= z@2Kr2ip}#DocQjB+wSdca@%Qrl4ojTD>O%+goNary}T1yH}&_AU}o7%;_u(zhdNHS z)>Q4wFJ8H^W!vUQ@9XB4Hnn?fU@^6N`ru?XG0A`ON9(gz{jl-6tv!LUJ>b_n+YEO9 zyO+*iR{*yA*rZJBj_I+c!-Br+oL=HRiY?k%a#xixRa5iK0e@RuxOnmNs<^wQ&8~Xh z7B)rZ?<^_`9J{~&Y_(IGJmch4R_+zJ~rT?klT4$r?I6xG*qY3KaQn^y9p4%|C5Tiwg~>b8&4|D*jqpiAt~axu%R z=Y~Hze&_MQm*BeJe__&pN&M%-T)VEEU3JDXZszlpn4>xQ)n%7bTjck7_UEA)8OqPn zf;-&WGs8^i?jJW+J~|$jt_OdzO`Dipx3Eg^pR9Fkwm5BBcKk5q!lGY$XiJOKKW~>W zxo0RXVox>|$JxRPw zWNT*oJ8DNY88mQEenwDgkF>m1)thfy;CGWf{rx*%o3t}!qoU%#?18a;y8BlB(mlO- zKh#`NoD=+e0sp9_bzIIFx-5iN#CK`86NSAKw*J#yJvtVbX;-Gz?Y0#z}VcK;6;5bG5(hE155l0T0nmtXt(RpBA zSi1}IwtV~HyxenB<#=@W{A4G)f|V{IbeGl%w(VNkwQF^9X`yLp{_LLvD$nmu$-HrL z&h+B)oLwJ%IP0SX533Jn9Tg6jv00|SoIiZHNz?UhG81xJ{MLUjW$D-{KG!m6)9|EU zr+xcvvtDGV$MTs<^XYTdr7MwGVNJ(`&?Vwx%0S4Dz;9*&QCQ3j>^w$UEJHHa%km>{Hzfr zkGz%n$JLei(*?N|4#$p#-n>@UP8YD>%~G!2rco+x-u15@eXWJqX20LQXWErz%?s_j z*?PMvMo!-1x-sMa*GcCtUYrTxuduRTG@yKo`C_^WEDaPXpSR zc>Tee`O}+uRQ5|u%`x8{8D!u5jHM!e!%@=`@1)cmpFdWoJNyM$#y@fJ{?@9bvqkK< zetqf60qGaVeECuJxxWe{o32{9vQ6H~biZk*{U@*e@xX$z1vW0JU4FdS`^?=Joo20Z z@jGktWo5qwsew&TT{3$B$++i*yTP+2)#T&Tf=Z@&KeO4>T(`ok=@o_J9WoL)@maga$8(0dx0psOeOYfPQWz1{gKlRC_9f{{=CN5f({b$DCqSt{z z3ChUW@TN1G6?or0xU}V=!G+(Byj`~VhnC4BW+Zl-eaO}=@iV{AH)ikuaMo>$?DOZR zr=>Yx9Np{pXU`9P-{toKW2?%l{W|t-nQLFNuJn`H56!kjxP&(O{PXfAcTTizqx{Gv zeg|D#F?4IY^L=7}@!L6NPcb9{%yididwVNvCAU1GLhEr#F3v;C!rE~~n|4<_Kz)!g z&9uTKqtC5nCwyrB!0Hl{zUIxPOf9pqcXig*;U0ED54((AGw7nHrE|yo_S$jn`(0~( zZ`tibN!^l{D2F>}$7P!Qr#OZ)%|EepOK$6?Cf+-CC?KOAZoW&Mde@}f%G+74?rIZ1 z6KSXLI4xfD#HUpkN_-ZDgBQCL^cZ=kNLFu2v>tk=$FQxM^oxVrDXOB2T5ldx-gKeU z++~Ammam7+_FaE)#dq+1PfckzALp=*=OG{67U9V%+O=wNtVgKR)nS_m%#-P(MAwr|&JZ^|zWG zKlytelM|odKe}@J2Uo7{N$NfIRLTBmO>3sk#R1>8=@VqY`|mip#VSY|*m uJiT+d>7qG3!@X>WHd!=aM74|JS@5`|A5Y%zt^1EL`g;!Y*z7ib`u_n=yD4S> diff --git a/Resources/Textures/Structures/Furniture/toilet.rsi/disposal.png b/Resources/Textures/Structures/Furniture/toilet.rsi/disposal.png index cb2db2f9d8e9006ef560ad3c6962142e9de840dd..de472b8e7cba1092f2a5f72137c8bd168f3affc0 100644 GIT binary patch delta 2451 zcmV;E32gTF@&TI@kRyKyVo5|nRCt{2T5n8LR~G-h-AygSsO9meIy4Me*$7xd$Y!N+ zw~4PqqHY_i{jkYyVi!r5M@`hNlEyDExNC^QcGXQQY#nJ$c9Z=AnbfUKz!AI4iW>!j zTDIbf17+G_#*u*ni`BgS0Qd6V%=>d^=7DbYmrU~Bz31NZXU>1Q_r7}$@R);>zeti4 zMIee|bYl!?YHH$)sg@QTw;<9F^aw>!lnxv?fY9AgT#Wb>9-=5pW*I>)xE|F0NG<3Z z;86gp(rRN^4DmhINjKD&M76ow+;F$KquOgrUSyqC7x5?`>v$#);8S=E9=V}tYdO_| zvmKY|d-=|C`uu;#FTP-v@u5R^5fnvH`tQt)=HP*wcK`r+Yg098o=!?iXdz6*3$#Lj zc@YGF?c29$_O-UQ8Pa@m_ERGkx~Q$Gl|K9XXK{^n);a-zbJjUEX_!JFEj>fE{nwS1 zy5oX@F_!5y;jDE^dmRT$IrGo}ND4enSJ0FsR;FP1~y9saBB>GE)CKAlw0>GT|cDo$_FqvOK z+sU)tZrAhV{!l=YVBTFT?coG%Oa2qHVFDwhIm1yn`3k7XkpT zUhAjrM0wU-D5ekqfR!s&0#GVUo=KnC7`ip2+Fn?ghr+_Vn8rkc$pHR<9}R~a;PrYD zo(O*0a#F6Tm%vd#Qh^d!hpCw z>-K3F0yl0f+yl5=E>u)hz;3rATS2v;yvyZ6C>V+wn+T2`JC413cH`)=Du)H?A^1QwjVljQb8y0 zyLNp508m_9WOxoSycGrjK@g;>s$D29Qo0qNHg_H%|2P1u{D~9q!Q=6mb|WkZf@HJV z=={+oPt;s7U5E&R008Liy+YeEGgm8UNW7DOT~2>% z1_VKn9F9_a^UWjxz-nEN{CvRuw=Muc+0Qlt0PWAuFF;2}yY4*zfuR9B9uEXTfYrJj zy}eg3JUooDGUW{rQQYlrLsnK6tk&f*sKkAfkfZ2NwFgSRF z(sp#TGtJ9<4j=+#Wo5zbj$VZUQ00GP5r73AkB1WkL6XOLB;*D;dh9q20VA-k0RW&Y zfE-7U9XB=pWpXfmTR0r0-1>q%B1cred<|eW!2H(2B*0=QZzf_kz;_LD1H^n3{;uJP z0Uovh4;zoY0TkzeSKoRYsZ)2fRzmK>k3L!gk<7ub-grY*k2}e!iiKZcfYg7fJ5t@j zgNlL(K7~hfb2CDrkR*yC*VNRsC<2=aQ4}RxW+o~tE2DI&t*KEQkstdI&@_O&$iX){ zimh8V1HdDHP#~Es-{>f9Q!DNu&@=$a#kHJjLH7LB_x}^JvvX)xNiSEZ8v-N+{pHv9 z%$05l@a5O{aJJ*JY6*`V%xix@T6zYyZrN;Vvcqi16S=I={Tz-=Kc}TL(>3y$`gUA_f^x2(MW`XAycYENpjYRkKP;jG>HYRr}KZbCW)k z+#!*b=%9tT(&OXfrbBlX?bFlK0D!4g ztMTC0ttby*L0n&hRaLvNtgn||q7p&m@Or)IzuK=zmd1imFa(#&h5oDk@Or&b{e-6^ zNqF$jE>u+9JQ&E3W0jrGe{`j~;mY%-3YB1vdWDPT_SdBCQ*WKMM-M{}WezI|66tS8S zwDxBJ31y1qWB4?l8bXanq>Mmxh1fs3x^VgO6(AxNpm^D|X%l(WJXlm!?V`!iXVcXe zB?6-SxpVDtodaHexfGe1tC{!!KxgL#qm<O!x2rwxCKH6VFi5t7#x0RXpm&u(sN z)`HhwFUOkP5=_ln;Bb`U#EJLN*?9q-ofqH__;r8rDi6$s!nt$p7#;n}kPnfkqoW-T zM=7ReEm)IVg4bRz$JDF^d-v?-0B|z>lola)rRq2IdFtPDF*P-7k$(A$pQFEj0JjFa zVYAub^Z9=O01ii~t`hV4`Gk(bBsFft2LP3AO>PMa3-j>jzx)-Ky3TNdASe_+bK(#5 zS#uFgURQ*V-+vbXxD6X$kpLih*<1!VlCc_WHXCd<8w5d+;xz&(iunBV>(uC4yns2` zd_EtpUhBuzYyG&?b%rClXhG8e^U4DNok$P_$!dRHt}ucYNUB^_Dp;N$n-#j9_K)isYxIa3o?n39=L_$UzDf zV&Nf&!%@l|J$8Ii>k?uzz(HF(iQ`lNi(hfv}Lsk?QX1Fpy+&Oh^FbMgfK8 zlp6#D1O;Kmh2>V4Qz5X(sR#l=j`cx6K<@X=l{7JNaL9Z6d^`L?Qt7Vxs`_7F{p&lb zGh;h;Qnk18KP}bCU*UVRILpYtPG3aqIwil;_KP`dmjBTsk`sAquLOE68EfE0E*7Qy~br zz*a#s=N)%5mrl8-IvhZ=#N=S)u;(P%?dg$jb?xk(3sX#tOFd1ky4x}}dwy=~aC6xv zrHWH34O@`|M=}({k`xbLNwi=A-KMFRDB40%1|ja6Wn?7SHJd)rD}y5`hNQV9TC_0m z#izKN8!DA;4r@?D4&+OKv6?}Irdo7+P6l#B+A>fYHfLp~g_}z+8fum(r6tCmZqI=c z;Yc>zT;An!TUqUDo^w@1WvVzArXbfaH84T0bsvq=9=z(kk#38maii(m2 zCQkAxfaj>#C@GF(_$b-RL<`nvK8|K6inX#b1u}dJkVQ#?20Y71j8)*HL|L}-bQ~QU zE7P%YVw_I_u>xz2=D0YX;iF??qXe0z*=QQNk7i}T8W-bJfSbBCyK2)cr87qX44S}W zQsQKeq-l&vBe(Nuf0oRvN2Nte`SH?XPG? zP*^9-6eWvptQ8fgLlQ+w&@n6XoZo;jA`)~OMy*P!XVkPVlRC%4T48BL7DRtXjWE(x zRU#Emc43X7#B-#oDS}FK8sOothR`a57@4A-%7@&5RF?8F90v80X_At8$}L*LfDh6o zQC2htDXQWpAS^9NvQBZNsOp|+M65=Uy2vmZL#ez%Y5s~9S&`CZnZ$ysd!|tZ4OclC zON_1w4CD8x5zR1&7Dx)#q!(*goU$x26p$Fp%Kk1H4j!AAXuuk%$Sqn?Xo{2-fnsSE zFu}elx_l1d5Tj8#!lcYG*aL{pG9;)v0=Y2Bl)t0qVOdK8Ly=hIT+FADQYB%{BTd6d z0i%GHB{v|Fq;MpXLEB-1HDD+|HH6m$osn4{l#li9i_OY(YfC+IxwHANdn98oairy2oi@?q|&4gowzkbK@1OD z4T}Xq27Xce)euo*VGjV9v8Z}>83n`QR7FWd45K5K_IDa(pbE?(-pSV<(NZPxwXCTa zQvw4Vf?f1fd3r5ljnx&93NU#0Mhxp-5jB8`VP&27m!1&Evy_UQyNpPCmJDp` zDpM-U>HcnDV9HP&VkJ^yc-A9YKr$#F1(=Iirg(pqPvK!hN3g8`6}hk$K*J>pOdN)m z7){iG{rp_$u(k5MB&gsm5a5?3ZNt4 z;Y$?qa7bk>bX5lmRbtLd?tjdUr-=cX2>}c3_VSpaKuC!6ZS`-Az zX)~b`)4g(hbRC2Fk9pX(IK|^8Rh9>)V?~!Va09^o^z1S~gftkzvchXT(jkQ5lT_1e z5V&*4EL=qzd~if@FjQR^NKOH@P*g0F3a_`Po0+kXFDJm3raz>t6Ev1vPL6C5=0WL zT1;}f09?m_<$)0o?!N|McKY`}zE08Lk3hsjM!*uB#(_Zxb_*%891YwAeLjTUUv{M6 z#9|PvK?FjB$49hEB2fT6(0GiTV!`n8IZ+UT4bvc8MFU_0Cn%)ALrjBVI0?u=LL@^2 zqF`{bj1J*QA2C3$FcxYvK!7A{JD3zW4kBKpAnaEib;_|pei5Y`>Ud|J zl66jDC8vW28?L|rtWKMX1o5n>DvT;1aPZt+lq2R>UB%m+nS_B4hWuTbgswRI!K?osxyZesXDVq7#8ybS4)Rg(lXp#!S?Iv zd^iYx)x6?9^84b<-^xC@MY(EKEF{;iJMvKuj!J_J&BG+d+)^KGR#v>PCHE*3Vb2_Z zbAHcMz=R{nS7XRK={fccQ%SnZJXasOXZyLUNMQHEG%zfWk^>3`?Sru zz}>y8eNko_)*{W`;dn3nktPk|^bjuvQ(2(FdO6y+TF?dlG>rbg01U4xh zSd=ab_87#T#rMce3ck7^OAs)Flp#3n6u9rQ<~-6sLrh*(A>k}Mf+OKP^(X<2TDX8s zIv`+^mLO2^nm6nj|G73PrXYzbG78BGia=@<$3T2gph;1HB#BNzN`MP2%2I>WX>qv2 z%KuKAl&6p+gEIj;pAI=34T8D~1k*`4{wX02DVWEQ8S%rBfODm={qad~0G$Cc4TNMW zlS%(L_F2Ftb^9R|?;QrC(wk{{xIA89QI?YF{{be2#5^1%P~qe?rbWnZa*z!dA!`MZ zZOB(bK9@)SdG~H66`XJ4u(XBe0^WUzMOI>LdAF$F?cM)zVuqE+q<@@eeq=|HQZyE+ zBBbHr%mEzJaeDWHqC@h6!f;%X6?w@&n>4T}OUc)p?y4;B{^1T9__zJRiM0pbY5ac> zCv|7HA3SH^J-hJha|zx<81Ae~oHGOM^g_Uc^>GXVLOAf+tfd@ta@6fs0Lx>^<{t5<00-|$`MoAn|B_prEnWggM>TZDz?D+5;*JN%K1ma zxt3pDGe~pY>)|{nlAh-}2O`2-pA==Lo!>o8jkn7-P5$ks#@l6^mbUDcX>&wEcF8rE zGM5dloUcB+-_Y#}ulwUjf#+G^+On6+w&-jt!0A1#Hh4of*Ll)KfWwZiHf3*=+c3GJ zhK3uShVEr!cro8KG48AI+CTio0dvWp95Bq8f%Yo9So%bjE!iM1(1`&I-^Bo2zA-Y? z2*70k!*?+Nmv4*=H3D!M!0=rRz~vhwLyZ7j1~7aV1917q$WS8ymjMjl#Q809?K?GSmpb zWdOr>F#wluj0`mba2dexT@1kG8zV!F09*zzd=~?7`NqglBLJ5H4By27T)r_f)Cj<3 z0K<1N0GDr!3^f998Nl#e48Y|ZBSVbh}30G9y_-^Bo2zA-Y?2*70k z!*?+Nmv4*=H3D!M!0=rRz~vhwLyZ7j1~7aVe-o~t(hu>&nefSd`S1~aU!MGV9eli> zNsaH4KoEmoAc$cj3F78`_<5cn@+gA1kV+7i83fVTKDp1fIQZniY8|XmNd?FLl`{0T zXAU>L-?P!Qsx521{oFs2njF%fZl}a1e%5()-ob709o7fE5It*6hx0WrQsbvu6E?P* z)xh-nsI88wOTt!dN8{IYm=Y44vf;HaKKo~8sP+m`#g=le>RYF_AIYW~ELt))bLfm4 z>)8AsKfScD!K6<{IX2#o+;Ztg?uq7?HeH(j!@y=2XA0YuRY%ij1^2Vt(+-6a!tC+w z-n_+)xG}QUU1|2-FZ54~2aPn{NUWEfb~=UWzOW!BCMGm>#`Wt1-mG%uc;^upm)?$5 zBaTN9!>&{-T3CE+^7PIph9=|}{q*9$QpblbU%tG>z&RaX95LMVti5dk@opxu>U`+- z+1st^+{SIQx8;qz*Jt3m$&vSe__WZ`ZS%r~BkbvYKiEBckKSSP^5t!$xgC#mI+B=} z_<5hrZ_M2PMEjp+vh-tf=FFK`G+2<6St4%Hq&9tzqOl*(oJ*eCwPA4M_nJ0J>elV^ z{pa4#Rq8I9S|dYvX3NkMy^r4L)$2G+%Hwr*Ub%C6cRL=BSbxlN@AS|&=j{7#`p};l zg37y=cdX~u88csy?|pQk-?o8O?ti?*+^75NJGwVp*|VVYo??p@U!J@&+hkc4)NsO|d$&pb`aKJVB;=b8c?P@jo%lgQtsk!lRq`>J#EB6eZiB( zL7xshaJE~}l(Z`y?X%Xg&&ipq8*EitkLlkrn)u~Tr1W&t%LU;z_q^SiY8+i-_Hc*L z^nAL7Ir2nrE3xgU7~9I5z0kmVFJsxtzQLzm}a? zWl~IY;=Qi5Ke_(SifOZ3>YEzBET+yq(3O~1b=BSuYw8sIv}f(a)diix+Au?hT-wAh zI%4iUv{#qE3|{u9uTR|nWw4E48eJ;BmR30K>5wCiMopVGtyyE_#yxu1Uim{OYo8u1 z=-TnA^Syg-4*p^8%mwO(XI?q= z_^@wIS^CnACKG%2j$F~E^}(Qqy@pMFf6Kbtse7ti-gf-)b*I)xeOtgSjZ*bS z*DrnhQUBGavu6$spVngA=`6?1qcdiXZg0B0;_ijv?T59w-}5?K5OzEEXub9QYJV8s zbL_5N(_YeJ`dwJth**$5WlH^p+gGe3+b2wq4b9*8k+eLo>EgddBokAozF2kA=7`oo zHAbv=j19V7ZQ7y4TTd>Y7u$hQ^0bNlR(E-F-+9Nv1q+4;H8uzD@3kPOd-2v+lJBkE z()O9PVIjnlB}?(`yRhb-PJLrY^I5ZJ+Y0A3s5`vF3RAQG6E6gHOZjTZ?g>*~>@-e$ z?1RD2E?u&88o71ql#!E^MpwNGPpHwY+&6VhF zMTgkgd-`T(ZePL`XRKK@-h6U??EP!^x7TPNzpM4G9zo_GPJUkP+=UB`cxzZ_=)TDX zTv6dIhk5E-XJcmUJl1B@%QsK_7!o`Bul1VNJNEd_{oj4}>>C4broMNh`WK8$6y)X( zU)K6~dsw9>L8qjGK3ub>-mXHtbM)KG=l0I+*68w)Fl)bl6HKB4=6$^DYIw7|OY89b%WL|KvDT-X!hGdbi@r2q<2&N= z6FrWON0!%uSO1{(#eEB6xG|G^D8ZK_=FG0Y3qIYaI`Pe& z4o|#uaP`wq5(iG4C~Wt}FHa8Xk+ObVa!~cZ-w92Mob<}-tf%^Gt%JMtw|>>?>*}NK z9Y6f`Gh$}d#B0%sy@oZ2p0@Wc45M$hqyO1I$G$M;O7kH{X77tkEu6=kIg{S?v!{rj zn{Fk3Jo(vb?UTrQ$?Y%P$d4PbduhzqKrh+Ah4-({zuVx;FW1tH<=rdYU(LREr|qhg zuL$CkjaN?3=tI{n8ohE=TBqdKzW=CuyQ&0m0^Ehw-h4oV*Hc$FIB6bw_n9GAFZN-` z6&XqGl50Cx)w!#ume!r0a>p`yL{{3y<}Ihx>itP$qE&Ftnx69yuW8tFadD4}#QT4J zGk=%nom7cwf*gSV>U+j8g*zz(6Dp0st2P*(F@*xJ!#y}=jxj-ZcBM-P=q|1|Ec;x zu1(FcT$>ilHv$2!S@XAuLz_p9&%V_H*md`ZKh-yl4@%h_Nwl1|x%!0DiV(X~AAT*q j(@QZIn-05QhY0CtZ+o@HYlEF1jocx&qjhC;>WKdaH}1eG From 9bb599f5494e61f7cc41369e030fd15f394941a6 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Mon, 20 May 2024 00:05:12 +0200 Subject: [PATCH 018/235] Resprite zookeper id (#28131) Change zookeper id --- .../Objects/Misc/id_cards.rsi/idzookeeper.png | Bin 1413 -> 245 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Resources/Textures/Objects/Misc/id_cards.rsi/idzookeeper.png b/Resources/Textures/Objects/Misc/id_cards.rsi/idzookeeper.png index c24212aab8d417dd9f5a8852f8b5b6c40e9bda63..aa82cb3cb633b335dd5e6b2efeb90f8476d911aa 100644 GIT binary patch delta 218 zcmV<0044v03-tkzBYyx1a7bBm000XT000XT0n*)m`~Uy|r%6OXR9Hu2U>F6XAut*O zqaiTTLx7Rk0C1bj@E=|6u4D$H^%H9kR&^lDBTg{<2LWUno1;jzh=~*pktZP@hS8+x z#if>L2P`*7jRAB%xsD;)0We>~ET&c{5bXev4`G&5%l9BNSyM;}I%@eE*-oOf(ST+{ zQbK9SsU<23K*2AhO`6}{U1eY(rlcgw0U)bME%k`8oHQ?usvZr2(GVC7f#Dhg0DPG* UDgj>1-~a#s07*qoM6N<$g7?%>+yDRo delta 1395 zcmV-(1&sRj0fh^YBYy;ddQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+U-|ek|Zez ze)kl61OX8}jz1W&H`wFvrl_> z#FJ{O+%PEddM-*ao&G$0f5Q6l$FZaw8zMDb;5 z*(>uqyWEO|Z~pxr>OE-B+qr){?)h)q`tY6%&X@{)!hu~#BKpWvISDKPTlSBo?Qf?q z5RPY@MA56HS09Fl&GE6?ixVHC4?c{3^X1~dE6NZ0>Z_Yi`hhQxW%$J+IPGm^ezr&@ zT6}(4ymOKm*sm?beV9O0l^NJ%9)nY3_r!#POE4owm4wK3~|JcHmDNA zvx^c2GA3*`C$v4b?aGfP>C~6 zf+Q1^(LwfZ;rZp?v>z0$wL_gKmg8=^0OlR%Fk>UPdw)m)5L!2GKs4Mgrl+`xPrwQg z)Z^xY4Yt@X6WPmt(IDAYgn{FbHnTgiPk!I>8Wea z-FoS@w}J2(Y3RtqMj3Uq$y35a9hiCAEVIt$vL?jJ z$O>gxsT-V08!;f5oLCn-v+1{K`0B)OMc<0Pq^M8Nomy8z_;(zc6g|hyZ^)b+`tZTFdeYUj zOn+}kk4{U*BweV`q|=t_LWx+pd$O@aJx*OlxHUpA<5(aI-I3nPb^2@-0!azi%I2sU zd$ccPpQ7Qjf}2_&(~4XaMY*GL{p4?E=#0n2Trk<&L7X zsE;;C9oIXba_Fcfc?)@p3~I-#tBI5^LVp)NA$ah@VuzLm_ec2CR4%-FYzMWnxUFs| zUNiN9LsM|WZF54)^3j&>Z9459_Z;u!Dc^X(H4ofEt38bsb33u(W#A@v#^v{D_>|zX z+RkD-A`?PTH(Df)?J?~4?g2d_t-{_pn(+=I1fmi!88~s0QNbUE%#7U97ZYDui5)jv^H> zk)-;08w67kFp;c7vWlvXU>Zov$qxa^Dylhvg;<4QDXSS67*?Z*ndwQ7kZd?w5_VU3 z|1Zh2BFW&MNymtaB8t)gJ}{a-M!_f;1*3pi005GeH2_lJ-Mat)002ovPDHLkV1j Date: Mon, 20 May 2024 10:07:45 +1200 Subject: [PATCH 019/235] Fix gun cooldown timespan overflow exception (#28136) --- .../Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs index 274828a2086..f3ff89a660e 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs @@ -112,6 +112,12 @@ private void OnCycleMode(EntityUid uid, GunComponent component, CycleModeEvent a private void OnGunSelected(EntityUid uid, GunComponent component, HandSelectedEvent args) { + if (Timing.ApplyingState) + return; + + if (component.FireRateModified <= 0) + return; + var fireDelay = 1f / component.FireRateModified; if (fireDelay.Equals(0f)) return; From 053dfd4af1dfff10397ddfeea5b8fed23946a7f1 Mon Sep 17 00:00:00 2001 From: Teemu Kauhanen Date: Mon, 20 May 2024 01:22:45 +0300 Subject: [PATCH 020/235] add WhiteCane recipe (#28146) --- .../Prototypes/Entities/Structures/Machines/lathe.yml | 1 + Resources/Prototypes/Recipes/Lathes/medical.yml | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index d4c3e9dcbdb..c9eb87fd277 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -953,6 +953,7 @@ - Saw - Hemostat - ClothingEyesGlassesChemical + - WhiteCane dynamicRecipes: - ChemicalPayload - CryostasisBeaker diff --git a/Resources/Prototypes/Recipes/Lathes/medical.yml b/Resources/Prototypes/Recipes/Lathes/medical.yml index 822945de103..3c87d0090bc 100644 --- a/Resources/Prototypes/Recipes/Lathes/medical.yml +++ b/Resources/Prototypes/Recipes/Lathes/medical.yml @@ -244,3 +244,10 @@ Steel: 600 Plastic: 300 +- type: latheRecipe + id: WhiteCane + result: WhiteCane + completetime: 2 + materials: + Steel: 100 + Plastic: 100 From fe45890228429e42ff30ef693803f21e31b85433 Mon Sep 17 00:00:00 2001 From: Killerqu00 <47712032+Killerqu00@users.noreply.github.com> Date: Mon, 20 May 2024 00:23:38 +0200 Subject: [PATCH 021/235] add (un)equip delay for EVA helmets to fix sound spam (#28142) add equip delay for EVA helmets --- .../Prototypes/Entities/Clothing/Head/base_clothinghead.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index e19217686f4..40f9e0a3d4e 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -131,6 +131,8 @@ #Copies ClothingHeadHardsuitBase behavior equipSound: /Audio/Mecha/mechmove03.ogg unequipSound: /Audio/Mecha/mechmove03.ogg + equipDelay: 2 + unequipDelay: 2 - type: Tag tags: - WhitelistChameleon From 30ea1215f842e8c5c2ab85cafbcfa5638517b42f Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 19 May 2024 22:23:52 +0000 Subject: [PATCH 022/235] 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 9dffda89017..34a59d22bd1 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Doctor-Cpu - changes: - - message: Cargo palllets can no longer be ordered from cargo request terminal - type: Remove - id: 6105 - time: '2024-03-07T17:53:57.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25882 - author: Beck Thompson changes: - message: Lawdrobe has some new items and new ads! @@ -3868,3 +3861,10 @@ id: 6604 time: '2024-05-19T01:35:46.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27975 +- author: SHOTbyGUN + changes: + - message: White cane recipe for MedFab + type: Add + id: 6605 + time: '2024-05-19T22:22:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28146 From 10aeac756039c70bab2a91965fe98dbea78fce97 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sun, 19 May 2024 15:35:22 -0700 Subject: [PATCH 023/235] Make standing state down sound nullable (#28132) --- Content.Shared/Standing/StandingStateComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Standing/StandingStateComponent.cs b/Content.Shared/Standing/StandingStateComponent.cs index 5d7bb0a59fd..02708722355 100644 --- a/Content.Shared/Standing/StandingStateComponent.cs +++ b/Content.Shared/Standing/StandingStateComponent.cs @@ -9,7 +9,7 @@ public sealed partial class StandingStateComponent : Component { [ViewVariables(VVAccess.ReadWrite)] [DataField] - public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + public SoundSpecifier? DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); [DataField, AutoNetworkedField] public bool Standing { get; set; } = true; From c88d5d53dd283b6c0f5bbdd3dee2595b75379e92 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 19 May 2024 18:35:55 -0400 Subject: [PATCH 024/235] Fix magic mirror (#28084) * Fix magic mirror * buff magic mirror --- .../MagicMirror/MagicMirrorSystem.cs | 32 ++--------------- .../MagicMirror/MagicMirrorComponent.cs | 2 +- .../MagicMirror/SharedMagicMirrorSystem.cs | 36 +++++++++++++++++++ .../Entities/Structures/Wallmounts/mirror.yml | 7 +++- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/Content.Server/MagicMirror/MagicMirrorSystem.cs b/Content.Server/MagicMirror/MagicMirrorSystem.cs index 84f1f1c3e57..fc1bff97566 100644 --- a/Content.Server/MagicMirror/MagicMirrorSystem.cs +++ b/Content.Server/MagicMirror/MagicMirrorSystem.cs @@ -7,9 +7,7 @@ using Content.Shared.Humanoid.Markings; using Content.Shared.Interaction; using Content.Shared.MagicMirror; -using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; namespace Content.Server.MagicMirror; @@ -22,14 +20,14 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly MarkingManager _markings = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnOpenUIAttempt); - Subs.BuiEvents(MagicMirrorUiKey.Key, subs => + Subs.BuiEvents(MagicMirrorUiKey.Key, + subs => { subs.Event(OnUiClosed); subs.Event(OnMagicMirrorSelect); @@ -278,32 +276,6 @@ private void OnAddSlotDoAfter(EntityUid uid, MagicMirrorComponent component, Mag } - private void UpdateInterface(EntityUid mirrorUid, EntityUid targetUid, MagicMirrorComponent component) - { - if (!TryComp(targetUid, out var humanoid)) - return; - - var hair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.Hair, out var hairMarkings) - ? new List(hairMarkings) - : new(); - - var facialHair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.FacialHair, out var facialHairMarkings) - ? new List(facialHairMarkings) - : new(); - - var state = new MagicMirrorUiState( - humanoid.Species, - hair, - humanoid.MarkingSet.PointsLeft(MarkingCategories.Hair) + hair.Count, - facialHair, - humanoid.MarkingSet.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count); - - // TODO: Component states - component.Target = targetUid; - _uiSystem.SetUiState(mirrorUid, MagicMirrorUiKey.Key, state); - Dirty(mirrorUid, component); - } - private void OnUiClosed(Entity ent, ref BoundUIClosedEvent args) { ent.Comp.Target = null; diff --git a/Content.Shared/MagicMirror/MagicMirrorComponent.cs b/Content.Shared/MagicMirror/MagicMirrorComponent.cs index 63575439052..95b17369795 100644 --- a/Content.Shared/MagicMirror/MagicMirrorComponent.cs +++ b/Content.Shared/MagicMirror/MagicMirrorComponent.cs @@ -47,5 +47,5 @@ public sealed partial class MagicMirrorComponent : Component /// Sound emitted when slots are changed /// [DataField, ViewVariables(VVAccess.ReadWrite)] - public SoundSpecifier ChangeHairSound = new SoundPathSpecifier("/Audio/Items/scissors.ogg"); + public SoundSpecifier? ChangeHairSound = new SoundPathSpecifier("/Audio/Items/scissors.ogg"); } diff --git a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs index f9c941ffe39..91059d60bfd 100644 --- a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs +++ b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs @@ -1,6 +1,8 @@ using Content.Shared.DoAfter; +using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; using Content.Shared.Interaction; +using Content.Shared.UserInterface; using Robust.Shared.Serialization; namespace Content.Shared.MagicMirror; @@ -8,10 +10,12 @@ namespace Content.Shared.MagicMirror; public abstract class SharedMagicMirrorSystem : EntitySystem { [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] protected readonly SharedUserInterfaceSystem _uiSystem = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnBeforeUIOpen); SubscribeLocalEvent(OnMirrorRangeCheck); } @@ -22,6 +26,38 @@ private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, r args.Result = BoundUserInterfaceRangeResult.Fail; } } + + private void OnBeforeUIOpen(Entity ent, ref BeforeActivatableUIOpenEvent args) + { + ent.Comp.Target ??= args.User; + UpdateInterface(ent, args.User, ent); + } + + protected void UpdateInterface(EntityUid mirrorUid, EntityUid targetUid, MagicMirrorComponent component) + { + if (!TryComp(targetUid, out var humanoid)) + return; + + var hair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.Hair, out var hairMarkings) + ? new List(hairMarkings) + : new(); + + var facialHair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.FacialHair, out var facialHairMarkings) + ? new List(facialHairMarkings) + : new(); + + var state = new MagicMirrorUiState( + humanoid.Species, + hair, + humanoid.MarkingSet.PointsLeft(MarkingCategories.Hair) + hair.Count, + facialHair, + humanoid.MarkingSet.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count); + + // TODO: Component states + component.Target = targetUid; + _uiSystem.SetUiState(mirrorUid, MagicMirrorUiKey.Key, state); + Dirty(mirrorUid, component); + } } [Serializable, NetSerializable] diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml index d02bce020da..1fe3318ef55 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml @@ -11,7 +11,12 @@ - type: Clickable - type: Transform anchored: true - - type: MagicMirror + - type: MagicMirror #instant and silent + changeHairSound: null + addSlotTime: 0 + removeSlotTime: 0 + selectSlotTime: 0 + changeSlotTime: 0 - type: ActivatableUI key: enum.MagicMirrorUiKey.Key - type: UserInterface From b463896c2f7e911cc91d2137cae5845217f43f80 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 19 May 2024 22:37:01 +0000 Subject: [PATCH 025/235] 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 34a59d22bd1..f0a01a2354b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Beck Thompson - changes: - - message: Lawdrobe has some new items and new ads! - type: Add - id: 6106 - time: '2024-03-07T19:18:39.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25299 - author: Blackern5000 changes: - message: Salvagers can now pull in overgrown floral anomalies. @@ -3868,3 +3861,10 @@ id: 6605 time: '2024-05-19T22:22:45.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28146 +- author: EmoGarbage404 + changes: + - message: Mirrors work again. + type: Fix + id: 6606 + time: '2024-05-19T22:35:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28084 From 09113824e9cbb3ce3050cc312845f4e6889ddaa6 Mon Sep 17 00:00:00 2001 From: Flesh <62557990+PolterTzi@users.noreply.github.com> Date: Mon, 20 May 2024 01:01:44 +0200 Subject: [PATCH 026/235] Add plant metabolism effects to chemical guidebook (#28038) * draft one of plant metabolism guidebook * loc fix? * add attributes loc * fix loc syntax * improved appearance * last commit was undercooked, my bad * last commit was still undercooked, my worse * last commit was even still undercooked, my worst * Addressed comments? * Fix newlines * Hopefully this works * Cleanup, I think * 2xs --- .../Guidebook/Controls/GuideReagentEmbed.xaml | 11 +++++++ .../Controls/GuideReagentEmbed.xaml.cs | 33 +++++++++++++++++++ .../PlantMetabolism/PlantAdjustAttribute.cs | 26 ++++++++++++++- .../PlantMetabolism/PlantAdjustHealth.cs | 3 ++ .../PlantAdjustMutationLevel.cs | 3 ++ .../PlantMetabolism/PlantAdjustMutationMod.cs | 2 ++ .../PlantMetabolism/PlantAdjustNutrition.cs | 2 ++ .../PlantMetabolism/PlantAdjustPests.cs | 3 ++ .../PlantMetabolism/PlantAdjustToxins.cs | 3 ++ .../PlantMetabolism/PlantAdjustWater.cs | 2 ++ .../PlantMetabolism/PlantAdjustWeeds.cs | 3 ++ .../PlantMetabolism/PlantAffectGrowth.cs | 2 ++ .../PlantMetabolism/PlantCryoxadone.cs | 2 +- .../PlantMetabolism/PlantDiethylamine.cs | 2 +- .../PlantMetabolism/PlantPhalanximine.cs | 2 +- .../PlantMetabolism/RobustHarvest.cs | 2 +- .../Chemistry/Reagent/ReagentPrototype.cs | 10 ++++++ .../Locale/en-US/guidebook/chemistry/core.ftl | 2 ++ .../en-US/guidebook/chemistry/effects.ftl | 32 +++++++++++++++++- .../guidebook/chemistry/plant-attributes.ftl | 9 +++++ .../en-US/metabolism/metabolism-groups.ftl | 1 + .../Chemistry/metabolism_groups.yml | 5 +++ 22 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 Resources/Locale/en-US/guidebook/chemistry/plant-attributes.ftl diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml index f46e319abeb..73a17e9bcc9 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml +++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml @@ -47,6 +47,17 @@ + + + + + + + + diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs index 537494933bc..87931bf8455 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs @@ -157,6 +157,39 @@ private void GenerateControl(ReagentPrototype reagent) } #endregion + #region PlantMetabolisms + if (_chemistryGuideData.ReagentGuideRegistry.TryGetValue(reagent.ID, out var guideEntryRegistryPlant) && + guideEntryRegistryPlant.PlantMetabolisms != null && + guideEntryRegistryPlant.PlantMetabolisms.Count > 0) + { + PlantMetabolismsDescriptionContainer.Children.Clear(); + var metabolismLabel = new RichTextLabel(); + metabolismLabel.SetMarkup(Loc.GetString("guidebook-reagent-plant-metabolisms-rate")); + var descriptionLabel = new RichTextLabel + { + Margin = new Thickness(25, 0, 10, 0) + }; + var descMsg = new FormattedMessage(); + var descriptionsCount = guideEntryRegistryPlant.PlantMetabolisms.Count; + var i = 0; + foreach (var effectString in guideEntryRegistryPlant.PlantMetabolisms) + { + descMsg.AddMarkup(effectString); + i++; + if (i < descriptionsCount) + descMsg.PushNewline(); + } + descriptionLabel.SetMessage(descMsg); + + PlantMetabolismsDescriptionContainer.AddChild(metabolismLabel); + PlantMetabolismsDescriptionContainer.AddChild(descriptionLabel); + } + else + { + PlantMetabolismsContainer.Visible = false; + } + #endregion + GenerateSources(reagent); FormattedMessage description = new(); diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustAttribute.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustAttribute.cs index f43b4828f93..80c5ca5cf2b 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustAttribute.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustAttribute.cs @@ -15,6 +15,18 @@ public abstract partial class PlantAdjustAttribute : ReagentEffect [DataField] public float Prob { get; protected set; } = 1; // = (80); + /// + /// Localisation key for the name of the adjusted attribute. Used for guidebook descriptions. + /// + [DataField] + public abstract string GuidebookAttributeName { get; set; } + + /// + /// Whether the attribute in question is a good thing. Used for guidebook descriptions to determine the color of the number. + /// + [DataField] + public virtual bool GuidebookIsAttributePositive { get; protected set; } = true; + /// /// Checks if the plant holder can metabolize the reagent or not. Checks if it has an alive plant by default. /// @@ -40,6 +52,18 @@ public bool CanMetabolize(EntityUid plantHolder, [NotNullWhen(true)] out PlantHo return !(Prob <= 0f) && IoCManager.Resolve().Prob(Prob); } - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability)); + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + string color; + if (GuidebookIsAttributePositive ^ Amount < 0.0) + { + color = "green"; + } + else + { + color = "red"; + } + return Loc.GetString("reagent-effect-guidebook-plant-attribute", ("attribute", Loc.GetString(GuidebookAttributeName)), ("amount", Amount.ToString("0.00")), ("colorName", color), ("chance", Probability)); + } } } diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustHealth.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustHealth.cs index f03464469eb..af74c17de77 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustHealth.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustHealth.cs @@ -5,6 +5,9 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism { public sealed partial class PlantAdjustHealth : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-health"; + + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustMutationLevel.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustMutationLevel.cs index 8c8d04765a2..cf0983bc510 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustMutationLevel.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustMutationLevel.cs @@ -4,6 +4,9 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism { public sealed partial class PlantAdjustMutationLevel : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level"; + + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustMutationMod.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustMutationMod.cs index af4a00a044e..b43e8853886 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustMutationMod.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustMutationMod.cs @@ -6,6 +6,8 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism [UsedImplicitly] public sealed partial class PlantAdjustMutationMod : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod"; + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustNutrition.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustNutrition.cs index 900121412e8..68bb59870de 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustNutrition.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustNutrition.cs @@ -7,6 +7,8 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism [UsedImplicitly] public sealed partial class PlantAdjustNutrition : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition"; + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustPests.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustPests.cs index 27729b4b2ca..9e9787d030d 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustPests.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustPests.cs @@ -6,6 +6,9 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism [UsedImplicitly] public sealed partial class PlantAdjustPests : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-pests"; + public override bool GuidebookIsAttributePositive { get; protected set; } = false; + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustToxins.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustToxins.cs index 35130238ad5..2279f749105 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustToxins.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustToxins.cs @@ -6,6 +6,9 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism [UsedImplicitly] public sealed partial class PlantAdjustToxins : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-toxins"; + public override bool GuidebookIsAttributePositive { get; protected set; } = false; + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustWater.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustWater.cs index 71b4670dc65..a1c184d3b7a 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustWater.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustWater.cs @@ -7,6 +7,8 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism [UsedImplicitly] public sealed partial class PlantAdjustWater : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-water"; + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustWeeds.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustWeeds.cs index 6184b95adb8..82958e97a1c 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustWeeds.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustWeeds.cs @@ -6,6 +6,9 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism [UsedImplicitly] public sealed partial class PlantAdjustWeeds : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-weeds"; + public override bool GuidebookIsAttributePositive { get; protected set; } = false; + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAffectGrowth.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAffectGrowth.cs index 54ec628fdc9..e92b1954c07 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAffectGrowth.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAffectGrowth.cs @@ -7,6 +7,8 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism [UsedImplicitly] public sealed partial class PlantAffectGrowth : PlantAdjustAttribute { + public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth"; + public override void Effect(ReagentEffectArgs args) { if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager)) diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantCryoxadone.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantCryoxadone.cs index 83a5f56e592..55a7e5c97bb 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantCryoxadone.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantCryoxadone.cs @@ -28,6 +28,6 @@ public override void Effect(ReagentEffectArgs args) plantHolderComp.ForceUpdate = true; } - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability)); + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-cryoxadone", ("chance", Probability)); } } diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantDiethylamine.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantDiethylamine.cs index 23cb436d2f1..c98f0be78cb 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantDiethylamine.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantDiethylamine.cs @@ -36,6 +36,6 @@ public override void Effect(ReagentEffectArgs args) } } - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability)); + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-diethylamine", ("chance", Probability)); } } diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantPhalanximine.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantPhalanximine.cs index c0640d7fc04..7aaca63b7dc 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantPhalanximine.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantPhalanximine.cs @@ -19,6 +19,6 @@ public override void Effect(ReagentEffectArgs args) plantHolderComp.Seed.Viable = true; } - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability)); + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-phalanximine", ("chance", Probability)); } } diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs index 990a5a5003f..4a01cdf51f5 100644 --- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs +++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs @@ -49,6 +49,6 @@ public override void Effect(ReagentEffectArgs args) } } - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability)); + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-robust-harvest", ("seedlesstreshold", PotencySeedlessThreshold), ("limit", PotencyLimit), ("increase", PotencyIncrease), ("chance", Probability)); } } diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs index 6095676b9e0..7e96332ebe9 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs @@ -195,12 +195,22 @@ public struct ReagentGuideEntry public Dictionary, ReagentEffectsGuideEntry>? GuideEntries; + public List? PlantMetabolisms = null; + public ReagentGuideEntry(ReagentPrototype proto, IPrototypeManager prototype, IEntitySystemManager entSys) { ReagentPrototype = proto.ID; GuideEntries = proto.Metabolisms? .Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys))) .ToDictionary(x => x.Key, x => x.Item2); + if (proto.PlantMetabolisms.Count > 0) + { + PlantMetabolisms = new List (proto.PlantMetabolisms + .Select(x => x.GuidebookEffectDescription(prototype, entSys)) + .Where(x => x is not null) + .Select(x => x!) + .ToArray()); + } } } diff --git a/Resources/Locale/en-US/guidebook/chemistry/core.ftl b/Resources/Locale/en-US/guidebook/chemistry/core.ftl index 5179915e050..2ffb0b2be23 100644 --- a/Resources/Locale/en-US/guidebook/chemistry/core.ftl +++ b/Resources/Locale/en-US/guidebook/chemistry/core.ftl @@ -15,6 +15,8 @@ guidebook-reagent-sources-ent-wrapper = [bold]{$name}[/bold] \[1\] guidebook-reagent-sources-gas-wrapper = [bold]{$name} (gas)[/bold] \[1\] guidebook-reagent-effects-header = Effects guidebook-reagent-effects-metabolism-group-rate = [bold]{$group}[/bold] [color=gray]({$rate} units per second)[/color] +guidebook-reagent-plant-metabolisms-header = Plant Metabolism +guidebook-reagent-plant-metabolisms-rate = [bold]Plant Metabolism[/bold] [color=gray](1 unit every 3 seconds as base)[/color] guidebook-reagent-physical-description = [italic]Seems to be {$description}.[/italic] guidebook-reagent-recipes-mix-info = {$minTemp -> [0] {$hasMax -> diff --git a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl index 5b449cd520a..5579c95e9df 100644 --- a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl +++ b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl @@ -339,7 +339,7 @@ reagent-effect-guidebook-innoculate-zombie-infection = *[other] cure } an ongoing zombie infection, and provides immunity to future infections -reagent-effect-guidebook-reduce-rotting = +reagent-effect-guidebook-reduce-rotting = { $chance -> [1] Regenerates *[other] regenerate @@ -350,3 +350,33 @@ reagent-effect-guidebook-missing = [1] Causes *[other] cause } an unknown effect as nobody has written this effect yet + +reagent-effect-guidebook-plant-attribute = + { $chance -> + [1] Adjusts + *[other] adjust + } {$attribute} by [color={$colorName}]{$amount}[/color] + +reagent-effect-guidebook-plant-cryoxadone = + { $chance -> + [1] Ages back + *[other] age back + } the plant, depending on the plant's age and time to grow + +reagent-effect-guidebook-plant-phalanximine = + { $chance -> + [1] Makes + *[other] make + } a plant not viable due to mutation viable again + +reagent-effect-guidebook-plant-diethylamine = + { $chance -> + [1] Increases + *[other] increase + } the plant's lifespan and/or base health with 10% chance for each. + +reagent-effect-guidebook-plant-robust-harvest = + { $chance -> + [1] Increases + *[other] increase + } the plant's potency by {$increase} up to a maximum of {$limit}. Causes the plant to lose its seeds once the potency reaches {$seedlesstreshold}. Trying to add potency over {$limit} may cause decrease in yield at a 10% chance. diff --git a/Resources/Locale/en-US/guidebook/chemistry/plant-attributes.ftl b/Resources/Locale/en-US/guidebook/chemistry/plant-attributes.ftl new file mode 100644 index 00000000000..25752210089 --- /dev/null +++ b/Resources/Locale/en-US/guidebook/chemistry/plant-attributes.ftl @@ -0,0 +1,9 @@ +plant-attribute-growth = age +plant-attribute-water = water level +plant-attribute-weeds = weeds level +plant-attribute-toxins = toxins level +plant-attribute-nutrition = nutrition level +plant-attribute-mutation-level = mutation level +plant-attribute-pests = pests level +plant-attribute-mutation-mod = mutation modifier +plant-attribute-health = health diff --git a/Resources/Locale/en-US/metabolism/metabolism-groups.ftl b/Resources/Locale/en-US/metabolism/metabolism-groups.ftl index 404d0fc7868..b9bd477fbb6 100644 --- a/Resources/Locale/en-US/metabolism/metabolism-groups.ftl +++ b/Resources/Locale/en-US/metabolism/metabolism-groups.ftl @@ -5,3 +5,4 @@ metabolism-group-alcohol = Alcohol metabolism-group-food = Food metabolism-group-drink = Drink metabolism-group-gas = Gas +metabolism-group-plant-metabolisms = Plant Metabolism diff --git a/Resources/Prototypes/Chemistry/metabolism_groups.yml b/Resources/Prototypes/Chemistry/metabolism_groups.yml index b2035671af0..1cc1f19311d 100644 --- a/Resources/Prototypes/Chemistry/metabolism_groups.yml +++ b/Resources/Prototypes/Chemistry/metabolism_groups.yml @@ -27,3 +27,8 @@ - type: metabolismGroup id: Gas name: metabolism-group-gas + +# Dummy for the guide +- type: metabolismGroup + id: PlantMetabolisms + name: metabolism-group-plant-metabolisms From 5145009c35935f853ded6445e977619853de4c2b Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 19 May 2024 23:02:50 +0000 Subject: [PATCH 027/235] 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 f0a01a2354b..a7de6f03697 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Blackern5000 - changes: - - message: Salvagers can now pull in overgrown floral anomalies. - type: Add - id: 6107 - time: '2024-03-07T19:37:57.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/24650 - author: Nimfar11 changes: - message: Slimes can now drink FourteenLoko without harming their bodies. @@ -3868,3 +3861,10 @@ id: 6606 time: '2024-05-19T22:35:55.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28084 +- author: PolterTzi + changes: + - message: The guidebook now lists the effects reagents have on plants. + type: Add + id: 6607 + time: '2024-05-19T23:01:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28038 From 91a4537e7233122ae0b84814b465f20c597cc36d Mon Sep 17 00:00:00 2001 From: TsjipTsjip <19798667+tsjiptsjip@users.noreply.github.com> Date: Mon, 20 May 2024 01:04:16 +0200 Subject: [PATCH 028/235] Admin access configurator (#28107) * Port spritework and initial prototypes by @Arimah Co-authored-by: Alice 'Arimah' Heurlin <30327355+arimah@users.noreply.github.com> * Make Admin PDA's spawn with a universal ID card * Add universal access configurator to aghost satchel of holding * Add Admin suffixes to adminonly items: AdminPDA, UniversalIDCard, AccessConfiguratorUniversal * Admin jobicon --------- Co-authored-by: Alice 'Arimah' Heurlin <30327355+arimah@users.noreply.github.com> --- .../Fills/Backpacks/StarterGear/satchel.yml | 3 +- .../Entities/Objects/Devices/pda.yml | 3 +- .../Objects/Misc/identification_cards.yml | 28 ++++++++ .../Objects/Tools/access_configurator.yml | 59 +++++++++++++++++ Resources/Prototypes/StatusEffects/job.yml | 7 ++ .../Interface/Misc/job_icons.rsi/Admin.png | Bin 0 -> 159 bytes .../Interface/Misc/job_icons.rsi/meta.json | 5 +- .../Objects/Misc/id_cards.rsi/admin.png | Bin 0 -> 1328 bytes .../Misc/id_cards.rsi/green-inhand-left.png | Bin 0 -> 1239 bytes .../Misc/id_cards.rsi/green-inhand-right.png | Bin 0 -> 1260 bytes .../Objects/Misc/id_cards.rsi/idadmin.png | Bin 0 -> 1039 bytes .../Objects/Misc/id_cards.rsi/meta.json | 16 ++++- .../equipped-BELT.png | Bin 0 -> 1038 bytes .../icon.png | Bin 0 -> 4288 bytes .../inhand-left.png | Bin 0 -> 1327 bytes .../inhand-right.png | Bin 0 -> 1362 bytes .../meta.json | 62 ++++++++++++++++++ 17 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 Resources/Textures/Interface/Misc/job_icons.rsi/Admin.png create mode 100644 Resources/Textures/Objects/Misc/id_cards.rsi/admin.png create mode 100644 Resources/Textures/Objects/Misc/id_cards.rsi/green-inhand-left.png create mode 100644 Resources/Textures/Objects/Misc/id_cards.rsi/green-inhand-right.png create mode 100644 Resources/Textures/Objects/Misc/id_cards.rsi/idadmin.png create mode 100644 Resources/Textures/Objects/Tools/universal_access_configurator.rsi/equipped-BELT.png create mode 100644 Resources/Textures/Objects/Tools/universal_access_configurator.rsi/icon.png create mode 100644 Resources/Textures/Objects/Tools/universal_access_configurator.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Tools/universal_access_configurator.rsi/inhand-right.png create mode 100644 Resources/Textures/Objects/Tools/universal_access_configurator.rsi/meta.json diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml index 305d8d5e86b..665fcc182e6 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml @@ -214,7 +214,7 @@ - id: BoxSurvival - id: Bible - id: RubberStampChaplain - + - type: entity noSpawn: true parent: ClothingBackpackSatchel @@ -296,4 +296,5 @@ contents: - id: GasAnalyzer - id: trayScanner + - id: AccessConfiguratorUniversal - type: Unremoveable diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 52fe2a790a6..897b95b6848 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -719,10 +719,11 @@ parent: CentcomPDA id: AdminPDA name: Admin PDA + suffix: Admin description: If you are not an admin please return this PDA to the nearest admin. components: - type: Pda - id: PassengerIDCard + id: UniversalIDCard - type: HealthAnalyzer scanDelay: 0 - type: CartridgeLoader diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index 85ed0b1523b..6b70ecc805e 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -856,3 +856,31 @@ - state: idseniorofficer - type: PresetIdCard # DeltaV - Change senior job titles customJob: Senior Officer + +- type: entity + parent: IDCardStandard + id: UniversalIDCard + name: universal ID card + suffix: Admin + description: An ID card that gives you access beyond your wildest dreams. + components: + - type: Sprite + sprite: Objects/Misc/id_cards.rsi + layers: + - state: admin + - state: idadmin + - type: Clothing + sprite: Objects/Misc/id_cards.rsi + - type: Item + heldPrefix: green + - type: IdCard + jobTitle: Universal + jobIcon: JobIconAdmin + - type: Access + groups: + - AllAccess + tags: + - CentralCommand + - NuclearOperative + - SyndicateAgent + - DV-SpareSafe diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index b2c4d8e5282..f6cbb3ea6bb 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -85,3 +85,62 @@ - type: ContainerContainer containers: AccessOverrider-privilegedId: !type:ContainerSlot + +- type: entity + parent: AccessConfigurator + id: AccessConfiguratorUniversal + name: universal access configurator + suffix: Admin + description: A modified access configurator used only by the mythical Administrator. + components: + - type: Sprite + sprite: Objects/Tools/universal_access_configurator.rsi + - type: Clothing + sprite: Objects/Tools/universal_access_configurator.rsi + - type: AccessOverrider + accessLevels: + - Armory + - Atmospherics + - Bar + - Brig + - Detective + - Captain + - Cargo + - Chapel + - Chemistry + - ChiefEngineer + - ChiefMedicalOfficer + - Command + - Engineering + - External + - HeadOfPersonnel + - HeadOfSecurity + - Hydroponics + - Janitor + - Kitchen + - Lawyer + - Maintenance + - Medical + - Quartermaster + - Research + - ResearchDirector + - Salvage + - Security + - Service + - Theatre + - CentralCommand + - NuclearOperative + - SyndicateAgent + - DV-SpareSafe + privilegedIdSlot: + name: id-card-console-privileged-id + ejectSound: /Audio/Machines/id_swipe.ogg + insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg + ejectOnBreak: true + swap: false + whitelist: + components: + - IdCard + denialSound: + path: /Audio/Machines/custom_deny.ogg + doAfter: 0.5 diff --git a/Resources/Prototypes/StatusEffects/job.yml b/Resources/Prototypes/StatusEffects/job.yml index 30c677dd2c9..9e9a2986059 100644 --- a/Resources/Prototypes/StatusEffects/job.yml +++ b/Resources/Prototypes/StatusEffects/job.yml @@ -375,3 +375,10 @@ icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: Visitor + +- type: statusIcon + parent: JobIcon + id: JobIconAdmin + icon: + sprite: /Textures/Interface/Misc/job_icons.rsi + state: Admin diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/Admin.png b/Resources/Textures/Interface/Misc/job_icons.rsi/Admin.png new file mode 100644 index 0000000000000000000000000000000000000000..38a76df8d8e49ee190d4be0cd3a9b9cf25fda3af GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqoCO|{#S9F5he4R}c>anMpkSb< zi(`mHcybB@Q<(G2{|ZvFf9(FZOk`C#sib`3L+)i&kxD&#TY>-f+#M~RPOJyH9Fua~ zHt{5|8Z_i_dC0gj?&4Hn^|>L_uHzQKsH~XJ#L%Q5@$iM(3@4!B44$rjF6*2UngA?L BD?9)I literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json index 745cc43b844..fff9f78288c 100644 --- a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord) | Mindshield icon taken from https://github.com/tgstation/tgstation/blob/master/icons/mob/huds/hud.dmi", + "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord) | Mindshield icon taken from https://github.com/tgstation/tgstation/blob/master/icons/mob/huds/hud.dmi | Admin recolored from MedicalIntern by TsjipTsjip", "size": { "x": 8, @@ -185,6 +185,9 @@ }, { "name": "InitialInfected" + }, + { + "name": "Admin" } ] } diff --git a/Resources/Textures/Objects/Misc/id_cards.rsi/admin.png b/Resources/Textures/Objects/Misc/id_cards.rsi/admin.png new file mode 100644 index 0000000000000000000000000000000000000000..99ca993b0b85b09d9969d9855d1275ca6dafd4ad GIT binary patch literal 1328 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%u1Od5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y2=9ZF3nBND}m`vLFhHYsTY(KatnYqyQCInmZhe+73JqDfW2&$iPJ5XZaB?@ z>P^Az76Y7m^?{Dj2SqGWM8kxDsRzV_CtDx~p72xifT_I*n5<7_%>Bo}z_`TI#WAGf zR??sU|LqSuFsZdMvMuJ5C_S)(Ibs8sR%e4omY6_~!lUzh&5u|zr0fjYX?``Q{@Tv3 z_Gbho^bUs!F*!f5&u?Jty78P>+~CT>)d$%st}adNV`ybi-l*7+XgRx2B&^V>??=sN z&6k-BY<2f4j~-a3#FkcnyM)uH_~*-mKQC-yVw3;%-Dt`DLWa%hvt$@9Gqs-MSCR4-aw4pSit8nJU3M3q`T8E$M?BhV0e?_}Mq zgSR~!Rk*jmt3D9$@A8yQ0uF2n?h}|CrU?r(S;#i5eW5D;XV)j59C6L9ipl}PS63%5 oTAZ-O)9h74AbXDnI|GXZ!=;k#%sKo|vq5E;r>mdKI;Vst0CRB2b^rhX literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/id_cards.rsi/green-inhand-left.png b/Resources/Textures/Objects/Misc/id_cards.rsi/green-inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..958c3ba056d251b31db9408266ca99eebfd99f67 GIT binary patch literal 1239 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`Vd-k-W@=$z;Ob&(Xz1$ZWMJ&%=;~r=YUyZU zY+>LG)9aF-T$-DjR|3gTk1g7 zhxJd6ebEh@yhtg?Jd(Hn>v0kB*A4NIP_AmFB(q z=Ds^h>y_f!FXU>?U-Pv@P~h3WaJNgHX9VKrJh22Ds&Lc-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`;o@pwY-(&~;Ob&(Xz1$ZXl&u;=H%>Zv4~Pj*wm=R%;iu*SQ+p9GS<5!{+A=UOCV09yhE&{o zb9-a7n~&QIqB8T7iO&=cm=DOT2SG5|!N*)2La&2+nV4#D{QI&qr5xMJ}r=^}UeV z{rJYNch#qLv*$nBq!A^#=grz%?^BJlb5z!+*Il1~_v>=`RNFZgwa?=j=K5K^xPE*& z=j)08I%ZA_-|74!XO(*GvP>=U`^+T*vO4G4*DtM={r>aYmC38`vXtI>{_{b-a>`Dh zW}W8WANW69eqMCtsU@0Q(6|q@7^K|9q#Tr2R6Fc=A#}mwR(Z>VJ1^z0DX)&)^PWTX zx~yrr{hZ3p{iolq`8fAzwv6no6W$D#-}fFo{%>`r-mx-0-O3fV*2gcFGao31n*Hy3 bSOX(N&1&5Zd$xRi4HEQp^>bP0l+XkK=S8pB literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/id_cards.rsi/idadmin.png b/Resources/Textures/Objects/Misc/id_cards.rsi/idadmin.png new file mode 100644 index 0000000000000000000000000000000000000000..3705c6bf4e9b445def9e3edb31cff9f8376821a6 GIT binary patch literal 1039 zcmaJ=PfXKL9Imp!f?$jX14hlWL`B2;XKZC_sAF39@?@e1KnKofl)M&!9RjmRsD8WU;lIWjr-_gL(C3-HZaEhIVWn-f5z}fm# zL917^xK1aof>BQ-0w%;N@XRI46+MaG*A>a!-)3pB55X0QK1iylOoKFXAc!)M`X|~WV&cnle|0(PiR&B`Up^K`HMjE#;;D@rsv;$R)oB~2i%_>fp z5k_tq*&sbDfWbM#(oxO5ypLBDF=x40wKSNMC7LKOhM|ii(Rex<8;)}sIm+{yY?6GSaFhv~ZKFA99mzxxN)ln?#nOW2`_u>mU>CA6Yco=Q7e(uff&Z z=MrnnWl1uu-`Kwz-P|JX@uw}@l10n-&?0Vkh^-$#_%u$g#ClFn7QD};-MziU`>~@J zZy)J=a=fp)wb_w9yOr9Yg@J1)Zav%9cdI}6>!~k^dmEr5xIT3A#d7TK%FK@W@j@q) zkT~UcgnD@`aw%I4XBhsWE+p-uAACTqjuR-1OMm{Ho|dUILwwuUWvS@LSrUX+0c zPl`7$9t06Q2r8aDD0tdU5J42@&4UcolNUu_y0#Os;z#oS@%#P0?|t9*u2mN1ClbdJ z9LG(ROL~>fbMb%ZARF&H8{2F;N(&8IBP+CR2AIp)WEq39Yp&udHto*F7M|g_G3eAA zv|*fAE#mTKjNv2KXKap}nT>qYTEi49<5kDg;OD2$AaHCAF3N^z_<7uPO5Fh0x(juy zyJlr=IC}=nL@E<-F*QNtu6v;xX|SiOvUPkcK+uEGH4W|t)i5d`PXY{NK9#gYgn**( zC@m`rIt?TdNrK2eC5h6itfczecDv2DQ#=V)1(eNZ zV+~14GDI@$c+`xNUN|{W&~a!5j!zxpfmqR8CM~K#7U}L4Tz@F*h5a4$&d4;=OqPDje23J{GDtB(1<=gVM{?sQm@aoN5_&H$rV^I zLR{G23OzVt-4ic|ZOb;p#>XD(_JFnZ&$){?*pt{S>$!ULcIVB*iRWOP%av}KN3Q<3 zSDHWmcsvBB_wn{k_O|}8D|ffI;kN@PzF&B^|I6hUhciz;o!W^X@P8Zc ZCPeP#*YWqSUgxJ`R=KdCKRCOz`3nx|HO>G4 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Tools/universal_access_configurator.rsi/icon.png b/Resources/Textures/Objects/Tools/universal_access_configurator.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4b0331deab754b6f4258d1a00fe2ad00a5b36ccf GIT binary patch literal 4288 zcmbtYd00~G_TET^X{9-ol?rKt<$$wCQ*gk{%qcA`Z=xb47Ggk&Mu2(B%96^|PJ`HR zG#m6ZXlXg66_sXYMuVx>i4qkQ2N2xeP1F6|``mti+{?rB>~C+@dcXCp_g!o4ha*7& zK65n|YXAT+*LNLmBXs2e01Qx{1+DhUS%uJL5#2kS9zxkpPl~1z0gqTp3=!o^j^07s zNQ{n6-u;Z|3IK2uQfN3m+<$`$fkL*5R@qoF$cd0Q0JvfqiP40eL^>*lxPz47W+Hx2 zZGs}jx|xJI_*?rYdJ*GD>r$x1kd%N>Lds5pbF2v#gK}lKKmcSSJsQOz?@FM#Fx*VO z;<`X<)nl{?>Z=KTr<=*Qpu+uwP+k-&5#?ZIXGyTOu|YXHTG`k;I6B%`plq#eY|+-x z*U{3(-o?Su#nuV+{bK^{MvdL>vJtoT`(DtUn@Jp!t3($s zDlwW)p@vc@yMAObD2_s>(BdeGD6bGllvx-lA(oOvTlLl6-`~YIfkux`AP{|VZYB_d z6^Rt<;%RGbjq~z$u=e)yva#`A>+H4G`M0&sYwaAJZGUs{^8De8qY(Cxi3#){zOn!G zb^N8TN($seXlEReO4>_|T}!2qQD2YjLi%+qcE7~??i>5-SnPl4i-v+htBCzSME`gM zsYkW^DO>2~r|^jhklLw`tOw2zRzjs_?~C&YWqcU8fZt^prvIdgA=ukP)`?j5_)_+U zOF_E|SEt&~-tVio$@8|_(&7{IJwq>QJzT2x#^cpBEqyjlZMlBeK}6^Byqcd(;DCTm-B zN3p*<`vvj9?dy()YI?x$YMdZITLbWfxtjrIe$NZ1Qtwnd-=D0LJ60X;d?wD#$$0^~ zGMU0T7OktTMm;@<Pm&!s`jHRgesW%NT;I9A<@4vy+JlG81Kf-VjR?Z4 z+o^t+Iz>H&#%h_>o938C@msAtQnfTel6%%tPHe#wISjYSC%0bh>2WJTmiC{XiEWr3EyQdt+YI1AaBPG(tpmO>XKRcV z@&@lvwa>u8tzgleJ4LcaA^dOcy>F4cJuGt}{JgS-JV-f`=kicf`Ma_iP2aa$JZo_y z;=bGD@yu<7?7;@e5PXn?Ay^^ZbFoG$>ZCBoLF4?3+!Cf9X}}%t&z_vy*ov0-NYjl* zXlYY)x=(qp{6mx473@?MVM5ayZSfIgoIP_Xqs4iL@^z18t&rF^Q2o}jBM#%Pc($7W z%E(QIm)cJvE(~uIvvpV)ad{&m+_h9tgl%8Od;0OhQ>2bJpIau6QU7c{^96XlB_>$z z=Qmu)n2SdQMI0Y3@)4XffY;)KnENy+;h%AXGt29GoB?lKiPff zWs~qlf)uvlSZe1=;mSYYC?u*|IXbfj9Q|XThvun4A6BDCp1eTOQmVF@K_ul!8xm=0sIF_A)d9-GU-Vz?X3tlW(0Oa19ITt z)45Clax_PB9v1}nz{B|n{@i)5nvI5(@=em&%*It#i|&BTdkWs^k|~R*RvxDJ4HApB z>3zqmwvl}pfiX-yZ$7?_HCC1?m&t*mbYQ$tYEVDla=>rLCs)_MMy|X*aK$NL!s7(9 zS)}8m$|c% zXIHoHi2!BsYtHY%1RolK$>@QSBj+VVoaU!9v$uCS%FoT3B$*xJEkmNFNGL_izPjv! zvdXL2$O(gYtCf#V8dvCi;W)~rA@@GVBqaJTR2Ra}oQE$v(QISgt&I0dbf_9{asQf? zyNgwqplJs_!X3jIH$vdek#b;>&=eV!HgZzy#Qdd5Ab!0W@UMmPqk7yQb~gE7+~bUt z5Ep-~mYmcASVmBmPiURE$>iJ+>e@fT-;)F7=?FM&nV-S3o0C$-)D${h9*gF28ZXvm zGiPifG^MMQz4Zx_O|$K!!9Gj~J2Yeu-}Pmk;^rHtZ|H;L-j~UXC#vIjc1YyF$)%j) zfRcptxmEV*6(86KON`+pojs?tYK~5}9G6Rz)${c+ijhADdf{!<^MUyi^J`C>QkI%c zA{GjHIa9;&ieB>W+T4Llm#0deI^}3-&w*tshx@hlB<6OB*FmNF$C-Hk^csG3YBm#a zp7Ew1D3}^9mS4m2l=GI|tDBAgQcse?&a&3XhRpB86s^#0zrVRVY{@KvE_O=02BcSY z(ffir{kc!-Enc>nR^e*F(ST2csDr#UcWr0zX?O*bxS;qhK0@h_2utUf;~XYBY7C=F zTPsGNF3hC2h#Xe%_R8FrvPz|iS<1|&iTWX|DxDlAAkn=L;&9(a0#TEn^Ysb;zzHqD zVYwdgea!w_OG8DfY*BaTfso|G^<{hnP(2SK7h>w1R?Y2RZU=R{#i(!iP;8!X%L~Li zv!88f21pM98SV1oql>c0?+CQ zx>-99aoX46j{346K0i5#tuSWRRdM2%=#Lei)1jSNxUDAeDFuUce+gPDq`i$eWgJabUQH@bbjE8U3gJ$kj5c0pdLa=)KOP;lz0tQN;wwlav&(g6PEw) zL*-kobj?)RdWux_!r7DxwI7M-8x&0rNQ66Fkx`Yh?vq;i$vS7=@`gS@4YI142^7~v zm#QHnD-T|q%XXEG$=41US%~pXmT|)2^tfFKR+1v|Xm0sosPKhdmzB~$yn3FZz9};m z`5bY$Iw`SE0NeT)S+XLwVM|}hlooR(9oezDbIAj3y;74+5tNuYyOu+*Cg#s8j1fvH zHpMjj>1=XvRq8%O9j{|O6TFO2uEV53VaTLTYccM5bDsAYsAHNxg>4u74PiEi?BI&H z+EyJ|hT4)^I)v{i+Batgwv$raqqn|_ziL!)oe8ILT7A`3&Ee|w;vQ%=xGOT#O0v03 z&M~O#{N(}tz(4$7ir5_k$j)MQ&6Hnoso4F~t)yFq28u#i#--jNXb$f0L9JTGcj zB-_dk4K3k=^v#`t>@jhzNdgi%Tqw$Uz9Xa`n^)y7cAGfMIG@q>@WU+pVnVM#bR3Fq zg*9Hr%omPI?Sj1aEV?}I@EO^~>C7kjC*`&;{zki!mK5)8YXu2eO#efu@jroF{Hq2%` z!XKpABcon?CYX1htcY~hav0?1GUMm7(oI=6yT6V$6cS)IhN*cifq&KL?Eg)pD!E%D z0Z@5N2;Vh>&3r2_(sY-#WNlb*&?%wq7<5dWlG$!|P7C#syURP6U7=EIseCh39m`|+ zHvB8t$n|BlpHn1G3=AOJ|5TYGicr-+oig6&)dJqf{i9D>EueY)JO1XNDHM4fjdK;t zD(?`lC%+jA{Ee*WcH+9OBV zCdP!tZWDnM>D5QxW{ppzQF6s!+TG*!&qiwcz}&aIot|j`&|{AW_m}3~RsE;n>m7hA I_KZ644^FkPO#lD@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Tools/universal_access_configurator.rsi/inhand-left.png b/Resources/Textures/Objects/Tools/universal_access_configurator.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe2029ac5214e3af56350eaf4185ff07a8432b1 GIT binary patch literal 1327 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`WME=uVP@iD;OgvRXz1!@VQOUTV(MyQY~*Tb z>}X;P)9aF-T$-DjR|3v4~Pj*wm=R%;iu*SQ+p9GS0+(7&=6~8(DLLt!|C+7K&K4#_$$>K)5^>?wi(ewb6Mucbe0b}-FVWFX zcmJHdyZN^H&n4pX|FbU%f8O1`?Z*!zPy5Li7F$$S_P-E(Q5y4U(o!``Yco|A>ped< zzxy+5_O|PiC*zRdxYeq| zeBlvB2lrTUgV`0#VMjN`-CXWs&TjIRZ4KAe&{}!#XK~LP-etaJ=g8`d;<$ePy+!{r srZ;?(%|q?YW^p~RMRG?%|D*T@Mh52{{S7NClJY?^p00i_>zopr0Opa*FaQ7m literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Tools/universal_access_configurator.rsi/inhand-right.png b/Resources/Textures/Objects/Tools/universal_access_configurator.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..17951b140769ae30e37a9251f00d176269daaa54 GIT binary patch literal 1362 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`Wb9~aXlCJP;OgvRXz1$ZWMN_EYU*li>0;>O z=45OR)9aF-T$-DjR|3v4~Pj*wm=R%;iu*SQ+p9GSzoW<*ucQRc+AtqF{I+w zn_CZ90vG+6w#v)%@s0WC!!8+jbC~X26L6RtoZpZLlTBML^BHaaxo?-nmGabe zPR{*(>;IS5Udy&$J~yD@+P7sp^R|iygbL)GdVRR!WkkvUDV!6mSkL*FA1T>)UzP6~ zuS+b$mb1S+`Tp&(KPQ;LV6|Pj%JPPiWc$~SmaW&^RqpiKF+So*Sk4h)k)b;IuU_sQ zn`^2Pj<_T0YK?JdLmu_@xK=7PZ2CIQzNOkey7 zzfu>^{6T>Q$<^4HUCcXdURL_f{<-G=^}md2XKi{vy%IQk^7k2;qhD{o4~{s;xBESR zfySc9pHTY?YN&h`~EJmwc*D5?8|DWY-YRR$E=n9{KWkCk4t$MeA;$aXW8tEeg+$MBzKuC b*u!nWFd==mchLLfO&|$RS3j3^P6 Date: Sun, 19 May 2024 23:05:22 +0000 Subject: [PATCH 029/235] Automatic changelog update --- Resources/Changelog/Admin.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index c06a6b31254..f4f19277f48 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -222,5 +222,18 @@ Entries: id: 28 time: '2024-05-12T15:07:54.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27883 +- author: Arimah and TsjipTsjip + changes: + - message: Universal access configurator which can modify all access levels with + the correct ID card. + type: Add + - message: Universal ID card which has all access levels. + type: Add + - message: Admin PDA's spawn with a universal ID card, admin ghosts get a universal + access configurator in their bluespace satchel. + type: Tweak + id: 29 + time: '2024-05-19T23:04:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28107 Name: Admin Order: 3 From 44b74a78e1a1f06e9d98d0cf6835e6c1f15f9733 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Mon, 20 May 2024 06:24:15 -0500 Subject: [PATCH 030/235] Remove extra gear prototype (#28062) Remove extra prototype --- Resources/Prototypes/Entities/Mobs/Player/humanoid.yml | 2 +- .../Prototypes/Roles/Jobs/Fun/misc_startinggear.yml | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml index 42d5eade20f..4b499f5af2c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml @@ -891,7 +891,7 @@ settings: short - type: GhostTakeoverAvailable - type: Loadout - prototypes: [ SyndicateOperativeGearDisasterVictim ] + prototypes: [ SyndicateOperativeGearCivilian ] - type: RandomMetadata nameSegments: - names_first diff --git a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml index 527dc2f4514..8513d484828 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml @@ -79,19 +79,9 @@ back: ClothingBackpackDuffelSyndicate shoes: ClothingShoesBootsCombat gloves: ClothingHandsGlovesColorBlack - -# Syndicate Operative Outfit - Disaster Victim -- type: startingGear - id: SyndicateOperativeGearDisasterVictim - equipment: - jumpsuit: ClothingUniformJumpsuitSyndieFormal - back: ClothingBackpackDuffelSyndicate - shoes: ClothingShoesBootsCombat - gloves: ClothingHandsGlovesColorBlack id: SyndiPDA ears: ClothingHeadsetAltSyndicate - #Syndicate Operative Outfit - Basic - type: startingGear id: SyndicateOperativeGearBasic From 77abecef2a6c37fdd6b340fe90e44b82c0750324 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 20 May 2024 09:32:25 -0400 Subject: [PATCH 031/235] Slightly emphasize popup when someone points at you (#28152) Slightly emphasize when someone points at you --- Content.Server/Pointing/EntitySystems/PointingSystem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index 7bbf6409cdd..960c8dc28aa 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -88,7 +88,10 @@ private void SendMessage(EntityUid source, IEnumerable viewers, ? viewerPointedAtMessage : viewerMessage; - RaiseNetworkEvent(new PopupEntityEvent(message, PopupType.Small, netSource), viewerEntity); + // Someone pointing at YOU is slightly more important + var popupType = viewerEntity == pointed ? PopupType.Medium : PopupType.Small; + + RaiseNetworkEvent(new PopupEntityEvent(message, popupType, netSource), viewerEntity); } _replay.RecordServerMessage(new PopupEntityEvent(viewerMessage, PopupType.Small, netSource)); From 640cf00dffb5006f0e85f98916c4d77a3e33ff6f Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 20 May 2024 13:33:31 +0000 Subject: [PATCH 032/235] 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 a7de6f03697..73771ef3cdf 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Nimfar11 - changes: - - message: Slimes can now drink FourteenLoko without harming their bodies. - type: Tweak - id: 6108 - time: '2024-03-07T20:50:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25889 - author: NakataRin changes: - message: Evacuation shuttle arrives 10 minutes on green alert now. @@ -3868,3 +3861,11 @@ id: 6607 time: '2024-05-19T23:01:44.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28038 +- author: Tayrtahn + changes: + - message: The popup for someone pointing at you is now slightly larger than normal + popups. + type: Tweak + id: 6608 + time: '2024-05-20T13:32:25.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28152 From d92b6a08cb309c9fe744516a4c1b124ea0505f43 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 20 May 2024 09:52:49 -0400 Subject: [PATCH 033/235] Prevent ghosts from spawning on terminating maps/grids (#28099) * Extra checks to prevent ghosts spawning on terminating maps/grids * Add test for grid deletion --- .../Tests/Minds/GhostTests.cs | 16 +++++++++++ .../GameTicking/GameTicker.Spawning.cs | 15 +++++++--- Content.Server/Ghost/GhostSystem.cs | 28 +++++++++++++++---- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Content.IntegrationTests/Tests/Minds/GhostTests.cs b/Content.IntegrationTests/Tests/Minds/GhostTests.cs index ad9d53a70db..7a156e71e41 100644 --- a/Content.IntegrationTests/Tests/Minds/GhostTests.cs +++ b/Content.IntegrationTests/Tests/Minds/GhostTests.cs @@ -156,4 +156,20 @@ public async Task TestGridGhostOnQueueDelete() await data.Pair.CleanReturnAsync(); } + [Test] + public async Task TestGhostGridNotTerminating() + { + var data = await SetupData(); + + Assert.DoesNotThrowAsync(async () => + { + // Delete the grid + await data.Server.WaitPost(() => data.SEntMan.DeleteEntity(data.MapData.Grid.Owner)); + }); + + await data.Pair.RunTicksSync(5); + + await data.Pair.CleanReturnAsync(); + } + } diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 57ab1acce68..4ddef7c2a98 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -377,11 +377,16 @@ public void SpawnObserver(ICommonSession player) public EntityCoordinates GetObserverSpawnPoint() { _possiblePositions.Clear(); - - foreach (var (point, transform) in EntityManager.EntityQuery(true)) + var spawnPointQuery = EntityManager.EntityQueryEnumerator(); + while (spawnPointQuery.MoveNext(out var uid, out var point, out var transform)) { - if (point.SpawnType != SpawnPointType.Observer) + if (point.SpawnType != SpawnPointType.Observer + || TerminatingOrDeleted(uid) + || transform.MapUid == null + || TerminatingOrDeleted(transform.MapUid.Value)) + { continue; + } _possiblePositions.Add(transform.Coordinates); } @@ -423,7 +428,9 @@ public EntityCoordinates GetObserverSpawnPoint() if (_mapManager.MapExists(DefaultMap)) { - return new EntityCoordinates(_mapManager.GetMapEntityId(DefaultMap), Vector2.Zero); + var mapUid = _mapManager.GetMapEntityId(DefaultMap); + if (!TerminatingOrDeleted(mapUid)) + return new EntityCoordinates(mapUid, Vector2.Zero); } // Just pick a point at this point I guess. diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index f4e6a4d607d..6c3d69fea7f 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -409,23 +409,41 @@ public bool DoGhostBooEvent(EntityUid target) return SpawnGhost(mind, spawnPosition, canReturn); } + private bool IsValidSpawnPosition(EntityCoordinates? spawnPosition) + { + if (spawnPosition?.IsValid(EntityManager) != true) + return false; + + var mapUid = spawnPosition?.GetMapUid(EntityManager); + var gridUid = spawnPosition?.EntityId; + // Test if the map is being deleted + if (mapUid == null || TerminatingOrDeleted(mapUid.Value)) + return false; + // Test if the grid is being deleted + if (gridUid != null && TerminatingOrDeleted(gridUid.Value)) + return false; + + return true; + } + public EntityUid? SpawnGhost(Entity mind, EntityCoordinates? spawnPosition = null, bool canReturn = false) { if (!Resolve(mind, ref mind.Comp)) return null; - // Test if the map is being deleted - var mapUid = spawnPosition?.GetMapUid(EntityManager); - if (mapUid == null || TerminatingOrDeleted(mapUid.Value)) + // Test if the map or grid is being deleted + if (!IsValidSpawnPosition(spawnPosition)) spawnPosition = null; + // If it's bad, look for a valid point to spawn spawnPosition ??= _ticker.GetObserverSpawnPoint(); - if (!spawnPosition.Value.IsValid(EntityManager)) + // Make sure the new point is valid too + if (!IsValidSpawnPosition(spawnPosition)) { Log.Warning($"No spawn valid ghost spawn position found for {mind.Comp.CharacterName}" - + " \"{ToPrettyString(mind)}\""); + + $" \"{ToPrettyString(mind)}\""); _minds.TransferTo(mind.Owner, null, createGhost: false, mind: mind.Comp); return null; } From 7ff91799cc4a9102cb2bb600a0590db7292bbbfb Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 20 May 2024 10:12:05 -0400 Subject: [PATCH 034/235] Fix non-ghosts and admins counting toward most followed (#28120) * Fixed non-ghosts and admins counting toward most followed * Redone to better leverage EntityQueryEnumerator * Remember to test your code before you commit it, kids * Review revisions * Update Content.Shared/Follower/FollowerSystem.cs Co-authored-by: ShadowCommander * Update Content.Shared/Follower/FollowerSystem.cs Co-authored-by: ShadowCommander * Update Content.Shared/Follower/FollowerSystem.cs * Clean up --------- Co-authored-by: ShadowCommander Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> --- Content.Server/Ghost/GhostSystem.cs | 2 +- Content.Shared/Follower/FollowerSystem.cs | 35 ++++++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index 6c3d69fea7f..1a411f13a05 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -306,7 +306,7 @@ private void OnGhostnadoRequest(GhostnadoRequestEvent msg, EntitySessionEventArg return; } - if (_followerSystem.GetMostFollowed() is not {} target) + if (_followerSystem.GetMostGhostFollowed() is not {} target) return; WarpTo(uid, target); diff --git a/Content.Shared/Follower/FollowerSystem.cs b/Content.Shared/Follower/FollowerSystem.cs index 6c02b130762..8027ee449c4 100644 --- a/Content.Shared/Follower/FollowerSystem.cs +++ b/Content.Shared/Follower/FollowerSystem.cs @@ -1,11 +1,11 @@ using System.Numerics; +using Content.Shared.Administration.Managers; using Content.Shared.Database; using Content.Shared.Follower.Components; using Content.Shared.Ghost; using Content.Shared.Hands; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Events; -using Content.Shared.Movement.Systems; using Content.Shared.Tag; using Content.Shared.Verbs; using Robust.Shared.Containers; @@ -14,6 +14,7 @@ using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; +using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Shared.Follower; @@ -26,6 +27,7 @@ public sealed class FollowerSystem : EntitySystem [Dependency] private readonly SharedJointSystem _jointSystem = default!; [Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!; [Dependency] private readonly INetManager _netMan = default!; + [Dependency] private readonly ISharedAdminManager _adminManager = default!; public override void Initialize() { @@ -249,20 +251,33 @@ public void StopAllFollowers(EntityUid uid, } /// - /// Get the most followed entity. + /// Gets the entity with the most non-admin ghosts following it. /// - public EntityUid? GetMostFollowed() + public EntityUid? GetMostGhostFollowed() { EntityUid? picked = null; - int most = 0; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp)) + var most = 0; + + // Keep a tally of how many ghosts are following each entity + var followedEnts = new Dictionary(); + + // Look for followers that are ghosts and are player controlled + var query = EntityQueryEnumerator(); + while (query.MoveNext(out _, out var follower, out _, out var actor)) { - var count = comp.Following.Count; - if (count > most) + // Exclude admins + if (_adminManager.IsAdmin(actor.PlayerSession)) + continue; + + var followed = follower.Following; + // Add new entry or increment existing + followedEnts.TryGetValue(followed, out var currentValue); + followedEnts[followed] = currentValue + 1; + + if (followedEnts[followed] > most) { - picked = uid; - most = count; + picked = followed; + most = followedEnts[followed]; } } From 026cda21231a01e2b5f8a818f2e0a17ce81b417e Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 20 May 2024 14:13:11 +0000 Subject: [PATCH 035/235] 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 73771ef3cdf..08d13027c8f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: NakataRin - changes: - - message: Evacuation shuttle arrives 10 minutes on green alert now. - type: Tweak - id: 6109 - time: '2024-03-07T21:01:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25906 - author: Admiral-Obvious-001 changes: - message: Zombies are now tougher to kill. @@ -3869,3 +3862,11 @@ id: 6608 time: '2024-05-20T13:32:25.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28152 +- author: Tayrtahn + changes: + - message: '"Warp to most followed" ghost option no longer includes admins and non-ghost + followers.' + type: Fix + id: 6609 + time: '2024-05-20T14:12:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28120 From 64deb96fb90350d5b1d9395acaa917aa39b75055 Mon Sep 17 00:00:00 2001 From: Killerqu00 <47712032+Killerqu00@users.noreply.github.com> Date: Mon, 20 May 2024 21:59:07 +0200 Subject: [PATCH 036/235] rotate forensic scanner stored sprite (#28162) rotate forensic scanner --- .../Prototypes/Entities/Objects/Devices/forensic_scanner.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml b/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml index 170751766be..a0ada5ebe34 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml @@ -9,6 +9,7 @@ state: forensicnew - type: Item size: Small + storedRotation: 90 - type: Clothing sprite: Objects/Devices/forensic_scanner.rsi quickEquip: false From 74308b008f0ea1b82c05c1bef6f18ae6b12eddb7 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 21 May 2024 17:40:35 +1200 Subject: [PATCH 037/235] Use non-generic `TryComp()` for metadata & transform (#28133) --- Content.Client/Gravity/GravitySystem.Shake.cs | 4 ++-- Content.Client/Maps/GridDraggingSystem.cs | 4 ++-- Content.Client/Salvage/SalvageSystem.cs | 2 +- Content.Client/Sprite/SpriteFadeSystem.cs | 2 +- Content.Client/Weapons/Misc/TetherGunSystem.cs | 2 +- .../Atmos/EntitySystems/AtmosphereSystem.Commands.cs | 2 +- Content.Server/Bible/BibleSystem.cs | 2 +- .../EntitySystems/DisassembleOnAltVerbSystem.cs | 2 +- .../Explosion/EntitySystems/TriggerSystem.cs | 2 +- Content.Server/Fax/FaxSystem.cs | 4 ++-- Content.Server/Gravity/GravityGeneratorSystem.cs | 4 ++-- Content.Server/Medical/MedicalScannerSystem.cs | 2 +- .../NPC/Pathfinding/PathfindingSystem.Distance.cs | 4 ++-- .../NPC/Pathfinding/PathfindingSystem.Grid.cs | 2 +- Content.Server/NPC/Pathfinding/PathfindingSystem.cs | 10 +++++----- Content.Server/NPC/Systems/NPCUtilitySystem.cs | 8 ++++---- Content.Server/PAI/PAISystem.cs | 2 +- Content.Server/Paper/PaperSystem.cs | 2 +- Content.Server/Physics/Controllers/MoverController.cs | 2 +- Content.Server/Polymorph/Systems/PolymorphSystem.cs | 2 +- Content.Server/Projectiles/ProjectileSystem.cs | 2 +- Content.Server/Salvage/FultonSystem.cs | 2 +- Content.Server/Salvage/SalvageSystem.Runner.cs | 2 +- Content.Server/Shuttles/Systems/ArrivalsSystem.cs | 4 ++-- .../Shuttles/Systems/EmergencyShuttleSystem.cs | 6 +++--- .../Shuttles/Systems/ShuttleConsoleSystem.cs | 2 +- .../Shuttles/Systems/ShuttleSystem.GridFill.cs | 4 ++-- Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs | 6 +++--- .../Spawners/EntitySystems/SpawnOnDespawnSystem.cs | 2 +- Content.Shared/Interaction/SharedInteractionSystem.cs | 4 ++-- Content.Shared/Movement/Systems/SharedJetpackSystem.cs | 2 +- .../Movement/Systems/SharedMoverController.Input.cs | 2 +- .../EntitySystems/PressurizedSolutionSystem.cs | 2 +- .../Nutrition/EntitySystems/SharedDrinkSystem.cs | 2 +- Content.Shared/Random/RulesSystem.cs | 10 +++++----- Content.Shared/Sound/SharedEmitSoundSystem.cs | 2 +- .../Storage/EntitySystems/SharedStorageSystem.cs | 2 +- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 10 +++++----- 38 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Content.Client/Gravity/GravitySystem.Shake.cs b/Content.Client/Gravity/GravitySystem.Shake.cs index c4356588d35..9b9918ca3e7 100644 --- a/Content.Client/Gravity/GravitySystem.Shake.cs +++ b/Content.Client/Gravity/GravitySystem.Shake.cs @@ -25,7 +25,7 @@ private void OnShakeInit(EntityUid uid, GravityShakeComponent component, Compone { var localPlayer = _playerManager.LocalEntity; - if (!TryComp(localPlayer, out var xform) || + if (!TryComp(localPlayer, out TransformComponent? xform) || xform.GridUid != uid && xform.MapUid != uid) { return; @@ -46,7 +46,7 @@ protected override void ShakeGrid(EntityUid uid, GravityComponent? gravity = nul var localPlayer = _playerManager.LocalEntity; - if (!TryComp(localPlayer, out var xform)) + if (!TryComp(localPlayer, out TransformComponent? xform)) return; if (xform.GridUid != uid || diff --git a/Content.Client/Maps/GridDraggingSystem.cs b/Content.Client/Maps/GridDraggingSystem.cs index 16357c89838..e82786847e3 100644 --- a/Content.Client/Maps/GridDraggingSystem.cs +++ b/Content.Client/Maps/GridDraggingSystem.cs @@ -61,7 +61,7 @@ private void StopDragging() { if (_dragging == null) return; - if (_lastMousePosition != null && TryComp(_dragging.Value, out var xform) && + if (_lastMousePosition != null && TryComp(_dragging.Value, out TransformComponent? xform) && TryComp(_dragging.Value, out var body) && xform.MapID == _lastMousePosition.Value.MapId) { @@ -104,7 +104,7 @@ public override void Update(float frameTime) StartDragging(gridUid, Transform(gridUid).InvWorldMatrix.Transform(mousePos.Position)); } - if (!TryComp(_dragging, out var xform)) + if (!TryComp(_dragging, out TransformComponent? xform)) { StopDragging(); return; diff --git a/Content.Client/Salvage/SalvageSystem.cs b/Content.Client/Salvage/SalvageSystem.cs index fb305c5fdc4..e1bce367cae 100644 --- a/Content.Client/Salvage/SalvageSystem.cs +++ b/Content.Client/Salvage/SalvageSystem.cs @@ -38,7 +38,7 @@ private void OnPlayAmbientMusic(ref PlayAmbientMusicEvent ev) var player = _playerManager.LocalEntity; - if (!TryComp(player, out var xform) || + if (!TryComp(player, out TransformComponent? xform) || !TryComp(xform.MapUid, out var expedition) || expedition.Stage < ExpeditionStage.MusicCountdown) { diff --git a/Content.Client/Sprite/SpriteFadeSystem.cs b/Content.Client/Sprite/SpriteFadeSystem.cs index d9584b60a65..676a6e583d5 100644 --- a/Content.Client/Sprite/SpriteFadeSystem.cs +++ b/Content.Client/Sprite/SpriteFadeSystem.cs @@ -45,7 +45,7 @@ public override void FrameUpdate(float frameTime) var spriteQuery = GetEntityQuery(); var change = ChangeRate * frameTime; - if (TryComp(player, out var playerXform) && + if (TryComp(player, out TransformComponent? playerXform) && _stateManager.CurrentState is GameplayState state && spriteQuery.TryGetComponent(player, out var playerSprite)) { diff --git a/Content.Client/Weapons/Misc/TetherGunSystem.cs b/Content.Client/Weapons/Misc/TetherGunSystem.cs index 634dbd24e79..398aeabb839 100644 --- a/Content.Client/Weapons/Misc/TetherGunSystem.cs +++ b/Content.Client/Weapons/Misc/TetherGunSystem.cs @@ -82,7 +82,7 @@ public override void Update(float frameTime) const float BufferDistance = 0.1f; - if (TryComp(gun.TetherEntity, out var tetherXform) && + if (TryComp(gun.TetherEntity, out TransformComponent? tetherXform) && tetherXform.Coordinates.TryDistance(EntityManager, TransformSystem, coords, out var distance) && distance < BufferDistance) { diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs index f711b235af6..5a41a7567b2 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs @@ -165,7 +165,7 @@ private CompletionResult FixGridAtmosCommandCompletions(IConsoleShell shell, str foreach (var grid in _mapManager.GetAllGrids(playerMap.Value).OrderBy(o => o.Owner)) { var uid = grid.Owner; - if (!TryComp(uid, out var gridXform)) + if (!TryComp(uid, out TransformComponent? gridXform)) continue; options.Add(new CompletionOption(uid.ToString(), $"{MetaData(uid).EntityName} - Map {gridXform.MapID}")); diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index 0c60e40dacb..2cb0ac1dd76 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -167,7 +167,7 @@ private void AddSummonVerb(EntityUid uid, SummonableComponent component, GetVerb { Act = () => { - if (!TryComp(args.User, out var userXform)) + if (!TryComp(args.User, out TransformComponent? userXform)) return; AttemptSummon((uid, component), args.User, userXform); diff --git a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs index 61b6f3d93d2..d694f84a9c7 100644 --- a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs +++ b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs @@ -56,7 +56,7 @@ public async void AttemptDisassemble(EntityUid uid, EntityUid user, EntityUid ta if (component.Deleted || Deleted(uid)) return; - if (!TryComp(uid, out var transformComp)) + if (!TryComp(uid, out TransformComponent? transformComp)) return; var entity = EntityManager.SpawnEntity(component.Prototype, transformComp.Coordinates); diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index 8e0a75ea24f..7c6b5df7f14 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -166,7 +166,7 @@ private void HandleDeleteTrigger(EntityUid uid, DeleteOnTriggerComponent compone private void HandleGibTrigger(EntityUid uid, GibOnTriggerComponent component, TriggerEvent args) { - if (!TryComp(uid, out var xform)) + if (!TryComp(uid, out TransformComponent? xform)) return; if (component.DeleteItems) { diff --git a/Content.Server/Fax/FaxSystem.cs b/Content.Server/Fax/FaxSystem.cs index e86dbca4a13..16d2d391f65 100644 --- a/Content.Server/Fax/FaxSystem.cs +++ b/Content.Server/Fax/FaxSystem.cs @@ -459,7 +459,7 @@ public void Copy(EntityUid uid, FaxMachineComponent? component, FaxCopyMessage a if (sendEntity == null) return; - if (!TryComp(sendEntity, out var metadata) || + if (!TryComp(sendEntity, out MetaDataComponent? metadata) || !TryComp(sendEntity, out var paper)) return; @@ -506,7 +506,7 @@ public void Send(EntityUid uid, FaxMachineComponent? component, FaxSendMessage a if (!component.KnownFaxes.TryGetValue(component.DestinationFaxAddress, out var faxName)) return; - if (!TryComp(sendEntity, out var metadata) || + if (!TryComp(sendEntity, out MetaDataComponent? metadata) || !TryComp(sendEntity, out var paper)) return; diff --git a/Content.Server/Gravity/GravityGeneratorSystem.cs b/Content.Server/Gravity/GravityGeneratorSystem.cs index 8e4da75fac8..0b53df63fd0 100644 --- a/Content.Server/Gravity/GravityGeneratorSystem.cs +++ b/Content.Server/Gravity/GravityGeneratorSystem.cs @@ -41,7 +41,7 @@ private void OnParentChanged(EntityUid uid, GravityGeneratorComponent component, private void OnComponentShutdown(EntityUid uid, GravityGeneratorComponent component, ComponentShutdown args) { if (component.GravityActive && - TryComp(uid, out var xform) && + TryComp(uid, out TransformComponent? xform) && TryComp(xform.ParentUid, out GravityComponent? gravity)) { component.GravityActive = false; @@ -114,7 +114,7 @@ public override void Update(float frameTime) UpdateUI(ent, chargeRate); if (active != gravGen.GravityActive && - TryComp(uid, out var xform) && + TryComp(uid, out TransformComponent? xform) && TryComp(xform.ParentUid, out var gravity)) { // Force it on in the faster path. diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 91184ddc162..b24690e204a 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -86,7 +86,7 @@ private void AddInsertOtherVerb(EntityUid uid, MedicalScannerComponent component return; var name = "Unknown"; - if (TryComp(args.Using.Value, out var metadata)) + if (TryComp(args.Using.Value, out MetaDataComponent? metadata)) name = metadata.EntityName; InteractionVerb verb = new() diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs index 95d5c9c4651..5daf38c4209 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs @@ -30,8 +30,8 @@ private Vector2 GetDiff(PathPoly start, PathPoly end) if (end.GraphUid != start.GraphUid) { - if (!TryComp(start.GraphUid, out var startXform) || - !TryComp(end.GraphUid, out var endXform)) + if (!TryComp(start.GraphUid, out TransformComponent? startXform) || + !TryComp(end.GraphUid, out TransformComponent? endXform)) { return Vector2.Zero; } diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs index 6462c10fe55..52f7db77ed6 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs @@ -261,7 +261,7 @@ private void OnCollisionLayerChange(ref CollisionLayerChangeEvent ev) private void OnBodyTypeChange(ref PhysicsBodyTypeChangedEvent ev) { - if (TryComp(ev.Entity, out var xform) && + if (TryComp(ev.Entity, out TransformComponent? xform) && xform.GridUid != null) { var aabb = _lookup.GetAABBNoContainer(ev.Entity, xform.Coordinates.Position, xform.LocalRotation); diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs index a59af88ff58..3672ad047b4 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs @@ -264,7 +264,7 @@ public async Task GetRandomPath( int limit = 40, PathFlags flags = PathFlags.None) { - if (!TryComp(entity, out var start)) + if (!TryComp(entity, out TransformComponent? start)) return new PathResultEvent(PathResult.NoPath, new List()); var layer = 0; @@ -294,7 +294,7 @@ public async Task GetRandomPath( CancellationToken cancelToken, PathFlags flags = PathFlags.None) { - if (!TryComp(entity, out var start)) + if (!TryComp(entity, out TransformComponent? start)) return null; var request = GetRequest(entity, start.Coordinates, end, range, cancelToken, flags); @@ -325,8 +325,8 @@ public async Task GetPath( CancellationToken cancelToken, PathFlags flags = PathFlags.None) { - if (!TryComp(entity, out var xform) || - !TryComp(target, out var targetXform)) + if (!TryComp(entity, out TransformComponent? xform) || + !TryComp(target, out TransformComponent? targetXform)) return new PathResultEvent(PathResult.NoPath, new List()); var request = GetRequest(entity, xform.Coordinates, targetXform.Coordinates, range, cancelToken, flags); @@ -400,7 +400,7 @@ public async void GetPathEvent( var gridUid = coordinates.GetGridUid(EntityManager); if (!TryComp(gridUid, out var comp) || - !TryComp(gridUid, out var xform)) + !TryComp(gridUid, out TransformComponent? xform)) { return null; } diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs index 4b0ccafa1d4..2e8c628b503 100644 --- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs +++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs @@ -260,8 +260,8 @@ private float GetScore(NPCBlackboard blackboard, EntityUid targetUid, UtilityCon { var radius = blackboard.GetValueOrDefault(NPCBlackboard.VisionRadius, EntityManager); - if (!TryComp(targetUid, out var targetXform) || - !TryComp(owner, out var xform)) + if (!TryComp(targetUid, out TransformComponent? targetXform) || + !TryComp(owner, out TransformComponent? xform)) { return 0f; } @@ -308,8 +308,8 @@ private float GetScore(NPCBlackboard blackboard, EntityUid targetUid, UtilityCon if (blackboard.TryGetValue("Target", out var currentTarget, EntityManager) && currentTarget == targetUid && - TryComp(owner, out var xform) && - TryComp(targetUid, out var targetXform) && + TryComp(owner, out TransformComponent? xform) && + TryComp(targetUid, out TransformComponent? targetXform) && xform.Coordinates.TryDistance(EntityManager, _transform, targetXform.Coordinates, out var distance) && distance <= radius + bufferRange) { diff --git a/Content.Server/PAI/PAISystem.cs b/Content.Server/PAI/PAISystem.cs index 091afb15576..0cdb0bc29a2 100644 --- a/Content.Server/PAI/PAISystem.cs +++ b/Content.Server/PAI/PAISystem.cs @@ -111,7 +111,7 @@ public void PAITurningOff(EntityUid uid) if (TryComp(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument); - if (TryComp(uid, out var metadata)) + if (TryComp(uid, out MetaDataComponent? metadata)) { var proto = metadata.EntityPrototype; if (proto != null) diff --git a/Content.Server/Paper/PaperSystem.cs b/Content.Server/Paper/PaperSystem.cs index d9202341d55..61addc1b517 100644 --- a/Content.Server/Paper/PaperSystem.cs +++ b/Content.Server/Paper/PaperSystem.cs @@ -148,7 +148,7 @@ private void OnInputTextMessage(EntityUid uid, PaperComponent paperComp, PaperIn if (TryComp(uid, out var appearance)) _appearance.SetData(uid, PaperVisuals.Status, PaperStatus.Written, appearance); - if (TryComp(uid, out var meta)) + if (TryComp(uid, out MetaDataComponent? meta)) _metaSystem.SetEntityDescription(uid, "", meta); _adminLogger.Add(LogType.Chat, LogImpact.Low, diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index 759b8ef29c6..6edc202d153 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -271,7 +271,7 @@ private void HandleShuttleMovement(float frameTime) consoleEnt = cargoConsole.Entity; } - if (!TryComp(consoleEnt, out var xform)) continue; + if (!TryComp(consoleEnt, out TransformComponent? xform)) continue; var gridId = xform.GridUid; // This tries to see if the grid is a shuttle and if the console should work. diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index e6ba1d02afd..d4a9159d44f 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -248,7 +248,7 @@ private void OnDestruction(Entity ent, ref Destructi } } - if (configuration.TransferName && TryComp(uid, out var targetMeta)) + if (configuration.TransferName && TryComp(uid, out MetaDataComponent? targetMeta)) _metaData.SetEntityName(child, targetMeta.EntityName); if (configuration.TransferHumanoidAppearance) diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index 0061b16e47c..f8c8ef64b79 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -72,7 +72,7 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St if (component.DeleteOnCollide) QueueDel(uid); - if (component.ImpactEffect != null && TryComp(uid, out var xform)) + if (component.ImpactEffect != null && TryComp(uid, out TransformComponent? xform)) { RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, GetNetCoordinates(xform.Coordinates)), Filter.Pvs(xform.Coordinates, entityMan: EntityManager)); } diff --git a/Content.Server/Salvage/FultonSystem.cs b/Content.Server/Salvage/FultonSystem.cs index a24bab45846..ad998e53598 100644 --- a/Content.Server/Salvage/FultonSystem.cs +++ b/Content.Server/Salvage/FultonSystem.cs @@ -53,7 +53,7 @@ public override void Update(float frameTime) private void Fulton(EntityUid uid, FultonedComponent component) { if (!Deleted(component.Beacon) && - TryComp(component.Beacon, out var beaconXform) && + TryComp(component.Beacon, out TransformComponent? beaconXform) && !Container.IsEntityOrParentInContainer(component.Beacon.Value, xform: beaconXform) && CanFulton(uid)) { diff --git a/Content.Server/Salvage/SalvageSystem.Runner.cs b/Content.Server/Salvage/SalvageSystem.Runner.cs index 23607e2bdc5..161b7910844 100644 --- a/Content.Server/Salvage/SalvageSystem.Runner.cs +++ b/Content.Server/Salvage/SalvageSystem.Runner.cs @@ -32,7 +32,7 @@ private void InitializeRunner() private void OnConsoleFTLAttempt(ref ConsoleFTLAttemptEvent ev) { - if (!TryComp(ev.Uid, out var xform) || + if (!TryComp(ev.Uid, out TransformComponent? xform) || !TryComp(xform.MapUid, out var salvage)) { return; diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index 23f7e10a3e3..e87e781e620 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -316,7 +316,7 @@ public void HandlePlayerSpawning(PlayerSpawningEvent ev) TryGetArrivals(out var arrivals); - if (TryComp(arrivals, out var arrivalsXform)) + if (TryComp(arrivals, out TransformComponent? arrivalsXform)) { var mapId = arrivalsXform.MapID; @@ -413,7 +413,7 @@ public override void Update(float frameTime) var curTime = _timing.CurTime; TryGetArrivals(out var arrivals); - if (TryComp(arrivals, out var arrivalsXform)) + if (TryComp(arrivals, out TransformComponent? arrivalsXform)) { while (query.MoveNext(out var uid, out var comp, out var shuttle, out var xform)) { diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 9bfe1bf9866..2d8ae4b735e 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -261,7 +261,7 @@ public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleCo if (!Resolve(stationUid, ref stationShuttle)) return; - if (!TryComp(stationShuttle.EmergencyShuttle, out var xform) || + if (!TryComp(stationShuttle.EmergencyShuttle, out TransformComponent? xform) || !TryComp(stationShuttle.EmergencyShuttle, out var shuttle)) { Log.Error($"Attempted to call an emergency shuttle for an uninitialized station? Station: {ToPrettyString(stationUid)}. Shuttle: {ToPrettyString(stationShuttle.EmergencyShuttle)}"); @@ -284,7 +284,7 @@ public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleCo if (_shuttle.TryFTLDock(stationShuttle.EmergencyShuttle.Value, shuttle, targetGrid.Value, DockTag)) { - if (TryComp(targetGrid.Value, out var targetXform)) + if (TryComp(targetGrid.Value, out TransformComponent? targetXform)) { var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false); @@ -330,7 +330,7 @@ private void OnStationInit(EntityUid uid, StationCentcommComponent component, Ma return; // Post mapinit? fancy - if (TryComp(component.Entity, out var xform)) + if (TryComp(component.Entity, out TransformComponent? xform)) { component.MapEntity = xform.MapUid; return; diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index 89dc114cafc..2b5769881d2 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -242,7 +242,7 @@ private void UpdateState(EntityUid consoleUid, ref DockingInterfaceState? dockSt RaiseLocalEvent(entity.Value, ref getShuttleEv); entity = getShuttleEv.Console; - TryComp(entity, out var consoleXform); + TryComp(entity, out TransformComponent? consoleXform); var shuttleGridUid = consoleXform?.GridUid; NavInterfaceState navState; diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs index c4cf2820e24..853548add37 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -184,7 +184,7 @@ private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapIn return; if (!TryComp(uid, out var dock) || - !TryComp(uid, out var xform) || + !TryComp(uid, out TransformComponent? xform) || xform.GridUid == null) { return; @@ -196,7 +196,7 @@ private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapIn if (_loader.TryLoad(mapId, component.Path.ToString(), out var ent) && ent.Count == 1 && - TryComp(ent[0], out var shuttleXform)) + TryComp(ent[0], out TransformComponent? shuttleXform)) { var escape = GetSingleDock(ent[0]); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs index ce79466b589..ed5d109e852 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs @@ -16,7 +16,7 @@ private void InitializeIFF() private void OnIFFShow(EntityUid uid, IFFConsoleComponent component, IFFShowIFFMessage args) { - if (!TryComp(uid, out var xform) || xform.GridUid == null || + if (!TryComp(uid, out TransformComponent? xform) || xform.GridUid == null || (component.AllowedFlags & IFFFlags.HideLabel) == 0x0) { return; @@ -34,7 +34,7 @@ private void OnIFFShow(EntityUid uid, IFFConsoleComponent component, IFFShowIFFM private void OnIFFShowVessel(EntityUid uid, IFFConsoleComponent component, IFFShowVesselMessage args) { - if (!TryComp(uid, out var xform) || xform.GridUid == null || + if (!TryComp(uid, out TransformComponent? xform) || xform.GridUid == null || (component.AllowedFlags & IFFFlags.Hide) == 0x0) { return; @@ -54,7 +54,7 @@ private void OnIFFConsoleAnchor(EntityUid uid, IFFConsoleComponent component, re { // If we anchor / re-anchor then make sure flags up to date. if (!args.Anchored || - !TryComp(uid, out var xform) || + !TryComp(uid, out TransformComponent? xform) || !TryComp(xform.GridUid, out var iff)) { _uiSystem.SetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState() diff --git a/Content.Server/Spawners/EntitySystems/SpawnOnDespawnSystem.cs b/Content.Server/Spawners/EntitySystems/SpawnOnDespawnSystem.cs index 77927c9bba9..f5a34728dc8 100644 --- a/Content.Server/Spawners/EntitySystems/SpawnOnDespawnSystem.cs +++ b/Content.Server/Spawners/EntitySystems/SpawnOnDespawnSystem.cs @@ -14,7 +14,7 @@ public override void Initialize() private void OnDespawn(EntityUid uid, SpawnOnDespawnComponent comp, ref TimedDespawnEvent args) { - if (!TryComp(uid, out var xform)) + if (!TryComp(uid, out TransformComponent? xform)) return; Spawn(comp.Prototype, xform.Coordinates); diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 8b3431cb024..c82a749755d 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -570,7 +570,7 @@ public bool InRangeUnobstructed( Ignored? predicate = null, bool popup = false) { - if (!TryComp(other, out var otherXform)) + if (!TryComp(other, out TransformComponent? otherXform)) return false; return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate, @@ -633,7 +633,7 @@ public bool InRangeUnobstructed( fixtureA.FixtureCount > 0 && TryComp(other, out var fixtureB) && fixtureB.FixtureCount > 0 && - TryComp(origin, out var xformA)) + TryComp(origin, out TransformComponent? xformA)) { var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation(); var xfA = new Transform(worldPosA, worldRotA); diff --git a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs index 8c42511f846..724eca682fb 100644 --- a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs +++ b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs @@ -113,7 +113,7 @@ private void OnJetpackToggle(EntityUid uid, JetpackComponent component, ToggleJe if (args.Handled) return; - if (TryComp(uid, out var xform) && !CanEnableOnGrid(xform.GridUid)) + if (TryComp(uid, out TransformComponent? xform) && !CanEnableOnGrid(xform.GridUid)) { _popup.PopupClient(Loc.GetString("jetpack-no-station"), uid, args.Performer); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index ba175b345f8..1ccb7f0c3f1 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -313,7 +313,7 @@ private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bo // For stuff like "Moving out of locker" or the likes // We'll relay a movement input to the parent. if (_container.IsEntityInContainer(entity) && - TryComp(entity, out var xform) && + TryComp(entity, out TransformComponent? xform) && xform.ParentUid.IsValid() && _mobState.IsAlive(entity)) { diff --git a/Content.Shared/Nutrition/EntitySystems/PressurizedSolutionSystem.cs b/Content.Shared/Nutrition/EntitySystems/PressurizedSolutionSystem.cs index d63b8e7326c..82d90f4c928 100644 --- a/Content.Shared/Nutrition/EntitySystems/PressurizedSolutionSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/PressurizedSolutionSystem.cs @@ -179,7 +179,7 @@ public bool TrySpray(Entity entity, EntityUid? ta var solution = _solutionContainer.SplitSolution(soln.Value, interactions.Volume); // Spray the solution onto the ground and anyone nearby - if (TryComp(entity, out var transform)) + if (TryComp(entity, out TransformComponent? transform)) _puddle.TrySplashSpillAt(entity, transform.Coordinates, solution, out _, sound: false); var drinkName = Identity.Entity(entity, EntityManager); diff --git a/Content.Shared/Nutrition/EntitySystems/SharedDrinkSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedDrinkSystem.cs index 7cae3b92086..bf1e585fab9 100644 --- a/Content.Shared/Nutrition/EntitySystems/SharedDrinkSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/SharedDrinkSystem.cs @@ -81,7 +81,7 @@ private string HalfEmptyOrHalfFull(ExaminedEvent args) { string remainingString = "drink-component-on-examine-is-half-full"; - if (TryComp(args.Examiner, out var examiner) && examiner.EntityName.Length > 0 + if (TryComp(args.Examiner, out MetaDataComponent? examiner) && examiner.EntityName.Length > 0 && string.Compare(examiner.EntityName.Substring(0, 1), "m", StringComparison.InvariantCultureIgnoreCase) > 0) remainingString = "drink-component-on-examine-is-half-empty"; diff --git a/Content.Shared/Random/RulesSystem.cs b/Content.Shared/Random/RulesSystem.cs index f8711fb63e0..6b8a58abb71 100644 --- a/Content.Shared/Random/RulesSystem.cs +++ b/Content.Shared/Random/RulesSystem.cs @@ -26,7 +26,7 @@ public bool IsTrue(EntityUid uid, RulesPrototype rules) break; case GridInRangeRule griddy: { - if (!TryComp(uid, out var xform)) + if (!TryComp(uid, out TransformComponent? xform)) { return false; } @@ -50,7 +50,7 @@ public bool IsTrue(EntityUid uid, RulesPrototype rules) } case InSpaceRule: { - if (!TryComp(uid, out var xform) || + if (!TryComp(uid, out TransformComponent? xform) || xform.GridUid != null) { return false; @@ -146,7 +146,7 @@ public bool IsTrue(EntityUid uid, RulesPrototype rules) } case NearbyEntitiesRule entity: { - if (!TryComp(uid, out var xform) || + if (!TryComp(uid, out TransformComponent? xform) || xform.MapUid == null) { return false; @@ -177,7 +177,7 @@ public bool IsTrue(EntityUid uid, RulesPrototype rules) } case NearbyTilesPercentRule tiles: { - if (!TryComp(uid, out var xform) || + if (!TryComp(uid, out TransformComponent? xform) || !TryComp(xform.GridUid, out var grid)) { return false; @@ -227,7 +227,7 @@ public bool IsTrue(EntityUid uid, RulesPrototype rules) } case OnMapGridRule: { - if (!TryComp(uid, out var xform) || + if (!TryComp(uid, out TransformComponent? xform) || xform.GridUid != xform.MapUid || xform.MapUid == null) { diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index a9a50698d7d..0dcdc44c9f2 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -72,7 +72,7 @@ private void OnEmitSpawnOnInit(EntityUid uid, EmitSoundOnSpawnComponent componen private void OnEmitSoundOnLand(EntityUid uid, BaseEmitSoundComponent component, ref LandEvent args) { if (!args.PlaySound || - !TryComp(uid, out var xform) || + !TryComp(uid, out TransformComponent? xform) || !TryComp(xform.GridUid, out var grid)) { return; diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 278758e7fec..ed9a7855999 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -464,7 +464,7 @@ private void AfterInteract(EntityUid uid, StorageComponent storageComp, AfterInt return; } - if (_xformQuery.TryGetComponent(uid, out var transformOwner) && TryComp(target, out var transformEnt)) + if (TryComp(uid, out TransformComponent? transformOwner) && TryComp(target, out TransformComponent? transformEnt)) { var parent = transformOwner.ParentUid; diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 6fa11f87d2a..0fb6ac3eff9 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -315,7 +315,7 @@ public void AttemptLightAttackMiss(EntityUid user, EntityUid weaponUid, MeleeWea public bool AttemptLightAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target) { - if (!TryComp(target, out var targetXform)) + if (!TryComp(target, out TransformComponent? targetXform)) return false; return AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(targetXform.Coordinates)), null); @@ -323,7 +323,7 @@ public bool AttemptLightAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponC public bool AttemptDisarmAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target) { - if (!TryComp(target, out var targetXform)) + if (!TryComp(target, out TransformComponent? targetXform)) return false; return AttemptAttack(user, weaponUid, weapon, new DisarmAttackEvent(GetNetEntity(target), GetNetCoordinates(targetXform.Coordinates)), null); @@ -446,7 +446,7 @@ protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, Entity // For consistency with wide attacks stuff needs damageable. if (Deleted(target) || !HasComp(target) || - !TryComp(target, out var targetXform) || + !TryComp(target, out TransformComponent? targetXform) || // Not in LOS. !InRange(user, target.Value, component.Range, session)) { @@ -534,7 +534,7 @@ protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, Entity private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session) { // TODO: This is copy-paste as fuck with DoPreciseAttack - if (!TryComp(user, out var userXform)) + if (!TryComp(user, out TransformComponent? userXform)) return false; var targetMap = GetCoordinates(ev.Coordinates).ToMap(EntityManager, TransformSystem); @@ -748,7 +748,7 @@ protected virtual bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid private void DoLungeAnimation(EntityUid user, EntityUid weapon, Angle angle, MapCoordinates coordinates, float length, string? animation) { // TODO: Assert that offset eyes are still okay. - if (!TryComp(user, out var userXform)) + if (!TryComp(user, out TransformComponent? userXform)) return; var invMatrix = TransformSystem.GetInvWorldMatrix(userXform); From 5620dcd86bb8deaa149fe9a55b0432d940fb70f4 Mon Sep 17 00:00:00 2001 From: Killerqu00 <47712032+killerqu00@users.noreply.github.com> Date: Tue, 21 May 2024 12:16:35 +0200 Subject: [PATCH 038/235] sleeper agent appear later into the round and only once (#28160) --- Resources/Prototypes/GameRules/events.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 15920b3498c..6b0a55ed366 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -472,10 +472,10 @@ # noSpawn: true # components: # - type: StationEvent -# earliestStart: 25 -# weight: 15 +# earliestStart: 30 +# weight: 8 # minimumPlayers: 15 -# reoccurrenceDelay: 60 +# maxOccurrences: 1 # can only happen once per round # startAnnouncement: station-event-communication-interception # startAudio: # path: /Audio/Announcements/intercept.ogg From 7a42d25e25d882b0d79067fffd88f36bf57bbefb Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 21 May 2024 10:17:43 +0000 Subject: [PATCH 039/235] 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 08d13027c8f..12da3a2a900 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Admiral-Obvious-001 - changes: - - message: Zombies are now tougher to kill. - type: Tweak - id: 6110 - time: '2024-03-07T21:02:50.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25876 - author: TheShuEd changes: - message: Removed Hamlet, Smile and Pun Pun from Thief objectives @@ -3870,3 +3863,10 @@ id: 6609 time: '2024-05-20T14:12:05.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28120 +- author: Killerqu00 + changes: + - message: Sleeper Agents only appear once per round. + type: Tweak + id: 6610 + time: '2024-05-21T10:16:35.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28160 From f580b4be64c7dd7488b2b2d836aab9a920cdf0ba Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Tue, 21 May 2024 05:11:49 -0700 Subject: [PATCH 040/235] Add defib event, add fields to be able to disable crit defib and do after movement (#28174) * Add defib event, add fields to be able to disable crit defib and do after movement * Fix check --- Content.Server/Medical/DefibrillatorSystem.cs | 10 +++++++++- Content.Shared/Medical/DefibrillatorComponent.cs | 6 ++++++ Content.Shared/Medical/TargetDefibrillatedEvent.cs | 4 ++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Content.Shared/Medical/TargetDefibrillatedEvent.cs diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs index c8410c7c3c5..e3e6dc889c9 100644 --- a/Content.Server/Medical/DefibrillatorSystem.cs +++ b/Content.Server/Medical/DefibrillatorSystem.cs @@ -161,6 +161,9 @@ public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, Defi if (_mobState.IsAlive(target, mobState)) return false; + if (!component.CanDefibCrit && _mobState.IsCritical(target, mobState)) + return false; + return true; } @@ -178,7 +181,8 @@ public bool TryStartZap(EntityUid uid, EntityUid target, EntityUid user, Defibri { BlockDuplicate = true, BreakOnHandChange = true, - NeedHand = true + NeedHand = true, + BreakOnMove = !component.AllowDoAfterMovement }); } @@ -248,6 +252,10 @@ public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorCo // if we don't have enough power left for another shot, turn it off if (!_powerCell.HasActivatableCharge(uid)) TryDisable(uid, component); + + // TODO clean up this clown show above + var ev = new TargetDefibrillatedEvent(user, (uid, component)); + RaiseLocalEvent(target, ref ev); } public override void Update(float frameTime) diff --git a/Content.Shared/Medical/DefibrillatorComponent.cs b/Content.Shared/Medical/DefibrillatorComponent.cs index 2da52852854..61a02187d09 100644 --- a/Content.Shared/Medical/DefibrillatorComponent.cs +++ b/Content.Shared/Medical/DefibrillatorComponent.cs @@ -60,6 +60,12 @@ public sealed partial class DefibrillatorComponent : Component [DataField("doAfterDuration"), ViewVariables(VVAccess.ReadWrite)] public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(3); + [DataField] + public bool AllowDoAfterMovement = true; + + [DataField] + public bool CanDefibCrit = true; + /// /// The sound when someone is zapped. /// diff --git a/Content.Shared/Medical/TargetDefibrillatedEvent.cs b/Content.Shared/Medical/TargetDefibrillatedEvent.cs new file mode 100644 index 00000000000..60d1a215845 --- /dev/null +++ b/Content.Shared/Medical/TargetDefibrillatedEvent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Medical; + +[ByRefEvent] +public readonly record struct TargetDefibrillatedEvent(EntityUid User, Entity Defibrillator); From fdf0953f7e9e7cc9e8119d751299bb2ba425beda Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Wed, 22 May 2024 10:52:57 +0300 Subject: [PATCH 041/235] Artifact portal effect changes (#28200) Update portal.yml --- Resources/Prototypes/Entities/Effects/portal.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Effects/portal.yml b/Resources/Prototypes/Entities/Effects/portal.yml index eb69ac821f5..75300693cd9 100644 --- a/Resources/Prototypes/Entities/Effects/portal.yml +++ b/Resources/Prototypes/Entities/Effects/portal.yml @@ -65,7 +65,7 @@ energy: 1 netsync: false - type: TimedDespawn - lifetime: 120 + lifetime: 1 - type: Portal canTeleportToOtherMaps: true From 18c8d58475951f01e3b38ee98ebce7e6b4dee8ef Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 22 May 2024 07:54:04 +0000 Subject: [PATCH 042/235] 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 12da3a2a900..d20cf44cdb9 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: TheShuEd - changes: - - message: Removed Hamlet, Smile and Pun Pun from Thief objectives - type: Remove - id: 6111 - time: '2024-03-07T23:53:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25921 - author: MACMAN2003 changes: - message: Diagonal windows no longer space you when in a pressurized environment. @@ -3870,3 +3863,10 @@ id: 6610 time: '2024-05-21T10:16:35.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28160 +- author: TheShuEd + changes: + - message: Artifact portal lifespan reduced from 120 seconds to 1 second + type: Tweak + id: 6611 + time: '2024-05-22T07:52:58.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28200 From 06b4df686b747543a46fd90d5767d0ce9fff19de Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Wed, 22 May 2024 11:16:20 +0000 Subject: [PATCH 043/235] Firelock improvements part 1 (#26582) * Change prying system and pryunpoweredcomp to allow for custom time modifiers This will be useful if I go the route of making firelocks pryable when unpowered instead of just being able to open and close instantly when unpowered. * Make firelocks properly predicted Shared system made. Since atmos checks can only be done on the server we just have it set relevant bools on the component and then dirty it. Ditched atmos checks on trying to open, they now only happen whenever firelocks are updated. * Make firelocks pryable without a crowbar While this usually would only allow you to do this when a door is unpowered, firelocks do not have the airlock component which actually does that check. As such firelocks will always allow you to pry them open/closed by hand. * Clean up System. Change update interval to be based on ticks. Move as much as possible to shared * Make firelocks unable to emergency close for 2 seconds after being pried open * Clean up * More cleanup * Reorganize SharedFirelockSystem methods to match Initialize order --------- Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> --- Content.Client/Doors/FirelockSystem.cs | 3 +- .../Doors/Systems/FirelockSystem.cs | 134 +++--------------- .../Doors/Components/FirelockComponent.cs | 48 ++++++- .../Doors/Systems/SharedDoorSystem.cs | 3 +- .../Doors/Systems/SharedFirelockSystem.cs | 125 ++++++++++++++++ .../Components/PryUnpoweredComponent.cs | 2 + Content.Shared/Prying/Systems/PryingSystem.cs | 8 +- .../Structures/Doors/Firelocks/firelock.yml | 2 + 8 files changed, 200 insertions(+), 125 deletions(-) create mode 100644 Content.Shared/Doors/Systems/SharedFirelockSystem.cs diff --git a/Content.Client/Doors/FirelockSystem.cs b/Content.Client/Doors/FirelockSystem.cs index cfd84a47133..f64b4c8e522 100644 --- a/Content.Client/Doors/FirelockSystem.cs +++ b/Content.Client/Doors/FirelockSystem.cs @@ -1,9 +1,10 @@ using Content.Shared.Doors.Components; +using Content.Shared.Doors.Systems; using Robust.Client.GameObjects; namespace Content.Client.Doors; -public sealed class FirelockSystem : EntitySystem +public sealed class FirelockSystem : SharedFirelockSystem { [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index 3d4c8a4ec59..5ad86fb20aa 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -1,67 +1,56 @@ using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Monitor.Systems; -using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Shuttles.Components; -using Content.Shared.Access.Systems; using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.Doors; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; -using Content.Shared.Popups; -using Content.Shared.Prying.Components; using Robust.Shared.Map.Components; namespace Content.Server.Doors.Systems { - public sealed class FirelockSystem : EntitySystem + public sealed class FirelockSystem : SharedFirelockSystem { - [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedDoorSystem _doorSystem = default!; [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + [Dependency] private readonly SharedMapSystem _mapping = default!; - private static float _visualUpdateInterval = 0.5f; - private float _accumulatedFrameTime; + private const int UpdateInterval = 30; + private int _accumulatedTicks; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnBeforeDoorOpened); - SubscribeLocalEvent(OnDoorGetPryTimeModifier); - SubscribeLocalEvent(OnUpdateState); SubscribeLocalEvent(OnBeforeDoorAutoclose); SubscribeLocalEvent(OnAtmosAlarm); - // Visuals - SubscribeLocalEvent(UpdateVisuals); - SubscribeLocalEvent(UpdateVisuals); SubscribeLocalEvent(PowerChanged); + } private void PowerChanged(EntityUid uid, FirelockComponent component, ref PowerChangedEvent args) { // TODO this should REALLLLY not be door specific appearance thing. _appearance.SetData(uid, DoorVisuals.Powered, args.Powered); + component.Powered = args.Powered; + Dirty(uid, component); } - #region Visuals - private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component); - public override void Update(float frameTime) { - _accumulatedFrameTime += frameTime; - if (_accumulatedFrameTime < _visualUpdateInterval) + _accumulatedTicks += 1; + if (_accumulatedTicks < UpdateInterval) return; - _accumulatedFrameTime -= _visualUpdateInterval; + _accumulatedTicks = 0; var airtightQuery = GetEntityQuery(); var appearanceQuery = GetEntityQuery(); @@ -84,94 +73,13 @@ public override void Update(float frameTime) { var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery); _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance); + firelock.Temperature = fire; + firelock.Pressure = pressure; + Dirty(uid, firelock); } } } - private void UpdateVisuals(EntityUid uid, - FirelockComponent? firelock = null, - DoorComponent? door = null, - AirtightComponent? airtight = null, - AppearanceComponent? appearance = null, - TransformComponent? xform = null) - { - if (!Resolve(uid, ref door, ref appearance, false)) - return; - - // only bother to check pressure on doors that are some variation of closed. - if (door.State != DoorState.Closed - && door.State != DoorState.Welded - && door.State != DoorState.Denying) - { - _appearance.SetData(uid, DoorVisuals.ClosedLights, false, appearance); - return; - } - - var query = GetEntityQuery(); - if (!Resolve(uid, ref firelock, ref airtight, ref appearance, ref xform, false) || !query.Resolve(uid, ref airtight, false)) - return; - - var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, query); - _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance); - } - #endregion - - public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null) - { - if (!Resolve(uid, ref firelock, ref door)) - return false; - - if (door.State == DoorState.Open) - { - if (_doorSystem.TryClose(uid, door)) - { - return _doorSystem.OnPartialClose(uid, door); - } - } - return false; - } - - private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) - { - // Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas - var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid); - - if (!this.IsPowered(uid, EntityManager) || (!overrideAccess && IsHoldingPressureOrFire(uid, component))) - args.Cancel(); - } - - private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args) - { - var state = CheckPressureAndFire(uid, component); - - if (state.Fire) - { - _popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-fire-message"), - uid, args.User, PopupType.MediumCaution); - } - else if (state.Pressure) - { - _popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-pressure-message"), - uid, args.User, PopupType.MediumCaution); - } - - if (state.Fire || state.Pressure) - args.PryTimeModifier *= component.LockedPryTimeModifier; - } - - private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args) - { - var ev = new BeforeDoorAutoCloseEvent(); - RaiseLocalEvent(uid, ev); - UpdateVisuals(uid, component, args); - if (ev.Cancelled) - { - return; - } - - _doorSystem.SetNextStateChange(uid, component.AutocloseDelay); - } - private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args) { if (!this.IsPowered(uid, EntityManager)) @@ -204,12 +112,6 @@ private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosAlarm } } - public bool IsHoldingPressureOrFire(EntityUid uid, FirelockComponent firelock) - { - var result = CheckPressureAndFire(uid, firelock); - return result.Pressure || result.Fire; - } - public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid uid, FirelockComponent firelock) { var query = GetEntityQuery(); @@ -234,17 +136,17 @@ public bool IsHoldingPressureOrFire(EntityUid uid, FirelockComponent firelock) return (false, false); } - if (!TryComp(xform.ParentUid, out GridAtmosphereComponent? gridAtmosphere)) + if (!HasComp(xform.ParentUid)) return (false, false); var grid = Comp(xform.ParentUid); - var pos = grid.CoordinatesToTile(xform.Coordinates); + var pos = _mapping.CoordinatesToTile(xform.ParentUid, grid, xform.Coordinates); var minPressure = float.MaxValue; var maxPressure = float.MinValue; var minTemperature = float.MaxValue; var maxTemperature = float.MinValue; - bool holdingFire = false; - bool holdingPressure = false; + var holdingFire = false; + var holdingPressure = false; // We cannot simply use `_atmosSystem.GetAdjacentTileMixtures` because of how the `includeBlocked` option // works, we want to ignore the firelock's blocking, while including blockers on other tiles. @@ -284,7 +186,7 @@ public bool IsHoldingPressureOrFire(EntityUid uid, FirelockComponent firelock) { // Is there some airtight entity blocking this direction? If yes, don't include this direction in the // pressure differential - if (HasAirtightBlocker(grid.GetAnchoredEntities(adjacentPos), dir.GetOpposite(), airtightQuery)) + if (HasAirtightBlocker(_mapping.GetAnchoredEntities(xform.ParentUid, grid, adjacentPos), dir.GetOpposite(), airtightQuery)) continue; var p = gas.Pressure; diff --git a/Content.Shared/Doors/Components/FirelockComponent.cs b/Content.Shared/Doors/Components/FirelockComponent.cs index 97e57185cac..ca62daaa0fd 100644 --- a/Content.Shared/Doors/Components/FirelockComponent.cs +++ b/Content.Shared/Doors/Components/FirelockComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Doors.Components; +using Robust.Shared.GameStates; namespace Content.Shared.Doors.Components { @@ -7,9 +7,11 @@ namespace Content.Shared.Doors.Components /// auto-closing on depressurization, air/fire alarm interactions, and preventing normal door functions when /// retaining pressure.. /// - [RegisterComponent] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class FirelockComponent : Component { + #region Settings + /// /// Pry time modifier to be used when the firelock is currently closed due to fire or pressure. /// @@ -39,5 +41,47 @@ public sealed partial class FirelockComponent : Component /// [DataField("alarmAutoClose"), ViewVariables(VVAccess.ReadWrite)] public bool AlarmAutoClose = true; + + /// + /// The cooldown duration before a firelock can automatically close due to a hazardous environment after it has + /// been pried open. Measured in seconds. + /// + [DataField] + public TimeSpan EmergencyCloseCooldownDuration = TimeSpan.FromSeconds(2); + + #endregion + + #region Set by system + + /// + /// When the firelock will be allowed to automatically close again due to a hazardous environment. + /// + [DataField] + public TimeSpan? EmergencyCloseCooldown; + + /// + /// Whether the firelock can open, or is locked due to its environment. + /// + public bool IsLocked => Pressure || Temperature; + + /// + /// Whether the firelock is holding back a hazardous pressure. + /// + [DataField, AutoNetworkedField] + public bool Pressure; + + /// + /// Whether the firelock is holding back extreme temperatures. + /// + [DataField, AutoNetworkedField] + public bool Temperature; + + /// + /// Whether the airlock is powered. + /// + [DataField, AutoNetworkedField] + public bool Powered; + + #endregion } } diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index b58b7b265e9..20456c14777 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -6,7 +6,6 @@ using Content.Shared.Database; using Content.Shared.Doors.Components; using Content.Shared.Emag.Systems; -using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Physics; using Content.Shared.Popups; @@ -466,7 +465,7 @@ public bool OnPartialClose(EntityUid uid, DoorComponent? door = null, PhysicsCom door.Partial = true; - // Make sure no entity waled into the airlock when it started closing. + // Make sure no entity walked into the airlock when it started closing. if (!CanClose(uid, door)) { door.NextStateChange = GameTiming.CurTime + door.OpenTimeTwo; diff --git a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs new file mode 100644 index 00000000000..7d033efdd40 --- /dev/null +++ b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs @@ -0,0 +1,125 @@ +using Content.Shared.Access.Systems; +using Content.Shared.Doors.Components; +using Content.Shared.Popups; +using Content.Shared.Prying.Components; +using Robust.Shared.Timing; + +namespace Content.Shared.Doors.Systems; + +public abstract class SharedFirelockSystem : EntitySystem +{ + [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedDoorSystem _doorSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUpdateState); + + // Access/Prying + SubscribeLocalEvent(OnBeforeDoorOpened); + SubscribeLocalEvent(OnDoorGetPryTimeModifier); + SubscribeLocalEvent(OnAfterPried); + + // Visuals + SubscribeLocalEvent(UpdateVisuals); + SubscribeLocalEvent(UpdateVisuals); + } + + public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null) + { + if (!Resolve(uid, ref firelock, ref door)) + return false; + + if (door.State != DoorState.Open + || firelock.EmergencyCloseCooldown != null + && _gameTiming.CurTime < firelock.EmergencyCloseCooldown) + return false; + + if (!_doorSystem.TryClose(uid, door)) + return false; + + return _doorSystem.OnPartialClose(uid, door); + } + + private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args) + { + var ev = new BeforeDoorAutoCloseEvent(); + RaiseLocalEvent(uid, ev); + UpdateVisuals(uid, component, args); + if (ev.Cancelled) + { + return; + } + + _doorSystem.SetNextStateChange(uid, component.AutocloseDelay); + } + + #region Access/Prying + + private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) + { + // Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas + var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid); + + if (!component.Powered || (!overrideAccess && component.IsLocked)) + args.Cancel(); + } + + private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args) + { + if (component.Temperature) + { + _popupSystem.PopupClient(Loc.GetString("firelock-component-is-holding-fire-message"), + uid, args.User, PopupType.MediumCaution); + } + else if (component.Pressure) + { + _popupSystem.PopupClient(Loc.GetString("firelock-component-is-holding-pressure-message"), + uid, args.User, PopupType.MediumCaution); + } + + if (component.IsLocked) + args.PryTimeModifier *= component.LockedPryTimeModifier; + } + + private void OnAfterPried(EntityUid uid, FirelockComponent component, ref PriedEvent args) + { + component.EmergencyCloseCooldown = _gameTiming.CurTime + component.EmergencyCloseCooldownDuration; + } + + #endregion + + #region Visuals + + private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component); + + private void UpdateVisuals(EntityUid uid, + FirelockComponent? firelock = null, + DoorComponent? door = null, + AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref door, ref appearance, false)) + return; + + // only bother to check pressure on doors that are some variation of closed. + if (door.State != DoorState.Closed + && door.State != DoorState.Welded + && door.State != DoorState.Denying) + { + _appearance.SetData(uid, DoorVisuals.ClosedLights, false, appearance); + return; + } + + if (!Resolve(uid, ref firelock, ref appearance, false)) + return; + + _appearance.SetData(uid, DoorVisuals.ClosedLights, firelock.IsLocked, appearance); + } + + #endregion +} diff --git a/Content.Shared/Prying/Components/PryUnpoweredComponent.cs b/Content.Shared/Prying/Components/PryUnpoweredComponent.cs index f0e61dc9685..b6b69a2577d 100644 --- a/Content.Shared/Prying/Components/PryUnpoweredComponent.cs +++ b/Content.Shared/Prying/Components/PryUnpoweredComponent.cs @@ -8,4 +8,6 @@ namespace Content.Shared.Prying.Components; [RegisterComponent, NetworkedComponent] public sealed partial class PryUnpoweredComponent : Component { + [DataField] + public float PryModifier = 0.1f; } diff --git a/Content.Shared/Prying/Systems/PryingSystem.cs b/Content.Shared/Prying/Systems/PryingSystem.cs index ab87585c706..372c89c9ae0 100644 --- a/Content.Shared/Prying/Systems/PryingSystem.cs +++ b/Content.Shared/Prying/Systems/PryingSystem.cs @@ -93,17 +93,17 @@ public bool TryPry(EntityUid target, EntityUid user, out DoAfterId? id) id = null; // We don't care about displaying a message if no tool was used. - if (!CanPry(target, user, out _)) + if (!TryComp(target, out var unpoweredComp) || !CanPry(target, user, out _, unpoweredComp: unpoweredComp)) // If we have reached this point we want the event that caused this // to be marked as handled. return true; // hand-prying is much slower - var modifier = CompOrNull(user)?.SpeedModifier ?? 0.1f; + var modifier = CompOrNull(user)?.SpeedModifier ?? unpoweredComp.PryModifier; return StartPry(target, user, null, modifier, out id); } - private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null) + private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null, PryUnpoweredComponent? unpoweredComp = null) { BeforePryEvent canev; @@ -113,7 +113,7 @@ private bool CanPry(EntityUid target, EntityUid user, out string? message, Pryin } else { - if (!TryComp(target, out _)) + if (!Resolve(target, ref unpoweredComp)) { message = null; return false; diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index fcdb432dce6..860db862aee 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -108,6 +108,8 @@ - type: DoorBolt - type: AccessReader access: [ [ "Engineering" ] ] + - type: PryUnpowered + pryModifier: 0.5 - type: entity id: Firelock From 4301cf536b7a0c277f57eb2bcd0859bbd3fb10ce Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 22 May 2024 11:17:27 +0000 Subject: [PATCH 044/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d20cf44cdb9..a8e3a5f155b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: MACMAN2003 - changes: - - message: Diagonal windows no longer space you when in a pressurized environment. - type: Fix - id: 6112 - time: '2024-03-08T07:50:34.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25926 - author: mryikes changes: - message: New Nuke Detonation Song "Clearly Nuclear". @@ -3870,3 +3863,15 @@ id: 6611 time: '2024-05-22T07:52:58.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28200 +- author: nikthechampiongr + changes: + - message: Firelocks can now be pried by hand. + type: Tweak + - message: Firelocks will now not automatically close for 2 seconds after you pry + them open. + type: Tweak + - message: Firelocks are now properly predicted. + type: Fix + id: 6612 + time: '2024-05-22T11:16:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/26582 From 4694f79a4529d508f718d030f50eb8f8926e072f Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Wed, 22 May 2024 05:29:19 -0700 Subject: [PATCH 045/235] Revert "Make hotplate and grill anchorable on table" (#28202) Revert "Make hotplate and grill anchorable on table (#28026)" This reverts commit 26747be232c364abcb4e43fb934c85ef0e3e1264. --- .../Prototypes/Entities/Structures/Machines/hotplate.yml | 4 +++- .../Prototypes/Entities/Structures/Machines/microwave.yml | 1 - .../Entities/Structures/Machines/reagent_grinder.yml | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml b/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml index 05cbb60ed30..3764f135915 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml @@ -15,7 +15,9 @@ mask: - TabletopMachineMask layer: - - TabletopMachineLayer + - Impassable + - MidImpassable + - LowImpassable hard: false - type: ApcPowerReceiver powerLoad: 300 diff --git a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml index d78960030d6..fe4eb145183 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml @@ -58,7 +58,6 @@ - TabletopMachineMask layer: - TabletopMachineLayer - hard: false - type: Sprite sprite: Structures/Machines/microwave.rsi drawdepth: SmallObjects diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index 9894a2691fe..d6e73333133 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml @@ -32,7 +32,6 @@ - TabletopMachineMask layer: - TabletopMachineLayer - hard: false - type: Sprite sprite: Structures/Machines/juicer.rsi drawdepth: SmallObjects From 57b4af9f71146e7931a0358b48e62db6d3be6aa8 Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Wed, 22 May 2024 16:23:55 +0200 Subject: [PATCH 046/235] Implement permissive version of AddMarkup and use it for tips (#28204) * Implement permissive version of ddMarkup Use permissive ddMarkup for news article input Use permissive ddMarkup for tips * Fix doc comment format --- .../MassMedia/Ui/ArticleEditorPanel.xaml.cs | 2 +- Content.Client/Message/RichTextLabelExt.cs | 18 ++++++++++++++++++ Content.Client/Tips/TippyUIController.cs | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs index 7f98e3e0c3d..5e068f1e9c5 100644 --- a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs +++ b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs @@ -76,7 +76,7 @@ private void OnPreview(BaseButton.ButtonEventArgs eventArgs) TextEditPanel.Visible = !_preview; PreviewPanel.Visible = _preview; - PreviewLabel.SetMarkup(Rope.Collapse(ContentField.TextRope)); + PreviewLabel.SetMarkupPermissive(Rope.Collapse(ContentField.TextRope)); } private void OnCancel(BaseButton.ButtonEventArgs eventArgs) diff --git a/Content.Client/Message/RichTextLabelExt.cs b/Content.Client/Message/RichTextLabelExt.cs index ab6d17bf44d..7ff6390764b 100644 --- a/Content.Client/Message/RichTextLabelExt.cs +++ b/Content.Client/Message/RichTextLabelExt.cs @@ -5,9 +5,27 @@ namespace Content.Client.Message; public static class RichTextLabelExt { + + + /// + /// Sets the labels markup. + /// + /// + /// Invalid markup will cause exceptions to be thrown. Don't use this for user input! + /// public static RichTextLabel SetMarkup(this RichTextLabel label, string markup) { label.SetMessage(FormattedMessage.FromMarkup(markup)); return label; } + + /// + /// Sets the labels markup.
+ /// Uses FormatedMessage.FromMarkupPermissive which treats invalid markup as text. + ///
+ public static RichTextLabel SetMarkupPermissive(this RichTextLabel label, string markup) + { + label.SetMessage(FormattedMessage.FromMarkupPermissive(markup)); + return label; + } } diff --git a/Content.Client/Tips/TippyUIController.cs b/Content.Client/Tips/TippyUIController.cs index 67c3ee45a7e..2cc694d97d4 100644 --- a/Content.Client/Tips/TippyUIController.cs +++ b/Content.Client/Tips/TippyUIController.cs @@ -175,7 +175,7 @@ private void NextState(TippyUI tippy) sprite.LayerSetVisible("hiding", false); } sprite.Rotation = 0; - tippy.Label.SetMarkup(_currentMessage.Msg); + tippy.Label.SetMarkupPermissive(_currentMessage.Msg); tippy.Label.Visible = false; tippy.LabelPanel.Visible = false; tippy.Visible = true; From ead372110e5d98adf71dd1113a455b36aeec8296 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Wed, 22 May 2024 18:59:49 +0200 Subject: [PATCH 047/235] Remove dupe closet janitor bombsuit (#28177) * Remove dupe closet janitor bombsuit * Update Resources/migration.yml Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> --------- Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> --- Resources/Migrations/migration.yml | 3 +++ .../Prototypes/Catalog/Cargo/cargo_service.yml | 2 +- .../Prototypes/Catalog/Fills/Crates/service.yml | 13 ------------- .../Prototypes/Catalog/Fills/Lockers/service.yml | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/Resources/Migrations/migration.yml b/Resources/Migrations/migration.yml index 1da37d007af..a2c8d060e64 100644 --- a/Resources/Migrations/migration.yml +++ b/Resources/Migrations/migration.yml @@ -326,3 +326,6 @@ DrinkBottleGoldschlager: DrinkBottleGildlager # 2024-05-14 soda_dispenser: SodaDispenser chem_master: ChemMaster + +# 2024-05-21 +CrateJanitorExplosive: ClosetJanitorBombFilled diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml index ebcd9dfc5e6..d2ca08e1166 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml @@ -173,7 +173,7 @@ icon: sprite: Clothing/Head/Helmets/janitor_bombsuit.rsi state: icon - product: CrateJanitorExplosive + product: ClosetJanitorBombFilled cost: 1000 category: cargoproduct-category-name-service group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/service.yml b/Resources/Prototypes/Catalog/Fills/Crates/service.yml index 3b52faa0c0b..569f2a9285b 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/service.yml @@ -326,16 +326,3 @@ prob: 0.1 - id: ShardGlassPlasma prob: 0.1 - -- type: entity - id: CrateJanitorExplosive - parent: ClosetJanitorBomb - name: janitorial bomb suit crate - description: Supplies a bomb suit for cleaning up any explosive compounds, buy one today! - components: - - type: StorageFill - contents: - - id: ClothingOuterSuitJanitorBomb - amount: 1 - - id: ClothingHeadHelmetJanitorBombSuit - amount: 1 diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index bfa46a6135c..19e89e1f408 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -138,7 +138,7 @@ - type: entity id: ClosetJanitorBombFilled parent: ClosetJanitorBomb - suffix: Filled + suffix: DO NOT MAP, Filled components: - type: StorageFill contents: From 1aedf691124e544843ae718582538766b2efa182 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Wed, 22 May 2024 21:16:14 -0700 Subject: [PATCH 048/235] Prioritize empty item slots when inserting (#28203) * Prioritize empty item slots when inserting * Revert "Prioritize empty item slots when inserting" This reverts commit 4272a65cba5fc18df801812b0af20123aec08409. * Prioritize empty item slots when inserting * Try drop * Check for any can insert before dropping --- .../Containers/ItemSlot/ItemSlotsSystem.cs | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 914b34d3c12..2e3f9ed461a 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -197,6 +197,7 @@ private void OnInteractUsing(EntityUid uid, ItemSlotsComponent itemSlots, Intera if (!EntityManager.TryGetComponent(args.User, out HandsComponent? hands)) return; + var slots = new List(); foreach (var slot in itemSlots.Slots.Values) { if (!slot.InsertOnInteract) @@ -205,10 +206,20 @@ private void OnInteractUsing(EntityUid uid, ItemSlotsComponent itemSlots, Intera if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap, popup: args.User)) continue; - // Drop the held item onto the floor. Return if the user cannot drop. - if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands)) - return; + slots.Add(slot); + } + if (slots.Count == 0) + return; + + // Drop the held item onto the floor. Return if the user cannot drop. + if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands)) + return; + + slots.Sort(SortEmpty); + + foreach (var slot in slots) + { if (slot.Item != null) _handsSystem.TryPickupAnyHand(args.User, slot.Item.Value, handsComp: hands); @@ -333,6 +344,65 @@ public bool TryInsertFromHand(EntityUid uid, ItemSlot slot, EntityUid user, Hand Insert(uid, slot, held, user, excludeUserAudio: excludeUserAudio); return true; } + + /// + /// Tries to insert an item into any empty slot. + /// + /// The entity that has the item slots. + /// The item to be inserted. + /// The entity performing the interaction. + /// + /// If true, will exclude the user when playing sound. Does nothing client-side. + /// Useful for predicted interactions + /// + /// False if failed to insert item + public bool TryInsertEmpty(Entity ent, EntityUid item, EntityUid? user, bool excludeUserAudio = false) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + var slots = new List(); + foreach (var slot in ent.Comp.Slots.Values) + { + if (slot.ContainerSlot?.ContainedEntity != null) + continue; + + if (CanInsert(ent, item, user, slot)) + slots.Add(slot); + } + + if (slots.Count == 0) + return false; + + if (user != null && _handsSystem.IsHolding(user.Value, item)) + { + if (!_handsSystem.TryDrop(user.Value, item)) + return false; + } + + slots.Sort(SortEmpty); + + foreach (var slot in slots) + { + if (TryInsert(ent, slot, item, user, excludeUserAudio: excludeUserAudio)) + return true; + } + + return false; + } + + private static int SortEmpty(ItemSlot a, ItemSlot b) + { + var aEnt = a.ContainerSlot?.ContainedEntity; + var bEnt = b.ContainerSlot?.ContainedEntity; + if (aEnt == null && bEnt == null) + return a.Priority.CompareTo(b.Priority); + + if (aEnt == null) + return -1; + + return 1; + } #endregion #region Eject From 3b29f2b41cf69929e15fb458b0ef40edb2931f8e Mon Sep 17 00:00:00 2001 From: blueDev2 <89804215+blueDev2@users.noreply.github.com> Date: Thu, 23 May 2024 16:27:45 -0400 Subject: [PATCH 049/235] Add Fruit Tag to watermelon slice (#28226) --- .../Prototypes/Entities/Objects/Consumable/Food/produce.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 1bd895829bd..888e4e4e353 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -1647,6 +1647,9 @@ reagents: - ReagentId: JuiceWatermelon Quantity: 4 + - type: Tag + tags: + - Fruit - type: entity name: grapes From f739bebe197129a657a9e819ce72ed75d9aafebd Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 23 May 2024 20:28:53 +0000 Subject: [PATCH 050/235] 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 a8e3a5f155b..9ae89dad4a3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: mryikes - changes: - - message: New Nuke Detonation Song "Clearly Nuclear". - type: Add - id: 6113 - time: '2024-03-09T01:44:54.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25765 - author: Plykiya changes: - message: Pre-filled syringes start in inject mode now. @@ -3875,3 +3868,10 @@ id: 6612 time: '2024-05-22T11:16:20.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/26582 +- author: blueDev2 + changes: + - message: Watermelon slices are now considered fruit + type: Fix + id: 6613 + time: '2024-05-23T20:27:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28226 From 471726b6e52a80f9b2c4ac54e7d861ba0e25447a Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Thu, 23 May 2024 18:23:55 -0700 Subject: [PATCH 051/235] Fix whatever the fuck is going on in storage system slightly (#28236) * Fix whatever the fuck is going on in storage system slightly * Fix inverted check * h * Add silent bool * Silent --- .../BypassInteractionChecksComponent.cs | 6 ++ .../Inventory/InventorySystem.Slots.cs | 2 +- Content.Shared/Lock/LockSystem.cs | 9 +++ .../EntitySystems/SharedStorageSystem.cs | 62 ++++++++++--------- Content.Shared/Storage/StorageComponent.cs | 4 +- .../Entities/Mobs/Player/admin_ghost.yml | 1 + 6 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 Content.Shared/Interaction/Components/BypassInteractionChecksComponent.cs diff --git a/Content.Shared/Interaction/Components/BypassInteractionChecksComponent.cs b/Content.Shared/Interaction/Components/BypassInteractionChecksComponent.cs new file mode 100644 index 00000000000..ca0ff963151 --- /dev/null +++ b/Content.Shared/Interaction/Components/BypassInteractionChecksComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Interaction.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BypassInteractionChecksComponent : Component; diff --git a/Content.Shared/Inventory/InventorySystem.Slots.cs b/Content.Shared/Inventory/InventorySystem.Slots.cs index 19831278b0a..228b788722e 100644 --- a/Content.Shared/Inventory/InventorySystem.Slots.cs +++ b/Content.Shared/Inventory/InventorySystem.Slots.cs @@ -75,7 +75,7 @@ private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev, EntitySessionEv if (TryGetSlotEntity(uid, ev.Slot, out var entityUid) && TryComp(entityUid, out var storageComponent)) { - _storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent); + _storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent, false); } } diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 54f5d801ea0..4115358d9d7 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Storage; using Content.Shared.Storage.Components; using Content.Shared.Verbs; using Content.Shared.Wires; @@ -42,11 +43,13 @@ public override void Initialize() SubscribeLocalEvent(OnEmagged); SubscribeLocalEvent(OnDoAfterLock); SubscribeLocalEvent(OnDoAfterUnlock); + SubscribeLocalEvent(OnStorageInteractAttempt); SubscribeLocalEvent(OnLockToggleAttempt); SubscribeLocalEvent(OnAttemptChangePanel); SubscribeLocalEvent(OnUnanchorAttempt); } + private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) { _appearanceSystem.SetData(uid, LockVisuals.Locked, lockComp.Locked); @@ -293,6 +296,12 @@ private void OnDoAfterUnlock(EntityUid uid, LockComponent component, UnlockDoAft TryUnlock(uid, args.User, skipDoAfter: true); } + private void OnStorageInteractAttempt(Entity ent, ref StorageInteractAttemptEvent args) + { + if (ent.Comp.Locked) + args.Cancelled = true; + } + private void OnLockToggleAttempt(Entity ent, ref LockToggleAttemptEvent args) { if (args.Cancelled) diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index ed9a7855999..34eae72c07d 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -2,17 +2,15 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; -using Content.Shared.Administration; -using Content.Shared.Administration.Managers; using Content.Shared.Containers.ItemSlots; using Content.Shared.Destructible; using Content.Shared.DoAfter; -using Content.Shared.Ghost; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Implants.Components; using Content.Shared.Input; using Content.Shared.Interaction; +using Content.Shared.Interaction.Components; using Content.Shared.Inventory; using Content.Shared.Item; using Content.Shared.Lock; @@ -41,7 +39,6 @@ public abstract class SharedStorageSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] protected readonly IRobustRandom Random = default!; - [Dependency] private readonly ISharedAdminManager _admin = default!; [Dependency] protected readonly ActionBlockerSystem ActionBlocker = default!; [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; @@ -250,17 +247,8 @@ private void OnBoundUIClosed(EntityUid uid, StorageComponent storageComp, BoundU private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent args) { - var silent = false; - if (!args.CanAccess || !args.CanInteract || TryComp(uid, out var lockComponent) && lockComponent.Locked) - { - // we allow admins to open the storage anyways - if (!_admin.HasAdminFlag(args.User, AdminFlags.Admin)) - return; - - silent = true; - } - - silent |= HasComp(args.User); + if (!CanInteract(args.User, (uid, component), args.CanAccess && args.CanInteract)) + return; // Does this player currently have the storage UI open? var uiOpen = _ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User); @@ -275,7 +263,7 @@ private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent< } else { - OpenStorageUI(uid, args.User, component, silent); + OpenStorageUI(uid, args.User, component); } } }; @@ -299,13 +287,16 @@ private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent< /// Opens the storage UI for an entity /// /// The entity to open the UI for - public void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = false) + public void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = true) { if (!Resolve(uid, ref storageComp, false)) return; // prevent spamming bag open / honkerton honk sound silent |= TryComp(uid, out var useDelay) && UseDelay.IsDelayed((uid, useDelay)); + if (!CanInteract(entity, (uid, storageComp), silent: silent)) + return; + if (!silent) { if (!_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key)) @@ -327,7 +318,7 @@ private void AddTransferVerbs(EntityUid uid, StorageComponent component, GetVerb var entities = component.Container.ContainedEntities; - if (entities.Count == 0 || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) + if (entities.Count == 0 || !CanInteract(args.User, (uid, component))) return; // if the target is storage, add a verb to transfer storage. @@ -338,7 +329,7 @@ private void AddTransferVerbs(EntityUid uid, StorageComponent component, GetVerb { Text = Loc.GetString("storage-component-transfer-verb"), IconEntity = GetNetEntity(args.Using), - Act = () => TransferEntities(uid, args.Target, args.User, component, lockComponent, targetStorage, targetLock) + Act = () => TransferEntities(uid, args.Target, args.User, component, null, targetStorage, targetLock) }; args.Verbs.Add(verb); @@ -351,7 +342,7 @@ private void AddTransferVerbs(EntityUid uid, StorageComponent component, GetVerb /// true if inserted, false otherwise private void OnInteractUsing(EntityUid uid, StorageComponent storageComp, InteractUsingEvent args) { - if (args.Handled || !storageComp.ClickInsert || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) + if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert, false)) return; if (HasComp(uid)) @@ -369,7 +360,7 @@ private void OnInteractUsing(EntityUid uid, StorageComponent storageComp, Intera /// private void OnActivate(EntityUid uid, StorageComponent storageComp, ActivateInWorldEvent args) { - if (args.Handled || TryComp(uid, out var lockComponent) && lockComponent.Locked) + if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert)) return; // Toggle @@ -379,7 +370,7 @@ private void OnActivate(EntityUid uid, StorageComponent storageComp, ActivateInW } else { - OpenStorageUI(uid, args.User, storageComp); + OpenStorageUI(uid, args.User, storageComp, false); } args.Handled = true; @@ -393,7 +384,7 @@ private void OnImplantActivate(EntityUid uid, StorageComponent storageComp, Open if (args.Handled) return; - OpenStorageUI(uid, args.Performer, storageComp); + OpenStorageUI(uid, args.Performer, storageComp, false); args.Handled = true; } @@ -1407,7 +1398,7 @@ public ItemSizePrototype GetMaxItemSize(Entity uid) } /// - /// Checks if a storage's UI is open by anyone when locked, and closes it unless they're an admin. + /// Checks if a storage's UI is open by anyone when locked, and closes it. /// private void OnLockToggled(EntityUid uid, StorageComponent component, ref LockToggledEvent args) { @@ -1417,11 +1408,8 @@ private void OnLockToggled(EntityUid uid, StorageComponent component, ref LockTo // Gets everyone looking at the UI foreach (var actor in _ui.GetActors(uid, StorageComponent.StorageUiKey.Key).ToList()) { - if (_admin.HasAdminFlag(actor, AdminFlags.Admin)) - continue; - - // And closes it unless they're an admin - _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor); + if (!CanInteract(actor, (uid, component))) + _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor); } } @@ -1461,7 +1449,7 @@ private void HandleToggleSlotUI(ICommonSession? session, string slot) if (!_ui.IsUiOpen(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt)) { - OpenStorageUI(storageEnt.Value, playerEnt); + OpenStorageUI(storageEnt.Value, playerEnt, silent: false); } else { @@ -1476,6 +1464,20 @@ protected void ClearCantFillReasons() #endif } + private bool CanInteract(EntityUid user, Entity storage, bool canInteract = true, bool silent = true) + { + if (HasComp(user)) + return true; + + if (!canInteract) + return false; + + var ev = new StorageInteractAttemptEvent(silent); + RaiseLocalEvent(storage, ref ev); + + return !ev.Cancelled; + } + /// /// Plays a clientside pickup animation for the specified uid. /// diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index ef682dd4f94..2860f8dacfe 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -5,7 +5,6 @@ using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Map; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -228,6 +227,9 @@ public AnimateInsertingEntitiesEvent(NetEntity storage, List storedEn } } + [ByRefEvent] + public record struct StorageInteractAttemptEvent(bool Silent, bool Cancelled = false); + [NetSerializable] [Serializable] public enum StorageVisuals : byte diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index aebaeb2c7f4..3a281e5bfc1 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -96,6 +96,7 @@ - type: InventorySlots - type: Loadout prototypes: [ MobAghostGear ] + - type: BypassInteractionChecks - type: entity id: ActionAGhostShowSolar From 9adf1ed4026143919ccbadc9796c08a7724396a4 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Thu, 23 May 2024 22:43:04 -0400 Subject: [PATCH 052/235] Remove AlertType and AlertCategory (#27933) --- Content.Client/Alerts/ClientAlertsSystem.cs | 2 +- Content.Client/Revenant/RevenantSystem.cs | 3 +- .../Systems/Alerts/AlertsUIController.cs | 3 +- .../Systems/Alerts/Widgets/AlertsUI.xaml.cs | 13 +- .../Components/Mobs/AlertsComponentTests.cs | 15 +- .../Tests/Gravity/WeightlessStatusTests.cs | 8 +- .../Abilities/Mime/MimePowersComponent.cs | 8 + .../Abilities/Mime/MimePowersSystem.cs | 11 +- Content.Server/Alert/Commands/ClearAlert.cs | 4 +- Content.Server/Alert/Commands/ShowAlert.cs | 4 +- .../Atmos/Components/BarotraumaComponent.cs | 10 + .../Atmos/Components/FlammableComponent.cs | 5 + .../Atmos/EntitySystems/BarotraumaSystem.cs | 10 +- .../Atmos/EntitySystems/FlammableSystem.cs | 4 +- .../Body/Components/BloodstreamComponent.cs | 4 + .../Body/Components/InternalsComponent.cs | 6 + .../Body/Components/LungComponent.cs | 4 +- .../Body/Systems/BloodstreamSystem.cs | 4 +- .../Body/Systems/InternalsSystem.cs | 14 +- .../Chemistry/ReagentEffects/AdjustAlert.cs | 8 +- Content.Server/Clothing/MagbootsSystem.cs | 4 +- .../Ensnaring/EnsnareableSystem.Ensnaring.cs | 4 +- .../Ninja/Systems/SpaceNinjaSystem.cs | 11 +- .../Revenant/EntitySystems/RevenantSystem.cs | 2 +- .../Shuttles/Systems/ShuttleConsoleSystem.cs | 4 +- Content.Server/Silicons/Borgs/BorgSystem.cs | 18 +- .../Components/TemperatureComponent.cs | 8 + .../Temperature/Systems/TemperatureSystem.cs | 14 +- Content.Shared/Alert/AlertCategory.cs | 20 -- .../Alert/AlertCategoryPrototype.cs | 14 ++ Content.Shared/Alert/AlertKey.cs | 12 +- Content.Shared/Alert/AlertOrderPrototype.cs | 35 +-- Content.Shared/Alert/AlertPrototype.cs | 206 +++++++++--------- Content.Shared/Alert/AlertState.cs | 3 +- Content.Shared/Alert/AlertType.cs | 59 ----- Content.Shared/Alert/AlertsSystem.cs | 26 +-- Content.Shared/Alert/ClickAlertEvent.cs | 7 +- .../Buckle/Components/StrapComponent.cs | 3 +- .../Buckle/SharedBuckleSystem.Buckle.cs | 5 +- Content.Shared/Clothing/MagbootsComponent.cs | 4 + .../Pacification/PacificationSystem.cs | 5 +- .../Pacification/PacifiedComponent.cs | 4 + .../Cuffs/Components/CuffableComponent.cs | 5 + Content.Shared/Cuffs/SharedCuffableSystem.cs | 4 +- .../Damage/Components/StaminaComponent.cs | 5 + .../Damage/Systems/StaminaSystem.cs | 8 +- .../Components/EnsnareableComponent.cs | 5 + Content.Shared/Gravity/SharedGravitySystem.cs | 15 +- .../Mobs/Components/MobThresholdsComponent.cs | 28 ++- .../Mobs/Systems/MobThresholdSystem.cs | 2 +- .../Pulling/Components/PullableComponent.cs | 5 + .../Pulling/Components/PullerComponent.cs | 7 +- .../Movement/Pulling/Systems/PullingSystem.cs | 8 +- .../Ninja/Components/SpaceNinjaComponent.cs | 4 + .../Nutrition/Components/HungerComponent.cs | 14 +- .../Nutrition/Components/ThirstComponent.cs | 12 +- .../Nutrition/EntitySystems/HungerSystem.cs | 4 +- .../Nutrition/EntitySystems/ThirstSystem.cs | 2 +- .../Revenant/Components/RevenantComponent.cs | 4 + .../Shuttles/Components/PilotComponent.cs | 5 + .../Borgs/Components/BorgChassisComponent.cs | 10 +- .../StatusEffect/StatusEffectPrototype.cs | 2 +- .../StatusEffect/StatusEffectsSystem.cs | 2 +- .../Weapons/Reflect/ReflectSystem.cs | 7 +- .../Shared/Alert/AlertManagerTests.cs | 17 +- .../Shared/Alert/AlertOrderPrototypeTests.cs | 26 +-- .../Shared/Alert/AlertPrototypeTests.cs | 6 +- .../Alert/ServerAlertsComponentTests.cs | 17 +- Resources/Prototypes/Alerts/categories.yml | 35 +++ 69 files changed, 482 insertions(+), 385 deletions(-) delete mode 100644 Content.Shared/Alert/AlertCategory.cs create mode 100644 Content.Shared/Alert/AlertCategoryPrototype.cs delete mode 100644 Content.Shared/Alert/AlertType.cs create mode 100644 Resources/Prototypes/Alerts/categories.yml diff --git a/Content.Client/Alerts/ClientAlertsSystem.cs b/Content.Client/Alerts/ClientAlertsSystem.cs index 223bf7876ac..359c8957f9d 100644 --- a/Content.Client/Alerts/ClientAlertsSystem.cs +++ b/Content.Client/Alerts/ClientAlertsSystem.cs @@ -91,7 +91,7 @@ private void OnPlayerDetached(EntityUid uid, AlertsComponent component, LocalPla ClearAlerts?.Invoke(this, EventArgs.Empty); } - public void AlertClicked(AlertType alertType) + public void AlertClicked(ProtoId alertType) { RaiseNetworkEvent(new ClickAlertEvent(alertType)); } diff --git a/Content.Client/Revenant/RevenantSystem.cs b/Content.Client/Revenant/RevenantSystem.cs index 49d29d8a5f4..e050fe35aa2 100644 --- a/Content.Client/Revenant/RevenantSystem.cs +++ b/Content.Client/Revenant/RevenantSystem.cs @@ -1,5 +1,4 @@ using Content.Client.Alerts; -using Content.Shared.Alert; using Content.Shared.Revenant; using Content.Shared.Revenant.Components; using Robust.Client.GameObjects; @@ -42,7 +41,7 @@ private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref private void OnUpdateAlert(Entity ent, ref UpdateAlertSpriteEvent args) { - if (args.Alert.AlertType != AlertType.Essence) + if (args.Alert.ID != ent.Comp.EssenceAlert) return; var sprite = args.SpriteViewEnt.Comp; diff --git a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs index 3b85972a9b2..5c195120389 100644 --- a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs +++ b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs @@ -7,6 +7,7 @@ using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Systems.Alerts; @@ -43,7 +44,7 @@ private void OnScreenLoad() SyncAlerts(); } - private void OnAlertPressed(object? sender, AlertType e) + private void OnAlertPressed(object? sender, ProtoId e) { _alertsSystem?.AlertClicked(e); } diff --git a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs index a1a494c47b3..d6a79a81c46 100644 --- a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs +++ b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs @@ -4,6 +4,7 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Input; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Systems.Alerts.Widgets; @@ -21,8 +22,10 @@ public AlertsUI() RobustXamlLoader.Load(this); } - public void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype, - IReadOnlyDictionary alertStates) + public void SyncControls(AlertsSystem alertsSystem, + AlertOrderPrototype? alertOrderPrototype, + IReadOnlyDictionary alertStates) { // remove any controls with keys no longer present if (SyncRemoveControls(alertStates)) @@ -46,7 +49,7 @@ public void ClearAllControls() _alertControls.Clear(); } - public event EventHandler? AlertPressed; + public event EventHandler>? AlertPressed; private bool SyncRemoveControls(IReadOnlyDictionary alertStates) { @@ -88,7 +91,7 @@ private void SyncUpdateControls(AlertsSystem alertsSystem, AlertOrderPrototype? } if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) && - existingAlertControl.Alert.AlertType == newAlert.AlertType) + existingAlertControl.Alert.ID == newAlert.ID) { // key is the same, simply update the existing control severity / cooldown existingAlertControl.SetSeverity(alertState.Severity); @@ -155,6 +158,6 @@ private void AlertControlPressed(BaseButton.ButtonEventArgs args) if (args.Event.Function != EngineKeyFunctions.UIClick) return; - AlertPressed?.Invoke(this, control.Alert.AlertType); + AlertPressed?.Invoke(this, control.Alert.ID); } } diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs index 1da77ac5589..ef4e6326cda 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs @@ -5,7 +5,6 @@ using Robust.Client.UserInterface; using Robust.Server.Player; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs { @@ -45,8 +44,8 @@ await server.WaitAssertion(() => Assert.That(alerts, Is.Not.Null); var alertCount = alerts.Count; - alertsSystem.ShowAlert(playerUid, AlertType.Debug1); - alertsSystem.ShowAlert(playerUid, AlertType.Debug2); + alertsSystem.ShowAlert(playerUid, "Debug1"); + alertsSystem.ShowAlert(playerUid, "Debug2"); Assert.That(alerts, Has.Count.EqualTo(alertCount + 2)); }); @@ -87,14 +86,14 @@ static AlertsUI FindAlertsUI(Control control) // we should be seeing 3 alerts - our health, and the 2 debug alerts, in a specific order. Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(3)); var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c); - var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray(); - var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug1, AlertType.Debug2 }; + var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray(); + var expectedIDs = new[] { "HumanHealth", "Debug1", "Debug2" }; Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); }); await server.WaitAssertion(() => { - alertsSystem.ClearAlert(playerUid, AlertType.Debug1); + alertsSystem.ClearAlert(playerUid, "Debug1"); }); await pair.RunTicksSync(5); @@ -104,8 +103,8 @@ await client.WaitAssertion(() => // we should be seeing 2 alerts now because one was cleared Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2)); var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c); - var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray(); - var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug2 }; + var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray(); + var expectedIDs = new[] { "HumanHealth", "Debug2" }; Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); }); diff --git a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs index 0ad198d6ef2..74641126aee 100644 --- a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs +++ b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs @@ -1,5 +1,6 @@ using Content.Server.Gravity; using Content.Shared.Alert; +using Content.Shared.Gravity; using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Gravity @@ -38,6 +39,7 @@ public async Task WeightlessStatusTest() var entityManager = server.ResolveDependency(); var alertsSystem = server.ResolveDependency().GetEntitySystem(); + var weightlessAlert = SharedGravitySystem.WeightlessAlert; EntityUid human = default; @@ -56,7 +58,7 @@ await server.WaitAssertion(() => await server.WaitAssertion(() => { // No gravity without a gravity generator - Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless)); + Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert)); generatorUid = entityManager.SpawnEntity("WeightlessGravityGeneratorDummy", entityManager.GetComponent(human).Coordinates); }); @@ -66,7 +68,7 @@ await server.WaitAssertion(() => await server.WaitAssertion(() => { - Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless), Is.False); + Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert), Is.False); // This should kill gravity entityManager.DeleteEntity(generatorUid); @@ -76,7 +78,7 @@ await server.WaitAssertion(() => await server.WaitAssertion(() => { - Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless)); + Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert)); }); await pair.RunTicksSync(10); diff --git a/Content.Server/Abilities/Mime/MimePowersComponent.cs b/Content.Server/Abilities/Mime/MimePowersComponent.cs index fd4fc2c2af9..d56644ed191 100644 --- a/Content.Server/Abilities/Mime/MimePowersComponent.cs +++ b/Content.Server/Abilities/Mime/MimePowersComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Alert; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -47,5 +48,12 @@ public sealed partial class MimePowersComponent : Component /// [DataField("vowCooldown")] public TimeSpan VowCooldown = TimeSpan.FromMinutes(5); + + [DataField] + public ProtoId VowAlert = "VowOfSilence"; + + [DataField] + public ProtoId VowBrokenAlert = "VowBroken"; + } } diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index c1d2643d6fa..2c3e59175d0 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Popups; -using Content.Server.Speech.Muting; using Content.Shared.Actions; using Content.Shared.Actions.Events; using Content.Shared.Alert; @@ -56,7 +55,7 @@ public override void Update(float frameTime) private void OnComponentInit(EntityUid uid, MimePowersComponent component, ComponentInit args) { EnsureComp(uid); - _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); + _alertsSystem.ShowAlert(uid, component.VowAlert); _actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid); //Nyano - Summary: Add Psionic Ability to Mime. if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) @@ -123,8 +122,8 @@ public void BreakVow(EntityUid uid, MimePowersComponent? mimePowers = null) mimePowers.VowBroken = true; mimePowers.VowRepentTime = _timing.CurTime + mimePowers.VowCooldown; RemComp(uid); - _alertsSystem.ClearAlert(uid, AlertType.VowOfSilence); - _alertsSystem.ShowAlert(uid, AlertType.VowBroken); + _alertsSystem.ClearAlert(uid, mimePowers.VowAlert); + _alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert); _actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallActionEntity); } @@ -146,8 +145,8 @@ public void RetakeVow(EntityUid uid, MimePowersComponent? mimePowers = null) mimePowers.ReadyToRepent = false; mimePowers.VowBroken = false; AddComp(uid); - _alertsSystem.ClearAlert(uid, AlertType.VowBroken); - _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); + _alertsSystem.ClearAlert(uid, mimePowers.VowAlert); + _alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert); _actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid); } } diff --git a/Content.Server/Alert/Commands/ClearAlert.cs b/Content.Server/Alert/Commands/ClearAlert.cs index 73a6ca52c70..2e317de7547 100644 --- a/Content.Server/Alert/Commands/ClearAlert.cs +++ b/Content.Server/Alert/Commands/ClearAlert.cs @@ -40,13 +40,13 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) var alertType = args[0]; var alertsSystem = _e.System(); - if (!alertsSystem.TryGet(Enum.Parse(alertType), out var alert)) + if (!alertsSystem.TryGet(alertType, out var alert)) { shell.WriteLine("unrecognized alertType " + alertType); return; } - alertsSystem.ClearAlert(attachedEntity, alert.AlertType); + alertsSystem.ClearAlert(attachedEntity, alert.ID); } } } diff --git a/Content.Server/Alert/Commands/ShowAlert.cs b/Content.Server/Alert/Commands/ShowAlert.cs index f37ab23f2fa..cae24ff3360 100644 --- a/Content.Server/Alert/Commands/ShowAlert.cs +++ b/Content.Server/Alert/Commands/ShowAlert.cs @@ -41,7 +41,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) var alertType = args[0]; var severity = args[1]; var alertsSystem = _e.System(); - if (!alertsSystem.TryGet(Enum.Parse(alertType), out var alert)) + if (!alertsSystem.TryGet(alertType, out var alert)) { shell.WriteLine("unrecognized alertType " + alertType); return; @@ -53,7 +53,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) } short? severity1 = sevint == -1 ? null : sevint; - alertsSystem.ShowAlert(attachedEntity, alert.AlertType, severity1, null); + alertsSystem.ShowAlert(attachedEntity, alert.ID, severity1, null); } } } diff --git a/Content.Server/Atmos/Components/BarotraumaComponent.cs b/Content.Server/Atmos/Components/BarotraumaComponent.cs index 4e29699872e..d261c5ab030 100644 --- a/Content.Server/Atmos/Components/BarotraumaComponent.cs +++ b/Content.Server/Atmos/Components/BarotraumaComponent.cs @@ -1,5 +1,7 @@ +using Content.Shared.Alert; using Content.Shared.Damage; using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; namespace Content.Server.Atmos.Components { @@ -46,5 +48,13 @@ public sealed partial class BarotraumaComponent : Component [ViewVariables(VVAccess.ReadWrite)] public bool HasImmunity = false; + [DataField] + public ProtoId HighPressureAlert = "HighPressure"; + + [DataField] + public ProtoId LowPressureAlert = "LowPressure"; + + [DataField] + public ProtoId PressureAlertCategory = "Pressure"; } } diff --git a/Content.Server/Atmos/Components/FlammableComponent.cs b/Content.Server/Atmos/Components/FlammableComponent.cs index e00f5efbdc5..9ae99a15136 100644 --- a/Content.Server/Atmos/Components/FlammableComponent.cs +++ b/Content.Server/Atmos/Components/FlammableComponent.cs @@ -1,5 +1,7 @@ +using Content.Shared.Alert; using Content.Shared.Damage; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Prototypes; namespace Content.Server.Atmos.Components { @@ -77,5 +79,8 @@ public sealed partial class FlammableComponent : Component /// [DataField, ViewVariables(VVAccess.ReadWrite)] public float FirestackFade = -0.1f; + + [DataField] + public ProtoId FireAlert = "Fire"; } } diff --git a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs index 98a5ffa70a8..ec508790ba8 100644 --- a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs +++ b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs @@ -245,7 +245,7 @@ public override void Update(float frameTime) _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking low pressure damage"); } - _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 2); + _alertsSystem.ShowAlert(uid, barotrauma.LowPressureAlert, 2); } else if (pressure >= Atmospherics.HazardHighPressure) { @@ -260,7 +260,7 @@ public override void Update(float frameTime) _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking high pressure damage"); } - _alertsSystem.ShowAlert(uid, AlertType.HighPressure, 2); + _alertsSystem.ShowAlert(uid, barotrauma.HighPressureAlert, 2); } else { @@ -275,13 +275,13 @@ public override void Update(float frameTime) switch (pressure) { case <= Atmospherics.WarningLowPressure: - _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 1); + _alertsSystem.ShowAlert(uid, barotrauma.LowPressureAlert, 1); break; case >= Atmospherics.WarningHighPressure: - _alertsSystem.ShowAlert(uid, AlertType.HighPressure, 1); + _alertsSystem.ShowAlert(uid, barotrauma.HighPressureAlert, 1); break; default: - _alertsSystem.ClearAlertCategory(uid, AlertCategory.Pressure); + _alertsSystem.ClearAlertCategory(uid, barotrauma.PressureAlertCategory); break; } } diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index 4a8cbbdc884..e8721920dd8 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -415,11 +415,11 @@ public override void Update(float frameTime) if (!flammable.OnFire) { - _alertsSystem.ClearAlert(uid, AlertType.Fire); + _alertsSystem.ClearAlert(uid, flammable.FireAlert); continue; } - _alertsSystem.ShowAlert(uid, AlertType.Fire); + _alertsSystem.ShowAlert(uid, flammable.FireAlert); if (flammable.FireStacks > 0) { diff --git a/Content.Server/Body/Components/BloodstreamComponent.cs b/Content.Server/Body/Components/BloodstreamComponent.cs index 1d8aa9ffd3d..a6d2afab219 100644 --- a/Content.Server/Body/Components/BloodstreamComponent.cs +++ b/Content.Server/Body/Components/BloodstreamComponent.cs @@ -1,5 +1,6 @@ using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; +using Content.Shared.Alert; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; @@ -171,5 +172,8 @@ public sealed partial class BloodstreamComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] public TimeSpan StatusTime; + + [DataField] + public ProtoId BleedingAlert = "Bleed"; } } diff --git a/Content.Server/Body/Components/InternalsComponent.cs b/Content.Server/Body/Components/InternalsComponent.cs index 18caab8dcf0..098f1789218 100644 --- a/Content.Server/Body/Components/InternalsComponent.cs +++ b/Content.Server/Body/Components/InternalsComponent.cs @@ -1,3 +1,6 @@ +using Content.Shared.Alert; +using Robust.Shared.Prototypes; + namespace Content.Server.Body.Components { /// @@ -18,5 +21,8 @@ public sealed partial class InternalsComponent : Component [ViewVariables(VVAccess.ReadWrite)] [DataField] public TimeSpan Delay = TimeSpan.FromSeconds(3); + + [DataField] + public ProtoId InternalsAlert = "Internals"; } } diff --git a/Content.Server/Body/Components/LungComponent.cs b/Content.Server/Body/Components/LungComponent.cs index 46600b30207..72af4d9e63a 100644 --- a/Content.Server/Body/Components/LungComponent.cs +++ b/Content.Server/Body/Components/LungComponent.cs @@ -1,8 +1,8 @@ -using Content.Server.Atmos; using Content.Server.Body.Systems; using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Chemistry.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Body.Components; @@ -33,5 +33,5 @@ public sealed partial class LungComponent : Component /// The type of gas this lung needs. Used only for the breathing alerts, not actual metabolism. /// [DataField] - public AlertType Alert = AlertType.LowOxygen; + public ProtoId Alert = "LowOxygen"; } diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index ddeb154e790..f961307fc6a 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -392,11 +392,11 @@ public bool TryModifyBleedAmount(EntityUid uid, float amount, BloodstreamCompone component.BleedAmount = Math.Clamp(component.BleedAmount, 0, component.MaxBleedAmount); if (component.BleedAmount == 0) - _alertsSystem.ClearAlert(uid, AlertType.Bleed); + _alertsSystem.ClearAlert(uid, component.BleedingAlert); else { var severity = (short) Math.Clamp(Math.Round(component.BleedAmount, MidpointRounding.ToZero), 0, 10); - _alertsSystem.ShowAlert(uid, AlertType.Bleed, severity); + _alertsSystem.ShowAlert(uid, component.BleedingAlert, severity); } return true; diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index 8afd1c767f6..c1e1de2baad 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -144,12 +144,12 @@ private void OnDoAfter(Entity ent, ref InternalsDoAfterEvent private void OnInternalsStartup(Entity ent, ref ComponentStartup args) { - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } private void OnInternalsShutdown(Entity ent, ref ComponentShutdown args) { - _alerts.ClearAlert(ent, AlertType.Internals); + _alerts.ClearAlert(ent, ent.Comp.InternalsAlert); } private void OnInhaleLocation(Entity ent, ref InhaleLocationEvent args) @@ -159,7 +159,7 @@ private void OnInhaleLocation(Entity ent, ref InhaleLocation var gasTank = Comp(ent.Comp.GasTankEntity!.Value); args.Gas = _gasTank.RemoveAirVolume((ent.Comp.GasTankEntity.Value, gasTank), Atmospherics.BreathVolume); // TODO: Should listen to gas tank updates instead I guess? - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } } public void DisconnectBreathTool(Entity ent) @@ -173,7 +173,7 @@ public void DisconnectBreathTool(Entity ent) DisconnectTank(ent); } - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } public void ConnectBreathTool(Entity ent, EntityUid toolEntity) @@ -184,7 +184,7 @@ public void ConnectBreathTool(Entity ent, EntityUid toolEnti } ent.Comp.BreathToolEntity = toolEntity; - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } public void DisconnectTank(InternalsComponent? component) @@ -196,7 +196,7 @@ public void DisconnectTank(InternalsComponent? component) _gasTank.DisconnectFromInternals((component.GasTankEntity.Value, tank)); component.GasTankEntity = null; - _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component)); + _alerts.ShowAlert(component.Owner, component.InternalsAlert, GetSeverity(component)); } public bool TryConnectTank(Entity ent, EntityUid tankEntity) @@ -208,7 +208,7 @@ public bool TryConnectTank(Entity ent, EntityUid tankEntity) _gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank)); ent.Comp.GasTankEntity = tankEntity; - _alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent)); + _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); return true; } diff --git a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs index 8d475570ad0..40858176bd1 100644 --- a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs +++ b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs @@ -10,8 +10,8 @@ public sealed partial class AdjustAlert : ReagentEffect /// /// The specific Alert that will be adjusted /// - [DataField("alertType", required: true)] - public AlertType Type; + [DataField(required: true)] + public ProtoId AlertType; /// /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately. @@ -42,7 +42,7 @@ public override void Effect(ReagentEffectArgs args) if (Clear && Time <= 0) { - alertSys.ClearAlert(args.SolutionEntity, Type); + alertSys.ClearAlert(args.SolutionEntity, AlertType); } else { @@ -52,7 +52,7 @@ public override void Effect(ReagentEffectArgs args) if ((ShowCooldown || Clear) && Time > 0) cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time)); - alertSys.ShowAlert(args.SolutionEntity, Type, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown); + alertSys.ShowAlert(args.SolutionEntity, AlertType, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown); } } diff --git a/Content.Server/Clothing/MagbootsSystem.cs b/Content.Server/Clothing/MagbootsSystem.cs index f12558389e3..3838ad168d1 100644 --- a/Content.Server/Clothing/MagbootsSystem.cs +++ b/Content.Server/Clothing/MagbootsSystem.cs @@ -29,11 +29,11 @@ protected override void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bo if (state) { - _alerts.ShowAlert(parent, AlertType.Magboots); + _alerts.ShowAlert(parent, component.MagbootsAlert); } else { - _alerts.ClearAlert(parent, AlertType.Magboots); + _alerts.ClearAlert(parent, component.MagbootsAlert); } } diff --git a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs index 105a8f9720d..51d3242ce4f 100644 --- a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs +++ b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs @@ -163,8 +163,8 @@ public void ForceFree(EntityUid ensnare, EnsnaringComponent component) public void UpdateAlert(EntityUid target, EnsnareableComponent component) { if (!component.IsEnsnared) - _alerts.ClearAlert(target, AlertType.Ensnared); + _alerts.ClearAlert(target, component.EnsnaredAlert); else - _alerts.ShowAlert(target, AlertType.Ensnared); + _alerts.ShowAlert(target, component.EnsnaredAlert); } } diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs index 1dfaf4f3393..0c1e88653fa 100644 --- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs +++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs @@ -102,20 +102,23 @@ private int Download(EntityUid uid, List ids) /// public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null) { - if (!Resolve(uid, ref comp, false) || comp.Deleted || comp.Suit == null) + if (!Resolve(uid, ref comp, false)) + return; + + if (comp.Deleted || comp.Suit == null) { - _alerts.ClearAlert(uid, AlertType.SuitPower); + _alerts.ClearAlert(uid, comp.SuitPowerAlert); return; } if (GetNinjaBattery(uid, out _, out var battery)) { var severity = ContentHelpers.RoundToLevels(MathF.Max(0f, battery.CurrentCharge), battery.MaxCharge, 8); - _alerts.ShowAlert(uid, AlertType.SuitPower, (short) severity); + _alerts.ShowAlert(uid, comp.SuitPowerAlert, (short) severity); } else { - _alerts.ClearAlert(uid, AlertType.SuitPower); + _alerts.ClearAlert(uid, comp.SuitPowerAlert); } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index 86be70c41fe..c390432f3a1 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -141,7 +141,7 @@ public bool ChangeEssenceAmount(EntityUid uid, FixedPoint2 amount, RevenantCompo if (TryComp(uid, out var store)) _store.UpdateUserInterface(uid, uid, store); - _alerts.ShowAlert(uid, AlertType.Essence); + _alerts.ShowAlert(uid, component.EssenceAlert); if (component.Essence <= 0) { diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index 2b5769881d2..7a19fd13b2e 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -317,7 +317,7 @@ public void AddPilot(EntityUid uid, EntityUid entity, ShuttleConsoleComponent co component.SubscribedPilots.Add(entity); - _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle); + _alertsSystem.ShowAlert(entity, pilotComponent.PilotingAlert); pilotComponent.Console = uid; ActionBlockerSystem.UpdateCanMove(entity); @@ -339,7 +339,7 @@ public void RemovePilot(EntityUid pilotUid, PilotComponent pilotComponent) if (!helm.SubscribedPilots.Remove(pilotUid)) return; - _alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle); + _alertsSystem.ClearAlert(pilotUid, pilotComponent.PilotingAlert); _popup.PopupEntity(Loc.GetString("shuttle-pilot-end"), pilotUid, pilotUid); diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index ceab044d4c1..97adfd00eb4 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -84,7 +84,7 @@ public override void Initialize() private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args) { - UpdateBatteryAlert(uid); + UpdateBatteryAlert((uid, component)); _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); } @@ -183,7 +183,7 @@ private void OnMobStateChanged(EntityUid uid, BorgChassisComponent component, Mo private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, PowerCellChangedEvent args) { - UpdateBatteryAlert(uid); + UpdateBatteryAlert((uid, component)); if (!TryComp(uid, out var draw)) return; @@ -256,12 +256,12 @@ private void OnBrainPointAttempt(EntityUid uid, BorgBrainComponent component, Po args.Cancel(); } - private void UpdateBatteryAlert(EntityUid uid, PowerCellSlotComponent? slotComponent = null) + private void UpdateBatteryAlert(Entity ent, PowerCellSlotComponent? slotComponent = null) { - if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery, slotComponent)) + if (!_powerCell.TryGetBatteryFromSlot(ent, out var battery, slotComponent)) { - _alerts.ClearAlert(uid, AlertType.BorgBattery); - _alerts.ShowAlert(uid, AlertType.BorgBatteryNone); + _alerts.ClearAlert(ent, ent.Comp.BatteryAlert); + _alerts.ShowAlert(ent, ent.Comp.NoBatteryAlert); return; } @@ -269,13 +269,13 @@ private void UpdateBatteryAlert(EntityUid uid, PowerCellSlotComponent? slotCompo // we make sure 0 only shows if they have absolutely no battery. // also account for floating point imprecision - if (chargePercent == 0 && _powerCell.HasDrawCharge(uid, cell: slotComponent)) + if (chargePercent == 0 && _powerCell.HasDrawCharge(ent, cell: slotComponent)) { chargePercent = 1; } - _alerts.ClearAlert(uid, AlertType.BorgBatteryNone); - _alerts.ShowAlert(uid, AlertType.BorgBattery, chargePercent); + _alerts.ClearAlert(ent, ent.Comp.NoBatteryAlert); + _alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent); } /// diff --git a/Content.Server/Temperature/Components/TemperatureComponent.cs b/Content.Server/Temperature/Components/TemperatureComponent.cs index ec00a570f96..3bfa12f2693 100644 --- a/Content.Server/Temperature/Components/TemperatureComponent.cs +++ b/Content.Server/Temperature/Components/TemperatureComponent.cs @@ -1,7 +1,9 @@ using Content.Server.Temperature.Systems; +using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Damage; using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; namespace Content.Server.Temperature.Components; @@ -78,4 +80,10 @@ public float HeatCapacity /// [DataField] public bool TakingDamage = false; + + [DataField] + public ProtoId HotAlert = "Hot"; + + [DataField] + public ProtoId ColdAlert = "Cold"; } diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.cs b/Content.Server/Temperature/Systems/TemperatureSystem.cs index 6c9e99e5f3b..23c8cb6783f 100644 --- a/Content.Server/Temperature/Systems/TemperatureSystem.cs +++ b/Content.Server/Temperature/Systems/TemperatureSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Rejuvenate; using Content.Shared.Temperature; using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Temperature.Systems; @@ -33,6 +34,9 @@ public sealed class TemperatureSystem : EntitySystem private float _accumulatedFrametime; + [ValidatePrototypeId] + public const string TemperatureAlertCategory = "Temperature"; + public override void Initialize() { SubscribeLocalEvent(EnqueueDamage); @@ -180,13 +184,13 @@ private void OnRejuvenate(EntityUid uid, TemperatureComponent comp, RejuvenateEv private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureChangeEvent args) { - AlertType type; + ProtoId type; float threshold; float idealTemp; if (!TryComp(uid, out var temperature)) { - _alerts.ClearAlertCategory(uid, AlertCategory.Temperature); + _alerts.ClearAlertCategory(uid, TemperatureAlertCategory); return; } @@ -203,12 +207,12 @@ private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureCha if (args.CurrentTemperature <= idealTemp) { - type = AlertType.Cold; + type = temperature.ColdAlert; threshold = temperature.ColdDamageThreshold; } else { - type = AlertType.Hot; + type = temperature.HotAlert; threshold = temperature.HeatDamageThreshold; } @@ -230,7 +234,7 @@ private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureCha break; case > 0.66f: - _alerts.ClearAlertCategory(uid, AlertCategory.Temperature); + _alerts.ClearAlertCategory(uid, TemperatureAlertCategory); break; } } diff --git a/Content.Shared/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs deleted file mode 100644 index 7450f585a4e..00000000000 --- a/Content.Shared/Alert/AlertCategory.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Content.Shared.Alert; - -/// -/// Every category of alert. Corresponds to category field in alert prototypes defined in YML -/// -public enum AlertCategory -{ - Pressure, - Temperature, - Breathing, - Buckled, - Health, - Internals, - Stamina, - Piloting, - Hunger, - Thirst, - Toxins, - Battery -} diff --git a/Content.Shared/Alert/AlertCategoryPrototype.cs b/Content.Shared/Alert/AlertCategoryPrototype.cs new file mode 100644 index 00000000000..7c7d0475214 --- /dev/null +++ b/Content.Shared/Alert/AlertCategoryPrototype.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Alert; + +/// +/// This is a prototype for a category for marking alerts as mutually exclusive. +/// +[Prototype] +public sealed partial class AlertCategoryPrototype : IPrototype +{ + /// + [IdDataField] + public string ID { get; } = default!; +} diff --git a/Content.Shared/Alert/AlertKey.cs b/Content.Shared/Alert/AlertKey.cs index c784af4cd48..c5c3a7643ec 100644 --- a/Content.Shared/Alert/AlertKey.cs +++ b/Content.Shared/Alert/AlertKey.cs @@ -1,5 +1,5 @@ -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Alert; @@ -11,13 +11,13 @@ namespace Content.Shared.Alert; [Serializable, NetSerializable] public struct AlertKey { - public AlertType? AlertType { get; private set; } = Alert.AlertType.Error; - public readonly AlertCategory? AlertCategory; + public ProtoId? AlertType { get; private set; } = default!; + public readonly ProtoId? AlertCategory; /// NOTE: if the alert has a category you must pass the category for this to work /// properly as a key. I.e. if the alert has a category and you pass only the alert type, and you /// compare this to another AlertKey that has both the category and the same alert type, it will not consider them equal. - public AlertKey(AlertType? alertType, AlertCategory? alertCategory) + public AlertKey(ProtoId? alertType, ProtoId? alertCategory) { AlertCategory = alertCategory; AlertType = alertType; @@ -49,7 +49,7 @@ public override int GetHashCode() /// alert category, must not be null /// An alert key for the provided alert category. This must only be used for /// queries and never storage, as it is lacking an alert type. - public static AlertKey ForCategory(AlertCategory category) + public static AlertKey ForCategory(ProtoId category) { return new(null, category); } diff --git a/Content.Shared/Alert/AlertOrderPrototype.cs b/Content.Shared/Alert/AlertOrderPrototype.cs index 8279d592b4b..af4241a27e7 100644 --- a/Content.Shared/Alert/AlertOrderPrototype.cs +++ b/Content.Shared/Alert/AlertOrderPrototype.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Alert /// /// Defines the order of alerts so they show up in a consistent order. /// - [Prototype("alertOrder")] + [Prototype] [DataDefinition] public sealed partial class AlertOrderPrototype : IPrototype, IComparer { @@ -15,7 +15,7 @@ public sealed partial class AlertOrderPrototype : IPrototype, IComparer(alert)] = i++; + _typeToIdx[alert] = i++; break; case "category": - _categoryToIdx[Enum.Parse(alert)] = i++; + _categoryToIdx[alert] = i++; break; default: throw new ArgumentException(); @@ -58,17 +58,17 @@ public sealed partial class AlertOrderPrototype : IPrototype, IComparer _typeToIdx = new(); - private readonly Dictionary _categoryToIdx = new(); + private readonly Dictionary, int> _typeToIdx = new(); + private readonly Dictionary, int> _categoryToIdx = new(); private int GetOrderIndex(AlertPrototype alert) { - if (_typeToIdx.TryGetValue(alert.AlertType, out var idx)) + if (_typeToIdx.TryGetValue(alert.ID, out var idx)) { return idx; } if (alert.Category != null && - _categoryToIdx.TryGetValue((AlertCategory) alert.Category, out idx)) + _categoryToIdx.TryGetValue(alert.Category.Value, out idx)) { return idx; } @@ -78,20 +78,25 @@ private int GetOrderIndex(AlertPrototype alert) public int Compare(AlertPrototype? x, AlertPrototype? y) { - if ((x == null) && (y == null)) return 0; - if (x == null) return 1; - if (y == null) return -1; + if (x == null && y == null) + return 0; + if (x == null) + return 1; + if (y == null) + return -1; var idx = GetOrderIndex(x); var idy = GetOrderIndex(y); if (idx == -1 && idy == -1) { // break ties by type value // Must cast to int to avoid integer overflow when subtracting (enum's unsigned) - return (int)x.AlertType - (int)y.AlertType; + return string.Compare(x.ID, y.ID, StringComparison.InvariantCulture); } - if (idx == -1) return 1; - if (idy == -1) return -1; + if (idx == -1) + return 1; + if (idy == -1) + return -1; var result = idx - idy; // not strictly necessary (we don't care about ones that go at the same index) // but it makes the sort stable @@ -99,7 +104,7 @@ public int Compare(AlertPrototype? x, AlertPrototype? y) { // break ties by type value // Must cast to int to avoid integer overflow when subtracting (enum's unsigned) - return (int)x.AlertType - (int)y.AlertType; + return string.Compare(x.ID, y.ID, StringComparison.InvariantCulture); } return result; diff --git a/Content.Shared/Alert/AlertPrototype.cs b/Content.Shared/Alert/AlertPrototype.cs index 248cc00ba40..f53da27c0de 100644 --- a/Content.Shared/Alert/AlertPrototype.cs +++ b/Content.Shared/Alert/AlertPrototype.cs @@ -1,120 +1,116 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; -namespace Content.Shared.Alert +namespace Content.Shared.Alert; + +/// +/// An alert popup with associated icon, tooltip, and other data. +/// +[Prototype] +public sealed partial class AlertPrototype : IPrototype { /// - /// An alert popup with associated icon, tooltip, and other data. + /// Type of alert, no 2 alert prototypes should have the same one. /// - [Prototype("alert")] - public sealed partial class AlertPrototype : IPrototype - { - [ViewVariables] - string IPrototype.ID => AlertType.ToString(); - - /// - /// Type of alert, no 2 alert prototypes should have the same one. - /// - [IdDataField] - public AlertType AlertType { get; private set; } - - /// - /// List of icons to use for this alert. Each entry corresponds to a different severity level, starting from the - /// minimum and incrementing upwards. If severities are not supported, the first entry is used. - /// - [DataField("icons", required: true)] - public List Icons = new(); - - /// - /// An entity used for displaying the in the UI control. - /// - [DataField] - public EntProtoId AlertViewEntity = "AlertSpriteView"; - - /// - /// Name to show in tooltip window. Accepts formatting. - /// - [DataField("name")] - public string Name { get; private set; } = ""; - - /// - /// Description to show in tooltip window. Accepts formatting. - /// - [DataField("description")] - public string Description { get; private set; } = ""; - - /// - /// Category the alert belongs to. Only one alert of a given category - /// can be shown at a time. If one is shown while another is already being shown, - /// it will be replaced. This can be useful for categories of alerts which should naturally - /// replace each other and are mutually exclusive, for example lowpressure / highpressure, - /// hot / cold. If left unspecified, the alert will not replace or be replaced by any other alerts. - /// - [DataField("category")] - public AlertCategory? Category { get; private set; } - - /// - /// Key which is unique w.r.t category semantics (alerts with same category have equal keys, - /// alerts with no category have different keys). - /// - public AlertKey AlertKey => new(AlertType, Category); - - /// - /// -1 (no effect) unless MaxSeverity is specified. Defaults to 1. Minimum severity level supported by this state. - /// - public short MinSeverity => MaxSeverity == -1 ? (short) -1 : _minSeverity; - - [DataField("minSeverity")] private short _minSeverity = 1; - - /// - /// Maximum severity level supported by this state. -1 (default) indicates - /// no severity levels are supported by the state. - /// - [DataField("maxSeverity")] - public short MaxSeverity = -1; - - /// - /// Indicates whether this state support severity levels - /// - public bool SupportsSeverity => MaxSeverity != -1; - - /// - /// Defines what to do when the alert is clicked. - /// This will always be null on clientside. - /// - [DataField("onClick", serverOnly: true)] - public IAlertClick? OnClick { get; private set; } - - /// severity level, if supported by this alert - /// the icon path to the texture for the provided severity level - public SpriteSpecifier GetIcon(short? severity = null) - { - var minIcons = SupportsSeverity - ? MaxSeverity - MinSeverity - : 1; + [IdDataField] + public string ID { get; private set; } = default!; - if (Icons.Count < minIcons) - throw new InvalidOperationException($"Insufficient number of icons given for alert {AlertType}"); + /// + /// List of icons to use for this alert. Each entry corresponds to a different severity level, starting from the + /// minimum and incrementing upwards. If severities are not supported, the first entry is used. + /// + [DataField(required: true)] + public List Icons = new(); - if (!SupportsSeverity) - return Icons[0]; + /// + /// An entity used for displaying the in the UI control. + /// + [DataField] + public EntProtoId AlertViewEntity = "AlertSpriteView"; - if (severity == null) - { - throw new ArgumentException($"No severity specified but this alert ({AlertKey}) has severity.", nameof(severity)); - } + /// + /// Name to show in tooltip window. Accepts formatting. + /// + [DataField] + public string Name { get; private set; } = string.Empty; - if (severity < MinSeverity) - { - throw new ArgumentOutOfRangeException(nameof(severity), $"Severity below minimum severity in {AlertKey}."); - } + /// + /// Description to show in tooltip window. Accepts formatting. + /// + [DataField] + public string Description { get; private set; } = string.Empty; - if (severity > MaxSeverity) - { - throw new ArgumentOutOfRangeException(nameof(severity), $"Severity above maximum severity in {AlertKey}."); - } + /// + /// Category the alert belongs to. Only one alert of a given category + /// can be shown at a time. If one is shown while another is already being shown, + /// it will be replaced. This can be useful for categories of alerts which should naturally + /// replace each other and are mutually exclusive, for example lowpressure / highpressure, + /// hot / cold. If left unspecified, the alert will not replace or be replaced by any other alerts. + /// + [DataField] + public ProtoId? Category { get; private set; } + + /// + /// Key which is unique w.r.t category semantics (alerts with same category have equal keys, + /// alerts with no category have different keys). + /// + public AlertKey AlertKey => new(ID, Category); - return Icons[severity.Value - _minSeverity]; + /// + /// -1 (no effect) unless MaxSeverity is specified. Defaults to 1. Minimum severity level supported by this state. + /// + public short MinSeverity => MaxSeverity == -1 ? (short) -1 : _minSeverity; + + [DataField("minSeverity")] private short _minSeverity = 1; + + /// + /// Maximum severity level supported by this state. -1 (default) indicates + /// no severity levels are supported by the state. + /// + [DataField] + public short MaxSeverity = -1; + + /// + /// Indicates whether this state support severity levels + /// + public bool SupportsSeverity => MaxSeverity != -1; + + /// + /// Defines what to do when the alert is clicked. + /// This will always be null on clientside. + /// + [DataField(serverOnly: true)] + public IAlertClick? OnClick { get; private set; } + + /// severity level, if supported by this alert + /// the icon path to the texture for the provided severity level + public SpriteSpecifier GetIcon(short? severity = null) + { + var minIcons = SupportsSeverity + ? MaxSeverity - MinSeverity + : 1; + + if (Icons.Count < minIcons) + throw new InvalidOperationException($"Insufficient number of icons given for alert {ID}"); + + if (!SupportsSeverity) + return Icons[0]; + + if (severity == null) + { + throw new ArgumentException($"No severity specified but this alert ({AlertKey}) has severity.", nameof(severity)); + } + + if (severity < MinSeverity) + { + throw new ArgumentOutOfRangeException(nameof(severity), $"Severity below minimum severity in {AlertKey}."); } + + if (severity > MaxSeverity) + { + throw new ArgumentOutOfRangeException(nameof(severity), $"Severity above maximum severity in {AlertKey}."); + } + + return Icons[severity.Value - _minSeverity]; } } diff --git a/Content.Shared/Alert/AlertState.cs b/Content.Shared/Alert/AlertState.cs index effd9522036..d6309f6b426 100644 --- a/Content.Shared/Alert/AlertState.cs +++ b/Content.Shared/Alert/AlertState.cs @@ -1,3 +1,4 @@ +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Alert; @@ -9,5 +10,5 @@ public struct AlertState public (TimeSpan, TimeSpan)? Cooldown; public bool AutoRemove; public bool ShowCooldown; - public AlertType Type; + public ProtoId Type; } diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs deleted file mode 100644 index b989b8d4b6f..00000000000 --- a/Content.Shared/Alert/AlertType.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Content.Shared.Alert -{ - /// - /// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML - /// NOTE: Using byte for a compact encoding when sending this in messages, can upgrade - /// to ushort - /// - public enum AlertType : byte - { - Error, - LowOxygen, - LowNitrogen, - LowPressure, - HighPressure, - Fire, - Cold, - Hot, - Weightless, - Stun, - Handcuffed, - Ensnared, - Buckled, - HumanCrit, - HumanDead, - HumanHealth, - BorgBattery, - BorgBatteryNone, - PilotingShuttle, - Peckish, - Starving, - Thirsty, - Parched, - Stamina, - Pulled, - Pulling, - Magboots, - Internals, - Toxins, - Muted, - VowOfSilence, - VowBroken, - Essence, - Corporeal, - Bleed, - Pacified, - Debug1, - Debug2, - Debug3, - Debug4, - Debug5, - Debug6, - SuitPower, - BorgHealth, - BorgCrit, - BorgDead, - Deflecting - } - -} diff --git a/Content.Shared/Alert/AlertsSystem.cs b/Content.Shared/Alert/AlertsSystem.cs index 5b888e30c4c..83c6fcd0dd7 100644 --- a/Content.Shared/Alert/AlertsSystem.cs +++ b/Content.Shared/Alert/AlertsSystem.cs @@ -11,7 +11,7 @@ public abstract class AlertsSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IGameTiming _timing = default!; - private FrozenDictionary _typeToAlert = default!; + private FrozenDictionary, AlertPrototype> _typeToAlert = default!; public IReadOnlyDictionary? GetActiveAlerts(EntityUid euid) { @@ -20,23 +20,23 @@ public abstract class AlertsSystem : EntitySystem : null; } - public short GetSeverityRange(AlertType alertType) + public short GetSeverityRange(ProtoId alertType) { var minSeverity = _typeToAlert[alertType].MinSeverity; return (short)MathF.Max(minSeverity,_typeToAlert[alertType].MaxSeverity - minSeverity); } - public short GetMaxSeverity(AlertType alertType) + public short GetMaxSeverity(ProtoId alertType) { return _typeToAlert[alertType].MaxSeverity; } - public short GetMinSeverity(AlertType alertType) + public short GetMinSeverity(ProtoId alertType) { return _typeToAlert[alertType].MinSeverity; } - public bool IsShowingAlert(EntityUid euid, AlertType alertType) + public bool IsShowingAlert(EntityUid euid, ProtoId alertType) { if (!EntityManager.TryGetComponent(euid, out AlertsComponent? alertsComponent)) return false; @@ -51,7 +51,7 @@ public bool IsShowingAlert(EntityUid euid, AlertType alertType) } /// true iff an alert of the indicated alert category is currently showing - public bool IsShowingAlertCategory(EntityUid euid, AlertCategory alertCategory) + public bool IsShowingAlertCategory(EntityUid euid, ProtoId alertCategory) { return EntityManager.TryGetComponent(euid, out AlertsComponent? alertsComponent) && alertsComponent.Alerts.ContainsKey(AlertKey.ForCategory(alertCategory)); @@ -78,7 +78,7 @@ public bool TryGetAlertState(EntityUid euid, AlertKey key, out AlertState alertS /// be erased if there is currently a cooldown for the alert) /// if true, the alert will be removed at the end of the cooldown /// if true, the cooldown will be visibly shown over the alert icon - public void ShowAlert(EntityUid euid, AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null, bool autoRemove = false, bool showCooldown = true ) + public void ShowAlert(EntityUid euid, ProtoId alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null, bool autoRemove = false, bool showCooldown = true ) { // This should be handled as part of networking. if (_timing.ApplyingState) @@ -131,7 +131,7 @@ public void ShowAlert(EntityUid euid, AlertType alertType, short? severity = nul /// /// Clear the alert with the given category, if one is currently showing. /// - public void ClearAlertCategory(EntityUid euid, AlertCategory category) + public void ClearAlertCategory(EntityUid euid, ProtoId category) { if(!TryComp(euid, out AlertsComponent? alertsComponent)) return; @@ -150,7 +150,7 @@ public void ClearAlertCategory(EntityUid euid, AlertCategory category) /// /// Clear the alert of the given type if it is currently showing. /// - public void ClearAlert(EntityUid euid, AlertType alertType) + public void ClearAlert(EntityUid euid, ProtoId alertType) { if (_timing.ApplyingState) return; @@ -286,13 +286,13 @@ private void HandlePrototypesReloaded(PrototypesReloadedEventArgs obj) protected virtual void LoadPrototypes() { - var dict = new Dictionary(); + var dict = new Dictionary, AlertPrototype>(); foreach (var alert in _prototypeManager.EnumeratePrototypes()) { - if (!dict.TryAdd(alert.AlertType, alert)) + if (!dict.TryAdd(alert.ID, alert)) { Log.Error("Found alert with duplicate alertType {0} - all alerts must have" + - " a unique alertType, this one will be skipped", alert.AlertType); + " a unique alertType, this one will be skipped", alert.ID); } } @@ -303,7 +303,7 @@ protected virtual void LoadPrototypes() /// Tries to get the alert of the indicated type /// /// true if found - public bool TryGet(AlertType alertType, [NotNullWhen(true)] out AlertPrototype? alert) + public bool TryGet(ProtoId alertType, [NotNullWhen(true)] out AlertPrototype? alert) { return _typeToAlert.TryGetValue(alertType, out alert); } diff --git a/Content.Shared/Alert/ClickAlertEvent.cs b/Content.Shared/Alert/ClickAlertEvent.cs index fe7ca97e4c2..43dd086b562 100644 --- a/Content.Shared/Alert/ClickAlertEvent.cs +++ b/Content.Shared/Alert/ClickAlertEvent.cs @@ -1,4 +1,5 @@ -using Robust.Shared.Serialization; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Alert; @@ -8,9 +9,9 @@ namespace Content.Shared.Alert; [Serializable, NetSerializable] public sealed class ClickAlertEvent : EntityEventArgs { - public readonly AlertType Type; + public readonly ProtoId Type; - public ClickAlertEvent(AlertType alertType) + public ClickAlertEvent(ProtoId alertType) { Type = alertType; } diff --git a/Content.Shared/Buckle/Components/StrapComponent.cs b/Content.Shared/Buckle/Components/StrapComponent.cs index 72c92ebf84b..9a19cea0c9a 100644 --- a/Content.Shared/Buckle/Components/StrapComponent.cs +++ b/Content.Shared/Buckle/Components/StrapComponent.cs @@ -3,6 +3,7 @@ using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Buckle.Components; @@ -115,7 +116,7 @@ public sealed partial class StrapComponent : Component /// [DataField] [ViewVariables(VVAccess.ReadWrite)] - public AlertType BuckledAlertType = AlertType.Buckled; + public ProtoId BuckledAlertType = "Buckled"; /// /// The sum of the sizes of all the buckled entities in this strap diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 0d67473ffee..4e94c6134b4 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -40,6 +40,9 @@ private void InitializeBuckle() SubscribeLocalEvent(OnBuckleUpdateCanMove); } + [ValidatePrototypeId] + public const string BuckledAlertCategory = "Buckled"; + private void OnBuckleComponentStartup(EntityUid uid, BuckleComponent component, ComponentStartup args) { UpdateBuckleStatus(uid, component); @@ -165,7 +168,7 @@ private void UpdateBuckleStatus(EntityUid uid, BuckleComponent buckleComp, Strap } else { - _alerts.ClearAlertCategory(uid, AlertCategory.Buckled); + _alerts.ClearAlertCategory(uid, BuckledAlertCategory); } } diff --git a/Content.Shared/Clothing/MagbootsComponent.cs b/Content.Shared/Clothing/MagbootsComponent.cs index 0d0d59f89f5..0d074ff38b6 100644 --- a/Content.Shared/Clothing/MagbootsComponent.cs +++ b/Content.Shared/Clothing/MagbootsComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Alert; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -16,4 +17,7 @@ public sealed partial class MagbootsComponent : Component [DataField("on"), AutoNetworkedField] public bool On; + + [DataField] + public ProtoId MagbootsAlert = "Magboots"; } diff --git a/Content.Shared/CombatMode/Pacification/PacificationSystem.cs b/Content.Shared/CombatMode/Pacification/PacificationSystem.cs index 6d94c087af6..a927e1a6970 100644 --- a/Content.Shared/CombatMode/Pacification/PacificationSystem.cs +++ b/Content.Shared/CombatMode/Pacification/PacificationSystem.cs @@ -7,7 +7,6 @@ using Content.Shared.Popups; using Content.Shared.Throwing; using Content.Shared.Weapons.Ranged.Events; -using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Timing; namespace Content.Shared.CombatMode.Pacification; @@ -109,7 +108,7 @@ private void OnStartup(EntityUid uid, PacifiedComponent component, ComponentStar _actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, false); } - _alertsSystem.ShowAlert(uid, AlertType.Pacified); + _alertsSystem.ShowAlert(uid, component.PacifiedAlert); } private void OnShutdown(EntityUid uid, PacifiedComponent component, ComponentShutdown args) @@ -121,7 +120,7 @@ private void OnShutdown(EntityUid uid, PacifiedComponent component, ComponentShu _combatSystem.SetCanDisarm(uid, true, combatMode); _actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, true); - _alertsSystem.ClearAlert(uid, AlertType.Pacified); + _alertsSystem.ClearAlert(uid, component.PacifiedAlert); } private void OnBeforeThrow(Entity ent, ref BeforeThrowEvent args) diff --git a/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs b/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs index 464ef778851..96081e5dc67 100644 --- a/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs +++ b/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs @@ -1,4 +1,6 @@ +using Content.Shared.Alert; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Shared.CombatMode.Pacification; @@ -42,4 +44,6 @@ public sealed partial class PacifiedComponent : Component [DataField] public EntityUid? LastAttackedEntity = null; + [DataField] + public ProtoId PacifiedAlert = "Pacified"; } diff --git a/Content.Shared/Cuffs/Components/CuffableComponent.cs b/Content.Shared/Cuffs/Components/CuffableComponent.cs index 5da6fa41a5f..4ddfe1b53ee 100644 --- a/Content.Shared/Cuffs/Components/CuffableComponent.cs +++ b/Content.Shared/Cuffs/Components/CuffableComponent.cs @@ -1,6 +1,8 @@ +using Content.Shared.Alert; using Content.Shared.Damage; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -39,6 +41,9 @@ public sealed partial class CuffableComponent : Component /// [DataField("canStillInteract"), ViewVariables(VVAccess.ReadWrite)] public bool CanStillInteract = true; + + [DataField] + public ProtoId CuffedAlert = "Handcuffed"; } [Serializable, NetSerializable] diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 0077f5a358e..f0f9a949839 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -172,9 +172,9 @@ public void UpdateCuffState(EntityUid uid, CuffableComponent component) _actionBlocker.UpdateCanMove(uid); if (component.CanStillInteract) - _alerts.ClearAlert(uid, AlertType.Handcuffed); + _alerts.ClearAlert(uid, component.CuffedAlert); else - _alerts.ShowAlert(uid, AlertType.Handcuffed); + _alerts.ShowAlert(uid, component.CuffedAlert); var ev = new CuffedStateChangeEvent(); RaiseLocalEvent(uid, ref ev); diff --git a/Content.Shared/Damage/Components/StaminaComponent.cs b/Content.Shared/Damage/Components/StaminaComponent.cs index 5a2fba49701..14c3f6d9f53 100644 --- a/Content.Shared/Damage/Components/StaminaComponent.cs +++ b/Content.Shared/Damage/Components/StaminaComponent.cs @@ -1,4 +1,6 @@ +using Content.Shared.Alert; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Damage.Components; @@ -51,4 +53,7 @@ public sealed partial class StaminaComponent : Component [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] [AutoPausedField] public TimeSpan NextUpdate = TimeSpan.Zero; + + [DataField] + public ProtoId StaminaAlert = "Stamina"; } diff --git a/Content.Shared/Damage/Systems/StaminaSystem.cs b/Content.Shared/Damage/Systems/StaminaSystem.cs index 840b2e04311..1f9a7f1dd84 100644 --- a/Content.Shared/Damage/Systems/StaminaSystem.cs +++ b/Content.Shared/Damage/Systems/StaminaSystem.cs @@ -79,8 +79,7 @@ private void OnShutdown(EntityUid uid, StaminaComponent component, ComponentShut { RemCompDeferred(uid); } - - SetStaminaAlert(uid); + _alerts.ClearAlert(uid, component.StaminaAlert); } private void OnStartup(EntityUid uid, StaminaComponent component, ComponentStartup args) @@ -204,13 +203,10 @@ private void OnCollide(EntityUid uid, StaminaDamageOnCollideComponent component, private void SetStaminaAlert(EntityUid uid, StaminaComponent? component = null) { if (!Resolve(uid, ref component, false) || component.Deleted) - { - _alerts.ClearAlert(uid, AlertType.Stamina); return; - } var severity = ContentHelpers.RoundToLevels(MathF.Max(0f, component.CritThreshold - component.StaminaDamage), component.CritThreshold, 7); - _alerts.ShowAlert(uid, AlertType.Stamina, (short) severity); + _alerts.ShowAlert(uid, component.StaminaAlert, (short) severity); } /// diff --git a/Content.Shared/Ensnaring/Components/EnsnareableComponent.cs b/Content.Shared/Ensnaring/Components/EnsnareableComponent.cs index 553f6df1c77..2536fac4edc 100644 --- a/Content.Shared/Ensnaring/Components/EnsnareableComponent.cs +++ b/Content.Shared/Ensnaring/Components/EnsnareableComponent.cs @@ -1,5 +1,7 @@ +using Content.Shared.Alert; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Ensnaring.Components; @@ -40,6 +42,9 @@ public sealed partial class EnsnareableComponent : Component [DataField("state")] public string? State; + + [DataField] + public ProtoId EnsnaredAlert = "Ensnared"; } [Serializable, NetSerializable] diff --git a/Content.Shared/Gravity/SharedGravitySystem.cs b/Content.Shared/Gravity/SharedGravitySystem.cs index 100d2ee74fb..df13be51fd4 100644 --- a/Content.Shared/Gravity/SharedGravitySystem.cs +++ b/Content.Shared/Gravity/SharedGravitySystem.cs @@ -17,6 +17,9 @@ public abstract partial class SharedGravitySystem : EntitySystem [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly InventorySystem _inventory = default!; + [ValidatePrototypeId] + public const string WeightlessAlert = "Weightless"; + public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, TransformComponent? xform = null) { Resolve(uid, ref body, false); @@ -93,11 +96,11 @@ private void OnGravityChange(ref GravityChangedEvent ev) if (!ev.HasGravity) { - _alerts.ShowAlert(uid, AlertType.Weightless); + _alerts.ShowAlert(uid, WeightlessAlert); } else { - _alerts.ClearAlert(uid, AlertType.Weightless); + _alerts.ClearAlert(uid, WeightlessAlert); } } } @@ -106,11 +109,11 @@ private void OnAlertsSync(AlertSyncEvent ev) { if (IsWeightless(ev.Euid)) { - _alerts.ShowAlert(ev.Euid, AlertType.Weightless); + _alerts.ShowAlert(ev.Euid, WeightlessAlert); } else { - _alerts.ClearAlert(ev.Euid, AlertType.Weightless); + _alerts.ClearAlert(ev.Euid, WeightlessAlert); } } @@ -118,11 +121,11 @@ private void OnAlertsParentChange(EntityUid uid, AlertsComponent component, ref { if (IsWeightless(uid)) { - _alerts.ShowAlert(uid, AlertType.Weightless); + _alerts.ShowAlert(uid, WeightlessAlert); } else { - _alerts.ClearAlert(uid, AlertType.Weightless); + _alerts.ClearAlert(uid, WeightlessAlert); } } diff --git a/Content.Shared/Mobs/Components/MobThresholdsComponent.cs b/Content.Shared/Mobs/Components/MobThresholdsComponent.cs index e97d3672a21..0e37cf9b10e 100644 --- a/Content.Shared/Mobs/Components/MobThresholdsComponent.cs +++ b/Content.Shared/Mobs/Components/MobThresholdsComponent.cs @@ -2,6 +2,7 @@ using Content.Shared.FixedPoint; using Content.Shared.Mobs.Systems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Mobs.Components; @@ -24,13 +25,16 @@ public sealed partial class MobThresholdsComponent : Component /// Used for alternate health alerts (silicons, for example) /// [DataField("stateAlertDict")] - public Dictionary StateAlertDict = new() + public Dictionary> StateAlertDict = new() { - {MobState.Alive, AlertType.HumanHealth}, - {MobState.Critical, AlertType.HumanCrit}, - {MobState.Dead, AlertType.HumanDead}, + {MobState.Alive, "HumanHealth"}, + {MobState.Critical, "HumanCrit"}, + {MobState.Dead, "HumanDead"}, }; + [DataField] + public ProtoId HealthAlertCategory = "Health"; + /// /// Whether or not this entity should display damage overlays (robots don't feel pain, black out etc.) /// @@ -53,19 +57,19 @@ public sealed class MobThresholdsComponentState : ComponentState public MobState CurrentThresholdState; - public Dictionary StateAlertDict = new() - { - {MobState.Alive, AlertType.HumanHealth}, - {MobState.Critical, AlertType.HumanCrit}, - {MobState.Dead, AlertType.HumanDead}, - }; + public Dictionary> StateAlertDict; public bool ShowOverlays; public bool AllowRevives; - public MobThresholdsComponentState(Dictionary unsortedThresholds, bool triggersAlerts, MobState currentThresholdState, - Dictionary stateAlertDict, bool showOverlays, bool allowRevives) + public MobThresholdsComponentState(Dictionary unsortedThresholds, + bool triggersAlerts, + MobState currentThresholdState, + Dictionary> stateAlertDict, + bool showOverlays, + bool allowRevives) { UnsortedThresholds = unsortedThresholds; TriggersAlerts = triggersAlerts; diff --git a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs index 59d9fb4c239..b11de9eac56 100644 --- a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs +++ b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs @@ -431,7 +431,7 @@ private void MobThresholdStartup(EntityUid target, MobThresholdsComponent thresh private void MobThresholdShutdown(EntityUid target, MobThresholdsComponent component, ComponentShutdown args) { if (component.TriggersAlerts) - _alerts.ClearAlertCategory(target, AlertCategory.Health); + _alerts.ClearAlertCategory(target, component.HealthAlertCategory); } private void OnUpdateMobState(EntityUid target, MobThresholdsComponent component, ref UpdateMobStateEvent args) diff --git a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs index db889e7e3bd..100cd9d6d3e 100644 --- a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs @@ -1,4 +1,6 @@ +using Content.Shared.Alert; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Shared.Movement.Pulling.Components; @@ -36,4 +38,7 @@ public sealed partial class PullableComponent : Component [Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)] [AutoNetworkedField, DataField] public bool PrevFixedRotation; + + [DataField] + public ProtoId PulledAlert = "Pulled"; } diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs index 1fc9b731bd5..061ec13ed2a 100644 --- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs @@ -1,5 +1,7 @@ -using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Alert; +using Content.Shared.Movement.Pulling.Systems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Movement.Pulling.Components; @@ -38,4 +40,7 @@ public sealed partial class PullerComponent : Component /// [DataField] public bool NeedsHands = true; + + [DataField] + public ProtoId PullingAlert = "Pulling"; } diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 81b2fee5695..2781c495298 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -223,7 +223,7 @@ private void StopPulling(EntityUid pullableUid, PullableComponent pullableComp) if (TryComp(oldPuller, out var pullerComp)) { var pullerUid = oldPuller.Value; - _alertsSystem.ClearAlert(pullerUid, AlertType.Pulling); + _alertsSystem.ClearAlert(pullerUid, pullerComp.PullingAlert); pullerComp.Pulling = null; Dirty(oldPuller.Value, pullerComp); @@ -237,7 +237,7 @@ private void StopPulling(EntityUid pullableUid, PullableComponent pullableComp) } - _alertsSystem.ClearAlert(pullableUid, AlertType.Pulled); + _alertsSystem.ClearAlert(pullableUid, pullableComp.PulledAlert); } public bool IsPulled(EntityUid uid, PullableComponent? component = null) @@ -460,8 +460,8 @@ public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, // Messaging var message = new PullStartedMessage(pullerUid, pullableUid); - _alertsSystem.ShowAlert(pullerUid, AlertType.Pulling); - _alertsSystem.ShowAlert(pullableUid, AlertType.Pulled); + _alertsSystem.ShowAlert(pullerUid, pullerComp.PullingAlert); + _alertsSystem.ShowAlert(pullableUid, pullableComp.PulledAlert); RaiseLocalEvent(pullerUid, message); RaiseLocalEvent(pullableUid, message); diff --git a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs index 0f3bff265cb..91c816df5c9 100644 --- a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs +++ b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Alert; using Content.Shared.Ninja.Systems; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -53,4 +54,7 @@ public sealed partial class SpaceNinjaComponent : Component /// [DataField] public EntProtoId SpiderChargeObjective = "SpiderChargeObjective"; + + [DataField] + public ProtoId SuitPowerAlert = "SuitPower"; } diff --git a/Content.Shared/Nutrition/Components/HungerComponent.cs b/Content.Shared/Nutrition/Components/HungerComponent.cs index 9ac82ba283c..79d895ddae6 100644 --- a/Content.Shared/Nutrition/Components/HungerComponent.cs +++ b/Content.Shared/Nutrition/Components/HungerComponent.cs @@ -2,6 +2,7 @@ using Content.Shared.Damage; using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; @@ -65,15 +66,18 @@ public sealed partial class HungerComponent : Component /// /// A dictionary relating hunger thresholds to corresponding alerts. /// - [DataField("hungerThresholdAlerts", customTypeSerializer: typeof(DictionarySerializer))] + [DataField("hungerThresholdAlerts")] [AutoNetworkedField] - public Dictionary HungerThresholdAlerts = new() + public Dictionary> HungerThresholdAlerts = new() { - { HungerThreshold.Peckish, AlertType.Peckish }, - { HungerThreshold.Starving, AlertType.Starving }, - { HungerThreshold.Dead, AlertType.Starving } + { HungerThreshold.Peckish, "Peckish" }, + { HungerThreshold.Starving, "Starving" }, + { HungerThreshold.Dead, "Starving" } }; + [DataField] + public ProtoId HungerAlertCategory = "Hunger"; + /// /// A dictionary relating HungerThreshold to how much they modify . /// diff --git a/Content.Shared/Nutrition/Components/ThirstComponent.cs b/Content.Shared/Nutrition/Components/ThirstComponent.cs index 731346401fd..f3ac881361f 100644 --- a/Content.Shared/Nutrition/Components/ThirstComponent.cs +++ b/Content.Shared/Nutrition/Components/ThirstComponent.cs @@ -1,6 +1,7 @@ using Content.Shared.Alert; using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Nutrition.Components; @@ -56,11 +57,14 @@ public sealed partial class ThirstComponent : Component {ThirstThreshold.Dead, 0.0f}, }; - public static readonly Dictionary ThirstThresholdAlertTypes = new() + [DataField] + public ProtoId ThirstyCategory = "Thirst"; + + public static readonly Dictionary> ThirstThresholdAlertTypes = new() { - {ThirstThreshold.Thirsty, AlertType.Thirsty}, - {ThirstThreshold.Parched, AlertType.Parched}, - {ThirstThreshold.Dead, AlertType.Parched}, + {ThirstThreshold.Thirsty, "Thirsty"}, + {ThirstThreshold.Parched, "Parched"}, + {ThirstThreshold.Dead, "Parched"}, }; } diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index 4de4e4d5feb..bff15f06ff9 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -61,7 +61,7 @@ private void OnMapInit(EntityUid uid, HungerComponent component, MapInitEvent ar private void OnShutdown(EntityUid uid, HungerComponent component, ComponentShutdown args) { - _alerts.ClearAlertCategory(uid, AlertCategory.Hunger); + _alerts.ClearAlertCategory(uid, component.HungerAlertCategory); } private void OnRefreshMovespeed(EntityUid uid, HungerComponent component, RefreshMovementSpeedModifiersEvent args) @@ -142,7 +142,7 @@ private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component } else { - _alerts.ClearAlertCategory(uid, AlertCategory.Hunger); + _alerts.ClearAlertCategory(uid, component.HungerAlertCategory); } if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier)) diff --git a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs index 8ea7d9140c3..205d8d6cde3 100644 --- a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs @@ -165,7 +165,7 @@ private void UpdateEffects(EntityUid uid, ThirstComponent component) } else { - _alerts.ClearAlertCategory(uid, AlertCategory.Thirst); + _alerts.ClearAlertCategory(uid, component.ThirstyCategory); } switch (component.CurrentThirstThreshold) diff --git a/Content.Shared/Revenant/Components/RevenantComponent.cs b/Content.Shared/Revenant/Components/RevenantComponent.cs index 947c1a4b3fc..d7fb28ef136 100644 --- a/Content.Shared/Revenant/Components/RevenantComponent.cs +++ b/Content.Shared/Revenant/Components/RevenantComponent.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Shared.Alert; using Content.Shared.FixedPoint; using Content.Shared.Store; using Content.Shared.Whitelist; @@ -200,6 +201,9 @@ public sealed partial class RevenantComponent : Component public EntityWhitelist? MalfunctionBlacklist; #endregion + [DataField] + public ProtoId EssenceAlert = "Essence"; + #region Visualizer [DataField("state")] public string State = "idle"; diff --git a/Content.Shared/Shuttles/Components/PilotComponent.cs b/Content.Shared/Shuttles/Components/PilotComponent.cs index 1a6927cf813..cb42db4436f 100644 --- a/Content.Shared/Shuttles/Components/PilotComponent.cs +++ b/Content.Shared/Shuttles/Components/PilotComponent.cs @@ -1,7 +1,9 @@ using System.Numerics; +using Content.Shared.Alert; using Content.Shared.Movement.Systems; using Robust.Shared.GameStates; using Robust.Shared.Map; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Shared.Shuttles.Components @@ -32,6 +34,9 @@ public sealed partial class PilotComponent : Component [ViewVariables] public ShuttleButtons HeldButtons = ShuttleButtons.None; + [DataField] + public ProtoId PilotingAlert = "PilotingShuttle"; + public override bool SendOnlyToOwner => true; } } diff --git a/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs b/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs index 71d3a7bd166..e1776873da9 100644 --- a/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs +++ b/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs @@ -1,6 +1,8 @@ -using Content.Shared.Whitelist; +using Content.Shared.Alert; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Silicons.Borgs.Components; @@ -76,6 +78,12 @@ public sealed partial class BorgChassisComponent : Component [DataField("noMindState")] public string NoMindState = string.Empty; #endregion + + [DataField] + public ProtoId BatteryAlert = "BorgBattery"; + + [DataField] + public ProtoId NoBatteryAlert = "BorgBatteryNone"; } [Serializable, NetSerializable] diff --git a/Content.Shared/StatusEffect/StatusEffectPrototype.cs b/Content.Shared/StatusEffect/StatusEffectPrototype.cs index ae9e26879eb..8b1f84e4d81 100644 --- a/Content.Shared/StatusEffect/StatusEffectPrototype.cs +++ b/Content.Shared/StatusEffect/StatusEffectPrototype.cs @@ -10,7 +10,7 @@ public sealed partial class StatusEffectPrototype : IPrototype public string ID { get; private set; } = default!; [DataField("alert")] - public AlertType? Alert { get; private set; } + public ProtoId? Alert { get; private set; } /// /// Whether a status effect should be able to apply to any entity, diff --git a/Content.Shared/StatusEffect/StatusEffectsSystem.cs b/Content.Shared/StatusEffect/StatusEffectsSystem.cs index f3e3e12bd8c..9806077f9bb 100644 --- a/Content.Shared/StatusEffect/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffect/StatusEffectsSystem.cs @@ -219,7 +219,7 @@ public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool re /// This is mostly for stuns, since Stun and Knockdown share an alert key. Other times this pretty much /// will not be useful. /// - private (TimeSpan, TimeSpan)? GetAlertCooldown(EntityUid uid, AlertType alert, StatusEffectsComponent status) + private (TimeSpan, TimeSpan)? GetAlertCooldown(EntityUid uid, ProtoId alert, StatusEffectsComponent status) { (TimeSpan, TimeSpan)? maxCooldown = null; foreach (var kvp in status.ActiveEffects) diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs index 36dbedb4cb1..03ad97edff2 100644 --- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs +++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs @@ -42,6 +42,9 @@ public sealed class ReflectSystem : EntitySystem [Dependency] private readonly StandingStateSystem _standing = default!; [Dependency] private readonly AlertsSystem _alerts = default!; + [ValidatePrototypeId] + private const string DeflectingAlert = "Deflecting"; + public override void Initialize() { base.Initialize(); @@ -296,11 +299,11 @@ private void RefreshReflectUser(EntityUid user) private void EnableAlert(EntityUid alertee) { - _alerts.ShowAlert(alertee, AlertType.Deflecting); + _alerts.ShowAlert(alertee, DeflectingAlert); } private void DisableAlert(EntityUid alertee) { - _alerts.ClearAlert(alertee, AlertType.Deflecting); + _alerts.ClearAlert(alertee, DeflectingAlert); } } diff --git a/Content.Tests/Shared/Alert/AlertManagerTests.cs b/Content.Tests/Shared/Alert/AlertManagerTests.cs index 2d5f6af5a7f..c57df63d5b7 100644 --- a/Content.Tests/Shared/Alert/AlertManagerTests.cs +++ b/Content.Tests/Shared/Alert/AlertManagerTests.cs @@ -1,6 +1,5 @@ using System.IO; using Content.Client.Alerts; -using Content.Server.Alert; using Content.Shared.Alert; using NUnit.Framework; using Robust.Shared.GameObjects; @@ -45,15 +44,15 @@ public void TestAlertManager() prototypeManager.Initialize(); prototypeManager.LoadFromStream(new StringReader(PROTOTYPES)); - Assert.That(alertsSystem.TryGet(AlertType.LowPressure, out var lowPressure)); - Assert.That(lowPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); - Assert.That(alertsSystem.TryGet(AlertType.HighPressure, out var highPressure)); - Assert.That(highPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/highpressure.png")))); + Assert.That(alertsSystem.TryGet("LowPressure", out var lowPressure)); + Assert.That(lowPressure!.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); + Assert.That(alertsSystem.TryGet("HighPressure", out var highPressure)); + Assert.That(highPressure!.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/highpressure.png")))); - Assert.That(alertsSystem.TryGet(AlertType.LowPressure, out lowPressure)); - Assert.That(lowPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); - Assert.That(alertsSystem.TryGet(AlertType.HighPressure, out highPressure)); - Assert.That(highPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/highpressure.png")))); + Assert.That(alertsSystem.TryGet("LowPressure", out lowPressure)); + Assert.That(lowPressure!.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/lowpressure.png")))); + Assert.That(alertsSystem.TryGet("HighPressure", out highPressure)); + Assert.That(highPressure!.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Pressure/highpressure.png")))); } } } diff --git a/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs b/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs index 56f76d46a92..efcd59acbbb 100644 --- a/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs +++ b/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs @@ -85,24 +85,24 @@ public void TestAlertOrderPrototype() var alerts = prototypeManager.EnumeratePrototypes(); // ensure they sort according to our expected criteria - var expectedOrder = new List(); - expectedOrder.Add(AlertType.Handcuffed); - expectedOrder.Add(AlertType.Ensnared); - expectedOrder.Add(AlertType.HighPressure); + var expectedOrder = new List(); + expectedOrder.Add("Handcuffed"); + expectedOrder.Add("Ensnared"); + expectedOrder.Add("HighPressure"); // stuff with only category + same category ordered by enum value - expectedOrder.Add(AlertType.Peckish); - expectedOrder.Add(AlertType.Hot); - expectedOrder.Add(AlertType.Stun); - expectedOrder.Add(AlertType.LowPressure); - expectedOrder.Add(AlertType.Cold); - // stuff at end of list ordered by enum value - expectedOrder.Add(AlertType.Weightless); - expectedOrder.Add(AlertType.PilotingShuttle); + expectedOrder.Add("Peckish"); + expectedOrder.Add("Hot"); + expectedOrder.Add("Stun"); + expectedOrder.Add("LowPressure"); + expectedOrder.Add("Cold"); + // stuff at end of list ordered by ID + expectedOrder.Add("PilotingShuttle"); + expectedOrder.Add("Weightless"); var actual = alerts.ToList(); actual.Sort(alertOrder); - Assert.That(actual.Select(a => a.AlertType).ToList(), Is.EqualTo(expectedOrder)); + Assert.That(actual.Select(a => a.ID).ToList(), Is.EqualTo(expectedOrder)); } } } diff --git a/Content.Tests/Shared/Alert/AlertPrototypeTests.cs b/Content.Tests/Shared/Alert/AlertPrototypeTests.cs index d95acb8aff4..43ae98b452b 100644 --- a/Content.Tests/Shared/Alert/AlertPrototypeTests.cs +++ b/Content.Tests/Shared/Alert/AlertPrototypeTests.cs @@ -39,9 +39,9 @@ public void OneTimeSetUp() [Test] public void TestAlertKey() { - Assert.That(new AlertKey(AlertType.HumanHealth, null), Is.Not.EqualTo(AlertKey.ForCategory(AlertCategory.Health))); - Assert.That((new AlertKey(null, AlertCategory.Health)), Is.EqualTo(AlertKey.ForCategory(AlertCategory.Health))); - Assert.That((new AlertKey(AlertType.Buckled, AlertCategory.Health)), Is.EqualTo(AlertKey.ForCategory(AlertCategory.Health))); + Assert.That(new AlertKey("HumanHealth", null), Is.Not.EqualTo(AlertKey.ForCategory("Health"))); + Assert.That((new AlertKey(null, "Health")), Is.EqualTo(AlertKey.ForCategory("Health"))); + Assert.That((new AlertKey("Buckled", "Health")), Is.EqualTo(AlertKey.ForCategory("Health"))); } [TestCase(0, "/Textures/Interface/Alerts/Human/human.rsi/human0.png")] diff --git a/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs b/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs index 47ae3ef74a6..bcc32e13dee 100644 --- a/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs +++ b/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs @@ -15,6 +15,9 @@ namespace Content.Tests.Shared.Alert public sealed class ServerAlertsComponentTests : ContentUnitTest { const string PROTOTYPES = @" +- type: alertCategory + id: Pressure + - type: alert id: LowPressure category: Pressure @@ -49,10 +52,10 @@ public void ShowAlerts() var alertsComponent = new AlertsComponent(); alertsComponent = IoCManager.InjectDependencies(alertsComponent); - Assert.That(entManager.System().TryGet(AlertType.LowPressure, out var lowpressure)); - Assert.That(entManager.System().TryGet(AlertType.HighPressure, out var highpressure)); + Assert.That(entManager.System().TryGet("LowPressure", out var lowpressure)); + Assert.That(entManager.System().TryGet("HighPressure", out var highpressure)); - entManager.System().ShowAlert(alertsComponent.Owner, AlertType.LowPressure, null, null); + entManager.System().ShowAlert(alertsComponent.Owner, "LowPressure"); var getty = new ComponentGetState(); entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); @@ -60,17 +63,17 @@ public void ShowAlerts() var alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; Assert.That(alertState, Is.Not.Null); Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); - Assert.That(alertState.Alerts.ContainsKey(lowpressure.AlertKey)); + Assert.That(alertState.Alerts.ContainsKey(lowpressure!.AlertKey)); - entManager.System().ShowAlert(alertsComponent.Owner, AlertType.HighPressure, null, null); + entManager.System().ShowAlert(alertsComponent.Owner, "HighPressure"); // Lazy entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); - Assert.That(alertState.Alerts.ContainsKey(highpressure.AlertKey)); + Assert.That(alertState.Alerts.ContainsKey(highpressure!.AlertKey)); - entManager.System().ClearAlertCategory(alertsComponent.Owner, AlertCategory.Pressure); + entManager.System().ClearAlertCategory(alertsComponent.Owner, "Pressure"); entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; diff --git a/Resources/Prototypes/Alerts/categories.yml b/Resources/Prototypes/Alerts/categories.yml new file mode 100644 index 00000000000..2365422ed91 --- /dev/null +++ b/Resources/Prototypes/Alerts/categories.yml @@ -0,0 +1,35 @@ +- type: alertCategory + id: Pressure + +- type: alertCategory + id: Temperature + +- type: alertCategory + id: Breathing + +- type: alertCategory + id: Buckled + +- type: alertCategory + id: Health + +- type: alertCategory + id: Internals + +- type: alertCategory + id: Stamina + +- type: alertCategory + id: Piloting + +- type: alertCategory + id: Hunger + +- type: alertCategory + id: Thirst + +- type: alertCategory + id: Toxins + +- type: alertCategory + id: Battery From 396a6ad5a624903a7800ecff7f3c2a5ac9c11471 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 24 May 2024 16:08:23 +1200 Subject: [PATCH 053/235] Content changes for engine delta-state PR (#28134) * Update GasTileOverlayState * Update DecalGridState * Update NavMapState * poke * poke2 * poke3 * Poke dem tests --- .../EntitySystems/GasTileOverlaySystem.cs | 39 ++++++++++------ Content.Client/Decals/DecalSystem.cs | 39 ++++++++++------ Content.Client/Pinpointer/NavMapSystem.cs | 41 +++++++++++------ .../Components/GasTileOverlayComponent.cs | 45 ++++++++---------- .../SharedGasTileOverlaySystem.cs | 2 +- Content.Shared/Decals/DecalGridComponent.cs | 35 ++++++-------- Content.Shared/Decals/SharedDecalSystem.cs | 2 +- .../Pinpointer/SharedNavMapSystem.cs | 46 ++++++++----------- 8 files changed, 129 insertions(+), 120 deletions(-) diff --git a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs index 78185ce6b0e..86cf0a9eb82 100644 --- a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -1,4 +1,5 @@ using Content.Client.Atmos.Overlays; +using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.EntitySystems; using JetBrains.Annotations; @@ -36,28 +37,38 @@ public override void Shutdown() private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args) { - if (args.Current is not GasTileOverlayState state) - return; + Dictionary modifiedChunks; - // is this a delta or full state? - if (!state.FullState) + switch (args.Current) { - foreach (var index in comp.Chunks.Keys) + // is this a delta or full state? + case GasTileOverlayDeltaState delta: { - if (!state.AllChunks!.Contains(index)) - comp.Chunks.Remove(index); + modifiedChunks = delta.ModifiedChunks; + foreach (var index in comp.Chunks.Keys) + { + if (!delta.AllChunks.Contains(index)) + comp.Chunks.Remove(index); + } + + break; } - } - else - { - foreach (var index in comp.Chunks.Keys) + case GasTileOverlayState state: { - if (!state.Chunks.ContainsKey(index)) - comp.Chunks.Remove(index); + modifiedChunks = state.Chunks; + foreach (var index in comp.Chunks.Keys) + { + if (!state.Chunks.ContainsKey(index)) + comp.Chunks.Remove(index); + } + + break; } + default: + return; } - foreach (var (index, data) in state.Chunks) + foreach (var (index, data) in modifiedChunks) { comp.Chunks[index] = data; } diff --git a/Content.Client/Decals/DecalSystem.cs b/Content.Client/Decals/DecalSystem.cs index 901ab270fb5..41e5f39c286 100644 --- a/Content.Client/Decals/DecalSystem.cs +++ b/Content.Client/Decals/DecalSystem.cs @@ -56,34 +56,43 @@ protected override void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGrid private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args) { - if (args.Current is not DecalGridState state) - return; - // is this a delta or full state? _removedChunks.Clear(); + Dictionary modifiedChunks; - if (!state.FullState) + switch (args.Current) { - foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + case DecalGridDeltaState delta: { - if (!state.AllChunks!.Contains(key)) - _removedChunks.Add(key); + modifiedChunks = delta.ModifiedChunks; + foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + { + if (!delta.AllChunks.Contains(key)) + _removedChunks.Add(key); + } + + break; } - } - else - { - foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + case DecalGridState state: { - if (!state.Chunks.ContainsKey(key)) - _removedChunks.Add(key); + modifiedChunks = state.Chunks; + foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + { + if (!state.Chunks.ContainsKey(key)) + _removedChunks.Add(key); + } + + break; } + default: + return; } if (_removedChunks.Count > 0) RemoveChunks(gridUid, gridComp, _removedChunks); - if (state.Chunks.Count > 0) - UpdateChunks(gridUid, gridComp, state.Chunks); + if (modifiedChunks.Count > 0) + UpdateChunks(gridUid, gridComp, modifiedChunks); } private void OnChunkUpdate(DecalChunkUpdateEvent ev) diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs index e33bc5d3291..9aeb792a429 100644 --- a/Content.Client/Pinpointer/NavMapSystem.cs +++ b/Content.Client/Pinpointer/NavMapSystem.cs @@ -14,27 +14,40 @@ public override void Initialize() private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args) { - if (args.Current is not NavMapComponentState state) - return; + Dictionary modifiedChunks; + Dictionary beacons; - if (!state.FullState) + switch (args.Current) { - foreach (var index in component.Chunks.Keys) + case NavMapDeltaState delta: { - if (!state.AllChunks!.Contains(index)) - component.Chunks.Remove(index); + modifiedChunks = delta.ModifiedChunks; + beacons = delta.Beacons; + foreach (var index in component.Chunks.Keys) + { + if (!delta.AllChunks!.Contains(index)) + component.Chunks.Remove(index); + } + + break; } - } - else - { - foreach (var index in component.Chunks.Keys) + case NavMapState state: { - if (!state.Chunks.ContainsKey(index)) - component.Chunks.Remove(index); + modifiedChunks = state.Chunks; + beacons = state.Beacons; + foreach (var index in component.Chunks.Keys) + { + if (!state.Chunks.ContainsKey(index)) + component.Chunks.Remove(index); + } + + break; } + default: + return; } - foreach (var (origin, chunk) in state.Chunks) + foreach (var (origin, chunk) in modifiedChunks) { var newChunk = new NavMapChunk(origin); Array.Copy(chunk, newChunk.TileData, chunk.Length); @@ -42,7 +55,7 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone } component.Beacons.Clear(); - foreach (var (nuid, beacon) in state.Beacons) + foreach (var (nuid, beacon) in beacons) { component.Beacons[nuid] = beacon; } diff --git a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs index e72a1d67584..2c3149b11a8 100644 --- a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs +++ b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs @@ -1,7 +1,6 @@ using Robust.Shared.GameStates; using Robust.Shared.Serialization; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Shared.Atmos.Components; @@ -24,55 +23,47 @@ public sealed partial class GasTileOverlayComponent : Component public GameTick ForceTick { get; set; } } - [Serializable, NetSerializable] -public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState +public sealed class GasTileOverlayState(Dictionary chunks) : ComponentState { - public readonly Dictionary Chunks; - public bool FullState => AllChunks == null; - - // required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; + public readonly Dictionary Chunks = chunks; +} - public GasTileOverlayState(Dictionary chunks) - { - Chunks = chunks; - } +[Serializable, NetSerializable] +public sealed class GasTileOverlayDeltaState( + Dictionary modifiedChunks, + HashSet allChunks) + : ComponentState, IComponentDeltaState +{ + public readonly Dictionary ModifiedChunks = modifiedChunks; + public readonly HashSet AllChunks = allChunks; - public void ApplyToFullState(IComponentState fullState) + public void ApplyToFullState(GasTileOverlayState state) { - DebugTools.Assert(!FullState); - var state = (GasTileOverlayState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { - if (!AllChunks!.Contains(key)) + if (!AllChunks.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { state.Chunks[chunk] = new(data); } } - public IComponentState CreateNewFullState(IComponentState fullState) + public GasTileOverlayState CreateNewFullState(GasTileOverlayState state) { - DebugTools.Assert(!FullState); - var state = (GasTileOverlayState) fullState; - DebugTools.Assert(state.FullState); - - var chunks = new Dictionary(state.Chunks.Count); + var chunks = new Dictionary(AllChunks.Count); - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { chunks[chunk] = new(data); } foreach (var (chunk, data) in state.Chunks) { - if (AllChunks!.Contains(chunk)) + if (AllChunks.Contains(chunk)) chunks.TryAdd(chunk, new(data)); } diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index f468724db33..8e7dfdedaf9 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -55,7 +55,7 @@ private void OnGetState(EntityUid uid, GasTileOverlayComponent component, ref Co data[index] = chunk; } - args.State = new GasTileOverlayState(data) { AllChunks = new(component.Chunks.Keys) }; + args.State = new GasTileOverlayDeltaState(data, new(component.Chunks.Keys)); } public static Vector2i GetGasChunkIndices(Vector2i indices) diff --git a/Content.Shared/Decals/DecalGridComponent.cs b/Content.Shared/Decals/DecalGridComponent.cs index 8ac05cb280f..67a9c037696 100644 --- a/Content.Shared/Decals/DecalGridComponent.cs +++ b/Content.Shared/Decals/DecalGridComponent.cs @@ -62,46 +62,37 @@ public record DecalGridChunkCollection(Dictionary ChunkCol } [Serializable, NetSerializable] - public sealed class DecalGridState : ComponentState, IComponentDeltaState + public sealed class DecalGridState(Dictionary chunks) : ComponentState { - public Dictionary Chunks; - public bool FullState => AllChunks == null; - - // required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; + public Dictionary Chunks = chunks; + } - public DecalGridState(Dictionary chunks) - { - Chunks = chunks; - } + [Serializable, NetSerializable] + public sealed class DecalGridDeltaState(Dictionary modifiedChunks, HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary ModifiedChunks = modifiedChunks; + public HashSet AllChunks = allChunks; - public void ApplyToFullState(IComponentState fullState) + public void ApplyToFullState(DecalGridState state) { - DebugTools.Assert(!FullState); - var state = (DecalGridState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { if (!AllChunks!.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { state.Chunks[chunk] = new(data); } } - public IComponentState CreateNewFullState(IComponentState fullState) + public DecalGridState CreateNewFullState(DecalGridState state) { - DebugTools.Assert(!FullState); - var state = (DecalGridState) fullState; - DebugTools.Assert(state.FullState); - var chunks = new Dictionary(state.Chunks.Count); - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { chunks[chunk] = new(data); } diff --git a/Content.Shared/Decals/SharedDecalSystem.cs b/Content.Shared/Decals/SharedDecalSystem.cs index 0665ccbf84b..0a2349ea299 100644 --- a/Content.Shared/Decals/SharedDecalSystem.cs +++ b/Content.Shared/Decals/SharedDecalSystem.cs @@ -49,7 +49,7 @@ private void OnGetState(EntityUid uid, DecalGridComponent component, ref Compone data[index] = chunk; } - args.State = new DecalGridState(data) { AllChunks = new(component.ChunkCollection.ChunkCollection.Keys) }; + args.State = new DecalGridDeltaState(data, new(component.ChunkCollection.ChunkCollection.Keys)); } private void OnGridInitialize(GridInitializeEvent msg) diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index ffe81c2d0ea..7c12321b5db 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -96,7 +96,7 @@ private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentG chunks.Add(origin, chunk.TileData); } - args.State = new NavMapComponentState(chunks, component.Beacons); + args.State = new NavMapState(chunks, component.Beacons); return; } @@ -109,12 +109,7 @@ private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentG chunks.Add(origin, chunk.TileData); } - args.State = new NavMapComponentState(chunks, component.Beacons) - { - // TODO NAVMAP cache a single AllChunks hashset in the component. - // Or maybe just only send them if a chunk gets removed. - AllChunks = new(component.Chunks.Keys), - }; + args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys)); } #endregion @@ -122,32 +117,35 @@ private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentG #region: System messages [Serializable, NetSerializable] - protected sealed class NavMapComponentState( + protected sealed class NavMapState( Dictionary chunks, Dictionary beacons) - : ComponentState, IComponentDeltaState + : ComponentState { public Dictionary Chunks = chunks; public Dictionary Beacons = beacons; + } - // Required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; - - public bool FullState => AllChunks == null; + [Serializable, NetSerializable] + protected sealed class NavMapDeltaState( + Dictionary modifiedChunks, + Dictionary beacons, + HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary ModifiedChunks = modifiedChunks; + public Dictionary Beacons = beacons; + public HashSet AllChunks = allChunks; - public void ApplyToFullState(IComponentState fullState) + public void ApplyToFullState(NavMapState state) { - DebugTools.Assert(!FullState); - var state = (NavMapComponentState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { if (!AllChunks!.Contains(key)) state.Chunks.Remove(key); } - foreach (var (index, data) in Chunks) + foreach (var (index, data) in ModifiedChunks) { if (!state.Chunks.TryGetValue(index, out var stateValue)) state.Chunks[index] = stateValue = new int[data.Length]; @@ -162,12 +160,8 @@ public void ApplyToFullState(IComponentState fullState) } } - public IComponentState CreateNewFullState(IComponentState fullState) + public NavMapState CreateNewFullState(NavMapState state) { - DebugTools.Assert(!FullState); - var state = (NavMapComponentState) fullState; - DebugTools.Assert(state.FullState); - var chunks = new Dictionary(state.Chunks.Count); foreach (var (index, data) in state.Chunks) { @@ -176,13 +170,13 @@ public IComponentState CreateNewFullState(IComponentState fullState) var newData = chunks[index] = new int[ArraySize]; - if (Chunks.TryGetValue(index, out var updatedData)) + if (ModifiedChunks.TryGetValue(index, out var updatedData)) Array.Copy(newData, updatedData, ArraySize); else Array.Copy(newData, data, ArraySize); } - return new NavMapComponentState(chunks, new(Beacons)); + return new NavMapState(chunks, new(Beacons)); } } From 048e2fa28eafd0eb17bb1a238923efa3a050dc80 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 24 May 2024 16:11:45 +1200 Subject: [PATCH 054/235] Update engine to v223.0.0 (#28239) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index ec794ce4e46..6a6bfe33ca9 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit ec794ce4e4693069d3b3ebf7a88ead5ff2f860e0 +Subproject commit 6a6bfe33ca98dd3df6035ef4dcb7429f4dd5db66 From 768f48bd4fb2d2ee860ec561143552860f74a314 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 24 May 2024 17:03:03 +1200 Subject: [PATCH 055/235] Improve InteractionSystem range & BUI checks (#27999) * Improve InteractionSystem range & BUI checks * Ghost fixes * AAA * Fix test * fix nullable * revert to broadcast event * Fixes for eengine PR * Ah buckle code * ) --- .../UI/InstrumentBoundUserInterface.cs | 10 +- .../Interactable/InteractionSystem.cs | 22 +- Content.IntegrationTests/Tests/EntityTest.cs | 4 + .../Configurable/ConfigurationSystem.cs | 1 + .../Interaction/InteractionSystem.cs | 29 +- .../StationEvents/Events/ImmovableRodRule.cs | 4 +- .../Weapons/Melee/MeleeWeaponSystem.cs | 9 +- Content.Shared/Actions/SharedActionsSystem.cs | 8 +- .../Buckle/SharedBuckleSystem.Buckle.cs | 2 +- .../Interaction/SharedInteractionSystem.cs | 254 ++++++++++++------ .../Inventory/InventorySystem.Equip.cs | 6 +- .../MagicMirror/SharedMagicMirrorSystem.cs | 10 +- .../Movement/Pulling/Systems/PullingSystem.cs | 13 +- .../EntitySystems/SharedStorageSystem.cs | 2 +- .../UserInterface/ActivatableUIComponent.cs | 2 +- .../UserInterface/ActivatableUISystem.cs | 88 ++---- Content.Shared/Verbs/SharedVerbSystem.cs | 14 +- .../Fun/Instruments/base_instruments.yml | 2 +- 18 files changed, 232 insertions(+), 248 deletions(-) diff --git a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs index 2a846ff708a..0f5729f55b1 100644 --- a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs +++ b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs @@ -37,14 +37,8 @@ public InstrumentBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, u protected override void ReceiveMessage(BoundUserInterfaceMessage message) { - switch (message) - { - case InstrumentBandResponseBuiMessage bandRx: - _bandMenu?.Populate(bandRx.Nearby, EntMan); - break; - default: - break; - } + if (message is InstrumentBandResponseBuiMessage bandRx) + _bandMenu?.Populate(bandRx.Nearby, EntMan); } protected override void Open() diff --git a/Content.Client/Interactable/InteractionSystem.cs b/Content.Client/Interactable/InteractionSystem.cs index 0af8830e9ac..ff0a607920f 100644 --- a/Content.Client/Interactable/InteractionSystem.cs +++ b/Content.Client/Interactable/InteractionSystem.cs @@ -4,24 +4,6 @@ namespace Content.Client.Interactable { - public sealed class InteractionSystem : SharedInteractionSystem - { - [Dependency] private readonly SharedContainerSystem _container = default!; - - public override bool CanAccessViaStorage(EntityUid user, EntityUid target) - { - if (!EntityManager.EntityExists(target)) - return false; - - if (!_container.TryGetContainingContainer(target, out var container)) - return false; - - if (!HasComp(container.Owner)) - return false; - - // we don't check if the user can access the storage entity itself. This should be handed by the UI system. - // Need to return if UI is open or not - return true; - } - } + // TODO Remove Shared prefix + public sealed class InteractionSystem : SharedInteractionSystem; } diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index d3b1fb47221..54af64122be 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -350,8 +350,12 @@ public async Task AllComponentsOneToOneDeleteTest() "DebrisFeaturePlacerController", // Above. "LoadedChunk", // Worldgen chunk loading malding. "BiomeSelection", // Whaddya know, requires config. + "ActivatableUI", // Requires enum key }; + // TODO TESTS + // auto ignore any components that have a "required" data field. + await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; var entityManager = server.ResolveDependency(); diff --git a/Content.Server/Configurable/ConfigurationSystem.cs b/Content.Server/Configurable/ConfigurationSystem.cs index 2683bf4e095..5f5f1ef7d16 100644 --- a/Content.Server/Configurable/ConfigurationSystem.cs +++ b/Content.Server/Configurable/ConfigurationSystem.cs @@ -24,6 +24,7 @@ public override void Initialize() private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args) { + // TODO use activatable ui system if (args.Handled) return; diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 4eac7e9ef1d..9ac82b21858 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -7,31 +7,6 @@ namespace Content.Server.Interaction { - /// - /// Governs interactions during clicking on entities - /// - [UsedImplicitly] - public sealed partial class InteractionSystem : SharedInteractionSystem - { - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - - public override bool CanAccessViaStorage(EntityUid user, EntityUid target) - { - if (Deleted(target)) - return false; - - if (!_container.TryGetContainingContainer(target, out var container)) - return false; - - if (!TryComp(container.Owner, out StorageComponent? storage)) - return false; - - if (storage.Container?.ID != container.ID) - return false; - - // we don't check if the user can access the storage entity itself. This should be handed by the UI system. - return _uiSystem.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, user); - } - } + // TODO Remove Shared prefix + public sealed class InteractionSystem : SharedInteractionSystem; } diff --git a/Content.Server/StationEvents/Events/ImmovableRodRule.cs b/Content.Server/StationEvents/Events/ImmovableRodRule.cs index cacb839cd39..aa193f2f4cb 100644 --- a/Content.Server/StationEvents/Events/ImmovableRodRule.cs +++ b/Content.Server/StationEvents/Events/ImmovableRodRule.cs @@ -28,7 +28,9 @@ protected override void Started(EntityUid uid, ImmovableRodRuleComponent compone if (proto.TryGetComponent(out var rod) && proto.TryGetComponent(out var despawn)) { - TryFindRandomTile(out _, out _, out _, out var targetCoords); + if (!TryFindRandomTile(out _, out _, out _, out var targetCoords)) + return; + var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed); var angle = RobustRandom.NextAngle(); var direction = angle.ToVec(); diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 2612e99ec9a..190a2d0263e 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -187,15 +187,10 @@ protected override bool InRange(EntityUid user, EntityUid target, float range, I if (session is { } pSession) { (targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession); - } - else - { - var xform = Transform(target); - targetCoordinates = xform.Coordinates; - targetLocalAngle = xform.LocalRotation; + return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); } - return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); + return Interaction.InRangeUnobstructed(user, target, range); } protected override void DoDamageEffect(List targets, EntityUid? user, TransformComponent targetXform) diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 30687c93225..c92d67ba812 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -502,13 +502,7 @@ private bool ValidateEntityTargetBase(EntityUid user, EntityUid target, EntityTa return distance <= action.Range; } - if (_interactionSystem.InRangeUnobstructed(user, target, range: action.Range) - && _containerSystem.IsInSameOrParentContainer(user, target)) - { - return true; - } - - return _interactionSystem.CanAccessViaStorage(user, target); + return _interactionSystem.InRangeAndAccessible(user, target, range: action.Range); } public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, Entity action) diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 4e94c6134b4..00040211e36 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -64,7 +64,7 @@ private void OnBuckleMove(EntityUid uid, BuckleComponent component, ref MoveEven return; var strapPosition = Transform(strapUid).Coordinates; - if (ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) + if (ev.NewPosition.EntityId.IsValid() && ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) return; TryUnbuckle(uid, uid, true, component); diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index c82a749755d..3324ce5b9b8 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -1,11 +1,10 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; -using Content.Shared.Administration; using Content.Shared.Administration.Logs; -using Content.Shared.Administration.Managers; using Content.Shared.CombatMode; using Content.Shared.Database; +using Content.Shared.Ghost; using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Input; @@ -15,12 +14,13 @@ using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Movement.Components; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Storage; using Content.Shared.Tag; using Content.Shared.Timing; +using Content.Shared.UserInterface; using Content.Shared.Verbs; using Content.Shared.Wall; using JetBrains.Annotations; @@ -36,6 +36,7 @@ using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Timing; +using Robust.Shared.Utility; #pragma warning disable 618 @@ -50,12 +51,11 @@ public abstract partial class SharedInteractionSystem : EntitySystem [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly ISharedAdminManager _adminManager = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _broadphase = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedVerbSystem _verbSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; @@ -64,6 +64,18 @@ public abstract partial class SharedInteractionSystem : EntitySystem [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + + private EntityQuery _ignoreUiRangeQuery; + private EntityQuery _fixtureQuery; + private EntityQuery _itemQuery; + private EntityQuery _physicsQuery; + private EntityQuery _handsQuery; + private EntityQuery _relayQuery; + private EntityQuery _combatQuery; + private EntityQuery _wallMountQuery; + private EntityQuery _delayQuery; + private EntityQuery _uiQuery; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; @@ -76,6 +88,17 @@ public abstract partial class SharedInteractionSystem : EntitySystem public override void Initialize() { + _ignoreUiRangeQuery = GetEntityQuery(); + _fixtureQuery = GetEntityQuery(); + _itemQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + _handsQuery = GetEntityQuery(); + _relayQuery = GetEntityQuery(); + _combatQuery = GetEntityQuery(); + _wallMountQuery = GetEntityQuery(); + _delayQuery = GetEntityQuery(); + _uiQuery = GetEntityQuery(); + SubscribeLocalEvent(HandleUserInterfaceRangeCheck); SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); @@ -111,29 +134,57 @@ public override void Shutdown() /// private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) { - var user = ev.Actor; + _uiQuery.TryComp(ev.Target, out var uiComp); + if (!_actionBlockerSystem.CanInteract(ev.Actor, ev.Target)) + { + // We permit ghosts to open uis unless explicitly blocked + if (ev.Message is not OpenBoundInterfaceMessage || !HasComp(ev.Actor) || uiComp?.BlockSpectators == true) + { + ev.Cancel(); + return; + } + } + + var range = _ui.GetUiRange(ev.Target, ev.UiKey); - if (!_actionBlockerSystem.CanInteract(user, ev.Target)) + // As long as range>0, the UI frame updates should have auto-closed the UI if it is out of range. + DebugTools.Assert(range <= 0 || UiRangeCheck(ev.Actor, ev.Target, range)); + + if (range <= 0 && !IsAccessible(ev.Actor, ev.Target)) { ev.Cancel(); return; } - // Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that - // they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into - // other people's backpacks). - if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target) - && !CanAccessViaStorage(user, ev.Target) - && !_adminManager.HasAdminFlag(user, AdminFlags.Admin)) + if (uiComp == null) + return; + + if (uiComp.SingleUser && uiComp.CurrentSingleUser != ev.Actor) { ev.Cancel(); return; } - if (!InRangeUnobstructed(user, ev.Target)) - { + if (!uiComp.RequireHands) + return; + + if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0) ev.Cancel(); - } + } + + private bool UiRangeCheck(Entity user, Entity target, float range) + { + if (!Resolve(target, ref target.Comp)) + return false; + + if (user.Owner == target.Owner) + return true; + + // Fast check: if the user is the parent of the entity (e.g., holding it), we always assume that it is in range + if (target.Comp.ParentUid == user.Owner) + return true; + + return InRangeAndAccessible(user, target, range) || _ignoreUiRangeQuery.HasComp(user); } /// @@ -190,10 +241,7 @@ private bool HandleTryPullObject(ICommonSession? session, EntityCoordinates coor if (!InRangeUnobstructed(userEntity.Value, uid, popup: true)) return false; - if (!TryComp(uid, out PullableComponent? pull)) - return false; - - _pullSystem.TogglePull(uid, userEntity.Value, pull); + _pullSystem.TogglePull(uid, userEntity.Value); return false; } @@ -269,7 +317,7 @@ private bool ShouldCheckAccess(EntityUid user) public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target) { // Always allow attack in these cases - if (target == null || !TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) + if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) return false; // Only eat input if: @@ -277,7 +325,7 @@ public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target) // - Target doesn't cancel should-interact event // This is intended to allow items to be picked up in combat mode, // but to also allow items to force attacks anyway (like mobs which are items, e.g. mice) - if (!HasComp(target)) + if (!_itemQuery.HasComp(target)) return false; var combatEv = new CombatModeShouldHandInteractEvent(); @@ -307,7 +355,7 @@ public void UserInteraction( bool checkAccess = true, bool checkCanUse = true) { - if (TryComp(user, out var relay) && relay.RelayEntity is not null) + if (_relayQuery.TryComp(user, out var relay) && relay.RelayEntity is not null) { // TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways. if (_actionBlockerSystem.CanInteract(user, target)) @@ -321,7 +369,7 @@ public void UserInteraction( if (target != null && Deleted(target.Value)) return; - if (!altInteract && TryComp(user, out var combatMode) && combatMode.IsInCombatMode) + if (!altInteract && _combatQuery.TryComp(user, out var combatMode) && combatMode.IsInCombatMode) { if (!CombatModeCanHandInteract(user, target)) return; @@ -343,10 +391,7 @@ public void UserInteraction( // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // Also checks if the item is accessible via some storage UI (e.g., open backpack) - if (checkAccess - && target != null - && !_containerSystem.IsInSameOrParentContainer(user, target.Value) - && !CanAccessViaStorage(user, target.Value)) + if (checkAccess && target != null && !IsAccessible(user, target.Value)) return; var inRangeUnobstructed = target == null @@ -354,7 +399,7 @@ public void UserInteraction( : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities // Does the user have hands? - if (!TryComp(user, out var hands) || hands.ActiveHand == null) + if (!_handsQuery.TryComp(user, out var hands) || hands.ActiveHand == null) { var ev = new InteractNoHandEvent(user, target, coordinates); RaiseLocalEvent(user, ev); @@ -494,7 +539,7 @@ public float UnobstructedDistance( predicate ??= _ => false; var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask); - var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); + var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); if (rayResults.Count == 0) return dir.Length(); @@ -557,23 +602,29 @@ public bool InRangeUnobstructed( } var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask); - var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); + var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); return rayResults.Count == 0; } public bool InRangeUnobstructed( - EntityUid origin, - EntityUid other, + Entity origin, + Entity other, float range = InteractionRange, CollisionGroup collisionMask = InRangeUnobstructedMask, Ignored? predicate = null, bool popup = false) { - if (!TryComp(other, out TransformComponent? otherXform)) + if (!Resolve(other, ref other.Comp)) return false; - return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate, + return InRangeUnobstructed(origin, + other, + other.Comp.Coordinates, + other.Comp.LocalRotation, + range, + collisionMask, + predicate, popup); } @@ -605,8 +656,8 @@ public bool InRangeUnobstructed( /// True if the two points are within a given range without being obstructed. /// public bool InRangeUnobstructed( - EntityUid origin, - EntityUid other, + Entity origin, + Entity other, EntityCoordinates otherCoordinates, Angle otherAngle, float range = InteractionRange, @@ -614,10 +665,10 @@ public bool InRangeUnobstructed( Ignored? predicate = null, bool popup = false) { - Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false); + Ignored combinedPredicate = e => e == origin.Owner || (predicate?.Invoke(e) ?? false); var inRange = true; MapCoordinates originPos = default; - var targetPos = otherCoordinates.ToMap(EntityManager, _transform); + var targetPos = _transform.ToMapCoordinates(otherCoordinates); Angle targetRot = default; // So essentially: @@ -627,23 +678,30 @@ public bool InRangeUnobstructed( // Alternatively we could check centre distances first though // that means we wouldn't be able to easily check overlap interactions. if (range > 0f && - TryComp(origin, out var fixtureA) && + _fixtureQuery.TryComp(origin, out var fixtureA) && // These fixture counts are stuff that has the component but no fixtures for (e.g. buttons). // At least until they get removed. fixtureA.FixtureCount > 0 && - TryComp(other, out var fixtureB) && + _fixtureQuery.TryComp(other, out var fixtureB) && fixtureB.FixtureCount > 0 && - TryComp(origin, out TransformComponent? xformA)) + Resolve(origin, ref origin.Comp)) { - var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation(); + var (worldPosA, worldRotA) = origin.Comp.GetWorldPositionRotation(); var xfA = new Transform(worldPosA, worldRotA); var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId); var xfB = new Transform(targetPos.Position, parentRotB + otherAngle); // Different map or the likes. - if (!_sharedBroadphaseSystem.TryGetNearest(origin, other, - out _, out _, out var distance, - xfA, xfB, fixtureA, fixtureB)) + if (!_broadphase.TryGetNearest( + origin, + other, + out _, + out _, + out var distance, + xfA, + xfB, + fixtureA, + fixtureB)) { inRange = false; } @@ -665,15 +723,15 @@ public bool InRangeUnobstructed( else { // We'll still do the raycast from the centres but we'll bump the range as we know they're in range. - originPos = _transform.GetMapCoordinates(origin, xform: xformA); + originPos = _transform.GetMapCoordinates(origin, xform: origin.Comp); range = (originPos.Position - targetPos.Position).Length(); } } // No fixtures, e.g. wallmounts. else { - originPos = _transform.GetMapCoordinates(origin); - var otherParent = Transform(other).ParentUid; + originPos = _transform.GetMapCoordinates(origin, origin); + var otherParent = (other.Comp ?? Transform(other)).ParentUid; targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle; } @@ -724,13 +782,13 @@ private Ignored GetPredicate( { HashSet ignored = new(); - if (HasComp(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide) + if (_itemQuery.HasComp(target) && _physicsQuery.TryComp(target, out var physics) && physics.CanCollide) { // If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck // inside of walls, users can still pick them up. - ignored.UnionWith(_sharedBroadphaseSystem.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); + ignored.UnionWith(_broadphase.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); } - else if (TryComp(target, out WallMountComponent? wallMount)) + else if (_wallMountQuery.TryComp(target, out var wallMount)) { // wall-mount exemptions may be restricted to a specific angle range.da @@ -748,13 +806,7 @@ private Ignored GetPredicate( ignored.UnionWith(grid.GetAnchoredEntities(targetCoords)); } - Ignored combinedPredicate = e => - { - return e == target - || (predicate?.Invoke(e) ?? false) - || ignored.Contains(e); - }; - + Ignored combinedPredicate = e => e == target || (predicate?.Invoke(e) ?? false) || ignored.Contains(e); return combinedPredicate; } @@ -951,10 +1003,8 @@ public bool InteractionActivate( bool checkUseDelay = true, bool checkAccess = true) { - UseDelayComponent? delayComponent = null; - if (checkUseDelay - && TryComp(used, out delayComponent) - && _useDelay.IsDelayed((used, delayComponent))) + _delayQuery.TryComp(used, out var delayComponent); + if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return false; if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) @@ -965,11 +1015,11 @@ public bool InteractionActivate( // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) - if (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used)) + if (checkAccess && !IsAccessible(user, used)) return false; // Does the user have hands? - if (!HasComp(user)) + if (!_handsQuery.HasComp(user)) return false; var activateMsg = new ActivateInWorldEvent(user, used); @@ -979,7 +1029,9 @@ public bool InteractionActivate( DoContactInteraction(user, used, activateMsg); // Still need to call this even without checkUseDelay in case this gets relayed from Activate. - _useDelay.TryResetDelay(used, component: delayComponent); + if (delayComponent != null) + _useDelay.TryResetDelay(used, component: delayComponent); + if (!activateMsg.WasLogged) _adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); return true; @@ -1000,11 +1052,8 @@ public bool UseInHandInteraction( bool checkCanInteract = true, bool checkUseDelay = true) { - UseDelayComponent? delayComponent = null; - - if (checkUseDelay - && TryComp(used, out delayComponent) - && _useDelay.IsDelayed((used, delayComponent))) + _delayQuery.TryComp(used, out var delayComponent); + if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return true; // if the item is on cooldown, we consider this handled. if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) @@ -1066,11 +1115,60 @@ public void DroppedInteraction(EntityUid user, EntityUid item) } #endregion + /// + /// Check if a user can access a target (stored in the same containers) and is in range without obstructions. + /// + public bool InRangeAndAccessible( + Entity user, + Entity target, + float range = InteractionRange, + CollisionGroup collisionMask = InRangeUnobstructedMask, + Ignored? predicate = null) + { + if (user == target) + return true; + + if (!Resolve(user, ref user.Comp)) + return false; + + if (!Resolve(target, ref target.Comp)) + return false; + + return IsAccessible(user, target) && InRangeUnobstructed(user, target, range, collisionMask, predicate); + } + + /// + /// Check if a user can access a target or if they are stored in different containers. + /// + public bool IsAccessible(Entity user, Entity target) + { + if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container)) + return true; + + return container != null && CanAccessViaStorage(user, target, container); + } + /// /// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This /// checks if the user can access the item in these situations. /// - public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target); + public bool CanAccessViaStorage(EntityUid user, EntityUid target) + { + if (!_containerSystem.TryGetContainingContainer(target, out var container)) + return false; + + return CanAccessViaStorage(user, target, container); + } + + /// + public bool CanAccessViaStorage(EntityUid user, EntityUid target, BaseContainer container) + { + if (StorageComponent.ContainerId != container.ID) + return false; + + // we don't check if the user can access the storage entity itself. This should be handed by the UI system. + return _ui.IsUiOpen(target, StorageComponent.StorageUiKey.Key, user); + } /// /// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't @@ -1151,19 +1249,15 @@ public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, HandledEntityE RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA)); } + private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev) { if (ev.Result == BoundUserInterfaceRangeResult.Fail) return; - if (InRangeUnobstructed(ev.Actor, ev.Target, ev.Data.InteractionRange)) - { - ev.Result = BoundUserInterfaceRangeResult.Pass; - } - else - { - ev.Result = BoundUserInterfaceRangeResult.Fail; - } + ev.Result = UiRangeCheck(ev.Actor!, ev.Target, ev.Data.InteractionRange) + ? BoundUserInterfaceRangeResult.Pass + : BoundUserInterfaceRangeResult.Fail; } } diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 400dfb0beb3..7fd156213b4 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -210,11 +210,7 @@ public bool CanAccess(EntityUid actor, EntityUid target, EntityUid itemUid) return false; // Can the actor reach the item? - if (_interactionSystem.InRangeUnobstructed(actor, itemUid) && _containerSystem.IsInSameOrParentContainer(actor, itemUid)) - return true; - - // Is the item in an open storage UI, i.e., is the user quick-equipping from an open backpack? - if (_interactionSystem.CanAccessViaStorage(actor, itemUid)) + if (_interactionSystem.InRangeAndAccessible(actor, itemUid)) return true; // Is the actor currently stripping the target? Here we could check if the actor has the stripping UI open, but diff --git a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs index 91059d60bfd..433ad6b4fc9 100644 --- a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs +++ b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Interaction; using Content.Shared.UserInterface; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.MagicMirror; @@ -21,10 +22,13 @@ public override void Initialize() private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args) { - if (!Exists(component.Target) || !_interaction.InRangeUnobstructed(uid, component.Target.Value)) - { + if (args.Result == BoundUserInterfaceRangeResult.Fail) + return; + + DebugTools.Assert(component.Target != null && Exists(component.Target)); + + if (!_interaction.InRangeUnobstructed(uid, component.Target.Value)) args.Result = BoundUserInterfaceRangeResult.Fail; - } } private void OnBeforeUIOpen(Entity ent, ref BeforeActivatableUIOpenEvent args) diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 2781c495298..3de71172c72 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -362,14 +362,17 @@ public bool CanPull(EntityUid puller, EntityUid pullableUid, PullerComponent? pu return !startPull.Cancelled && !getPulled.Cancelled; } - public bool TogglePull(EntityUid pullableUid, EntityUid pullerUid, PullableComponent pullable) + public bool TogglePull(Entity pullable, EntityUid pullerUid) { - if (pullable.Puller == pullerUid) + if (!Resolve(pullable, ref pullable.Comp, false)) + return false; + + if (pullable.Comp.Puller == pullerUid) { - return TryStopPull(pullableUid, pullable); + return TryStopPull(pullable, pullable.Comp); } - return TryStartPull(pullerUid, pullableUid, pullableComp: pullable); + return TryStartPull(pullerUid, pullable, pullableComp: pullable); } public bool TogglePull(EntityUid pullerUid, PullerComponent puller) @@ -377,7 +380,7 @@ public bool TogglePull(EntityUid pullerUid, PullerComponent puller) if (!TryComp(puller.Pulling, out var pullable)) return false; - return TogglePull(puller.Pulling.Value, pullerUid, pullable); + return TogglePull((puller.Pulling.Value, pullable), pullerUid); } public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 34eae72c07d..51da15934b8 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -1082,7 +1082,7 @@ public bool PlayerInsertHeldEntity(EntityUid uid, EntityUid player, StorageCompo /// true if inserted, false otherwise public bool PlayerInsertEntityInWorld(Entity uid, EntityUid player, EntityUid toInsert) { - if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid)) + if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid.Owner)) return false; if (!Insert(uid, toInsert, out _, user: player, uid.Comp)) diff --git a/Content.Shared/UserInterface/ActivatableUIComponent.cs b/Content.Shared/UserInterface/ActivatableUIComponent.cs index 3f83816b7de..93f05acac05 100644 --- a/Content.Shared/UserInterface/ActivatableUIComponent.cs +++ b/Content.Shared/UserInterface/ActivatableUIComponent.cs @@ -57,7 +57,7 @@ public sealed partial class ActivatableUIComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] [DataField] - public bool AllowSpectator = true; + public bool BlockSpectators; /// /// Whether the item must be in the user's currently selected/active hand. diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index a6d27ac5459..c1822c4ee33 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -8,7 +8,7 @@ using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Verbs; -using Robust.Shared.Containers; +using Robust.Shared.Utility; namespace Content.Shared.UserInterface; @@ -19,15 +19,12 @@ public sealed partial class ActivatableUISystem : EntitySystem [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; - - private readonly List _toClose = new(); public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnInteractUsing); @@ -37,28 +34,24 @@ public override void Initialize() SubscribeLocalEvent>(GetActivationVerb); SubscribeLocalEvent>(GetVerb); - // TODO ActivatableUI - // Add UI-user component, and listen for user container changes. - // I.e., should lose a computer UI if a player gets shut into a locker. - SubscribeLocalEvent(OnGotInserted); - SubscribeLocalEvent(OnGotRemoved); - - SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); SubscribeLocalEvent(OnActionPerform); InitializePower(); } - private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) + private void OnStartup(Entity ent, ref ComponentStartup args) { - if (!TryComp(ev.Target, out ActivatableUIComponent? comp)) - return; - - if (!comp.RequireHands) + if (ent.Comp.Key == null) + { + Log.Error($"Missing UI Key for entity: {ToPrettyString(ent)}"); return; + } - if (!TryComp(ev.Actor, out HandsComponent? hands) || hands.Hands.Count == 0) - ev.Cancel(); + // TODO BUI + // set interaction range to zero to avoid constant range checks. + // + // if (ent.Comp.InHandsOnly && _uiSystem.TryGetInterfaceData(ent.Owner, ent.Comp.Key, out var data)) + // data.InteractionRange = 0; } private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args) @@ -77,9 +70,10 @@ private void GetActivationVerb(EntityUid uid, ActivatableUIComponent component, args.Verbs.Add(new ActivationVerb { - // TODO VERBS add "open UI" icon Act = () => InteractUI(args.User, uid, component), - Text = Loc.GetString(component.VerbText) + Text = Loc.GetString(component.VerbText), + // TODO VERB ICON find a better icon + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), }); } @@ -90,9 +84,10 @@ private void GetVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEv args.Verbs.Add(new Verb { - // TODO VERBS add "open UI" icon Act = () => InteractUI(args.User, uid, component), - Text = Loc.GetString(component.VerbText) + Text = Loc.GetString(component.VerbText), + // TODO VERB ICON find a better icon + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), }); } @@ -119,7 +114,7 @@ private bool ShouldAddVerb(EntityUid uid, ActivatableUIComponent component, G } } - return args.CanInteract || component.AllowSpectator && HasComp(args.User); + return args.CanInteract || HasComp(args.User) && !component.BlockSpectators; } private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) @@ -191,7 +186,7 @@ private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUICompone return true; } - if (!_blockerSystem.CanInteract(user, uiEntity) && (!aui.AllowSpectator || !HasComp(user))) + if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp(user) || aui.BlockSpectators)) return false; if (aui.RequireHands) @@ -286,47 +281,4 @@ private void OnHandUnequipped(Entity ent, ref GotUnequip if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) CloseAll(ent, ent); } - - private void OnGotInserted(Entity ent, ref EntGotInsertedIntoContainerMessage args) - { - CheckAccess((ent, ent)); - } - - private void OnGotRemoved(Entity ent, ref EntGotRemovedFromContainerMessage args) - { - CheckAccess((ent, ent)); - } - - public void CheckAccess(Entity ent) - { - if (!Resolve(ent, ref ent.Comp)) - return; - - if (ent.Comp.Key == null) - { - Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}"); - return; - } - - foreach (var user in _uiSystem.GetActors(ent.Owner, ent.Comp.Key)) - { - if (!_container.IsInSameOrParentContainer(user, ent) - && !_interaction.CanAccessViaStorage(user, ent)) - { - _toClose.Add(user); - continue; - - } - - if (!_interaction.InRangeUnobstructed(user, ent)) - _toClose.Add(user); - } - - foreach (var user in _toClose) - { - _uiSystem.CloseUi(ent.Owner, ent.Comp.Key, user); - } - - _toClose.Clear(); - } } diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 60714aea8f3..e78fe98f4c3 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -72,19 +72,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List Date: Fri, 24 May 2024 05:04:10 +0000 Subject: [PATCH 056/235] 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 9ae89dad4a3..d330b043c61 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Plykiya - changes: - - message: Pre-filled syringes start in inject mode now. - type: Tweak - id: 6114 - time: '2024-03-09T10:19:03.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25881 - author: EmoGarbage404 changes: - message: Added a new "colorblind friendly" toggle in the accessibility menu. This @@ -3875,3 +3868,10 @@ id: 6613 time: '2024-05-23T20:27:45.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28226 +- author: ElectroJr + changes: + - message: Ghosts can once again open paper & other UIs + type: Fix + id: 6614 + time: '2024-05-24T05:03:03.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27999 From a2e7c476c0551f88eec14c90f56ba45fa9f4ff55 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 24 May 2024 19:02:04 +1200 Subject: [PATCH 057/235] Update engine to v223.1.1 (#28245) * Update engine to v223.1.0 * Update engine to v223.1.1 --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 6a6bfe33ca9..c250010dada 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 6a6bfe33ca98dd3df6035ef4dcb7429f4dd5db66 +Subproject commit c250010dada8279a48f6861dc80c0eebafee3421 From a036d3a485587a99ba77891373005a221a0c401e Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Fri, 24 May 2024 14:44:42 +0000 Subject: [PATCH 058/235] Fix firelock prediction issues with periodic pulses of closing lights (#28227) * Fix firelock prediction issues with periodic pulses of closing lights For some reason this function was setting a time for the next state which was triggering the door system to try to close the firelock. This does not happen serverside because the function only fires from an event called clientside apparently. It appears to be an attempt to stop firelocks from closing instantly that did not function properly, and I cannot discern any other purpose. As such I have removed it. * Remove redundant serverside check This became redundant with commit 439a87f2 --- Content.Server/Doors/Systems/FirelockSystem.cs | 15 --------------- .../Doors/Components/FirelockComponent.cs | 2 -- .../Doors/Systems/SharedFirelockSystem.cs | 15 --------------- 3 files changed, 32 deletions(-) diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index 5ad86fb20aa..e2b8b5829d1 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -28,8 +28,6 @@ public override void Initialize() { base.Initialize(); - - SubscribeLocalEvent(OnBeforeDoorAutoclose); SubscribeLocalEvent(OnAtmosAlarm); SubscribeLocalEvent(PowerChanged); @@ -80,19 +78,6 @@ public override void Update(float frameTime) } } - private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args) - { - if (!this.IsPowered(uid, EntityManager)) - args.Cancel(); - - // Make firelocks autoclose, but only if the last alarm type it - // remembers was a danger. This is to prevent people from - // flooding hallways with endless bad air/fire. - if (component.AlarmAutoClose && - (_atmosAlarmable.TryGetHighestAlert(uid, out var alarm) && alarm != AtmosAlarmType.Danger || alarm == null)) - args.Cancel(); - } - private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosAlarmEvent args) { if (!this.IsPowered(uid, EntityManager)) diff --git a/Content.Shared/Doors/Components/FirelockComponent.cs b/Content.Shared/Doors/Components/FirelockComponent.cs index ca62daaa0fd..3f7d6c3f704 100644 --- a/Content.Shared/Doors/Components/FirelockComponent.cs +++ b/Content.Shared/Doors/Components/FirelockComponent.cs @@ -19,8 +19,6 @@ public sealed partial class FirelockComponent : Component [DataField("lockedPryTimeModifier"), ViewVariables(VVAccess.ReadWrite)] public float LockedPryTimeModifier = 1.5f; - [DataField("autocloseDelay")] public TimeSpan AutocloseDelay = TimeSpan.FromSeconds(3f); - /// /// Maximum pressure difference before the firelock will refuse to open, in kPa. /// diff --git a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs index 7d033efdd40..47a29a4ba80 100644 --- a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs +++ b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs @@ -18,8 +18,6 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnUpdateState); - // Access/Prying SubscribeLocalEvent(OnBeforeDoorOpened); SubscribeLocalEvent(OnDoorGetPryTimeModifier); @@ -46,19 +44,6 @@ public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = n return _doorSystem.OnPartialClose(uid, door); } - private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args) - { - var ev = new BeforeDoorAutoCloseEvent(); - RaiseLocalEvent(uid, ev); - UpdateVisuals(uid, component, args); - if (ev.Cancelled) - { - return; - } - - _doorSystem.SetNextStateChange(uid, component.AutocloseDelay); - } - #region Access/Prying private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) From 0406d11a6c2394bc57a533b53c75964dd8c77196 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 24 May 2024 14:45:49 +0000 Subject: [PATCH 059/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d330b043c61..61b8e7ec78f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: EmoGarbage404 - changes: - - message: Added a new "colorblind friendly" toggle in the accessibility menu. This - allows you to toggle between a standard and colorblind-friendly palette for - things like progress bars and the medical HUD. - type: Add - - message: The medical HUD is now bright, even in low light levels. - type: Tweak - id: 6115 - time: '2024-03-09T11:43:20.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25318 - author: metalgearsloth changes: - message: Fix NPC mouse movement. @@ -3875,3 +3864,10 @@ id: 6614 time: '2024-05-24T05:03:03.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27999 +- author: nikthechampiongr + changes: + - message: Firelocks will no longer randomly pulse closing lights. + type: Fix + id: 6615 + time: '2024-05-24T14:44:42.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28227 From 7809aab96dfbd947c16687d85e2ef50772846ed9 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 24 May 2024 15:57:02 -0400 Subject: [PATCH 060/235] Hotfix for crashes from bad item names (#28256) --- Content.Client/Examine/ExamineSystem.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 45db4efa53c..125ec62e492 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -239,8 +239,10 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso if (knowTarget) { - var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player)); - var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]"); + // TODO: FormattedMessage.RemoveMarkupPermissive + // var itemName = FormattedMessage.RemoveMarkupPermissive(Identity.Name(target, EntityManager, player)); + var itemName = FormattedMessage.FromMarkupPermissive(Identity.Name(target, EntityManager, player)).ToString(); + var labelMessage = FormattedMessage.FromMarkupPermissive($"[bold]{itemName}[/bold]"); var label = new RichTextLabel(); label.SetMessage(labelMessage); hBox.AddChild(label); From 1942652d87f557e736c62b34c4f4b5955f341210 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 25 May 2024 15:13:24 +1200 Subject: [PATCH 061/235] Fix weapon error logs (#28264) --- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 0fb6ac3eff9..7fc440db479 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -174,49 +174,39 @@ private void OnStopAttack(StopAttackEvent msg, EntitySessionEventArgs args) private void OnLightAttack(LightAttackEvent msg, EntitySessionEventArgs args) { - var user = args.SenderSession.AttachedEntity; - - if (user == null) + if (args.SenderSession.AttachedEntity is not {} user) return; - if (!TryGetWeapon(user.Value, out var weaponUid, out var weapon) || + if (!TryGetWeapon(user, out var weaponUid, out var weapon) || weaponUid != GetEntity(msg.Weapon)) { return; } - AttemptAttack(args.SenderSession.AttachedEntity!.Value, weaponUid, weapon, msg, args.SenderSession); + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } private void OnHeavyAttack(HeavyAttackEvent msg, EntitySessionEventArgs args) { - if (args.SenderSession.AttachedEntity == null) - { + if (args.SenderSession.AttachedEntity is not {} user) return; - } - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon) || + if (!TryGetWeapon(user, out var weaponUid, out var weapon) || weaponUid != GetEntity(msg.Weapon)) { return; } - AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession); + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args) { - if (args.SenderSession.AttachedEntity == null) - { - return; - } - - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon)) - { + if (args.SenderSession.AttachedEntity is not {} user) return; - } - AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession); + if (TryGetWeapon(user, out var weaponUid, out var weapon)) + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } /// @@ -343,23 +333,32 @@ private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponCompo if (!CombatMode.IsInCombatMode(user)) return false; + EntityUid? target = null; switch (attack) { case LightAttackEvent light: - var lightTarget = GetEntity(light.Target); + if (light.Target != null && !TryGetEntity(light.Target, out target)) + { + // Target was lightly attacked & deleted. + return false; + } - if (!Blocker.CanAttack(user, lightTarget, (weaponUid, weapon))) + if (!Blocker.CanAttack(user, target, (weaponUid, weapon))) return false; // Can't self-attack if you're the weapon - if (weaponUid == lightTarget) + if (weaponUid == target) return false; break; case DisarmAttackEvent disarm: - var disarmTarget = GetEntity(disarm.Target); + if (disarm.Target != null && !TryGetEntity(disarm.Target, out target)) + { + // Target was lightly attacked & deleted. + return false; + } - if (!Blocker.CanAttack(user, disarmTarget, (weaponUid, weapon), true)) + if (!Blocker.CanAttack(user, target, (weaponUid, weapon), true)) return false; break; default: From 7f6d5233dfec229e1733486666390884b3d1c64f Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 25 May 2024 21:49:46 +1200 Subject: [PATCH 062/235] Update engine to v223.1.2 (#28273) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index c250010dada..796abe12305 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit c250010dada8279a48f6861dc80c0eebafee3421 +Subproject commit 796abe1230554c31daffbfa6559a0a4bcf50b1af From 7cdca77ba3375bfebde7e800ebe4e9ca33ec1a23 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 26 May 2024 08:03:05 +1200 Subject: [PATCH 063/235] Fix dud modular grenade visuals (#28265) --- .../Objects/Weapons/Throwable/grenades.yml | 3 +- .../Graphs/weapons/modular_grenade.yml | 30 ++++++++++++++++-- .../Weapons/Grenades/modular.rsi/meta.json | 6 +++- .../Grenades/modular.rsi/no-trigger.png | Bin 0 -> 347 bytes 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 Resources/Textures/Objects/Weapons/Grenades/modular.rsi/no-trigger.png diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index 0261bd8cadd..b1d260c3276 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -320,11 +320,12 @@ emptyCase: { state: empty } wiredCase: { state: wired } caseWithTrigger: { state: no-payload } + caseWithPayload: { state: no-trigger } grenade: { state: complete } enum.Trigger.TriggerVisuals.VisualState: enum.ConstructionVisuals.Layer: Primed: { state: primed } - Unprimed: { state: complete } + # Unprimed: - type: StaticPrice price: 25 diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml index 020be4e09c3..243a030c981 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml @@ -12,7 +12,7 @@ doAfter: 1 - node: emptyCase - entity: ModularGrenade + entity: ModularGrenade actions: - !type:AppearanceChange edges: @@ -31,7 +31,7 @@ doAfter: 2 - node: wiredCase - entity: ModularGrenade + entity: ModularGrenade actions: - !type:AppearanceChange - !type:PlaySound @@ -50,6 +50,12 @@ store: payloadTrigger name: Trigger doAfter: 0.5 + - to: caseWithPayload + steps: + - tag: Payload + store: payload + name: Payload + doAfter: 0.5 - node: caseWithTrigger actions: @@ -71,6 +77,26 @@ name: Payload doAfter: 0.5 + - node: caseWithPayload + actions: + - !type:AppearanceChange + - !type:PlaySound + sound: /Audio/Machines/button.ogg + edges: + - to: wiredCase + steps: + - tool: Prying + doAfter: 0.5 + completed: + - !type:EmptyContainer + container: payload + - to: grenade + steps: + - component: PayloadTrigger + store: payloadTrigger + name: Trigger + doAfter: 0.5 + - node: grenade actions: - !type:AppearanceChange diff --git a/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/meta.json b/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/meta.json index f23b6ec168d..b0b12127c59 100644 --- a/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/29c0ed1b000619cb5398ef921000a8d4502ba0b6 and modified by Swept", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/29c0ed1b000619cb5398ef921000a8d4502ba0b6 and modified by Swept & ElectroSR", "size": { "x": 32, "y": 32 @@ -19,6 +19,10 @@ "name": "no-payload", "directions": 1 }, + { + "name": "no-trigger", + "directions": 1 + }, { "name": "complete", "directions": 1 diff --git a/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/no-trigger.png b/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/no-trigger.png new file mode 100644 index 0000000000000000000000000000000000000000..1be2cbc421b2add226c928b7612c9b856a09c276 GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!;0I3^$B+p3WC_;A2_iie2RN2`G8GjS{a@(Q z=H4$iF_n#H!Wzl6owH|4&)A=RU9U^#Aj{HDwiDBAYk!IOD7SO0Y^m*=uT?c;?&f zT^!F*VIz`Sv4yW`C8I|f!&b(Vd^Z&T&vMk`01F&gV7>`++5XhtJuoB4;ERwVPtKh^+&!ACD#s Date: Sat, 25 May 2024 20:04:12 +0000 Subject: [PATCH 064/235] 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 61b8e7ec78f..d3b1dfb4050 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Fix NPC mouse movement. - type: Fix - id: 6116 - time: '2024-03-10T15:41:42.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25965 - author: DoutorWhite changes: - message: Prevents rendering from crashing in certain scenarios @@ -3871,3 +3864,10 @@ id: 6615 time: '2024-05-24T14:44:42.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28227 +- author: ElectroJr + changes: + - message: Fixed modular grenade visuals getting stuck in an incorrect state. + type: Fix + id: 6616 + time: '2024-05-25T20:03:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28265 From 8833e0b04640b848657baeaf12a81d795f731947 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 25 May 2024 13:07:37 -0700 Subject: [PATCH 065/235] Fix not networking whitelist and blacklist in storage component (#28238) --- .../Storage/EntitySystems/SharedStorageSystem.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 51da15934b8..24dfda6ad48 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -22,6 +22,7 @@ using Content.Shared.Storage.Components; using Content.Shared.Timing; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -152,7 +153,9 @@ private void OnStorageGetState(EntityUid uid, StorageComponent component, ref Co Grid = new List(component.Grid), MaxItemSize = component.MaxItemSize, StoredItems = storedItems, - SavedLocations = component.SavedLocations + SavedLocations = component.SavedLocations, + Whitelist = component.Whitelist, + Blacklist = component.Blacklist }; } @@ -164,6 +167,8 @@ private void OnStorageHandleState(EntityUid uid, StorageComponent component, ref component.Grid.Clear(); component.Grid.AddRange(state.Grid); component.MaxItemSize = state.MaxItemSize; + component.Whitelist = state.Whitelist; + component.Blacklist = state.Blacklist; component.StoredItems.Clear(); @@ -1494,5 +1499,9 @@ protected sealed class StorageComponentState : ComponentState public List Grid = new(); public ProtoId? MaxItemSize; + + public EntityWhitelist? Whitelist; + + public EntityWhitelist? Blacklist; } } From ee90f7a0f41578b8072ffeee1bad034dc8eb6ec1 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Sat, 25 May 2024 20:08:15 +0000 Subject: [PATCH 066/235] fix id card console not updating records (#28237) * fix id card console not updating records * test --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- Content.Server/Access/Systems/IdCardConsoleSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs index e680b0c6f40..4e63c93a837 100644 --- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -135,6 +135,8 @@ private void TryWriteToTargetId(EntityUid uid, _idCard.TryChangeJobDepartment(targetId, job); } + UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job); + if (!newAccessList.TrueForAll(x => component.AccessLevels.Contains(x))) { _sawmill.Warning($"User {ToPrettyString(uid)} tried to write unknown access tag."); @@ -168,8 +170,6 @@ private void TryWriteToTargetId(EntityUid uid, This current implementation is pretty shit as it logs 27 entries (27 lines) if someone decides to give themselves AA*/ _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(player):player} has modified {ToPrettyString(targetId):entity} with the following accesses: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]"); - - UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job); } /// From a3a0d2fd28fc1e1ef8172d645c812561669ec21a Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 25 May 2024 20:09:22 +0000 Subject: [PATCH 067/235] 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 d3b1dfb4050..22c9a547de7 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: DoutorWhite - changes: - - message: Prevents rendering from crashing in certain scenarios - type: Fix - id: 6117 - time: '2024-03-10T17:07:24.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25960 - author: nikthechampiongr changes: - message: Shields will no longer absorb asphyxiation damage, or any other damage @@ -3871,3 +3864,10 @@ id: 6616 time: '2024-05-25T20:03:05.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28265 +- author: deltanedas + changes: + - message: Fixed ID Cards not updating the manifest when changed. + type: Fix + id: 6617 + time: '2024-05-25T20:08:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28237 From d36ff9043a00047bd775b800a911b5a6cc5f6f52 Mon Sep 17 00:00:00 2001 From: Vasilis Date: Sat, 25 May 2024 22:09:52 +0200 Subject: [PATCH 068/235] Remove the network tab (#28231) It is useless and bloat, if a user needs to change these settings they are free to modify their cvars manually via the clientconfig.toml file or via the cvar command. --- Content.Client/Options/UI/OptionsMenu.xaml | 1 - Content.Client/Options/UI/OptionsMenu.xaml.cs | 3 +- .../Options/UI/Tabs/NetworkTab.xaml | 102 -------------- .../Options/UI/Tabs/NetworkTab.xaml.cs | 125 ------------------ 4 files changed, 1 insertion(+), 230 deletions(-) delete mode 100644 Content.Client/Options/UI/Tabs/NetworkTab.xaml delete mode 100644 Content.Client/Options/UI/Tabs/NetworkTab.xaml.cs diff --git a/Content.Client/Options/UI/OptionsMenu.xaml b/Content.Client/Options/UI/OptionsMenu.xaml index 69daaa2cea7..9278752f6ab 100644 --- a/Content.Client/Options/UI/OptionsMenu.xaml +++ b/Content.Client/Options/UI/OptionsMenu.xaml @@ -8,7 +8,6 @@ - diff --git a/Content.Client/Options/UI/OptionsMenu.xaml.cs b/Content.Client/Options/UI/OptionsMenu.xaml.cs index bb2c1ce0ed9..4a44b7d6493 100644 --- a/Content.Client/Options/UI/OptionsMenu.xaml.cs +++ b/Content.Client/Options/UI/OptionsMenu.xaml.cs @@ -19,8 +19,7 @@ public OptionsMenu() Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics")); Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls")); Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio")); - Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-network")); - Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings + Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings UpdateTabs(); } diff --git a/Content.Client/Options/UI/Tabs/NetworkTab.xaml b/Content.Client/Options/UI/Tabs/NetworkTab.xaml deleted file mode 100644 index d010f0bd314..00000000000 --- a/Content.Client/Options/UI/Tabs/NetworkTab.xaml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - [RegisterComponent, Access(typeof(ThiefRuleSystem))] -public sealed partial class ThiefRuleComponent : Component -{ - [DataField] - public ProtoId BigObjectiveGroup = "ThiefBigObjectiveGroups"; - - [DataField] - public ProtoId SmallObjectiveGroup = "ThiefObjectiveGroups"; - - [DataField] - public ProtoId EscapeObjectiveGroup = "ThiefEscapeObjectiveGroups"; - - [DataField] - public float BigObjectiveChance = 0.7f; - - [DataField] - public float MaxObjectiveDifficulty = 2.5f; - - [DataField] - public int MaxStealObjectives = 10; -} +public sealed partial class ThiefRuleComponent : Component; diff --git a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs index b85019d8afa..62f92963aa7 100644 --- a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs @@ -22,9 +22,6 @@ public sealed partial class TraitorRuleComponent : Component [DataField] public ProtoId SyndicateFaction = "Syndicate"; - [DataField] - public ProtoId ObjectiveGroup = "TraitorObjectiveGroups"; - [DataField] public ProtoId CodewordAdjectives = "adjectives"; @@ -72,7 +69,4 @@ public enum SelectionState /// [DataField] public int StartingBalance = 20; - - [DataField] - public int MaxDifficulty = 5; } diff --git a/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs b/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs index 81bdda706bd..0367aa1460c 100644 --- a/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs @@ -1,6 +1,8 @@ using Content.Server.GameTicking.Rules.Components; using Content.Server.Objectives; +using Content.Shared.Mind; using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace Content.Server.GameTicking.Rules; @@ -47,7 +49,8 @@ public bool StartRule(string rule, EntityUid mindId, [NotNullWhen(true)] out Ent private void OnObjectivesTextGetInfo(EntityUid uid, GenericAntagRuleComponent comp, ref ObjectivesTextGetInfoEvent args) { - args.Minds = comp.Minds; + // just temporary until this is deleted + args.Minds = comp.Minds.Select(mindId => (mindId, Comp(mindId).CharacterName ?? "?")).ToList(); args.AgentName = Loc.GetString(comp.AgentName); } } diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs index 083085fa0d8..faec4a9e9ca 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -24,7 +24,6 @@ public override void Initialize() SubscribeLocalEvent(AfterAntagSelected); SubscribeLocalEvent(OnGetBriefing); - SubscribeLocalEvent(OnObjectivesTextGetInfo); } private void AfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) @@ -33,41 +32,9 @@ private void AfterAntagSelected(Entity ent, ref AfterAntagEn return; //Generate objectives - GenerateObjectives(mindId, mind, ent); _antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null); } - private void GenerateObjectives(EntityUid mindId, MindComponent mind, ThiefRuleComponent thiefRule) - { - // Give thieves their objectives - var difficulty = 0f; - - if (_random.Prob(thiefRule.BigObjectiveChance)) // 70% chance to 1 big objective (structure or animal) - { - var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.BigObjectiveGroup); - if (objective != null) - { - _mindSystem.AddObjective(mindId, mind, objective.Value); - difficulty += Comp(objective.Value).Difficulty; - } - } - - for (var i = 0; i < thiefRule.MaxStealObjectives && thiefRule.MaxObjectiveDifficulty > difficulty; i++) // Many small objectives - { - var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.SmallObjectiveGroup); - if (objective == null) - continue; - - _mindSystem.AddObjective(mindId, mind, objective.Value); - difficulty += Comp(objective.Value).Difficulty; - } - - //Escape target - var escapeObjective = _objectives.GetRandomObjective(mindId, mind, thiefRule.EscapeObjectiveGroup); - if (escapeObjective != null) - _mindSystem.AddObjective(mindId, mind, escapeObjective.Value); - } - //Add mind briefing private void OnGetBriefing(Entity thief, ref GetBriefingEvent args) { @@ -87,10 +54,4 @@ private string MakeBriefing(EntityUid thief) briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n"; return briefing; } - - private void OnObjectivesTextGetInfo(Entity ent, ref ObjectivesTextGetInfoEvent args) - { - args.Minds = _antag.GetAntagMindEntityUids(ent.Owner); - args.AgentName = Loc.GetString("thief-round-end-agent-name"); - } } diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 1c894a460cc..29de85a42e5 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -31,15 +31,12 @@ public sealed class TraitorRuleSystem : GameRuleSystem [Dependency] private readonly SharedJobSystem _jobs = default!; [Dependency] private readonly ObjectivesSystem _objectives = default!; - public const int MaxPicks = 20; - public override void Initialize() { base.Initialize(); SubscribeLocalEvent(AfterEntitySelected); - SubscribeLocalEvent(OnObjectivesTextGetInfo); SubscribeLocalEvent(OnObjectivesTextPrepend); } @@ -67,7 +64,7 @@ private void MakeCodewords(TraitorRuleComponent component) } } - public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true) + public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true) { //Grab the mind if it wasnt provided if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind)) @@ -112,37 +109,16 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool _npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false); _npcFaction.AddFaction(traitor, component.SyndicateFaction); - // Give traitors their objectives - if (giveObjectives) - { - var difficulty = 0f; - for (var pick = 0; pick < MaxPicks && component.MaxDifficulty > difficulty; pick++) - { - var objective = _objectives.GetRandomObjective(mindId, mind, component.ObjectiveGroup); - if (objective == null) - continue; - - _mindSystem.AddObjective(mindId, mind, objective.Value); - var adding = Comp(objective.Value).Difficulty; - difficulty += adding; - Log.Debug($"Added objective {ToPrettyString(objective):objective} with {adding} difficulty"); - } - } - return true; } - private void OnObjectivesTextGetInfo(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextGetInfoEvent args) - { - args.Minds = _antag.GetAntagMindEntityUids(uid); - args.AgentName = Loc.GetString("traitor-round-end-agent-name"); - } - + // TODO: AntagCodewordsComponent private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args) { args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords))); } + // TODO: figure out how to handle this? add priority to briefing event? private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string? objectiveIssuer = null) { var sb = new StringBuilder(); diff --git a/Content.Server/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs index 47fe4eb5f88..f8ecc22828e 100644 --- a/Content.Server/Objectives/ObjectivesSystem.cs +++ b/Content.Server/Objectives/ObjectivesSystem.cs @@ -36,14 +36,14 @@ public override void Initialize() private void OnRoundEndText(RoundEndTextAppendEvent ev) { // go through each gamerule getting data for the roundend summary. - var summaries = new Dictionary>>(); + var summaries = new Dictionary>>(); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var gameRule)) { if (!_gameTicker.IsGameRuleAdded(uid, gameRule)) continue; - var info = new ObjectivesTextGetInfoEvent(new List(), string.Empty); + var info = new ObjectivesTextGetInfoEvent(new List<(EntityUid, string)>(), string.Empty); RaiseLocalEvent(uid, ref info); if (info.Minds.Count == 0) continue; @@ -51,7 +51,7 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev) // first group the gamerules by their agents, for example 2 different dragons var agent = info.AgentName; if (!summaries.ContainsKey(agent)) - summaries[agent] = new Dictionary>(); + summaries[agent] = new Dictionary>(); var prepend = new ObjectivesTextPrependEvent(""); RaiseLocalEvent(uid, ref prepend); @@ -79,7 +79,7 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev) foreach (var (_, minds) in summary) { total += minds.Count; - totalInCustody += minds.Where(m => IsInCustody(m)).Count(); + totalInCustody += minds.Where(pair => IsInCustody(pair.Item1)).Count(); } var result = new StringBuilder(); @@ -104,19 +104,16 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev) } } - private void AddSummary(StringBuilder result, string agent, List minds) + private void AddSummary(StringBuilder result, string agent, List<(EntityUid, string)> minds) { var agentSummaries = new List<(string summary, float successRate, int completedObjectives)>(); - foreach (var mindId in minds) + foreach (var (mindId, name) in minds) { - if (!TryComp(mindId, out MindComponent? mind)) - continue; - - var title = GetTitle(mindId, mind); - if (title == null) + if (!TryComp(mindId, out var mind)) continue; + var title = GetTitle((mindId, mind), name); var custody = IsInCustody(mindId, mind) ? Loc.GetString("objectives-in-custody") : string.Empty; var objectives = mind.Objectives; @@ -238,34 +235,18 @@ private bool IsInCustody(EntityUid mindId, MindComponent? mind = null) /// /// Get the title for a player's mind used in round end. + /// Pass in the original entity name which is shown alongside username. /// - public string? GetTitle(EntityUid mindId, MindComponent? mind = null) + public string GetTitle(Entity mind, string name) { - if (!Resolve(mindId, ref mind)) - return null; - - var name = mind.CharacterName; - var username = (string?) null; - - if (mind.OriginalOwnerUserId != null && - _player.TryGetPlayerData(mind.OriginalOwnerUserId.Value, out var sessionData)) + if (Resolve(mind, ref mind.Comp) && + mind.Comp.OriginalOwnerUserId != null && + _player.TryGetPlayerData(mind.Comp.OriginalOwnerUserId.Value, out var sessionData)) { - username = sessionData.UserName; + var username = sessionData.UserName; + return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name)); } - - if (username != null) - { - if (name != null) - return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name)); - - return Loc.GetString("objectives-player-user", ("user", username)); - } - - // nothing to identify the player by, just give up - if (name == null) - return null; - return Loc.GetString("objectives-player-named", ("name", name)); } } @@ -279,7 +260,7 @@ private bool IsInCustody(EntityUid mindId, MindComponent? mind = null) /// The objectives system already checks if the game rule is added so you don't need to check that in this event's handler. /// [ByRefEvent] -public record struct ObjectivesTextGetInfoEvent(List Minds, string AgentName); +public record struct ObjectivesTextGetInfoEvent(List<(EntityUid, string)> Minds, string AgentName); /// /// Raised on the game rule before text for each agent's objectives is added, letting you prepend something. diff --git a/Resources/Locale/en-US/objectives/round-end.ftl b/Resources/Locale/en-US/objectives/round-end.ftl index b4314b2caff..3da81fc9640 100644 --- a/Resources/Locale/en-US/objectives/round-end.ftl +++ b/Resources/Locale/en-US/objectives/round-end.ftl @@ -6,7 +6,6 @@ objectives-round-end-result = {$count -> objectives-round-end-result-in-custody = {$custody} out of {$count} {MAKEPLURAL($agent)} were in custody. objectives-player-user-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color]) -objectives-player-user = [color=gray]{$user}[/color] objectives-player-named = [color=White]{$name}[/color] objectives-no-objectives = {$custody}{$title} was a {$agent}. diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 6b0a55ed366..5b03447e367 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -378,7 +378,7 @@ - id: MobGiantSpiderAngry prob: 0.05 -- type: entity +- type: entity id: SpiderClownSpawn parent: BaseGameRule noSpawn: true @@ -467,9 +467,9 @@ prototype: Nukeops #- type: entity # DeltaV - Currently many issues with this event -# id: SleeperAgentsRule -# parent: BaseGameRule # noSpawn: true +# parent: BaseTraitorRule +# id: SleeperAgentsRule # components: # - type: StationEvent # earliestStart: 30 @@ -480,7 +480,6 @@ # startAudio: # path: /Audio/Announcements/intercept.ogg # - type: AlertLevelInterceptionRule -# - type: TraitorRule # - type: AntagSelection # definitions: # - prefRoles: [ Traitor ] diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 3f1e8d4aacb..1b58ada6485 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -35,7 +35,19 @@ id: Thief components: - type: ThiefRule + - type: AntagObjectives + objectives: + - EscapeThiefShuttleObjective + - type: AntagRandomObjectives + sets: + - groups: ThiefBigObjectiveGroups + prob: 0.7 + maxPicks: 1 + - groups: ThiefObjectiveGroups + maxPicks: 10 + maxDifficulty: 2.5 - type: AntagSelection + agentName: thief-round-end-agent-name definitions: - prefRoles: [ Thief ] maxRange: @@ -53,14 +65,3 @@ prototype: Thief briefing: sound: "/Audio/Misc/thief_greeting.ogg" - -- type: entity - noSpawn: true - parent: BaseGameRule - id: Exterminator - components: - - type: GenericAntagRule - agentName: terminator-round-end-agent-name - objectives: - - TerminateObjective - - ShutDownObjective diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index d669f18cbea..800d854e89b 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -134,16 +134,30 @@ prototype: Nukeops - type: entity - id: Traitor + abstract: true parent: BaseGameRule + id: BaseTraitorRule + components: + - type: TraitorRule + # TODO: codewords in yml + # TODO: uplink in yml + - type: AntagRandomObjectives + sets: + - groups: TraitorObjectiveGroups + maxDifficulty: 5 + - type: AntagSelection + agentName: traitor-round-end-agent-name + +- type: entity noSpawn: true + parent: BaseTraitorRule + id: Traitor components: - type: GameRule minPlayers: 5 delay: min: 240 max: 420 - - type: TraitorRule - type: AntagSelection definitions: - prefRoles: [ Traitor ] diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 94104e1896c..8d0bf8866d3 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -63,13 +63,6 @@ ThiefObjectiveGroupStructure: 0 #Temporarily disabled until obvious ways to steal structures are added ThiefObjectiveGroupAnimal: 2 -- type: weightedRandom - id: ThiefEscapeObjectiveGroups - weights: - ThiefObjectiveGroupEscape: 1 - - - - type: weightedRandom id: ThiefObjectiveGroupCollection weights: From 1ecb083ffd46b6c505ce8f57145247ddc55b3f7e Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Sat, 25 May 2024 22:15:56 +0200 Subject: [PATCH 070/235] move nukie profile loading into its own rule (#28208) * move profile loading out of nukeops rule * make BaseNukeopsRule and use AntagLoadProfileRule * untroll --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Rules/AntagLoadProfileRuleSystem.cs | 39 +++++++++++++++++++ .../AntagLoadProfileRuleCOmponent.cs | 7 ++++ .../GameTicking/Rules/NukeopsRuleSystem.cs | 28 ------------- Resources/Prototypes/GameRules/events.yml | 4 +- Resources/Prototypes/GameRules/roundstart.yml | 16 ++++++-- 5 files changed, 60 insertions(+), 34 deletions(-) create mode 100644 Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs create mode 100644 Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs diff --git a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs new file mode 100644 index 00000000000..fd3fb6cd655 --- /dev/null +++ b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs @@ -0,0 +1,39 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Humanoid; +using Content.Server.Preferences.Managers; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Preferences; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Rules; + +public sealed class AntagLoadProfileRuleSystem : GameRuleSystem +{ + [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IServerPreferencesManager _prefs = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSelectEntity); + } + + private void OnSelectEntity(Entity ent, ref AntagSelectEntityEvent args) + { + if (args.Handled) + return; + + var profile = args.Session != null + ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile + : HumanoidCharacterProfile.RandomWithSpecies(); + if (profile?.Species is not {} speciesId || !_proto.TryIndex(speciesId, out var species)) + species = _proto.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); + + args.Entity = Spawn(species.Prototype); + _humanoid.LoadProfile(args.Entity.Value, profile); + } +} diff --git a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs new file mode 100644 index 00000000000..5e58fd14fc0 --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.GameTicking.Rules.Components; + +/// +/// Makes this rules antags spawn a humanoid, either from the player's profile or a random one. +/// +[RegisterComponent] +public sealed partial class AntagLoadProfileRuleComponent : Component; diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 232d24004b9..d6f1c3c619a 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -1,11 +1,9 @@ using Content.Server.Antag; using Content.Server.Communications; using Content.Server.GameTicking.Rules.Components; -using Content.Server.Humanoid; using Content.Server.Nuke; using Content.Server.NukeOps; using Content.Server.Popups; -using Content.Server.Preferences.Managers; using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Shuttles.Events; @@ -13,20 +11,16 @@ using Content.Server.Station.Components; using Content.Server.Store.Components; using Content.Server.Store.Systems; -using Content.Shared.Humanoid; -using Content.Shared.Humanoid.Prototypes; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.NPC.Components; using Content.Shared.NPC.Systems; using Content.Shared.Nuke; using Content.Shared.NukeOps; -using Content.Shared.Preferences; using Content.Shared.Store; using Content.Shared.Tag; using Content.Shared.Zombies; using Robust.Shared.Map; -using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; @@ -36,10 +30,7 @@ namespace Content.Server.GameTicking.Rules; public sealed class NukeopsRuleSystem : GameRuleSystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IServerPreferencesManager _prefs = default!; [Dependency] private readonly EmergencyShuttleSystem _emergency = default!; - [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; @@ -71,7 +62,6 @@ public override void Initialize() SubscribeLocalEvent(OnWarDeclared); SubscribeLocalEvent(OnShuttleCallAttempt); - SubscribeLocalEvent(OnAntagSelectEntity); SubscribeLocalEvent(OnAfterAntagEntSelected); } @@ -471,24 +461,6 @@ private void CheckRoundShouldEnd(Entity ent) nukeops.RoundEndBehavior = RoundEndBehavior.Nothing; } - // this should really go anywhere else but im tired. - private void OnAntagSelectEntity(Entity ent, ref AntagSelectEntityEvent args) - { - if (args.Handled) - return; - - var profile = args.Session != null - ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile - : HumanoidCharacterProfile.RandomWithSpecies(); - if (!_prototypeManager.TryIndex(profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species)) - { - species = _prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); - } - - args.Entity = Spawn(species.Prototype); - _humanoid.LoadProfile(args.Entity.Value, profile); - } - private void OnAfterAntagEntSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) { if (ent.Comp.TargetStation is not { } station) diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 5b03447e367..b8d55554a2b 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -432,9 +432,9 @@ prototype: InitialInfected - type: entity - id: LoneOpsSpawn - parent: BaseGameRule noSpawn: true + parent: BaseNukeopsRule + id: LoneOpsSpawn components: - type: StationEvent earliestStart: 60 # DeltaV - was 45 diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 800d854e89b..a198ee7bd07 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -64,17 +64,25 @@ roundEndDelay: 10 - type: entity - id: Nukeops + abstract: true parent: BaseGameRule - noSpawn: true + id: BaseNukeopsRule components: - - type: GameRule - minPlayers: 35 # DeltaV - Was 20 - type: RandomMetadata #this generates the random operation name cuz it's cool. nameSegments: - operationPrefix - operationSuffix - type: NukeopsRule + - type: AntagSelection + - type: AntagLoadProfileRule + +- type: entity + noSpawn: true + parent: BaseNukeopsRule + id: Nukeops + components: + - type: GameRule + minPlayers: 35 # DeltaV - Was 20 - type: LoadMapRule gameMap: NukieOutpost - type: AntagSelection From c4b0fcbd5c170d8640e9d768f19c74a453c07cc7 Mon Sep 17 00:00:00 2001 From: Repo <47093363+Titian3@users.noreply.github.com> Date: Sun, 26 May 2024 08:18:05 +1200 Subject: [PATCH 071/235] Fix gamerule display issues (#28178) * A comprehensive rule list for joining admins and mid round command to get rule list added * Fix up for when a rule is added vs started and some logging * fix command help localization, fix admin flags and spam anouncement. * Send admin message only to the joining player not all admins. * Bit better formatting in chat box --- Content.Server/Chat/Managers/ChatManager.cs | 8 ++ Content.Server/Chat/Managers/IChatManager.cs | 1 + .../GameTicking/GameTicker.GameRule.cs | 74 ++++++++++++++++++- .../GameTicking/GameTicker.Player.cs | 11 +++ .../GameTicking/Rules/SecretRuleSystem.cs | 1 - .../game-rules/gamerule-admin.ftl | 6 ++ .../game-ticking/game-rules/rule-secret.ftl | 2 - 7 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 Resources/Locale/en-US/game-ticking/game-rules/gamerule-admin.ftl delete mode 100644 Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index 812aed80bd7..79683db6411 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -150,6 +150,14 @@ public void SendAdminAnnouncement(string message, AdminFlags? flagBlacklist, Adm _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Admin announcement: {message}"); } + public void SendAdminAnnouncementMessage(ICommonSession player, string message, bool suppressLog = true) + { + var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", + ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), + ("message", FormattedMessage.EscapeText(message))); + ChatMessageToOne(ChatChannel.Admin, message, wrappedMessage, default, false, player.Channel); + } + public void SendAdminAlert(string message) { var clients = _adminManager.ActiveAdmins.Select(p => p.Channel); diff --git a/Content.Server/Chat/Managers/IChatManager.cs b/Content.Server/Chat/Managers/IChatManager.cs index 59945bf5ca6..c8c057a1ad7 100644 --- a/Content.Server/Chat/Managers/IChatManager.cs +++ b/Content.Server/Chat/Managers/IChatManager.cs @@ -23,6 +23,7 @@ public interface IChatManager void SendHookOOC(string sender, string message); void SendAdminAnnouncement(string message, AdminFlags? flagBlacklist = null, AdminFlags? flagWhitelist = null); + void SendAdminAnnouncementMessage(ICommonSession player, string message, bool suppressLog = true); void SendAdminAlert(string message); void SendAdminAlert(EntityUid player, string message); diff --git a/Content.Server/GameTicking/GameTicker.GameRule.cs b/Content.Server/GameTicking/GameTicker.GameRule.cs index f52a3cb296d..a6d0a4baf0a 100644 --- a/Content.Server/GameTicking/GameTicker.GameRule.cs +++ b/Content.Server/GameTicking/GameTicker.GameRule.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Server.Administration; using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Shared.Administration; using Content.Shared.Database; using Content.Shared.Prototypes; @@ -42,6 +43,14 @@ private void InitializeGameRules() string.Empty, "cleargamerules", ClearGameRulesCommand); + + // List game rules command. + var localizedHelp = Loc.GetString("listgamerules-command-help"); + + _consoleHost.RegisterCommand("listgamerules", + string.Empty, + $"listgamerules - {localizedHelp}", + ListGameRuleCommand); } private void ShutdownGameRules() @@ -49,6 +58,7 @@ private void ShutdownGameRules() _consoleHost.UnregisterCommand("addgamerule"); _consoleHost.UnregisterCommand("endgamerule"); _consoleHost.UnregisterCommand("cleargamerules"); + _consoleHost.UnregisterCommand("listgamerules"); } /// @@ -64,6 +74,13 @@ public EntityUid AddGameRule(string ruleId) var ev = new GameRuleAddedEvent(ruleEntity, ruleId); RaiseLocalEvent(ruleEntity, ref ev, true); + + var currentTime = RunLevel == GameRunLevel.PreRoundLobby ? TimeSpan.Zero : RoundDuration(); + if (!HasComp(ruleEntity) && !HasComp(ruleEntity)) + { + _allPreviousGameRules.Add((currentTime, ruleId + " (Pending)")); + } + return ruleEntity; } @@ -110,7 +127,8 @@ public bool StartGameRule(EntityUid ruleEntity, GameRuleComponent? ruleData = nu if (delayTime > TimeSpan.Zero) { _sawmill.Info($"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}"); - _adminLogger.Add(LogType.EventStarted, $"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}"); + _adminLogger.Add(LogType.EventStarted, + $"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}"); var delayed = EnsureComp(ruleEntity); delayed.RuleStartTime = _gameTiming.CurTime + (delayTime); @@ -118,7 +136,20 @@ public bool StartGameRule(EntityUid ruleEntity, GameRuleComponent? ruleData = nu } } - _allPreviousGameRules.Add((RoundDuration(), id)); + var currentTime = RunLevel == GameRunLevel.PreRoundLobby ? TimeSpan.Zero : RoundDuration(); + + // Remove the first occurrence of the pending entry before adding the started entry + var pendingRuleIndex = _allPreviousGameRules.FindIndex(rule => rule.Item2 == id + " (Pending)"); + if (pendingRuleIndex >= 0) + { + _allPreviousGameRules.RemoveAt(pendingRuleIndex); + } + + if (!HasComp(ruleEntity) && !HasComp(ruleEntity)) + { + _allPreviousGameRules.Add((currentTime, id)); + } + _sawmill.Info($"Started game rule {ToPrettyString(ruleEntity)}"); _adminLogger.Add(LogType.EventStarted, $"Started game rule {ToPrettyString(ruleEntity)}"); @@ -296,6 +327,7 @@ private void AddGameRuleCommand(IConsoleShell shell, string argstr, string[] arg if (shell.Player != null) { _adminLogger.Add(LogType.EventStarted, $"{shell.Player} tried to add game rule [{rule}] via command"); + _chatManager.SendAdminAnnouncement(Loc.GetString("add-gamerule-admin", ("rule", rule), ("admin", shell.Player))); } else { @@ -306,6 +338,7 @@ private void AddGameRuleCommand(IConsoleShell shell, string argstr, string[] arg // Start rule if we're already in the middle of a round if(RunLevel == GameRunLevel.InRound) StartGameRule(ent); + } } @@ -349,5 +382,42 @@ private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] ClearGameRules(); } + [AdminCommand(AdminFlags.Admin)] + private void ListGameRuleCommand(IConsoleShell shell, string argstr, string[] args) + { + _sawmill.Info($"{shell.Player} tried to get list of game rules via command"); + _adminLogger.Add(LogType.Action, $"{shell.Player} tried to get list of game rules via command"); + var message = GetGameRulesListMessage(false); + shell.WriteLine(message); + } + private string GetGameRulesListMessage(bool forChatWindow) + { + if (_allPreviousGameRules.Count > 0) + { + var sortedRules = _allPreviousGameRules.OrderBy(rule => rule.Item1).ToList(); + var message = "\n"; + + if (!forChatWindow) + { + var header = Loc.GetString("list-gamerule-admin-header"); + message += $"\n{header}\n"; + message += "|------------|------------------\n"; + } + + foreach (var (time, rule) in sortedRules) + { + var formattedTime = time.ToString(@"hh\:mm\:ss"); + message += $"| {formattedTime,-10} | {rule,-16} \n"; + } + + return message; + } + else + { + return Loc.GetString("list-gamerule-admin-no-rules"); + + } + } + #endregion } diff --git a/Content.Server/GameTicking/GameTicker.Player.cs b/Content.Server/GameTicking/GameTicker.Player.cs index dcf2dc5366d..5b8b03b248c 100644 --- a/Content.Server/GameTicking/GameTicker.Player.cs +++ b/Content.Server/GameTicking/GameTicker.Player.cs @@ -1,4 +1,6 @@ +using System.Linq; using Content.Server.Database; +using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.GameWindow; @@ -197,6 +199,15 @@ public void PlayerJoinGame(ICommonSession session, bool silent = false) _playerGameStatuses[session.UserId] = PlayerGameStatus.JoinedGame; _db.AddRoundPlayers(RoundId, session.UserId); + if (_adminManager.HasAdminFlag(session, AdminFlags.Admin)) + { + if (_allPreviousGameRules.Count > 0) + { + var rulesMessage = GetGameRulesListMessage(true); + _chatManager.SendAdminAnnouncementMessage(session, Loc.GetString("starting-rule-selected-preset", ("preset", rulesMessage))); + } + } + RaiseNetworkEvent(new TickerJoinGameEvent(), session.Channel); } diff --git a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs index 95bf5986a5a..d25262b797a 100644 --- a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs @@ -46,7 +46,6 @@ protected override void Added(EntityUid uid, SecretRuleComponent component, Game Log.Info($"Selected {preset.ID} as the secret preset."); _adminLogger.Add(LogType.EventStarted, $"Selected {preset.ID} as the secret preset."); - _chatManager.SendAdminAnnouncement(Loc.GetString("rule-secret-selected-preset", ("preset", preset.ID))); foreach (var rule in preset.Rules) { diff --git a/Resources/Locale/en-US/game-ticking/game-rules/gamerule-admin.ftl b/Resources/Locale/en-US/game-ticking/game-rules/gamerule-admin.ftl new file mode 100644 index 00000000000..3b31fe46630 --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-rules/gamerule-admin.ftl @@ -0,0 +1,6 @@ +#When an admin adds a game rule +add-gamerule-admin = Game rule({$rule}) added - {$admin} +list-gamerule-admin-header = | Time | Rule added +list-gamerule-admin-no-rules = No game rules have been added. +starting-rule-selected-preset = Current gamerules in use: {$preset} +listgamerules-command-help = Lists all rules that have been added for the round so far. diff --git a/Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl b/Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl deleted file mode 100644 index c38220cca1d..00000000000 --- a/Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl +++ /dev/null @@ -1,2 +0,0 @@ -# Sent to admin chat -rule-secret-selected-preset = Selected {$preset} for secret. From 671105384d2e58854cdd4780109d613b6843d6f8 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Sat, 25 May 2024 23:18:40 +0300 Subject: [PATCH 072/235] Tomato killers don't kill the server anymore. (#28173) * tomato killer auto death * fix * Update miscellaneous.yml --- .../Entities/Mobs/NPCs/miscellaneous.yml | 27 ++++++++++++++++--- .../Entities/Objects/Consumable/Food/meat.yml | 4 +++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml index f77429d5978..e09d3917c75 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml @@ -114,13 +114,22 @@ - trigger: !type:DamageTypeTrigger damageType: Blunt - damage: 100 + damage: 40 behaviors: - - !type:GibBehavior { } + - !type:SpawnEntitiesBehavior + spawn: + FoodMeatTomato: + min: 1 + max: 2 + - !type:DoActsBehavior + acts: ["Destruction"] + - !type:PlaySoundBehavior + sound: + collection: gib - type: MobThresholds thresholds: 0: Alive - 24: Dead + 35: Dead - type: Fixtures fixtures: fix1: @@ -136,7 +145,7 @@ hidden: true damage: groups: - Brute: 4 + Brute: 9 animation: WeaponArcBite - type: Climbing - type: NameIdentifier @@ -156,3 +165,13 @@ - type: Appearance - type: Produce seedId: killerTomato + - type: PassiveDamage # Slight passive damage. 35 hp \ 5 min \ 60 sec = 0.08 + allowedStates: + - Alive + - Dead + damageCap: 50 + damage: + types: + Blunt: 0.11 + - type: StaticPrice + price: 400 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml index 58af9cf3bd8..ac3cd1318fb 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml @@ -599,6 +599,8 @@ - type: SliceableFood count: 3 slice: FoodMeatTomatoCutlet + - type: StaticPrice + price: 100 - type: entity name: salami @@ -1267,6 +1269,8 @@ - type: Sprite state: salami-slice color: red + - type: StaticPrice + price: 30 - type: entity name: salami slice From c9372096a025b7e80ff4920a3cc4cc1a02431094 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 25 May 2024 20:19:46 +0000 Subject: [PATCH 073/235] Automatic changelog update --- Resources/Changelog/Admin.yml | 11 +++++++++++ Resources/Changelog/Changelog.yml | 20 ++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index f4f19277f48..1c810bf1601 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -235,5 +235,16 @@ Entries: id: 29 time: '2024-05-19T23:04:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28107 +- author: Repo + changes: + - message: Added listgamerules command, lists all run game modes for the round. + type: Add + - message: Added admin anouncement for addgamerule + type: Add + - message: Gamemode now shows after starting round. + type: Fix + id: 30 + time: '2024-05-25T20:18:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28178 Name: Admin Order: 3 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 22c9a547de7..1a290ea8c39 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: nikthechampiongr - changes: - - message: Shields will no longer absorb asphyxiation damage, or any other damage - they themselves can't take. - type: Fix - id: 6118 - time: '2024-03-11T01:55:19.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25972 - author: metalgearsloth changes: - message: Remove the buttons for generated debris from shuttle maps. @@ -3871,3 +3863,15 @@ id: 6617 time: '2024-05-25T20:08:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28237 +- author: TheShuEd + changes: + - message: 'Killer tomatoes got a small health and damage buff: 24 -> 35 hp and + 4->9 brute damage' + type: Tweak + - message: Killer tomatoes now die after 5 minutes of their existence + type: Tweak + - message: Killer tomatoes can now be profitably sold. + type: Add + id: 6618 + time: '2024-05-25T20:18:40.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28173 From c6b279ef80193aed5eec5f517c413aa1ec7af2e7 Mon Sep 17 00:00:00 2001 From: MilenVolf <63782763+MilenVolf@users.noreply.github.com> Date: Sat, 25 May 2024 23:20:43 +0300 Subject: [PATCH 074/235] Add direction relative to station for emergency shuttle's docking & nearby announcement (#28164) * Use nav beacon location for emergency shuttle's docking announcement Location of the shuttle relative to the nearest nav beacon in docking announcement message of the emergency shuttle * Add directions relative to station --- .../Shuttles/Systems/EmergencyShuttleSystem.cs | 11 ++++++++--- Resources/Locale/en-US/shuttles/emergency.ftl | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 2d8ae4b735e..e2b1ad32cd7 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -287,7 +287,8 @@ public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleCo if (TryComp(targetGrid.Value, out TransformComponent? targetXform)) { var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); - _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false); + var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform))); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir()), ("location", location)), playDefaultSound: false); } // shuttle timers @@ -313,8 +314,12 @@ public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleCo } else { - var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform))); - _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("direction", location)), playDefaultSound: false); + if (TryComp(targetGrid.Value, out var targetXform)) + { + var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); + var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform))); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir()), ("location", location)), playDefaultSound: false); + } _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}"); // TODO: Need filter extensions or something don't blame me. diff --git a/Resources/Locale/en-US/shuttles/emergency.ftl b/Resources/Locale/en-US/shuttles/emergency.ftl index c7162911351..2fa3a7a1240 100644 --- a/Resources/Locale/en-US/shuttles/emergency.ftl +++ b/Resources/Locale/en-US/shuttles/emergency.ftl @@ -13,9 +13,9 @@ emergency-shuttle-command-launch-desc = Early launches the emergency shuttle if # Emergency shuttle emergency-shuttle-left = The Emergency Shuttle has left the station. Estimate {$transitTime} seconds until the shuttle arrives at CentCom. emergency-shuttle-launch-time = The emergency shuttle will launch in {$consoleAccumulator} seconds. -emergency-shuttle-docked = The Emergency Shuttle has docked with the station on the {$direction} side. It will leave in {$time} seconds. +emergency-shuttle-docked = The Emergency Shuttle has docked {$direction} of the station, {$location}. It will leave in {$time} seconds. emergency-shuttle-good-luck = The Emergency Shuttle is unable to find a station. Good luck. -emergency-shuttle-nearby = The Emergency Shuttle is unable to find a valid docking port. It has warped {$direction}. +emergency-shuttle-nearby = The Emergency Shuttle is unable to find a valid docking port. It has warped in {$direction} of the station, {$location}. # Emergency shuttle console popup / announcement emergency-shuttle-console-no-early-launches = Early launch is disabled From 5a9c7073c2f32a04f1e121c2f349e562ac0a0306 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Sat, 25 May 2024 23:22:34 +0300 Subject: [PATCH 075/235] Fix candles (firestack fading) (#28139) Update FlammableSystem.cs --- Content.Server/Atmos/EntitySystems/FlammableSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index e8721920dd8..b6e26435a75 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -447,7 +447,7 @@ public override void Update(float frameTime) _damageableSystem.TryChangeDamage(uid, flammable.Damage * flammable.FireStacks * ev.Multiplier, interruptsDoAfters: false); - AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable); + AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable, flammable.OnFire); } else { From 9e350a452c382269cd16de88a0e461f45b2b3e22 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 26 May 2024 06:23:34 +1000 Subject: [PATCH 076/235] Fix water postshader (#28130) --- .../Movement/Systems/FloorOcclusionSystem.cs | 41 +++++++++++-------- .../Components/FloorOcclusionComponent.cs | 9 ++-- .../Systems/SharedFloorOcclusionSystem.cs | 24 +++++------ 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Content.Client/Movement/Systems/FloorOcclusionSystem.cs b/Content.Client/Movement/Systems/FloorOcclusionSystem.cs index 5c75f25ca2d..44573f8e084 100644 --- a/Content.Client/Movement/Systems/FloorOcclusionSystem.cs +++ b/Content.Client/Movement/Systems/FloorOcclusionSystem.cs @@ -10,51 +10,56 @@ public sealed class FloorOcclusionSystem : SharedFloorOcclusionSystem { [Dependency] private readonly IPrototypeManager _proto = default!; + private EntityQuery _spriteQuery; + public override void Initialize() { base.Initialize(); + + _spriteQuery = GetEntityQuery(); + SubscribeLocalEvent(OnOcclusionStartup); + SubscribeLocalEvent(OnOcclusionShutdown); SubscribeLocalEvent(OnOcclusionAuto); } - private void OnOcclusionAuto(EntityUid uid, FloorOcclusionComponent component, ref AfterAutoHandleStateEvent args) + private void OnOcclusionAuto(Entity ent, ref AfterAutoHandleStateEvent args) { - SetEnabled(uid, component, component.Enabled); + SetShader(ent.Owner, ent.Comp.Enabled); } - private void OnOcclusionStartup(EntityUid uid, FloorOcclusionComponent component, ComponentStartup args) + private void OnOcclusionStartup(Entity ent, ref ComponentStartup args) { - if (component.Enabled && TryComp(uid, out var sprite)) - SetShader(sprite, true); + SetShader(ent.Owner, ent.Comp.Enabled); } - protected override void SetEnabled(EntityUid uid, FloorOcclusionComponent component, bool enabled) + private void OnOcclusionShutdown(Entity ent, ref ComponentShutdown args) { - if (component.Enabled == enabled) - return; - - base.SetEnabled(uid, component, enabled); - - if (!TryComp(uid, out var sprite)) - return; + SetShader(ent.Owner, false); + } - SetShader(sprite, enabled); + protected override void SetEnabled(Entity entity) + { + SetShader(entity.Owner, entity.Comp.Enabled); } - private void SetShader(SpriteComponent sprite, bool enabled) + private void SetShader(Entity sprite, bool enabled) { + if (!_spriteQuery.Resolve(sprite.Owner, ref sprite.Comp, false)) + return; + var shader = _proto.Index("HorizontalCut").Instance(); - if (sprite.PostShader is not null && sprite.PostShader != shader) + if (sprite.Comp.PostShader is not null && sprite.Comp.PostShader != shader) return; if (enabled) { - sprite.PostShader = shader; + sprite.Comp.PostShader = shader; } else { - sprite.PostShader = null; + sprite.Comp.PostShader = null; } } } diff --git a/Content.Shared/Movement/Components/FloorOcclusionComponent.cs b/Content.Shared/Movement/Components/FloorOcclusionComponent.cs index aa9a1ced552..5d412f694a0 100644 --- a/Content.Shared/Movement/Components/FloorOcclusionComponent.cs +++ b/Content.Shared/Movement/Components/FloorOcclusionComponent.cs @@ -8,12 +8,9 @@ namespace Content.Shared.Movement.Components; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class FloorOcclusionComponent : Component { - /// - /// Is the shader currently enabled. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("enabled"), AutoNetworkedField] - public bool Enabled; + [ViewVariables] + public bool Enabled => Colliding.Count > 0; - [DataField("colliding")] + [DataField, AutoNetworkedField] public List Colliding = new(); } diff --git a/Content.Shared/Movement/Systems/SharedFloorOcclusionSystem.cs b/Content.Shared/Movement/Systems/SharedFloorOcclusionSystem.cs index 9d27ea42c63..6b7023a1c64 100644 --- a/Content.Shared/Movement/Systems/SharedFloorOcclusionSystem.cs +++ b/Content.Shared/Movement/Systems/SharedFloorOcclusionSystem.cs @@ -15,39 +15,37 @@ public override void Initialize() SubscribeLocalEvent(OnEndCollide); } - private void OnStartCollide(EntityUid uid, FloorOccluderComponent component, ref StartCollideEvent args) + private void OnStartCollide(Entity entity, ref StartCollideEvent args) { var other = args.OtherEntity; if (!TryComp(other, out var occlusion) || - occlusion.Colliding.Contains(uid)) + occlusion.Colliding.Contains(entity.Owner)) { return; } - SetEnabled(other, occlusion, true); - occlusion.Colliding.Add(uid); + occlusion.Colliding.Add(entity.Owner); + Dirty(other, occlusion); + SetEnabled((other, occlusion)); } - private void OnEndCollide(EntityUid uid, FloorOccluderComponent component, ref EndCollideEvent args) + private void OnEndCollide(Entity entity, ref EndCollideEvent args) { var other = args.OtherEntity; if (!TryComp(other, out var occlusion)) return; - occlusion.Colliding.Remove(uid); + if (!occlusion.Colliding.Remove(entity.Owner)) + return; - if (occlusion.Colliding.Count == 0) - SetEnabled(other, occlusion, false); + Dirty(other, occlusion); + SetEnabled((other, occlusion)); } - protected virtual void SetEnabled(EntityUid uid, FloorOcclusionComponent component, bool enabled) + protected virtual void SetEnabled(Entity entity) { - if (component.Enabled == enabled) - return; - component.Enabled = enabled; - Dirty(uid, component); } } From 80acbe400cb37b4dbf472d1cde4975851640105e Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 25 May 2024 20:24:40 +0000 Subject: [PATCH 077/235] 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 1a290ea8c39..7880f0a8d13 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Remove the buttons for generated debris from shuttle maps. - type: Tweak - id: 6119 - time: '2024-03-11T02:11:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25897 - author: Errant changes: - message: Species info is now available in the Guidebook and in the character editor. @@ -3875,3 +3868,10 @@ id: 6618 time: '2024-05-25T20:18:40.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28173 +- author: metalgearsloth + changes: + - message: Fix water shader getting stuck on sometimes. + type: Fix + id: 6619 + time: '2024-05-25T20:23:34.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28130 From 6eef3dcea3e0d4e57ba44dfc2f9c47b2761dfcb7 Mon Sep 17 00:00:00 2001 From: eoineoineoin Date: Sat, 25 May 2024 21:26:48 +0100 Subject: [PATCH 078/235] Objects ordered through cargo system shouldn't start anchored (#28115) * Order normal space heater instead of anchored variant * Make sure ordered objects don't spawn anchored * Order space heater flatpack instead of a regular space heater * Remove obsolete TODO * Remove unnecessary name --------- Co-authored-by: Eoin Mcloughlin --- Content.Server/Cargo/Systems/CargoSystem.Orders.cs | 5 +++++ .../Prototypes/Catalog/Cargo/cargo_engineering.yml | 2 +- .../Prototypes/Catalog/Fills/Crates/engineering.yml | 10 ++++++++++ Resources/Prototypes/Catalog/Fills/Crates/engines.yml | 2 +- .../Prototypes/Entities/Objects/Devices/flatpack.yml | 9 +++++++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs index 8978e6b2bfb..fe789194917 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs @@ -20,6 +20,8 @@ namespace Content.Server.Cargo.Systems { public sealed partial class CargoSystem { + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + /// /// How much time to wait (in seconds) before increasing bank accounts balance. /// @@ -491,6 +493,9 @@ private bool FulfillOrder(CargoOrderData order, EntityCoordinates spawn, string? // Create the item itself var item = Spawn(order.ProductId, spawn); + // Ensure the item doesn't start anchored + _transformSystem.Unanchor(item, Transform(item)); + // Create a sheet of paper to write the order details on var printed = EntityManager.SpawnEntity(paperProto, spawn); if (TryComp(printed, out var paper)) diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml b/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml index 754e30f133a..7ca6af84518 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml @@ -133,7 +133,7 @@ icon: sprite: Structures/Piping/Atmospherics/Portable/portable_sheater.rsi state: sheaterOff - product: SpaceHeaterAnchored + product: CrateEngineeringSpaceHeater cost: 300 category: cargoproduct-category-name-engineering group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml b/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml index 03c870fa580..26a8910c735 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml @@ -194,3 +194,13 @@ contents: - id: WeaponParticleDecelerator amount: 3 + +- type: entity + id: CrateEngineeringSpaceHeater + parent: CrateEngineering + name: space heater crate + description: Contains a space heater for climate control. + components: + - type: StorageFill + contents: + - id: SpaceHeaterFlatpack diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml index 79698b550a7..c37b7b7535a 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml @@ -42,7 +42,7 @@ components: - type: StorageFill contents: - - id: EmitterFlatpack # TODO change to flatpack + - id: EmitterFlatpack - type: entity id: CrateEngineeringSingularityCollector diff --git a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml index 2aecd132880..e3e77d5c88e 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml @@ -195,3 +195,12 @@ - state: overlay color: "#cec8ac" - state: icon-default + +- type: entity + parent: BaseFlatpack + id: SpaceHeaterFlatpack + name: space heater flatpack + description: A flatpack used for constructing a space heater. + components: + - type: Flatpack + entity: SpaceHeaterAnchored From 78dbdfa00c6b30d113b0b421fd420d33ec7fd9a8 Mon Sep 17 00:00:00 2001 From: Ady4ik <141335742+Ady4ik@users.noreply.github.com> Date: Sun, 26 May 2024 00:07:18 +0300 Subject: [PATCH 079/235] Move PendingZombieComponent to Shared (#28143) * Move PendingZombieComponent to Shared * network me boy --------- Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> --- .../Chemistry/ReagentEffects/CauseZombieInfection.cs | 1 + .../Chemistry/ReagentEffects/CureZombieInfection.cs | 1 + .../Zombies/PendingZombieComponent.cs | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) rename {Content.Server => Content.Shared}/Zombies/PendingZombieComponent.cs (94%) diff --git a/Content.Server/Chemistry/ReagentEffects/CauseZombieInfection.cs b/Content.Server/Chemistry/ReagentEffects/CauseZombieInfection.cs index 029b1495002..96c57f74653 100644 --- a/Content.Server/Chemistry/ReagentEffects/CauseZombieInfection.cs +++ b/Content.Server/Chemistry/ReagentEffects/CauseZombieInfection.cs @@ -2,6 +2,7 @@ using Content.Shared.Chemistry.Reagent; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; +using Content.Shared.Zombies; namespace Content.Server.Chemistry.ReagentEffects; diff --git a/Content.Server/Chemistry/ReagentEffects/CureZombieInfection.cs b/Content.Server/Chemistry/ReagentEffects/CureZombieInfection.cs index d56fc115310..20e2c015c46 100644 --- a/Content.Server/Chemistry/ReagentEffects/CureZombieInfection.cs +++ b/Content.Server/Chemistry/ReagentEffects/CureZombieInfection.cs @@ -2,6 +2,7 @@ using Content.Shared.Chemistry.Reagent; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; +using Content.Shared.Zombies; namespace Content.Server.Chemistry.ReagentEffects; diff --git a/Content.Server/Zombies/PendingZombieComponent.cs b/Content.Shared/Zombies/PendingZombieComponent.cs similarity index 94% rename from Content.Server/Zombies/PendingZombieComponent.cs rename to Content.Shared/Zombies/PendingZombieComponent.cs index 811d3f96440..0fb61c84df1 100644 --- a/Content.Server/Zombies/PendingZombieComponent.cs +++ b/Content.Shared/Zombies/PendingZombieComponent.cs @@ -1,12 +1,13 @@ using Content.Shared.Damage; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.Zombies; +namespace Content.Shared.Zombies; /// /// Temporary because diseases suck. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class PendingZombieComponent : Component { /// From 94375b73ce0e3008b1bdf40f521628ed5677e7a8 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 25 May 2024 14:07:27 -0700 Subject: [PATCH 080/235] Make it possible to hide full health bars below a total damage threshold (#28127) * Make it possible to hide full health bars below a total damage threshold * Fix not setting state --- .../Overlays/EntityHealthBarOverlay.cs | 20 +++++++------- .../Damage/Components/DamageableComponent.cs | 26 +++++++++++-------- .../Damage/Systems/DamageableSystem.cs | 6 ++--- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index c1c0ae93ec1..2b2ff14a22b 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -1,15 +1,14 @@ +using System.Numerics; +using Content.Client.UserInterface.Systems; using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.StatusIcon.Components; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; -using System.Numerics; -using Content.Shared.StatusIcon.Components; -using Content.Client.UserInterface.Systems; -using Robust.Shared.Prototypes; using static Robust.Shared.Maths.Color; namespace Content.Client.Overlays; @@ -79,6 +78,10 @@ protected override void Draw(in OverlayDrawArgs args) continue; } + // we are all progressing towards death every day + if (CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent) is not { } deathProgress) + continue; + var worldPosition = _transform.GetWorldPosition(xform); var worldMatrix = Matrix3.CreateTranslation(worldPosition); @@ -91,10 +94,6 @@ protected override void Draw(in OverlayDrawArgs args) var widthOfMob = bounds.Width * EyeManager.PixelsPerMeter; var position = new Vector2(-widthOfMob / EyeManager.PixelsPerMeter / 2, yOffset / EyeManager.PixelsPerMeter); - - // we are all progressing towards death every day - (float ratio, bool inCrit) deathProgress = CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent); - var color = GetProgressColor(deathProgress.ratio, deathProgress.inCrit); // Hardcoded width of the progress bar because it doesn't match the texture. @@ -122,10 +121,13 @@ protected override void Draw(in OverlayDrawArgs args) /// /// Returns a ratio between 0 and 1, and whether the entity is in crit. /// - private (float, bool) CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds) + private (float ratio, bool inCrit)? CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds) { if (_mobStateSystem.IsAlive(uid, component)) { + if (dmg.HealthBarThreshold != null && dmg.TotalDamage < dmg.HealthBarThreshold) + return null; + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) && !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds)) return (1, false); diff --git a/Content.Shared/Damage/Components/DamageableComponent.cs b/Content.Shared/Damage/Components/DamageableComponent.cs index be66d51e3bc..f8205568f10 100644 --- a/Content.Shared/Damage/Components/DamageableComponent.cs +++ b/Content.Shared/Damage/Components/DamageableComponent.cs @@ -5,8 +5,6 @@ using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Damage { @@ -18,7 +16,7 @@ namespace Content.Shared.Damage /// may also have resistances to certain damage types, defined via a . /// [RegisterComponent] - [NetworkedComponent()] + [NetworkedComponent] [Access(typeof(DamageableSystem), Other = AccessPermissions.ReadExecute)] public sealed partial class DamageableComponent : Component { @@ -26,8 +24,8 @@ public sealed partial class DamageableComponent : Component /// This specifies what damage types are supported by this component. /// If null, all damage types will be supported. /// - [DataField("damageContainer", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? DamageContainerID; + [DataField("damageContainer")] + public ProtoId? DamageContainerID; /// /// This will be applied to any damage that is dealt to this container, @@ -37,8 +35,8 @@ public sealed partial class DamageableComponent : Component /// Though DamageModifierSets can be deserialized directly, we only want to use the prototype version here /// to reduce duplication. /// - [DataField("damageModifierSet", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? DamageModifierSetId; + [DataField("damageModifierSet")] + public ProtoId? DamageModifierSetId; /// /// All the damage information is stored in this . @@ -46,7 +44,7 @@ public sealed partial class DamageableComponent : Component /// /// If this data-field is specified, this allows damageable components to be initialized with non-zero damage. /// - [DataField("damage", readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier + [DataField(readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier public DamageSpecifier Damage = new(); /// @@ -64,8 +62,8 @@ public sealed partial class DamageableComponent : Component [ViewVariables] public FixedPoint2 TotalDamage; - [DataField("radiationDamageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List RadiationDamageTypeIDs = new() { "Radiation" }; + [DataField("radiationDamageTypes")] + public List> RadiationDamageTypeIDs = new() { "Radiation" }; [DataField] public Dictionary> HealthIcons = new() @@ -77,6 +75,9 @@ public sealed partial class DamageableComponent : Component [DataField] public ProtoId RottingIcon = "HealthIconRotting"; + + [DataField] + public FixedPoint2? HealthBarThreshold; } [Serializable, NetSerializable] @@ -84,13 +85,16 @@ public sealed class DamageableComponentState : ComponentState { public readonly Dictionary DamageDict; public readonly string? ModifierSetId; + public readonly FixedPoint2? HealthBarThreshold; public DamageableComponentState( Dictionary damageDict, - string? modifierSetId) + string? modifierSetId, + FixedPoint2? healthBarThreshold) { DamageDict = damageDict; ModifierSetId = modifierSetId; + HealthBarThreshold = healthBarThreshold; } } } diff --git a/Content.Shared/Damage/Systems/DamageableSystem.cs b/Content.Shared/Damage/Systems/DamageableSystem.cs index 4aaf380c47d..3c3e1b736df 100644 --- a/Content.Shared/Damage/Systems/DamageableSystem.cs +++ b/Content.Shared/Damage/Systems/DamageableSystem.cs @@ -1,5 +1,4 @@ using System.Linq; -using Content.Shared.Administration.Logs; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; using Content.Shared.Inventory; @@ -229,12 +228,12 @@ private void DamageableGetState(EntityUid uid, DamageableComponent component, re { if (_netMan.IsServer) { - args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId); + args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId, component.HealthBarThreshold); } else { // avoid mispredicting damage on newly spawned entities. - args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageModifierSetId); + args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageModifierSetId, component.HealthBarThreshold); } } @@ -268,6 +267,7 @@ private void DamageableHandleState(EntityUid uid, DamageableComponent component, } component.DamageModifierSetId = state.ModifierSetId; + component.HealthBarThreshold = state.HealthBarThreshold; // Has the damage actually changed? DamageSpecifier newDamage = new() { DamageDict = new(state.DamageDict) }; From 0f131bae6062704f5dd33b720605171b10b005da Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:09:44 +0200 Subject: [PATCH 081/235] Add delta access to the admin cards --- .../Entities/Markers/Spawners/ghost_roles.yml | 17 ----------------- .../Objects/Misc/identification_cards.yml | 2 +- .../Objects/Tools/access_configurator.yml | 5 +++-- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index 530e0abe146..47303d6586f 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -149,20 +149,3 @@ - state: green - sprite: Objects/Weapons/Melee/energykatana.rsi state: icon - -- type: entity - parent: MarkerBase - id: SpawnPointGhostTerminator - name: terminator spawn point - components: - - type: GhostRole - name: ghost-role-information-exterminator-name - description: ghost-role-information-exterminator-description - rules: ghost-role-information-exterminator-rules - - type: GhostRoleMobSpawner - prototype: MobHumanTerminator - - type: Sprite - layers: - - state: green - - sprite: Mobs/Species/Terminator/parts.rsi - state: full diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index 6b70ecc805e..68e6cdfd516 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -883,4 +883,4 @@ - CentralCommand - NuclearOperative - SyndicateAgent - - DV-SpareSafe + - DV-SpareSafe # DeltaV diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index f6cbb3ea6bb..21295333b6b 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -102,7 +102,7 @@ - Armory - Atmospherics - Bar - - Brig + #- Brig - Detective - Captain - Cargo @@ -131,7 +131,8 @@ - CentralCommand - NuclearOperative - SyndicateAgent - - DV-SpareSafe + - DV-SpareSafe # DeltaV + - Orders # DeltaV privilegedIdSlot: name: id-card-console-privileged-id ejectSound: /Audio/Machines/id_swipe.ogg From 8c6e708c3cca6a1b3d8d2f8f54cb96da9013a624 Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:09:51 +0200 Subject: [PATCH 082/235] Fix linter error --- .../Prototypes/DeltaV/Entities/Structures/Walls/mountain.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Walls/mountain.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Walls/mountain.yml index 575687336ad..7e1a769a1e0 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Walls/mountain.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Walls/mountain.yml @@ -6,7 +6,7 @@ description: A rocky asteroid. components: - type: Gatherable - whitelist: + toolWhitelist: tags: - Pickaxe - type: Sprite @@ -111,7 +111,7 @@ description: A rocky asteroid. components: - type: Gatherable - whitelist: + toolWhitelist: tags: - Pickaxe - type: OreVein From ed3e930117f32c73a856f00dcf603c0324844664 Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:10:04 +0200 Subject: [PATCH 083/235] Remove some remaining terminator stuff --- .../Entities/Mobs/Player/terminator.yml | 101 ------------------ 1 file changed, 101 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/terminator.yml b/Resources/Prototypes/Entities/Mobs/Player/terminator.yml index 1f187e9b864..122574c23aa 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/terminator.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/terminator.yml @@ -6,107 +6,6 @@ - type: ZombieImmune # yeah no - type: FlashImmunity # no brain to brainwash, eyes are robotic -- type: entity - parent: [MobHuman, MobTerminatorBase] - id: MobHumanTerminator - # uses random name generator dont worry - name: exterminator - components: - - type: Terminator - - type: GenericAntag - rule: Exterminator - # reduced barotrauma damage - - type: Barotrauma - damage: - types: - Blunt: 0.1 - # 4x stamina, faster recovery - - type: Stamina - decay: 6 - cooldown: 1 - critThreshold: 400 - # immune to space drugs, pax, temporary blindness - - type: StatusEffects - allowed: - - Stun - - KnockedDown - - SlowedDown - - Stutter - - Electrocution - - Drunk - - SlurredSpeech - - RatvarianLanguage - - PressureImmunity - - Muted - - ForcedSleep - - StaminaModifier - - type: MobState - allowedStates: - - Alive - - Dead - # endoskeleton need it - - type: TransferMindOnGib - - type: MobThresholds - thresholds: - 0: Alive - # used for health display its not possible to actually fall into crit - 200: Dead - # fire!!!! - - type: Flammable - damage: - types: - Heat: 6.0 - # slightly wider thresholds - - type: Temperature - heatDamageThreshold: 390 - coldDamageThreshold: 240 - # take terminator flesh damage - - type: Damageable - damageModifierSet: CyberneticFlesh - # only organ is an endoskeleton, which is transferred when flesh dies - - type: Body - prototype: TerminatorFlesh - # endoskeleton transformation when either you would get burned to crit or killed by any damage - # you will become an endoskeleton as your last chance to kill the target - - type: Destructible - thresholds: - # the burn trigger is first incase of a bombing or nuking, it might well do over 200 damage but 100 heat is more important - - trigger: - !type:DamageTypeTrigger - damageType: Heat - damage: 100 - behaviors: - - !type:PopupBehavior - popup: terminator-endoskeleton-burn-popup - popupType: LargeCaution - - !type:GibBehavior - - trigger: - !type:DamageTrigger - damage: 200 - behaviors: - - !type:PopupBehavior - popup: terminator-endoskeleton-gib-popup - popupType: LargeCaution - - !type:GibBehavior - # faster than humans when damaged - - type: SlowOnDamage - speedModifierThresholds: - 70: 0.8 - 90: 0.6 - # arnold is very strong - - type: MeleeWeapon - damage: - types: - Blunt: 10 - Structural: 10 - - type: RandomHumanoidAppearance - - type: Tag - tags: - - CanPilot - - FootstepSound - - DoorBumpOpener - - Unimplantable # no brain to mindshield, no organic body to implant into - - type: entity parent: - BaseMob From 5768c2a8d409bbecbc52caf433a33dbf6ba8ba24 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Sun, 26 May 2024 03:17:01 +0200 Subject: [PATCH 084/235] Changing hands unwields item (#28161) Unhand me, fiend --- Content.Shared/Wieldable/WieldableSystem.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs index cee6c65fa11..c09044f84b4 100644 --- a/Content.Shared/Wieldable/WieldableSystem.cs +++ b/Content.Shared/Wieldable/WieldableSystem.cs @@ -41,6 +41,7 @@ public override void Initialize() SubscribeLocalEvent(OnItemLeaveHand); SubscribeLocalEvent(OnVirtualItemDeleted); SubscribeLocalEvent>(AddToggleWieldVerb); + SubscribeLocalEvent(OnDeselectWieldable); SubscribeLocalEvent(OnMeleeAttempt); SubscribeLocalEvent(OnShootAttempt); @@ -91,6 +92,14 @@ private void OnGunWielded(EntityUid uid, GunWieldBonusComponent component, ref I _gun.RefreshModifiers(uid); } + private void OnDeselectWieldable(EntityUid uid, WieldableComponent component, HandDeselectedEvent args) + { + if (!component.Wielded) + return; + + TryUnwield(uid, component, args.User); + } + private void OnGunRefreshModifiers(Entity bonus, ref GunRefreshModifiersEvent args) { if (TryComp(bonus, out WieldableComponent? wield) && From 2c8f18a622eaaccd7d58ed7a93186eddd8690b55 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 26 May 2024 01:18:07 +0000 Subject: [PATCH 085/235] 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 7880f0a8d13..4ec38486dac 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Errant - changes: - - message: Species info is now available in the Guidebook and in the character editor. - type: Add - id: 6120 - time: '2024-03-11T03:01:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25844 - author: Dygon changes: - message: 'The following criminal record statuses have been added: Suspect, Discharged, @@ -3875,3 +3868,10 @@ id: 6619 time: '2024-05-25T20:23:34.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28130 +- author: Errant + changes: + - message: Changing hands while wielding an item will now immediately unwield it. + type: Tweak + id: 6620 + time: '2024-05-26T01:17:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28161 From 85b0e876e753cad1010cc72483711ccaa0ec5784 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 26 May 2024 14:11:37 +1200 Subject: [PATCH 086/235] Fix storage UI interactions (#28291) * Fix storage UI interactions * Add VV support --- .../UserInterface/Systems/Storage/Controls/ItemGridPiece.cs | 4 +++- Content.Shared/Interaction/SharedInteractionSystem.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs b/Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs index f436cc8c39b..dd9986e4c6e 100644 --- a/Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs +++ b/Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs @@ -9,7 +9,7 @@ namespace Content.Client.UserInterface.Systems.Storage.Controls; -public sealed class ItemGridPiece : Control +public sealed class ItemGridPiece : Control, IEntityControl { private readonly IEntityManager _entityManager; private readonly StorageUIController _storageController; @@ -287,6 +287,8 @@ public static Vector2 GetCenterOffset(Entity entity, ItemStorage var actualSize = new Vector2(boxSize.X + 1, boxSize.Y + 1); return actualSize * new Vector2i(8, 8); } + + public EntityUid? UiEntity => Entity; } public enum ItemGridPieceMarks diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 3324ce5b9b8..6f6777ee964 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -1167,7 +1167,7 @@ public bool CanAccessViaStorage(EntityUid user, EntityUid target, BaseContainer return false; // we don't check if the user can access the storage entity itself. This should be handed by the UI system. - return _ui.IsUiOpen(target, StorageComponent.StorageUiKey.Key, user); + return _ui.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, user); } /// From d17a49b2fd6831165a7cfcfe8ed6f801f967cad6 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 26 May 2024 02:12:43 +0000 Subject: [PATCH 087/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 4ec38486dac..36276ccfcef 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: Dygon - changes: - - message: 'The following criminal record statuses have been added: Suspect, Discharged, - Paroled.' - type: Add - - message: Security huds now show an icon on entities that have a visible name linked - to a criminal record. - type: Add - id: 6121 - time: '2024-03-11T03:12:52.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25192 - author: Lank changes: - message: The detective is no longer independent, and again works under Security. @@ -3875,3 +3864,10 @@ id: 6620 time: '2024-05-26T01:17:01.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28161 +- author: ElectroJr + changes: + - message: Fixed being unable to interact with items via storage UIs + type: Fix + id: 6621 + time: '2024-05-26T02:11:37.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28291 From 386728db2f95f92733085ab1fd220ef9fd222f44 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 26 May 2024 15:18:27 +1200 Subject: [PATCH 088/235] Fix stripping not marking interactions as handled (#28292) --- Content.Server/Strip/StrippableSystem.cs | 33 ++----------------- .../Strip/SharedStrippableSystem.cs | 27 +++++++++++++-- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index 6f0a1ecb328..397396de501 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Ensnaring; -using Content.Shared.CombatMode; using Content.Shared.Cuffs; using Content.Shared.Cuffs.Components; using Content.Shared.Database; @@ -10,7 +9,6 @@ using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; using Content.Shared.Inventory.VirtualItem; @@ -18,7 +16,6 @@ using Content.Shared.Strip; using Content.Shared.Strip.Components; using Content.Shared.Verbs; -using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Utility; @@ -28,7 +25,6 @@ public sealed class StrippableSystem : SharedStrippableSystem { [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly EnsnareableSystem _ensnaringSystem = default!; - [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly SharedCuffableSystem _cuffableSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; @@ -45,7 +41,6 @@ public override void Initialize() SubscribeLocalEvent>(AddStripVerb); SubscribeLocalEvent>(AddStripExamineVerb); - SubscribeLocalEvent(OnActivateInWorld); // BUI SubscribeLocalEvent(OnStripButtonPressed); @@ -68,7 +63,7 @@ private void AddStripVerb(EntityUid uid, StrippableComponent component, GetVerbs { Text = Loc.GetString("strip-verb-get-data-text"), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")), - Act = () => StartOpeningStripper(args.User, (uid, component), true), + Act = () => TryOpenStrippingUi(args.User, (uid, component), true), }; args.Verbs.Add(verb); @@ -86,37 +81,13 @@ private void AddStripExamineVerb(EntityUid uid, StrippableComponent component, G { Text = Loc.GetString("strip-verb-get-data-text"), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")), - Act = () => StartOpeningStripper(args.User, (uid, component), true), + Act = () => TryOpenStrippingUi(args.User, (uid, component), true), Category = VerbCategory.Examine, }; args.Verbs.Add(verb); } - private void OnActivateInWorld(EntityUid uid, StrippableComponent component, ActivateInWorldEvent args) - { - if (args.Target == args.User) - return; - - if (!HasComp(args.User)) - return; - - StartOpeningStripper(args.User, (uid, component)); - } - - public override void StartOpeningStripper(EntityUid user, Entity strippable, bool openInCombat = false) - { - base.StartOpeningStripper(user, strippable, openInCombat); - - if (TryComp(user, out var mode) && mode.IsInCombatMode && !openInCombat) - return; - - if (HasComp(user)) - { - _userInterfaceSystem.OpenUi(strippable.Owner, StrippingUiKey.Key, user); - } - } - private void OnStripButtonPressed(Entity strippable, ref StrippingSlotButtonPressed args) { if (args.Actor is not { Valid: true } user || diff --git a/Content.Shared/Strip/SharedStrippableSystem.cs b/Content.Shared/Strip/SharedStrippableSystem.cs index 59b24ec943e..075cf81a4cb 100644 --- a/Content.Shared/Strip/SharedStrippableSystem.cs +++ b/Content.Shared/Strip/SharedStrippableSystem.cs @@ -1,17 +1,31 @@ +using Content.Shared.CombatMode; using Content.Shared.DragDrop; using Content.Shared.Hands.Components; +using Content.Shared.Interaction; using Content.Shared.Strip.Components; namespace Content.Shared.Strip; public abstract class SharedStrippableSystem : EntitySystem { + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnCanDropOn); SubscribeLocalEvent(OnCanDrop); SubscribeLocalEvent(OnDragDrop); + SubscribeLocalEvent(OnActivateInWorld); + } + + private void OnActivateInWorld(EntityUid uid, StrippableComponent component, ActivateInWorldEvent args) + { + if (args.Handled || args.Target == args.User) + return; + + if (TryOpenStrippingUi(args.User, (uid, component))) + args.Handled = true; } public (TimeSpan Time, bool Stealth) GetStripTimeModifiers(EntityUid user, EntityUid target, TimeSpan initialTime) @@ -29,13 +43,20 @@ private void OnDragDrop(EntityUid uid, StrippableComponent component, ref DragDr if (args.Handled || args.Target != args.User) return; - StartOpeningStripper(args.User, (uid, component)); - args.Handled = true; + if (TryOpenStrippingUi(args.User, (uid, component))) + args.Handled = true; } - public virtual void StartOpeningStripper(EntityUid user, Entity component, bool openInCombat = false) + public bool TryOpenStrippingUi(EntityUid user, Entity target, bool openInCombat = false) { + if (!openInCombat && TryComp(user, out var mode) && mode.IsInCombatMode) + return false; + + if (!HasComp(user)) + return false; + _ui.OpenUi(target.Owner, StrippingUiKey.Key, user); + return true; } private void OnCanDropOn(EntityUid uid, StrippingComponent component, ref CanDropTargetEvent args) From cc29590cfad85428fdaaee8f431cccb9e3c9cdd6 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 26 May 2024 15:20:29 +1200 Subject: [PATCH 089/235] Make NetworkConfigurator use BoundUserInterfaceCheckRangeEvent (#28293) --- .../Systems/NetworkConfiguratorSystem.cs | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index 34601241585..402d005dd47 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -63,9 +63,21 @@ public override void Initialize() SubscribeLocalEvent(OnToggleLinks); SubscribeLocalEvent(OnConfigButtonPressed); + SubscribeLocalEvent(OnUiRangeCheck); + SubscribeLocalEvent(OnComponentRemoved); } + private void OnUiRangeCheck(Entity ent, ref BoundUserInterfaceCheckRangeEvent args) + { + if (ent.Comp.ActiveDeviceList == null || args.Result == BoundUserInterfaceRangeResult.Fail) + return; + + DebugTools.Assert(Exists(ent.Comp.ActiveDeviceList)); + if (!_interactionSystem.InRangeUnobstructed(args.Actor!, ent.Comp.ActiveDeviceList.Value)) + args.Result = BoundUserInterfaceRangeResult.Fail; + } + private void OnShutdown(EntityUid uid, NetworkConfiguratorComponent component, ComponentShutdown args) { ClearDevices(uid, component); @@ -75,23 +87,6 @@ private void OnShutdown(EntityUid uid, NetworkConfiguratorComponent component, C component.ActiveDeviceList = null; } - public override void Update(float frameTime) - { - base.Update(frameTime); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var component)) - { - if (component.ActiveDeviceList != null - && EntityManager.EntityExists(component.ActiveDeviceList.Value) - && _interactionSystem.InRangeUnobstructed(uid, component.ActiveDeviceList.Value)) - continue; - - //The network configurator is a handheld device. There can only ever be an ui session open for the player holding the device. - _uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure); - } - } - private void OnMapInit(EntityUid uid, NetworkConfiguratorComponent component, MapInitEvent args) { UpdateListUiState(uid, component); From 9a7f4416b2bf15b4ee344257b984892c00783e26 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 26 May 2024 00:46:41 -0400 Subject: [PATCH 090/235] actually fix magic mirrors (#28282) --- .../MagicMirror/MagicMirrorSystem.cs | 32 +++++-------------- .../MagicMirror/SharedMagicMirrorSystem.cs | 21 ++++++++++-- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Content.Server/MagicMirror/MagicMirrorSystem.cs b/Content.Server/MagicMirror/MagicMirrorSystem.cs index fc1bff97566..8d8a6bfa3b4 100644 --- a/Content.Server/MagicMirror/MagicMirrorSystem.cs +++ b/Content.Server/MagicMirror/MagicMirrorSystem.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Server.DoAfter; using Content.Server.Humanoid; -using Content.Shared.UserInterface; using Content.Shared.DoAfter; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; @@ -24,7 +23,6 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnOpenUIAttempt); Subs.BuiEvents(MagicMirrorUiKey.Key, subs => @@ -36,7 +34,6 @@ public override void Initialize() subs.Event(OnTryMagicMirrorRemoveSlot); }); - SubscribeLocalEvent(OnMagicMirrorInteract); SubscribeLocalEvent(OnSelectSlotDoAfter); SubscribeLocalEvent(OnChangeColorDoAfter); @@ -44,23 +41,6 @@ public override void Initialize() SubscribeLocalEvent(OnAddSlotDoAfter); } - private void OnMagicMirrorInteract(Entity mirror, ref AfterInteractEvent args) - { - if (!args.CanReach || args.Target == null) - return; - - if (!_uiSystem.TryOpenUi(mirror.Owner, MagicMirrorUiKey.Key, args.User)) - return; - - UpdateInterface(mirror.Owner, args.Target.Value, mirror.Comp); - } - - private void OnOpenUIAttempt(EntityUid uid, MagicMirrorComponent mirror, ActivatableUIOpenAttemptEvent args) - { - if (!HasComp(args.User)) - args.Cancel(); - } - private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component, MagicMirrorSelectMessage message) { if (component.Target is not { } target) @@ -83,7 +63,8 @@ private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component, BreakOnMove = true, BreakOnHandChange = false, NeedHand = true - }, out var doAfterId); + }, + out var doAfterId); component.DoAfter = doAfterId; _audio.PlayPvs(component.ChangeHairSound, uid); @@ -137,7 +118,8 @@ private void OnTryMagicMirrorChangeColor(EntityUid uid, MagicMirrorComponent com BreakOnMove = true, BreakOnHandChange = false, NeedHand = true - }, out var doAfterId); + }, + out var doAfterId); component.DoAfter = doAfterId; } @@ -189,7 +171,8 @@ private void OnTryMagicMirrorRemoveSlot(EntityUid uid, MagicMirrorComponent comp BreakOnDamage = true, BreakOnHandChange = false, NeedHand = true - }, out var doAfterId); + }, + out var doAfterId); component.DoAfter = doAfterId; _audio.PlayPvs(component.ChangeHairSound, uid); @@ -241,7 +224,8 @@ private void OnTryMagicMirrorAddSlot(EntityUid uid, MagicMirrorComponent compone BreakOnMove = true, BreakOnHandChange = false, NeedHand = true - }, out var doAfterId); + }, + out var doAfterId); component.DoAfter = doAfterId; _audio.PlayPvs(component.ChangeHairSound, uid); diff --git a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs index 433ad6b4fc9..ea96d504c6e 100644 --- a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs +++ b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs @@ -11,15 +11,27 @@ namespace Content.Shared.MagicMirror; public abstract class SharedMagicMirrorSystem : EntitySystem { [Dependency] private readonly SharedInteractionSystem _interaction = default!; - [Dependency] protected readonly SharedUserInterfaceSystem _uiSystem = default!; + [Dependency] protected readonly SharedUserInterfaceSystem UISystem = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnMagicMirrorInteract); SubscribeLocalEvent(OnBeforeUIOpen); SubscribeLocalEvent(OnMirrorRangeCheck); } + private void OnMagicMirrorInteract(Entity mirror, ref AfterInteractEvent args) + { + if (!args.CanReach || args.Target == null) + return; + + if (!UISystem.TryOpenUi(mirror.Owner, MagicMirrorUiKey.Key, args.User)) + return; + + UpdateInterface(mirror, args.Target.Value, mirror); + } + private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args) { if (args.Result == BoundUserInterfaceRangeResult.Fail) @@ -33,7 +45,9 @@ private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, r private void OnBeforeUIOpen(Entity ent, ref BeforeActivatableUIOpenEvent args) { - ent.Comp.Target ??= args.User; + if (args.User != ent.Comp.Target && ent.Comp.Target != null) + return; + UpdateInterface(ent, args.User, ent); } @@ -41,6 +55,7 @@ protected void UpdateInterface(EntityUid mirrorUid, EntityUid targetUid, MagicMi { if (!TryComp(targetUid, out var humanoid)) return; + component.Target ??= targetUid; var hair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.Hair, out var hairMarkings) ? new List(hairMarkings) @@ -59,7 +74,7 @@ protected void UpdateInterface(EntityUid mirrorUid, EntityUid targetUid, MagicMi // TODO: Component states component.Target = targetUid; - _uiSystem.SetUiState(mirrorUid, MagicMirrorUiKey.Key, state); + UISystem.SetUiState(mirrorUid, MagicMirrorUiKey.Key, state); Dirty(mirrorUid, component); } } From e5820f3b2efef7d4b84fda80b3e0c394550fb146 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 26 May 2024 04:47:47 +0000 Subject: [PATCH 091/235] 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 36276ccfcef..0f5bddbcdbe 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Lank - changes: - - message: The detective is no longer independent, and again works under Security. - type: Tweak - id: 6122 - time: '2024-03-11T03:33:08.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25986 - author: pigeonpeas changes: - message: added new expedition ambience @@ -3871,3 +3864,10 @@ id: 6621 time: '2024-05-26T02:11:37.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28291 +- author: EmoGarbage404 + changes: + - message: Properly fix magic mirrors + type: Fix + id: 6622 + time: '2024-05-26T04:46:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28282 From 82b8d835b25f3cdca9e1eaf460cd3565c1389824 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Sun, 26 May 2024 05:14:29 +0000 Subject: [PATCH 092/235] fix antag selection being evil (#28197) * fix antag selection being evil * fix test * untroll the other tests * remove role timer troll * Allow tests to modify antag preferences * Fix antag selection * Misc test fixes * Add AntagPreferenceTest * Fix lazy mistakes * Test cleanup * Try stop players in lobbies from being assigned mid-round antags * ranting * I am going insane --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: ElectroJr --- .../Pair/TestPair.Helpers.cs | 28 +++++++ Content.IntegrationTests/PoolManager.cs | 4 +- .../Tests/GameRules/AntagPreferenceTest.cs | 76 +++++++++++++++++++ .../Tests/GameRules/NukeOpsTest.cs | 4 + .../Click/InteractionSystemTests.cs | 1 - .../Tests/ResettingEntitySystemTests.cs | 3 - .../Antag/AntagSelectionSystem.API.cs | 9 +++ Content.Server/Antag/AntagSelectionSystem.cs | 61 ++++++++------- .../GameTicking/GameTicker.RoundFlow.cs | 2 +- .../Managers/IServerPreferencesManager.cs | 2 + .../Managers/ServerPreferencesManager.cs | 30 ++++---- Content.Shared/Antag/AntagAcceptability.cs | 8 ++ Content.Shared/Roles/Jobs/SharedJobSystem.cs | 4 +- 13 files changed, 181 insertions(+), 51 deletions(-) create mode 100644 Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index 0ea6d3e2dcc..cc83232a066 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Server.Preferences.Managers; +using Content.Shared.Preferences; +using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -128,4 +131,29 @@ public List GetPrototypesWithComponent( return list; } + + /// + /// Helper method for enabling or disabling a antag role + /// + public async Task SetAntagPref(ProtoId id, bool value) + { + var prefMan = Server.ResolveDependency(); + + var prefs = prefMan.GetPreferences(Client.User!.Value); + // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? + var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; + + Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value)); + var newProfile = profile.WithAntagPreference(id, value); + + await Server.WaitPost(() => + { + prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait(); + }); + + // And why the fuck does it always create a new preference and profile object instead of just reusing them? + var newPrefs = prefMan.GetPreferences(Client.User.Value); + var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; + Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); + } } diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index 25e6c7ef26f..3b49ffcf847 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -65,11 +65,11 @@ public static partial class PoolManager options.BeforeStart += () => { + // Server-only systems (i.e., systems that subscribe to events with server-only components) var entSysMan = IoCManager.Resolve(); - entSysMan.LoadExtraSystemType(); - entSysMan.LoadExtraSystemType(); entSysMan.LoadExtraSystemType(); entSysMan.LoadExtraSystemType(); + IoCManager.Resolve().GetSawmill("loc").Level = LogLevel.Error; IoCManager.Resolve() .OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true); diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs new file mode 100644 index 00000000000..662ea3b9747 --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -0,0 +1,76 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.Server.Antag; +using Content.Server.Antag.Components; +using Content.Server.GameTicking; +using Content.Shared.GameTicking; +using Robust.Shared.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.IntegrationTests.Tests.GameRules; + +// Once upon a time, players in the lobby weren't ever considered eligible for antag roles. +// Lets not let that happen again. +[TestFixture] +public sealed class AntagPreferenceTest +{ + [Test] + public async Task TestLobbyPlayersValid() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + var server = pair.Server; + var client = pair.Client; + var ticker = server.System(); + var sys = server.System(); + + // Initially in the lobby + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(client.AttachedEntity, Is.Null); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + + EntityUid uid = default; + await server.WaitPost(() => uid = server.EntMan.Spawn("Traitor")); + var rule = new Entity(uid, server.EntMan.GetComponent(uid)); + var def = rule.Comp.Definitions.Single(); + + // IsSessionValid & IsEntityValid are preference agnostic and should always be true for players in the lobby. + // Though maybe that will change in the future, but then GetPlayerPool() needs to be updated to reflect that. + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + + // By default, traitor/antag preferences are disabled, so the pool should be empty. + var sessions = new List{pair.Player!}; + var pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(0)); + + // Opt into the traitor role. + await pair.SetAntagPref("Traitor", true); + + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(1)); + pool.TryPickAndTake(pair.Server.ResolveDependency(), out var picked); + Assert.That(picked, Is.EqualTo(pair.Player)); + Assert.That(sessions.Count, Is.EqualTo(1)); + + // opt back out + await pair.SetAntagPref("Traitor", false); + + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(0)); + + await server.WaitPost(() => server.EntMan.DeleteEntity(uid)); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index da5385f802f..95c92eef334 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -61,6 +61,9 @@ public async Task TryStopNukeOpsFromConstantlyFailing() Assert.That(client.AttachedEntity, Is.Null); Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + // Opt into the nukies role. + await pair.SetAntagPref("NukeopsCommander", true); + // There are no grids or maps Assert.That(entMan.Count(), Is.Zero); Assert.That(entMan.Count(), Is.Zero); @@ -205,6 +208,7 @@ public async Task TryStopNukeOpsFromConstantlyFailing() ticker.SetGamePreset((GamePresetPrototype?)null); server.CfgMan.SetCVar(CCVars.GridFill, false); + await pair.SetAntagPref("NukeopsCommander", false); await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs index 317aa10400b..4415eddf376 100644 --- a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs @@ -407,7 +407,6 @@ await server.WaitAssertion(() => await pair.CleanReturnAsync(); } - [Reflect(false)] public sealed class TestInteractionSystem : EntitySystem { public EntityEventHandler? InteractUsingEvent; diff --git a/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs b/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs index d5c2a9124dd..40457f54883 100644 --- a/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs +++ b/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs @@ -9,7 +9,6 @@ namespace Content.IntegrationTests.Tests [TestOf(typeof(RoundRestartCleanupEvent))] public sealed class ResettingEntitySystemTests { - [Reflect(false)] public sealed class TestRoundRestartCleanupEvent : EntitySystem { public bool HasBeenReset { get; set; } @@ -49,8 +48,6 @@ await server.WaitAssertion(() => system.HasBeenReset = false; - Assert.That(system.HasBeenReset, Is.False); - gameTicker.RestartRound(); Assert.That(system.HasBeenReset); diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs index d8554e3fb22..3b5855cdf95 100644 --- a/Content.Server/Antag/AntagSelectionSystem.API.cs +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -27,6 +27,11 @@ public bool TryGetNextAvailableDefinition(Entity ent, if (mindCount >= totalTargetCount) return false; + // TODO ANTAG fix this + // If here are two definitions with 1/10 and 10/10 slots filled, this will always return the second definition + // even though it has already met its target + // AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA I fucking hate game ticker code. + // It needs to track selected minds for each definition independently. foreach (var def in ent.Comp.Definitions) { var target = GetTargetAntagCount(ent, null, def); @@ -64,6 +69,10 @@ public int GetTargetAntagCount(Entity ent, AntagSelecti /// public int GetTargetAntagCount(Entity ent, AntagSelectionPlayerPool? pool, AntagSelectionDefinition def) { + // TODO ANTAG + // make pool non-nullable + // Review uses and ensure that people are INTENTIONALLY including players in the lobby if this is a mid-round + // antag selection. var poolSize = pool?.Count ?? _playerManager.Sessions .Count(s => s.State.Status is not SessionStatus.Disconnected and not SessionStatus.Zombie); diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index d371d71433e..5b6c891ddfc 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -14,6 +14,7 @@ using Content.Server.Shuttles.Components; using Content.Server.Station.Systems; using Content.Shared.Antag; +using Content.Shared.GameTicking; using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.Players; @@ -25,6 +26,7 @@ using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Server.Antag; @@ -85,10 +87,9 @@ private void OnPlayerSpawning(RulePlayerSpawningEvent args) continue; if (comp.SelectionsComplete) - return; + continue; ChooseAntags((uid, comp), pool); - comp.SelectionsComplete = true; foreach (var session in comp.SelectedSessions) { @@ -106,11 +107,7 @@ private void OnJobsAssigned(RulePlayerJobsAssignedEvent args) if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn) continue; - if (comp.SelectionsComplete) - continue; - - ChooseAntags((uid, comp)); - comp.SelectionsComplete = true; + ChooseAntags((uid, comp), args.Players); } } @@ -126,12 +123,18 @@ private void OnSpawnComplete(PlayerSpawnCompleteEvent args) var query = QueryActiveRules(); while (query.MoveNext(out var uid, out _, out var antag, out _)) { + // TODO ANTAG + // what why aasdiuhasdopiuasdfhksad + // stop this insanity please + // probability of antag assignment shouldn't depend on the order in which rules are returned by the query. if (!RobustRandom.Prob(LateJoinRandomChance)) continue; if (!antag.Definitions.Any(p => p.LateJoinAdditional)) continue; + DebugTools.AssertEqual(antag.SelectionTime, AntagSelectionTime.PostPlayerSpawn); + if (!TryGetNextAvailableDefinition((uid, antag), out var def)) continue; @@ -164,43 +167,40 @@ protected override void Started(EntityUid uid, AntagSelectionComponent component { base.Started(uid, component, gameRule, args); - if (component.SelectionsComplete) - return; - + // If the round has not yet started, we defer antag selection until roundstart if (GameTicker.RunLevel != GameRunLevel.InRound) return; - if (GameTicker.RunLevel == GameRunLevel.InRound && component.SelectionTime == AntagSelectionTime.PrePlayerSpawn) + if (component.SelectionsComplete) return; - ChooseAntags((uid, component)); - component.SelectionsComplete = true; - } + var players = _playerManager.Sessions + .Where(x => GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame) + .ToList(); - /// - /// Chooses antagonists from the current selection of players - /// - public void ChooseAntags(Entity ent) - { - var sessions = _playerManager.Sessions.ToList(); - ChooseAntags(ent, sessions); + ChooseAntags((uid, component), players); } /// /// Chooses antagonists from the given selection of players /// - public void ChooseAntags(Entity ent, List pool) + public void ChooseAntags(Entity ent, IList pool) { + if (ent.Comp.SelectionsComplete) + return; + foreach (var def in ent.Comp.Definitions) { ChooseAntags(ent, pool, def); } + + ent.Comp.SelectionsComplete = true; } /// /// Chooses antagonists from the given selection of players for the given antag definition. /// - public void ChooseAntags(Entity ent, List pool, AntagSelectionDefinition def) + public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def) { var playerPool = GetPlayerPool(ent, pool, def); var count = GetTargetAntagCount(ent, playerPool, def); @@ -324,7 +324,7 @@ public void MakeAntag(Entity ent, ICommonSession? sessi /// /// Gets an ordered player pool based on player preferences and the antagonist definition. /// - public AntagSelectionPlayerPool GetPlayerPool(Entity ent, List sessions, AntagSelectionDefinition def) + public AntagSelectionPlayerPool GetPlayerPool(Entity ent, IList sessions, AntagSelectionDefinition def) { var preferredList = new List(); var fallbackList = new List(); @@ -356,14 +356,18 @@ public bool IsSessionValid(Entity ent, ICommonSession? if (session == null) return true; - mind ??= session.GetMind(); - if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie) return false; if (ent.Comp.SelectedSessions.Contains(session)) return false; + mind ??= session.GetMind(); + + // If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity. + if (mind == null) + return true; + //todo: we need some way to check that we're not getting the same role twice. (double picking thieves or zombies through midrounds) switch (def.MultiAntagSetting) @@ -392,10 +396,11 @@ public bool IsSessionValid(Entity ent, ICommonSession? /// /// Checks if a given entity (mind/session not included) is valid for a given antagonist. /// - private bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def) + public bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def) { + // If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity. if (entity == null) - return false; + return true; if (HasComp(entity)) return false; diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index df597e69b2f..6eb42b65c03 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -795,7 +795,7 @@ public RulePlayerSpawningEvent(List playerPool, IReadOnlyDiction } /// - /// Event raised after players were assigned jobs by the GameTicker. + /// Event raised after players were assigned jobs by the GameTicker and have been spawned in. /// You can give on-station people special roles by listening to this event. /// public sealed class RulePlayerJobsAssignedEvent diff --git a/Content.Server/Preferences/Managers/IServerPreferencesManager.cs b/Content.Server/Preferences/Managers/IServerPreferencesManager.cs index 63c267f154a..cdb53bb4be7 100644 --- a/Content.Server/Preferences/Managers/IServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/IServerPreferencesManager.cs @@ -20,5 +20,7 @@ public interface IServerPreferencesManager PlayerPreferences? GetPreferencesOrNull(NetUserId? userId); IEnumerable> GetSelectedProfilesForPlayers(List userIds); bool HavePreferencesLoaded(ICommonSession session); + + Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile); } } diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index 1aad61715bb..e32af589e95 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -29,11 +29,14 @@ public sealed class ServerPreferencesManager : IServerPreferencesManager [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IDependencyCollection _dependencies = default!; + [Dependency] private readonly ILogManager _log = default!; // Cache player prefs on the server so we don't need as much async hell related to them. private readonly Dictionary _cachedPlayerPrefs = new(); + private ISawmill _sawmill = default!; + private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots); public void Init() @@ -42,6 +45,7 @@ public void Init() _netManager.RegisterNetMessage(HandleSelectCharacterMessage); _netManager.RegisterNetMessage(HandleUpdateCharacterMessage); _netManager.RegisterNetMessage(HandleDeleteCharacterMessage); + _sawmill = _log.GetSawmill("prefs"); } private async void HandleSelectCharacterMessage(MsgSelectCharacter message) @@ -78,27 +82,25 @@ private async void HandleSelectCharacterMessage(MsgSelectCharacter message) private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) { - var slot = message.Slot; - var profile = message.Profile; var userId = message.MsgChannel.UserId; - if (profile == null) - { - Logger.WarningS("prefs", - $"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {slot}."); - return; - } + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (message.Profile == null) + _sawmill.Error($"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {message.Slot}."); + else + await SetProfile(userId, message.Slot, message.Profile); + } + public async Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile) + { if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded) { - Logger.WarningS("prefs", $"User {userId} tried to modify preferences before they loaded."); + _sawmill.Error($"Tried to modify user {userId} preferences before they loaded."); return; } if (slot < 0 || slot >= MaxCharacterSlots) - { return; - } var curPrefs = prefsData.Prefs!; var session = _playerManager.GetSessionById(userId); @@ -112,10 +114,8 @@ private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor); - if (ShouldStorePrefs(message.MsgChannel.AuthType)) - { - await _db.SaveCharacterSlotAsync(message.MsgChannel.UserId, message.Profile, message.Slot); - } + if (ShouldStorePrefs(session.Channel.AuthType)) + await _db.SaveCharacterSlotAsync(userId, profile, slot); } private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message) diff --git a/Content.Shared/Antag/AntagAcceptability.cs b/Content.Shared/Antag/AntagAcceptability.cs index 02d0b5f58fe..f56be975033 100644 --- a/Content.Shared/Antag/AntagAcceptability.cs +++ b/Content.Shared/Antag/AntagAcceptability.cs @@ -22,6 +22,14 @@ public enum AntagAcceptability public enum AntagSelectionTime : byte { + /// + /// Antag roles are assigned before players are assigned jobs and spawned in. + /// This prevents antag selection from happening if the round is on-going. + /// PrePlayerSpawn, + + /// + /// Antag roles get assigned after players have been assigned jobs and have spawned in. + /// PostPlayerSpawn } diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 04ac45c4c5f..fcf76052785 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -146,8 +146,10 @@ public string MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId) public bool CanBeAntag(ICommonSession player) { + // If the player does not have any mind associated with them (e.g., has not spawned in or is in the lobby), then + // they are eligible to be given an antag role/entity. if (_playerSystem.ContentData(player) is not { Mind: { } mindId }) - return false; + return true; if (!MindTryGetJob(mindId, out _, out var prototype)) return true; From fb9aca471c37d71f3edc42e92290278e798d0258 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 26 May 2024 05:15:35 +0000 Subject: [PATCH 093/235] 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 0f5bddbcdbe..a26b705a35f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: pigeonpeas - changes: - - message: added new expedition ambience - type: Add - id: 6123 - time: '2024-03-11T06:56:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25983 - author: Ilya246 changes: - message: The biomass reclaimer may now reclaim plants, and inserting smaller entities @@ -3871,3 +3864,10 @@ id: 6622 time: '2024-05-26T04:46:41.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28282 +- author: deltanedas + changes: + - message: Fixed being picked for antag roles while in the lobby or not opted in. + type: Fix + id: 6623 + time: '2024-05-26T05:14:29.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28197 From f161fe6a56f3e2c42079cc1a45bc22a750960bd8 Mon Sep 17 00:00:00 2001 From: "Mr. 27" <45323883+Dutch-VanDerLinde@users.noreply.github.com> Date: Sun, 26 May 2024 10:48:34 -0400 Subject: [PATCH 094/235] Fix hypodarts not injecting with people that have ANY outerclothing (#28301) Update darts.yml --- Resources/Prototypes/Entities/Objects/Fun/darts.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Objects/Fun/darts.yml b/Resources/Prototypes/Entities/Objects/Fun/darts.yml index 4c7ae68420b..36c841995e5 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/darts.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/darts.yml @@ -126,6 +126,7 @@ maxVol: 7 - type: SolutionInjectOnEmbed transferAmount: 7 + blockSlots: NONE solution: melee - type: SolutionTransfer maxTransferAmount: 7 From d65b65076568528d7e30146d3a9ba339702f82be Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 26 May 2024 14:49:40 +0000 Subject: [PATCH 095/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index a26b705a35f..80f1972d560 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Ilya246 - changes: - - message: The biomass reclaimer may now reclaim plants, and inserting smaller entities - into it is now faster. - type: Tweak - id: 6124 - time: '2024-03-11T21:59:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/23731 - author: SlamBamActionman changes: - message: Moths eating lizard plushies will now start to Weh! @@ -3871,3 +3863,10 @@ id: 6623 time: '2024-05-26T05:14:29.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28197 +- author: Dutch-VanDerLinde + changes: + - message: Fix hypodarts not injecting into people with outerclothing equipped. + type: Fix + id: 6624 + time: '2024-05-26T14:48:34.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28301 From 3d8706561db7082ec4c42335eaf4d791d4cc8fb3 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 26 May 2024 16:07:16 -0400 Subject: [PATCH 096/235] fix borg ui mispredict opening (#28305) move borg ui junk to shared --- .../ActivatableUIRequiresLockSystem.cs | 43 ------------------- Content.Server/Silicons/Borgs/BorgSystem.cs | 9 ---- Content.Server/Wires/WiresSystem.cs | 23 ---------- .../ActivatableUIRequiresLockComponent.cs | 10 +++-- Content.Shared/Lock/LockSystem.cs | 26 +++++++++++ .../Silicons/Borgs/SharedBorgSystem.cs | 11 ++++- .../ActivatableUIRequiresPanelComponent.cs | 8 ++-- Content.Shared/Wires/SharedWiresSystem.cs | 22 ++++++++++ 8 files changed, 69 insertions(+), 83 deletions(-) delete mode 100644 Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs rename {Content.Server/Lock/Components => Content.Shared/Lock}/ActivatableUIRequiresLockComponent.cs (66%) rename {Content.Server => Content.Shared}/Wires/ActivatableUIRequiresPanelComponent.cs (71%) diff --git a/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs b/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs deleted file mode 100644 index 04f8e2eb54c..00000000000 --- a/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Server.Lock.Components; -using Content.Server.Popups; -using Content.Shared.UserInterface; -using Content.Shared.Lock; -using Content.Server.UserInterface; -using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; - -namespace Content.Server.Lock.EntitySystems; -public sealed class ActivatableUIRequiresLockSystem : EntitySystem -{ - [Dependency] private readonly ActivatableUISystem _activatableUI = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnUIOpenAttempt); - SubscribeLocalEvent(LockToggled); - } - - private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args) - { - if (args.Cancelled) - return; - - if (TryComp(uid, out var lockComp) && lockComp.Locked != component.requireLocked) - { - args.Cancel(); - if (lockComp.Locked) - _popupSystem.PopupEntity(Loc.GetString("entity-storage-component-locked-message"), uid, args.User); - } - } - - private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args) - { - if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.requireLocked) - return; - - _activatableUI.CloseAll(uid); - } -} - diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index 97adfd00eb4..082e38921a2 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -5,7 +5,6 @@ using Content.Server.Explosion.EntitySystems; using Content.Server.Hands.Systems; using Content.Server.PowerCell; -using Content.Shared.UserInterface; using Content.Shared.Access.Systems; using Content.Shared.Alert; using Content.Shared.Database; @@ -70,7 +69,6 @@ public override void Initialize() SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnPowerCellChanged); SubscribeLocalEvent(OnPowerCellSlotEmpty); - SubscribeLocalEvent(OnUIOpenAttempt); SubscribeLocalEvent(OnGetDeadIC); SubscribeLocalEvent(OnBrainMindAdded); @@ -214,13 +212,6 @@ private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, UpdateUI(uid, component); } - private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args) - { - // borgs can't view their own ui - if (args.User == uid) - args.Cancel(); - } - private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetCharactedDeadIcEvent args) { args.Dead = true; diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index c643759f504..944b0a0e250 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -4,27 +4,23 @@ using Content.Server.Construction; using Content.Server.Construction.Components; using Content.Server.Power.Components; -using Content.Server.UserInterface; using Content.Shared.DoAfter; using Content.Shared.GameTicking; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Tools.Components; -using Content.Shared.UserInterface; using Content.Shared.Wires; using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; namespace Content.Server.Wires; public sealed class WiresSystem : SharedWiresSystem { [Dependency] private readonly IPrototypeManager _protoMan = default!; - [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; @@ -52,8 +48,6 @@ public override void Initialize() SubscribeLocalEvent(OnTimedWire); SubscribeLocalEvent(OnWiresPowered); SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnAttemptOpenActivatableUI); - SubscribeLocalEvent(OnActivatableUIPanelChanged); SubscribeLocalEvent(SetWiresPanelSecurity); } @@ -473,23 +467,6 @@ private void OnPanelChanged(Entity ent, ref PanelChangedEvent ar _uiSystem.CloseUi(ent.Owner, WiresUiKey.Key); } - private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args) - { - if (args.Cancelled || !TryComp(uid, out var wires)) - return; - - if (component.RequireOpen != wires.Open) - args.Cancel(); - } - - private void OnActivatableUIPanelChanged(EntityUid uid, ActivatableUIRequiresPanelComponent component, ref PanelChangedEvent args) - { - if (args.Open == component.RequireOpen) - return; - - _activatableUI.CloseAll(uid); - } - private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args) { if (!string.IsNullOrEmpty(component.LayoutId)) diff --git a/Content.Server/Lock/Components/ActivatableUIRequiresLockComponent.cs b/Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs similarity index 66% rename from Content.Server/Lock/Components/ActivatableUIRequiresLockComponent.cs rename to Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs index dac677c1c27..7d701ffd874 100644 --- a/Content.Server/Lock/Components/ActivatableUIRequiresLockComponent.cs +++ b/Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs @@ -1,15 +1,17 @@ -namespace Content.Server.Lock.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.Lock; /// /// This is used for activatable UIs that require the entity to have a lock in a certain state. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] public sealed partial class ActivatableUIRequiresLockComponent : Component { /// /// TRUE: the lock must be locked to access the UI. /// FALSE: the lock must be unlocked to access the UI. /// - [DataField("requireLocked"), ViewVariables(VVAccess.ReadWrite)] - public bool requireLocked = false; + [DataField] + public bool RequireLocked; } diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 4115358d9d7..36ac5f025b1 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Popups; using Content.Shared.Storage; using Content.Shared.Storage.Components; +using Content.Shared.UserInterface; using Content.Shared.Verbs; using Content.Shared.Wires; using JetBrains.Annotations; @@ -25,6 +26,7 @@ namespace Content.Shared.Lock; public sealed class LockSystem : EntitySystem { [Dependency] private readonly AccessReaderSystem _accessReader = default!; + [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!; @@ -48,6 +50,9 @@ public override void Initialize() SubscribeLocalEvent(OnLockToggleAttempt); SubscribeLocalEvent(OnAttemptChangePanel); SubscribeLocalEvent(OnUnanchorAttempt); + + SubscribeLocalEvent(OnUIOpenAttempt); + SubscribeLocalEvent(LockToggled); } private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) @@ -349,5 +354,26 @@ private void OnUnanchorAttempt(Entity ent, ref Unanch args.User); args.Cancel(); } + + private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args) + { + if (args.Cancelled) + return; + + if (TryComp(uid, out var lockComp) && lockComp.Locked != component.RequireLocked) + { + args.Cancel(); + if (lockComp.Locked) + _sharedPopupSystem.PopupEntity(Loc.GetString("entity-storage-component-locked-message"), uid, args.User); + } + } + + private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args) + { + if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.RequireLocked) + return; + + _activatableUI.CloseAll(uid); + } } diff --git a/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs b/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs index 0431d95a42f..2983c0d642f 100644 --- a/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs +++ b/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Popups; using Content.Shared.PowerCell.Components; using Content.Shared.Silicons.Borgs.Components; +using Content.Shared.UserInterface; using Content.Shared.Wires; using Robust.Shared.Containers; @@ -30,7 +31,8 @@ public override void Initialize() SubscribeLocalEvent(OnInserted); SubscribeLocalEvent(OnRemoved); SubscribeLocalEvent(OnRefreshMovementSpeedModifiers); - + SubscribeLocalEvent(OnUIOpenAttempt); + InitializeRelay(); } @@ -75,6 +77,13 @@ private void OnStartup(EntityUid uid, BorgChassisComponent component, ComponentS component.ModuleContainer = Container.EnsureContainer(uid, component.ModuleContainerId, containerManager); } + private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args) + { + // borgs can't view their own ui + if (args.User == uid) + args.Cancel(); + } + protected virtual void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args) { diff --git a/Content.Server/Wires/ActivatableUIRequiresPanelComponent.cs b/Content.Shared/Wires/ActivatableUIRequiresPanelComponent.cs similarity index 71% rename from Content.Server/Wires/ActivatableUIRequiresPanelComponent.cs rename to Content.Shared/Wires/ActivatableUIRequiresPanelComponent.cs index f92df3d3d59..6e8fff6e49c 100644 --- a/Content.Server/Wires/ActivatableUIRequiresPanelComponent.cs +++ b/Content.Shared/Wires/ActivatableUIRequiresPanelComponent.cs @@ -1,15 +1,17 @@ -namespace Content.Server.Wires; +using Robust.Shared.GameStates; + +namespace Content.Shared.Wires; /// /// This is used for activatable UIs that require the entity to have a panel in a certain state. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, Access(typeof(SharedWiresSystem))] public sealed partial class ActivatableUIRequiresPanelComponent : Component { /// /// TRUE: the panel must be open to access the UI. /// FALSE: the panel must be closed to access the UI. /// - [DataField("requireOpen"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool RequireOpen = true; } diff --git a/Content.Shared/Wires/SharedWiresSystem.cs b/Content.Shared/Wires/SharedWiresSystem.cs index b4b0768e0f7..d84766a5fc6 100644 --- a/Content.Shared/Wires/SharedWiresSystem.cs +++ b/Content.Shared/Wires/SharedWiresSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Tools.Systems; +using Content.Shared.UserInterface; using Robust.Shared.Audio.Systems; namespace Content.Shared.Wires; @@ -10,6 +11,7 @@ namespace Content.Shared.Wires; public abstract class SharedWiresSystem : EntitySystem { [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!; + [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] protected readonly SharedToolSystem Tool = default!; @@ -21,6 +23,9 @@ public override void Initialize() SubscribeLocalEvent(OnPanelDoAfter); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnExamine); + + SubscribeLocalEvent(OnAttemptOpenActivatableUI); + SubscribeLocalEvent(OnActivatableUIPanelChanged); } private void OnPanelDoAfter(EntityUid uid, WiresPanelComponent panel, WirePanelDoAfterEvent args) @@ -132,4 +137,21 @@ public bool IsPanelOpen(Entity entity) return entity.Comp.Open; } + + private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args) + { + if (args.Cancelled || !TryComp(uid, out var wires)) + return; + + if (component.RequireOpen != wires.Open) + args.Cancel(); + } + + private void OnActivatableUIPanelChanged(EntityUid uid, ActivatableUIRequiresPanelComponent component, ref PanelChangedEvent args) + { + if (args.Open == component.RequireOpen) + return; + + _activatableUI.CloseAll(uid); + } } From b99a66afc178cd61bb59d26633da643b4a9f3fa5 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 27 May 2024 10:11:17 +1000 Subject: [PATCH 097/235] Selectively revert PullController (#28126) I am leaving the issues open and have updated #26547 with more info on what we should do long-term. This is just to bandaid the short-term complaining. --- .../Movement/Components/PullMoverComponent.cs | 13 + .../Components/PullMovingComponent.cs | 14 + .../Movement/Systems/PullController.cs | 318 ++++++++++++++++++ .../Pulling/Components/PullerComponent.cs | 2 +- .../Movement/Pulling/Systems/PullingSystem.cs | 44 --- 5 files changed, 346 insertions(+), 45 deletions(-) create mode 100644 Content.Server/Movement/Components/PullMoverComponent.cs create mode 100644 Content.Server/Movement/Components/PullMovingComponent.cs create mode 100644 Content.Server/Movement/Systems/PullController.cs diff --git a/Content.Server/Movement/Components/PullMoverComponent.cs b/Content.Server/Movement/Components/PullMoverComponent.cs new file mode 100644 index 00000000000..19a01c6b17d --- /dev/null +++ b/Content.Server/Movement/Components/PullMoverComponent.cs @@ -0,0 +1,13 @@ +namespace Content.Server.Movement.Components; + +/// +/// Added to an entity that is ctrl-click moving their pulled object. +/// +/// +/// This just exists so we don't have MoveEvent subs going off for every single mob constantly. +/// +[RegisterComponent] +public sealed partial class PullMoverComponent : Component +{ + +} diff --git a/Content.Server/Movement/Components/PullMovingComponent.cs b/Content.Server/Movement/Components/PullMovingComponent.cs new file mode 100644 index 00000000000..32c50d657ad --- /dev/null +++ b/Content.Server/Movement/Components/PullMovingComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Map; + +namespace Content.Server.Movement.Components; + +/// +/// Added when an entity is being ctrl-click moved when pulled. +/// +[RegisterComponent] +public sealed partial class PullMovingComponent : Component +{ + // Not serialized to indicate THIS CODE SUCKS, fix pullcontroller first + [ViewVariables] + public EntityCoordinates MovingTo; +} diff --git a/Content.Server/Movement/Systems/PullController.cs b/Content.Server/Movement/Systems/PullController.cs new file mode 100644 index 00000000000..72110ff67d2 --- /dev/null +++ b/Content.Server/Movement/Systems/PullController.cs @@ -0,0 +1,318 @@ +using System.Numerics; +using Content.Server.Movement.Components; +using Content.Server.Physics.Controllers; +using Content.Shared.ActionBlocker; +using Content.Shared.Gravity; +using Content.Shared.Input; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Rotatable; +using Robust.Server.Physics; +using Robust.Shared.Containers; +using Robust.Shared.Input.Binding; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Controllers; +using Robust.Shared.Physics.Dynamics.Joints; +using Robust.Shared.Player; +using Robust.Shared.Timing; + +namespace Content.Server.Movement.Systems; + +public sealed class PullController : VirtualController +{ + /* + * This code is awful. If you try to tweak this without refactoring it I'm gonna revert it. + */ + + // Parameterization for pulling: + // Speeds. Note that the speed is mass-independent (multiplied by mass). + // Instead, tuning to mass is done via the mass values below. + // Note that setting the speed too high results in overshoots (stabilized by drag, but bad) + private const float AccelModifierHigh = 15f; + private const float AccelModifierLow = 60.0f; + // High/low-mass marks. Curve is constant-lerp-constant, i.e. if you can even pull an item, + // you'll always get at least AccelModifierLow and no more than AccelModifierHigh. + private const float AccelModifierHighMass = 70.0f; // roundstart saltern emergency closet + private const float AccelModifierLowMass = 5.0f; // roundstart saltern emergency crowbar + // Used to control settling (turns off pulling). + private const float MaximumSettleVelocity = 0.1f; + private const float MaximumSettleDistance = 0.1f; + // Settle shutdown control. + // Mustn't be too massive, as that causes severe mispredicts *and can prevent it ever resolving*. + // Exists to bleed off "I pulled my crowbar" overshoots. + // Minimum velocity for shutdown to be necessary. This prevents stuff getting stuck b/c too much shutdown. + private const float SettleMinimumShutdownVelocity = 0.25f; + // Distance in which settle shutdown multiplier is at 0. It then scales upwards linearly with closer distances. + private const float SettleShutdownDistance = 1.0f; + // Velocity change of -LinearVelocity * frameTime * this + private const float SettleShutdownMultiplier = 20.0f; + + // How much you must move for the puller movement check to actually hit. + private const float MinimumMovementDistance = 0.005f; + + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + + /// + /// If distance between puller and pulled entity lower that this threshold, + /// pulled entity will not change its rotation. + /// Helps with small distance jittering + /// + private const float ThresholdRotDistance = 1; + + /// + /// If difference between puller and pulled angle lower that this threshold, + /// pulled entity will not change its rotation. + /// Helps with diagonal movement jittering + /// As of further adjustments, should divide cleanly into 90 degrees + /// + private const float ThresholdRotAngle = 22.5f; + + private EntityQuery _physicsQuery; + private EntityQuery _pullableQuery; + private EntityQuery _pullerQuery; + private EntityQuery _xformQuery; + + public override void Initialize() + { + CommandBinds.Builder + .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(OnRequestMovePulledObject)) + .Register(); + + _physicsQuery = GetEntityQuery(); + _pullableQuery = GetEntityQuery(); + _pullerQuery = GetEntityQuery(); + _xformQuery = GetEntityQuery(); + + UpdatesAfter.Add(typeof(MoverController)); + SubscribeLocalEvent(OnPullStop); + SubscribeLocalEvent(OnPullerMove); + + base.Initialize(); + } + + public override void Shutdown() + { + base.Shutdown(); + CommandBinds.Unregister(); + } + + private void OnPullStop(Entity ent, ref PullStoppedMessage args) + { + RemCompDeferred(ent); + } + + private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid) + { + if (session?.AttachedEntity is not { } player || + !player.IsValid()) + { + return false; + } + + if (!_pullerQuery.TryComp(player, out var pullerComp)) + return false; + + var pulled = pullerComp.Pulling; + + if (!_pullableQuery.TryComp(pulled, out var pullable)) + return false; + + if (_container.IsEntityInContainer(player)) + return false; + + // Cooldown buddy + if (_timing.CurTime < pullerComp.NextThrow) + return false; + + pullerComp.NextThrow = _timing.CurTime + pullerComp.ThrowCooldown; + + // Cap the distance + var range = 2f; + var fromUserCoords = coords.WithEntityId(player, EntityManager); + var userCoords = new EntityCoordinates(player, Vector2.Zero); + + if (!coords.InRange(EntityManager, TransformSystem, userCoords, range)) + { + var direction = fromUserCoords.Position - userCoords.Position; + + // TODO: Joint API not ass + // with that being said I think throwing is the way to go but. + if (pullable.PullJointId != null && + TryComp(player, out JointComponent? joint) && + joint.GetJoints.TryGetValue(pullable.PullJointId, out var pullJoint) && + pullJoint is DistanceJoint distance) + { + range = MathF.Max(0.01f, distance.MaxLength - 0.01f); + } + + fromUserCoords = new EntityCoordinates(player, direction.Normalized() * (range - 0.01f)); + coords = fromUserCoords.WithEntityId(coords.EntityId); + } + + EnsureComp(player); + var moving = EnsureComp(pulled!.Value); + moving.MovingTo = coords; + return false; + } + + private void OnPullerMove(EntityUid uid, PullMoverComponent component, ref MoveEvent args) + { + if (!_pullerQuery.TryComp(uid, out var puller)) + return; + + if (puller.Pulling is not { } pullable) + return; + + UpdatePulledRotation(uid, pullable); + + // WHY + if (args.NewPosition.EntityId == args.OldPosition.EntityId && + (args.NewPosition.Position - args.OldPosition.Position).LengthSquared() < + MinimumMovementDistance * MinimumMovementDistance) + { + return; + } + + if (_physicsQuery.TryComp(uid, out var physics)) + PhysicsSystem.WakeBody(uid, body: physics); + + StopMove(uid, pullable); + } + + private void StopMove(Entity mover, Entity moving) + { + RemCompDeferred(mover.Owner); + RemCompDeferred(moving.Owner); + } + + private void UpdatePulledRotation(EntityUid puller, EntityUid pulled) + { + // TODO: update once ComponentReference works with directed event bus. + if (!TryComp(pulled, out RotatableComponent? rotatable)) + return; + + if (!rotatable.RotateWhilePulling) + return; + + var pulledXform = _xformQuery.GetComponent(pulled); + var pullerXform = _xformQuery.GetComponent(puller); + + var pullerData = TransformSystem.GetWorldPositionRotation(pullerXform); + var pulledData = TransformSystem.GetWorldPositionRotation(pulledXform); + + var dir = pullerData.WorldPosition - pulledData.WorldPosition; + if (dir.LengthSquared() > ThresholdRotDistance * ThresholdRotDistance) + { + var oldAngle = pulledData.WorldRotation; + var newAngle = Angle.FromWorldVec(dir); + + var diff = newAngle - oldAngle; + if (Math.Abs(diff.Degrees) > ThresholdRotAngle / 2f) + { + // Ok, so this bit is difficult because ideally it would look like it's snapping to sane angles. + // Otherwise PIANO DOOR STUCK! happens. + // But it also needs to work with station rotation / align to the local parent. + // So... + var baseRotation = pulledData.WorldRotation - pulledXform.LocalRotation; + var localRotation = newAngle - baseRotation; + var localRotationSnapped = Angle.FromDegrees(Math.Floor((localRotation.Degrees / ThresholdRotAngle) + 0.5f) * ThresholdRotAngle); + TransformSystem.SetLocalRotation(pulled, localRotationSnapped, pulledXform); + } + } + } + + public override void UpdateBeforeSolve(bool prediction, float frameTime) + { + base.UpdateBeforeSolve(prediction, frameTime); + var movingQuery = EntityQueryEnumerator(); + + while (movingQuery.MoveNext(out var pullableEnt, out var mover, out var pullable, out var pullableXform)) + { + if (!mover.MovingTo.IsValid(EntityManager)) + { + RemCompDeferred(pullableEnt); + continue; + } + + if (pullable.Puller is not {Valid: true} puller) + continue; + + var pullerXform = _xformQuery.Get(puller); + var pullerPosition = TransformSystem.GetMapCoordinates(pullerXform); + + var movingTo = mover.MovingTo.ToMap(EntityManager, TransformSystem); + + if (movingTo.MapId != pullerPosition.MapId) + { + RemCompDeferred(pullableEnt); + continue; + } + + if (!TryComp(pullableEnt, out var physics) || + physics.BodyType == BodyType.Static || + movingTo.MapId != pullableXform.MapID) + { + RemCompDeferred(pullableEnt); + continue; + } + + var movingPosition = movingTo.Position; + var ownerPosition = TransformSystem.GetWorldPosition(pullableXform); + + var diff = movingPosition - ownerPosition; + var diffLength = diff.Length(); + + if (diffLength < MaximumSettleDistance && physics.LinearVelocity.Length() < MaximumSettleVelocity) + { + PhysicsSystem.SetLinearVelocity(pullableEnt, Vector2.Zero, body: physics); + RemCompDeferred(pullableEnt); + continue; + } + + var impulseModifierLerp = Math.Min(1.0f, Math.Max(0.0f, (physics.Mass - AccelModifierLowMass) / (AccelModifierHighMass - AccelModifierLowMass))); + var impulseModifier = MathHelper.Lerp(AccelModifierLow, AccelModifierHigh, impulseModifierLerp); + var multiplier = diffLength < 1 ? impulseModifier * diffLength : impulseModifier; + // Note the implication that the real rules of physics don't apply to pulling control. + var accel = diff.Normalized() * multiplier; + // Now for the part where velocity gets shutdown... + if (diffLength < SettleShutdownDistance && physics.LinearVelocity.Length() >= SettleMinimumShutdownVelocity) + { + // Shutdown velocity increases as we get closer to centre + var scaling = (SettleShutdownDistance - diffLength) / SettleShutdownDistance; + accel -= physics.LinearVelocity * SettleShutdownMultiplier * scaling; + } + + PhysicsSystem.WakeBody(pullableEnt, body: physics); + + var impulse = accel * physics.Mass * frameTime; + PhysicsSystem.ApplyLinearImpulse(pullableEnt, impulse, body: physics); + + // if the puller is weightless or can't move, then we apply the inverse impulse (Newton's third law). + // doing it under gravity produces an unsatisfying wiggling when pulling. + // If player can't move, assume they are on a chair and we need to prevent pull-moving. + if (_gravity.IsWeightless(puller) && pullerXform.Comp.GridUid == null || !_actionBlockerSystem.CanMove(puller)) + { + PhysicsSystem.WakeBody(puller); + PhysicsSystem.ApplyLinearImpulse(puller, -impulse); + } + } + + // Cleanup PullMover + var moverQuery = EntityQueryEnumerator(); + + while (moverQuery.MoveNext(out var uid, out _, out var puller)) + { + if (!HasComp(puller.Pulling)) + { + RemCompDeferred(uid); + continue; + } + } + } +} diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs index 061ec13ed2a..f47ae32f90b 100644 --- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs @@ -18,7 +18,7 @@ public sealed partial class PullerComponent : Component /// Next time the puller can throw what is being pulled. /// Used to avoid spamming it for infinite spin + velocity. /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, Access(Other = AccessPermissions.ReadWriteExecute)] public TimeSpan NextThrow; [DataField] diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 3de71172c72..225810daed2 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -43,8 +43,6 @@ public sealed class PullingSystem : EntitySystem [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly SharedTransformSystem _xformSys = default!; - [Dependency] private readonly ThrowingSystem _throwing = default!; public override void Initialize() { @@ -66,7 +64,6 @@ public override void Initialize() SubscribeLocalEvent(OnDropHandItems); CommandBinds.Builder - .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(OnRequestMovePulledObject)) .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(OnReleasePulledObject, handle: false)) .Register(); } @@ -245,47 +242,6 @@ public bool IsPulled(EntityUid uid, PullableComponent? component = null) return Resolve(uid, ref component, false) && component.BeingPulled; } - private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid) - { - if (session?.AttachedEntity is not { } player || - !player.IsValid()) - { - return false; - } - - if (!TryComp(player, out var pullerComp)) - return false; - - var pulled = pullerComp.Pulling; - - if (!HasComp(pulled)) - return false; - - if (_containerSystem.IsEntityInContainer(player)) - return false; - - // Cooldown buddy - if (_timing.CurTime < pullerComp.NextThrow) - return false; - - pullerComp.NextThrow = _timing.CurTime + pullerComp.ThrowCooldown; - - // Cap the distance - const float range = 2f; - var fromUserCoords = coords.WithEntityId(player, EntityManager); - var userCoords = new EntityCoordinates(player, Vector2.Zero); - - if (!userCoords.InRange(EntityManager, _xformSys, fromUserCoords, range)) - { - var userDirection = fromUserCoords.Position - userCoords.Position; - fromUserCoords = userCoords.Offset(userDirection.Normalized() * range); - } - - Dirty(player, pullerComp); - _throwing.TryThrow(pulled.Value, fromUserCoords, user: player, strength: 4f, animated: false, recoil: false, playSound: false, doSpin: false); - return false; - } - public bool IsPulling(EntityUid puller, PullerComponent? component = null) { return Resolve(puller, ref component, false) && component.Pulling != null; From 2a69a9e384166cb6868194d135f5f3379bc26d2b Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 27 May 2024 00:12:24 +0000 Subject: [PATCH 098/235] 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 80f1972d560..31c41b9ed5b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: SlamBamActionman - changes: - - message: Moths eating lizard plushies will now start to Weh! - type: Tweak - id: 6125 - time: '2024-03-11T23:05:25.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26003 - author: Ubaser changes: - message: Unpressurized areas now deal heat damage along with blunt. @@ -3870,3 +3863,10 @@ id: 6624 time: '2024-05-26T14:48:34.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28301 +- author: metalgearsloth + changes: + - message: Selectively revert pulling throwing for the short-term. + type: Tweak + id: 6625 + time: '2024-05-27T00:11:17.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28126 From 413889283c0020805814de3706563b7c83b21b1f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 27 May 2024 18:04:34 +1000 Subject: [PATCH 099/235] Add loadout group check (#28311) Forgot to add it back in one of the rewrites. --- Content.Shared/Preferences/Loadouts/RoleLoadout.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs index 40e13f0edfa..479974893ce 100644 --- a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs +++ b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs @@ -78,12 +78,20 @@ public void EnsureValid(HumanoidCharacterProfile profile, ICommonSession session { var loadout = loadouts[i]; + // Old prototype or otherwise invalid. if (!protoManager.TryIndex(loadout.Prototype, out var loadoutProto)) { loadouts.RemoveAt(i); continue; } + // Malicious client maybe, check the group even has it. + if (!groupProto.Loadouts.Contains(loadout.Prototype)) + { + loadouts.RemoveAt(i); + continue; + } + // Validate the loadout can be applied (e.g. points). if (!IsValid(profile, session, loadout.Prototype, collection, out _)) { From ac98ee082e1c01dbc6cea69f11f5df5c10ab57e3 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Mon, 27 May 2024 11:50:05 -0400 Subject: [PATCH 100/235] fix mirror server crashes (#28318) --- .../MagicMirror/SharedMagicMirrorSystem.cs | 25 +++++++++++++------ .../Entities/Structures/Wallmounts/mirror.yml | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs index ea96d504c6e..789b484cfd3 100644 --- a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs +++ b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs @@ -4,7 +4,6 @@ using Content.Shared.Interaction; using Content.Shared.UserInterface; using Robust.Shared.Serialization; -using Robust.Shared.Utility; namespace Content.Shared.MagicMirror; @@ -18,6 +17,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnMagicMirrorInteract); SubscribeLocalEvent(OnBeforeUIOpen); + SubscribeLocalEvent(OnAttemptOpenUI); SubscribeLocalEvent(OnMirrorRangeCheck); } @@ -26,10 +26,8 @@ private void OnMagicMirrorInteract(Entity mirror, ref Afte if (!args.CanReach || args.Target == null) return; - if (!UISystem.TryOpenUi(mirror.Owner, MagicMirrorUiKey.Key, args.User)) - return; - UpdateInterface(mirror, args.Target.Value, mirror); + UISystem.TryOpenUi(mirror.Owner, MagicMirrorUiKey.Key, args.User); } private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args) @@ -37,17 +35,27 @@ private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, r if (args.Result == BoundUserInterfaceRangeResult.Fail) return; - DebugTools.Assert(component.Target != null && Exists(component.Target)); + if (component.Target == null || !Exists(component.Target)) + { + component.Target = null; + args.Result = BoundUserInterfaceRangeResult.Fail; + return; + } if (!_interaction.InRangeUnobstructed(uid, component.Target.Value)) args.Result = BoundUserInterfaceRangeResult.Fail; } - private void OnBeforeUIOpen(Entity ent, ref BeforeActivatableUIOpenEvent args) + private void OnAttemptOpenUI(EntityUid uid, MagicMirrorComponent component, ref ActivatableUIOpenAttemptEvent args) { - if (args.User != ent.Comp.Target && ent.Comp.Target != null) - return; + var user = component.Target ?? args.User; + if (!HasComp(user)) + args.Cancel(); + } + + private void OnBeforeUIOpen(Entity ent, ref BeforeActivatableUIOpenEvent args) + { UpdateInterface(ent, args.User, ent); } @@ -55,6 +63,7 @@ protected void UpdateInterface(EntityUid mirrorUid, EntityUid targetUid, MagicMi { if (!TryComp(targetUid, out var humanoid)) return; + component.Target ??= targetUid; var hair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.Hair, out var hairMarkings) diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml index 1fe3318ef55..27b8390add2 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml @@ -19,6 +19,7 @@ changeSlotTime: 0 - type: ActivatableUI key: enum.MagicMirrorUiKey.Key + singleUser: true - type: UserInterface interfaces: enum.MagicMirrorUiKey.Key: From 958a2a2916f949907b49a6d06a27a764e8808eab Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 27 May 2024 23:52:23 +0200 Subject: [PATCH 101/235] Remove bogus C# finalizers (#28315) Begging people to learn how this programming language works before throwing random syntax into a file. None of these finalizers ever worked. I also checked whether they were memory leaks and needed *proper* shutdown logic, but they're all instantiated-once UI controls that last for the entire lifetime of the program so it's probably fine. --- Content.Client/Interaction/DragDropHelper.cs | 5 ----- .../Systems/Actions/Controls/ActionButtonContainer.cs | 5 ----- .../Systems/Inventory/Controls/ItemSlotButtonContainer.cs | 5 ----- 3 files changed, 15 deletions(-) diff --git a/Content.Client/Interaction/DragDropHelper.cs b/Content.Client/Interaction/DragDropHelper.cs index abe35bf6d9a..e453dfd74bf 100644 --- a/Content.Client/Interaction/DragDropHelper.cs +++ b/Content.Client/Interaction/DragDropHelper.cs @@ -73,11 +73,6 @@ public DragDropHelper(OnBeginDrag onBeginDrag, OnContinueDrag onContinueDrag, On _cfg.OnValueChanged(CCVars.DragDropDeadZone, SetDeadZone, true); } - ~DragDropHelper() - { - _cfg.UnsubValueChanged(CCVars.DragDropDeadZone, SetDeadZone); - } - /// /// Tell the helper that the mouse button was pressed down on /// a target, thus a drag has the possibility to begin for this target. diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs index c5f8adbdea3..38c08dc4721 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs @@ -119,9 +119,4 @@ public IEnumerable GetButtons() yield return button; } } - - ~ActionButtonContainer() - { - UserInterfaceManager.GetUIController().RemoveActionContainer(); - } } diff --git a/Content.Client/UserInterface/Systems/Inventory/Controls/ItemSlotButtonContainer.cs b/Content.Client/UserInterface/Systems/Inventory/Controls/ItemSlotButtonContainer.cs index 78c3b40adac..7a027fc4f81 100644 --- a/Content.Client/UserInterface/Systems/Inventory/Controls/ItemSlotButtonContainer.cs +++ b/Content.Client/UserInterface/Systems/Inventory/Controls/ItemSlotButtonContainer.cs @@ -22,9 +22,4 @@ public ItemSlotButtonContainer() { _inventoryController = UserInterfaceManager.GetUIController(); } - - ~ItemSlotButtonContainer() - { - _inventoryController.RemoveSlotGroup(SlotGroup); - } } From b3b20e1173bceea2027d53c287fda78c3afa63bb Mon Sep 17 00:00:00 2001 From: alex-georgeff <54858069+taurie@users.noreply.github.com> Date: Mon, 27 May 2024 18:00:19 -0400 Subject: [PATCH 102/235] Rewords plant/effect descriptions for clarity (#28156) (#28169) * Rewords plant/effect descriptions for clarity (#28156) * Forgot Robust Harvest. * Update Resources/Locale/en-US/guidebook/chemistry/effects.ftl --------- Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> --- Resources/Locale/en-US/guidebook/chemistry/effects.ftl | 6 +++--- Resources/Locale/en-US/reagents/meta/botany.ftl | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl index 5579c95e9df..1dacebd1349 100644 --- a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl +++ b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl @@ -365,9 +365,9 @@ reagent-effect-guidebook-plant-cryoxadone = reagent-effect-guidebook-plant-phalanximine = { $chance -> - [1] Makes - *[other] make - } a plant not viable due to mutation viable again + [1] Restores + *[other] restore + } viability to a plant rendered nonviable by a mutation reagent-effect-guidebook-plant-diethylamine = { $chance -> diff --git a/Resources/Locale/en-US/reagents/meta/botany.ftl b/Resources/Locale/en-US/reagents/meta/botany.ftl index 912ded5cf29..c7101c23279 100644 --- a/Resources/Locale/en-US/reagents/meta/botany.ftl +++ b/Resources/Locale/en-US/reagents/meta/botany.ftl @@ -11,7 +11,7 @@ reagent-name-plant-b-gone = plant-B-gone reagent-desc-plant-b-gone = A harmful toxic mixture to kill plantlife. Very effective against kudzu. reagent-name-robust-harvest = robust harvest -reagent-desc-robust-harvest = A highly effective fertilizer, with a limited potency-boosting effect on plants. Be careful with it's usage since using too much has a chance to reduce the plant yield. It has a positive effect on dionas. +reagent-desc-robust-harvest = A highly effective fertilizer with a limited potency-boosting effect on plants. Use it cautiously, as excessive application can reduce plant yield. It has a particularly beneficial effect on dionas. reagent-name-weed-killer = weed killer reagent-desc-weed-killer = A mixture that targets weeds. Very effective against kudzu. While useful it slowly poisons plants with toxins, be careful when using it. From 5b87c905115a1c6857c4009348bb4d8333ff0fb9 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Mon, 27 May 2024 18:37:27 -0400 Subject: [PATCH 103/235] Prevent stacking pipes (#28308) * prevent stacking pipes * access * notafet review * notafet review pt. 2 * not the actual fix --- .../PipeRestrictOverlapComponent.cs | 9 ++ .../PipeRestrictOverlapSystem.cs | 123 ++++++++++++++++++ .../ConstructionSystem.Initial.cs | 39 +++--- .../NodeContainer/Nodes/PipeNode.cs | 16 +-- .../conditions/no-unstackable-in-tile.ftl | 1 + .../Structures/Piping/Atmospherics/binary.yml | 1 + .../Structures/Piping/Atmospherics/pipes.yml | 1 + .../Structures/Piping/Atmospherics/unary.yml | 2 + .../Structures/Power/Generation/teg.yml | 1 + 9 files changed, 170 insertions(+), 23 deletions(-) create mode 100644 Content.Server/Atmos/Components/PipeRestrictOverlapComponent.cs create mode 100644 Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs diff --git a/Content.Server/Atmos/Components/PipeRestrictOverlapComponent.cs b/Content.Server/Atmos/Components/PipeRestrictOverlapComponent.cs new file mode 100644 index 00000000000..49e1a8c94d3 --- /dev/null +++ b/Content.Server/Atmos/Components/PipeRestrictOverlapComponent.cs @@ -0,0 +1,9 @@ +using Content.Server.Atmos.EntitySystems; + +namespace Content.Server.Atmos.Components; + +/// +/// This is used for restricting anchoring pipes so that they do not overlap. +/// +[RegisterComponent, Access(typeof(PipeRestrictOverlapSystem))] +public sealed partial class PipeRestrictOverlapComponent : Component; diff --git a/Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs b/Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs new file mode 100644 index 00000000000..c2ff87ca79c --- /dev/null +++ b/Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs @@ -0,0 +1,123 @@ +using System.Linq; +using Content.Server.Atmos.Components; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.Nodes; +using Content.Server.Popups; +using Content.Shared.Atmos; +using Content.Shared.Construction.Components; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Shared.Map.Components; + +namespace Content.Server.Atmos.EntitySystems; + +/// +/// This handles restricting pipe-based entities from overlapping outlets/inlets with other entities. +/// +public sealed class PipeRestrictOverlapSystem : EntitySystem +{ + [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly TransformSystem _xform = default!; + + private readonly List _anchoredEntities = new(); + private EntityQuery _nodeContainerQuery; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnAnchorStateChanged); + SubscribeLocalEvent(OnAnchorAttempt); + + _nodeContainerQuery = GetEntityQuery(); + } + + private void OnAnchorStateChanged(Entity ent, ref AnchorStateChangedEvent args) + { + if (!args.Anchored) + return; + + if (HasComp(ent) && CheckOverlap(ent)) + { + _popup.PopupEntity(Loc.GetString("pipe-restrict-overlap-popup-blocked", ("pipe", ent.Owner)), ent); + _xform.Unanchor(ent, Transform(ent)); + } + } + + private void OnAnchorAttempt(Entity ent, ref AnchorAttemptEvent args) + { + if (args.Cancelled) + return; + + if (!_nodeContainerQuery.TryComp(ent, out var node)) + return; + + var xform = Transform(ent); + if (CheckOverlap((ent, node, xform))) + { + _popup.PopupEntity(Loc.GetString("pipe-restrict-overlap-popup-blocked", ("pipe", ent.Owner)), ent, args.User); + args.Cancel(); + } + } + + [PublicAPI] + public bool CheckOverlap(EntityUid uid) + { + if (!_nodeContainerQuery.TryComp(uid, out var node)) + return false; + + return CheckOverlap((uid, node, Transform(uid))); + } + + public bool CheckOverlap(Entity ent) + { + if (ent.Comp2.GridUid is not { } grid || !TryComp(grid, out var gridComp)) + return false; + + var indices = _map.TileIndicesFor(grid, gridComp, ent.Comp2.Coordinates); + _anchoredEntities.Clear(); + _map.GetAnchoredEntities((grid, gridComp), indices, _anchoredEntities); + + foreach (var otherEnt in _anchoredEntities) + { + // this should never actually happen but just for safety + if (otherEnt == ent.Owner) + continue; + + if (!_nodeContainerQuery.TryComp(otherEnt, out var otherComp)) + continue; + + if (PipeNodesOverlap(ent, (otherEnt, otherComp, Transform(otherEnt)))) + return true; + } + + return false; + } + + public bool PipeNodesOverlap(Entity ent, Entity other) + { + var entDirs = GetAllDirections(ent).ToList(); + var otherDirs = GetAllDirections(other).ToList(); + + foreach (var dir in entDirs) + { + foreach (var otherDir in otherDirs) + { + if ((dir & otherDir) != 0) + return true; + } + } + + return false; + + IEnumerable GetAllDirections(Entity pipe) + { + foreach (var node in pipe.Comp1.Nodes.Values) + { + // we need to rotate the pipe manually like this because the rotation doesn't update for pipes that are unanchored. + if (node is PipeNode pipeNode) + yield return pipeNode.OriginalPipeDirection.RotatePipeDirection(pipe.Comp2.LocalRotation); + } + } + } +} diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 17ed5c90f4d..04d3722c66c 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -15,6 +15,7 @@ using Content.Shared.Inventory; using Content.Shared.Storage; using Robust.Shared.Containers; +using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Timing; @@ -91,7 +92,14 @@ private IEnumerable EnumerateNearby(EntityUid user) } // LEGACY CODE. See warning at the top of the file! - private async Task Construct(EntityUid user, string materialContainer, ConstructionGraphPrototype graph, ConstructionGraphEdge edge, ConstructionGraphNode targetNode) + private async Task Construct( + EntityUid user, + string materialContainer, + ConstructionGraphPrototype graph, + ConstructionGraphEdge edge, + ConstructionGraphNode targetNode, + EntityCoordinates coords, + Angle angle = default) { // We need a place to hold our construction items! var container = _container.EnsureContainer(user, materialContainer, out var existed); @@ -261,7 +269,7 @@ void ShutdownContainers() } var newEntityProto = graph.Nodes[edge.Target].Entity.GetId(null, user, new(EntityManager)); - var newEntity = EntityManager.SpawnEntity(newEntityProto, EntityManager.GetComponent(user).Coordinates); + var newEntity = Spawn(newEntityProto, _transformSystem.ToMapCoordinates(coords), rotation: angle); if (!TryComp(newEntity, out ConstructionComponent? construction)) { @@ -376,7 +384,13 @@ public async Task TryStartItemConstruction(string prototype, EntityUid use } } - if (await Construct(user, "item_construction", constructionGraph, edge, targetNode) is not { Valid: true } item) + if (await Construct( + user, + "item_construction", + constructionGraph, + edge, + targetNode, + Transform(user).Coordinates) is not { Valid: true } item) return false; // Just in case this is a stack, attempt to merge it. If it isn't a stack, this will just normally pick up @@ -511,23 +525,18 @@ void Cleanup() return; } - if (await Construct(user, (ev.Ack + constructionPrototype.GetHashCode()).ToString(), constructionGraph, - edge, targetNode) is not {Valid: true} structure) + if (await Construct(user, + (ev.Ack + constructionPrototype.GetHashCode()).ToString(), + constructionGraph, + edge, + targetNode, + GetCoordinates(ev.Location), + constructionPrototype.CanRotate ? ev.Angle : Angle.Zero) is not {Valid: true} structure) { Cleanup(); return; } - // We do this to be able to move the construction to its proper position in case it's anchored... - // Oh wow transform anchoring is amazing wow I love it!!!! - // ikr - var xform = Transform(structure); - var wasAnchored = xform.Anchored; - xform.Anchored = false; - xform.Coordinates = GetCoordinates(ev.Location); - xform.LocalRotation = constructionPrototype.CanRotate ? ev.Angle : Angle.Zero; - xform.Anchored = wasAnchored; - RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack, GetNetEntity(structure))); _adminLogger.Add(LogType.Construction, LogImpact.Low, $"{ToPrettyString(user):player} has turned a {ev.PrototypeName} construction ghost into {ToPrettyString(structure)} at {Transform(structure).Coordinates}"); Cleanup(); diff --git a/Content.Server/NodeContainer/Nodes/PipeNode.cs b/Content.Server/NodeContainer/Nodes/PipeNode.cs index 861f3eea98d..31ee5712493 100644 --- a/Content.Server/NodeContainer/Nodes/PipeNode.cs +++ b/Content.Server/NodeContainer/Nodes/PipeNode.cs @@ -20,7 +20,7 @@ public partial class PipeNode : Node, IGasMixtureHolder, IRotatableNode /// The directions in which this pipe can connect to other pipes around it. /// [DataField("pipeDirection")] - private PipeDirection _originalPipeDirection; + public PipeDirection OriginalPipeDirection; /// /// The *current* pipe directions (accounting for rotation) @@ -110,26 +110,26 @@ public override void Initialize(EntityUid owner, IEntityManager entMan) return; var xform = entMan.GetComponent(owner); - CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation); + CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(xform.LocalRotation); } bool IRotatableNode.RotateNode(in MoveEvent ev) { - if (_originalPipeDirection == PipeDirection.Fourway) + if (OriginalPipeDirection == PipeDirection.Fourway) return false; // update valid pipe direction if (!RotationsEnabled) { - if (CurrentPipeDirection == _originalPipeDirection) + if (CurrentPipeDirection == OriginalPipeDirection) return false; - CurrentPipeDirection = _originalPipeDirection; + CurrentPipeDirection = OriginalPipeDirection; return true; } var oldDirection = CurrentPipeDirection; - CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(ev.NewRotation); + CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(ev.NewRotation); return oldDirection != CurrentPipeDirection; } @@ -142,12 +142,12 @@ public override void OnAnchorStateChanged(IEntityManager entityManager, bool anc if (!RotationsEnabled) { - CurrentPipeDirection = _originalPipeDirection; + CurrentPipeDirection = OriginalPipeDirection; return; } var xform = entityManager.GetComponent(Owner); - CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation); + CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(xform.LocalRotation); } public override IEnumerable GetReachableNodes(TransformComponent xform, diff --git a/Resources/Locale/en-US/construction/conditions/no-unstackable-in-tile.ftl b/Resources/Locale/en-US/construction/conditions/no-unstackable-in-tile.ftl index 37ce0de9e8e..715825e801c 100644 --- a/Resources/Locale/en-US/construction/conditions/no-unstackable-in-tile.ftl +++ b/Resources/Locale/en-US/construction/conditions/no-unstackable-in-tile.ftl @@ -1 +1,2 @@ construction-step-condition-no-unstackable-in-tile = You cannot make a stack of similar devices. +pipe-restrict-overlap-popup-blocked = { CAPITALIZE(THE($pipe))} doesn't fit over the other pipes! diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index fa5804c6452..213b0b893d9 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -358,6 +358,7 @@ - type: PipeColorVisuals - type: Rotatable - type: GasRecycler + - type: PipeRestrictOverlap - type: NodeContainer nodes: inlet: diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml index 0025fc5ae1b..c2fc4e0565b 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml @@ -51,6 +51,7 @@ - type: Appearance - type: PipeColorVisuals - type: NodeContainer + - type: PipeRestrictOverlap - type: AtmosUnsafeUnanchor - type: AtmosPipeColor - type: Tag diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index c0664602b49..d301f43c788 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -246,6 +246,7 @@ key: enum.ThermomachineUiKey.Key - type: WiresPanel - type: WiresVisuals + - type: PipeRestrictOverlap - type: NodeContainer nodes: pipe: @@ -420,6 +421,7 @@ - type: GasCondenser - type: AtmosPipeColor - type: AtmosDevice + - type: PipeRestrictOverlap - type: ApcPowerReceiver powerLoad: 10000 - type: Machine diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml index 9a378c26a44..78d979ab8eb 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml @@ -176,6 +176,7 @@ nodeGroupID: Teg - type: AtmosUnsafeUnanchor + - type: PipeRestrictOverlap - type: TegCirculator - type: StealTarget stealGroup: Teg From b4ead5b9ccb0a12f69c51bf3559547c89a5bb18c Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 27 May 2024 22:38:34 +0000 Subject: [PATCH 104/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 31c41b9ed5b..e45f9d83ccf 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Ubaser - changes: - - message: Unpressurized areas now deal heat damage along with blunt. - type: Tweak - id: 6126 - time: '2024-03-12T02:38:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25770 - author: nikthechampiongr changes: - message: Recyclers now leave logs when they gib people. @@ -3870,3 +3863,12 @@ id: 6625 time: '2024-05-27T00:11:17.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28126 +- author: EmoGarbage404 + changes: + - message: Multiple pipes facing the same direction can no longer be placed on the + same tile. You can still have overlapping straight pipes or corners going in + opposite directions. + type: Fix + id: 6626 + time: '2024-05-27T22:37:27.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28308 From d1ea25f4fab929d40b4c424cfb63cac5c7468174 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 27 May 2024 22:41:06 +0000 Subject: [PATCH 105/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e45f9d83ccf..4dbcc6de133 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- author: nikthechampiongr - changes: - - message: Recyclers now leave logs when they gib people. - type: Add - - message: People sending a broadcast in the communications console now leave logs. - type: Add - id: 6127 - time: '2024-03-12T10:57:05.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26008 - author: ShadowCommander changes: - message: Fixed arrivals shuttle not docking with the station when it was slowly @@ -3872,3 +3863,10 @@ id: 6626 time: '2024-05-27T22:37:27.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28308 +- author: Emisse + changes: + - message: Derotate Train temporarily + type: Tweak + id: 6627 + time: '2024-05-27T22:40:00.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28328 From 38b4bbf97cb0f06a22e8d7753db178e5d841ae98 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Mon, 27 May 2024 18:43:17 -0400 Subject: [PATCH 106/235] Fix under-selecting antags (#28327) Fix under selecting antags --- .../Antag/AntagSelectionSystem.API.cs | 23 +++++++++++++++---- Content.Server/Antag/AntagSelectionSystem.cs | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs index 3b5855cdf95..16d83c4f3fc 100644 --- a/Content.Server/Antag/AntagSelectionSystem.API.cs +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -52,12 +52,26 @@ public bool TryGetNextAvailableDefinition(Entity ent, /// Gets the number of antagonists that should be present for a given rule based on the provided pool. /// A null pool will simply use the player count. /// - public int GetTargetAntagCount(Entity ent, AntagSelectionPlayerPool? pool = null) + public int GetTargetAntagCount(Entity ent, int? playerCount = null) { var count = 0; foreach (var def in ent.Comp.Definitions) { - count += GetTargetAntagCount(ent, pool, def); + count += GetTargetAntagCount(ent, playerCount, def); + } + + return count; + } + + public int GetTotalPlayerCount(IList pool) + { + var count = 0; + foreach (var session in pool) + { + if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie) + continue; + + count++; } return count; @@ -67,14 +81,13 @@ public int GetTargetAntagCount(Entity ent, AntagSelecti /// Gets the number of antagonists that should be present for a given antag definition based on the provided pool. /// A null pool will simply use the player count. /// - public int GetTargetAntagCount(Entity ent, AntagSelectionPlayerPool? pool, AntagSelectionDefinition def) + public int GetTargetAntagCount(Entity ent, int? playerCount, AntagSelectionDefinition def) { // TODO ANTAG // make pool non-nullable // Review uses and ensure that people are INTENTIONALLY including players in the lobby if this is a mid-round // antag selection. - var poolSize = pool?.Count ?? _playerManager.Sessions - .Count(s => s.State.Status is not SessionStatus.Disconnected and not SessionStatus.Zombie); + var poolSize = playerCount ?? GetTotalPlayerCount(_playerManager.Sessions); // factor in other definitions' affect on the count. var countOffset = 0; diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 5b6c891ddfc..a86611bedb3 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -203,7 +203,7 @@ public void ChooseAntags(Entity ent, IList ent, IList pool, AntagSelectionDefinition def) { var playerPool = GetPlayerPool(ent, pool, def); - var count = GetTargetAntagCount(ent, playerPool, def); + var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def); for (var i = 0; i < count; i++) { From d4de557c4b60dfa90899b5b68964ae1a9cc8b8a8 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 27 May 2024 22:44:23 +0000 Subject: [PATCH 107/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 4dbcc6de133..98ec517a502 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: ShadowCommander - changes: - - message: Fixed arrivals shuttle not docking with the station when it was slowly - spinning. - type: Fix - id: 6128 - time: '2024-03-12T12:57:35.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26033 - author: liltenhead changes: - message: Buffed the zombie virus to do purely poison damage, and more likely to @@ -3870,3 +3862,10 @@ id: 6627 time: '2024-05-27T22:40:00.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28328 +- author: EmoGarbage404 + changes: + - message: Fixed not enough antags being selected for gamemodes. + type: Fix + id: 6628 + time: '2024-05-27T22:43:17.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28327 From 3acf2363000ffa6a81f43d8f51bb5861bea65960 Mon Sep 17 00:00:00 2001 From: Kara Date: Mon, 27 May 2024 16:09:40 -0700 Subject: [PATCH 108/235] Update engine to 223.2.0 (#28329) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 796abe12305..721408bb373 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 796abe1230554c31daffbfa6559a0a4bcf50b1af +Subproject commit 721408bb37317e7a66cd34655e068369c47912ac From 67ad88d958505caa845de3f0b7be4d6ac68315c7 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Mon, 27 May 2024 20:04:32 -0400 Subject: [PATCH 109/235] remove firefighting remote and bolting firelocks (#28330) remove firefighting remote --- Resources/Migrations/migration.yml | 3 +++ Resources/Prototypes/Access/engineering.yml | 6 ------ .../Catalog/Fills/Lockers/engineer.yml | 2 -- .../Entities/Objects/Devices/door_remote.yml | 18 ------------------ .../Structures/Doors/Firelocks/firelock.yml | 3 --- 5 files changed, 3 insertions(+), 29 deletions(-) diff --git a/Resources/Migrations/migration.yml b/Resources/Migrations/migration.yml index a2c8d060e64..f8e6f46fcea 100644 --- a/Resources/Migrations/migration.yml +++ b/Resources/Migrations/migration.yml @@ -329,3 +329,6 @@ chem_master: ChemMaster # 2024-05-21 CrateJanitorExplosive: ClosetJanitorBombFilled + +# 2024-05-27 +DoorRemoteFirefight: null diff --git a/Resources/Prototypes/Access/engineering.yml b/Resources/Prototypes/Access/engineering.yml index 94901fdf7af..f2f79fa805f 100644 --- a/Resources/Prototypes/Access/engineering.yml +++ b/Resources/Prototypes/Access/engineering.yml @@ -16,9 +16,3 @@ - ChiefEngineer - Engineering - Atmospherics - -- type: accessGroup - id: FireFight - tags: - - Engineering - - Atmospherics diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml b/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml index b3efb6ec1d2..0477d250db2 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml @@ -94,7 +94,6 @@ - id: GasAnalyzer - id: MedkitOxygenFilled - id: HolofanProjector - - id: DoorRemoteFirefight - id: RCD - id: RCDAmmo - id: LunchboxEngineeringFilledRandom # Delta-V Lunchboxes! @@ -113,7 +112,6 @@ - id: GasAnalyzer - id: MedkitOxygenFilled - id: HolofanProjector - - id: DoorRemoteFirefight - id: RCD - id: RCDAmmo - id: LunchboxEngineeringFilledRandom # Delta-V Lunchboxes! diff --git a/Resources/Prototypes/Entities/Objects/Devices/door_remote.yml b/Resources/Prototypes/Entities/Objects/Devices/door_remote.yml index a2419101c65..76904ea2685 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/door_remote.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/door_remote.yml @@ -142,24 +142,6 @@ groups: - Engineering -- type: entity - parent: DoorRemoteDefault - id: DoorRemoteFirefight - name: fire-fighting door remote - description: A gadget which can open and bolt FireDoors remotely. - components: - - type: Sprite - layers: - - state: door_remotebase - - state: door_remotelightscolour - color: "#ff9900" - - state: door_remotescreencolour - color: "#e02020" - - - type: Access - groups: - - FireFight - - type: entity parent: DoorRemoteDefault id: DoorRemoteAll diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index 860db862aee..c098409f3aa 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -105,9 +105,6 @@ arc: 360 - type: StaticPrice price: 150 - - type: DoorBolt - - type: AccessReader - access: [ [ "Engineering" ] ] - type: PryUnpowered pryModifier: 0.5 From aaea3e2cf52a2562c2d465bcb713b1a5164d351b Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 28 May 2024 00:05:38 +0000 Subject: [PATCH 110/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 98ec517a502..7fa7746b62e 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: liltenhead - changes: - - message: Buffed the zombie virus to do purely poison damage, and more likely to - spread the infection per bite. - type: Tweak - id: 6129 - time: '2024-03-12T18:44:09.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25954 - author: UnicornOnLSD changes: - message: brought back the classic crew cut as an alternative to the current one @@ -3869,3 +3861,10 @@ id: 6628 time: '2024-05-27T22:43:17.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28327 +- author: EmoGarbage404 + changes: + - message: Removed the fire-fighting remote. + type: Remove + id: 6629 + time: '2024-05-28T00:04:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28330 From 6ae7ceb5a30b1ffdd4fb2a767dfc2d1405bac3d2 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 28 May 2024 02:46:54 +0200 Subject: [PATCH 111/235] Add popup for owner when inserting item in hand (#28032) --- Content.Server/Strip/StrippableSystem.cs | 3 +++ Resources/Locale/en-US/strip/strippable-component.ftl | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index 397396de501..ded9eab3eb2 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -413,6 +413,9 @@ private void StartStripInsertHand( var (time, stealth) = GetStripTimeModifiers(user, target, targetStrippable.HandStripDelay); + if (!stealth) + _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert-hand", ("user", Identity.Entity(user, EntityManager)), ("item", user.Comp.ActiveHandEntity!.Value)), target, target, PopupType.Large); + var prefix = stealth ? "stealthily " : ""; _adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands"); diff --git a/Resources/Locale/en-US/strip/strippable-component.ftl b/Resources/Locale/en-US/strip/strippable-component.ftl index 7654b20b03f..ee37a5e90c1 100644 --- a/Resources/Locale/en-US/strip/strippable-component.ftl +++ b/Resources/Locale/en-US/strip/strippable-component.ftl @@ -9,6 +9,7 @@ strippable-component-cannot-drop-message = {$owner} cannot drop that! strippable-component-alert-owner = {$user} is removing your {$item}! strippable-component-alert-owner-hidden = You feel someone fumbling in your {$slot}! strippable-component-alert-owner-insert = {$user} is putting {$item} on you! +strippable-component-alert-owner-insert-hand = {$user} is putting {$item} in your hand! # generic warning for when a user interacts with your equipped items. strippable-component-alert-owner-interact = {$user} is fumbling around with your {$item}! @@ -19,4 +20,4 @@ strip-verb-get-data-text = Strip ## UI strippable-bound-user-interface-stripping-menu-title = {$ownerName}'s inventory -strippable-bound-user-interface-stripping-menu-ensnare-button = Remove Leg Restraints \ No newline at end of file +strippable-bound-user-interface-stripping-menu-ensnare-button = Remove Leg Restraints From 90d9900e2c67535a77538f7c4ce46d0c88a0de02 Mon Sep 17 00:00:00 2001 From: Ghagliiarghii <68826635+ghagliiarghii@users.noreply.github.com> Date: Tue, 28 May 2024 02:51:30 +0200 Subject: [PATCH 112/235] Update Antagonists Guidebook Sections (#27931) * Update MinorAntagonists.xml Flesh out the Rat King and Space Dragon sections of MinorAntagonists.xml in the guidebook. * Rope the rest of the Guidebook Antag Entries into this (except Zombies they were fine) - Also fix two typos I discovered during research * oops missed a hyphen * Sydnicate :despair: * AUTOTELL ON -> OPERATIVES --- .../Entities/Objects/Misc/implanters.yml | 2 +- .../Objects/Misc/subdermal_implants.yml | 2 +- Resources/Prototypes/Objectives/ninja.yml | 2 +- .../Guidebook/Antagonist/MinorAntagonists.xml | 28 +++++++---- .../Antagonist/Nuclear Operatives.xml | 41 ++++++++-------- .../Guidebook/Antagonist/Revolutionaries.xml | 17 +++---- .../Guidebook/Antagonist/SpaceNinja.xml | 29 +++++------ .../Guidebook/Antagonist/Traitors.xml | 48 ++++++++++--------- 8 files changed, 93 insertions(+), 76 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml index c09deb13071..532bcadeb53 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml @@ -246,7 +246,7 @@ - type: entity id: MindShieldImplanter - name: mind-shield implanter + name: mindshield implanter parent: BaseImplantOnlyImplanter components: - type: Implanter diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index a0f5e254d5f..c92985c2cba 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -317,7 +317,7 @@ - type: entity parent: BaseSubdermalImplant id: MindShieldImplant - name: mind-shield implant + name: mindshield implant description: This implant will ensure loyalty to Nanotrasen and prevent mind control devices. noSpawn: true components: diff --git a/Resources/Prototypes/Objectives/ninja.yml b/Resources/Prototypes/Objectives/ninja.yml index fb94f2b3788..1576531a8b1 100644 --- a/Resources/Prototypes/Objectives/ninja.yml +++ b/Resources/Prototypes/Objectives/ninja.yml @@ -4,7 +4,7 @@ id: BaseNinjaObjective components: - type: Objective - # difficulty isn't used all since objectives are picked + # difficulty isn't used since all objectives are picked difficulty: 1.5 issuer: spiderclan - type: RoleRequirement diff --git a/Resources/ServerInfo/Guidebook/Antagonist/MinorAntagonists.xml b/Resources/ServerInfo/Guidebook/Antagonist/MinorAntagonists.xml index 8d96573a3d0..1a3486b301b 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/MinorAntagonists.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/MinorAntagonists.xml @@ -27,22 +27,21 @@ # Rat King - - + - A Rat King is a giant rat capable of setting a nest and creating rats to do their bidding (usually to get food). + A Rat King is a gigantic rat capable of eating an enormous amount of food, scampering under doors and tables, and raising an army of rats to defend them and do their bidding. ## Abilities - Abilities come at a cost to the Rat King's hunger. Simply eating replenishes it. + Abilities come at the cost of some of the Rat King's hunger. Eat to replenish it. - - Raise an Army of [color=#a4885c]Rat Servants[/color]. - - Conjure a cloud of ammonia. + - Raise an Army of [color=#a4885c]Rat Servants[/color]. You may command these [color=#a4885c]Rat Servants[/color] to stay in place, follow you, attack a target of your choice, or attack indiscriminately. + - Conjure a cloud of [color=#77b58e]ammonia[/color]. Note that [color=#77b58e]ammonia[/color] is mildly poisonous to others, but heals rats. # Space Dragon @@ -50,11 +49,13 @@ - A Space Dragon is a giant dragon that creates space carp rifts and eats the crew. + A Space Dragon is a giant extradimensional dragon that creates space carp rifts and eats the crew. ## Abilities - - Devour critical or dead victims. + - Devour critical or dead victims to heal slightly, or devour infrastructure such as doors, windows, and walls. + + - Breathe out a powerful flaming sphere which will explode periodically along a straight flight path and explode when it hits something, igniting things along the way. Be careful, your carps are not immune to the Dragon's Breath! @@ -62,6 +63,15 @@ - - Summon a Carp Rift that periodically spawns [color=#a4885c]Space Carp[/color]. + - Summon a Carp Rift that periodically spawns [color=#a4885c]Space Carp[/color]. + + ## Carp Rifts + + - Space Dragons may have up to three Carp Rifts active at any given time. + - Carp Rifts spawn a [color=#a4885c]Space Carp[/color] every thirty seconds while active. + - Rifts charge with extradimensional energy over a period of five minutes and are vulnerable during this time. Protect them! The space dragon will suffer a temporary debilitating feedback effect in the event that its rifts are destroyed before they are done charging. + - You may only charge one rift at any given time. + - After the rifts are done charging, they are invulnerable and will continue to spawn [color=#a4885c]Space Carp[/color] so long as the Dragon lives. + - If a period of five minutes passes since the appearance of the Space Dragon or the last time a Carp Rift was charging and unless the Space Dragon already controls the maximum of three Carp Rifts, the Space Dragon will disappear. diff --git a/Resources/ServerInfo/Guidebook/Antagonist/Nuclear Operatives.xml b/Resources/ServerInfo/Guidebook/Antagonist/Nuclear Operatives.xml index f3ced7eeda3..f6b91745710 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/Nuclear Operatives.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/Nuclear Operatives.xml @@ -1,24 +1,24 @@ # Nuclear Operatives - AUTOTELL ON STANDBY. YOUR OBJECTIVES ARE SIMPLE. DELIVER THE PAYLOAD AND GET OUT BEFORE THE PAYLOAD DETONATES. BEGIN MISSION. + OPERATIVES STANDBY. YOUR OBJECTIVES ARE SIMPLE. DELIVER THE PAYLOAD AND GET OUT BEFORE THE PAYLOAD DETONATES. BEGIN MISSION. If you hear this message then congratulations! You have just been chosen to be a nuclear operative for the syndicate. You have one goal, to blow up the space station with a nuclear fission explosive. ## Operatives Your team may contain varying amounts of three different roles: - - The [color=#a4885c]Nukie Commander[/color] wears the unique commander’s hardsuit, and will create or approve of the battle plan. + - The [color=#a4885c]Syndicate Commander[/color] wears the unique commander’s hardsuit and will create or approve of the battle plan. - - The [color=#a4885c]Nukie Agent[/color] wears the blood-red medic hardsuit, and can act as a combat medic for the mission. + - The [color=#a4885c]Syndicate Agent[/color] wears the blood-red medic hardsuit and can act as a combat medic for the mission. - - Regular [color=#a4885c]Nuclear Operatives[/color], who have the normal blood red hardsuit. + - Regular [color=#a4885c]Nuclear Operatives[/color] who have the normal blood red hardsuit. @@ -27,7 +27,7 @@ ## Preparation - Taking on a whole station in a fight is a difficult job, luckily you are well prepared for the job. Every operative has an [color=#a4885c]Uplink[/color]. It comes with 40 [color=#a4885c]Telecrystals[/color], which you can use to purchase gear. What you get depends on the strategy picked by your commander or voted for by the team. Only your commander and agent will have passenger access, but no external airlock access. This means you will need something to hack or blast your way onto the station. In general you should need some weapons, something to breach doors, and utility/healing. + Taking on a whole station in a fight is a difficult job, luckily you are well prepared for the job. Every operative has an [color=#a4885c]Uplink[/color]. It comes with 40 [color=#a4885c]Telecrystals[/color], which you can use to purchase gear. What you get depends on the strategy picked by your commander or voted for by the team. Only your commander and agent will have passenger access, but no external airlock access. This means you will need something to hack or blast your way onto the station. In general, you will need some weapons to protect yourself and kill the crew, something to breach doors to get where you need to go, utility items to make your job easier, and healing to not die. @@ -35,9 +35,14 @@ ## Getting to the Station - After gearing up and creating a plan, grab a jetpack from your armory and go with your other operatives to the [color=#a4885c]Nukie Shuttle[/color]. Here you will find some extra explosives and tools, as well as your nuke and nuke codes. + After gearing up and creating a plan, grab a jetpack from your armory and go with your other operatives to the [color=#a4885c]Syndicate Shuttle[/color]. Here you will find some extra explosives and tools, as well as your nuke and nuke codes. You will find an [color=#a4885c]IFF Console[/color] on the shuttle, it allows you to hide from other ships and mass scanners. Make sure that [color=#a4885c]Show IFF[/color] and [color=#a4885c]Show Vessel[/color] are toggled off to hide your shuttle from the crew. When everyone is ready, FTL to the station and fly to it with a jetpack. Don't forget the nuclear fission explosive on your ship if you are going to use it, and definitely don't forget the nuke codes or pinpointer. + + + + + ## The Disk You will notice that each operative starts with a [color=#a4885c]Pinpointer[/color]. This device is one of the most important items to the mission, and you should keep it with you at all times. Turn on the pinpointer when you arrive at the station and it will always point to the [color=#a4885c]Nuke Disk[/color], your next objective. @@ -51,7 +56,7 @@ ## The Nuke - On your shuttle you will find one of two [color=#a4885c]Nuclear Fission Explosives[/color] and a paper with the nuke codes. The paper will tell you which explosive it corresponds to. If the ID is the same as the one on your nuke, the codes will only work for it. If it is not, then they will only work for the explosive in the station's vault. + On your shuttle, you will find one of two [color=#a4885c]Nuclear Fission Explosives[/color] and a paper with the nuke codes. The paper will tell you which explosive it corresponds to. If the ID is the same as the one on your nuke, the codes will only work for it. If it is not, then they will only work for the explosive in the station's vault. Once you acquire the nuke disk, put it into the nuke and use the code to arm it. It takes 30 seconds for a crewmember to disarm it, and it will count down from 300. Be prepared to defend the nuke for as long as possible, remember that escaping alive isn't necessary, but recommended. @@ -61,28 +66,26 @@ ## Victories - The “victor” of the round is announced on the round end screen, as well as how much they won by. The scale of the victory depends on circumstances at the end of the round. + The “victor” of the round is announced on the round end screen, as well as by how much they won by. The scale of the victory depends on the circumstances at the end of the round. [color=#a4885c]Syndicate Major Victory[/color] - - The nuke detonates on station - - The crew escapes on the evac shuttle, but the nuke was armed - - The nuke was armed and delivered to central command on the evac shuttle + - The nuke detonates on the station + - The crew escapes on the evac shuttle, but the nuke is still armed + - The nuke is armed and delivered to central command on the evac shuttle [color=#a4885c]Syndicate Minor Victory[/color] - - The crew escapes on evac, but every nukie survives - - The crew escapes and some nukies die, but the crew loses the disk + - The crew escapes on evac, but every nuclear operative survives + - The crew escapes and some nuclear operatives die, but the crew loses the disk [color=#a4885c]Neutral Victory[/color] - - The nuke detonates off station + - The nuke detonates off the station [color=#a4885c]Crew Minor Victory[/color] - - The crew escapes on evac and some nukies die, but the crew keeps the disk + - The crew escapes on evac and some nuclear operatives die, but the crew keeps the disk [color=#a4885c]Crew Major Victory[/color] - All nuclear operatives die - - The crew blow up the nukie outpost with the nuke + - The crew blow up the nuclear operative outpost with the nuke - If you feel that you won't be able to completely win as crew or nukie, consider these options for a compromise. + If you feel that you won't be able to completely win as Crew or Nuclear Operatives, consider these options for a compromise. - - diff --git a/Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml b/Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml index e69220fc2a0..d873bdbbca8 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml @@ -1,7 +1,7 @@ # Revolutionaries - - Revolutionaries are antagonists that are sponsored by the Syndicate to take over the station. + - Revolutionaries are antagonists who are sponsored by the Syndicate to take over the station. ## Head Revolutionaries @@ -10,7 +10,7 @@ - [color=#5e9cff]Head Revolutionaries[/color] are chosen at the start of the shift and are tasked with taking over the station by killing, exiling or cuffing all of the Command staff. Head Revolutionaries will be given a [color=#a4885c]Flash[/color] and a pair of [color=#a4885c]Sunglasses[/color] to aid them. + [color=#5e9cff]Head Revolutionaries[/color] are chosen at the start of the shift and are tasked with taking over the station by killing, exiling, or cuffing all of the Command staff. Head Revolutionaries will be given a [color=#a4885c]Flash[/color] and a pair of [color=#a4885c]Sunglasses[/color] to aid them. ## Conversion @@ -18,7 +18,7 @@ - You can convert crew members by using a [color=#a4885c]Flash[/color] in [color=#ff0000]harm mode[/color] and attacking someone with it. Any flash can be used for conversion, but remember that flashes have limited charges. + You can convert crew members to [color=#ff0000]Revolutionaries[/color] by using a [color=#a4885c]Flash[/color] in [color=#ff0000]harm mode[/color] and attacking someone with it. Any flash can be used for conversion, but remember that flashes have limited charges. @@ -26,22 +26,23 @@ - However, things such as [color=#a4885c]Sunglasses[/color] and [color=#a4885c]Welding Masks[/color] offer flash protection and people wearing these will not be able to be converted. + However, gear providing flash protection such as [color=#a4885c]Sunglasses[/color] and [color=#a4885c]Welding Masks[/color] will also block your flashes, and crew wearing these will not be able to be converted. - While not flash protection, a [color=#a4885c]MindShield Implant[/color] will prevent the implanted person from being converted into a revolutionary. Assume all of [color=#a4885c]Security[/color] and [color=#a4885c]Command[/color] are implanted already. + While not flash protection, a [color=#a4885c]MindShield Implant[/color] will prevent the implanted person from being converted into a revolutionary. Assume all of [color=#a4885c]Security[/color] and [color=#a4885c]Command[/color] are implanted already. + Additionally, a [color=#a4885c]MindShield Implanter[/color] used on a [color=#ff0000]Revolutionary[/color] will de-convert them and they will no longer be loyal to your cause. If a [color=#a4885c]MindShield Implanter[/color] is used on a [color=#5e9cff]Head Revolutionary[/color], it will be destroyed but this will reveal you so be careful! ## Revolutionary - A [color=#ff0000]Revolutionary[/color] is the result of being converted by a [color=#5e9cff]Head Revolutionary[/color]. Revolutionaries are underlings of the Head Revolutionaries and should follow orders given by them and prioritize their well-being over anything else because if they die you will lose. + A [color=#ff0000]Revolutionary[/color] is the result of being converted by a [color=#5e9cff]Head Revolutionary[/color]. Revolutionaries are underlings of the Head Revolutionaries and should follow orders given by them and prioritize their well-being over anything else because if the [color=#5e9cff]Head Revolutionaries[/color] die the Revolution will fail. Keep in mind that you can't convert others as a regular revolutionary, only your boss can do that. ## Objectives - You must eliminate, exile or arrest all of the following Command staff on station in no particular order. + You must eliminate, exile, or arrest all of the following Command staff on station in no particular order. - Captain - Head of Personnel - Chief Engineer @@ -51,4 +52,4 @@ - Chief Medical Officer Remember, your objective is to take over the station and not to destroy it so try to minimize damage where possible. Viva la revolución! - + \ No newline at end of file diff --git a/Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml b/Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml index f088b9f27b1..496c00e6c33 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml @@ -1,15 +1,15 @@ # Space Ninja - The [color=#66FF00]Space Ninja[/color] is a ghost role randomly available mid-late game. If you pick it you will be given your gear, your objectives and the greeting. + The [color=#66FF00]Space Ninja[/color] is a ghost role randomly available mid-late game. If you pick it you will be given your gear, your objectives, and the greeting. You are a ninja, but in space. [color=#66FF00]The Spider Clan[/color] has sent you to the station to wreak all kinds of havoc, and you are equipped to keep it silent-but-deadly. - Whether you mercilessly pick off the station's crew one by one, or assassinate the clown over and over, your discipline has taught you that [color=#66FF00]your objectives must be at least attempted[/color]. For honor! + Whether you mercilessly pick off the station's crew one by one or assassinate the clown over and over, your discipline has taught you that [color=#66FF00]your objectives must be at least attempted[/color]. For honor! # Equipment - You begin implanted with a [color=#a4885c]death acidifier[/color], so if you are KIA or decide to commit seppuku you will leave behind one final gift to the janitor and all your precious equipment is kept out of enemy hands. + You begin implanted with a [color=#a4885c]death acidifier[/color], so if you are KIA or decide to commit seppuku you will leave behind one final gift to the janitor, and all your precious equipment is kept out of enemy hands. Your bag is full of tools for more subtle sabotage, along with a survival box if you need a snack. @@ -25,7 +25,7 @@ Your suit requires power to function. Its [color=#a4885c]internal battery[/color] can be replaced by clicking on it with another one, and [color=#a4885c]higher capacity batteries[/color] mean a [color=#a4885c]highly effective ninja[/color]. You can see the current charge by examining the suit or in a sweet battery alert at the top right of your screen. - If you run out of power and need to recharge your battery, just use your gloves to drain an APC, substation or a SMES. + If you run out of power and need to recharge your battery, just use your gloves to drain an APC, substation, or SMES. ## Ninja Gloves @@ -33,16 +33,16 @@ [color=#66FF00]These bad boys are your bread and butter.[/color] - They are made from [color=#a4885c]insulated nanomachines[/color] to assist you in gracefully breaking and entering into your destination without leaving behind fingerprints. + They are made from [color=#a4885c]insulated nanomachines[/color] to assist you in gracefully breaking into destinations without leaving behind fingerprints. You have an action to toggle gloves. When the gloves are turned on, they allow you to use [color=#a4885c]special abilities[/color], which are triggered by interacting with things with an empty hand and with combat mode disabled. Your glove abilities include: - Emagging an unlimited number of doors. - - Draining power from transformers such as APCs, substations or SMESes. The higher the voltage, the more efficient the draining is. + - Draining power from transformers such as APCs, substations, or SMESes. The higher the voltage, the more efficient the draining is. - You can shock any mob, stunning and slightly damaging them. - - You can download technologies from a R&D server for one of your objectives. - - You can hack a communications console to call in a threat. + - You can download technologies from an R&D server for one of your objectives. + - You can hack a communications console to call in a [textlink="threat" link="MinorAntagonists"]. ## Energy Katana @@ -52,7 +52,7 @@ Deals a lot of damage and can be recalled at will, costing suit power proportional to the distance teleported. While in hand you can [color=#a4885c]teleport[/color] to anywhere that you can see, meaning most doors and windows, but not past solid walls. - This has a limited number of charges which regenerate slowly, so keep a charge or two spare incase you need a quick getaway. + This has a limited number of charges which regenerate slowly, so keep a charge or two spare in case you need a quick getaway. ## Spider Clan Charge @@ -71,10 +71,11 @@ # Objectives - - Download X research nodes: Use your gloves on an R&D server with a number of unlocked technologies - - Doorjack X doors on the station: Use your gloves to emag a number of doors. - - Detonate the spider clan charge: Plant your spider clan charge at a random location and watch it go boom. - - Call in a threat: Use your gloves on a communications console. + - Download \[9-13\] research nodes: Use your gloves on an R&D server with a number of unlocked technologies + - Doorjack \[15-40\] doors on the station: Use your gloves to emag a number of doors. + - Detonate the spider clan charge: Plant your spider clan charge at the specified location and watch it go boom! + - Call in a [textlink="threat" link="MinorAntagonists"]: Use your gloves on a communications console. + - Set everyone to wanted: Use your gloves on a criminal records console. - Survive: Don't die. - + \ No newline at end of file diff --git a/Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml b/Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml index 45d41631c2e..e5d34304844 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml @@ -10,7 +10,7 @@ - - The [color=#a4885c]Uplink[/color] is the most important tool as a Traitor, as it can purchase tools and weapons with [color=#a4885c]telecrystals[/color](TC). + - The [color=#a4885c]Uplink[/color] is the most important tool for a Traitor, as it can purchase tools and weapons with [color=#a4885c]telecrystals[/color](TC). @@ -22,16 +22,23 @@ Make sure to relock your [color=#a4885c]PDA[/color] to prevent anyone else from seeing it! - Various gear include: - - - - - + + + + + + + + + + + + ## Objectives - - When becoming a Traitor, you will have a list of objectives, ranging from escape alive, stealing something, and killing someone. Using the [color=#a4885c]Uplink[/color] will help you with most of these tasks. + - When becoming a Traitor, you will have a list of objectives, ranging from escaping alive, stealing something, and killing someone. Using the [color=#a4885c]Uplink[/color] will help you with most of these tasks. ## List of Possible Tasks @@ -40,30 +47,25 @@ - Kill or maroon a randomly selected department head. - Keep a randomly selected traitor alive. - Escape on the evacuation shuttle alive and uncuffed. - - Help a randomly selected traitor finish 2/3 of their objectives. + - Help a randomly selected traitor finish half of their objectives. - Die a glorious death. - - Steal the Captain's [color=#a4885c]ID Card[/color]. + - Steal the Captain's [color=#a4885c]ID Card[/color], [color=#a4885c]Antique Laser Pistol[/color], [color=#a4885c]Jetpack[/color], or [color=#a4885c]Nuke Disk[/color]. - - - Steal the Captain's [color=#a4885c]Antique Laser Pistol[/color]. - - - - - Steal the Captain's [color=#a4885c]Jetpack[/color]. + + - - - Steal the Chief Medical Officer's [color=#a4885c]Hypospray[/color]. + + + - Steal the Chief Medical Officer's [color=#a4885c]Hypospray[/color] or [color=#a4885c]Handheld Crew Monitor[/color]. + - - Steal the Mystagogue's [color=#a4885c]Hardsuit[/color]. + - Steal the Mystagogue's [color=#a4885c]Hardsuit[/color] or [color=#a4885c]Hand Teleporter[/color]. - - - Steal the Mystagogue's [color=#a4885c]Hand Teleporter[/color]. - - Steal the Head of Security's [color=#a4885c]Secret Documents[/color]. @@ -78,9 +80,9 @@ - - Steal the [color=#a4885c]Nuke Disk[/color]. + - Steal the Quartermaster's [color=#a4885c]Requisition Digi-Board[/color]. - + - Steal the Logistics Officer's [color=#a4885c]lucky bill[/color]. From d25743c22f149a8261a467ca47a850bbaaa91d37 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Tue, 28 May 2024 02:51:50 +0200 Subject: [PATCH 113/235] Cargo bounty corrections (#28255) cargo bounty corrections --- .../Cargo/Systems/CargoSystem.Bounty.cs | 4 ++- .../Cargo/Prototypes/CargoBountyPrototype.cs | 8 ++++- .../Prototypes/Catalog/Bounties/bounties.yml | 15 +++++++++ .../Objects/Consumable/Food/Baked/bread.yml | 18 ++++++++--- .../Objects/Consumable/Food/Baked/cake.yml | 32 +++++++++++++++++++ .../Objects/Consumable/Food/Baked/misc.yml | 9 ++++-- .../Objects/Consumable/Food/Baked/pie.yml | 10 ++++++ .../Objects/Consumable/Food/Baked/pizza.yml | 12 ++++++- .../Objects/Consumable/Food/ingredients.yml | 18 +++++++++++ .../Objects/Consumable/Food/produce.yml | 13 ++++++++ Resources/Prototypes/tags.yml | 12 +++++-- 11 files changed, 138 insertions(+), 13 deletions(-) diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index e132e4f12a3..0fcfd160bb3 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -11,6 +11,7 @@ using Content.Shared.Database; using Content.Shared.NameIdentifier; using Content.Shared.Stacks; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Server.Containers; using Robust.Shared.Containers; @@ -23,6 +24,7 @@ public sealed partial class CargoSystem { [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!; [ValidatePrototypeId] private const string BountyNameIdentifierGroup = "Bounty"; @@ -311,7 +313,7 @@ public bool IsBountyComplete(HashSet entities, IEnumerable(); foreach (var entity in entities) { - if (!entry.Whitelist.IsValid(entity, EntityManager)) + if (!_whitelistSys.IsValid(entry.Whitelist, entity) || (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity))) continue; count += _stackQuery.CompOrNull(entity)?.Count ?? 1; diff --git a/Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs b/Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs index bf4907b0dd4..b40b03672ef 100644 --- a/Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs +++ b/Content.Shared/Cargo/Prototypes/CargoBountyPrototype.cs @@ -31,7 +31,7 @@ public sealed partial class CargoBountyPrototype : IPrototype /// /// The entries that must be satisfied for the cargo bounty to be complete. /// - [DataField( required: true)] + [DataField(required: true)] public List Entries = new(); /// @@ -50,6 +50,12 @@ public readonly partial record struct CargoBountyItemEntry() [DataField(required: true)] public EntityWhitelist Whitelist { get; init; } = default!; + /// + /// A blacklist that can be used to exclude items in the whitelist. + /// + [DataField] + public EntityWhitelist? Blacklist { get; init; } = null; + // todo: implement some kind of simple generic condition system /// diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml index 3c1bd02fcfc..bedfe442872 100644 --- a/Resources/Prototypes/Catalog/Bounties/bounties.yml +++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml @@ -309,6 +309,9 @@ whitelist: tags: - Pie + blacklist: + tags: + - Slice - type: cargoBounty id: BountyPrisonUniform @@ -541,6 +544,12 @@ whitelist: tags: - Fruit + blacklist: + tags: + - Slice + - Cake + - Pie + - Bread - type: cargoBounty id: BountyVegetable @@ -552,6 +561,12 @@ whitelist: tags: - Vegetable + blacklist: + tags: + - Slice + - Cake + - Pie + - Bread - type: cargoBounty id: BountyChili diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml index e40ac40a289..22c1fe021b9 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml @@ -34,7 +34,9 @@ flavors: - bread - type: Tag - tags: [] #override bread + tags: + - Bread + - Slice - type: SolutionContainerManager solutions: food: @@ -116,6 +118,8 @@ - type: Tag tags: - Fruit + - Bread + - Slice - type: entity name: cornbread @@ -274,6 +278,8 @@ - type: Tag tags: - Meat + - Bread + - Slice - type: entity name: mimana bread @@ -418,6 +424,8 @@ - type: Tag tags: - Meat + - Bread + - Slice - type: entity name: spider meat bread @@ -476,6 +484,8 @@ - type: Tag tags: - Meat + - Bread + - Slice - type: entity name: tofu bread @@ -585,6 +595,8 @@ - type: Tag tags: - Meat + - Bread + - Slice # Other than bread/slices @@ -594,9 +606,6 @@ id: FoodBreadBaguette description: Bon appétit! components: - - type: Tag - tags: - - Baguette - type: Sprite state: baguette - type: SliceableFood @@ -824,6 +833,7 @@ tags: - VimPilot - DoorBumpOpener + - Bread - type: CanEscapeInventory baseResistTime: 2 - type: Puller diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml index c939dec52c6..922d4938885 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml @@ -23,6 +23,9 @@ Quantity: 5 - type: Item size: Normal + - type: Tag + tags: + - Cake - type: entity parent: FoodCakeBase @@ -45,6 +48,10 @@ Quantity: 1 - type: Item size: Tiny + - type: Tag + tags: + - Cake + - Slice # Custom Cake Example @@ -63,6 +70,7 @@ slice: FoodCakeBlueberrySlice - type: Tag tags: + - Cake - Fruit - type: entity @@ -78,7 +86,9 @@ color: blue - type: Tag tags: + - Cake - Fruit + - Slice # Cake @@ -203,6 +213,7 @@ slice: FoodCakeOrangeSlice - type: Tag tags: + - Cake - Fruit - type: entity @@ -214,7 +225,9 @@ state: orange-slice - type: Tag tags: + - Cake - Fruit + - Slice # Tastes like sweetness, cake, oranges. - type: entity @@ -229,6 +242,7 @@ slice: FoodCakeLimeSlice - type: Tag tags: + - Cake - Fruit - type: entity @@ -240,7 +254,9 @@ state: lime-slice - type: Tag tags: + - Cake - Fruit + - Slice # Tastes like sweetness, cake, limes. - type: entity @@ -255,6 +271,7 @@ slice: FoodCakeLemonSlice - type: Tag tags: + - Cake - Fruit - type: entity @@ -266,7 +283,9 @@ state: lemon-slice - type: Tag tags: + - Cake - Fruit + - Slice # Tastes like sweetness, cake, lemons. - type: entity @@ -296,6 +315,7 @@ Quantity: 5 - type: Tag tags: + - Cake - Fruit - type: entity @@ -323,7 +343,9 @@ Quantity: 1 - type: Tag tags: + - Cake - Fruit + - Slice - type: entity name: chocolate cake @@ -379,6 +401,7 @@ slice: FoodCakeAppleSlice - type: Tag tags: + - Cake - Fruit - type: entity @@ -391,7 +414,9 @@ state: apple-slice - type: Tag tags: + - Cake - Fruit + - Slice # Tastes like sweetness, cake, slime. - type: entity @@ -436,6 +461,7 @@ Quantity: 11 - type: Tag tags: + - Cake - Fruit - type: entity @@ -457,7 +483,9 @@ Quantity: 2.2 - type: Tag tags: + - Cake - Fruit + - Slice # Tastes like sweetness, cake, pumpkin. - type: entity @@ -686,6 +714,7 @@ tags: - VimPilot - DoorBumpOpener + - Cake - type: CanEscapeInventory baseResistTime: 2 - type: Puller @@ -752,3 +781,6 @@ color: "#FFFF00" radius: 1.4 energy: 1.4 + - type: Tag + tags: + - Slice diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml index b1bbdfb5305..fde181d8b9b 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml @@ -163,9 +163,6 @@ # Nuggets -- type: Tag - id: Nugget - - type: entity name: chicken nugget parent: FoodBakedBase @@ -530,6 +527,9 @@ Quantity: 5 - ReagentId: Theobromine Quantity: 3 + - type: Tag + tags: + - Slice - type: entity name: special brownies @@ -585,6 +585,9 @@ Quantity: 3 - ReagentId: THC Quantity: 25 + - type: Tag + tags: + - Slice - type: entity name: onion rings diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml index 8cd1c5dfab6..f97d87a9c50 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml @@ -52,6 +52,10 @@ Quantity: 1.2 - ReagentId: Vitamin Quantity: 1 + - type: Tag + tags: + - Pie + - Slice # Pie @@ -94,6 +98,7 @@ tags: - Fruit - Pie + - Slice # Tastes like pie, apple. - type: entity @@ -182,6 +187,7 @@ tags: - Fruit - Pie + - Slice # Tastes like pie, cream, banana. - type: entity @@ -224,6 +230,7 @@ tags: - Fruit - Pie + - Slice # Tastes like pie, blackberries. - type: entity @@ -263,6 +270,7 @@ tags: - Fruit - Pie + - Slice # Tastes like pie, cherries. - type: entity @@ -302,6 +310,7 @@ tags: - Meat - Pie + - Slice # Tastes like pie, meat. - type: entity @@ -342,6 +351,7 @@ tags: - Meat - Pie + - Slice # Tastes like pie, meat, acid. - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml index bdce1d44086..154f34063c8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml @@ -59,6 +59,7 @@ tags: - Pizza - ReptilianFood + - Slice # Pizza @@ -135,6 +136,7 @@ tags: - Meat - Pizza + - Slice # Tastes like crust, tomato, cheese, meat. - type: entity @@ -291,6 +293,7 @@ tags: - Meat - Pizza + - Slice # Tastes like crust, tomato, cheese, meat, laziness. - type: entity @@ -391,6 +394,7 @@ tags: - Meat - Pizza + - Slice # Tastes like crust, tomato, cheese, sausage, sass. - type: entity @@ -411,9 +415,13 @@ - state: pineapple - type: SliceableFood slice: FoodPizzaPineappleSlice + - type: Tag + tags: + - Meat + - Pizza - type: entity - name: slice of pineapple pizza + name: slice of Hawaiian pizza parent: FoodPizzaSliceBase id: FoodPizzaPineappleSlice description: A slice of joy/sin. @@ -432,6 +440,7 @@ tags: - Meat - Pizza + - Slice # Tastes like crust, tomato, cheese, pineapple, ham. #TODO: This is a meme pizza from /tg/. It has specially coded mechanics. @@ -504,6 +513,7 @@ tags: - Meat - Pizza + - Slice # Tastes like crust, tomato, cheese, pepperoni, 9 millimeter bullets. #TODO: Make this do poison damage and make cut pizza slices eventually rot into this. diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml index 1df6615a9fb..dcf2f3355ce 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml @@ -302,6 +302,9 @@ - dough - type: Sprite state: dough-slice + - type: Tag + tags: + - Slice - type: entity name: cornmeal dough @@ -331,6 +334,9 @@ - dough - type: Sprite state: cornmealdough-slice + - type: Tag + tags: + - Slice - type: entity name: tortilla dough @@ -363,6 +369,9 @@ - type: Construction graph: Tortilla node: start + - type: Tag + tags: + - Slice - type: entity name: flattened tortilla dough @@ -503,6 +512,9 @@ reagents: - ReagentId: Nutriment Quantity: 5 + - type: Tag + tags: + - Slice - type: entity name: chèvre log @@ -550,6 +562,9 @@ Quantity: 1 - ReagentId: Vitamin Quantity: 0.2 + - type: Tag + tags: + - Slice - type: entity name: tofu @@ -595,6 +610,9 @@ Quantity: 3 - ReagentId: Nutriment Quantity: 2 + - type: Tag + tags: + - Slice - type: entity name: burned mess diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 888e4e4e353..a74e3450e94 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -1065,6 +1065,9 @@ state: slice - type: Extractable grindableSolutionName: food + - type: Tag + tags: + - Slice - type: entity name: pineapple slice @@ -1085,6 +1088,7 @@ - type: Tag tags: - Fruit + - Slice - type: entity name: onion slice @@ -1108,6 +1112,10 @@ Quantity: 1 - ReagentId: Vitamin Quantity: 1 + - type: Tag + tags: + - Vegetable + - Slice - type: entity name: red onion slice @@ -1131,6 +1139,10 @@ Quantity: 1 - ReagentId: Vitamin Quantity: 1 + - type: Tag + tags: + - Vegetable + - Slice - type: entity name: chili pepper @@ -1650,6 +1662,7 @@ - type: Tag tags: - Fruit + - Slice - type: entity name: grapes diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 253aa485653..439ad47c0f8 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -27,9 +27,6 @@ - type: Tag id: ATVKeys -- type: Tag - id: Baguette - - type: Tag id: Balloon @@ -258,6 +255,9 @@ - type: Tag id: CableCoil +- type: Tag + id: Cake + - type: Tag id: CaneBlade @@ -926,6 +926,9 @@ - type: Tag id: NozzleBackTank +- type: Tag + id: Nugget # for chicken nuggets + - type: Tag id: NukeOpsUplink @@ -1122,6 +1125,9 @@ - type: Tag id: Skewer +- type: Tag + id: Slice # sliced fruit, vegetables, pizza etc. + - type: Tag id: SmallAIChip From 49ae1f57eec04f3f2387193ebb1d6b0d391547ec Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Mon, 27 May 2024 20:52:56 -0400 Subject: [PATCH 114/235] Small anomaly behavior fix (#28290) * Small anomaly behavior fix * well put together code --- Content.Server/Anomaly/AnomalySystem.cs | 25 ++++++++----------- .../Effects/ShuffleParticlesAnomalySystem.cs | 20 +++++++-------- .../Anomaly/Components/AnomalyComponent.cs | 9 ++++--- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/Content.Server/Anomaly/AnomalySystem.cs b/Content.Server/Anomaly/AnomalySystem.cs index fac50fb409b..4b6cdd2beae 100644 --- a/Content.Server/Anomaly/AnomalySystem.cs +++ b/Content.Server/Anomaly/AnomalySystem.cs @@ -18,8 +18,6 @@ using Robust.Shared.Physics.Events; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager; -using System.Linq; namespace Content.Server.Anomaly; @@ -70,20 +68,21 @@ private void OnMapInit(Entity anomaly, ref MapInitEvent args) ChangeAnomalyStability(anomaly, Random.NextFloat(anomaly.Comp.InitialStabilityRange.Item1 , anomaly.Comp.InitialStabilityRange.Item2), anomaly.Comp); ChangeAnomalySeverity(anomaly, Random.NextFloat(anomaly.Comp.InitialSeverityRange.Item1, anomaly.Comp.InitialSeverityRange.Item2), anomaly.Comp); - ShuffleParticlesEffect(anomaly.Comp); + ShuffleParticlesEffect(anomaly); anomaly.Comp.Continuity = _random.NextFloat(anomaly.Comp.MinContituty, anomaly.Comp.MaxContituty); SetBehavior(anomaly, GetRandomBehavior()); } - public void ShuffleParticlesEffect(AnomalyComponent anomaly) + public void ShuffleParticlesEffect(Entity anomaly) { var particles = new List { AnomalousParticleType.Delta, AnomalousParticleType.Epsilon, AnomalousParticleType.Zeta, AnomalousParticleType.Sigma }; - anomaly.SeverityParticleType = Random.PickAndTake(particles); - anomaly.DestabilizingParticleType = Random.PickAndTake(particles); - anomaly.WeakeningParticleType = Random.PickAndTake(particles); - anomaly.TransformationParticleType = Random.PickAndTake(particles); + anomaly.Comp.SeverityParticleType = Random.PickAndTake(particles); + anomaly.Comp.DestabilizingParticleType = Random.PickAndTake(particles); + anomaly.Comp.WeakeningParticleType = Random.PickAndTake(particles); + anomaly.Comp.TransformationParticleType = Random.PickAndTake(particles); + Dirty(anomaly); } private void OnShutdown(Entity anomaly, ref ComponentShutdown args) @@ -199,14 +198,12 @@ private void SetBehavior(Entity anomaly, ProtoId anomaly, ProtoId behaviorProto) @@ -214,7 +211,7 @@ private void RemoveBehavior(Entity anomaly, ProtoId(OnStartCollide); } - private void OnStartCollide(EntityUid uid, ShuffleParticlesAnomalyComponent shuffle, StartCollideEvent args) + private void OnStartCollide(Entity ent, ref StartCollideEvent args) { - if (!TryComp(uid, out var anomaly)) + if (!TryComp(ent, out var anomaly)) return; - if (shuffle.ShuffleOnParticleHit && _random.Prob(shuffle.Prob)) - _anomaly.ShuffleParticlesEffect(anomaly); - - if (!TryComp(args.OtherEntity, out var particle)) + if (!HasComp(args.OtherEntity)) return; + + if (ent.Comp.ShuffleOnParticleHit && _random.Prob(ent.Comp.Prob)) + _anomaly.ShuffleParticlesEffect((ent, anomaly)); } - private void OnPulse(EntityUid uid, ShuffleParticlesAnomalyComponent shuffle, AnomalyPulseEvent args) + private void OnPulse(Entity ent, ref AnomalyPulseEvent args) { - if (!TryComp(uid, out var anomaly)) + if (!TryComp(ent, out var anomaly)) return; - if (shuffle.ShuffleOnPulse && _random.Prob(shuffle.Prob)) + if (ent.Comp.ShuffleOnPulse && _random.Prob(ent.Comp.Prob)) { - _anomaly.ShuffleParticlesEffect(anomaly); + _anomaly.ShuffleParticlesEffect((ent, anomaly)); } } } diff --git a/Content.Shared/Anomaly/Components/AnomalyComponent.cs b/Content.Shared/Anomaly/Components/AnomalyComponent.cs index 3878aeb81cb..88e942ec507 100644 --- a/Content.Shared/Anomaly/Components/AnomalyComponent.cs +++ b/Content.Shared/Anomaly/Components/AnomalyComponent.cs @@ -152,25 +152,25 @@ public sealed partial class AnomalyComponent : Component /// /// The particle type that increases the severity of the anomaly. /// - [DataField] + [DataField, AutoNetworkedField] public AnomalousParticleType SeverityParticleType; /// /// The particle type that destabilizes the anomaly. /// - [DataField] + [DataField, AutoNetworkedField] public AnomalousParticleType DestabilizingParticleType; /// /// The particle type that weakens the anomalys health. /// - [DataField] + [DataField, AutoNetworkedField] public AnomalousParticleType WeakeningParticleType; /// /// The particle type that change anomaly behaviour. /// - [DataField] + [DataField, AutoNetworkedField] public AnomalousParticleType TransformationParticleType; #region Points and Vessels @@ -317,6 +317,7 @@ public sealed partial class AnomalyComponent : Component /// /// Event broadcast when an anomaly's behavior is changed. +/// This is raised after the relevant components are applied /// [ByRefEvent] public readonly record struct AnomalyBehaviorChangedEvent(EntityUid Anomaly, ProtoId? Old, ProtoId? New); From e384630479fe1bb94cab2aa24a2b5390a3cdc5c2 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 28 May 2024 00:52:57 +0000 Subject: [PATCH 115/235] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 7fa7746b62e..4291972e775 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: UnicornOnLSD - changes: - - message: brought back the classic crew cut as an alternative to the current one - ! - type: Add - id: 6130 - time: '2024-03-12T18:47:29.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25935 - author: FungiFellow changes: - message: Added Improvised Shotgun Shell Recipe @@ -3868,3 +3860,10 @@ id: 6629 time: '2024-05-28T00:04:32.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28330 +- author: slarticodefast + changes: + - message: Corrected some cargo bounties. + type: Fix + id: 6630 + time: '2024-05-28T00:51:50.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28255 From ead8267de221be08b868aa309a099b11fd244ce0 Mon Sep 17 00:00:00 2001 From: Repo <47093363+Titian3@users.noreply.github.com> Date: Wed, 29 May 2024 02:02:15 +1200 Subject: [PATCH 116/235] Fix wall vending machines spawning items in walls. (#28279) * Find spawning of wall vending machines. * Review fixes --- .../VendingMachines/VendingMachineSystem.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index 723b9de6267..63ec8f2c24b 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Throwing; using Content.Shared.UserInterface; using Content.Shared.VendingMachines; +using Content.Shared.Wall; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -39,6 +40,8 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!; + private const float WallVendEjectDistanceFromWall = 1f; + public override void Initialize() { base.Initialize(); @@ -384,7 +387,20 @@ private void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = n return; } - var ent = Spawn(vendComponent.NextItemToEject, Transform(uid).Coordinates); + // Default spawn coordinates + var spawnCoordinates = Transform(uid).Coordinates; + + //Make sure the wallvends spawn outside of the wall. + + if (TryComp(uid, out var wallMountComponent)) + { + + var offset = wallMountComponent.Direction.ToWorldVec() * WallVendEjectDistanceFromWall; + spawnCoordinates = spawnCoordinates.Offset(offset); + } + + var ent = Spawn(vendComponent.NextItemToEject, spawnCoordinates); + if (vendComponent.ThrowNextItem) { var range = vendComponent.NonLimitedEjectRange; From c2e399881c8ffab9adc635bd5b385b4202338bec Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 28 May 2024 14:03:22 +0000 Subject: [PATCH 117/235] 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 4291972e775..1dcd2315575 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: FungiFellow - changes: - - message: Added Improvised Shotgun Shell Recipe - type: Add - id: 6131 - time: '2024-03-12T23:52:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25545 - author: NakataRin changes: - message: Skeletons can only spawn with 10 people on the server now. @@ -3867,3 +3860,10 @@ id: 6630 time: '2024-05-28T00:51:50.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28255 +- author: Repo + changes: + - message: NanoMed, wall vends will spawn items the correct direction. + type: Fix + id: 6631 + time: '2024-05-28T14:02:15.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28279 From df162070ac14f82b39b2b39e4bb5dcd85b7bccdb Mon Sep 17 00:00:00 2001 From: MilenVolf <63782763+MilenVolf@users.noreply.github.com> Date: Tue, 28 May 2024 17:23:05 +0300 Subject: [PATCH 118/235] Localize emergency shuttle's direction for announcements (#28340) Localize emergency shuttle direction --- Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index e2b1ad32cd7..45397ede088 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -22,6 +22,7 @@ using Content.Shared.Database; using Content.Shared.DeviceNetwork; using Content.Shared.GameTicking; +using Content.Shared.Localizations; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Events; using Content.Shared.Tag; @@ -287,8 +288,9 @@ public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleCo if (TryComp(targetGrid.Value, out TransformComponent? targetXform)) { var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); + var direction = ContentLocalizationManager.FormatDirection(angle.GetDir()); var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform))); - _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir()), ("location", location)), playDefaultSound: false); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playDefaultSound: false); } // shuttle timers @@ -317,8 +319,9 @@ public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleCo if (TryComp(targetGrid.Value, out var targetXform)) { var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); + var direction = ContentLocalizationManager.FormatDirection(angle.GetDir()); var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform))); - _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir()), ("location", location)), playDefaultSound: false); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playDefaultSound: false); } _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}"); From 59c399c6d06bd807a355401dfc260fdad75e6cdc Mon Sep 17 00:00:00 2001 From: S1rFl0 <49481675+S1rFl0@users.noreply.github.com> Date: Tue, 28 May 2024 16:43:12 +0200 Subject: [PATCH 119/235] Fix some grammar and spelling mistakes (#28350) Fix some wacky grammar and spelling mistakes --- .../Locale/en-US/interaction/interaction-popup-component.ftl | 2 +- .../Prototypes/Entities/Clothing/OuterClothing/softsuits.yml | 2 +- .../Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl index 4929b11b1cd..7db99c3d0ae 100644 --- a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl +++ b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl @@ -51,7 +51,7 @@ petting-failure-dragon = You raise your hand, but as {THE($target)} roars, you d petting-failure-hamster = You reach out to pet {THE($target)}, but {SUBJECT($target)} attempts to bite your finger and only your quick reflexes save you from an almost fatal injury. petting-failure-bear = You reach out to pet {THE($target)}, but {SUBJECT($target)} growls, making you think twice. petting-failure-monkey = You reach out to pet {THE($target)}, but {SUBJECT($target)} almost bites your fingers! -petting-failure-nymph = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} moves their branches away. +petting-failure-nymph = You reach out to pet {THE($target)}, but {SUBJECT($target)} move their branches away. petting-failure-shadow = You're trying to pet {THE($target)}, but your hand passes through the cold darkness of his body. ## Petting silicons diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml index 58faf268470..9db1ea2216c 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml @@ -37,7 +37,7 @@ parent: ClothingOuterEVASuitBase id: ClothingOuterSuitEmergency name: emergency EVA suit - description: An emergency EVA suit with a built-in helmet. It's horribly slow and lacking in temperature protection, but enough to bide you time from the harsh vaccuum of space. + description: An emergency EVA suit with a built-in helmet. It's horribly slow and lacking in temperature protection, but enough to buy you time from the harsh vaccuum of space. components: - type: Sprite sprite: Clothing/OuterClothing/Suits/eva_emergency.rsi diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml index d193eca0295..19b619270ff 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml @@ -201,7 +201,7 @@ deconstructionTarget: null - type: entity - name: sawn-off shogun + name: sawn-off shotgun parent: WeaponShotgunSawn id: WeaponShotgunSawnEmpty description: Groovy! Uses .50 shotgun shells. From adee8b16ab1ba9f4f656919f24ae76236f3a830b Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Tue, 28 May 2024 07:59:13 -0700 Subject: [PATCH 120/235] Add pressure and temperature warning text to firelocks (#28341) --- Content.Server/Doors/Systems/FirelockSystem.cs | 2 +- .../Doors/Systems/SharedFirelockSystem.cs | 14 ++++++++++++++ .../Locale/en-US/atmos/firelock-component.ftl | 4 +++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index e2b8b5829d1..c7da404fe88 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -69,7 +69,7 @@ public override void Update(float frameTime) && xformQuery.TryGetComponent(uid, out var xform) && appearanceQuery.TryGetComponent(uid, out var appearance)) { - var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery); + var (pressure, fire) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery); _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance); firelock.Temperature = fire; firelock.Pressure = pressure; diff --git a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs index 47a29a4ba80..4afe26039ba 100644 --- a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs +++ b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Access.Systems; using Content.Shared.Doors.Components; +using Content.Shared.Examine; using Content.Shared.Popups; using Content.Shared.Prying.Components; using Robust.Shared.Timing; @@ -26,6 +27,8 @@ public override void Initialize() // Visuals SubscribeLocalEvent(UpdateVisuals); SubscribeLocalEvent(UpdateVisuals); + + SubscribeLocalEvent(OnExamined); } public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null) @@ -107,4 +110,15 @@ private void UpdateVisuals(EntityUid uid, } #endregion + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + using (args.PushGroup(nameof(FirelockComponent))) + { + if (ent.Comp.Pressure) + args.PushMarkup(Loc.GetString("firelock-component-examine-pressure-warning")); + if (ent.Comp.Temperature) + args.PushMarkup(Loc.GetString("firelock-component-examine-temperature-warning")); + } + } } diff --git a/Resources/Locale/en-US/atmos/firelock-component.ftl b/Resources/Locale/en-US/atmos/firelock-component.ftl index fc375183e97..81f7e58462a 100644 --- a/Resources/Locale/en-US/atmos/firelock-component.ftl +++ b/Resources/Locale/en-US/atmos/firelock-component.ftl @@ -1,2 +1,4 @@ firelock-component-is-holding-pressure-message = A gush of air blows in your face... Maybe you should reconsider. -firelock-component-is-holding-fire-message = A gush of warm air blows in your face... Maybe you should reconsider. \ No newline at end of file +firelock-component-is-holding-fire-message = A gush of warm air blows in your face... Maybe you should reconsider. +firelock-component-examine-pressure-warning = The [color=red]extreme pressure[/color] differential warning is active. +firelock-component-examine-temperature-warning = The [color=red]extreme temperature[/color] warning is active. From 4bd0e8ddea0e3cebaa73a0b8082fd1eba421ee71 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 28 May 2024 15:00:19 +0000 Subject: [PATCH 121/235] 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 1dcd2315575..60412578e47 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: NakataRin - changes: - - message: Skeletons can only spawn with 10 people on the server now. - type: Tweak - id: 6132 - time: '2024-03-13T01:00:15.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26050 - author: SlamBamActionman changes: - message: Syndicate implanters can no longer be recycled. @@ -3867,3 +3860,10 @@ id: 6631 time: '2024-05-28T14:02:15.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28279 +- author: ShadowCommander + changes: + - message: Added pressure and temperature warnings to firelock examine text. + type: Add + id: 6632 + time: '2024-05-28T14:59:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28341 From b07a73cf8a5f3c8ebc54e10203b4264f25a5c792 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 28 May 2024 17:01:19 +0200 Subject: [PATCH 122/235] Fix paper scrap layers + cleanup paper.yml (#28299) Fix paper scrap + cleanup paper.yml --- .../Entities/Objects/Misc/paper.yml | 46 ++++--------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index 05a0b9d3455..1c8d8754884 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -79,9 +79,14 @@ description: 'A crumpled up piece of white paper.' components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: scrap + - state: paper_words + map: ["enum.PaperVisualLayers.Writing"] + visible: false + - state: paper_stamp-generic + map: ["enum.PaperVisualLayers.Stamp"] + visible: false - type: entity name: office paper @@ -102,7 +107,6 @@ description: 'The readout of a device forgotten to time' components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: paper_dotmatrix - state: paper_dotmatrix_words @@ -133,7 +137,6 @@ description: "A page of the captain's journal. In luxurious lavender." components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: paper color: "#e6e6fa" @@ -160,7 +163,6 @@ description: 'A single unit of bureaucracy.' components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: paper color: "#9ef5ff" @@ -190,7 +192,6 @@ description: A paper label designating a crate as containing a bounty. Selling a crate with this label will fulfill the bounty. components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: paper color: "#f7e574" @@ -230,7 +231,6 @@ escapeFormatting: false content: book-cnc-sheet - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: paper color: "#cccccc" @@ -254,18 +254,11 @@ id: PaperWritten noSpawn: true components: - - type: Paper - type: Sprite layers: # Changing it here is fine - if the PaperStatus key is actually added, # something happened, so that ought to override this either way. - state: paper_words - - type: ActivatableUI - key: enum.PaperUiKey.Key - - type: UserInterface - interfaces: - enum.PaperUiKey.Key: - type: PaperBoundUserInterface - type: entity parent: Paper @@ -274,7 +267,6 @@ components: - type: NukeCodePaper allNukesAvailable: true - - type: Paper - type: entity parent: NukeCodePaper @@ -346,7 +338,6 @@ suffix: Red components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: folder-colormap color: "#cc2323" @@ -358,7 +349,6 @@ suffix: Blue components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: folder-colormap color: "#355d99" @@ -370,7 +360,6 @@ suffix: Yellow components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: folder-colormap color: "#b38e3c" @@ -382,7 +371,6 @@ suffix: White components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: folder-white - state: folder-base @@ -393,7 +381,6 @@ suffix: Grey components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: folder-colormap color: "#999999" @@ -405,7 +392,6 @@ suffix: Black components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: folder-colormap color: "#3f3f3f" @@ -417,7 +403,6 @@ suffix: Green components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: folder-colormap color: "#43bc38" @@ -431,7 +416,6 @@ description: CentCom's miserable little pile of secrets! components: - type: Sprite - sprite: Objects/Misc/bureaucracy.rsi layers: - state: folder-centcom - state: folder-base @@ -514,15 +498,12 @@ - state: clipboard_over - type: Item sprite: Objects/Misc/cc-clipboard.rsi - size: Small - type: Clothing - slots: [belt] - quickEquip: false sprite: Objects/Misc/cc-clipboard.rsi - type: entity id: BoxFolderQmClipboard - parent: BoxFolderBase + parent: BoxFolderClipboard name: requisition digi-board description: A bulky electric clipboard, filled with shipping orders and financing details. With so many compromising documents, you ought to keep this safe. components: @@ -537,11 +518,6 @@ map: ["qm_clipboard_pen"] visible: false - state: qm_clipboard_over - - type: ContainerContainer - containers: - storagebase: !type:Container - ents: [] - pen_slot: !type:ContainerSlot {} - type: ItemSlots slots: pen_slot: @@ -554,18 +530,13 @@ sprite: Objects/Misc/qm_clipboard.rsi size: Normal - type: Clothing - slots: [belt] - quickEquip: false sprite: Objects/Misc/qm_clipboard.rsi - type: Storage grid: - 0,0,4,3 quickInsert: true - whitelist: - tags: - - Document - type: StorageFill - contents: [] #to override base folder fill + contents: [] #to override base clipboard fill - type: ItemMapper mapLayers: qm_clipboard_paper: @@ -587,7 +558,6 @@ enum.StorageUiKey.Key: type: StorageBoundUserInterface - type: MeleeWeapon - wideAnimationRotation: 180 damage: types: Blunt: 10 From a14aed3fca0335c519adfdae21ea61fe8f32e0bc Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Tue, 28 May 2024 15:35:08 +0000 Subject: [PATCH 123/235] missing nukies can be filled in by ghost roles (#28316) --- Content.Server/Antag/AntagSelectionSystem.cs | 12 +++++++-- .../Components/AntagSelectionComponent.cs | 1 + .../Entities/Markers/Spawners/ghost_roles.yml | 27 +++++++++++++++++++ Resources/Prototypes/GameRules/roundstart.yml | 8 +++--- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index a86611bedb3..b42831cbde8 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -205,16 +205,24 @@ public void ChooseAntags(Entity ent, IList /// Whether or not players should be picked to inhabit this antag or not. + /// If no players are left and is set, it will make a ghost role. /// [DataField] public bool PickPlayer = true; diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index 47303d6586f..4c2016ec607 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -108,6 +108,33 @@ - sprite: Structures/Wallmounts/signs.rsi state: radiation +- type: entity + noSpawn: true + parent: SpawnPointLoneNukeOperative + id: SpawnPointNukeopsCommander + components: + - type: GhostRole + name: roles-antag-nuclear-operative-commander-name + description: roles-antag-nuclear-operative-commander-objective + +- type: entity + noSpawn: true + parent: SpawnPointLoneNukeOperative + id: SpawnPointNukeopsMedic + components: + - type: GhostRole + name: roles-antag-nuclear-operative-agent-name + description: roles-antag-nuclear-operative-agent-objective + +- type: entity + noSpawn: true + parent: SpawnPointLoneNukeOperative + id: SpawnPointNukeopsOperative + components: + - type: GhostRole + name: roles-antag-nuclear-operative-name + description: roles-antag-nuclear-operative-objective + - type: entity parent: MarkerBase id: SpawnPointGhostDragon diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index a198ee7bd07..d67038e6ad0 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -90,8 +90,7 @@ definitions: - prefRoles: [ NukeopsCommander ] fallbackRoles: [ Nukeops, NukeopsMedic ] - max: 1 - playerRatio: 10 + spawnerPrototype: SpawnPointNukeopsCommander startingGear: SyndicateCommanderGearFull components: - type: NukeOperative @@ -107,8 +106,7 @@ prototype: NukeopsCommander - prefRoles: [ NukeopsMedic ] fallbackRoles: [ Nukeops, NukeopsCommander ] - max: 1 - playerRatio: 10 + spawnerPrototype: SpawnPointNukeopsMedic startingGear: SyndicateOperativeMedicFull components: - type: NukeOperative @@ -124,7 +122,7 @@ prototype: NukeopsMedic - prefRoles: [ Nukeops ] fallbackRoles: [ NukeopsCommander, NukeopsMedic ] - min: 0 + spawnerPrototype: SpawnPointNukeopsOperative max: 3 playerRatio: 10 startingGear: SyndicateOperativeGearFull From 27f0eb819624875bfc04045f69166458733cf411 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 28 May 2024 15:36:14 +0000 Subject: [PATCH 124/235] 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 60412578e47..91fd512c01c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: SlamBamActionman - changes: - - message: Syndicate implanters can no longer be recycled. - type: Tweak - id: 6133 - time: '2024-03-13T02:02:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26047 - author: Gyrandola changes: - message: Adding Sky Blue carpets to tables no longer results in red carpet ones. @@ -3867,3 +3860,10 @@ id: 6632 time: '2024-05-28T14:59:13.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28341 +- author: deltanedas + changes: + - message: Nukies will have ghost roles opened if not enough people can be picked. + type: Tweak + id: 6633 + time: '2024-05-28T15:35:08.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28316 From ea2668ff372cbabdd7dc026882e1976a077492fc Mon Sep 17 00:00:00 2001 From: Repo <47093363+Titian3@users.noreply.github.com> Date: Wed, 29 May 2024 06:00:42 +1200 Subject: [PATCH 125/235] Fix late join & observe to de-admin admins. (#28319) --- Content.Client/Lobby/UI/ObserveWarningWindow.xaml | 5 +++-- .../Lobby/UI/ObserveWarningWindow.xaml.cs | 13 +++++++++++++ .../GameTicking/Commands/JoinGameCommand.cs | 11 +++++++++++ .../GameTicking/Commands/ObserveCommand.cs | 9 +++++++++ .../en-US/lobby/ui/observe-warning-window.ftl | 2 ++ 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Content.Client/Lobby/UI/ObserveWarningWindow.xaml b/Content.Client/Lobby/UI/ObserveWarningWindow.xaml index 3fe8e83f57d..2feac5792a1 100644 --- a/Content.Client/Lobby/UI/ObserveWarningWindow.xaml +++ b/Content.Client/Lobby/UI/ObserveWarningWindow.xaml @@ -4,10 +4,11 @@ Pii = 1 << 18, + /// + /// Lets you take moderator actions on the game server. + /// + Moderator = 1 << 19, + + /// + /// Lets you check currently online admins. + /// + AdminWho = 1 << 20, + + /// + /// Lets you set the color of your OOC name. + /// + NameColor = 1 << 21, + /// /// DeltaV - The ability to whitelist people. Either this permission or +BAN is required for remove. /// - Whitelist = 1 << 20, + Whitelist = 1 << 25, /// /// Dangerous host permissions like scsi. diff --git a/Resources/engineCommandPerms.yml b/Resources/engineCommandPerms.yml index 42cc4668a93..b68a2b22dfb 100644 --- a/Resources/engineCommandPerms.yml +++ b/Resources/engineCommandPerms.yml @@ -90,12 +90,12 @@ - Flags: ADMIN Commands: - - delete - - kick - listplayers - tp - tpto - - respawn + +- Flags: FUN + Commands: - tippy - tip @@ -111,6 +111,12 @@ Commands: - spawn - cspawn + - delete + +- Flags: MODERATOR + Commands: + - kick + - respawn - Flags: HOST Commands: From 8c4a0237cf7b3c9cc2d8be27a4c624c532f6ce7e Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 08:15:50 +0000 Subject: [PATCH 219/235] Automatic changelog update --- Resources/Changelog/Admin.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index f730b93db62..b03b0b54fbe 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -272,5 +272,14 @@ Entries: id: 33 time: '2024-06-01T07:23:54.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27776 +- author: ShadowCommander, EmoGarbage + changes: + - message: 'Added new permissions: moderator, adminwho, and namecolor' + type: Add + - message: Changed various command perms from admin to moderator & fun. + type: Tweak + id: 34 + time: '2024-06-01T08:14:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28451 Name: Admin Order: 3 From 185a61175fc1a12847c09a0a35e67e945e540c82 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:35:14 -0700 Subject: [PATCH 220/235] Fix the client thinking it cannot shoot after mispredicting when it actually can (#28464) --- .../Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 6242312b070..784dd0793a8 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -186,6 +186,7 @@ private void ManualCycle(EntityUid uid, BallisticAmmoProviderComponent component !Paused(uid)) { gunComp.NextFire = Timing.CurTime + TimeSpan.FromSeconds(1 / gunComp.FireRateModified); + Dirty(uid, gunComp); } Dirty(uid, component); From eed9c5838621ae751d9ea9bd1757140a57697dd0 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+drsmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:08:31 +0200 Subject: [PATCH 221/235] Add job whitelist system (#28085) * Add job whitelist system * Address reviews * Fix name * Apply suggestions from code review Co-authored-by: Pieter-Jan Briers * cancinium --------- Co-authored-by: Pieter-Jan Briers --- Content.Client/Lobby/LobbyUIController.cs | 17 +- .../JobRequirementsManager.cs | 28 + Content.IntegrationTests/PoolManager.Cvars.cs | 1 + .../20240531011555_RoleWhitelist.Designer.cs | 1913 +++++++++++++++++ .../Postgres/20240531011555_RoleWhitelist.cs | 40 + .../PostgresServerDbContextModelSnapshot.cs | 31 + .../20240531011549_RoleWhitelist.Designer.cs | 1838 ++++++++++++++++ .../Sqlite/20240531011549_RoleWhitelist.cs | 40 + .../SqliteServerDbContextModelSnapshot.cs | 31 + Content.Server.Database/Model.cs | 20 + .../Commands/JobWhitelistCommands.cs | 214 ++ .../Administration/Managers/BanManager.cs | 8 +- .../Administration/Managers/IBanManager.cs | 4 +- Content.Server/Database/ServerDbBase.cs | 61 + Content.Server/Database/ServerDbManager.cs | 38 + Content.Server/Database/UserDbDataManager.cs | 50 +- Content.Server/Entry/EntryPoint.cs | 2 + .../Events/GetDisallowedJobsEvent.cs | 8 + .../GameTicking/Events/IsJobAllowedEvent.cs | 13 + .../GameTicking/GameTicker.Spawning.cs | 19 +- Content.Server/GameTicking/GameTicker.cs | 1 - Content.Server/IoC/ServerContentIoC.cs | 2 + .../JobWhitelist/JobWhitelistManager.cs | 114 + .../JobWhitelist/JobWhitelistSystem.cs | 83 + .../PlayTimeTrackingManager.cs | 9 +- .../PlayTimeTrackingSystem.cs | 30 +- .../Managers/ServerPreferencesManager.cs | 15 +- .../Events/StationJobsGetCandidatesEvent.cs | 8 + .../Systems/StationJobsSystem.Roundstart.cs | 8 +- .../Station/Systems/StationJobsSystem.cs | 2 +- Content.Shared/CCVar/CCVars.cs | 6 + .../Players/JobWhitelist/MsgJobWhitelist.cs | 33 + Content.Shared/Roles/JobPrototype.cs | 5 +- .../en-US/commands/job-whitelist-command.ftl | 20 + Resources/Locale/en-US/job/role-whitelist.ftl | 1 + 35 files changed, 4666 insertions(+), 47 deletions(-) create mode 100644 Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs create mode 100644 Content.Server/Administration/Commands/JobWhitelistCommands.cs create mode 100644 Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs create mode 100644 Content.Server/GameTicking/Events/IsJobAllowedEvent.cs create mode 100644 Content.Server/Players/JobWhitelist/JobWhitelistManager.cs create mode 100644 Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs create mode 100644 Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs create mode 100644 Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs create mode 100644 Resources/Locale/en-US/commands/job-whitelist-command.ftl create mode 100644 Resources/Locale/en-US/job/role-whitelist.ftl diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index ae9196c1100..f6a3eed962c 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -22,7 +22,6 @@ using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; using Robust.Shared.Utility; namespace Content.Client.Lobby; @@ -70,12 +69,9 @@ public override void Initialize() _profileEditor?.RefreshFlavorText(); }); - _configurationManager.OnValueChanged(CCVars.GameRoleTimers, args => - { - _profileEditor?.RefreshAntags(); - _profileEditor?.RefreshJobs(); - _profileEditor?.RefreshLoadouts(); - }); + _configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor()); + + _configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor()); } private LobbyCharacterPreviewPanel? GetLobbyPreview() @@ -193,6 +189,13 @@ private void RefreshLobbyPreview() PreviewPanel.SetSummaryText(humanoid.Summary); } + private void RefreshProfileEditor() + { + _profileEditor?.RefreshAntags(); + _profileEditor?.RefreshJobs(); + _profileEditor?.RefreshLoadouts(); + } + private void SaveProfile() { DebugTools.Assert(EditedProfile != null); diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index 58b48aa7d12..80683fae711 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.CCVar; using Content.Shared.Players; +using Content.Shared.Players.JobWhitelist; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Roles; using Robust.Client; @@ -24,6 +25,7 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager private readonly Dictionary _roles = new(); private readonly List _roleBans = new(); + private readonly List _jobWhitelists = new(); private ISawmill _sawmill = default!; @@ -36,6 +38,7 @@ public void Initialize() // Yeah the client manager handles role bans and playtime but the server ones are separate DEAL. _net.RegisterNetMessage(RxRoleBans); _net.RegisterNetMessage(RxPlayTime); + _net.RegisterNetMessage(RxJobWhitelist); _net.RegisterNetMessage(RxWhitelist); _client.RunLevelChanged += ClientOnRunLevelChanged; @@ -80,6 +83,13 @@ private void RxPlayTime(MsgPlayTime message) Updated?.Invoke(); } + private void RxJobWhitelist(MsgJobWhitelist message) + { + _jobWhitelists.Clear(); + _jobWhitelists.AddRange(message.Whitelist); + Updated?.Invoke(); + } + public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) { reason = null; @@ -90,6 +100,9 @@ public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessag return false; } + if (!CheckWhitelist(job, out reason)) + return false; + var player = _playerManager.LocalSession; if (player == null) return true; @@ -117,6 +130,21 @@ public bool CheckRoleTime(HashSet? requirements, [NotNullWhen(fa return reason == null; } + public bool CheckWhitelist(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) + { + reason = default; + if (!_cfg.GetCVar(CCVars.GameRoleWhitelist)) + return true; + + if (job.Whitelisted && !_jobWhitelists.Contains(job.ID)) + { + reason = FormattedMessage.FromUnformatted(Loc.GetString("role-not-whitelisted")); + return false; + } + + return true; + } + public TimeSpan FetchOverallPlaytime() { return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero; diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs index aa6b4dffdcc..5acd9d502c1 100644 --- a/Content.IntegrationTests/PoolManager.Cvars.cs +++ b/Content.IntegrationTests/PoolManager.Cvars.cs @@ -21,6 +21,7 @@ private static readonly (string cvar, string value)[] TestCvars = (CCVars.NPCMaxUpdates.Name, "999999"), (CVars.ThreadParallelCount.Name, "1"), (CCVars.GameRoleTimers.Name, "false"), + (CCVars.GameRoleWhitelist.Name, "false"), (CCVars.GridFill.Name, "false"), (CCVars.PreloadGrids.Name, "false"), (CCVars.ArrivalsShuttles.Name, "false"), diff --git a/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs new file mode 100644 index 00000000000..5032281dfec --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs @@ -0,0 +1,1913 @@ +// +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("20240531011555_RoleWhitelist")] + partial class RoleWhitelist + { + /// + 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.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("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + 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("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("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + 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("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + 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("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + 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.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.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.Navigation("CreatedBy"); + + 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.Navigation("CreatedBy"); + + 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/20240531011555_RoleWhitelist.cs b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs new file mode 100644 index 00000000000..1cd0d8df17d --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs @@ -0,0 +1,40 @@ +#nullable disable + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class RoleWhitelist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "role_whitelists", + columns: table => new + { + player_user_id = table.Column(type: "uuid", nullable: false), + role_id = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id }); + table.ForeignKey( + name: "FK_role_whitelists_player_player_user_id", + column: x => x.player_user_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "role_whitelists"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index bbbd64d874a..32a655f7f65 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -900,6 +900,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -1623,6 +1639,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -1822,6 +1851,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("AdminWatchlistsLastEdited"); b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); }); modelBuilder.Entity("Content.Server.Database.Preference", b => diff --git a/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs new file mode 100644 index 00000000000..531f013604f --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs @@ -0,0 +1,1838 @@ +// +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("20240531011549_RoleWhitelist")] + partial class RoleWhitelist + { + /// + 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.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("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + 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("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("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + 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("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + 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("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + 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.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.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.Navigation("CreatedBy"); + + 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.Navigation("CreatedBy"); + + 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/20240531011549_RoleWhitelist.cs b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs new file mode 100644 index 00000000000..9d192fc6853 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs @@ -0,0 +1,40 @@ +#nullable disable + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class RoleWhitelist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "role_whitelists", + columns: table => new + { + player_user_id = table.Column(type: "TEXT", nullable: false), + role_id = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id }); + table.ForeignKey( + name: "FK_role_whitelists_player_player_user_id", + column: x => x.player_user_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "role_whitelists"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index d343f49f416..2c444c83eb3 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -847,6 +847,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -1548,6 +1564,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -1747,6 +1776,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("AdminWatchlistsLastEdited"); b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); }); modelBuilder.Entity("Content.Server.Database.Preference", b => diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 57dcc3fc6dc..60935e8f06a 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -40,6 +40,7 @@ protected ServerDbContext(DbContextOptions options) : base(options) public DbSet AdminNotes { get; set; } = null!; public DbSet AdminWatchlists { get; set; } = null!; public DbSet AdminMessages { get; set; } = null!; + public DbSet RoleWhitelists { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -314,6 +315,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(ban => ban.LastEditedById) .HasPrincipalKey(author => author.UserId) .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity() + .HasOne(w => w.Player) + .WithMany(p => p.JobWhitelists) + .HasForeignKey(w => w.PlayerUserId) + .HasPrincipalKey(p => p.UserId) + .OnDelete(DeleteBehavior.Cascade); } public virtual IQueryable SearchLogs(IQueryable query, string searchText) @@ -530,6 +538,7 @@ public class Player public List AdminServerBansLastEdited { get; set; } = null!; public List AdminServerRoleBansCreated { get; set; } = null!; public List AdminServerRoleBansLastEdited { get; set; } = null!; + public List JobWhitelists { get; set; } = null!; } [Table("whitelist")] @@ -1099,4 +1108,15 @@ public class AdminMessage : IAdminRemarksCommon /// public bool Dismissed { get; set; } } + + [PrimaryKey(nameof(PlayerUserId), nameof(RoleId))] + public class RoleWhitelist + { + [Required, ForeignKey("Player")] + public Guid PlayerUserId { get; set; } + public Player Player { get; set; } = default!; + + [Required] + public string RoleId { get; set; } = default!; + } } diff --git a/Content.Server/Administration/Commands/JobWhitelistCommands.cs b/Content.Server/Administration/Commands/JobWhitelistCommands.cs new file mode 100644 index 00000000000..a84a11ef848 --- /dev/null +++ b/Content.Server/Administration/Commands/JobWhitelistCommands.cs @@ -0,0 +1,214 @@ +using System.Linq; +using Content.Server.Database; +using Content.Server.Players.JobWhitelist; +using Content.Shared.Administration; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Ban)] +public sealed class JobWhitelistAddCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + public override string Command => "jobwhitelistadd"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", + ("properAmount", 2), + ("currentAmount", args.Length))); + shell.WriteLine(Help); + return; + } + + var player = args[0].Trim(); + var job = new ProtoId(args[1].Trim()); + if (!_prototypes.TryIndex(job, out var jobPrototype)) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job.Id))); + shell.WriteLine(Help); + return; + } + + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var isWhitelisted = await _db.IsJobWhitelisted(guid, job); + if (isWhitelisted) + { + shell.WriteLine(Loc.GetString("cmd-jobwhitelist-already-whitelisted", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + _jobWhitelist.AddWhitelist(guid, job); + shell.WriteLine(Loc.GetString("cmd-jobwhitelistadd-added", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + if (args.Length == 2) + { + return CompletionResult.FromHintOptions( + _prototypes.EnumeratePrototypes().Select(p => p.ID), + Loc.GetString("cmd-jobwhitelist-hint-job")); + } + + return CompletionResult.Empty; + } +} + +[AdminCommand(AdminFlags.Ban)] +public sealed class GetJobWhitelistCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + + public override string Command => "jobwhitelistget"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length == 0) + { + shell.WriteError("This command needs at least one argument."); + shell.WriteLine(Help); + return; + } + + var player = string.Join(' ', args).Trim(); + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var whitelists = await _db.GetJobWhitelists(guid); + if (whitelists.Count == 0) + { + shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-none", ("player", player))); + return; + } + + shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-for", + ("player", player), + ("jobs", string.Join(", ", whitelists)))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + return CompletionResult.Empty; + } +} + +[AdminCommand(AdminFlags.Ban)] +public sealed class RemoveJobWhitelistCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + public override string Command => "jobwhitelistremove"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", + ("properAmount", 2), + ("currentAmount", args.Length))); + shell.WriteLine(Help); + return; + } + + var player = args[0].Trim(); + var job = new ProtoId(args[1].Trim()); + if (!_prototypes.TryIndex(job, out var jobPrototype)) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job))); + shell.WriteLine(Help); + return; + } + + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var isWhitelisted = await _db.IsJobWhitelisted(guid, job); + if (!isWhitelisted) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelistremove-was-not-whitelisted", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + _jobWhitelist.RemoveWhitelist(guid, job); + shell.WriteLine(Loc.GetString("cmd-jobwhitelistremove-removed", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + if (args.Length == 2) + { + return CompletionResult.FromHintOptions( + _prototypes.EnumeratePrototypes().Select(p => p.ID), + Loc.GetString("cmd-jobwhitelist-hint-job")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 3a05b934b24..68bd8170265 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -73,7 +73,9 @@ private async Task AddRoleBan(ServerRoleBanDef banDef) public HashSet? GetRoleBans(NetUserId playerUserId) { - return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null; + return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) + ? roleBans.Select(banDef => banDef.Role).ToHashSet() + : null; } private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray? hwId = null) @@ -263,13 +265,13 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da return $"Pardoned ban with id {banId}"; } - public HashSet? GetJobBans(NetUserId playerUserId) + public HashSet>? GetJobBans(NetUserId playerUserId) { if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans)) return null; return roleBans .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal)) - .Select(ban => ban.Role[JobPrefix.Length..]) + .Select(ban => new ProtoId(ban.Role[JobPrefix.Length..])) .ToHashSet(); } #endregion diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs index dafe3d35bdd..b60e0a25351 100644 --- a/Content.Server/Administration/Managers/IBanManager.cs +++ b/Content.Server/Administration/Managers/IBanManager.cs @@ -2,8 +2,10 @@ using System.Net; using System.Threading.Tasks; using Content.Shared.Database; +using Content.Shared.Roles; using Robust.Shared.Network; using Robust.Shared.Player; +using Robust.Shared.Prototypes; namespace Content.Server.Administration.Managers; @@ -24,7 +26,7 @@ public interface IBanManager /// 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 HashSet? GetRoleBans(NetUserId playerUserId); - public HashSet? GetJobBans(NetUserId playerUserId); + public HashSet>? GetJobBans(NetUserId playerUserId); /// /// Creates a job ban for the specified target, username or GUID diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index 8743a4b5db1..be6c7196d5f 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -14,9 +14,11 @@ using Content.Shared.Humanoid.Markings; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; +using Content.Shared.Roles; using Microsoft.EntityFrameworkCore; using Robust.Shared.Enums; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Database @@ -1579,6 +1581,65 @@ protected async Task> GetGroupedServerRoleBansAsNo #endregion + #region Job Whitelists + + public async Task AddJobWhitelist(Guid player, ProtoId job) + { + await using var db = await GetDb(); + var exists = await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .AnyAsync(); + + if (exists) + return false; + + var whitelist = new RoleWhitelist + { + PlayerUserId = player, + RoleId = job + }; + db.DbContext.RoleWhitelists.Add(whitelist); + await db.DbContext.SaveChangesAsync(); + return true; + } + + public async Task> GetJobWhitelists(Guid player, CancellationToken cancel) + { + await using var db = await GetDb(cancel); + return await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Select(w => w.RoleId) + .ToListAsync(cancellationToken: cancel); + } + + public async Task IsJobWhitelisted(Guid player, ProtoId job) + { + await using var db = await GetDb(); + return await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .AnyAsync(); + } + + public async Task RemoveJobWhitelist(Guid player, ProtoId job) + { + await using var db = await GetDb(); + var entry = await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .SingleOrDefaultAsync(); + + if (entry == null) + return false; + + db.DbContext.RoleWhitelists.Remove(entry); + await db.DbContext.SaveChangesAsync(); + return true; + } + + #endregion + // SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc. // Normalize DateTimes here so they're always Utc. Thanks. protected abstract DateTime NormalizeDatabaseTime(DateTime time); diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 01d15267274..1983fe43d20 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -9,6 +9,7 @@ using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Preferences; +using Content.Shared.Roles; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -17,6 +18,7 @@ using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using LogLevel = Robust.Shared.Log.LogLevel; using MSLogLevel = Microsoft.Extensions.Logging.LogLevel; @@ -290,6 +292,18 @@ Task AddConnectionLogAsync( Task MarkMessageAsSeen(int id, bool dismissedToo); #endregion + + #region Job Whitelists + + Task AddJobWhitelist(Guid player, ProtoId job); + + + Task> GetJobWhitelists(Guid player, CancellationToken cancel = default); + Task IsJobWhitelisted(Guid player, ProtoId job); + + Task RemoveJobWhitelist(Guid player, ProtoId job); + + #endregion } public sealed class ServerDbManager : IServerDbManager @@ -869,6 +883,30 @@ public Task MarkMessageAsSeen(int id, bool dismissedToo) return RunDbCommand(() => _db.MarkMessageAsSeen(id, dismissedToo)); } + public Task AddJobWhitelist(Guid player, ProtoId job) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.AddJobWhitelist(player, job)); + } + + public Task> GetJobWhitelists(Guid player, CancellationToken cancel = default) + { + DbReadOpsMetric.Inc(); + return RunDbCommand(() => _db.GetJobWhitelists(player, cancel)); + } + + public Task IsJobWhitelisted(Guid player, ProtoId job) + { + DbReadOpsMetric.Inc(); + return RunDbCommand(() => _db.IsJobWhitelisted(player, job)); + } + + public Task RemoveJobWhitelist(Guid player, ProtoId job) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.RemoveJobWhitelist(player, job)); + } + // Wrapper functions to run DB commands from the thread pool. // This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread. // For SQLite, this will also enable read parallelization (within limits). diff --git a/Content.Server/Database/UserDbDataManager.cs b/Content.Server/Database/UserDbDataManager.cs index c58c594dbad..1aac86c1291 100644 --- a/Content.Server/Database/UserDbDataManager.cs +++ b/Content.Server/Database/UserDbDataManager.cs @@ -1,8 +1,6 @@ using System.Threading; using System.Threading.Tasks; -using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; -using Robust.Server.Player; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Utility; @@ -19,11 +17,12 @@ namespace Content.Server.Database; /// public sealed class UserDbDataManager : IPostInjectInit { - [Dependency] private readonly IServerPreferencesManager _prefs = default!; [Dependency] private readonly ILogManager _logManager = default!; - [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; private readonly Dictionary _users = new(); + private readonly List _onLoadPlayer = []; + private readonly List _onFinishLoad = []; + private readonly List _onPlayerDisconnect = []; private ISawmill _sawmill = default!; @@ -51,8 +50,10 @@ public void ClientDisconnected(ICommonSession session) data.Cancel.Cancel(); data.Cancel.Dispose(); - _prefs.OnClientDisconnected(session); - _playTimeTracking.ClientDisconnected(session); + foreach (var onDisconnect in _onPlayerDisconnect) + { + onDisconnect(session); + } } private async Task Load(ICommonSession session, CancellationToken cancel) @@ -62,12 +63,20 @@ private async Task Load(ICommonSession session, CancellationToken cancel) // As such, this task must NOT throw a non-cancellation error! try { - await Task.WhenAll( - _prefs.LoadData(session, cancel), - _playTimeTracking.LoadData(session, cancel)); + var tasks = new List(); + foreach (var action in _onLoadPlayer) + { + tasks.Add(action(session, cancel)); + } + + await Task.WhenAll(tasks); cancel.ThrowIfCancellationRequested(); - _prefs.FinishLoad(session); + + foreach (var action in _onFinishLoad) + { + action(session); + } _sawmill.Verbose($"Load complete for user {session}"); } @@ -118,10 +127,31 @@ public Task GetLoadTask(ICommonSession session) return _users[session.UserId].Task; } + public void AddOnLoadPlayer(OnLoadPlayer action) + { + _onLoadPlayer.Add(action); + } + + public void AddOnFinishLoad(OnFinishLoad action) + { + _onFinishLoad.Add(action); + } + + public void AddOnPlayerDisconnect(OnPlayerDisconnect action) + { + _onPlayerDisconnect.Add(action); + } + void IPostInjectInit.PostInject() { _sawmill = _logManager.GetSawmill("userdb"); } private sealed record UserData(CancellationTokenSource Cancel, Task Task); + + public delegate Task OnLoadPlayer(ICommonSession player, CancellationToken cancel); + + public delegate void OnFinishLoad(ICommonSession player); + + public delegate void OnPlayerDisconnect(ICommonSession player); } diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 3cdf3bfe8e2..234a2e46586 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -14,6 +14,7 @@ using Content.Server.IoC; using Content.Server.Maps; using Content.Server.NodeContainer.NodeGroups; +using Content.Server.Players.JobWhitelist; using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; using Content.Server.ServerInfo; @@ -107,6 +108,7 @@ public override void Init() _voteManager.Initialize(); _updateManager.Initialize(); _playTimeTracking.Initialize(); + IoCManager.Resolve().Initialize(); } } diff --git a/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs b/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs new file mode 100644 index 00000000000..cd15cfb8f8d --- /dev/null +++ b/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Events; + +[ByRefEvent] +public readonly record struct GetDisallowedJobsEvent(ICommonSession Player, HashSet> Jobs); diff --git a/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs b/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs new file mode 100644 index 00000000000..51969d61ea0 --- /dev/null +++ b/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Events; + +[ByRefEvent] +public struct IsJobAllowedEvent(ICommonSession player, ProtoId jobId, bool cancelled = false) +{ + public readonly ICommonSession Player = player; + public readonly ProtoId JobId = jobId; + public bool Cancelled = cancelled; +} diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 4ddef7c2a98..b819b5930af 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -2,11 +2,11 @@ using System.Linq; using System.Numerics; using Content.Server.Administration.Managers; +using Content.Server.GameTicking.Events; using Content.Server.Ghost; using Content.Server.Spawners.Components; using Content.Server.Speech.Components; using Content.Server.Station.Components; -using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Mind; using Content.Shared.Players; @@ -137,8 +137,14 @@ private void SpawnPlayer(ICommonSession player, if (jobBans == null || jobId != null && jobBans.Contains(jobId)) return; - if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId)) - return; + if (jobId != null) + { + var ev = new IsJobAllowedEvent(player, new ProtoId(jobId)); + RaiseLocalEvent(ref ev); + if (ev.Cancelled) + return; + } + SpawnPlayer(player, character, station, jobId, lateJoin, silent); } @@ -181,10 +187,9 @@ private void SpawnPlayer(ICommonSession player, } // Figure out job restrictions - var restrictedRoles = new HashSet(); - - var getDisallowed = _playTimeTrackings.GetDisallowedJobs(player); - restrictedRoles.UnionWith(getDisallowed); + var restrictedRoles = new HashSet>(); + var ev = new GetDisallowedJobsEvent(player, restrictedRoles); + RaiseLocalEvent(ref ev); var jobBans = _banManager.GetJobBans(player.UserId); if (jobBans != null) diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index fa23312268f..d9511309b9d 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -19,7 +19,6 @@ using Robust.Server; using Robust.Server.GameObjects; using Robust.Server.GameStates; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Console; diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index f6800f72890..c6dfcadd382 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -13,6 +13,7 @@ using Content.Server.Maps; using Content.Server.MoMMI; using Content.Server.NodeContainer.NodeGroups; +using Content.Server.Players.JobWhitelist; using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; using Content.Server.ServerInfo; @@ -61,6 +62,7 @@ public static void Register() IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs new file mode 100644 index 00000000000..04289a40980 --- /dev/null +++ b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs @@ -0,0 +1,114 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Database; +using Content.Shared.CCVar; +using Content.Shared.Players.JobWhitelist; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Serilog; + +namespace Content.Server.Players.JobWhitelist; + +public sealed class JobWhitelistManager : IPostInjectInit +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; + + private readonly Dictionary> _whitelists = new(); + + public void Initialize() + { + _net.RegisterNetMessage(); + } + + private async Task LoadData(ICommonSession session, CancellationToken cancel) + { + var whitelists = await _db.GetJobWhitelists(session.UserId, cancel); + cancel.ThrowIfCancellationRequested(); + _whitelists[session.UserId] = whitelists.ToHashSet(); + } + + private void FinishLoad(ICommonSession session) + { + SendJobWhitelist(session); + } + + private void ClientDisconnected(ICommonSession session) + { + _whitelists.Remove(session.UserId); + } + + public async void AddWhitelist(NetUserId player, ProtoId job) + { + if (_whitelists.TryGetValue(player, out var whitelists)) + whitelists.Add(job); + + await _db.AddJobWhitelist(player, job); + + if (_player.TryGetSessionById(player, out var session)) + SendJobWhitelist(session); + } + + public bool IsAllowed(ICommonSession session, ProtoId job) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return true; + + if (!_prototypes.TryIndex(job, out var jobPrototype) || + !jobPrototype.Whitelisted) + { + return true; + } + + return IsWhitelisted(session.UserId, job); + } + + public bool IsWhitelisted(NetUserId player, ProtoId job) + { + if (!_whitelists.TryGetValue(player, out var whitelists)) + { + Log.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}", + player, + job, + Environment.StackTrace); + return false; + } + + return whitelists.Contains(job); + } + + public async void RemoveWhitelist(NetUserId player, ProtoId job) + { + _whitelists.GetValueOrDefault(player)?.Remove(job); + await _db.RemoveJobWhitelist(player, job); + + if (_player.TryGetSessionById(new NetUserId(player), out var session)) + SendJobWhitelist(session); + } + + public void SendJobWhitelist(ICommonSession player) + { + var msg = new MsgJobWhitelist + { + Whitelist = _whitelists.GetValueOrDefault(player.UserId) ?? new HashSet() + }; + + _net.ServerSendMessage(msg, player.Channel); + } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnFinishLoad(FinishLoad); + _userDb.AddOnPlayerDisconnect(ClientDisconnected); + } +} diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs b/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs new file mode 100644 index 00000000000..aaada99dea9 --- /dev/null +++ b/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs @@ -0,0 +1,83 @@ +using System.Collections.Immutable; +using Content.Server.GameTicking.Events; +using Content.Server.Station.Events; +using Content.Shared.CCVar; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Players.JobWhitelist; + +public sealed class JobWhitelistSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly JobWhitelistManager _manager = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + private ImmutableArray> _whitelistedJobs = []; + + public override void Initialize() + { + SubscribeLocalEvent(OnPrototypesReloaded); + SubscribeLocalEvent(OnStationJobsGetCandidates); + SubscribeLocalEvent(OnIsJobAllowed); + SubscribeLocalEvent(OnGetDisallowedJobs); + + CacheJobs(); + } + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev) + { + if (ev.WasModified()) + CacheJobs(); + } + + private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return; + + for (var i = ev.Jobs.Count - 1; i >= 0; i--) + { + var jobId = ev.Jobs[i]; + if (_player.TryGetSessionById(ev.Player, out var player) && + !_manager.IsAllowed(player, jobId)) + { + ev.Jobs.RemoveSwap(i); + } + } + } + + private void OnIsJobAllowed(ref IsJobAllowedEvent ev) + { + if (!_manager.IsAllowed(ev.Player, ev.JobId)) + ev.Cancelled = true; + } + + private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return; + + foreach (var job in _whitelistedJobs) + { + if (!_manager.IsAllowed(ev.Player, job)) + ev.Jobs.Add(job); + } + } + + private void CacheJobs() + { + var builder = ImmutableArray.CreateBuilder>(); + foreach (var job in _prototypes.EnumeratePrototypes()) + { + if (job.Whitelisted) + builder.Add(job.ID); + } + + _whitelistedJobs = builder.ToImmutable(); + } +} diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs index 9fcd80b72a3..7dd1995801a 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs @@ -55,7 +55,7 @@ namespace Content.Server.Players.PlayTimeTracking; /// Operations like refreshing and sending play time info to clients are deferred until the next frame (note: not tick). /// /// -public sealed partial class PlayTimeTrackingManager : ISharedPlaytimeManager +public sealed partial class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjectInit { [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IServerNetManager _net = default!; @@ -63,6 +63,7 @@ public sealed partial class PlayTimeTrackingManager : ISharedPlaytimeManager [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ITaskManager _task = default!; [Dependency] private readonly IRuntimeLog _runtimeLog = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; private ISawmill _sawmill = default!; @@ -457,4 +458,10 @@ private sealed class PlayTimeData /// public readonly HashSet DbTrackersDirty = new(); } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnPlayerDisconnect(ClientDisconnected); + } } diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index 5e848939fea..3c9b1b1246b 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -4,7 +4,9 @@ using Content.Server.Afk; using Content.Server.Afk.Events; using Content.Server.GameTicking; +using Content.Server.GameTicking.Events; using Content.Server.Mind; +using Content.Server.Station.Events; using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.Mobs; @@ -12,7 +14,6 @@ using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Roles; -using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; @@ -50,6 +51,9 @@ public override void Initialize() SubscribeLocalEvent(OnUnAFK); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnPlayerJoinedLobby); + SubscribeLocalEvent(OnStationJobsGetCandidates); + SubscribeLocalEvent(OnIsJobAllowed); + SubscribeLocalEvent(OnGetDisallowedJobs); _adminManager.OnPermsChanged += AdminPermsChanged; } @@ -175,6 +179,22 @@ private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev) _tracking.QueueSendWhitelist(ev.PlayerSession); // Nyanotrasen - Send whitelist status } + private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev) + { + RemoveDisallowedJobs(ev.Player, ev.Jobs); + } + + private void OnIsJobAllowed(ref IsJobAllowedEvent ev) + { + if (!IsAllowed(ev.Player, ev.JobId)) + ev.Cancelled = true; + } + + private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) + { + ev.Jobs.UnionWith(GetDisallowedJobs(ev.Player)); + } + public bool IsAllowed(ICommonSession player, string role) { if (!_prototypes.TryIndex(role, out var job) || @@ -193,9 +213,9 @@ public bool IsAllowed(ICommonSession player, string role) return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, isWhitelisted); } - public HashSet GetDisallowedJobs(ICommonSession player) + public HashSet> GetDisallowedJobs(ICommonSession player) { - var roles = new HashSet(); + var roles = new HashSet>(); if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return roles; @@ -227,7 +247,7 @@ public HashSet GetDisallowedJobs(ICommonSession player) return roles; } - public void RemoveDisallowedJobs(NetUserId userId, ref List jobs) + public void RemoveDisallowedJobs(NetUserId userId, List> jobs) { if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return; @@ -246,7 +266,7 @@ public void RemoveDisallowedJobs(NetUserId userId, ref List jobs) { var job = jobs[i]; - if (!_prototypes.TryIndex(job, out var jobber) || + if (!_prototypes.TryIndex(job, out var jobber) || jobber.Requirements == null || jobber.Requirements.Count == 0) continue; diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index e32af589e95..f7c15a23405 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -3,26 +3,21 @@ using System.Threading; using System.Threading.Tasks; using Content.Server.Database; -using Content.Server.Humanoid; using Content.Shared.CCVar; -using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; -using Content.Shared.Roles; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Player; -using Robust.Shared.Prototypes; using Robust.Shared.Utility; - namespace Content.Server.Preferences.Managers { /// /// Sends before the client joins the lobby. /// Receives and at any time. /// - public sealed class ServerPreferencesManager : IServerPreferencesManager + public sealed class ServerPreferencesManager : IServerPreferencesManager, IPostInjectInit { [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; @@ -30,6 +25,7 @@ public sealed class ServerPreferencesManager : IServerPreferencesManager [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IDependencyCollection _dependencies = default!; [Dependency] private readonly ILogManager _log = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; // Cache player prefs on the server so we don't need as much async hell related to them. private readonly Dictionary _cachedPlayerPrefs = @@ -326,5 +322,12 @@ private sealed class PlayerPrefData public bool PrefsLoaded; public PlayerPreferences? Prefs; } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnFinishLoad(FinishLoad); + _userDb.AddOnPlayerDisconnect(OnClientDisconnected); + } } } diff --git a/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs b/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs new file mode 100644 index 00000000000..58d860b1e12 --- /dev/null +++ b/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.Server.Station.Events; + +[ByRefEvent] +public readonly record struct StationJobsGetCandidatesEvent(NetUserId Player, List> Jobs); diff --git a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs index 4b7cd64961b..c3c3865c7bf 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs @@ -2,6 +2,7 @@ using Content.Server.Administration.Managers; using Content.Server.Players.PlayTimeTracking; using Content.Server.Station.Components; +using Content.Server.Station.Events; using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.Network; @@ -342,8 +343,9 @@ private Dictionary> GetPlayersJobCandidates(int? weight, foreach (var (player, profile) in profiles) { var roleBans = _banManager.GetJobBans(player); - var profileJobs = profile.JobPriorities.Keys.ToList(); - _playTime.RemoveDisallowedJobs(player, ref profileJobs); + var profileJobs = profile.JobPriorities.Keys.Select(k => new ProtoId(k)).ToList(); + var ev = new StationJobsGetCandidatesEvent(player, profileJobs); + RaiseLocalEvent(ref ev); List? availableJobs = null; @@ -354,7 +356,7 @@ private Dictionary> GetPlayersJobCandidates(int? weight, if (!(priority == selectedPriority || selectedPriority is null)) continue; - if (!_prototypeManager.TryIndex(jobId, out JobPrototype? job)) + if (!_prototypeManager.TryIndex(jobId, out var job)) continue; if (weight is not null && job.Weight != weight.Value) diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs index debac8902e2..3bfa815af1e 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.cs @@ -428,7 +428,7 @@ public IReadOnlySet GetOverflowJobs(EntityUid station, StationJobsCompon /// Whether or not to pick from the overflow list. /// A set of disallowed jobs, if any. /// The selected job, if any. - public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet? disallowedJobs = null) + public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet>? disallowedJobs = null) { if (station == EntityUid.Invalid) return null; diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 925ef818a83..ac0b3d3289a 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -225,6 +225,12 @@ public static readonly CVarDef public static readonly CVarDef GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED); + /// + /// If roles should be restricted based on whether or not they are whitelisted. + /// + public static readonly CVarDef + GameRoleWhitelist = CVarDef.Create("game.role_whitelist", true, CVar.SERVER | CVar.REPLICATED); + /// /// Whether or not disconnecting inside of a cryopod should remove the character or just store them until they reconnect. /// diff --git a/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs b/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs new file mode 100644 index 00000000000..8347e2d706a --- /dev/null +++ b/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs @@ -0,0 +1,33 @@ +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.Players.JobWhitelist; + +public sealed class MsgJobWhitelist : NetMessage +{ + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; + + public HashSet Whitelist = new(); + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) + { + var count = buffer.ReadVariableInt32(); + Whitelist.EnsureCapacity(count); + + for (var i = 0; i < count; i++) + { + Whitelist.Add(buffer.ReadString()); + } + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) + { + buffer.WriteVariableInt32(Whitelist.Count); + + foreach (var ban in Whitelist) + { + buffer.Write(ban); + } + } +} diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 9f158a79e08..d28211a67f3 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -1,10 +1,8 @@ using Content.Shared.Access; using Content.Shared.Players.PlayTimeTracking; -using Content.Shared.Roles; using Content.Shared.StatusIcon; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Roles { @@ -122,6 +120,9 @@ public sealed partial class JobPrototype : IPrototype [DataField("extendedAccessGroups")] public IReadOnlyCollection> ExtendedAccessGroups { get; private set; } = Array.Empty>(); + + [DataField] + public bool Whitelisted; } /// diff --git a/Resources/Locale/en-US/commands/job-whitelist-command.ftl b/Resources/Locale/en-US/commands/job-whitelist-command.ftl new file mode 100644 index 00000000000..7fa20f03dec --- /dev/null +++ b/Resources/Locale/en-US/commands/job-whitelist-command.ftl @@ -0,0 +1,20 @@ +cmd-jobwhitelist-job-does-not-exist = Job {$job} does not exist. +cmd-jobwhitelist-player-not-found = Player {$player} not found. +cmd-jobwhitelist-hint-player = [player] +cmd-jobwhitelist-hint-job = [job] + +cmd-jobwhitelistadd-desc = Lets a player play a whitelisted job. +cmd-jobwhitelistadd-help = Usage: jobwhitelistadd +cmd-jobwhitelistadd-already-whitelisted = {$player} is already whitelisted to play as {$jobId} .({$jobName}). +cmd-jobwhitelistadd-added = Added {$player} to the {$jobId} ({$jobName}) whitelist. + +cmd-jobwhitelistget-desc = Gets all the jobs that a player has been whitelisted for. +cmd-jobwhitelistget-help = Usage: jobwhitelistadd +cmd-jobwhitelistget-whitelisted-none = Player {$player} is not whitelisted for any jobs. +cmd-jobwhitelistget-whitelisted-for = "Player {$player} is whitelisted for: +{$jobs}" + +cmd-jobwhitelistremove-desc = Removes a player's ability to play a whitelisted job. +cmd-jobwhitelistremove-help = Usage: jobwhitelistadd +cmd-jobwhitelistremove-was-not-whitelisted = {$player} was not whitelisted to play as {$jobId} ({$jobName}). +cmd-jobwhitelistremove-removed = Removed {$player} from the whitelist for {$jobId} ({$jobName}). diff --git a/Resources/Locale/en-US/job/role-whitelist.ftl b/Resources/Locale/en-US/job/role-whitelist.ftl new file mode 100644 index 00000000000..3149f182b6a --- /dev/null +++ b/Resources/Locale/en-US/job/role-whitelist.ftl @@ -0,0 +1 @@ +role-not-whitelisted = You are not whitelisted to play this role. From adcc304a1b0eab8d5eef7f13245147b7b116e225 Mon Sep 17 00:00:00 2001 From: DeltaV-Bot <135767721+DeltaV-Bot@users.noreply.github.com> Date: Sat, 1 Jun 2024 16:19:09 +0000 Subject: [PATCH 222/235] Automatic changelog update --- Resources/Changelog/DeltaVChangelog.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index 774e4f23bb7..90993eb7663 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -2520,3 +2520,10 @@ id: 370 time: '2024-05-31T23:15:41.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/1274 +- author: NullWanderer + changes: + - message: Merged upstream. Please report any bugs + type: Add + id: 371 + time: '2024-06-01T16:15:18.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/1278 From 6e40de164b4c4d70abeef03a5684212e84c43b2d Mon Sep 17 00:00:00 2001 From: Null <56081759+NullWanderer@users.noreply.github.com> Date: Sat, 1 Jun 2024 20:29:42 +0200 Subject: [PATCH 223/235] Remove tesla spawner that was accidentally committed (#1279) Update crates.yml Signed-off-by: Null <56081759+NullWanderer@users.noreply.github.com> --- .../Prototypes/Entities/Markers/Spawners/Random/crates.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml index 1986dfc29c5..90a0842b9b1 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml @@ -75,7 +75,7 @@ - type: RandomSpawner rarePrototypes: - CrateEngineeringSingularityGenerator - - CrateEngineeringTeslaGenerator +# - CrateEngineeringTeslaGenerator # DeltaV - Teslas aren't allowed - CrateEngineeringTeslaGroundingRod - CrateEngineeringParticleAccelerator - CrateRCD @@ -135,4 +135,4 @@ - CrateEmergencyExplosive - CrateSecurityBiosuit chance: 0.9 - offset: 0.0 \ No newline at end of file + offset: 0.0 From 380c85847600d60dee104b6ee0fc7ef8dbe41b51 Mon Sep 17 00:00:00 2001 From: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Date: Sun, 2 Jun 2024 00:16:10 +0300 Subject: [PATCH 224/235] Fix pseudoitem fitness check (#1232) * fixie * Prettier code * Bleugh --- .../SharedPseudoItemSystem.Checks.cs | 164 ++---------------- 1 file changed, 17 insertions(+), 147 deletions(-) diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs index e139bc512f0..906503b3707 100644 --- a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs @@ -3,163 +3,33 @@ namespace Content.Shared.Nyanotrasen.Item.PseudoItem; -/// -/// Almost all of this is code taken from other systems, but adapted to use PseudoItem. -/// I couldn't use the original functions because the resolve would fuck shit up, even if I passed a constructed itemcomp -/// -/// This is horrible, and I hate it. But such is life -/// public partial class SharedPseudoItemSystem { + /// + /// Checks if the pseudo-item can be inserted into the specified storage entity. + /// + /// + /// This function creates and uses a fake item component if the entity doesn't have one. + /// public bool CheckItemFits(Entity itemEnt, Entity storageEnt) { if (!Resolve(itemEnt, ref itemEnt.Comp) || !Resolve(storageEnt, ref storageEnt.Comp)) return false; - if (Transform(itemEnt).Anchored) + if (!TryComp(itemEnt, out var metadata)) return false; - if (storageEnt.Comp.Whitelist?.IsValid(itemEnt, EntityManager) == false) - return false; - - if (storageEnt.Comp.Blacklist?.IsValid(itemEnt, EntityManager) == true) - return false; - - var maxSize = _storage.GetMaxItemSize(storageEnt); - if (_item.GetSizePrototype(itemEnt.Comp.Size) > maxSize) - return false; - - // The following is shitfucked together straight from TryGetAvailableGridSpace, but eh, it works - - var itemComp = new ItemComponent - { Size = itemEnt.Comp.Size, Shape = itemEnt.Comp.Shape, StoredOffset = itemEnt.Comp.StoredOffset }; - - var storageBounding = storageEnt.Comp.Grid.GetBoundingBox(); - - Angle startAngle; - if (storageEnt.Comp.DefaultStorageOrientation == null) - startAngle = Angle.FromDegrees(-itemComp.StoredRotation); // PseudoItem doesn't support this - else - { - if (storageBounding.Width < storageBounding.Height) - { - startAngle = storageEnt.Comp.DefaultStorageOrientation == StorageDefaultOrientation.Horizontal - ? Angle.Zero - : Angle.FromDegrees(90); - } - else - { - startAngle = storageEnt.Comp.DefaultStorageOrientation == StorageDefaultOrientation.Vertical - ? Angle.Zero - : Angle.FromDegrees(90); - } - } - - for (var y = storageBounding.Bottom; y <= storageBounding.Top; y++) - { - for (var x = storageBounding.Left; x <= storageBounding.Right; x++) - { - for (var angle = startAngle; angle <= Angle.FromDegrees(360 - startAngle); angle += Math.PI / 2f) - { - var location = new ItemStorageLocation(angle, (x, y)); - if (ItemFitsInGridLocation(itemEnt, storageEnt, location.Position, location.Rotation)) - return true; - } - } - } - - return false; - } - - private bool ItemFitsInGridLocation( - Entity itemEnt, - Entity storageEnt, - Vector2i position, - Angle rotation) - { - if (!Resolve(itemEnt, ref itemEnt.Comp) || !Resolve(storageEnt, ref storageEnt.Comp)) - return false; - - var gridBounds = storageEnt.Comp.Grid.GetBoundingBox(); - if (!gridBounds.Contains(position)) - return false; - - var itemShape = GetAdjustedItemShape(itemEnt, rotation, position); - - foreach (var box in itemShape) + TryComp(itemEnt, out var item); + // If the entity doesn't have an item comp, create a fake one + // The fake component is never actually added to the entity + item ??= new ItemComponent { - for (var offsetY = box.Bottom; offsetY <= box.Top; offsetY++) - { - for (var offsetX = box.Left; offsetX <= box.Right; offsetX++) - { - var pos = (offsetX, offsetY); - - if (!IsGridSpaceEmpty(itemEnt, storageEnt, pos, itemShape)) - return false; - } - } - } - - return true; - } - - private IReadOnlyList GetAdjustedItemShape(Entity entity, Angle rotation, - Vector2i position) - { - if (!Resolve(entity, ref entity.Comp)) - return new Box2i[] { }; - - var shapes = entity.Comp.Shape ?? _item.GetSizePrototype(entity.Comp.Size).DefaultShape; - var boundingShape = shapes.GetBoundingBox(); - var boundingCenter = ((Box2) boundingShape).Center; - var matty = Matrix3.CreateTransform(boundingCenter, rotation); - var drift = boundingShape.BottomLeft - matty.TransformBox(boundingShape).BottomLeft; - - var adjustedShapes = new List(); - foreach (var shape in shapes) - { - var transformed = matty.TransformBox(shape).Translated(drift); - var floored = new Box2i(transformed.BottomLeft.Floored(), transformed.TopRight.Floored()); - var translated = floored.Translated(position); - - adjustedShapes.Add(translated); - } - - return adjustedShapes; - } - - private bool IsGridSpaceEmpty(Entity itemEnt, Entity storageEnt, - Vector2i location, IReadOnlyList shape) - { - if (!Resolve(storageEnt, ref storageEnt.Comp)) - return false; - - var validGrid = false; - foreach (var grid in storageEnt.Comp.Grid) - { - if (grid.Contains(location)) - { - validGrid = true; - break; - } - } - - if (!validGrid) - return false; - - foreach (var (ent, storedItem) in storageEnt.Comp.StoredItems) - { - if (ent == itemEnt.Owner) - continue; - - var adjustedShape = shape; - foreach (var box in adjustedShape) - { - if (box.Contains(location)) - return false; - } - } + Owner = itemEnt, + Shape = itemEnt.Comp.Shape, + Size = itemEnt.Comp.Size, + StoredOffset = itemEnt.Comp.StoredOffset + }; - return true; + return _storage.CanInsert(storageEnt, itemEnt, out _, storageEnt.Comp, item, ignoreStacks: true); } } From a6be9a00110c9ce944df7a406723504e43eec9b0 Mon Sep 17 00:00:00 2001 From: TheOneWhoIsManyFrame Date: Sun, 2 Jun 2024 05:16:28 +0800 Subject: [PATCH 225/235] Harpy Can Now Mimic Harps! (#1252) It is weird this isn't already a thing, lol. Signed-off-by: TheOneWhoIsManyFrame --- Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml index 37c0694a692..41be2a2c0aa 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml @@ -21,6 +21,7 @@ "Sax": {66: 0} "Piano": {1: 0} "Church Organ": {19: 0} + "Harp": {46: 0} - type: UserInterface interfaces: enum.InstrumentUiKey.Key: From 7e80045d8b1084ce1c9af451987e434e8d1efc69 Mon Sep 17 00:00:00 2001 From: DeltaV-Bot <135767721+DeltaV-Bot@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:16:30 +0000 Subject: [PATCH 226/235] Automatic changelog update --- Resources/Changelog/DeltaVChangelog.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index 90993eb7663..7907aff7177 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -2527,3 +2527,11 @@ id: 371 time: '2024-06-01T16:15:18.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/1278 +- author: Mnemotechnician + changes: + - message: Felinids can now be placed even inside bags that already contain items, + as long as there is enough free space. Mrrrp. + type: Fix + id: 372 + time: '2024-06-01T21:16:10.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/1232 From f5aeac7eb888840d67a0530d95ea19a8a9c1f147 Mon Sep 17 00:00:00 2001 From: DeltaV-Bot <135767721+DeltaV-Bot@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:16:49 +0000 Subject: [PATCH 227/235] Automatic changelog update --- Resources/Changelog/DeltaVChangelog.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index 7907aff7177..ff628c9bba2 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -2535,3 +2535,10 @@ id: 372 time: '2024-06-01T21:16:10.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/1232 +- author: TheOneWhoIsManyFrame + changes: + - message: Harpies can now mimic harps! + type: Add + id: 373 + time: '2024-06-01T21:16:28.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/1252 From d405e0263df5409820125cf3950693e884e452f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 23:16:58 +0200 Subject: [PATCH 228/235] Update Credits (#1255) Co-authored-by: DeltaV-Bot --- Resources/Credits/GitHub.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index 5fd112c6e0b..91e60adf820 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, adeinitas, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, angelofallars, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, beck-thompson, BGare, bhenrich, Bixkitts, Blackern5000, Blazeror, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bribrooo, Bright0, brndd, c4llv07e, CaptainSqrBeard, Carbonhell, Carolyn3114, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, Clyybber, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, Guess-My-Name, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, HerCoyote23, hitomishirichan, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, Lukasz825700516, lunarcomets, luringens, lvvova1, Lyndomen, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, nuke-haus, NULL882, NullWanderer, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, shampunj, SignalWalker, Simyon264, Sirionaut, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, Subversionary, superjj18, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, wafehling, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem +0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, adeinitas, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, angelofallars, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, beck-thompson, BGare, bhenrich, Bixkitts, Blackern5000, Blazeror, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bribrooo, Bright0, brndd, c4llv07e, CaptainSqrBeard, Carbonhell, Carolyn3114, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, Clyybber, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, Guess-My-Name, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, HerCoyote23, hitomishirichan, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, Lukasz825700516, lunarcomets, luringens, lvvova1, Lyndomen, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, nuke-haus, NULL882, NullWanderer, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, shampunj, SignalWalker, Simyon264, Sirionaut, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, Subversionary, superjj18, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, wafehling, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem From ed67ebd5a6574d357b9eacfcd6552b68e4b44d1b Mon Sep 17 00:00:00 2001 From: Tryded <139474617+Tryded@users.noreply.github.com> Date: Sat, 1 Jun 2024 16:17:20 -0500 Subject: [PATCH 229/235] Revert Reverts New E-Sword Sprites #591 (#1264) --- .../Objects/Weapons/Melee/e_sword.yml | 2 +- .../Melee/e_sword.rsi/inhand-left-blade.png | Bin 7094 -> 0 bytes .../Weapons/Melee/e_sword.rsi/inhand-left.png | Bin 314 -> 0 bytes .../Melee/e_sword.rsi/inhand-right-blade.png | Bin 7218 -> 0 bytes .../Melee/e_sword.rsi/inhand-right.png | Bin 318 -> 0 bytes .../Weapons/Melee/e_sword.rsi/meta.json | 63 ------------------ 6 files changed, 1 insertion(+), 64 deletions(-) delete mode 100644 Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-left-blade.png delete mode 100644 Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-left.png delete mode 100644 Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-right-blade.png delete mode 100644 Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-right.png delete mode 100644 Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/meta.json diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index dcf65d03827..66079b88448 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -55,7 +55,7 @@ Blunt: 4.5 - type: Item size: Small - sprite: DeltaV/Objects/Weapons/Melee/e_sword.rsi # Delta-V + sprite: Objects/Weapons/Melee/e_sword.rsi - type: UseDelay delay: 1.0 - type: PointLight diff --git a/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-left-blade.png b/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-left-blade.png deleted file mode 100644 index 6bc304a12fd102d0ffdfb6de7edc53452cd3c675..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7094 zcmeHMc|4Ts+n++RBoQT*Y1B_*%$S8S8NwJ#js4iN&GL+yn8nO6IIS9Kk*ysqvLuBf z5)G|L;*^S{BC>?kQH11uhE6%}d*1VV&*$_0{_B~~EcbPPukUr;-{-pS=XpM}&&A1Z zf#Nbn2n4dg!Jg;_eg{e4^XGw|uaApx5QyTTXb*3p8$|@=@wp6EC;$~k@c<|g$znht zk*$NrH%Ay6s1T>LY!}W^g)UWTEP1CK{aJG4$o7>wmJO20rVIY1#p(0Z7H7ERwtXE9 zteHH3&|g)4xa6qk{vLyp$J8G3r^Lb<>}TO*Wo6wY;_G(7E5ej6e`8r4f%2+z{pj$0 zS@P)Qhmp#rs6zLl*mpb3;cG{_YSVu8lPpR3T&DA`%Db}mFYdu$oVn)Bb^B>q+HFW>EOIh&J zx{9SQMs2?0BD}-_Lu;>_k3F#TW{x!n7E(nGc|8L)ebMWPd1VtW&UV*c8uL^gujOb2 z)H!!c0t%}y@b4&4l`!&!eb!3J%+pD?6bb2<3j380!E?$o`3J+)!YaIMX$RDiF>(QJ zIM?iZd^al~CY86|VrY$DBfEK+k!v0$N2647bJH>(;OyCDez&j1@7lR-<*FmXm%}Ka4$84b8U(K`7XEpnoYR{XB~<)f5~sX;&uLfw{PB4 z-_3(?6RTT%1G4{x{F0YZ%;3I;frh;=?Uth+)n+QM2*2`B@8bzpw@*$3{`r7`kx$mt zx{WXLF639<-s8A_@cR9y{-WoTH%fH%PHwGx=LTRX2oanI8EIV-3Z9agR%9*6+WL-}Vj^#sjhK%UfkE}WEsdkTkEy9VS{O(Ka?s7BD+U#!)e z_4?Ui*4)Su=C+|?8nKVR!abKne1f${(Vt56WKYH28P$-qMqaOVYUo8>(NXqfTGtVRD&;9i=esmBB?tbq`b4?UuYVxd|b?+s$nWd(*ivd2>_Vj+YFq=6c4| z1-)u~(rDUiQwD-m)YNoBuJYp=^Q}ln!w2!peerK`8y&-Gel5<6pq02zNK^sLYeY8b zpytMUx?Lvycp3iH_B|fNgctix1eWjAa5EWB-w_dhYH;M-prV>i za_iE~J}<;NPj;e`%EkRKNGi|&eqH*a$CR>xF!5br&Y zZ#t53av*oS)_+l)E|1s}2iHT!^tSym~Obym#09ll7f3COWS8 z+-C6{G7PvBsv0-2WkI%N@m6~C7E}&7M9N11D5gUS9gdp;TO^bn*2=u zy#3cGto)|N!gtPkUw32-`C}3FdWUPb;oRakX+Kmnf8@CuI-y0l@nW9~Y-vAWPz=|| zOVvyoZ2UdJHfzFV3D;p??xVe*k~2fz?acG5_g9GJ=Dcq)DHeBmSzqQK?C+w$NRD~8 z`xabphbrYycpSt%G*s2m_mAH9ud$JA2`^9?x#)A2OVwl6s9$dX8@WohoKs@?urc71 z)q6JfQgE0D{q9TGWxw?sRuG(yIN3T)mgZ?at&@3j+&%xrz~x1Ct192qMV`8zm2b3; zbu%1E68RJ2gr-A9hvJ|dy>ou@i9cq8j_3YszDtlRnb6Jv9_U&|*SCEUKuH{)gHcZ@wK zoy*>$9b3BW%KQNH1$1K^sWWCmYxR@lx839Ftoj3LwFwGl_fR;odW`QnPpvq+(?wfO z93h80%jnaME%Q5$>h{ufFEL%=6+V_PqIW^n|dH^C)s*M+b&ks{Z;h z@;sDBv{Bx4rYi2JYKr_k{CVrP`|i~8SM*TyZkdI_8!t97eLEgs-fHb?bi+k=jmX|? z%gfY+v$Y{g{-h;AXPm{mvgQs95Zc}My0MO_K5XnFN9z*xR}rtQx~C>kH8a)3mVM6C z_Z4Y3+-k~jA}%;Pz6*AjUwZ+o9TCsDwkP(w?0Dk~w3T>wTIFfY4>FC1R957X(miT^ zaf+su+PdCdu#&OkhQcV8RT=R3)LC4XaXrqsc6vp_!J`cdJgOk z(p+nAe>sXftfeMK?O~kj7`ll^(Xa?KL$2Tg-7Wq71+n3UHB4|-{yIYs- zZL6B!UAkHt{gw}=&?Wn`b{N#Xn%L0M_ts(s&#@~1z!N#e?fDpVw%dtYIgh-YfAX?_a$cnX8g9ZiH+vs>y`Z zP)i;2S+r{FXMRg$C;48eOVIB4YI>s9lbr64wi;_QZy4h3Ga^MrpYs4=I!ZaI{NS_G z$xC+Nlt<$*sdmNe!lGo-WtGr)<;fHuOi!QN)?&4ohZ~kDG^^ZtnIDHXKx><@7zZ)I z+al!iDZ<$%0oEbL%R-(z#O@)lNvpbO58|i8`XDbA6rE) z-ZvtE8_qx0IuSXq$251_x&5oJ@5Hj>lIr^|hU|7s%wap6Be@1;XVYWVTD^Bk#zTm= zEU1&@iTDj{+5~%Fz7Ty7(#$~lU<-TTs{EB7-5W%aN9^$1PW%M z2sP)^8F)7$=^F(&BEXnJArFs0h(sbI5!#5$4@MZ9nVBJwCT4A|RmgSv(<&%YjNcDO7H_kN|^$^U!bP;4IBH5;T4bARSNB3xsrp1318d>j41> z6cUMpBT;aa8Dh3SI7=pf_vQ$`sR-(ch@|ik#zsg4oBbybfzUSMN4&rE5O{!32?#ep zzzyfq0NV(FBh;N8louKn}*07y;Kd56*&2r#MX$g_Ry9LO%;eWWr5v)H_84=H*! zl1}@Mr!jOI0tf}zU?KvL+4xU*(Eq!`UoK~wd|D_xkxL7gvN#Y4FlhpKI+wQgvVNg+UDh+K4r=w|D9MuGaqZ`v^Q8{n~LJEflNU1<_BNoVm z#Tir46aWpUGB9*FhC;=`&1irr97)HZ=~OBMKw&YnD4h8$u(TCZ`VtQzf_&f0bU~*@2MV$W|&kyK#7Aw9`#N~%L^PQ<%0GjacdHxFgoyiT{r36BL zl*7MS)PKX7&(x(o=*#6t&5G|1gw2d*wvtfRv{X>&bo=5dG^vUNn1B)i(5FuUsN>8O zjY;7I1K>XStx5i{v;Lx9F<30a!~{u!(@aq~IK~V^g`1fI6u2>ifuu1gD8Sfcrlh{J z3%CrSh{6Y~g265V&okIV)8`qw_FI|h|2Z2G6Oh&r65J(`XgC__filKpjqw;PZ02l3 z%@NX_@sHJ-OZN^k89!SObLrlKcaRpGdpM65$^!U**6EMB`Co9e{J+)czjL1roAI{c z@}j_IWeS}|oWBkK7r+^YO)MI~5pe%5_1TaaS!P{8z?^4n;Drgi&LO^E=)P5nw2}UU z$G7VH4@Ll~e+KzS`u>#br(FL?fqw-4SzSNn`bP@0eGR&6Pv6SECfN)xzlIHA`t?!h5^P{?zKFow$S~tVB{YcE9Kf`*~~BDR|yk;@i_b z&q~=LpeaSKqkK3sqP67fie^*x@W!~L?6Ft*Uc-ScU+nswo_`7V-%KC2Y@QnIcGxyI zdI~70f|;ypMnWQf95O(im}gD8jVkzWgsX z_QvOYv0@LKTTiU*%`*Jgh%a`2u&gq~OiiWjn2d>k(74?+Lp&f-cU)_F{v-uc@g+8C zeO$YV$_ybypFBJ@E@E&0sN?HQ06eJ;Zu6~GV^qaZao#Foy)CJ^iacL3dI?KxUKkAX hS$a!#F6emvy3A3?iKmC2Iq#R!IM_N73#@};{tFMYPqF|2 diff --git a/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-left.png b/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-left.png deleted file mode 100644 index 6e5fe94f81443ff80212f5b89234703fe25ae307..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fAQ1FeX zi(^Q|oVT+!@*XzeVX=2O7P$H^d%^2knH3xhE_KY?*1Z34egl>d}lg2?_`J5Qaf-^e{bx2~AlO zm90YdR3v3hNT^WnGwN5)`=0mw-t+mqzyF%~JTuRI-{0$dUC;NvuKRwTnQ*J!rV`?7 z#UT)ggt?iKHTZo{cr05Ae!lvHuL*%HSM;~B<64vXP!^l%M)LxoTt5~71$=345QuN* zNP<24wPw_@Zts&?i`MbKr-?_l$X_q2D*CoPETp>`U~y3 z8ZGa2^~!bD5BZ;M?pj>-bqu)s7NdHnG}m)9gzcXcKRtS>d{psGvrm^Er@Ucscx-O; zip=Tk)A1xT)m>)-5*woCRNqvfCK4t%(zd6wE=)Ac_I4aSo7=r)wD%c5?^JJ~Zm{CX zvB;$}>L#NTm-WuPSaqpkTU0J4c;a|R@0k>r6j%NzV>-G0oe2sC^pssiIQrf3?%5cV z>mff{clLdm?r42U-UH(*{L>4iw&&EtJzTcr12wS|>*BY^*)^HRQY=oLm??_$9HL44 z);{QFlOF4go>R29r@}dez%1irzZpz-RMjKLLbO;|ZS>R8t{zj>ueCFji-*g&#;!9b zWQQ%Bo44P?^rtu(8@#?lxwa=Dh5yk;W1QrQQD{+p6d`d&)5Xv-HdhdvqV1m3Y-bQ4 zIMyqp#}1*_68Fz+_Dl6^ia#5xpDp_@NdKXltZdK5i9oK?HSGdb_Q zlWn9)7LG}kwKUpUXYp$8`wI^?U)k7jp?~|75y=DQc{%w4JQ;H6m`6H{SXBjZSmAeq+(22c^77I`_bI?d>s zs&l=Rk9^S%&cg$=Hwl;r(vYA71lxjaIJdQ*=<)7X-;Wk!^ z7=otU#82+hwoWR-sD!8y%6#n}m~DEiQb)hq;moeid)j^_hvC$phHcM!(e#$Cy#E^I zxgh$ju2ND1TI~q>iPGys=OYs8b*p_uMXwGCvQ~HQTl?5&|5d|~Mt|5JNXf~{?Ai`Z zOd95Wiev4G4cLnSCBJljH15a-{*PDKJuE}R;z_S%t%6`PwXS6bf`~+t^ z@@?LsHp9|LxV23XH){v4+@a~t$n(hI103dMX%7{0?!%>z9XD{hdAZ!G;@`ZyO2(Tm z`n;8-!l5fk*~J^Pk3=P&Jk+w9v{}V7 za4M)CBDM{;c8j=$R(a&-jJ>TcswKd~?1aM0GWix&e*?vXo6KDK#XFHihKGW~4I=B05mMD6e{ z*6F(FE03d}uA#2tLB8D09|^2;?c8yc+vPZ5Se00yy0*!)fhShTju~1qV6yRIWLcYX z#O4UC=k-dlg(*%EjQWXASm3qt5X?>hSOm)*l2}a8E{XrF?qcYdsQX57I3^=NKV@R) z@(z>6``&&}>b+9z!k%fw@4RTctLwxHQ`&QE_k_RF$ctIIDLF@%eAOhbPE7l=!MpBn z9xv(HV@~mSc%5#c9Ds4WzKG+|6I3g+h@fd1YtbUNEhPTvos>44-2$zmb1gj6Ht9hZ zrt-mp?s6(D;QYzp-QFAKW;V`zT|74uQiJJ6n)#I+r-ySen67l$nieYe!K zS$EuC?dvnW*jurwVuRG!jgCX+IW`s7h@7{bO8Ty!y;r4nREfj_$%TP<)f~ah4av%S z2RT>g^nx23IL(E@~g5hpRbqprVh`Bx*UZSw|6^w3 zJB=lMAuMk*VcK%#Cn?obH(9sd-Gc}557;aEv(!6famE4XCJH0(sf`sHmP+12Ni)+F zIboL#tc-FPCp6p}eJb^%_Nsc!Ejw_(SzOHd)9x0M(wbZ0pSN9_cq4B#QH4Lpbuy;B zl0MI{vQyU7Y0gFr1O{x{2v}@N@xDwz;%$@!cS@3WZgO_gSi1+`WhrPq6`6M`_K$66 zQjlUZQ=O|ax^F(+FXeD3PTW;ypjYslk{ZR1x8bot=JiPz)P}H4tQg4AiqHh zr}Avf^G0QwU!Nz2_tIL~ulh#KmA!nogNxeaTIXtveAnN}I5g{=x-KGQ021@US&A^D zqA;+6d)bPYQ^8IpMBVlrj#RXg^X4ZgL(BpemKm{v-5M{d%xD}E?Y({0^o+a)FaL4!Bu6|y-LI@^4K61HZh0iHh1H{<)ssTm zsuq@dAzacVJuxK8#%krmfwAuA{-tNd-O`~+YsD$+-fiwWcXI|nWRD%jOhs=Bu&<)++emNPN$fo&F%sxMwQjfF}a8b}IDtnbb$P zap`lnx+v~nLvDZOt|%{kiK>#`9*`xLUY%l8ztYnnHp~v;Df8Z)`3hNnX)uc^I$6lB z*qN|nlap5ZY@XUAP^YTvqiwWy=aY%!;p3%Jvd~o~AsS-Js6Ee7O%f2jmq*s!Z7Pyn z|L*f=f|cYKQ5})%=_5-eq(hyigDY)1*IbdkUep*lS(1VK;&i{@%jLGv)DOlhk5>X2 zkurw^#wYZm8y#X^YiiqGsS|6ncrN~amBm}qaY2lQ^*z#bRI=;r97X$Pd(NQboQEuyL~9$O!#6p%7!h|!Hr<$hIDhYOF(Y_If?tu_{!RLFb`UqcpD9YXzTg1$P`b23v~tDX$&3MXh}H?N~7w) zY;i;+k!1*Y(9HbV0Lg#14aMJ+qD6)2>56On50S&D!B+=1&r1N$h201k zbRNR>)PdO%t)PZXHUPz{W7UyxV_%vN8m21_)n-%O2-Zd>-zmVB4$OngWf2evKA*47 z$EY*e?g*5YmKFkuMxfDf5CP}-F}P%3ID?}ir1-{R1aK&98jDL~GN3|EvMZCv)q%ml zap-q)aFk-~3wC~YAZ*XmbGTH5IoQC0*8>6&Xe3e-jzq)JT8M@A;3$#!! z!k5fKpwy8FI{i-;9Imm?Pk(=D!Lb3KRuI+zhsk480AnA3!Btu4l;y?aEcD6a0K%^M zxV@-u2r#I5&kJo#&52e&Y=knp)99>u3n6+Tl1ll3WAWHt^B5`x0eAs)Fc1#NjQSHE zwEto7mzOh7J}(r(h)LlISNJo?lZwIOT{S6iO*CkTMY_4d$#^VC=7z&! zFnB5!g+VQ#*u$oQsZI7;7?qHU3Q{4dcnv&21>h7UIO|v~Gyr!+YihwUSd0c)6Xi;E z(<0AP356x-Tbb*?(CWyaJyu?1t{an02kV8#pfdTKpF=h@IzZx*g|eaWSWP?ziN;~E zShR+w=1+D%s^jMes1$+;NJ$3Mhejv60|*wweSQE8nqbHV$Xq7dhRO8O zfeC|x3N7c00jm8y%?XxF3RzeV^I(7~td#GyVnB9B%r~_We+T{_Oe7B`pYea=`3e2O zqR-~?nQYHJ>^-iB01Ef-dHxFggUK3Pr8r!+pZUMJsQ-r3{+5?!pe>W_x4=IM@c!2N zwv>3$=B0u{=gXHsrU+HkfpN$_0Cj!}KpnpgQ9Q^DcK}=`zZc2B`e}cauN16?mMani z$769|mSS<5a90g79!|zla9S8m44{E@`!hR->Bi-g*?_(~SViDGgEcfi&rr4RX}0Ch zc)kZ9%poMWN`h`MNE+S--W&q z@=cZn8weQlw?1%V0{1z@kB#nomIw>!Klu8deg8oZAoVXN|483oa{ZF)A1UyUz`wHV zmt6lyfqw-4m0kaDa*6+ZR0bH}eIFlu5LRQ=>;)g9m%8pUHG+H8>%P}M)R-GN!F@6go|MG&OhHxt_S@%ci)DgxgZq+#F)o`n z9?Ly4eW4-cy-lUAhWyGmh44!;QNZ}jp`+;Zz%fSv~tMnMUU*a z>Wf)eh04UR8f8jNp?SZJN}Ew(_6l`YLcbYf65tO(Z2|teSfQ!8@ou9WgM*>}1rYkH AlK=n! diff --git a/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-right.png b/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/inhand-right.png deleted file mode 100644 index b82aed42c0c7f2d9a56ea40da0195e2543b8ad4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fAQ1FAN zi(^Q|oVT~Eg_;#4Tpv!BobG)3pSZ*6x=9Nc2gwJVR%s}BNJCWo+%cj`n1fAbAok6i>t>3iUE9=$-xxr60VsWh#Fk{DQ6Guw0}2`-b|{Fjt>s a{=;zXX;JL+;$D#789ZJ6T-G@yGywo%pLyy4 diff --git a/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/meta.json b/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/meta.json deleted file mode 100644 index ec329e4282c..00000000000 --- a/Resources/Textures/DeltaV/Objects/Weapons/Melee/e_sword.rsi/meta.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "inhand-left-blade", - "directions": 4, - "delays": [ - [ - 0.1, - 0.1 - ], - [ - 0.1, - 0.1 - ], - [ - 0.1, - 0.1 - ], - [ - 0.1, - 0.1 - ] - ] - }, - { - "name": "inhand-right-blade", - "directions": 4, - "delays": [ - [ - 0.1, - 0.1 - ], - [ - 0.1, - 0.1 - ], - [ - 0.1, - 0.1 - ], - [ - 0.1, - 0.1 - ] - ] - } - ] -} \ No newline at end of file From 5885e17727dbff435005245114532e85140c8f72 Mon Sep 17 00:00:00 2001 From: Remuchi <72476615+Remuchi@users.noreply.github.com> Date: Sun, 2 Jun 2024 04:17:39 +0700 Subject: [PATCH 230/235] Increased security PDAs' cartridge slots by 2 (#1266) tweak: increased security PDAs' cartridge slots by 2 --- .../DeltaV/Entities/Objects/Devices/pda.yml | 3 ++- .../Entities/Objects/Devices/pda.yml | 24 ++++++++++++------- .../Entities/Objects/Devices/pda.yml | 3 ++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/pda.yml index 109dd2e4c75..18ddf5e05fd 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/pda.yml @@ -20,7 +20,8 @@ map: [ "enum.PdaVisualLayers.IdLight" ] shader: "unshaded" visible: false - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 897b95b6848..b57992fe4f2 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -173,7 +173,8 @@ accentVColor: "#A32D26" - type: Icon state: pda-interncadet - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -431,7 +432,8 @@ borderColor: "#6f6192" - type: Icon state: pda-lawyer - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -645,7 +647,8 @@ accentHColor: "#447987" - type: Icon state: pda-hos - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -667,7 +670,8 @@ accentVColor: "#949137" - type: Icon state: pda-warden - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -688,7 +692,8 @@ borderColor: "#A32D26" - type: Icon state: pda-security - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -985,7 +990,8 @@ borderColor: "#774705" - type: Icon state: pda-detective - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -1008,7 +1014,8 @@ accentVColor: "#d7d7d0" - type: Icon state: pda-brigmedic - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -1100,7 +1107,8 @@ accentVColor: "#DFDFDF" - type: Icon state: pda-seniorofficer - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml index fcc6a66ff8c..c2b021f540f 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml @@ -32,7 +32,8 @@ accentVColor: "#DFDFDF" - type: Icon state: pda-security - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch, increased diskSpace by 2 to fit them + diskSpace: 7 preinstalled: - CrewManifestCartridge - NotekeeperCartridge From 89f5ea23733183d6775928e45a4fe63a9bfad53b Mon Sep 17 00:00:00 2001 From: DeltaV-Bot <135767721+DeltaV-Bot@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:17:59 +0000 Subject: [PATCH 231/235] Automatic changelog update --- Resources/Changelog/DeltaVChangelog.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index ff628c9bba2..ef95d4bb356 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -2542,3 +2542,11 @@ id: 373 time: '2024-06-01T21:16:28.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/1252 +- author: Remuchi + changes: + - message: Cartridge slots in all security PDAs were increased by 2 to compensate + for the addition of SecWatch and CrimeAssist. + type: Tweak + id: 374 + time: '2024-06-01T21:17:39.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/1266 From 708e17a3d362c80fce92014a52e1a98d48f8bff6 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:31:06 +0000 Subject: [PATCH 232/235] reverse engineering refactor and missing recipe test (#1230) * make jugg not atmos hardsuit reable lmao * re machine yaml refactor * use the enum name to localize re results * move a lot of code to shared and refactor * clientside rework * add test for missing recipes * untroll * make exped board recipe yml consistent with upstream * fix unearthed sneaky bugs + generic does nothing so remove * add mass media console board, remove roundstart boards from dynamic recipes * remove roundstart stuff, add rcd ammo to protolathe * dont dupe because of access electronics prototypes * fix final fails * final untroll * final untroll 2 --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: Null <56081759+NullWanderer@users.noreply.github.com> --- ...rseEngineeringMachineBoundUserInterface.cs | 55 +-- .../ReverseEngineeringMachineMenu.xaml | 14 +- .../ReverseEngineeringMachineMenu.xaml.cs | 111 ++--- .../ReverseEngineeringSystem.cs | 5 + .../Tests/DeltaV/ReverseEngineeringTest.cs | 66 +++ ...ctiveReverseEngineeringMachineComponent.cs | 19 - .../ReverseEngineeringMachineComponent.cs | 79 ---- .../ReverseEngineeringSystem.cs | 414 +++--------------- ...ctiveReverseEngineeringMachineComponent.cs | 18 + .../ReverseEngineeringComponent.cs | 36 +- .../ReverseEngineeringMachineComponent.cs | 90 ++++ .../ReverseEngineeringUi.cs | 53 +++ .../SharedReverseEngineering.cs | 106 ----- .../SharedReverseEngineeringSystem.cs | 244 +++++++++++ .../reverseengineering.ftl | 12 +- .../DeltaV/Recipes/Lathes/electronics.yml | 16 +- .../Clothing/OuterClothing/hardsuits.yml | 10 +- .../Circuitboards/Machine/production.yml | 71 +-- .../Devices/Electronics/atmos_alarms.yml | 6 - .../Objects/Devices/Electronics/disposal.yml | 3 - .../Objects/Devices/Electronics/door.yml | 3 - .../Objects/Devices/Electronics/firelock.yml | 3 - .../Objects/Devices/Electronics/intercom.yml | 3 - .../Devices/Electronics/power_electronics.yml | 3 - .../Entities/Objects/Misc/machine_parts.yml | 9 - .../Entities/Objects/Specific/chemistry.yml | 2 +- .../Entities/Objects/Tools/jetpacks.yml | 1 - .../Entities/Objects/Weapons/security.yml | 5 - .../Entities/Structures/Machines/lathe.yml | 12 +- .../Devices/CircuitBoards/production.yml | 3 - .../Machines/reverseEngineering.yml | 15 +- 31 files changed, 692 insertions(+), 795 deletions(-) create mode 100644 Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs create mode 100644 Content.IntegrationTests/Tests/DeltaV/ReverseEngineeringTest.cs delete mode 100644 Content.Server/Nyanotrasen/ReverseEngineering/ActiveReverseEngineeringMachineComponent.cs delete mode 100644 Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineComponent.cs create mode 100644 Content.Shared/Nyanotrasen/ReverseEngineering/ActiveReverseEngineeringMachineComponent.cs create mode 100644 Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineComponent.cs create mode 100644 Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringUi.cs delete mode 100644 Content.Shared/Nyanotrasen/ReverseEngineering/SharedReverseEngineering.cs create mode 100644 Content.Shared/Nyanotrasen/ReverseEngineering/SharedReverseEngineeringSystem.cs diff --git a/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineBoundUserInterface.cs b/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineBoundUserInterface.cs index 3053e5549a3..8986e6f9e22 100644 --- a/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineBoundUserInterface.cs +++ b/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineBoundUserInterface.cs @@ -1,13 +1,15 @@ using Content.Shared.ReverseEngineering; -using JetBrains.Annotations; using Robust.Client.GameObjects; +using Robust.Shared.Timing; namespace Content.Client.Nyanotrasen.ReverseEngineering; -[UsedImplicitly] public sealed class ReverseEngineeringMachineBoundUserInterface : BoundUserInterface { - private ReverseEngineeringMachineMenu? _revMenu; + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private ReverseEngineeringMachineMenu? _menu; public ReverseEngineeringMachineBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { @@ -17,34 +19,40 @@ protected override void Open() { base.Open(); - _revMenu = new ReverseEngineeringMachineMenu(); + if (_menu != null) + return; + + _menu = new ReverseEngineeringMachineMenu(Owner, _entMan, _timing); - _revMenu.OnClose += Close; - _revMenu.OpenCentered(); + _menu.OnClose += Close; + _menu.OpenCentered(); - _revMenu.OnScanButtonPressed += _ => + _menu.OnScanButtonPressed += () => { - SendMessage(new ReverseEngineeringMachineScanButtonPressedMessage()); + // every button flickering is bad so no prediction + SendMessage(new ReverseEngineeringScanMessage()); }; - _revMenu.OnSafetyButtonToggled += safetyArgs => + _menu.OnSafetyButtonToggled += () => { - SendMessage(new ReverseEngineeringMachineSafetyButtonToggledMessage(safetyArgs.Pressed)); + SendPredictedMessage(new ReverseEngineeringSafetyMessage()); }; - _revMenu.OnAutoScanButtonToggled += autoArgs => + _menu.OnAutoScanButtonToggled += () => { - SendMessage(new ReverseEngineeringMachineAutoScanButtonToggledMessage(autoArgs.Pressed)); + SendPredictedMessage(new ReverseEngineeringAutoScanMessage()); }; - _revMenu.OnStopButtonPressed += _ => + _menu.OnStopButtonPressed += () => { - SendMessage(new ReverseEngineeringMachineStopButtonPressedMessage()); + // see scan button + SendMessage(new ReverseEngineeringStopMessage()); }; - _revMenu.OnEjectButtonPressed += _ => + _menu.OnEjectButtonPressed += () => { - SendMessage(new ReverseEngineeringMachineEjectButtonPressedMessage()); + // doesn't sound nice when predicted + SendMessage(new ReverseEngineeringEjectMessage()); }; } @@ -52,14 +60,10 @@ protected override void UpdateState(BoundUserInterfaceState state) { base.UpdateState(state); - switch (state) - { - case ReverseEngineeringMachineScanUpdateState msg: - _revMenu?.SetButtonsDisabled(msg); - _revMenu?.UpdateInformationDisplay(msg); - _revMenu?.UpdateProbeTickProgressBar(msg); - break; - } + if (state is not ReverseEngineeringMachineState cast) + return; + + _menu?.UpdateState(cast); } protected override void Dispose(bool disposing) @@ -69,7 +73,8 @@ protected override void Dispose(bool disposing) if (!disposing) return; - _revMenu?.Dispose(); + _menu?.Close(); + _menu?.Dispose(); } } diff --git a/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineMenu.xaml b/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineMenu.xaml index bd8c62b440a..f2ebee5657b 100644 --- a/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineMenu.xaml +++ b/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineMenu.xaml @@ -30,21 +30,21 @@ Text="{Loc 'reverse-engineering-machine-eject-button'}" ToolTip="{Loc 'reverse-engineering-machine-eject-tooltip-info'}"> - + - - + + @@ -57,7 +57,7 @@ - + @@ -70,7 +70,7 @@ Scale="2 2"> - + diff --git a/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineMenu.xaml.cs b/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineMenu.xaml.cs index 1cd056d5fc5..77243798d76 100644 --- a/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineMenu.xaml.cs +++ b/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineMenu.xaml.cs @@ -5,40 +5,40 @@ using Robust.Client.State; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.Utility; +using Robust.Shared.Timing; namespace Content.Client.Nyanotrasen.ReverseEngineering; [GenerateTypedNameReferences] public sealed partial class ReverseEngineeringMachineMenu : FancyWindow { - [Dependency] private readonly IEntityManager _ent = default!; - public event Action? OnScanButtonPressed; - public event Action? OnSafetyButtonToggled; - public event Action? OnAutoScanButtonToggled; - public event Action? OnStopButtonPressed; - public event Action? OnEjectButtonPressed; - - public ReverseEngineeringMachineMenu() + private readonly IEntityManager _entMan; + private readonly IGameTiming _timing; + private readonly SharedReverseEngineeringSystem _revEng; + + private readonly Entity _owner; + + public event Action? OnScanButtonPressed; + public event Action? OnSafetyButtonToggled; + public event Action? OnAutoScanButtonToggled; + public event Action? OnStopButtonPressed; + public event Action? OnEjectButtonPressed; + + public ReverseEngineeringMachineMenu(EntityUid owner, IEntityManager entMan, IGameTiming timing) { RobustXamlLoader.Load(this); - IoCManager.InjectDependencies(this); - ScanButton.OnPressed += a => OnScanButtonPressed?.Invoke(a); - SafetyButton.OnToggled += a => OnSafetyButtonToggled?.Invoke(a); - AutoScanButton.OnToggled += a => OnAutoScanButtonToggled?.Invoke(a); - StopButton.OnPressed += a => OnStopButtonPressed?.Invoke(a); - EjectButton.OnPressed += a => OnEjectButtonPressed?.Invoke(a); - } + _entMan = entMan; + _timing = timing; + _revEng = entMan.System(); + _owner = (owner, entMan.GetComponent(owner)); - public void SetButtonsDisabled(ReverseEngineeringMachineScanUpdateState state) - { - ScanButton.Disabled = !state.CanScan; - StopButton.Disabled = !state.Scanning; - SafetyButton.Pressed = state.Safety; - AutoScanButton.Pressed = state.AutoProbe; - EjectButton.Disabled = (state.Target == null || state.Scanning); + ScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke(); + SafetyButton.OnToggled += _ => OnSafetyButtonToggled?.Invoke(); + AutoScanButton.OnToggled += _ => OnAutoScanButtonToggled?.Invoke(); + StopButton.OnPressed += _ => OnStopButtonPressed?.Invoke(); + EjectButton.OnPressed += _ => OnEjectButtonPressed?.Invoke(); } private void UpdateArtifactIcon(EntityUid? uid) @@ -48,61 +48,40 @@ private void UpdateArtifactIcon(EntityUid? uid) ItemDisplay.Visible = false; return; } - ItemDisplay.Visible = true; + ItemDisplay.Visible = true; ItemDisplay.SetEntity(uid); } - public void UpdateInformationDisplay(ReverseEngineeringMachineScanUpdateState state) + public void UpdateState(ReverseEngineeringMachineState state) { - var message = new FormattedMessage(); - _ent.TryGetEntity(state.Target, out var entityTarget); + Information.SetMessage(state.ScanMessage); + } - UpdateArtifactIcon(entityTarget); + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); - if (state.ScanReport == null) - { - if (!state.CanScan) //no item - message.AddMarkup(Loc.GetString("analysis-console-info-no-artifact")); - else if (state.Target == null) //ready to go - message.AddMarkup(Loc.GetString("analysis-console-info-ready")); - } - else - { - message.AddMessage(state.ScanReport); - } + var scanning = _revEng.IsActive(_owner); + var item = _revEng.GetItem(_owner); + ScanButton.Disabled = scanning || item == null; + StopButton.Disabled = !scanning; + SafetyButton.Pressed = _owner.Comp.SafetyOn; + AutoScanButton.Pressed = _owner.Comp.AutoScan; + EjectButton.Disabled = ScanButton.Disabled; - Information.SetMessage(message); - } + UpdateArtifactIcon(item); - public void UpdateProbeTickProgressBar(ReverseEngineeringMachineScanUpdateState state) - { - ProgressBar.Visible = state.Scanning; - ProgressLabel.Visible = state.Scanning; + ProgressBox.Visible = scanning; - if (!state.Scanning) + if (!_entMan.TryGetComponent(_owner, out var active) + || !_entMan.TryGetComponent(item, out var rev)) return; - if (state.Target != null) - { - TotalProgressLabel.Visible = true; - TotalProgressLabel.Text = Loc.GetString("reverse-engineering-total-progress-label"); - TotalProgressBar.Visible = true; - TotalProgressBar.Value = (float) state.TotalProgress / 100f; - } else - { - TotalProgressLabel.Visible = false; - TotalProgressBar.Visible = false; - } + TotalProgressBar.Value = (float) rev.Progress; - ProgressLabel.Text = Loc.GetString("analysis-console-progress-text", - ("seconds", (int) state.TotalTime.TotalSeconds - (int) state.TimeRemaining.TotalSeconds)); - ProgressBar.Value = (float) state.TimeRemaining.Divide(state.TotalTime); - } - - public override void Close() - { - base.Close(); + var remaining = Math.Max(active.NextProbe.TotalSeconds - _timing.CurTime.TotalSeconds, 0.0); + ProgressLabel.Text = Loc.GetString("analysis-console-progress-text", ("seconds", (int) remaining)); + ProgressBar.Value = 1f - (float) (remaining / _owner.Comp.AnalysisDuration.TotalSeconds); } } - diff --git a/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs b/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs new file mode 100644 index 00000000000..6a95bbe4281 --- /dev/null +++ b/Content.Client/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.ReverseEngineering; + +namespace Content.Client.ReverseEngineering; + +public sealed class ReverseEngineeringSystem : SharedReverseEngineeringSystem; diff --git a/Content.IntegrationTests/Tests/DeltaV/ReverseEngineeringTest.cs b/Content.IntegrationTests/Tests/DeltaV/ReverseEngineeringTest.cs new file mode 100644 index 00000000000..fd386851856 --- /dev/null +++ b/Content.IntegrationTests/Tests/DeltaV/ReverseEngineeringTest.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Content.Shared.Lathe; +using Content.Shared.ReverseEngineering; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.DeltaV; + +[TestFixture] +public sealed class ReverseEngineeringTest +{ + [Test] + public async Task AllReverseEngineeredPrintableTest() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var protoManager = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var lathes = new List(); + var reverseEngineered = new HashSet(); + foreach (var proto in protoManager.EnumeratePrototypes()) + { + if (proto.Abstract) + continue; + + if (pair.IsTestPrototype(proto)) + continue; + + if (proto.TryGetComponent(out var lathe)) + lathes.Add(lathe); + + if (!proto.TryGetComponent(out var rev)) + continue; + + foreach (var recipe in rev.Recipes) + { + reverseEngineered.Add(recipe); + } + } + + var latheRecipes = new HashSet(); + foreach (var lathe in lathes) + { + if (lathe.DynamicRecipes == null) + continue; + + foreach (var recipe in lathe.DynamicRecipes) + { + latheRecipes.Add(recipe); + } + } + + Assert.Multiple(() => + { + foreach (var recipe in reverseEngineered) + { + Assert.That(latheRecipes, Does.Contain(recipe), $"Reverse engineered recipe \"{recipe}\" cannot be unlocked on any lathe."); + } + }); + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.Server/Nyanotrasen/ReverseEngineering/ActiveReverseEngineeringMachineComponent.cs b/Content.Server/Nyanotrasen/ReverseEngineering/ActiveReverseEngineeringMachineComponent.cs deleted file mode 100644 index 76839956593..00000000000 --- a/Content.Server/Nyanotrasen/ReverseEngineering/ActiveReverseEngineeringMachineComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Robust.Shared.Serialization.TypeSerializers.Implementations; - -namespace Content.Server.ReverseEngineering; - -[RegisterComponent] -public sealed partial class ActiveReverseEngineeringMachineComponent : Component -{ - /// - /// When did the scanning start? - /// - [DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))] - public TimeSpan StartTime; - - /// - /// What is being scanned? - /// - [ViewVariables] - public EntityUid Item; -} diff --git a/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineComponent.cs b/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineComponent.cs deleted file mode 100644 index 5bf4c505921..00000000000 --- a/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineComponent.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Content.Shared.ReverseEngineering; -using Content.Shared.Construction.Prototypes; -using Robust.Shared.Prototypes; -using Robust.Shared.Utility; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Audio; - -namespace Content.Server.ReverseEngineering; - -/// -/// This machine can reverse engineer items and get a technology disk from them. -/// -[RegisterComponent] -public sealed partial class ReverseEngineeringMachineComponent : Component -{ - [DataField("diskPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string DiskPrototype = "TechnologyDisk"; - - [DataField("machinePartScanBonus", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string MachinePartScanBonus = "MatterBin"; // DeltaV Code: Change part checked for bonus to MatterBin as it is what is used in the crafting recipe - - /// - /// Added to the 3d6, scales off of scanner. - /// - public int ScanBonus = 1; - - - [DataField("machinePartDangerAversionScore", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string MachinePartDangerAversionScore = "Manipulator"; - - /// - /// If we rolled destruction, this is added to the roll and if it <= 9 it becomes - /// stagnation instead. - /// - public int DangerAversionScore = 1; - - /// - /// Whether the machine is going to receive the danger bonus. - /// - [DataField("dangerBonus")] - public int DangerBonus = 3; - - [DataField("failSound1")] - public SoundSpecifier FailSound1 { get; set; } = new SoundPathSpecifier("/Audio/Effects/spray.ogg"); - - [DataField("failSound2")] - public SoundSpecifier FailSound2 { get; set; } = new SoundPathSpecifier("/Audio/Effects/sparks4.ogg"); - - [DataField("successSound")] - public SoundSpecifier SuccessSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/microwave_done_beep.ogg"); - - [DataField("clickSound")] - public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); - public EntityUid? CurrentItem; - - /// - /// Malus from the item's difficulty. - /// - [ViewVariables] - public int CurrentItemDifficulty = 0; - - /// - /// Whether the safety is on. - /// - public bool SafetyOn = true; - - /// - /// Whether autoscan is on. - /// - public bool AutoScan = true; - - public int Progress = 0; - - public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(30); - - public FormattedMessage? CachedMessage; - - public ReverseEngineeringTickResult? LastResult; -} diff --git a/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs b/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs index a745a821264..3d12579391b 100644 --- a/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs +++ b/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs @@ -1,225 +1,70 @@ -using Content.Shared.Containers.ItemSlots; -using Content.Shared.ReverseEngineering; -using Content.Shared.Audio; -using Content.Shared.Examine; using Content.Server.Research.TechnologyDisk.Components; using Content.Server.UserInterface; using Content.Server.Power.Components; -using Content.Server.Construction; -using Content.Server.Popups; -using Content.Shared.UserInterface; -using Robust.Shared.Containers; +using Content.Shared.Popups; +using Content.Shared.Research.Prototypes; +using Content.Shared.ReverseEngineering; +using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Utility; -using Robust.Shared.Timing; -using Robust.Server.GameObjects; -using Robust.Shared.Audio.Systems; namespace Content.Server.ReverseEngineering; -public sealed class ReverseEngineeringSystem : EntitySystem +public sealed class ReverseEngineeringSystem : SharedReverseEngineeringSystem { - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly UserInterfaceSystem _ui = default!; - [Dependency] private readonly ItemSlotsSystem _slots = default!; - [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; - private const string TargetSlot = "target_slot"; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnEntInserted); - SubscribeLocalEvent(OnEntRemoved); - SubscribeLocalEvent(OnRefreshParts); - SubscribeLocalEvent(OnExamineParts); - - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnShutdown); - - SubscribeLocalEvent(OnScanButtonPressed); - SubscribeLocalEvent(OnSafetyButtonToggled); - SubscribeLocalEvent(OnAutoScanButtonToggled); - SubscribeLocalEvent(OnStopButtonPressed); - SubscribeLocalEvent(OnEjectButtonPressed); SubscribeLocalEvent(OnPowerChanged); - - SubscribeLocalEvent(OnExamined); - - SubscribeLocalEvent((e,c,_) => UpdateUserInterface(e,c)); - } public override void Update(float frameTime) { base.Update(frameTime); - foreach (var (active, rev) in EntityQuery()) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var active, out var rev)) { - UpdateUserInterface(rev.Owner, rev); + if (GetItem((uid, rev)) == null) + { + CancelProbe((uid, rev)); + continue; + } - if (_timing.CurTime - active.StartTime < rev.AnalysisDuration) + if (Timing.CurTime < active.NextProbe) continue; - FinishProbe(rev.Owner, rev, active); + ProbeItem((uid, rev, active)); } } - private void OnEntInserted(EntityUid uid, ReverseEngineeringMachineComponent component, EntInsertedIntoContainerMessage args) - { - if (args.Container.ID != TargetSlot || !TryComp(args.Entity, out var rev)) - return; - - _slots.SetLock(uid, TargetSlot, true); - component.CurrentItem = args.Entity; - component.CurrentItemDifficulty = rev.Difficulty; - component.CachedMessage = GetReverseEngineeringScanMessage(component); - UpdateUserInterface(uid, component); - - if (TryComp(uid, out var appearance)) - _appearanceSystem.SetData(uid, ReverseEngineeringVisuals.ChamberOpen, false, appearance); - } - - private void OnEntRemoved(EntityUid uid, ReverseEngineeringMachineComponent component, EntRemovedFromContainerMessage args) - { - if (args.Container.ID != TargetSlot) - return; - - component.CurrentItem = null; - component.CurrentItemDifficulty = 0; - component.Progress = 0; - CancelProbe(uid, component); - - if (TryComp(uid, out var appearance)) - _appearanceSystem.SetData(uid, ReverseEngineeringVisuals.ChamberOpen, true, appearance); - } - - private void OnRefreshParts(EntityUid uid, ReverseEngineeringMachineComponent component, RefreshPartsEvent args) - { - var bonusRating = args.PartRatings[component.MachinePartScanBonus]; - var aversionRating = args.PartRatings[component.MachinePartDangerAversionScore]; - - component.ScanBonus = (int) bonusRating; - component.DangerAversionScore = (int) aversionRating; - } - - private void OnExamineParts(EntityUid uid, ReverseEngineeringMachineComponent component, UpgradeExamineEvent args) - { - args.AddNumberUpgrade("reverse-engineering-machine-bonus-upgrade", component.ScanBonus - 1); - args.AddNumberUpgrade("reverse-engineering-machine-aversion-upgrade", component.DangerAversionScore - 1); - } - - private void OnStartup(EntityUid uid, ActiveReverseEngineeringMachineComponent component, ComponentStartup args) - { - _ambientSoundSystem.SetAmbience(uid, true); - } - - private void OnShutdown(EntityUid uid,ActiveReverseEngineeringMachineComponent component, ComponentShutdown args) - { - _ambientSoundSystem.SetAmbience(uid, false); - } - - private void OnScanButtonPressed(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineScanButtonPressedMessage args) - { - if (component.CurrentItem == null) - return; - - if (HasComp(uid)) - return; - - _audio.PlayPvs(component.ClickSound, uid); - - component.CachedMessage = null; - var activeComp = EnsureComp(uid); - activeComp.StartTime = _timing.CurTime; - activeComp.Item = component.CurrentItem.Value; - } - - private void OnSafetyButtonToggled(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineSafetyButtonToggledMessage args) - { - _audio.PlayPvs(component.ClickSound, uid); - - component.SafetyOn = args.Safety; - component.CachedMessage = null; - UpdateUserInterface(uid, component); - } - - private void OnAutoScanButtonToggled(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineAutoScanButtonToggledMessage args) - { - _audio.PlayPvs(component.ClickSound, uid); - - component.AutoScan = args.AutoScan; - } - - private void OnPowerChanged(EntityUid uid, ReverseEngineeringMachineComponent component, ref PowerChangedEvent args) + private void OnPowerChanged(Entity ent, ref PowerChangedEvent args) { if (!args.Powered) - CancelProbe(uid, component); - } - - private void OnExamined(EntityUid uid, ReverseEngineeringComponent component, ExaminedEvent args) - { - // TODO: Eventually this should probably get shoved into a contextual examine somewhere like health or machine upgrading. - // And this can be predicted I guess if difficulty becomes read only. - args.PushMarkup(Loc.GetString("reverse-engineering-examine", ("diff", component.Difficulty))); + CancelProbe(ent); } - private void OnStopButtonPressed(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineStopButtonPressedMessage args) + private ReverseEngineeringTickResult Roll(Entity ent) { - _audio.PlayPvs(component.ClickSound, uid); + var (uid, comp) = ent; + var roll = comp.ScanBonus; + for (var i = 0; i < 3; i++) + roll += _random.Next(1, 6); - CancelProbe(uid, component); - } + if (!comp.SafetyOn) + roll += comp.DangerBonus; - private void OnEjectButtonPressed(EntityUid uid, ReverseEngineeringMachineComponent component, ReverseEngineeringMachineEjectButtonPressedMessage args) - { - _audio.PlayPvs(component.ClickSound, uid); - - Eject(uid, component); - } - - private void UpdateUserInterface(EntityUid uid, ReverseEngineeringMachineComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (!_ui.HasUi(uid, ReverseEngineeringMachineUiKey.Key)) - return; + roll -= GetDifficulty(ent); - EntityUid? item = component.CurrentItem; - if (component.CachedMessage == null) - component.CachedMessage = GetReverseEngineeringScanMessage(component); - - var totalTime = TimeSpan.Zero; - var scanning = TryComp(uid, out var active); - var canScan = (item != null && !scanning); - var remaining = active != null ? _timing.CurTime - active.StartTime : TimeSpan.Zero; - EntityManager.TryGetNetEntity(item, out var netItem); - - var state = new ReverseEngineeringMachineScanUpdateState(netItem, canScan, component.CachedMessage, scanning, component.SafetyOn, component.AutoScan, component.Progress, remaining, component.AnalysisDuration); - - _ui.SetUiState(uid, ReverseEngineeringMachineUiKey.Key, state); - } - - private ReverseEngineeringTickResult Roll(ReverseEngineeringMachineComponent component, out int actualRoll) - { - int roll = (_random.Next(1, 6) + _random.Next(1, 6) + _random.Next(1, 6)); - - roll += component.ScanBonus; - - if (!component.SafetyOn) - roll += component.DangerBonus; - - roll -= component.CurrentItemDifficulty; - - actualRoll = roll; return roll switch { - <= 9 => ReverseEngineeringTickResult.Destruction, + // never let it be destroyed with safety on + <= 9 => comp.SafetyOn + ? ReverseEngineeringTickResult.Stagnation + : ReverseEngineeringTickResult.Destruction, <= 10 => ReverseEngineeringTickResult.Stagnation, <= 12 => ReverseEngineeringTickResult.SuccessMinor, <= 15 => ReverseEngineeringTickResult.SuccessAverage, @@ -228,190 +73,77 @@ private ReverseEngineeringTickResult Roll(ReverseEngineeringMachineComponent com }; } - private void FinishProbe(EntityUid uid, ReverseEngineeringMachineComponent? component = null, ActiveReverseEngineeringMachineComponent? active = null) + private void ProbeItem(Entity ent) { - if (!Resolve(uid, ref component, ref active)) + var (uid, comp, active) = ent; + if (GetItem((uid, comp)) is not {} item) return; - if (!TryComp(component.CurrentItem, out var rev)) + if (!TryComp(item, out var rev)) { - Logger.Error("We somehow scanned a " + component.CurrentItem + " for reverse engineering..."); + Log.Error($"We somehow scanned a {ToPrettyString(item):item} for reverse engineering..."); return; } - component.CachedMessage = null; - - var result = Roll(component, out var actualRoll); - + var result = Roll((uid, comp)); if (result == ReverseEngineeringTickResult.Destruction) { - if (!component.SafetyOn && actualRoll + component.DangerAversionScore < 9) - { - Del(component.CurrentItem.Value); - component.CurrentItem = null; - if (TryComp(uid, out var appearance)) - _appearanceSystem.SetData(uid, ReverseEngineeringVisuals.ChamberOpen, true, appearance); - _slots.SetLock(uid, TargetSlot, false); - _audio.PlayPvs(component.FailSound1, uid); - _audio.PlayPvs(component.FailSound2, uid); - _popupSystem.PopupEntity(Loc.GetString("reverse-engineering-popup-failure", ("machine", uid)), uid, Shared.Popups.PopupType.MediumCaution); - CancelProbe(uid, component); - } else - { - result = ReverseEngineeringTickResult.Stagnation; - } - } + _popup.PopupEntity(Loc.GetString("reverse-engineering-popup-failure", ("machine", uid)), uid, PopupType.MediumCaution); - component.LastResult = result; + Eject((uid, comp)); + Del(item); - int bonus = 0; + foreach (var sound in comp.FailSounds) + Audio.PlayPvs(sound, uid); - switch (result) - { - case ReverseEngineeringTickResult.Stagnation: - { - bonus += 1; - break; - } - case ReverseEngineeringTickResult.SuccessMinor: - { - bonus += 10; - break; - } - case ReverseEngineeringTickResult.SuccessAverage: - { - bonus += 25; - break; - } - case ReverseEngineeringTickResult.SuccessMajor: - { - bonus += 40; - break; - } - case ReverseEngineeringTickResult.InstantSuccess: - { - bonus += 100; - break; - } + UpdateUI((uid, comp)); + CancelProbe((uid, comp)); + return; } - component.Progress += bonus; - component.Progress = Math.Clamp(component.Progress, 0, 100); + comp.LastResult = result; + Dirty(uid, comp); - if (component.Progress < 100) - { - if (component.AutoScan) - { - active.StartTime = _timing.CurTime; - } - else - { - RemComp(uid); - } - } else + var bonus = result switch { - CreateDisk(uid, component.DiskPrototype, rev.Recipes); - _audio.PlayPvs(component.SuccessSound, uid); - if (rev.NewItem == null) - { - Eject(uid, component); - } else - { - _slots.SetLock(uid, TargetSlot, false); - Spawn(rev.NewItem, Transform(uid).Coordinates); - if (component.CurrentItem != null) - Del(component.CurrentItem.Value); - } - RemComp(uid); - } - - UpdateUserInterface(uid, component); - } - - private void CreateDisk(EntityUid uid, string diskPrototype, List? recipes) - { - var disk = Spawn(diskPrototype, Transform(uid).Coordinates); - - if (!TryComp(disk, out var diskComponent)) - return; + ReverseEngineeringTickResult.Stagnation => 1, + ReverseEngineeringTickResult.SuccessMinor => 10, + ReverseEngineeringTickResult.SuccessAverage => 25, + ReverseEngineeringTickResult.SuccessMajor => 40, + ReverseEngineeringTickResult.InstantSuccess => 100 + }; - diskComponent.Recipes = recipes; - } + rev.Progress = Math.Clamp(rev.Progress + bonus, 0, 100); + Dirty(item, rev); - private FormattedMessage? GetReverseEngineeringScanMessage(ReverseEngineeringMachineComponent component) - { - var msg = new FormattedMessage(); - - if (component.CurrentItem == null) + if (rev.Progress < 100) { - msg.AddMarkup(Loc.GetString("reverse-engineering-status-ready")); - return msg; + if (ent.Comp1.AutoScan) + StartProbing(ent); + else + CancelProbe((uid, comp)); } - - msg.AddMarkup(Loc.GetString("reverse-engineering-current-item", ("item", component.CurrentItem.Value))); - msg.PushNewline(); - msg.PushNewline(); - - var analysisScore = component.ScanBonus; - if (!component.SafetyOn) - analysisScore += component.DangerBonus; - - msg.AddMarkup(Loc.GetString("reverse-engineering-analysis-score", ("score", analysisScore))); - msg.PushNewline(); - msg.AddMarkup(Loc.GetString("reverse-engineering-item-difficulty", ("difficulty", component.CurrentItemDifficulty))); - msg.PushNewline(); - msg.AddMarkup(Loc.GetString("reverse-engineering-progress", ("progress", component.Progress))); - msg.PushNewline(); - - if (component.LastResult != null) + else { - string lastProbe = string.Empty; - - switch (component.LastResult) + CreateDisk((uid, comp), rev.Recipes); + Eject((uid, comp)); + Audio.PlayPvs(comp.SuccessSound, uid); + if (rev.NewItem is {} proto) { - case ReverseEngineeringTickResult.Destruction: - lastProbe = Loc.GetString("reverse-engineering-failure"); - break; - case ReverseEngineeringTickResult.Stagnation: - lastProbe = Loc.GetString("reverse-engineering-stagnation"); - break; - case ReverseEngineeringTickResult.SuccessMinor: - lastProbe = Loc.GetString("reverse-engineering-minor"); - break; - case ReverseEngineeringTickResult.SuccessAverage: - lastProbe = Loc.GetString("reverse-engineering-average"); - break; - case ReverseEngineeringTickResult.SuccessMajor: - lastProbe = Loc.GetString("reverse-engineering-major"); - break; - case ReverseEngineeringTickResult.InstantSuccess: - lastProbe = Loc.GetString("reverse-engineering-success"); - break; + Spawn(proto, Transform(uid).Coordinates); + Del(item); } - - msg.AddMarkup(Loc.GetString("reverse-engineering-last-attempt-result", ("result", lastProbe))); } - return msg; + UpdateUI((uid, comp)); } - private void Eject(EntityUid uid, ReverseEngineeringMachineComponent? component = null) + private void CreateDisk(Entity ent, List> recipes) { - if (!Resolve(uid, ref component)) - return; - - _slots.SetLock(uid, TargetSlot, false); - _slots.TryEject(uid, TargetSlot, null, out var item); - } - - private void CancelProbe(EntityUid uid, ReverseEngineeringMachineComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.CachedMessage = null; - component.LastResult = null; - RemComp(uid); - UpdateUserInterface(uid, component); + var uid = Spawn(ent.Comp.DiskPrototype, Transform(ent).Coordinates); + var disk = Comp(uid); + disk.Recipes = new(); + foreach (var id in recipes) + disk.Recipes.Add(id); } } diff --git a/Content.Shared/Nyanotrasen/ReverseEngineering/ActiveReverseEngineeringMachineComponent.cs b/Content.Shared/Nyanotrasen/ReverseEngineering/ActiveReverseEngineeringMachineComponent.cs new file mode 100644 index 00000000000..28e31fb24fe --- /dev/null +++ b/Content.Shared/Nyanotrasen/ReverseEngineering/ActiveReverseEngineeringMachineComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.ReverseEngineering; + +/// +/// Added to RE machines when they are actively scanning an item. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedReverseEngineeringSystem))] +[AutoGenerateComponentPause, AutoGenerateComponentState] +public sealed partial class ActiveReverseEngineeringMachineComponent : Component +{ + /// + /// When is the next probe roll due for? + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] + public TimeSpan NextProbe = TimeSpan.Zero; +} diff --git a/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringComponent.cs b/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringComponent.cs index e928180e0ac..92ec167c554 100644 --- a/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringComponent.cs +++ b/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringComponent.cs @@ -1,38 +1,46 @@ +using Content.Shared.Research.Prototypes; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Utility; namespace Content.Shared.ReverseEngineering; /// /// This item has some value in reverse engineering lathe recipes. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, Access(typeof(SharedReverseEngineeringSystem))] +[AutoGenerateComponentState] public sealed partial class ReverseEngineeringComponent : Component { /// /// The recipes that can be reverse engineered from this. - /// Does not neccesarily line up with lathe recipes. /// - [DataField("recipes")] - public List? Recipes; + [DataField(required: true)] + public List> Recipes = new(); /// /// Difficulty score 1-5 how hard this is to reverse engineer. + /// Rolls have this number taken away from them. /// - [DataField("difficulty")] + [DataField] public int Difficulty = 1; /// - /// Used to mark whether this entity intentionally lets its children use its recipe. - /// e.g. all jetpacks unlock the same jetpack recipe. Used for tests. + /// A new item that should be given back by the reverse engineering machine instead of this one. + /// E.g., NT aligned versions of syndicate items. /// - [DataField("generic")] - public bool Generic = false; + [DataField] + public EntProtoId? NewItem; /// - /// A new item that should be given back by the reverse engineering machine instead of this one. - /// E.g., NT aligned versions of syndicate items. + /// How far along this specific item has been reverse engineered. + /// Lets you resume if ejected, after completion it gets reset. + /// + [DataField, AutoNetworkedField] + public int Progress; + + /// + /// On client, the message shown in the scan information box. /// - [DataField("newItem", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? NewItem; + public FormattedMessage ScanMessage = new(); } diff --git a/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineComponent.cs b/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineComponent.cs new file mode 100644 index 00000000000..52df142ad25 --- /dev/null +++ b/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringMachineComponent.cs @@ -0,0 +1,90 @@ +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.ReverseEngineering; + +/// +/// This machine can reverse engineer items and get a technology disk from them. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedReverseEngineeringSystem))] +[AutoGenerateComponentState] +public sealed partial class ReverseEngineeringMachineComponent : Component +{ + /// + /// Name of the slot in ItemSlotsComponent that stores the target item. + /// + [DataField] + public string Slot = "target_slot"; + + /// + /// Tech disk prototype to spawn after completion. + /// Must have TechDiskComponent + /// + [DataField] + public EntProtoId DiskPrototype = "TechnologyDisk"; + + /// + /// Added to the 3d6, scales off of scanner. + /// + [DataField] + public int ScanBonus = 1; + + /// + /// If we rolled destruction, this is added to the roll and if it <= 9 it becomes + /// stagnation instead. + /// + [DataField] + public int DangerAversionScore = 1; + + /// + /// Whether the machine is going to receive the danger bonus. + /// + [DataField] + public int DangerBonus = 3; + + /// + /// Sounds simultaneously played when an item is destroyed. + /// + [DataField] + public List FailSounds = new() + { + new SoundPathSpecifier("/Audio/Effects/spray.ogg"), + new SoundPathSpecifier("/Audio/Effects/sparks4.ogg") + }; + + /// + /// Sound played when an item is successfully reverse engineered. + /// + [DataField] + public SoundSpecifier? SuccessSound = new SoundPathSpecifier("/Audio/Machines/microwave_done_beep.ogg"); + + [DataField] + public SoundSpecifier? ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); + + /// + /// Whether the safety is on. + /// + [DataField, AutoNetworkedField] + public bool SafetyOn = true; + + /// + /// Whether autoscan is on. + /// + [DataField, AutoNetworkedField] + public bool AutoScan = true; + + /// + /// How long to wait between analysis rolls. + /// + [DataField] + public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(30); + + /// + /// Last result to show in the ui + /// + [DataField, AutoNetworkedField] + public ReverseEngineeringTickResult? LastResult; +} diff --git a/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringUi.cs b/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringUi.cs new file mode 100644 index 00000000000..3a0a5e2ec10 --- /dev/null +++ b/Content.Shared/Nyanotrasen/ReverseEngineering/ReverseEngineeringUi.cs @@ -0,0 +1,53 @@ +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.ReverseEngineering; + +[Serializable, NetSerializable] +public enum ReverseEngineeringMachineUiKey : byte +{ + Key +} + +[Serializable, NetSerializable] +public sealed class ReverseEngineeringScanMessage : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class ReverseEngineeringSafetyMessage : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class ReverseEngineeringAutoScanMessage : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class ReverseEngineeringStopMessage : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class ReverseEngineeringEjectMessage : BoundUserInterfaceMessage; + +/// +/// State updated when the values it uses changes to avoid creating it every frame. +/// +[Serializable, NetSerializable] +public sealed class ReverseEngineeringMachineState : BoundUserInterfaceState +{ + public readonly FormattedMessage ScanMessage; + + public ReverseEngineeringMachineState(FormattedMessage scanMessage) + { + ScanMessage = scanMessage; + } +} + +/// +// 3d6 + scanner bonus + danger bonus - item difficulty +/// +[Serializable, NetSerializable] +public enum ReverseEngineeringTickResult : byte +{ + Destruction, // 9 (only destroys if danger bonus is active, less the aversion bonus) + Stagnation, // 10 + SuccessMinor, // 11-12 + SuccessAverage, // 13-15 + SuccessMajor, // 16-17 + InstantSuccess // 18 +} diff --git a/Content.Shared/Nyanotrasen/ReverseEngineering/SharedReverseEngineering.cs b/Content.Shared/Nyanotrasen/ReverseEngineering/SharedReverseEngineering.cs deleted file mode 100644 index 9d73c57692d..00000000000 --- a/Content.Shared/Nyanotrasen/ReverseEngineering/SharedReverseEngineering.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Robust.Shared.Serialization; -using Robust.Shared.Utility; - -namespace Content.Shared.ReverseEngineering; - -[Serializable, NetSerializable] -public enum ReverseEngineeringMachineUiKey : byte -{ - Key -} - -[Serializable, NetSerializable] -public sealed class ReverseEngineeringMachineScanButtonPressedMessage : BoundUserInterfaceMessage -{ -} - -[Serializable, NetSerializable] -public sealed class ReverseEngineeringMachineSafetyButtonToggledMessage : BoundUserInterfaceMessage -{ - public bool Safety; - - public ReverseEngineeringMachineSafetyButtonToggledMessage(bool safety) - { - Safety = safety; - } -} - -[Serializable, NetSerializable] -public sealed class ReverseEngineeringMachineAutoScanButtonToggledMessage : BoundUserInterfaceMessage -{ - public bool AutoScan; - - public ReverseEngineeringMachineAutoScanButtonToggledMessage(bool autoScan) - { - AutoScan = autoScan; - } -} - -[Serializable, NetSerializable] -public sealed class ReverseEngineeringMachineStopButtonPressedMessage : BoundUserInterfaceMessage -{ -} - -[Serializable, NetSerializable] -public sealed class ReverseEngineeringMachineEjectButtonPressedMessage : BoundUserInterfaceMessage -{ -} - - -[Serializable, NetSerializable] -public sealed class ReverseEngineeringMachineScanUpdateState : BoundUserInterfaceState -{ - public NetEntity? Target; - - public bool CanScan; - - public FormattedMessage? ScanReport; - - public bool Scanning; - - public bool Safety; - - public bool AutoProbe; - - public int TotalProgress; - - public TimeSpan TimeRemaining; - - public TimeSpan TotalTime; - - public ReverseEngineeringMachineScanUpdateState(NetEntity? target, bool canScan, - FormattedMessage? scanReport, bool scanning, bool safety, bool autoProbe, int totalProgress, TimeSpan timeRemaining, TimeSpan totalTime) - { - Target = target; - CanScan = canScan; - - ScanReport = scanReport; - - Scanning = scanning; - Safety = safety; - AutoProbe = autoProbe; - TotalProgress = totalProgress; - TimeRemaining = timeRemaining; - TotalTime = totalTime; - } -} - -/// -// 3d6 + scanner bonus + danger bonus - item difficulty -/// -[Serializable, NetSerializable] -public enum ReverseEngineeringTickResult : byte -{ - Destruction, // 9 (only destroys if danger bonus is active, effectively 8 since aversion bonus is always 1) - Stagnation, // 10 - SuccessMinor, // 11-12 - SuccessAverage, // 13-15 - SuccessMajor, // 16-17 - InstantSuccess // 18 -} - -[Serializable, NetSerializable] -public enum ReverseEngineeringVisuals : byte -{ - ChamberOpen, -} diff --git a/Content.Shared/Nyanotrasen/ReverseEngineering/SharedReverseEngineeringSystem.cs b/Content.Shared/Nyanotrasen/ReverseEngineering/SharedReverseEngineeringSystem.cs new file mode 100644 index 00000000000..5ca61fefc03 --- /dev/null +++ b/Content.Shared/Nyanotrasen/ReverseEngineering/SharedReverseEngineeringSystem.cs @@ -0,0 +1,244 @@ +using Content.Shared.Audio; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Examine; +using Content.Shared.Nutrition.Components; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared.ReverseEngineering; + +public abstract class SharedReverseEngineeringSystem : EntitySystem +{ + [Dependency] protected readonly IGameTiming Timing = default!; + [Dependency] private readonly ItemSlotsSystem _slots = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] protected readonly SharedAudioSystem Audio = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnItemExamined); + + SubscribeLocalEvent(OnEntInserted); + SubscribeLocalEvent(OnEntRemoved); + Subs.BuiEvents(ReverseEngineeringMachineUiKey.Key, subs => + { + subs.Event(OnOpened); + subs.Event(OnScanPressed); + subs.Event(OnSafetyToggled); + subs.Event(OnAutoScanToggled); + subs.Event(OnStopPressed); + subs.Event(OnEjectPressed); + }); + + SubscribeLocalEvent(OnActiveStartup); + SubscribeLocalEvent(OnActiveShutdown); + } + + /// + /// Returns true if the machine is actively reverse engineering something. + /// + public bool IsActive(EntityUid uid) + { + return HasComp(uid); + } + + /// + /// Gets the item currently in the machine's target slot. + /// + public EntityUid? GetItem(Entity ent) + { + if (!_slots.TryGetSlot(ent, ent.Comp.Slot, out var slot)) + return null; + + return slot.Item; + } + + /// + /// Gets the difficulty of the current item, or 0 if there is none. + /// + public int GetDifficulty(Entity ent) + { + if (GetItem(ent) is not {} item || !TryComp(item, out var rev)) + return 0; + + return rev.Difficulty; + } + + private void OnItemExamined(Entity ent, ref ExaminedEvent args) + { + // TODO: Eventually this should probably get shoved into a contextual examine somewhere like health or machine upgrading. + args.PushMarkup(Loc.GetString("reverse-engineering-examine", ("diff", ent.Comp.Difficulty))); + } + + private void OnEntInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + var (uid, comp) = ent; + if (args.Container.ID != comp.Slot) + return; + + _slots.SetLock(uid, comp.Slot, true); + UpdateUI(ent); + + _appearance.SetData(uid, OpenableVisuals.Opened, false); + } + + private void OnEntRemoved(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.Slot) + return; + + CancelProbe(ent); + + _appearance.SetData(ent, OpenableVisuals.Opened, true); + } + + private void OnActiveStartup(Entity ent, ref ComponentStartup args) + { + _ambientSound.SetAmbience(ent, true); + } + + private void OnActiveShutdown(Entity ent, ref ComponentShutdown args) + { + _ambientSound.SetAmbience(ent, false); + } + + #region UI + + protected void UpdateUI(Entity ent) + { + var scanMessage = GetScanMessage(ent); + var state = new ReverseEngineeringMachineState(scanMessage); + _ui.SetUiState(ent.Owner, ReverseEngineeringMachineUiKey.Key, state); + } + + private void OnOpened(Entity ent, ref BoundUIOpenedEvent args) + { + UpdateUI(ent); + } + + private void OnScanPressed(Entity ent, ref ReverseEngineeringScanMessage args) + { + if (!Timing.IsFirstTimePredicted) + return; + + if (IsActive(ent) || GetItem(ent) == null) + return; + + var (uid, comp) = ent; + Audio.PlayPredicted(comp.ClickSound, uid, args.Actor); + + var active = EnsureComp(uid); + StartProbing((uid, comp, active)); + } + + private void OnSafetyToggled(Entity ent, ref ReverseEngineeringSafetyMessage args) + { + if (!Timing.IsFirstTimePredicted) + return; + + var (uid, comp) = ent; + Audio.PlayPredicted(comp.ClickSound, uid, args.Actor); + + comp.SafetyOn = !comp.SafetyOn; + Dirty(uid, comp); + + UpdateUI(ent); + } + + private void OnAutoScanToggled(Entity ent, ref ReverseEngineeringAutoScanMessage args) + { + if (!Timing.IsFirstTimePredicted) + return; + + var (uid, comp) = ent; + Audio.PlayPredicted(comp.ClickSound, uid, args.Actor); + + comp.AutoScan = !comp.AutoScan; + Dirty(uid, comp); + + UpdateUI(ent); + } + + private void OnStopPressed(Entity ent, ref ReverseEngineeringStopMessage args) + { + if (!Timing.IsFirstTimePredicted) + return; + + Audio.PlayPredicted(ent.Comp.ClickSound, ent, args.Actor); + + CancelProbe(ent); + } + + private void OnEjectPressed(Entity ent, ref ReverseEngineeringEjectMessage args) + { + if (!Timing.IsFirstTimePredicted) + return; + + Audio.PlayPredicted(ent.Comp.ClickSound, ent, args.Actor); + + Eject(ent); + } + + #endregion + + protected void StartProbing(Entity ent) + { + ent.Comp2.NextProbe = Timing.CurTime + ent.Comp1.AnalysisDuration; + Dirty(ent, ent.Comp2); + } + + protected void CancelProbe(Entity ent) + { + ent.Comp.LastResult = null; + Dirty(ent, ent.Comp); + RemComp(ent); + + UpdateUI(ent); + } + + protected void Eject(Entity ent) + { + if (!TryComp(ent, out var slots) || !_slots.TryGetSlot(ent, ent.Comp.Slot, out var slot, slots)) + return; + + _slots.SetLock(ent, slot, false, slots); + _slots.TryEject(ent, slot, user: null, out _); + } + + private FormattedMessage GetScanMessage(Entity ent) + { + var msg = new FormattedMessage(); + if (GetItem(ent) is not {} item || !TryComp(item, out var rev)) + { + msg.AddMarkup(Loc.GetString("reverse-engineering-status-ready")); + return msg; + } + + var comp = ent.Comp; + msg.PushMarkup(Loc.GetString("reverse-engineering-current-item", ("item", item))); + msg.PushNewline(); + + var analysisScore = comp.ScanBonus; + if (!comp.SafetyOn) + analysisScore += comp.DangerBonus; + + msg.PushMarkup(Loc.GetString("reverse-engineering-analysis-score", ("score", analysisScore))); + msg.PushMarkup(Loc.GetString("reverse-engineering-item-difficulty", ("difficulty", rev.Difficulty))); + msg.PushMarkup(Loc.GetString("reverse-engineering-progress", ("progress", rev.Progress))); + + if (comp.LastResult is {} result) + { + var lastProbe = Loc.GetString($"reverse-engineering-result-{result}"); + + msg.AddMarkup(Loc.GetString("reverse-engineering-last-attempt-result", ("result", lastProbe))); + } + + return msg; + } +} diff --git a/Resources/Locale/en-US/nyanotrasen/reverse-engineering/reverseengineering.ftl b/Resources/Locale/en-US/nyanotrasen/reverse-engineering/reverseengineering.ftl index 29584b3d806..3dd1bd8fc7b 100644 --- a/Resources/Locale/en-US/nyanotrasen/reverse-engineering/reverseengineering.ftl +++ b/Resources/Locale/en-US/nyanotrasen/reverse-engineering/reverseengineering.ftl @@ -20,12 +20,12 @@ reverse-engineering-last-attempt-result = Last probe result: {$result} reverse-engineering-total-progress-label = Total -reverse-engineering-failure = CRITICAL FAILURE -reverse-engineering-stagnation = Minimal Progress -reverse-engineering-minor = Minor progress -reverse-engineering-average = Acceptable progress -reverse-engineering-major = Major progress -reverse-engineering-success = Breakthrough +reverse-engineering-result-Destruction = CRITICAL FAILURE +reverse-engineering-result-Stagnation = Minimal Progress +reverse-engineering-result-SuccessMinor = Minor progress +reverse-engineering-result-SuccessAverage = Acceptable progress +reverse-engineering-result-SuccessMajor = Major progress +reverse-engineering-result-InstantSuccess = Breakthrough reverse-engineering-machine-bonus-upgrade = Analysis power reverse-engineering-machine-aversion-upgrade = Destruction aversion bonus diff --git a/Resources/Prototypes/DeltaV/Recipes/Lathes/electronics.yml b/Resources/Prototypes/DeltaV/Recipes/Lathes/electronics.yml index 82e0294e828..3e16bb0f437 100644 --- a/Resources/Prototypes/DeltaV/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/DeltaV/Recipes/Lathes/electronics.yml @@ -1,8 +1,18 @@ - type: latheRecipe id: SalvageExpeditionsComputerCircuitboard result: SalvageExpeditionsComputerCircuitboard + category: Circuitry completetime: 4 materials: - Steel: 100 - Glass: 900 - Silver: 100 + Steel: 100 + Glass: 500 + Silver: 100 + +- type: latheRecipe + id: ComputerMassMediaCircuitboard + result: ComputerMassMediaCircuitboard + category: Circuitry + completetime: 4 + materials: + Steel: 100 + Glass: 500 diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index 1b667f66112..3403f25ac81 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -60,11 +60,6 @@ - type: HeldSpeedModifier - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitAtmos - - type: ReverseEngineering # Nyano - difficulty: 5 - newItem: ClothingOuterHardsuitJuggernautReverseEngineered - recipes: - - ClothingOuterHardsuitJuggernautReverseEngineered #Engineering Hardsuit - type: entity @@ -635,6 +630,11 @@ - type: HeldSpeedModifier - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitCybersun + - type: ReverseEngineering # Nyano + difficulty: 5 + newItem: ClothingOuterHardsuitJuggernautReverseEngineered + recipes: + - ClothingOuterHardsuitJuggernautReverseEngineered #Wizard Hardsuit - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 51b460d0979..34671636f92 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -11,9 +11,6 @@ Manipulator: 1 materialRequirements: Glass: 1 - - type: ReverseEngineering # delta - recipes: - - AutolatheMachineCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -53,9 +50,6 @@ Amount: 2 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # delta - recipes: - - ProtolatheMachineCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -169,9 +163,6 @@ Amount: 2 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - recipes: - - CircuitImprinterMachineCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -241,10 +232,6 @@ requirements: MatterBin: 1 Manipulator: 2 - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - UniformPrinterMachineCircuitboard - type: entity id: VaccinatorMachineCircuitboard @@ -520,9 +507,6 @@ MatterBin: 1 materialRequirements: Glass: 1 - - type: ReverseEngineering # delta - recipes: - - CondenserMachineCircuitBoard - type: entity id: PortableScrubberMachineCircuitBoard @@ -560,9 +544,6 @@ Capacitor: 2 materialRequirements: Cable: 5 - - type: ReverseEngineering # delta - recipes: - - SpaceHeaterMachineCircuitBoard - type: entity id: CloningPodMachineCircuitboard @@ -655,9 +636,6 @@ Amount: 2 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - recipes: - - ChemMasterMachineCircuitboard - type: entity id: ChemDispenserMachineCircuitboard @@ -679,9 +657,6 @@ Amount: 2 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - recipes: - - ChemDispenserMachineCircuitboard - type: entity id: BiomassReclaimerMachineCircuitboard @@ -768,9 +743,6 @@ PowerCell: 4 materialRequirements: CableHV: 10 - - type: ReverseEngineering # Nyano - recipes: - - SMESMachineCircuitboard - type: entity id: CellRechargerCircuitboard @@ -903,9 +875,6 @@ materialRequirements: CableMV: 5 CableHV: 5 - - type: ReverseEngineering # Nyano - recipes: - - SubstationMachineCircuitboard - type: PhysicalComposition materialComposition: Glass: 200 @@ -1075,10 +1044,6 @@ Amount: 1 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - ReagentGrinderMachineCircuitboard - type: entity id: HotplateMachineCircuitboard @@ -1092,10 +1057,6 @@ Capacitor: 2 materialRequirements: Glass: 1 - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - HotplateMachineCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -1110,10 +1071,6 @@ materialRequirements: Glass: 2 Cable: 5 - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - ElectricGrillMachineCircuitboard - type: entity id: StasisBedMachineCircuitboard @@ -1149,9 +1106,6 @@ Capacitor: 2 materialRequirements: Cable: 1 - - type: ReverseEngineering # delta - recipes: - - ElectrolysisUnitMachineCircuitboard - type: entity id: CentrifugeMachineCircuitboard @@ -1167,9 +1121,6 @@ Manipulator: 1 materialRequirements: Steel: 1 - - type: ReverseEngineering # delta - recipes: - - CentrifugeMachineCircuitboard - type: entity id: MaterialReclaimerMachineCircuitboard @@ -1185,10 +1136,6 @@ materialRequirements: Steel: 5 Plastic: 5 - - type: ReverseEngineering # delta - difficulty: 2 - recipes: - - MaterialReclaimerMachineCircuitboard - type: entity id: OreProcessorMachineCircuitboard @@ -1204,9 +1151,6 @@ Manipulator: 3 materialRequirements: Glass: 1 - - type: ReverseEngineering # Nyano - recipes: - - OreProcessorMachineCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -1256,9 +1200,6 @@ materialRequirements: Glass: 2 Cable: 2 - - type: ReverseEngineering # Nyano - recipes: - - MicrowaveMachineCircuitboard - type: Tag tags: - MicrowaveMachineBoard @@ -1315,10 +1256,6 @@ materialRequirements: CableHV: 5 Glass: 2 - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - EmitterCircuitboard - type: entity id: SurveillanceCameraRouterCircuitboard @@ -1419,9 +1356,6 @@ Amount: 1 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - recipes: - - BoozeDispenserMachineCircuitboard - type: entity id: CargoTelepadMachineCircuitboard @@ -1460,9 +1394,6 @@ Amount: 1 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - recipes: - - SodaDispenserMachineCircuitboard - type: entity id: TelecomServerCircuitboard @@ -1652,4 +1583,4 @@ WoodPlank: 5 Steel: 2 Glass: 5 - Cable: 2 \ No newline at end of file + Cable: 2 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/atmos_alarms.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/atmos_alarms.yml index 730b591c0ac..838ec637d33 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/atmos_alarms.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/atmos_alarms.yml @@ -10,9 +10,6 @@ - type: Tag tags: - AirAlarmElectronics - - type: ReverseEngineering # Nyano - recipes: - - AirAlarmElectronics - type: StaticPrice price: 61 @@ -30,8 +27,5 @@ - type: Tag tags: - FireAlarmElectronics - - type: ReverseEngineering # Nyano - recipes: - - FireAlarmElectronics - type: StaticPrice price: 61 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/disposal.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/disposal.yml index 009736707d8..7c2f2a6fda0 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/disposal.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/disposal.yml @@ -10,8 +10,5 @@ - type: Tag tags: - MailingUnitElectronics - - type: ReverseEngineering # Nyano - recipes: - - MailingUnitElectronics - type: StaticPrice price: 55 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml index 2238d91ad3d..a832bea7414 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml @@ -10,9 +10,6 @@ - type: Tag tags: - DoorElectronics - - type: ReverseEngineering # Nyano - recipes: - - DoorElectronics - type: DoorElectronics - type: StaticPrice price: 55 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/firelock.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/firelock.yml index 679c3e4ddac..c7fa8f9ecd5 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/firelock.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/firelock.yml @@ -12,8 +12,5 @@ - type: Tag tags: - FirelockElectronics - - type: ReverseEngineering # Nyano - recipes: - - FirelockElectronics - type: StaticPrice price: 61 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml index 2196c9d3655..3446a3ba4f9 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml @@ -10,8 +10,5 @@ - type: Tag tags: - IntercomElectronics - - type: ReverseEngineering # Nyano - recipes: - - IntercomElectronics - type: StaticPrice price: 55 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml index 24da64964c8..d3293bbfd22 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml @@ -9,9 +9,6 @@ - type: Sprite sprite: Objects/Misc/module.rsi state: charger_APC - - type: ReverseEngineering # Nyano - recipes: - - APCElectronics - type: PhysicalComposition materialComposition: Glass: 50 diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 592f8be693b..62a63c80c31 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -22,9 +22,6 @@ - type: MachinePart part: Capacitor rating: 1 - - type: ReverseEngineering # Nyano - recipes: - - CapacitorStockPart - type: Tag tags: - CapacitorStockPart @@ -41,9 +38,6 @@ - type: MachinePart part: Manipulator rating: 1 - - type: ReverseEngineering # Nyano - recipes: - - MicroManipulatorStockPart - type: entity id: MatterBinStockPart @@ -57,6 +51,3 @@ - type: MachinePart part: MatterBin rating: 1 - - type: ReverseEngineering # Nyano - recipes: - - MatterBinStockPart diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 3fe48e9d25c..801be7d5969 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -241,7 +241,7 @@ - type: ReverseEngineering # delta difficulty: 2 recipes: - - cryostasisBeaker + - CryostasisBeaker - type: entity name: bluespace beaker diff --git a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml index 2f39e254fa3..7decafbd09f 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml @@ -60,7 +60,6 @@ # - type: DynamicPrice # price: 100 - type: ReverseEngineering # Nyano - generic: true difficulty: 3 recipes: - JetpackBlue diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index 4b3bf011032..ceb81376752 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -150,11 +150,6 @@ - type: Item size: Small sprite: Objects/Weapons/Melee/flash.rsi -# - type: DynamicPrice -# price: 40 - - type: ReverseEngineering # Nyano - recipes: - - Flash - type: StaticPrice price: 40 - type: Appearance diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index c9eb87fd277..ec10b4031ec 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -368,6 +368,7 @@ - FauxTileAstroSnow - OreBagOfHolding - DeviceQuantumSpinInverter + - RCDAmmo # DeltaV - type: EmagLatheRecipes emagDynamicRecipes: - ExplosivePayload @@ -509,16 +510,13 @@ # Begin Nyano additions - ReverseEngineeringMachineCircuitboard - CrewMonitoringComputerCircuitboard - - DoorElectronics - - FireAlarmElectronics - - FirelockElectronics - - IntercomElectronics - - MailingUnitElectronics - SalvageMagnetMachineCircuitboard - - StationMapElectronics - MetempsychoticMachineCircuitboard # End Nyano additions - - SalvageExpeditionsComputerCircuitboard # DeltaV + # Begin DeltaV additions + - SalvageExpeditionsComputerCircuitboard + - ComputerMassMediaCircuitboard + # End DeltaV additions - type: MaterialStorage whitelist: tags: diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml index 87be7c838f3..a3dfb68a9f1 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml @@ -61,9 +61,6 @@ Steel: 4 Glass: 2 Cable: 4 - - type: ReverseEngineering - recipes: - - DeepFryerMachineCircuitboard - type: entity id: MailTeleporterMachineCircuitboard diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/reverseEngineering.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/reverseEngineering.yml index 3c3a9908c65..1520951e56f 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/reverseEngineering.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/reverseEngineering.yml @@ -1,5 +1,5 @@ - type: entity - parent: BaseMachinePowered + parent: [BaseMachinePowered, ConstructibleMachine] id: ReverseEngineeringMachine name: reverse engineering machine description: Destructively analyses pieces of technology in the hopes that we can retrieve information from them. @@ -10,7 +10,7 @@ snapCardinals: true layers: - state: open - map: ["open"] + map: ["enum.OpenableVisuals.Layer"] - state: unlit shader: unshaded map: ["enum.PowerDeviceVisualLayers.Powered"] @@ -33,8 +33,8 @@ graph: Machine node: machine containers: - - machine_board - - machine_parts + - machine_board + - machine_parts - type: ContainerContainer containers: machine_board: !type:Container @@ -42,7 +42,7 @@ target_slot: !type:ContainerSlot - type: EmptyOnMachineDeconstruct containers: - - target_slot + - target_slot - type: Machine board: ReverseEngineeringMachineCircuitboard - type: Destructible @@ -51,6 +51,7 @@ !type:DamageTrigger damage: 100 behaviors: + - !type:EmptyAllContainersBehaviour - !type:ChangeConstructionNodeBehavior node: machineFrame - !type:DoActsBehavior @@ -72,7 +73,7 @@ enum.PowerDeviceVisualLayers.Powered: True: { visible: true } False: { visible: false } - enum.ReverseEngineeringVisuals.ChamberOpen: - open: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: True: { state: open } False: { state: closed } From 2b4a8b42add42a6d7e1eb2c7c817e1c383b3868f Mon Sep 17 00:00:00 2001 From: DeltaV-Bot <135767721+DeltaV-Bot@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:31:25 +0000 Subject: [PATCH 233/235] Automatic changelog update --- Resources/Changelog/DeltaVChangelog.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index ef95d4bb356..26b8131c401 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -2550,3 +2550,11 @@ id: 374 time: '2024-06-01T21:17:39.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/1266 +- author: deltanedas + changes: + - message: Fixed a lot of reverse engineered recipes being broken or being unlocked + roundstart anyway. + type: Tweak + id: 375 + time: '2024-06-01T21:31:06.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/1230 From 9c049d7ae3ce5b276136b554f8d82e3b3426a6dc Mon Sep 17 00:00:00 2001 From: Haltell Date: Sun, 2 Jun 2024 07:02:32 +0930 Subject: [PATCH 234/235] Add More Loadout Options (#1269) * Add More Loadout Options Added new loadout options for Captain, Chemist, Passenger, and Security. Added Filled Jack Boots for security loadouts. * Fixed Commenting Properly Commented Out MedicalGloves/Shoes instead of deleting them. * More Security Options Added Blue Jumpsuit to Sec Officer Added Red Beret to Sec Officer and Cadet Added Durathread Vest to Officers Added Red Scarf to all of Security * Logistics Officer Beret Fixed LogiOfficerBeret not being in loadout_groups.yml * Command Loadout Options Added Glove Options for Captain and HoP Added More Uniform Options for HoP Added More Outer Clothing Options for HoP Added Shoe Options for Captain and HoP * Winter Options Scarfs added to most roles, with the following exceptions: - Assistant Roles - Captain - Chef - Mantis - Prison Guard - Station Specific Jobs (except Corpsman) Stethoscope added as option for Medical Doctors, CMOs, and Paramedics Salvager Cape added and Miner Winter Boots replaced Logi Winter Boots as option for salvagers Heads of Staff Winter Boots Added for Respective Heads Paramedic Winter Boots added for Paramedics Removed Requirement for Engineers to wear a hat Added hats option for Atmospheric Technicians Added Roboticist Coat Option for Scientists * Musician Eyewear Fixed musician's customizable eyewear by removing their default sunglasses. Musician no longer able to buy expensive sunglasses. Cheap sunglasses available through glasses loadout group with 2 hours of musician time. * Beer Goggles Changed Bartender's Glasses Options to include beer goggles. * Update glasses.yml Add blankspace to the end of glasses.yml Fix the ad hoc cheap sunglasses timer * Update glasses.yml Fix sunglasses timer * Clothing Options Added extra clothing options for Chemist and Musician. Formal attire and red dress respectively. --------- Co-authored-by: Null <56081759+NullWanderer@users.noreply.github.com> --- .../deltav/preferences/loadout-groups.ftl | 48 ++++ .../DeltaV/Catalog/Fills/Items/Belts/misc.yml | 9 + .../DeltaV/Loadouts/Miscellaneous/scarfs.yml | 90 +++++++ .../DeltaV/Loadouts/loadout_groups.yml | 31 +++ .../DeltaV/Loadouts/role_loadouts.yml | 2 + .../Loadouts/Jobs/Cargo/quartermaster.yml | 10 + .../Jobs/Cargo/salvage_specialist.yml | 19 ++ .../Loadouts/Jobs/Civilian/bartender.yml | 10 + .../Loadouts/Jobs/Civilian/musician.yml | 19 ++ .../Loadouts/Jobs/Civilian/passenger.yml | 78 ++++++ .../Loadouts/Jobs/Command/captain.yml | 38 +++ .../Jobs/Command/head_of_personnel.yml | 83 +++++++ .../Jobs/Engineering/chief_engineer.yml | 9 + .../Loadouts/Jobs/Medical/chemist.yml | 59 +++++ .../Jobs/Medical/chief_medical_officer.yml | 10 + .../Loadouts/Jobs/Medical/medical_doctor.yml | 10 + .../Loadouts/Jobs/Medical/paramedic.yml | 9 + .../Jobs/Science/research_director.yml | 10 + .../Loadouts/Jobs/Science/scientist.yml | 9 + .../Jobs/Security/head_of_security.yml | 10 + .../Jobs/Security/security_officer.yml | 36 +++ .../Loadouts/Miscellaneous/glasses.yml | 22 ++ .../Prototypes/Loadouts/loadout_groups.yml | 232 +++++++++++++++++- .../Prototypes/Loadouts/role_loadouts.yml | 38 ++- .../Nyanotrasen/Loadouts/role_loadouts.yml | 1 + .../Roles/Jobs/Civilian/musician.yml | 4 +- .../Prototypes/Roles/Jobs/Command/captain.yml | 4 +- .../Roles/Jobs/Command/head_of_personnel.yml | 4 +- 28 files changed, 893 insertions(+), 11 deletions(-) create mode 100644 Resources/Prototypes/DeltaV/Catalog/Fills/Items/Belts/misc.yml create mode 100644 Resources/Prototypes/DeltaV/Loadouts/Miscellaneous/scarfs.yml diff --git a/Resources/Locale/en-US/deltav/preferences/loadout-groups.ftl b/Resources/Locale/en-US/deltav/preferences/loadout-groups.ftl index b6f51638ee4..edad6d75557 100644 --- a/Resources/Locale/en-US/deltav/preferences/loadout-groups.ftl +++ b/Resources/Locale/en-US/deltav/preferences/loadout-groups.ftl @@ -1,7 +1,32 @@ # This file will contain strings for both DeltaV and Nyanotrasen loadouts, because I'm lazy +# Command +loadout-group-captain-gloves = Captain gloves +loadout-group-captain-shoes = Captain shoes +loadout-group-hop-gloves = Head of Personnel gloves +loadout-group-hop-shoes = Head of Personnel shoes + +# Civilian +loadout-group-librarian-neck = Librarian neck + +loadout-group-bartender-glasses = Bartender glasses +loadout-group-bartender-neck = Bartender neck + +loadout-group-janitor-neck = Janitor neck + +loadout-group-botanist-neck = Botanist Neck + +loadout-group-mime-neck = Mime neck + +loadout-group-musician-jumpsuit = Musician jumpsuit +loadout-group-musician-neck = Musician neck + # Logistics + +loadout-group-cargo-technician-neck = Cargo Technician neck + loadout-group-courier-head = Courier head +loadout-group-courier-neck = Courier neck loadout-group-courier-jumpsuit = Courier jumpsuit loadout-group-courier-shoes = Courier shoes loadout-group-courier-outerclothing = Courier outer clothing @@ -11,6 +36,18 @@ loadout-group-mail-carrier-head = Mail Carrier head loadout-group-mail-carrier-jumpsuit = Mail Carrier jumpsuit loadout-group-mail-carrier-outerclothing = Mail Carrier outer clothing +loadout-group-salvage-specialist-neck = Salvage Specialist neck + +# Medical +loadout-group-chemist-gloves = Chemist gloves +loadout-group-chemist-shoes = Chemist shoes +loadout-group-chemist-neck = Chemist neck + +loadout-group-medical-doctor-neck = Medical Doctor neck + +# Miscellaneous +loadout-group-scarfs = Scarf + # Epistemics loadout-group-mantis-head = Mantis head loadout-group-mantis-jumpsuit = Mantis jumpsuit @@ -19,10 +56,21 @@ loadout-group-mantis-outerclothing = Mantis outer clothing loadout-group-mantis-shoes = Mantis shoes loadout-group-mantis-gloves = Mantis gloves +# Engineering +loadout-group-station-engineer-neck = Station Engineer neck + +loadout-group-atmospheric-technician-neck = Atmospheric Technician neck + # Security +loadout-group-head-of-security-shoes = Head of Security shoes + +loadout-group-security-cadet-head = Security Cadet head +loadout-group-security-neck = Security neck + loadout-group-brig-medic-head = Brigmedic head loadout-group-brig-medic-jumpsuit = Brigmedic jumpsuit loadout-group-brig-medic-back = Brigmedic backpack +loadout-group-brig-medic-neck = Corpsman neck loadout-group-prison-guard-head = Prison Guard head loadout-group-prison-guard-jumpsuit = Prison Guard jumpsuit diff --git a/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Belts/misc.yml b/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Belts/misc.yml new file mode 100644 index 00000000000..f97a5be4c4e --- /dev/null +++ b/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Belts/misc.yml @@ -0,0 +1,9 @@ +- type: entity + id: ClothingShoesBootsJackFilled + parent: ClothingShoesBootsJack + suffix: Filled + components: + - type: ContainerFill + containers: + item: + - CombatKnife diff --git a/Resources/Prototypes/DeltaV/Loadouts/Miscellaneous/scarfs.yml b/Resources/Prototypes/DeltaV/Loadouts/Miscellaneous/scarfs.yml new file mode 100644 index 00000000000..f2a51d7c9d8 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Loadouts/Miscellaneous/scarfs.yml @@ -0,0 +1,90 @@ +# Neck +#Red +- type: loadout + id: ScarfRed + equipment: ScarfRed + +- type: startingGear + id: ScarfRed + equipment: + neck: ClothingNeckScarfStripedRed + +#Blue +- type: loadout + id: ScarfBlue + equipment: ScarfBlue + +- type: startingGear + id: ScarfBlue + equipment: + neck: ClothingNeckScarfStripedBlue + +#LightBlue +- type: loadout + id: ScarfLightBlue + equipment: ScarfLightBlue + +- type: startingGear + id: ScarfLightBlue + equipment: + neck: ClothingNeckScarfStripedLightBlue + +#Green +- type: loadout + id: ScarfGreen + equipment: ScarfGreen + +- type: startingGear + id: ScarfGreen + equipment: + neck: ClothingNeckScarfStripedGreen + +#Purple +- type: loadout + id: ScarfPurple + equipment: ScarfPurple + +- type: startingGear + id: ScarfPurple + equipment: + neck: ClothingNeckScarfStripedPurple + +#Brown +- type: loadout + id: ScarfBrown + equipment: ScarfBrown + +- type: startingGear + id: ScarfBrown + equipment: + neck: ClothingNeckScarfStripedBrown + +#Orange +- type: loadout + id: ScarfOrange + equipment: ScarfOrange + +- type: startingGear + id: ScarfOrange + equipment: + neck: ClothingNeckScarfStripedOrange + +#Black +- type: loadout + id: ScarfBlack + equipment: ScarfBlack + +- type: startingGear + id: ScarfBlack + equipment: + neck: ClothingNeckScarfStripedBlack + +#Zebra +- type: loadout + id: ScarfZebra + equipment: ScarfZebra + +- type: startingGear + id: ScarfZebra + equipment: + neck: ClothingNeckScarfStripedZebra diff --git a/Resources/Prototypes/DeltaV/Loadouts/loadout_groups.yml b/Resources/Prototypes/DeltaV/Loadouts/loadout_groups.yml index 0411e0ec372..10ba7d8e0b5 100644 --- a/Resources/Prototypes/DeltaV/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/DeltaV/Loadouts/loadout_groups.yml @@ -8,6 +8,14 @@ - CourierHead - MailCarrierHead +- type: loadoutGroup + id: CourierNeck + name: loadout-group-courier-neck + minLimit: 0 + loadouts: + - ScarfBrown + - ScarfBlue + - type: loadoutGroup id: CourierJumpsuit name: loadout-group-courier-jumpsuit @@ -49,6 +57,14 @@ loadouts: - CorpsmanBeret +- type: loadoutGroup + id: BrigMedicNeck + name: loadout-group-brig-medic-neck + minLimit: 0 + loadouts: + - ScarfRed + - ScarfBlue + - type: loadoutGroup id: BrigMedicJumpsuit name: loadout-group-brig-medic-jumpsuit @@ -63,3 +79,18 @@ - BrigMedicBackpack - BrigMedicSatchel - BrigMedicDuffel + +- type: loadoutGroup + id: Scarfs + name: loadout-group-scarfs + minLimit: 0 + loadouts: + - ScarfRed + - ScarfBlue + - ScarfLightBlue + - ScarfGreen + - ScarfPurple + - ScarfBrown + - ScarfOrange + - ScarfBlack + - ScarfZebra diff --git a/Resources/Prototypes/DeltaV/Loadouts/role_loadouts.yml b/Resources/Prototypes/DeltaV/Loadouts/role_loadouts.yml index 4ef61a5bdfb..4f332464ba3 100644 --- a/Resources/Prototypes/DeltaV/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/DeltaV/Loadouts/role_loadouts.yml @@ -3,6 +3,7 @@ id: JobCourier groups: - CourierHead + - CourierNeck - CourierJumpsuit - CourierOuterClothing - CourierPDA @@ -15,6 +16,7 @@ id: JobBrigmedic groups: - BrigMedicHead + - BrigMedicNeck - BrigMedicJumpsuit - BrigMedicBack - SecurityShoes diff --git a/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml index 27f938f9faf..44b5bacfab9 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml @@ -128,3 +128,13 @@ id: QuartermasterWintercoat equipment: outerClothing: ClothingOuterWinterQM + +# Shoes - DeltaV +- type: loadout + id: QuartermasterWinterBoots + equipment: QuartermasterWinterBoots + +- type: startingGear + id: QuartermasterWinterBoots + equipment: + shoes: ClothingShoesBootsWinterLogisticsOfficer diff --git a/Resources/Prototypes/Loadouts/Jobs/Cargo/salvage_specialist.yml b/Resources/Prototypes/Loadouts/Jobs/Cargo/salvage_specialist.yml index 3123882b878..c352ad60901 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Cargo/salvage_specialist.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Cargo/salvage_specialist.yml @@ -45,3 +45,22 @@ id: SalvageBoots equipment: shoes: ClothingShoesBootsSalvage + +- type: loadout # DeltaV + id: SalvageWinterBoots + equipment: SalvageWinterBoots + +- type: startingGear # DeltaV + id: SalvageWinterBoots + equipment: + shoes: ClothingShoesBootsWinterMiner + +# Neck - DeltaV +- type: loadout # DeltaV + id: SalvageCloak + equipment: SalvageCloak + +- type: startingGear # DeltaV + id: SalvageCloak + equipment: + neck: ClothingNeckSalvager diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml index 8eee752d596..0ae19394ae3 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml @@ -72,3 +72,13 @@ id: BartenderWintercoat equipment: outerClothing: ClothingOuterWinterBar + +# Glasses - DeltaV +- type: loadout + id: BartenderGlasses + equipment: BartenderGlasses + +- type: startingGear + id: BartenderGlasses + equipment: + eyes: ClothingEyesHudBeer diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/musician.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/musician.yml index 82b213d2241..92c93be4afb 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/musician.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/musician.yml @@ -1,3 +1,22 @@ +# Jumpsuit - DeltaV +- type: loadout # DeltaV + id: MusicianJumpsuit + equipment: MusicianJumpsuit + +- type: startingGear # DeltaV + id: MusicianJumpsuit + equipment: + jumpsuit: ClothingUniformJumpsuitMusician + +- type: loadout # DeltaV + id: MusicianJumpskirt + equipment: MusicianJumpskirt + +- type: startingGear # DeltaV + id: MusicianJumpskirt + equipment: + jumpsuit: ClothingUniformDressRed + # Back - type: loadout id: MusicianBackpack diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml index 5c09b1299f3..883e5a793cf 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml @@ -54,6 +54,84 @@ equipment: jumpsuit: ClothingUniformColorRainbow +# Rose Hoodie w/ Skirt - DeltaV +- type: loadout + id: MioSkirt + equipment: MioSkirt + +- type: startingGear + id: MioSkirt + equipment: + jumpsuit: ClothingCostumeMioSkirt + +# Turqoise Hoodie w/ Shorts - DeltaV +- type: loadout + id: NaotaHoodie + equipment: NaotaHoodie + +- type: startingGear + id: NaotaHoodie + equipment: + jumpsuit: ClothingCostumeNaota + +# Casual Blue - DeltaV +- type: loadout + id: CasualRedSkirt + equipment: CasualRedSkirt + +- type: startingGear + id: CasualRedSkirt + equipment: + jumpsuit: ClothingUniformJumpskirtCasualRed + +- type: loadout + id: CasualRedSuit + equipment: CasualRedSuit + +- type: startingGear + id: CasualRedSuit + equipment: + jumpsuit: ClothingUniformJumpsuitCasualRed + + +# Casual Blue - DeltaV +- type: loadout + id: CasualBlueSkirt + equipment: CasualBlueSkirt + +- type: startingGear + id: CasualBlueSkirt + equipment: + jumpsuit: ClothingUniformJumpskirtCasualBlue + +- type: loadout + id: CasualBlueSuit + equipment: CasualBlueSuit + +- type: startingGear + id: CasualBlueSuit + equipment: + jumpsuit: ClothingUniformJumpsuitCasualBlue + +# Casual Purple - DeltaV +- type: loadout + id: CasualPurpleSkirt + equipment: CasualPurpleSkirt + +- type: startingGear + id: CasualPurpleSkirt + equipment: + jumpsuit: ClothingUniformJumpskirtCasualPurple + +- type: loadout + id: CasualPurpleSuit + equipment: CasualPurpleSuit + +- type: startingGear + id: CasualPurpleSuit + equipment: + jumpsuit: ClothingUniformJumpsuitCasualPurple + # Ancient - type: loadout id: AncientJumpsuit diff --git a/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml b/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml index 7800597909c..dcd639b2a99 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml @@ -54,6 +54,15 @@ equipment: head: ClothingHeadHatCapcap +- type: loadout # DeltaV + id: CaptainBeret + equipment: CaptainBeret + +- type: startingGear # DeltaV + id: CaptainBeret + equipment: + head: ClothingHeadHatBeretCap + # Neck - type: loadout id: CaptainCloak @@ -128,3 +137,32 @@ id: CaptainWintercoat equipment: outerClothing: ClothingOuterWinterCap + +# Gloves - DeltaV +- type: loadout #DeltaV + id: CaptainGloves + equipment: CaptainGloves + +- type: startingGear #DeltaV + id: CaptainGloves + equipment: + gloves: ClothingHandsGlovesCaptain + +- type: loadout # DeltaV + id: InspectionGloves + equipment: InspectionGloves + +- type: startingGear # DeltaV + id: InspectionGloves + equipment: + gloves: ClothingHandsGlovesInspection + +# Shoes - DeltaV +- type: loadout # DeltaV + id: CaptainWinterBoots + equipment: CaptainWinterBoots + +- type: startingGear # DeltaV + id: CaptainWinterBoots + equipment: + shoes: ClothingShoesBootsWinterCap diff --git a/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml index a31f7895d42..53ea0376bb9 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml @@ -27,6 +27,24 @@ equipment: jumpsuit: ClothingUniformJumpskirtHoP +- type: loadout # DeltaV + id: HoPMess + equipment: HoPMess + +- type: startingGear # DeltaV + id: HoPMess + equipment: + jumpsuit: ClothingUniformJumpskirtHoPMess + +- type: loadout # DeltaV + id: HoPBoatswain + equipment: HoPBoatswain + +- type: startingGear # DeltaV + id: HoPBoatswain + equipment: + jumpsuit: ClothingUniformJumpsuitBoatswain + # Head - type: loadout id: HoPHead @@ -105,3 +123,68 @@ id: HoPWintercoat equipment: outerClothing: ClothingOuterWinterHoP + +- type: loadout # DeltaV + id: HoPFormalCoat + equipment: HoPFormalCoat + +- type: startingGear # DeltaV + id: HoPFormalCoat + equipment: + outerClothing: ClothingOuterCoatHoPFormal + +- type: loadout # DeltaV + id: HoPArmoredCoat + equipment: HoPArmoredCoat + +- type: startingGear # DeltaV + id: HoPArmoredCoat + equipment: + outerClothing: ClothingOuterCoatHoPArmored + +# Gloves - DeltaV +- type: loadout # DeltaV + id: HoPGloves + equipment: HoPGloves + +- type: startingGear # DeltaV + id: HoPGloves + equipment: + gloves: ClothingHandsGlovesHop + +# Shoes - DeltaV +- type: loadout # DeltaV + id: BrownLeatherShoes + equipment: BrownLeatherShoes + +- type: startingGear # DeltaV + id: BrownLeatherShoes + equipment: + shoes: ClothingShoesLeather + +- type: loadout # DeltaV + id: WhiteLeatherShoes + equipment: WhiteLeatherShoes + +- type: startingGear # DeltaV + id: WhiteLeatherShoes + equipment: + shoes: ClothingShoesMiscWhite + +- type: loadout # DeltaV + id: LaceupShoes + equipment: LaceupShoes + +- type: startingGear # DeltaV + id: LaceupShoes + equipment: + shoes: ClothingShoesBootsLaceup + +- type: loadout # DeltaV + id: HoPWinterBoots + equipment: HoPWinterBoots + +- type: startingGear # DeltaV + id: HoPWinterBoots + equipment: + shoes: ClothingShoesBootsWinterHeadOfPersonel diff --git a/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml index f682b202fea..12e607a5109 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml @@ -115,3 +115,12 @@ id: BrownShoes equipment: shoes: ClothingShoesColorBrown + +- type: loadout + id: ChiefEngineerWinterBoots + equipment: ChiefEngineerWinterBoots + +- type: startingGear + id: ChiefEngineerWinterBoots + equipment: + shoes: ClothingShoesBootsWinterChiefEngineer diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml index e905eb93093..54babe12e2e 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml @@ -1,3 +1,13 @@ +# Neck - DeltaV +- type: loadout # DeltaV + id: ChemistTie + equipment: ChemistTie + +- type: startingGear # DeltaV + id: ChemistTie + equipment: + neck: ClothingNeckTieChem + # Jumpsuit - type: loadout id: ChemistJumpsuit @@ -17,6 +27,15 @@ equipment: jumpsuit: ClothingUniformJumpskirtChemistry +- type: loadout # DeltaV + id: ChemistFormalSuit + equipment: ChemistFormalSuit + +- type: startingGear # DeltaV + id: ChemistFormalSuit + equipment: + jumpsuit: ClothingUniformJumpsuitChemShirt + # Back - type: loadout id: ChemistBackpack @@ -63,3 +82,43 @@ id: ChemistWintercoat equipment: outerClothing: ClothingOuterWinterChem + +## Start DeltaV Changes +- type: loadout + id: ChemistApron + equipment: ChemistApron + +- type: startingGear + id: ChemistApron + equipment: + outerClothing: ClothingOuterApronChemist + +# Gloves - DeltaV +- type: loadout + id: ChemistGloves + equipment: ChemistGloves + +- type: startingGear + id: ChemistGloves + equipment: + gloves: ClothingHandsGlovesChemist + +# Shoes - DeltaV +- type: loadout + id: ChemistWinterBoots + equipment: ChemistWinterBoots + +- type: startingGear + id: ChemistWinterBoots + equipment: + shoes: ClothingShoesBootsWinterChem + +- type: loadout + id: ChemistShoes + equipment: ChemistShoes + +- type: startingGear + id: ChemistShoes + equipment: + shoes: ClothingShoesEnclosedChem +## End DeltaV Changes diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml index 3b5be353f2e..23db1514d8c 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml @@ -119,3 +119,13 @@ id: ChiefMedicalOfficerWintercoat equipment: outerClothing: ClothingOuterWinterCMO + +# Shoes - DeltaV +- type: loadout + id: ChiefMedicalOfficerWinterBoots + equipment: ChiefMedicalOfficerWinterBoots + +- type: startingGear + id: ChiefMedicalOfficerWinterBoots + equipment: + shoes: ClothingShoesBootsWinterChiefMedicalOfficer diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml index 5b2ae4ba049..60550cf25b5 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml @@ -100,6 +100,16 @@ equipment: head: ClothingHeadNurseHat +# Neck - DeltaV +- type: loadout # DeltaV + id: Stethoscope + equipment: Stethoscope + +- type: startingGear # DeltaV + id: Stethoscope + equipment: + neck: ClothingNeckStethoscope + # Jumpsuit - type: loadout id: MedicalDoctorJumpsuit diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml index 74b98057f02..e60cde9d8d3 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml @@ -83,3 +83,12 @@ id: BlueShoes equipment: shoes: ClothingShoesColorBlue + +- type: loadout # DeltaV + id: ParamedicWinterBoots + equipment: ParamedicWinterBoots + +- type: startingGear # DeltaV + id: ParamedicWinterBoots + equipment: + shoes: ClothingShoesBootsWinterParamedic diff --git a/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml b/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml index a979c6d492e..ac6ee5fbf48 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml @@ -107,3 +107,13 @@ id: ResearchDirectorWintercoat equipment: outerClothing: ClothingOuterWinterRD + +# Shoes - DeltaV +- type: loadout # DeltaV + id: ResearchDirectorWinterBoots + equipment: ResearchDirectorWinterBoots + +- type: startingGear # DeltaV + id: ResearchDirectorWinterBoots + equipment: + shoes: ClothingShoesBootsWinterMystagogue diff --git a/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml b/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml index e4ffb944fb8..f5097071fc7 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml @@ -160,6 +160,15 @@ equipment: outerClothing: ClothingOuterCoatRnd +- type: loadout # DeltaV + id: RoboticistLabCoat + equipment: RoboticistLabCoat + +- type: startingGear # DeltaV + id: RoboticistLabCoat + equipment: + outerClothing: ClothingOuterCoatRobo + - type: loadout id: ScienceWintercoat equipment: ScienceWintercoat diff --git a/Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml index a345049ed1e..78e29fd8bd5 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml @@ -109,3 +109,13 @@ id: HeadofSecurityWinterCoat equipment: outerClothing: ClothingOuterWinterHoS + +# Shoes - DeltaV +- type: loadout # DeltaV + id: HeadOfSecurityWinterBoots + equipment: HeadOfSecurityWinterBoots + +- type: startingGear # DeltaV + id: HeadOfSecurityWinterBoots + equipment: + shoes: ClothingShoesBootsWinterHeadOfSecurity diff --git a/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml b/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml index 81fca69cd54..20ba1c2e693 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml @@ -79,6 +79,24 @@ equipment: jumpsuit: ClothingUniformJumpskirtSecGrey +- type: loadout # DeltaV + id: SecurityJumpsuitBlue + equipment: SecurityJumpsuitBlue + +- type: startingGear # DeltaV + id: SecurityJumpsuitBlue + equipment: + jumpsuit: ClothingUniformJumpsuitSecBlue + +- type: loadout # DeltaV + id: SecurityJumpskirtBlue + equipment: SecurityJumpskirtBlue + +- type: startingGear # DeltaV + id: SecurityJumpskirtBlue + equipment: + jumpsuit: ClothingUniformJumpskirtSecBlue + - type: loadout id: SeniorOfficerJumpsuit equipment: SeniorOfficerJumpsuit @@ -169,6 +187,15 @@ equipment: outerClothing: ClothingOuterArmorPlateCarrier +- type: loadout # DeltaV + id: DuraVest + equipment: DuraVest + +- type: startingGear # DeltaV + id: DuraVest + equipment: + outerClothing: ClothingOuterArmorDuraVest + - type: loadout id: ArmorVestSlim equipment: ArmorVestSlim @@ -197,6 +224,15 @@ equipment: shoes: ClothingShoesBootsCombatFilled +- type: loadout # DeltaV + id: JackBoots + equipment: JackBoots + +- type: startingGear # DeltaV + id: JackBoots + equipment: + shoes: ClothingShoesBootsJackFilled + - type: loadout id: SecurityWinterBoots equipment: SecurityWinterBoots diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml b/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml index 76029486d77..c141d270810 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml @@ -8,6 +8,15 @@ role: JobLibrarian time: 3600 # 1 hour of being the biggest nerd on the station +- type: loadoutEffectGroup # DeltaV + id: CheapSunglassesTimer + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:RoleTimeRequirement + role: JobMusician + time: 7200 # 2 hours of being a rockstar + - type: loadoutEffectGroup id: JensenTimer effects: @@ -54,3 +63,16 @@ id: GlassesJensen equipment: eyes: ClothingEyesGlassesJensen + +# Cheap Sunglasses - DeltaV +- type: loadout # DeltaV + id: GlassesCheapSunglasses + equipment: GlassesCheapSunglasses + effects: + - !type:GroupLoadoutEffect + proto: CheapSunglassesTimer + +- type: startingGear # DeltaV + id: GlassesCheapSunglasses + equipment: + eyes: ClothingEyesGlassesCheapSunglasses diff --git a/Resources/Prototypes/Loadouts/loadout_groups.yml b/Resources/Prototypes/Loadouts/loadout_groups.yml index 87823c88027..5ec4b0d72a9 100644 --- a/Resources/Prototypes/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/loadout_groups.yml @@ -32,6 +32,7 @@ minLimit: 0 loadouts: - Glasses + - GlassesCheapSunglasses # DeltaV - GlassesJamjar - GlassesJensen @@ -43,6 +44,7 @@ loadouts: - CaptainHead - CaptainCap + - CaptainBeret # DeltaV - type: loadoutGroup id: CaptainJumpsuit @@ -53,6 +55,14 @@ - CaptainFormalSuit - CaptainFormalSkirt +- type: loadoutGroup # DeltaV + id: CaptainGloves + name: loadout-group-captain-gloves + minLimit: 0 + loadouts: + - CaptainGloves + - InspectionGloves + - type: loadoutGroup id: CaptainNeck name: loadout-group-captain-neck @@ -77,6 +87,15 @@ - CaptainOuterClothing - CaptainWintercoat +- type: loadoutGroup # DeltaV + id: CaptainShoes + name: loadout-group-captain-shoes + loadouts: + - BrownLeatherShoes + - WhiteLeatherShoes + - LaceupShoes + - CaptainWinterBoots + - type: loadoutGroup id: HoPHead name: loadout-group-hop-head @@ -84,12 +103,22 @@ loadouts: - HoPHead +- type: loadoutGroup # DeltaV + id: HoPGloves + name: loadout-group-hop-gloves + minLimit: 0 + loadouts: + - HoPGloves + - InspectionGloves + - type: loadoutGroup id: HoPJumpsuit name: loadout-group-hop-jumpsuit loadouts: - HoPJumpsuit - HoPJumpskirt + - HoPMess # DeltaV + - HoPBoatswain # DeltaV - type: loadoutGroup id: HoPNeck @@ -98,6 +127,7 @@ loadouts: - HoPCloak - HoPMantle + - ScarfBlue # DeltaV - type: loadoutGroup id: HoPBackpack @@ -113,8 +143,20 @@ name: loadout-group-hop-outerclothing minLimit: 0 loadouts: + - HoPFormalCoat # DeltaV + - HoPArmoredCoat # DeltaV + - DuraVest # DeltaV - HoPWintercoat +- type: loadoutGroup # DeltaV + id: HoPShoes + name: loadout-group-hop-shoes + loadouts: + - BrownLeatherShoes + - WhiteLeatherShoes + - LaceupShoes + - HoPWinterBoots + # Civilian - type: loadoutGroup id: PassengerJumpsuit @@ -122,6 +164,14 @@ loadouts: - GreyJumpsuit - GreyJumpskirt + - MioSkirt # Start DeltaV Changes + - NaotaHoodie + - CasualBlueSkirt + - CasualBlueSuit + - CasualRedSkirt + - CasualRedSuit + - CasualPurpleSkirt + - CasualPurpleSuit # End DeltaV Changes - RainbowJumpsuit - AncientJumpsuit @@ -169,6 +219,23 @@ - BartenderHead - BartenderBowler +- type: loadoutGroup # DeltaV + id: BartenderGlasses + name: loadout-group-bartender-glasses + minLimit: 0 + loadouts: + - Glasses + - BartenderGlasses + - GlassesCheapSunglasses + +- type: loadoutGroup # DeltaV + id: BartenderNeck + name: loadout-group-bartender-neck + minLimit: 0 + loadouts: + - ScarfBlack + - ScarfPurple + - type: loadoutGroup id: BartenderJumpsuit name: loadout-group-bartender-jumpsuit @@ -216,6 +283,13 @@ - ChefJacket - ChefWintercoat +- type: loadoutGroup # DeltaV + id: LibrarianNeck + name: loadout-group-librarian-neck + loadouts: + - ScarfGreen + - ScarfRed + - type: loadoutGroup id: LibrarianJumpsuit name: loadout-group-librarian-jumpsuit @@ -246,6 +320,10 @@ minLimit: 0 loadouts: - LawyerNeck + - ScarfRed # Delta V + - ScarfBlue # Delta V + - ScarfBlack # Delta V + - ScarfPurple # Delta V - type: loadoutGroup id: LawyerBackpack @@ -306,6 +384,7 @@ minLimit: 0 loadouts: - ChaplainNeck + - ScarfBlack # DeltaV - type: loadoutGroup id: JanitorHead @@ -321,6 +400,13 @@ - JanitorJumpsuit - JanitorJumpskirt +- type: loadoutGroup # DeltaV + id: JanitorNeck + name: loadout-group-janitor-neck + minLimit: 0 + loadouts: + - ScarfPurple + - type: loadoutGroup id: JanitorGloves name: loadout-group-janitor-gloves @@ -361,6 +447,14 @@ - BotanistSatchel - BotanistDuffel +- type: loadoutGroup # DeltaV + id: BotanistNeck + name: loadout-group-botanist-neck + minLimit: 0 + loadouts: + - ScarfGreen + - ScarfBrown + - type: loadoutGroup id: BotanistOuterClothing name: loadout-group-botanist-outerclothing @@ -415,6 +509,14 @@ - MimeFrenchBeret - MimeCap +- type: loadoutGroup # DeltaV + id: MimeNeck + name: loadout-group-mime-neck + minLimit: 0 + loadouts: + - ScarfBlack + - ScarfZebra + - type: loadoutGroup id: MimeMask name: loadout-group-mime-mask @@ -446,6 +548,22 @@ loadouts: - MimeWintercoat +- type: loadoutGroup # DeltaV + id: MusicianNeck + name: loadout-group-musician-neck + minLimit: 0 + loadouts: + - ScarfBlack + - ScarfPurple + - ScarfZebra + +- type: loadoutGroup # DeltaV + id: MusicianJumpsuit + name: loadout-group-musician-jumpsuit + loadouts: + - MusicianJumpsuit + - MusicianJumpskirt + - type: loadoutGroup id: MusicianBackpack name: loadout-group-musician-backpack @@ -469,6 +587,7 @@ loadouts: - QuartermasterHead - QuartermasterBeret + - LogiOfficerBeret # DeltaV - type: loadoutGroup id: QuartermasterJumpsuit @@ -495,6 +614,7 @@ loadouts: - QuartermasterCloak - QuartermasterMantle + - ScarfBrown # DeltaV - type: loadoutGroup id: QuartermasterOuterClothing @@ -509,6 +629,7 @@ loadouts: - BrownShoes - CargoWinterBoots + - QuartermasterWinterBoots # DeltaV - type: loadoutGroup id: CargoTechnicianHead @@ -517,6 +638,13 @@ loadouts: - CargoTechnicianHead +- type: loadoutGroup # DeltaV + id: CargoTechnicianNeck + name: loadout-group-cargo-technician-neck + minLimit: 0 + loadouts: + - ScarfBrown + - type: loadoutGroup id: CargoTechnicianJumpsuit name: loadout-group-cargo-technician-jumpsuit @@ -546,6 +674,13 @@ - BlackShoes - CargoWinterBoots +- type: loadoutGroup # DeltaV + id: SalvageSpecialistNeck + name: loadout-group-salvage-specialist-neck + loadouts: + - SalvageCloak + - ScarfBrown + - type: loadoutGroup id: SalvageSpecialistBackpack name: loadout-group-salvage-specialist-backpack @@ -566,7 +701,8 @@ name: loadout-group-salvage-specialist-shoes loadouts: - SalvageBoots - - CargoWinterBoots + #- CargoWinterBoots + - SalvageWinterBoots # DeltaV # Engineering - type: loadoutGroup @@ -601,6 +737,7 @@ loadouts: - ChiefEngineerCloak - ChiefEngineerMantle + - ScarfOrange # DeltaV - type: loadoutGroup id: ChiefEngineerOuterClothing @@ -615,6 +752,7 @@ loadouts: - BrownShoes - EngineeringWinterBoots + - ChiefEngineerWinterBoots # DeltaV - type: loadoutGroup id: TechnicalAssistantJumpsuit @@ -626,12 +764,20 @@ - type: loadoutGroup id: StationEngineerHead name: loadout-group-station-engineer-head + minLimit: 0 # DeltaV - Not everyone remembers their (hard)hats loadouts: - StationEngineerHardhatYellow - StationEngineerHardhatOrange - StationEngineerHardhatRed - SeniorEngineerBeret +- type: loadoutGroup # DeltaV + id: StationEngineerNeck + name: loadout-group-station-engineer-neck + minLimit: 0 + loadouts: + - ScarfOrange + - type: loadoutGroup id: StationEngineerJumpsuit name: loadout-group-station-engineer-jumpsuit @@ -672,6 +818,14 @@ - StationEngineerPDA - SeniorEngineerPDA +- type: loadoutGroup # DeltaV + id: AtmosphericTechnicianNeck + name: loadout-group-atmospheric-technician-neck + minLimit: 0 + loadouts: + - ScarfLightBlue + - ScarfOrange + - type: loadoutGroup id: AtmosphericTechnicianJumpsuit name: loadout-group-atmospheric-technician-jumpsuit @@ -718,6 +872,7 @@ loadouts: - ResearchDirectorMantle - ResearchDirectorCloak + - ScarfPurple # DeltaV - type: loadoutGroup id: ResearchDirectorJumpsuit @@ -749,6 +904,7 @@ loadouts: - BrownShoes - ScienceWinterBoots + - ResearchDirectorWinterBoots # DeltaV - type: loadoutGroup id: ScientistHead @@ -765,6 +921,7 @@ minLimit: 0 loadouts: - ScientistTie + - ScarfPurple # DeltaV - type: loadoutGroup id: ScientistJumpsuit @@ -792,6 +949,7 @@ loadouts: - RegularLabCoat - ScienceLabCoat + - RoboticistLabCoat # DeltaV - ScienceWintercoat - SeniorResearcherLabCoat @@ -852,6 +1010,7 @@ loadouts: - HeadofSecurityCloak - HeadofSecurityMantle + - ScarfRed # DeltaV - type: loadoutGroup id: HeadofSecurityOuterClothing @@ -860,6 +1019,15 @@ - HeadofSecurityCoat - HeadofSecurityWinterCoat +- type: loadoutGroup # DeltaV + id: HeadOfSecurityShoes + name: loadout-group-head-of-security-shoes + loadouts: + - CombatBoots + - JackBoots # DeltaV + - SecurityWinterBoots + - HeadOfSecurityWinterBoots # DeltaV + - type: loadoutGroup id: WardenHead name: loadout-group-warden-head @@ -890,6 +1058,7 @@ - SecurityHelmet - SecurityBeret - SecurityHat + - MimeHead # DeltaV - Red Beret - type: loadoutGroup id: SecurityJumpsuit @@ -899,6 +1068,8 @@ - SecurityJumpskirt - SecurityJumpsuitGrey - SecurityJumpskirtGrey + - SecurityJumpsuitBlue # DeltaV + - SecurityJumpskirtBlue # DeltaV - SeniorOfficerJumpsuit - SeniorOfficerJumpskirt @@ -923,6 +1094,7 @@ loadouts: - ArmorVest - PlateCarrier # DeltaV + - DuraVest # DeltaV - ArmorVestSlim - SecurityOfficerWintercoat @@ -931,6 +1103,7 @@ name: loadout-group-security-shoes loadouts: - CombatBoots + - JackBoots # DeltaV - SecurityWinterBoots - type: loadoutGroup @@ -940,6 +1113,13 @@ - SecurityPDA - SeniorOfficerPDA +- type: loadoutGroup # DeltaV + id: SecurityNeck + name: loadout-group-security-neck + minLimit: 0 + loadouts: + - ScarfRed + - type: loadoutGroup id: DetectiveHead name: loadout-group-detective-head @@ -954,6 +1134,7 @@ minLimit: 0 loadouts: - DetectiveTie + - ScarfRed # DeltaV - type: loadoutGroup id: DetectiveJumpsuit @@ -987,6 +1168,13 @@ - RedJumpsuit - RedJumpskirt +- type: loadoutGroup # DeltaV + id: SecurityCadetHead + name: loadout-group-security-cadet-head + minLimit: 0 + loadouts: + - MimeHead # DeltaV - Red Beret + # Medical - type: loadoutGroup id: ChiefMedicalOfficerHead @@ -1028,6 +1216,8 @@ loadouts: - ChiefMedicalOfficerCloak - ChiefMedicalOfficerMantle + - ScarfBlue # DeltaV + - Stethoscope # DeltaV - type: loadoutGroup id: ChiefMedicalOfficerShoes @@ -1035,6 +1225,7 @@ loadouts: - BrownShoes - MedicalWinterBoots + - ChiefMedicalOfficerWinterBoots - type: loadoutGroup id: MedicalDoctorHead @@ -1049,6 +1240,14 @@ - PurpleSurgeryCap - NurseHat +- type: loadoutGroup # DeltaV + id: MedicalDoctorNeck + name: loadout-group-medical-doctor-neck + minLimit: 0 + loadouts: + - Stethoscope + - ScarfBlue + - type: loadoutGroup id: MedicalDoctorJumpsuit name: loadout-group-medical-doctor-jumpsuit @@ -1121,6 +1320,7 @@ loadouts: - ChemistJumpsuit - ChemistJumpskirt + - ChemistFormalSuit # DeltaV - type: loadoutGroup id: ChemistOuterClothing @@ -1130,6 +1330,7 @@ - RegularLabCoat - ChemistLabCoat - ChemistWintercoat + - ChemistApron # DeltaV - type: loadoutGroup id: ChemistBackpack @@ -1139,6 +1340,34 @@ - ChemistSatchel - ChemistDuffel +# Chemist Neck - DeltaV +- type: loadoutGroup + id: ChemistNeck + name: loadout-group-chemist-neck + loadouts: + - ChemistTie # DeltaV + - ScarfOrange + - ScarfBlue + +# Chemist Gloves - DeltaV +- type: loadoutGroup + id: ChemistGloves + name: loadout-group-chemist-gloves + minLimit: 0 + loadouts: + - LatexGloves + - NitrileGloves + - ChemistGloves # DeltaV + +# Chemist Shoes - DeltaV +- type: loadoutGroup + id: ChemistShoes + name: loadout-group-chemist-shoes + loadouts: + - WhiteShoes + - ChemistWinterBoots # DeltaV + - ChemistShoes # DeltaV + - type: loadoutGroup id: ParamedicHead name: loadout-group-paramedic-head @@ -1175,6 +1404,7 @@ loadouts: - BlueShoes - MedicalWinterBoots + - ParamedicWinterBoots # DeltaV # Wildcards - type: loadoutGroup diff --git a/Resources/Prototypes/Loadouts/role_loadouts.yml b/Resources/Prototypes/Loadouts/role_loadouts.yml index 5bc6fc4572b..b3ec4edaada 100644 --- a/Resources/Prototypes/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Loadouts/role_loadouts.yml @@ -7,6 +7,8 @@ - CaptainJumpsuit - CaptainBackpack - CaptainOuterClothing + - CaptainGloves # DeltaV + - CaptainShoes # DeltaV - Trinkets - type: roleLoadout @@ -17,6 +19,8 @@ - HoPJumpsuit - HoPBackpack - HoPOuterClothing + - HoPGloves # DeltaV + - HoPShoes # DeltaV - Glasses - Trinkets @@ -24,6 +28,7 @@ - type: roleLoadout id: JobPassenger groups: + - Scarfs # DeltaV - PassengerJumpsuit - CommonBackpack - PassengerFace @@ -37,15 +42,18 @@ id: JobBartender groups: - BartenderHead + - BartenderGlasses # DeltaV + - BartenderNeck # DeltaV - BartenderJumpsuit - CommonBackpack - BartenderOuterClothing - - Glasses + #- Glasses - Trinkets - type: roleLoadout id: JobServiceWorker groups: + - Scarfs # DeltaV - BartenderJumpsuit - CommonBackpack - Glasses @@ -65,6 +73,7 @@ - type: roleLoadout id: JobLibrarian groups: + - LibrarianNeck # DeltaV - LibrarianJumpsuit - CommonBackpack - Glasses @@ -95,6 +104,7 @@ id: JobJanitor groups: - JanitorHead + - JanitorNeck # DeltaV - JanitorJumpsuit - JanitorGloves - CommonBackpack @@ -106,6 +116,7 @@ id: JobBotanist groups: - BotanistHead + - BotanistNeck # DeltaV - BotanistJumpsuit - BotanistBackpack - BotanistOuterClothing @@ -116,6 +127,7 @@ id: JobClown groups: - ClownHead + - Scarfs # DeltaV - ClownJumpsuit - ClownBackpack - ClownOuterClothing @@ -128,6 +140,7 @@ groups: - MimeHead - MimeMask + - MimeNeck # DeltaV - MimeJumpsuit - MimeBackpack - MimeOuterClothing @@ -137,6 +150,8 @@ - type: roleLoadout id: JobMusician groups: + - MusicianJumpsuit # DeltaV + - MusicianNeck # DeltaV - MusicianBackpack - MusicianOuterClothing - Glasses @@ -159,6 +174,7 @@ id: JobCargoTechnician groups: - CargoTechnicianHead + - CargoTechnicianNeck # DeltaV - CargoTechnicianJumpsuit - CargoTechnicianBackpack - CargoTechnicianOuterClothing @@ -169,6 +185,7 @@ - type: roleLoadout id: JobSalvageSpecialist groups: + - SalvageSpecialistNeck # DeltaV - SalvageSpecialistBackpack - SalvageSpecialistOuterClothing - SalvageSpecialistShoes @@ -198,6 +215,7 @@ id: JobStationEngineer groups: - StationEngineerHead + - StationEngineerNeck # DeltaV - StationEngineerJumpsuit - StationEngineerBackpack - StationEngineerOuterClothing @@ -208,6 +226,8 @@ - type: roleLoadout id: JobAtmosphericTechnician groups: + - StationEngineerHead # DeltaV - Safety First! + - AtmosphericTechnicianNeck # DeltaV - AtmosphericTechnicianJumpsuit - AtmosphericTechnicianBackpack - AtmosphericTechnicianOuterClothing @@ -260,13 +280,15 @@ - SecurityBackpack - SecurityBelt - HeadofSecurityOuterClothing - - SecurityShoes + #- SecurityShoes + - HeadOfSecurityShoes # DeltaV - Trinkets - type: roleLoadout id: JobWarden groups: - WardenHead + - SecurityNeck # DeltaV - WardenJumpsuit - SecurityBackpack - SecurityBelt @@ -278,6 +300,7 @@ id: JobSecurityOfficer groups: - SecurityHead + - SecurityNeck # DeltaV - SecurityJumpsuit - SecurityBackpack - SecurityOuterClothing @@ -300,6 +323,8 @@ - type: roleLoadout id: JobSecurityCadet groups: + - SecurityCadetHead # DeltaV + - SecurityNeck # DeltaV - SecurityCadetJumpsuit - SecurityBackpack - Trinkets @@ -323,6 +348,7 @@ id: JobMedicalDoctor groups: - MedicalDoctorHead + - MedicalDoctorNeck # DeltaV - MedicalMask - MedicalDoctorJumpsuit - MedicalGloves @@ -345,11 +371,14 @@ id: JobChemist groups: - MedicalMask + - ChemistNeck # DeltaV - ChemistJumpsuit - - MedicalGloves +# - MedicalGloves + - ChemistGloves # DeltaV - ChemistBackpack - ChemistOuterClothing - - MedicalShoes +# - MedicalShoes + - ChemistShoes # DeltaV - Trinkets - type: roleLoadout @@ -357,6 +386,7 @@ groups: - ParamedicHead - MedicalMask + - MedicalDoctorNeck # DeltaV - ParamedicJumpsuit - MedicalGloves - ParamedicBackpack diff --git a/Resources/Prototypes/Nyanotrasen/Loadouts/role_loadouts.yml b/Resources/Prototypes/Nyanotrasen/Loadouts/role_loadouts.yml index 2b4ad4b8e37..e3684bee40e 100644 --- a/Resources/Prototypes/Nyanotrasen/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Nyanotrasen/Loadouts/role_loadouts.yml @@ -25,6 +25,7 @@ id: JobPrisonGuard groups: - PrisonGuardHead + - SecurityNeck # DeltaV - PrisonGuardJumpsuit - SecurityBackpack - SecurityOuterClothing diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/musician.yml b/Resources/Prototypes/Roles/Jobs/Civilian/musician.yml index 33ffebbf750..015f6cdf167 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/musician.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/musician.yml @@ -21,8 +21,8 @@ - type: startingGear id: MusicianGear equipment: - jumpsuit: ClothingUniformJumpsuitMusician - eyes: ClothingEyesGlassesSunglasses + #jumpsuit: ClothingUniformJumpsuitMusician # DeltaV - Commented out for loadout options + #eyes: ClothingEyesGlassesSunglasses # DeltaV - Commented out for loadout options shoes: ClothingShoesBootsLaceup id: MusicianPDA ears: ClothingHeadsetService diff --git a/Resources/Prototypes/Roles/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml index 1a6ae90de68..fe736ad7ca1 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/captain.yml @@ -48,8 +48,8 @@ - type: startingGear id: CaptainGear equipment: - shoes: ClothingShoesBootsLaceup + #shoes: ClothingShoesBootsLaceup - DeltaV - Commented out for loadout options eyes: ClothingEyesGlassesSunglasses - gloves: ClothingHandsGlovesCaptain + #gloves: ClothingHandsGlovesCaptain - DeltaV - Commented out for loadout options id: CaptainPDA ears: ClothingHeadsetAltCommand diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 8b3390acbc6..3f4ce4d0212 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -74,8 +74,8 @@ - type: startingGear id: HoPGear equipment: - shoes: ClothingShoesLeather # DeltaV - HoP needs something better than plebe shoes. + #shoes: ClothingShoesLeather # DeltaV - Commented for Loadouts id: HoPPDA - gloves: ClothingHandsGlovesHop + #gloves: ClothingHandsGlovesHop # DeltaV - Commented for Loadouts ears: ClothingHeadsetHoP # DeltaV - HoP is now a service role, replaces their all channels headset. belt: BoxFolderClipboard From e2d136d207dd1cf109c4cdf1381f7fd8eb9d7fb8 Mon Sep 17 00:00:00 2001 From: DeltaV-Bot <135767721+DeltaV-Bot@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:32:52 +0000 Subject: [PATCH 235/235] Automatic changelog update --- Resources/Changelog/DeltaVChangelog.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index 26b8131c401..3d499510244 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -2558,3 +2558,10 @@ id: 375 time: '2024-06-01T21:31:06.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/1230 +- author: Haltell + changes: + - message: Nanotrasen uniform stores resotcked; many more options for your style! + type: Add + id: 376 + time: '2024-06-01T21:32:32.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/1269