diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index d29a4c2d96e..46492e19abe 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -30,7 +30,7 @@ jobs: run_linters: name: Run Linters needs: start_gate - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 10 steps: @@ -75,6 +75,7 @@ jobs: bash tools/ci/install_node.sh bash tools/ci/install/install_spaceman_dmm.sh dreamchecker bash tools/ci/install_ripgrep.sh + sudo apt install -y python3-pip tools/bootstrap/python -c '' - name: Give Linters A Go id: linter-setup @@ -140,19 +141,25 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v4 + - name: Restore Flyway + uses: actions/cache@v4 + with: + path: ~/flyway + key: ${{ runner.os }}-flyway-${{ hashFiles('dependencies.sh') }} - name: Restore BYOND cache uses: actions/cache@v4 with: path: ~/BYOND key: ${{ runner.os }}-byond-${{ hashFiles('dependencies.sh') }} + - name: Install flyway + run: | + bash tools/ci/install/install_flyway.sh - name: Setup database run: | sudo systemctl start mysql - mysql -u root -proot -e 'CREATE DATABASE ss13_ci;' - mysql -u root -proot ss13_ci < SQL/database_schema_prefixed.sql - mysql -u root -proot ss13_ci < SQL/unified_schema.sql - # mysql -u root -proot -e 'CREATE DATABASE tg_ci_prefixed;' - # mysql -u root -proot tg_ci_prefixed < SQL/tgstation_schema_prefixed.sql + mysql -u root -proot -e 'CREATE DATABASE ss13;' + source dependencies.sh + ~/flyway/flyway-$FLYWAY_VERSION/flyway -user=root -password=root -url=jdbc:mariadb://localhost:3306/ss13 -locations="filesystem:sql/migrations" migrate - name: Install rust-g run: | bash tools/ci/install/install_rust_g.sh diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 87869168fcf..44df201905e 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,6 +8,7 @@ "donkie.vscode-tgstation-test-adapter", "anturk.dmi-editor", "aaron-bond.better-comments", - "ss13.opendream" + "ss13.opendream", + "tamasfe.even-better-toml" ] } diff --git a/README.md b/README.md index b0228569fea..a957b846c10 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,12 @@ On **May 9, 2022** we have changed the way to compile the codebase. ## SQL Setup The SQL backend for the library and stats tracking requires a MariaDB server. -Your server details go in /config/legacy/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql. -More detailed setup instructions arecoming soon, for now ask in our Discord. +Your server details go in /config/legacy/dbconfig.txt. + +Flyway is used for setup and migration. Run the migrations in `sql/migrations` against your database, and everything should just work. +We do not use table prefixes. + +More detailed setup instructions are coming soon, for now ask in our Discord. todo: update this section @@ -62,6 +66,7 @@ These are also the folders you are likely going to encounter while managing the - /players: player data, like saves and characters get dumped in here - /tmp: server scratch space - /assets - for asset generation + - /config - used as scratch space for config You only need to make the top level folders (e.g. config, data) static folders in TGS4. diff --git a/SQL/database_schema.sql b/SQL/database_schema.sql deleted file mode 100644 index f1b50df6fd4..00000000000 --- a/SQL/database_schema.sql +++ /dev/null @@ -1,439 +0,0 @@ -/** - * make sure to bump schema version and mark changes in database_changelog.md! - * - * default prefix is rp_ - * find replace case sensitive %_PREFIX_% - * PRESERVE ANY vr_'s! We need to replace those tables and features at some point, that's how we konw. - **/ - --- core -- - --- --- Table structure for table `schema_revision` --- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%schema_revision` ( - `major` TINYINT(3) unsigned NOT NULL, - `minor` TINYINT(3) unsigned NOT NULL, - `date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`major`, `minor`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- persistence -- - --- SSpersistence modules/bulk_entity -CREATE TABLE IF NOT EXISTS `%_PREFIX_%persistence_bulk_entity` ( - `id` INT(24) NOT NULL AUTO_INCREMENT, - `generation` INT(11) NOT NULL, - `persistence_key` VARCHAR(64) NOT NULL, - `level_id` VARCHAR(64) NOT NULL, - `data` MEDIUMTEXT, - `round_id` INT(11) NOT NULL, - PRIMARY KEY (`id`), - INDEX(`level_id`, `generation`, `persistence_key`), - INDEX(`level_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- SSpersistence modules/level_objects -CREATE TABLE IF NOT EXISTS `%_PREFIX_%persistence_static_level_objects` ( - `generation` INT(11) NOT NULL, - `object_id` VARCHAR(64) NOT NULL, - `level_id` VARCHAR(64) NOT NULL, - `data` MEDIUMTEXT NOT NULL, - PRIMARY KEY(`generation`, `object_id`, `level_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- SSpersistence modules/level_objects -CREATE TABLE IF NOT EXISTS `%_PREFIX_%persistence_static_map_objects` ( - `generation` INT(11) NOT NULL, - `object_id` VARCHAR(64) NOT NULL, - `map_id` VARCHAR(64) NOT NULL, - `data` MEDIUMTEXT NOT NULL, - PRIMARY KEY(`generation`, `object_id`, `map_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- SSpersistence modules/level_objects -CREATE TABLE IF NOT EXISTS `%_PREFIX_%persistence_static_global_objects` ( - `generation` INT(11) NOT NULL, - `object_id` VARCHAR(64) NOT NULL, - `data` MEDIUMTEXT NOT NULL, - PRIMARY KEY(`generation`, `object_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- SSpersistence modules/level_objects -CREATE TABLE IF NOT EXISTS `%_PREFIX_%persistence_dynamic_objects` ( - `generation` INT(11) NOT NULL, - `object_id` INT(24) NOT NULL AUTO_INCREMENT, - `level_id` VARCHAR(64) NOT NULL, - `prototype_id` VARCHAR(256) NOT NULL, - `status` INT(24) NOT NULL DEFAULT 0, - `data` MEDIUMTEXT NOT NULL, - `x` INT(8) NOT NULL, - `y` INT(8) NoT NULL, - PRIMARY KEY(`object_id`, `generation`), - INDEX(`object_id`), - INDEX(`level_id`, `generation`), - INDEX(`prototype_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- SSpersistence modules/spatial_metadata -CREATE TABLE IF NOT EXISTS `%_PREFIX_%persistence_level_metadata` ( - `created` DATETIME NOT NULL DEFAULT Now(), - `saved` DATETIME NOT NULL, - `saved_round_id` INT(11) NOT NULL, - `level_id` VARCHAR(64) NOT NULL, - `data` MEDIUMTEXT NOT NULL, - `generation` INT(11) NOT NULL, - PRIMARY KEY(`level_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- SSpersistence modules/string_kv -CREATE TABLE IF NOT EXISTS `%_PREFIX_%persistence_string_kv` ( - `created` DATETIME NOT NULL DEFAULT Now(), - `modified` DATETIME NOT NULL, - `key` VARCHAR(64) NOT NULL, - `value` MEDIUMTEXT NULL, - `group` VARCHAR(64) NOT NULL, - `revision` INT(11) NOT NULL, - PRIMARY KEY(`key`, `group`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- photography -- - --- picture table -- --- used to store data about pictures -- --- hash is in sha1 format. -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%pictures` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `hash` char(40) NOT NULL, - `created` datetime NOT NULL DEFAULT Now(), - `width` int NOT NULL, - `height` int NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `hash` (`hash`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- photograph table -- --- used to store data about photographs -- --- picture is picture hash in picture table -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%photographs` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `picture` char(40) NOT NULL, - `created` datetime NOT NULL DEFAULT Now(), - `scene` MEDIUMTEXT null, - `desc` MEDIUMTEXT null, - CONSTRAINT `linked_picture` FOREIGN KEY (`picture`) - REFERENCES `%_PREFIX_%pictures` (`hash`) - ON DELETE CASCADE - ON UPDATE CASCADE, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Players -- - --- Player lookup table -- --- Used to look up player ID from ckey, as well as -- --- store last computerid/ip for a ckey. -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%player_lookup` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `ckey` varchar(32) NOT NULL, - `firstseen` datetime NOT NULL, - `lastseen` datetime NOT NULL, - `ip` varchar(18) NOT NULL, - `computerid` varchar(32) NOT NULL, - `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', - `playerid` int(11), - PRIMARY KEY (`id`), - UNIQUE KEY `ckey` (`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Primary player table -- --- Allows for one-to-many player-ckey association. -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%player` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `flags` int(24) NOT NULL DEFAULT 0, - `firstseen` datetime NOT NULL DEFAULT Now(), - `lastseen` datetime NOT NULL, - `misc` MEDIUMTEXT NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Playtime / JEXP -- - --- Role Time Table - Master -- --- Stores total role time. -- - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%playtime` ( - `player` INT(11) NOT NULL, - `roleid` VARCHAR(64) NOT NULL, - `minutes` INT UNSIGNED NOT NULL, - PRIMARY KEY(`player`, `roleid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Role Time - Logging -- --- Stores changes in role time -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%playtime_log` ( - `player` INT(11), - `id` BIGINT(20) NOT NULL AUTO_INCREMENT, - `roleid` VARCHAR(64) NOT NULL, - `delta` INT(11) NOT NULL, - `datetime` TIMESTAMP NOT NULL DEFAULT NOW() ON UPDATE NOW(), - PRIMARY KEY (`id`), - KEY `player` (`player`), - KEY `roleid` (`roleid`), - KEY `datetime` (`datetime`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -DELIMITER $$ -CREATE TRIGGER `playtimeTlogupdate` AFTER UPDATE ON `%_PREFIX_%playtime` FOR EACH ROW BEGIN INSERT into `%_PREFIX_%playtime_log` (player, roleid, delta) VALUES (NEW.player, NEW.roleid, NEW.minutes-OLD.minutes); -END -$$ -CREATE TRIGGER `playtimeTloginsert` AFTER INSERT ON `%_PREFIX_%playtime` FOR EACH ROW BEGIN INSERT into `%_PREFIX_%playtime_log` (player, roleid, delta) VALUES (NEW.player, NEW.roleid, NEW.minutes); -END -$$ -CREATE TRIGGER `playtimeTlogdelete` AFTER DELETE ON `%_PREFIX_%playtime` FOR EACH ROW BEGIN INSERT into `%_PREFIX_%playtime_log` (player, roleid, delta) VALUES (OLD.player, OLD.roleid, 0-OLD.minutes); -END -$$ -DELIMITER ; - - --- Preferences -- - --- Stores game preferences -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%game_preferences` ( - `player` INT(11) NOT NULL, - `entries` MEDIUMTEXT NOT NULL, - `misc` MEDIUMTEXT NOT NULL, - `keybinds` MEDIUMTEXT NOT NULL, - `toggles` MEDIUMTEXT NOT NULL, - `modified` DATETIME NOT NULL, - `version` INT(11) NOT NULL, - PRIMARY KEY (`player`), - CONSTRAINT `linked_player` FOREIGN KEY (`player`) - REFERENCES `%_PREFIX_%player` (`id`) - ON DELETE CASCADE - ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Security - Ipintel -- - --- Ipintel Cache Table -- --- Stores cache entries for IPIntel -- --- IP is in INET_ATON. -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%ipintel` ( - `ip` INT(10) unsigned NOT NULL, - `date` TIMESTAMP NOT NULL DEFAULT NOW() ON UPDATE NOW(), - `intel` double NOT NULL DEFAULT '0', - PRIMARY KEY (`ip`), - KEY `idx_ipintel` (`ip`, `intel`, `date`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- --- Table structure for table `round` --- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%round` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `initialize_datetime` DATETIME NOT NULL, - `start_datetime` DATETIME NULL, - `shutdown_datetime` DATETIME NULL, - `end_datetime` DATETIME NULL, - `server_ip` INT(10) UNSIGNED NOT NULL, - `server_port` SMALLINT(5) UNSIGNED NOT NULL, - `commit_hash` CHAR(40) NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- Connection log -- --- Logs all connections to the server. -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%connection_log` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `serverip` varchar(45) NOT NULL, - `ckey` varchar(32) NOT NULL, - `ip` varchar(45) NOT NULL, - `computerid` varchar(32) NOT NULL, - PRIMARY KEY(`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- /datum/character - Character Table -- -CREATE TABLE IF NOT EXISTS `%_PREFIX_%character` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `created` DATETIME NOT NULL DEFAULT Now(), - `last_played` DATETIME NULL, - `last_persisted` DATETIME NULL, - `playerid` INT(11) NOT NULL, - `canonical_name` VARCHAR(128) NOT NULL, - `persist_data` MEDIUMTEXT NULL, - `character_type` VARCHAR(64) NOT NULL, - PRIMARY KEY(`id`), - CONSTRAINT `character_has_player` FOREIGN KEY (`playerid`) - REFERENCES `%_PREFIX_%player` (`id`) - ON DELETE CASCADE - ON UPDATE CASCADE, - UNIQUE (`playerid`, `canonical_name`, `character_type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%admin` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `ckey` varchar(32) NOT NULL, - `rank` varchar(32) NOT NULL DEFAULT 'Administrator', - `level` int(2) NOT NULL DEFAULT '0', - `flags` int(16) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%admin_log` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `adminckey` varchar(32) NOT NULL, - `adminip` varchar(18) NOT NULL, - `log` text NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%ban` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `bantime` datetime NOT NULL, - `serverip` varchar(32) NOT NULL, - `bantype` varchar(32) NOT NULL, - `reason` text NOT NULL, - `job` varchar(32) DEFAULT NULL, - `duration` int(11) NOT NULL, - `rounds` int(11) DEFAULT NULL, - `expiration_time` datetime NOT NULL, - `ckey` varchar(32) NOT NULL, - `computerid` varchar(32) NOT NULL, - `ip` varchar(32) NOT NULL, - `a_ckey` varchar(32) NOT NULL, - `a_computerid` varchar(32) NOT NULL, - `a_ip` varchar(32) NOT NULL, - `who` text NOT NULL, - `adminwho` text NOT NULL, - `edits` text, - `unbanned` tinyint(1) DEFAULT NULL, - `unbanned_datetime` datetime DEFAULT NULL, - `unbanned_ckey` varchar(32) DEFAULT NULL, - `unbanned_computerid` varchar(32) DEFAULT NULL, - `unbanned_ip` varchar(32) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%feedback` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `time` datetime NOT NULL, - `round_id` int(8) NOT NULL, - `var_name` varchar(32) NOT NULL, - `var_value` int(16) DEFAULT NULL, - `details` text, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 ; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%poll_option` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `pollid` int(11) NOT NULL, - `text` varchar(255) NOT NULL, - `percentagecalc` tinyint(1) NOT NULL DEFAULT '1', - `minval` int(3) DEFAULT NULL, - `maxval` int(3) DEFAULT NULL, - `descmin` varchar(32) DEFAULT NULL, - `descmid` varchar(32) DEFAULT NULL, - `descmax` varchar(32) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%poll_question` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `polltype` varchar(16) NOT NULL DEFAULT 'OPTION', - `starttime` datetime NOT NULL, - `endtime` datetime NOT NULL, - `question` varchar(255) NOT NULL, - `adminonly` tinyint(1) DEFAULT '0', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%poll_textreply` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `pollid` int(11) NOT NULL, - `ckey` varchar(32) NOT NULL, - `ip` varchar(18) NOT NULL, - `replytext` text NOT NULL, - `adminrank` varchar(32) NOT NULL DEFAULT 'Player', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%poll_vote` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `pollid` int(11) NOT NULL, - `optionid` int(11) NOT NULL, - `ckey` varchar(255) NOT NULL, - `ip` varchar(16) NOT NULL, - `adminrank` varchar(32) NOT NULL, - `rating` int(2) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%privacy` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `ckey` varchar(32) NOT NULL, - `option` varchar(128) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%death` ( - `id` INT(11) NOT NULL AUTO_INCREMENT , - `pod` TEXT NOT NULL COMMENT 'Place of death' , - `coord` TEXT NOT NULL COMMENT 'X, Y, Z POD' , - `tod` DATETIME NOT NULL COMMENT 'Time of death' , - `job` TEXT NOT NULL , - `special` TEXT NOT NULL , - `name` TEXT NOT NULL , - `byondkey` TEXT NOT NULL , - `laname` TEXT NOT NULL COMMENT 'Last attacker name' , - `lakey` TEXT NOT NULL COMMENT 'Last attacker key' , - `gender` TEXT NOT NULL , - `bruteloss` INT(11) NOT NULL , - `brainloss` INT(11) NOT NULL , - `fireloss` INT(11) NOT NULL , - `oxyloss` INT(11) NOT NULL , - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%karma` ( - `id` INT(11) NOT NULL AUTO_INCREMENT , - `spendername` TEXT NOT NULL , - `spenderkey` TEXT NOT NULL , - `receivername` TEXT NOT NULL , - `receiverkey` TEXT NOT NULL , - `receiverrole` TEXT NOT NULL , - `receiverspecial` TEXT NOT NULL , - `isnegative` TINYINT(1) NOT NULL , - `spenderip` TEXT NOT NULL , - `time` DATETIME NOT NULL , - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%karmatotals` ( - `id` INT(11) NOT NULL AUTO_INCREMENT , - `byondkey` TEXT NOT NULL , - `karma` INT(11) NOT NULL , - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%library` ( - `id` INT(11) NOT NULL AUTO_INCREMENT , - `author` TEXT NOT NULL , - `title` TEXT NOT NULL , - `content` TEXT NOT NULL , - `category` TEXT NOT NULL , - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `%_PREFIX_%population` ( - `id` INT(11) NOT NULL AUTO_INCREMENT , - `playercount` INT(11) NULL DEFAULT NULL , - `admincount` INT(11) NULL DEFAULT NULL , - `time` DATETIME NOT NULL , - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/SQL/unified_schema.sql b/SQL/unified_schema.sql deleted file mode 100644 index 32441c5e22e..00000000000 --- a/SQL/unified_schema.sql +++ /dev/null @@ -1,17 +0,0 @@ -/** - * make sure to bump schema version and mark changes in database_changelog.md! - * - * you MUST use unified_ as a prefix. - * - * unified schema for citadel, **sync changes to both servers.** - **/ - --- --- Table structure for table `schema_revision` --- -CREATE TABLE IF NOT EXISTS `unified_schema_revision` ( - `major` TINYINT(3) unsigned NOT NULL, - `minor` TINYINT(3) unsigned NOT NULL, - `date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`major`, `minor`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/citadel.dme b/citadel.dme index abf6e203047..7746ae80ceb 100644 --- a/citadel.dme +++ b/citadel.dme @@ -686,6 +686,10 @@ #include "code\controllers\subsystem\sound\_sound.dm" #include "code\controllers\subsystem\sound\channel_manager.dm" #include "code\controllers\subsystem\sound\soundbyte_manager.dm" +#include "code\controllers\toml_config\toml_config_entry.dm" +#include "code\controllers\toml_config\toml_configuration.dm" +#include "code\controllers\toml_config\entries\backend.dm" +#include "code\controllers\toml_config\entries\backend.repository.dm" #include "code\datums\ability.dm" #include "code\datums\ability_handler.dm" #include "code\datums\access.dm" @@ -2249,6 +2253,7 @@ #include "code\modules\admin\verbs\debug\fucky_wucky.dm" #include "code\modules\admin\verbs\debug\profiling.dm" #include "code\modules\admin\verbs\debug\reestablish_db_connection.dm" +#include "code\modules\admin\verbs\debug\reload_configuration.dm" #include "code\modules\admin\verbs\debug\spawn.dm" #include "code\modules\admin\verbs\SDQL2\SDQL_2.dm" #include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm" diff --git a/code/__DEFINES/controllers/_repository.dm b/code/__DEFINES/controllers/_repository.dm index b2f5702f752..9f08048dc8d 100644 --- a/code/__DEFINES/controllers/_repository.dm +++ b/code/__DEFINES/controllers/_repository.dm @@ -7,6 +7,9 @@ //* This is here in [code/__DEFINES/controllers/_repositories.dm] for compile order reasons. *// /datum/controller/subsystem/repository/proc/__init_repositories() +//* This is here in [code/__DEFINES/controllers/_repositories.dm] for compile order reasons. *// +/datum/controller/subsystem/repository/proc/__get_all_repositories() + // todo: redo recover logic; maybe /datum/controller as a whole should be brushed up #define REPOSITORY_DEF(what) \ GLOBAL_REAL(RS##what, /datum/controller/repository/##what); \ @@ -27,4 +30,12 @@ GLOBAL_REAL(RS##what, /datum/controller/repository/##what); \ ..(); \ RS##what.Initialize(); \ } \ +/datum/controller/subsystem/repository/__get_all_repositories() { \ + . = ..(); \ + . += RS##what; \ +} \ /datum/controller/repository/##what + +/// Returned from /datum/controller/repository's fetch_or_defer() if we don't have something +/// on hand, but also don't know that it doesn't exist. +#define REPOSITORY_FETCH_DEFER "defer" diff --git a/code/__DEFINES/controllers/dbcore.dm b/code/__DEFINES/controllers/dbcore.dm index afc6709805c..19df14d5588 100644 --- a/code/__DEFINES/controllers/dbcore.dm +++ b/code/__DEFINES/controllers/dbcore.dm @@ -17,6 +17,11 @@ */ #define DB_MINOR_VERSION 3 +//* Tables *// + +/// Prefixes are currently disabled. +#define DB_PREFIX_TABLE_NAME(TABLE) TABLE + //* Misc *// /// pass this into duplicate_key on mass_insert() to overwrite old values diff --git a/code/__HELPERS/nameof.dm b/code/__HELPERS/nameof.dm index 7cd5777f465..6e625578ae6 100644 --- a/code/__HELPERS/nameof.dm +++ b/code/__HELPERS/nameof.dm @@ -4,6 +4,12 @@ * datum may be null, but it does need to be a typed var. **/ #define NAMEOF(datum, X) (#X || ##datum.##X) +/** + * NAMEOF: Compile time checked variable name to string conversion + * evaluates to a string equal to "X", but compile errors if X isn't a var on datum. + * datum may be null, but it does need to be a typed var. + **/ +#define NAMEOF_PROC(datum, X) (#X || ##datum.##X()) /** * NAMEOF that actually works in static definitions because src::type requires src to be defined diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 7fcdc575e3a..5c4a63dc189 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1,23 +1,12 @@ /** * Holds procs designed to help with filtering text * Contains groups: - * ! SQL sanitization * ! Text sanitization * ! Text searches * ! Text modification * ! Misc */ -/** - *! SQL sanitization - */ - -/proc/format_table_name(table) - return CONFIG_GET(string/sql_server_prefix) + table - -/proc/format_unified_table_name(table) - return CONFIG_GET(string/sql_unified_prefix) + table - /** *! Text sanitization */ diff --git a/code/controllers/README.md b/code/controllers/README.md new file mode 100644 index 00000000000..1364ca595ee --- /dev/null +++ b/code/controllers/README.md @@ -0,0 +1,13 @@ +# Controllers + +Backend controllers orchestrating the game. + +## Globals + +Many, but not all, controllers are accessible from anywhere in the code with standardized names. + +- Master: As the name implies, the Master Controller performs init, shutdown, and acts as a process scheduler during a round. +- Failsafe: A controller that ensures the Master Controller is running properly. +- Configuration: A global datum that holds server configuration. +- RSname: Repository controllers storing /datum/prototype's that can be queried. +- SSname: Subsystems that handle init behavior, ticking, and other game functions.. diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index b933dea3bd4..5474503030a 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -8,19 +8,6 @@ var/list/entries var/list/entries_by_type - // var/list/maplist - // var/datum/map_config/defaultmap - - /* - var/list/modes // allowed modes - var/list/gamemode_cache - var/list/votable_modes // votable modes - var/list/storyteller_cache - var/list/mode_names - var/list/mode_reports - var/list/mode_false_report_weight - */ - var/motd /// If the configuration is loaded @@ -59,7 +46,7 @@ loaded = TRUE if (Master) - Master.OnConfigLoad() + Master.on_config_loaded() /datum/controller/configuration/proc/full_wipe() if(IsAdminAdvancedProcCall()) diff --git a/code/controllers/configuration/entries/database.dm b/code/controllers/configuration/entries/database.dm index 3fbc32d38fe..f930d31acbf 100644 --- a/code/controllers/configuration/entries/database.dm +++ b/code/controllers/configuration/entries/database.dm @@ -4,9 +4,6 @@ /datum/config_entry/string/sql_server_prefix protection = CONFIG_ENTRY_LOCKED -/datum/config_entry/string/sql_unified_prefix - protection = CONFIG_ENTRY_LOCKED - /datum/config_entry/string/sql_address protection = CONFIG_ENTRY_HIDDEN | CONFIG_ENTRY_LOCKED config_entry_value = "localhost" diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm index 93d5897ff45..252845318f5 100644 --- a/code/controllers/controller.dm +++ b/code/controllers/controller.dm @@ -15,6 +15,9 @@ // todo: kil var/verbose_logging = FALSE +/datum/controller/vv_delete() + return FALSE + /** * Called to initialize a controller. * diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 7d0c002dad7..60a8334a375 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -117,6 +117,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new load_configuration() if(!config) config = new + if(!Configuration) + Configuration = new + Configuration.Initialize() //# 2. set up random seed if(!random_seed) @@ -883,10 +886,12 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/datum/controller/subsystem/SS = S SS.StopLoadingMap() -/datum/controller/master/proc/OnConfigLoad() +/datum/controller/master/proc/on_config_loaded() for (var/thing in subsystems) var/datum/controller/subsystem/SS = thing - SS.OnConfigLoad() + SS.on_config_loaded() + for(var/datum/controller/repository/repository in SSrepository.get_all_repositories()) + repository.on_config_loaded() /** * CitRP snowflake special: Check if any subsystems are sleeping. diff --git a/code/controllers/repository.dm b/code/controllers/repository.dm index 402a48e30dd..a53aa8e7ffc 100644 --- a/code/controllers/repository.dm +++ b/code/controllers/repository.dm @@ -26,6 +26,17 @@ /// expected type of prototype var/expected_type + /// database key; this is immutable. + /// * persistence is disabled if this is not set + var/database_key + /// store version + /// * persistence is disabled if this is not set + /// * migration is triggered if this doesn't match a loaded entry + /// * this should only ever be incremented. + var/store_version + /// store enabled? Updated by config reloads. + var/store_enabled = FALSE + /// by-id lookup var/list/id_lookup /// by-type lookup @@ -34,6 +45,11 @@ /// fetched subtype lists var/tmp/list/subtype_lists + /// 'doesn't exist' cache for DB loads + var/tmp/list/doesnt_exist_cache + var/const/doesnt_exist_cache_trim_at = 1000 + var/const/doesnt_exist_cache_trim_to = 500 + /// temporary id to path lookup used during init // todo: figure out a way to not do this, this is bad var/tmp/list/init_reverse_lookup_shim @@ -43,6 +59,7 @@ type_lookup = list() subtype_lists = list() init_reverse_lookup_shim = list() + doesnt_exist_cache = list() for(var/datum/prototype/casted as anything in subtypesof(expected_type)) if(initial(casted.abstract_type) == casted) continue @@ -57,6 +74,14 @@ init_reverse_lookup_shim = null return ..() +/datum/controller/repository/vv_edit_var(var_name, var_value, mass_edit, raw_edit) + switch(var_name) + if(NAMEOF(src, store_version), NAMEOF(src, database_key), NAMEOF(src, store_enabled)) + return FALSE + if(NAMEOF(src, expected_type)) + return FALSE + return ..() + /** * Repository Recover() * @@ -82,6 +107,13 @@ . = FALSE src.subtype_lists = list() +/** + * Called when config is reloaded. + */ +/datum/controller/repository/proc/on_config_loaded() + SHOULD_CALL_PARENT(TRUE) + store_enabled = Configuration.get_entry(/datum/toml_config_entry/backend/repository/persistence) && database_key && store_version + /** * regenerates entries, kicking out anything that's in the way */ @@ -105,6 +137,7 @@ * * * Allows passing in a prototype instance which will be returned as itself. * Useful for procs that should accept types, IDs, *and* instances. + * * Unlike fetch local / fetch or defer, this **can** sleep! * * prototypes returned should never, ever be modified * @@ -119,7 +152,102 @@ if(init_reverse_lookup_shim) var/potential_path = init_reverse_lookup_shim[type_or_id] return fetch(potential_path) - return id_lookup[type_or_id] + . = id_lookup[type_or_id] + if(.) + return + if(!store_enabled) + return + if(doesnt_exist_cache[type_or_id]) + return + return handle_db_load(type_or_id) + else if(ispath(type_or_id)) + . = type_lookup[type_or_id] + if(.) + return + if(initial(type_or_id.abstract_type) == type_or_id) + CRASH("tried to fetch an abstract prototype") + var/datum/prototype/loading = new type_or_id + loading.hardcoded = TRUE + load(loading) + return loading + else if(istype(type_or_id)) + return type_or_id + else + CRASH("what?") + +/** + * Fetches a prototype by type or ID. + * + * * Allows passing in a prototype instance which will be returned as itself. + * Useful for procs that should accept types, IDs, *and* instances. + * * If something doesn't exist and we don't know if it exists in the database, we throw a runtime error. + * * If fetching a hardcoded path, this should generally be used as it never sleeps. + * + * prototypes returned should never, ever be modified + * + * @return prototype instance or null + */ +/datum/controller/repository/proc/fetch_local_or_throw(datum/prototype/type_or_id) as /datum/prototype + RETURN_TYPE(/datum/prototype) + // todo: optimize + if(isnull(type_or_id)) + return + else if(istext(type_or_id)) + if(init_reverse_lookup_shim) + var/potential_path = init_reverse_lookup_shim[type_or_id] + return fetch_local_or_throw(potential_path) + . = id_lookup[type_or_id] + if(.) + return + if(!store_enabled) + return + if(doesnt_exist_cache[type_or_id]) + return + CRASH("fetch_local_or_throw of [type_or_id] couldn't determine if id existed without a fetch.") + else if(ispath(type_or_id)) + . = type_lookup[type_or_id] + if(.) + return + if(initial(type_or_id.abstract_type) == type_or_id) + CRASH("tried to fetch an abstract prototype") + var/datum/prototype/loading = new type_or_id + loading.hardcoded = TRUE + load(loading) + return loading + else if(istype(type_or_id)) + return type_or_id + else + CRASH("what?") + +/** + * Fetches a prototype by type or ID. + * + * * Allows passing in a prototype instance which will be returned as itself. + * Useful for procs that should accept types, IDs, *and* instances. + * * If something doesn't exist and we don't know if it exists in the database, we return + * REPOSITORY_FETCH_DEFER. The caller should invoke normal fetch() at a time when sleeping is allowed. + * + * prototypes returned should never, ever be modified + * + * @return prototype instance or null + */ +/datum/controller/repository/proc/fetch_or_defer(datum/prototype/type_or_id) as /datum/prototype + RETURN_TYPE(/datum/prototype) + // todo: optimize + if(isnull(type_or_id)) + return + else if(istext(type_or_id)) + if(init_reverse_lookup_shim) + var/potential_path = init_reverse_lookup_shim[type_or_id] + return fetch_or_defer(potential_path) + . = id_lookup[type_or_id] + if(.) + return + if(!store_enabled) + return + if(doesnt_exist_cache[type_or_id]) + return + return REPOSITORY_FETCH_DEFER else if(ispath(type_or_id)) . = type_lookup[type_or_id] if(.) @@ -165,7 +293,7 @@ for(var/datum/prototype/casted as anything in subtypesof(path)) if(initial(casted.abstract_type) == casted) continue - var/datum/prototype/instance = fetch(casted) + var/datum/prototype/instance = fetch_local_or_throw(casted) generating += instance return generating @@ -184,7 +312,11 @@ * After this call, the repository now owns the instance, not whichever system created it. */ /datum/controller/repository/proc/register(datum/prototype/instance) - return load(instance) + . = load(instance) + if(!.) + return + if(store_enabled) + handle_db_store(instance) //* Private API *// @@ -203,7 +335,7 @@ CRASH("attempted to load an instance that collides with a currently loaded instance on type.") if(!instance.register()) . = FALSE - CRASH("instance refused to unregister. this is undefined behavior.") + CRASH("instance failed to register. this is undefined behavior.") id_lookup[instance.id] = instance if(instance.hardcoded) // invalidate cache @@ -222,7 +354,7 @@ PROTECTED_PROC(TRUE) if(!instance.unregister()) . = FALSE - CRASH("instance refused to unregister. this is undefined behavior.") + CRASH("instance failed to unregister. this is undefined behavior.") id_lookup -= instance.id if(instance.hardcoded) // invalidate cache @@ -230,3 +362,90 @@ subtype_lists = list() type_lookup -= instance.type return TRUE + +/** + * Perform migration on a data-list from the database. + * + * * Edit the passed in list directly. + * * This should update to latest. + * + * todo: proc to auto-migrate everything. + */ +/datum/controller/repository/proc/migrate(list/modifying, from_version) + PROTECTED_PROC(TRUE) + +/datum/controller/repository/proc/handle_db_store(datum/prototype/instance) + doesnt_exist_cache -= instance.id + + // intentionally allow admin proccalls to bypass checks in NewQuery() + var/old_usr = usr + usr = null + + var/datum/db_query/store_query = SSdbcore.NewQuery( + "INSERT INTO " + DB_PREFIX_TABLE_NAME("backend_repository") + "(repository, id, version, data) VALUES \ + (:repo, :id, :version, :data) ON DUPLICATE KEY UPDATE data = :data, modifiedTime = Now(), version = :version", + list( + "repo" = database_key, + "id" = instance.id, + "version" = store_version, + "data" = json_encode(instance.serialize()), + ), + ) + + usr = old_usr + + store_query.Execute(TRUE) + qdel(store_query) + +/datum/controller/repository/proc/handle_db_load(instance_id) + if(doesnt_exist_cache[instance_id]) + return + + // intentionally allow admin proccalls to bypass checks in NewQuery() + var/old_usr = usr + usr = null + + var/datum/db_query/load_query = SSdbcore.NewQuery( + "SELECT version, data FROM " + DB_PREFIX_TABLE_NAME("backend_repository") + " WHERE repository = :repo, id = :id", + list( + "repo" = database_key, + "id" = instance_id, + ), + ) + + usr = old_usr + + load_query.Execute(TRUE) + + if(!length(load_query.item)) + mark_doesnt_exist(instance_id) + else + var/list/fetched = load_query.item[1] + var/version = fetched[1] + var/encoded_data = fetched[2] + var/list/decoded_data = json_decode(encoded_data) + var/migrated = FALSE + + if(version < store_version) + migrate(decoded_data, version) + migrated = TRUE + else if(version == store_version) + mark_doesnt_exist(instance_id) + CRASH("[version] was not less or eq to [store_version]. something's very wrong!") + + var/datum/prototype/loaded_instance = new expected_type + loaded_instance.deserialize(decoded_data) + if(!load(loaded_instance)) + mark_doesnt_exist(instance_id) + CRASH("[instance_id] failed to load into the repository during database load!") + . = loaded_instance + + if(migrated) + handle_db_store(loaded_instance) + + qdel(load_query) + +/datum/controller/repository/proc/mark_doesnt_exist(instance_id) + doesnt_exist_cache[instance_id] = TRUE + if(length(doesnt_exist_cache) > doesnt_exist_cache_trim_at) + doesnt_exist_cache.len = doesnt_exist_cache_trim_to diff --git a/code/controllers/repository/designs.dm b/code/controllers/repository/designs.dm index 23ed3186a24..2667472b765 100644 --- a/code/controllers/repository/designs.dm +++ b/code/controllers/repository/designs.dm @@ -4,6 +4,7 @@ REPOSITORY_DEF(designs) name = "Repository - Designs" expected_type = /datum/prototype/design + database_key = "design" //* caches *// diff --git a/code/controllers/repository/guidebook.dm b/code/controllers/repository/guidebook.dm index f5ea86eb187..33c878a9fb7 100644 --- a/code/controllers/repository/guidebook.dm +++ b/code/controllers/repository/guidebook.dm @@ -1,6 +1,7 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2023 Citadel Station developers. *// +// todo: this shouldn't be a repository, it can just be a controller REPOSITORY_DEF(guidebook) name = "Repository - Guidebook" expected_type = /datum/prototype/guidebook_section diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index bf1333d28b4..3287b34004f 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -421,7 +421,7 @@ state = SS_PAUSING /// Called after the config has been loaded or reloaded. -/datum/controller/subsystem/proc/OnConfigLoad() +/datum/controller/subsystem/proc/on_config_loaded() return /** diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm index eff83427a20..ff29b03f16e 100644 --- a/code/controllers/subsystem/assets.dm +++ b/code/controllers/subsystem/assets.dm @@ -191,7 +191,7 @@ SUBSYSTEM_DEF(assets) /datum/controller/subsystem/assets/proc/get_dynamic_item_url_by_name(name) return dynamic_asset_items_by_name[name]?.get_url() -/datum/controller/subsystem/assets/OnConfigLoad() +/datum/controller/subsystem/assets/on_config_loaded() var/newtransporttype = /datum/asset_transport/browse_rsc switch (CONFIG_GET(string/asset_transport)) if ("webroot") diff --git a/code/controllers/subsystem/characters/storage.dm b/code/controllers/subsystem/characters/storage.dm index 5a1b6045010..bb84401ee12 100644 --- a/code/controllers/subsystem/characters/storage.dm +++ b/code/controllers/subsystem/characters/storage.dm @@ -33,7 +33,7 @@ // last played is not updated by this proc // everything else can though! var/datum/db_query/update_query = SSdbcore.NewQuery( - "UPDATE [format_table_name("character")] \ + "UPDATE [DB_PREFIX_TABLE_NAME("character")] \ SET[persisting? " last_persisted = NOW()," : ""] canonical_name = :name, persist_data = :data, \ playerid = :pid \ WHERE id = :id", @@ -48,7 +48,7 @@ qdel(update_query) else var/datum/db_query/insert_query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("character")] \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("character")] \ (`created`, `last_played`, `last_persisted`, `playerid`, `canonical_name`, \ `persist_data`, `character_type`) \ VALUES (NOW(), NULL, [persisting? "NOW" : "NULL"], :pid, :name, :data, :type)", @@ -85,7 +85,7 @@ var/datum/db_query/load_query = SSdbcore.NewQuery( "SELECT `id` FROM \ - [format_table_name("character")] WHERE playerid = :id AND canonical_name = :name AND character_type = :type", + [DB_PREFIX_TABLE_NAME("character")] WHERE playerid = :id AND canonical_name = :name AND character_type = :type", list( "id" = playerid, "canonical_name" = name, @@ -134,7 +134,7 @@ var/datum/db_query/load_query = SSdbcore.NewQuery( "SELECT `created`, `last_played`, `last_persisted`, `playerid`, `canonical_name`, `persist_data`, `character_type` FROM \ - [format_table_name("character")] WHERE id = :id", + [DB_PREFIX_TABLE_NAME("character")] WHERE id = :id", list( "id" = id, ) @@ -190,7 +190,7 @@ . = list() var/datum/db_query/iteration_query = SSdbcore.ExecuteQuery( - "SELECT id FROM [format_table_name("character")] WHERE playerid = :id", + "SELECT id FROM [DB_PREFIX_TABLE_NAME("character")] WHERE playerid = :id", list( "id" = playerid ) @@ -223,7 +223,7 @@ // section below can never be allowed to runtime var/datum/db_query/mark_query = SSdbcore.ExecuteQuery( - "UPDATE [format_table_name("character")] SET last_played = NOW() WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("character")] SET last_played = NOW() WHERE id = :id", list( "id" = id ) diff --git a/code/controllers/subsystem/dbcore/_dbcore.dm b/code/controllers/subsystem/dbcore/_dbcore.dm index 106a7fe0911..7e2b57d43fe 100644 --- a/code/controllers/subsystem/dbcore/_dbcore.dm +++ b/code/controllers/subsystem/dbcore/_dbcore.dm @@ -46,7 +46,7 @@ SUBSYSTEM_DEF(dbcore) //This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem if(SSdbcore.Connect()) var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery( - "UPDATE [format_table_name("round")] SET shutdown_datetime = Now() WHERE id = :round_id", + "UPDATE [DB_PREFIX_TABLE_NAME("round")] SET shutdown_datetime = Now() WHERE id = :round_id", list("round_id" = GLOB.round_id) ) query_round_shutdown.Execute() @@ -114,7 +114,7 @@ SUBSYSTEM_DEF(dbcore) if(CONFIG_GET(flag/sql_enabled)) if(Connect()) log_world("Database connection established.") - var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1") + var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [DB_PREFIX_TABLE_NAME("schema_revision")] ORDER BY date DESC LIMIT 1") query_db_version.Execute() if(query_db_version.NextRow()) db_major = text2num(query_db_version.item[1]) @@ -135,7 +135,7 @@ SUBSYSTEM_DEF(dbcore) if(!Connect()) return var/datum/db_query/query_round_initialize = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)", list("internet_address" = world.internet_address || "0", "port" = "[world.port]") ) query_round_initialize.Execute(async = FALSE) @@ -147,7 +147,7 @@ SUBSYSTEM_DEF(dbcore) if(!Connect()) return var/datum/db_query/query_round_start = SSdbcore.NewQuery( - "UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id", + "UPDATE [DB_PREFIX_TABLE_NAME("round")] SET start_datetime = Now() WHERE id = :round_id", list("round_id" = GLOB.round_id) ) query_round_start.Execute() @@ -157,7 +157,7 @@ SUBSYSTEM_DEF(dbcore) if(!Connect()) return var/datum/db_query/query_round_end = SSdbcore.NewQuery( - "UPDATE [format_table_name("round")] SET end_datetime = Now() WHERE id = :round_id", + "UPDATE [DB_PREFIX_TABLE_NAME("round")] SET end_datetime = Now() WHERE id = :round_id", list("round_id" = GLOB.round_id) ) query_round_end.Execute() diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index 170e520e4cf..e0f1dfd5cbb 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -20,7 +20,7 @@ SUBSYSTEM_DEF(ipintel) /// max retries var/max_retries = 1 -/datum/controller/subsystem/ipintel/OnConfigLoad() +/datum/controller/subsystem/ipintel/on_config_loaded() . = ..() enabled = !!CONFIG_GET(flag/ipintel_enabled) consequetive_errors = 0 @@ -139,7 +139,7 @@ SUBSYSTEM_DEF(ipintel) /datum/controller/subsystem/ipintel/proc/ipintel_cache_fetch_impl(address) PRIVATE_PROC(TRUE) var/datum/db_query/fetch = SSdbcore.NewQuery( - "SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) FROM [format_table_name("ipintel")] WHERE ip = INET_ATON(:ip)", + "SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) FROM [DB_PREFIX_TABLE_NAME("ipintel")] WHERE ip = INET_ATON(:ip)", list( "ip" = address, ) @@ -167,7 +167,7 @@ SUBSYSTEM_DEF(ipintel) /datum/controller/subsystem/ipintel/proc/ipintel_cache_store_impl(datum/ipintel/entry) PRIVATE_PROC(TRUE) var/datum/db_query/update = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON(:ip), :intel) \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("ipintel")] (ip, intel) VALUES (INET_ATON(:ip), :intel) \ ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()", list( "ip" = entry.address, diff --git a/code/controllers/subsystem/materials.dm b/code/controllers/subsystem/materials.dm index 4cd8fae0414..edd53d80467 100644 --- a/code/controllers/subsystem/materials.dm +++ b/code/controllers/subsystem/materials.dm @@ -108,12 +108,16 @@ SUBSYSTEM_DEF(materials) // todo: optimize . = list() for(var/i in 1 to length(L)) - var/key = L[i] - var/datum/prototype/material/resolved = RSmaterials.fetch(key) - if(isnull(resolved)) - continue + var/datum/prototype/material/key = L[i] var/value = L[key] - .[resolved.id] = value + if(istype(key)) + key = key.id + else if(ispath(key)) + key = initial(key.id) + else if(istext(key)) + else + CRASH("what? '[key]'") + .[key] = value /** * ensures a list is full of material references for keys @@ -126,10 +130,14 @@ SUBSYSTEM_DEF(materials) . = list() for(var/i in 1 to length(L)) var/key = L[i] - var/datum/prototype/material/resolved = RSmaterials.fetch(key) - if(isnull(resolved)) - continue var/value = L[key] + var/datum/prototype/material/resolved = RSmaterials.fetch_or_defer(key) + switch(resolved) + if(null) + continue + if(REPOSITORY_FETCH_DEFER) + // todo: handle this + continue .[resolved] = value /** @@ -142,9 +150,15 @@ SUBSYSTEM_DEF(materials) . = list() for(var/i in 1 to length(L)) var/key = L[i] - var/value = L[key] - var/datum/prototype/material/resolved = RSmaterials.fetch(value) - .[key] = resolved?.id + var/datum/prototype/material/value = L[key] + if(istype(value)) + value = value.id + else if(ispath(value)) + value = initial(value.id) + else if(istext(value)) + else + CRASH("what? '[value]'") + .[key] = value /** * ensures a list is full of material references for values @@ -157,7 +171,12 @@ SUBSYSTEM_DEF(materials) for(var/i in 1 to length(L)) var/key = L[i] var/value = L[key] - var/datum/prototype/material/resolved = RSmaterials.fetch(value) + var/datum/prototype/material/resolved = RSmaterials.fetch_or_defer(value) + switch(resolved) + if(REPOSITORY_FETCH_DEFER) + // todo: handle this + else + value = resolved .[key] = resolved /** diff --git a/code/controllers/subsystem/persistence/modules/bulk_entity.dm b/code/controllers/subsystem/persistence/modules/bulk_entity.dm index 7d04d37568e..a17d389e00b 100644 --- a/code/controllers/subsystem/persistence/modules/bulk_entity.dm +++ b/code/controllers/subsystem/persistence/modules/bulk_entity.dm @@ -19,7 +19,7 @@ for(var/datum/bulk_entity_chunk/chunk as anything in chunks) var/datum/db_query/query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("persistence_bulk_entity")] \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_bulk_entity")] \ (generation, persistence_key, level_id, data, round_id) \ VALUES (:generation, :persistence, :level, :data, :round)", list( @@ -47,7 +47,7 @@ usr = null var/datum/db_query/query = SSdbcore.NewQuery( - "SELECT data FROM [format_table_name("persistence_bulk_entity")] \ + "SELECT data FROM [DB_PREFIX_TABLE_NAME("persistence_bulk_entity")] \ WHERE generation = :generation AND persistence_key = :persistence AND level_id = :level", list( "generation" = generation, @@ -85,7 +85,7 @@ SSdbcore.dangerously_block_on_multiple_unsanitized_queries( list( - "TRUNCATE TABLE [format_table_name("persistence_bulk_entity")]", + "TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_bulk_entity")]", ), ) @@ -99,7 +99,7 @@ usr = null SSdbcore.RunQuery( - "DELETE FROM [format_table_name("persistence_bulk_entity")] WHERE level_id = :level", + "DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_bulk_entity")] WHERE level_id = :level", list( "level" = level_id, ), diff --git a/code/controllers/subsystem/persistence/modules/level_objects.dm b/code/controllers/subsystem/persistence/modules/level_objects.dm index 43072527d78..8016594e631 100644 --- a/code/controllers/subsystem/persistence/modules/level_objects.dm +++ b/code/controllers/subsystem/persistence/modules/level_objects.dm @@ -20,7 +20,7 @@ switch(entity.obj_persist_static_mode) if(OBJ_PERSIST_STATIC_MODE_LEVEL) query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("persistence_static_level_objects")] (generation, object_id, level_id, data) \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")] (generation, object_id, level_id, data) \ VALUES (:generation, :object_id, :level_id, :data) ON DUPLICATE KEY UPDATE \ data = VALUES(data)", list( @@ -32,7 +32,7 @@ ) if(OBJ_PERSIST_STATIC_MODE_MAP) query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("persistence_static_map_objects")] (generation, object_id, map_id, data) \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_static_map_objects")] (generation, object_id, map_id, data) \ VALUES (:generation, :object_id, :map_id, :data) ON DUPLICATE KEY UPDATE \ data = VALUES(data)", list( @@ -44,7 +44,7 @@ ) if(OBJ_PERSIST_STATIC_MODE_GLOBAL) query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("persistence_static_global_objects")] (generation, object_id, data) \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_static_global_objects")] (generation, object_id, data) \ VALUES (:generation, :object_id, :data) ON DUPLICATE KEY UPDATE \ data = VALUES(data)", list( @@ -79,7 +79,7 @@ var/datum/db_query/query if(entity.obj_persist_dynamic_id != PERSISTENCE_DYNAMIC_ID_AUTOSET) query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("persistence_dynamic_objects")] (id, generation, status, data, prototype_id, level_id, x, y) \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] (id, generation, status, data, prototype_id, level_id, x, y) \ VALUES (:status, :data, :prototype, :level, :x, :y) ON DUPLICATE KEY UPDATE \ x = VALUES(x), y = VALUES(y), data = VALUES(data), prototype = VALUES(prototype), level = VALUES(level), \ status = VALUES(status)", @@ -97,7 +97,7 @@ query.warn_execute() else query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("persistence_dynamic_objects")] (status, data, prototype_id, level_id, x, y) \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] (status, data, prototype_id, level_id, x, y) \ VALUES (:status, :data, :prototype, :level, :x, :y)", list( "status" = entity.obj_persist_dynamic_status, @@ -139,7 +139,7 @@ var/datum/db_query/query = SSdbcore.NewQuery( "SELECT object_id, prototype_id, status, data, x, y \ - FROM [format_table_name("persistence_dynamic_objects")] \ + FROM [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] \ WHERE level_id = :level AND generation = :generation", list( "generation" = generation, @@ -200,7 +200,7 @@ switch(entity.obj_persist_static_mode) if(OBJ_PERSIST_STATIC_MODE_GLOBAL) query = SSdbcore.NewQuery( - "SELECT data FROM [format_table_name("persistence_static_global_objects")] \ + "SELECT data FROM [DB_PREFIX_TABLE_NAME("persistence_static_global_objects")] \ WHERE object_id = :object AND generation = :generation", list( "object" = entity.obj_persist_static_id, @@ -209,7 +209,7 @@ ) if(OBJ_PERSIST_STATIC_MODE_LEVEL) query = SSdbcore.NewQuery( - "SELECT data FROM [format_table_name("persistence_static_level_objects")] \ + "SELECT data FROM [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")] \ WHERE object_id = :object AND level_id = :level AND generation = :generation", list( "object" = entity.obj_persist_static_id, @@ -220,7 +220,7 @@ bind_id = level_id if(OBJ_PERSIST_STATIC_MODE_MAP) query = SSdbcore.NewQuery( - "SELECT data FROM [format_table_name("persistence_static_map_objects")] \ + "SELECT data FROM [DB_PREFIX_TABLE_NAME("persistence_static_map_objects")] \ WHERE object_id = :object AND map_id = :map AND generation = :generation", list( "object" = entity.obj_persist_static_id, @@ -258,9 +258,9 @@ SSdbcore.dangerously_block_on_multiple_unsanitized_queries( list( - "TRUNCATE TABLE [format_table_name("persistence_static_map_objects")]", - "TRUNCATE TABLE [format_table_name("persistence_static_level_objects")]", - "TRUNCATE TABLE [format_table_name("persistence_static_global_objects")]", + "TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_static_map_objects")]", + "TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")]", + "TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_static_global_objects")]", ), ) @@ -276,7 +276,7 @@ usr = null SSdbcore.RunQuery( - "DELETE FROM [format_table_name("persistence_static_level_objects")] WHERE level_id = :level", + "DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")] WHERE level_id = :level", list( "level" = level_id, ), @@ -294,7 +294,7 @@ usr = null SSdbcore.RunQuery( - "DELETE FROM [format_table_name("persistence_static_map_objects")] WHERE map_id = :map", + "DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_static_map_objects")] WHERE map_id = :map", list( "map" = map_id, ), @@ -312,7 +312,7 @@ usr = null SSdbcore.RunQuery( - "DELETE FROM [format_table_name("persistence_static_global_objects")]", + "DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_static_global_objects")]", ) usr = intentionally_allow_admin_proccall @@ -327,7 +327,7 @@ usr = null SSdbcore.RunQuery( - "TRUNCATE TABLE [format_table_name("persistence_dynamic_objects")]", + "TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")]", ) usr = intentionally_allow_admin_proccall @@ -342,7 +342,7 @@ usr = null SSdbcore.RunQuery( - "DELETE FROM [format_table_name("persistence_dynamic_objects")] WHERE level_id = :level", + "DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] WHERE level_id = :level", list( "level" = level_id, ), diff --git a/code/controllers/subsystem/persistence/modules/spatial_metadata.dm b/code/controllers/subsystem/persistence/modules/spatial_metadata.dm index 8318e210040..22d8c1a1b4b 100644 --- a/code/controllers/subsystem/persistence/modules/spatial_metadata.dm +++ b/code/controllers/subsystem/persistence/modules/spatial_metadata.dm @@ -66,7 +66,7 @@ var/datum/db_query/query = SSdbcore.NewQuery( "SELECT TIMESTAMPDIFF(HOUR, saved, NOW()), saved_round_id, data, generation \ - FROM [format_table_name("persistence_level_metadata")] \ + FROM [DB_PREFIX_TABLE_NAME("persistence_level_metadata")] \ WHERE level_id = :level", list( "level" = level_id, @@ -103,7 +103,7 @@ src.round_id_saved = GLOB.round_number SSdbcore.RunQuery( - "INSERT INTO [format_table_name("persistence_level_metadata")] (saved, saved_round_id, level_id, data, generation) \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_level_metadata")] (saved, saved_round_id, level_id, data, generation) \ VALUES (Now(), :round, :level, :data, :generation) ON DUPLICATE KEY UPDATE \ data = VALUES(data), generation = VALUES(generation), saved_round_id = VALUES(saved_round_id), saved = VALUES(saved)", list( diff --git a/code/controllers/subsystem/persistence/modules/string_kkv.dm b/code/controllers/subsystem/persistence/modules/string_kkv.dm index 6ae51d36a22..66b1ff79468 100644 --- a/code/controllers/subsystem/persistence/modules/string_kkv.dm +++ b/code/controllers/subsystem/persistence/modules/string_kkv.dm @@ -60,7 +60,7 @@ var/oldusr = usr usr = null var/datum/db_query/query = SSdbcore.NewQuery( - "SELECT `value` FROM [format_table_name("persistence_string_kkv")] WHERE `group` = :group AND `key` = :key", + "SELECT `value` FROM [DB_PREFIX_TABLE_NAME("persistence_string_kkv")] WHERE `group` = :group AND `key` = :key", list( "group" = group, "key" = key @@ -79,7 +79,7 @@ var/oldusr = usr usr = null var/datum/db_query/query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("persistence_string_kkv")] (`group`, `key`, `value`) VALUES (:group, :key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`), `modified` = Now(), `revision` = `revision` + 1", + "INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_string_kkv")] (`group`, `key`, `value`) VALUES (:group, :key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`), `modified` = Now(), `revision` = `revision` + 1", list( "group" = group, "key" = key, diff --git a/code/controllers/subsystem/persistence/world.dm b/code/controllers/subsystem/persistence/world.dm index 30ec4b2cfd2..e1f82acdc8d 100644 --- a/code/controllers/subsystem/persistence/world.dm +++ b/code/controllers/subsystem/persistence/world.dm @@ -234,21 +234,21 @@ for(var/datum/map_level_persistence/level_metadata as anything in ordered_level_metadata) SSdbcore.RunQuery( - "DELETE FROM [format_table_name("persistence_bulk_entity")] WHERE level_id = :level, generation != :generation", + "DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_bulk_entity")] WHERE level_id = :level, generation != :generation", list( "level" = level_metadata.level_id, "generation" = level_metadata.generation, ), ) SSdbcore.RunQuery( - "DELETE FROM [format_table_name("persistence_static_level_objects")] WHERE level_id = :level, generation != :generation", + "DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")] WHERE level_id = :level, generation != :generation", list( "level" = level_metadata.level_id, "generation" = level_metadata.generation, ), ) SSdbcore.RunQuery( - "DELETE FROM [format_table_name("persistence_dynamic_objects")] WHERE level_id = :level, generation != :generation", + "DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] WHERE level_id = :level, generation != :generation", list( "level" = level_metadata.level_id, "generation" = level_metadata.generation, diff --git a/code/controllers/subsystem/photography.dm b/code/controllers/subsystem/photography.dm index 7e4fbbcbccd..37f23abe4c9 100644 --- a/code/controllers/subsystem/photography.dm +++ b/code/controllers/subsystem/photography.dm @@ -115,7 +115,7 @@ SUBSYSTEM_DEF(photography) var/datum/db_query/query = SSdbcore.NewQuery( {" - INSERT INTO [format_table_name("pictures")] + INSERT INTO [DB_PREFIX_TABLE_NAME("pictures")] (`hash`, `width`, `height`) VALUES (:hash, :width, :height) "}, @@ -140,7 +140,7 @@ SUBSYSTEM_DEF(photography) var/datum/db_query/query = SSdbcore.NewQuery( {" SELECT `width`, `height` - FROM [format_table_name("pictures")] + FROM [DB_PREFIX_TABLE_NAME("pictures")] WHERE `hash` = :hash "}, list( @@ -195,7 +195,7 @@ SUBSYSTEM_DEF(photography) var/datum/db_query/query = SSdbcore.NewQuery( {" - INSERT INTO [format_table_name("photographs")] + INSERT INTO [DB_PREFIX_TABLE_NAME("photographs")] (`picture`, `scene`, `desc`) VALUES (:hash, :scene, :desc) "}, @@ -220,7 +220,7 @@ SUBSYSTEM_DEF(photography) var/datum/db_query/query = SSdbcore.NewQuery( {" SELECT `picture`, `scene`, `desc` - FROM [format_table_name("photographs")] + FROM [DB_PREFIX_TABLE_NAME("photographs")] WHERE `id` = :id "}, list( diff --git a/code/controllers/subsystem/playtime.dm b/code/controllers/subsystem/playtime.dm index 617cdbf0335..5f1d1c2ec22 100644 --- a/code/controllers/subsystem/playtime.dm +++ b/code/controllers/subsystem/playtime.dm @@ -46,7 +46,7 @@ SUBSYSTEM_DEF(playtime) "player" = playerid ) C.persistent.playtime_queued = list() - SSdbcore.MassInsertLegacy(format_table_name("playtime"), built, duplicate_key = "ON DUPLICATE KEY UPDATE minutes = minutes + VALUES(minutes)") + SSdbcore.MassInsertLegacy(DB_PREFIX_TABLE_NAME("playtime"), built, duplicate_key = "ON DUPLICATE KEY UPDATE minutes = minutes + VALUES(minutes)") /** * returns a list of playtime roles diff --git a/code/controllers/subsystem/repository.dm b/code/controllers/subsystem/repository.dm index 04db037a497..4464288f229 100644 --- a/code/controllers/subsystem/repository.dm +++ b/code/controllers/subsystem/repository.dm @@ -11,3 +11,6 @@ SUBSYSTEM_DEF(repository) __create_repositories() __init_repositories() return SS_INIT_SUCCESS + +/datum/controller/subsystem/repository/proc/get_all_repositories() + return __get_all_repositories() diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 44f7944a2b9..5a204c1320d 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -89,6 +89,10 @@ SUBSYSTEM_DEF(statpanels) STATPANEL_DATA_CLICK(config.stat_key(), config.stat_entry(), "\ref[config]") else STATPANEL_DATA_LINE("FATAL - NO CONFIG") + if(config) + STATPANEL_DATA_CLICK(Configuration.stat_key(), Configuration.stat_entry(), "\ref[Configuration]") + else + STATPANEL_DATA_LINE("FATAL - NO CONFIG (NEW)") STATPANEL_DATA_ENTRY("BYOND:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)") if(Master) STATPANEL_DATA_CLICK(Master.stat_key(), Master.stat_entry(), "\ref[Master]") diff --git a/code/controllers/toml_config/README.md b/code/controllers/toml_config/README.md new file mode 100644 index 00000000000..025e5b593f1 --- /dev/null +++ b/code/controllers/toml_config/README.md @@ -0,0 +1,3 @@ +# Configuration Module + +Experimental new TOML configuration to replace old configuration with eventually. diff --git a/code/controllers/toml_config/entries/backend.dm b/code/controllers/toml_config/entries/backend.dm new file mode 100644 index 00000000000..25e66949ac3 --- /dev/null +++ b/code/controllers/toml_config/entries/backend.dm @@ -0,0 +1,6 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/datum/toml_config_entry/backend + abstract_type = /datum/toml_config_entry/backend + category = "backend" diff --git a/code/controllers/toml_config/entries/backend.repository.dm b/code/controllers/toml_config/entries/backend.repository.dm new file mode 100644 index 00000000000..9c812b38a10 --- /dev/null +++ b/code/controllers/toml_config/entries/backend.repository.dm @@ -0,0 +1,14 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/datum/toml_config_entry/backend/repository + abstract_type = /datum/toml_config_entry/backend/repository + category = "backend.repository" + +/datum/toml_config_entry/backend/repository/persistence + key = "persistence" + desc = {" + Enable repository persistence. This requires the database to be available. Without this, most persistence + features will not function. + "} + default = TRUE diff --git a/code/controllers/toml_config/toml_config_entry.dm b/code/controllers/toml_config/toml_config_entry.dm new file mode 100644 index 00000000000..88eb4e6039a --- /dev/null +++ b/code/controllers/toml_config/toml_config_entry.dm @@ -0,0 +1,107 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Config entry. + * + * Supports at time of writing: + * * numbers + * * strings + * * (nested) lists + * * (nested) dictionaries + * + * Supports at time of writing auditing VV edits to: + * * numbers + * * strings + */ +/datum/toml_config_entry + abstract_type = /datum/toml_config_entry + /// key / name + var/key + /// category / where this is + var/category + + /// description of this entry + var/desc + + /// default value + var/default + /// current value + var/value + + /// vv edit disallowed + /// * Does not stop get_entry and set_entry from setting our value. Those are not considered vv-protected. + var/vv_locked = FALSE + /// vv read disallowed + /// * Does not automatically imply [vv_locked]. + /// * Does not stop get_entry and set_entry from pulling our value. Those are not considered vv-protected. + var/vv_secret = FALSE + /// sensitive + /// * Requires get_sensitive_entry() and set_sensitive_entry() to read/write. + /// * Does not actually imply [vv_locked] and [vv_secret]. + var/sensitive = FALSE + +/datum/toml_config_entry/vv_edit_var(var_name, var_value, mass_edit, raw_edit) + switch(var_name) + if(NAMEOF(src, default)) + return FALSE + if(NAMEOF(src, value)) + if(vv_locked) + return FALSE + if(NAMEOF(src, key)) + return FALSE + if(NAMEOF(src, category)) + return FALSE + if(NAMEOF(src, desc)) + return FALSE + if(NAMEOF(src, vv_locked)) + return FALSE + if(NAMEOF(src, vv_secret)) + return FALSE + if(NAMEOF(src, sensitive)) + return FALSE + return ..() + +/datum/toml_config_entry/vv_get_var(var_name, resolve) + switch(var_name) + if(NAMEOF(src, value)) + if(vv_locked) + return "-- secret --" + return ..() + +/datum/toml_config_entry/CanProcCall(procname) + switch(procname) + if(NAMEOF_PROC(src, New), NAMEOF_PROC(src, Destroy)) + return FALSE + if(NAMEOF_PROC(src, reset)) + return FALSE + if(NAMEOF_PROC(src, apply)) + return FALSE + return ..() + +/datum/toml_config_entry/vv_delete() + return FALSE + +/** + * Called once when resetting. + */ +/datum/toml_config_entry/proc/reset() + if(isnum(default)) + value = default + else if(istext(default)) + value = default + else if(islist(default)) + value = deep_copy_list(default) + else + CRASH("unexpected value in default.") + +/** + * Called once with the value from each load. + * + * Can be used to overlay values. + * + * @params + * * raw_config_value - Raw parsed data. We own the reference to this once this proc is called. + */ +/datum/toml_config_entry/proc/apply(raw_config_value) + value = raw_config_value diff --git a/code/controllers/toml_config/toml_configuration.dm b/code/controllers/toml_config/toml_configuration.dm new file mode 100644 index 00000000000..ec2984bcdde --- /dev/null +++ b/code/controllers/toml_config/toml_configuration.dm @@ -0,0 +1,169 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +// todo: maybe rename to config? or keep it as Configuration to keep with naming scheme of other 'system / backend' modules like the MC? +GLOBAL_REAL(Configuration, /datum/controller/toml_configuration) + +// todo: /datum/controller/configuration +/datum/controller/toml_configuration + /// Entries by type. + VAR_PRIVATE/list/datum/toml_config_entry/typed_entries + /// Entries as same structure as the underlying toml/json + VAR_PRIVATE/list/datum/toml_config_entry/keyed_entries + +/datum/controller/toml_configuration/CanProcCall(procname) + switch(procname) + if(NAMEOF_PROC(src, New), NAMEOF_PROC(src, Destroy), NAMEOF_PROC(src, Initialize)) + return FALSE + if(NAMEOF_PROC(src, get_entry), NAMEOF_PROC(src, set_entry)) + return FALSE + if(NAMEOF_PROC(src, get_sensitive_entry), NAMEOF_PROC(src, set_sensitive_entry)) + return FALSE + if(NAMEOF_PROC(src, reload), NAMEOF_PROC(src, reset), NAMEOF_PROC(src, load), NAMEOF_PROC(src, recursively_load_from_list)) + return FALSE + return ..() + +/datum/controller/toml_configuration/vv_edit_var(var_name, var_value, mass_edit, raw_edit) + switch(var_name) + if(NAMEOF(src, keyed_entries)) + return FALSE + return ..() + +/datum/controller/toml_configuration/New() + if(Configuration != src) + if(Configuration) + qdel(Configuration) + Configuration = src + +/datum/controller/toml_configuration/Initialize() + keyed_entries = list() + typed_entries = list() + for(var/datum/toml_config_entry/path as anything in typesof(/datum/toml_config_entry)) + if(initial(path.abstract_type) == path) + continue + var/datum/toml_config_entry/entry = new path + typed_entries[entry.type] = entry + var/list/nesting = splittext(entry.category, ".") + var/list/current_list = keyed_entries + for(var/i in 1 to length(nesting)) + LAZYINITLIST(current_list[nesting[i]]) + current_list = current_list[nesting[i]] + current_list[entry.key] = entry + reload() + +/datum/controller/toml_configuration/stat_key() + return "Configuration (New):" + +/datum/controller/toml_configuration/stat_entry() + return "Edit" + +/** + * HEY! LISTEN! By calling this proc you are affirming that: + * + * * The entry type you are passing in is static and not a variable that can be tampered with. + * * The value you get will be immediately consumed in a non-VV-able manner. + */ +/datum/controller/toml_configuration/proc/get_sensitive_entry(datum/toml_config_entry/entry_type) + // todo: cache / optimize + var/datum/toml_config_entry/entry = typed_entries[entry_type] + if(!entry) + return + if(!entry.sensitive) + CRASH("attempted to get sensitive entry with sensitive get entry.") + return entry.value + +/** + * HEY! LISTEN! By calling this proc you are affirming that: + * + * * The entry type you are passing in is static and not a variable that can be tampered with. + * * The value you are passing in is trusted and validated and not a variable that can be tampered with. + */ +/datum/controller/toml_configuration/proc/set_sensitive_entry(datum/toml_config_entry/entry_type, value) + // todo: cache / optimize + var/datum/toml_config_entry/entry = typed_entries[entry_type] + if(!entry) + return + if(entry.sensitive) + CRASH("attempted to set non-sensitive entry with sensitive set entry.") + entry.value = value + +/datum/controller/toml_configuration/proc/get_entry(datum/toml_config_entry/entry_type) + // todo: cache / optimize + var/datum/toml_config_entry/entry = typed_entries[entry_type] + if(!entry) + return + if(entry.sensitive) + CRASH("attempted to get sensitive entry with normal get entry.") + return entry.value + +/datum/controller/toml_configuration/proc/set_entry(datum/toml_config_entry/entry_type, value) + // todo: cache / optimize + var/datum/toml_config_entry/entry = typed_entries[entry_type] + if(!entry) + return + if(entry.sensitive) + CRASH("attempted to set sensitive entry with normal set entry.") + entry.value = value + +/datum/controller/toml_configuration/proc/admin_reload() + reload() + +/** + * Automatically loads default config, and the server's config file. + * + * todo: allow for overriding directories + */ +/datum/controller/toml_configuration/proc/reload() + reset() + load("config.default/config.toml") + load("config/config.toml") + +/** + * Resets the configuration. + */ +/datum/controller/toml_configuration/proc/reset() + for(var/path in typed_entries) + var/datum/toml_config_entry/entry = typed_entries[path] + entry.reset() + +/** + * Loads from a given layer. + * * This will not reset the configuration. Repeated calls to load will allow for layered configuration. + * + * HEY! LISTEN! By calling this proc you are affirming that: + * * The file you are passing in is trusted and not a variable that can be tampered with via VV. + */ +/datum/controller/toml_configuration/proc/load(filelike) + var/list/decoded + if(istext(filelike)) + if(!fexists(filelike)) + CRASH("failed to load [filelike]: does not exist") + decoded = rustg_read_toml_file(filelike) + else if(isfile(filelike)) + // noa path, it might be rsc cache; rust_g can't read that directly. + fdel("tmp/config/loading.toml") + fcopy(filelike, "tmp/config/loading.toml") + decoded = rustg_read_toml_file("tmp/config/loading.toml") + fdel("tmp/config/loading.toml") + if(!decoded) + CRASH("failed to decode config [filelike]!") + + recursively_load_from_list(decoded, keyed_entries) + +/datum/controller/toml_configuration/proc/recursively_load_from_list(list/decoded_list, list/entry_list) + if(!decoded_list || !entry_list) + return + for(var/key in decoded_list) + var/value = decoded_list[key] + if(islist(value)) + var/list/next_entry_list = entry_list[key] + if(!islist(next_entry_list)) + // todo: warn + else + recursively_load_from_list(value, next_entry_list[key]) + else + var/datum/toml_config_entry/entry = entry_list[key] + if(!istype(entry)) + // todo: warn + else + entry.apply(value) diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index c93c2373e7b..745ad7051e4 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -1,3 +1,10 @@ +/** + * Called when an admin attempts to delete us with introspection tools. + */ +/datum/proc/vv_delete() + . = TRUE + qdel(src) + /datum/proc/CanProcCall(procname) return TRUE diff --git a/code/game/machinery/telecomms/blackbox.dm b/code/game/machinery/telecomms/blackbox.dm index 61e83517e4c..fb9cef3c7c4 100644 --- a/code/game/machinery/telecomms/blackbox.dm +++ b/code/game/machinery/telecomms/blackbox.dm @@ -182,7 +182,7 @@ var/obj/machinery/blackbox_recorder/blackbox var/round_id var/datum/db_query/query = SSdbcore.RunQuery( - "SELECT MAX(round_id) AS round_id FROM [format_table_name("feedback")]", + "SELECT MAX(round_id) AS round_id FROM [DB_PREFIX_TABLE_NAME("feedback")]", list() ) @@ -195,7 +195,7 @@ var/obj/machinery/blackbox_recorder/blackbox for(var/datum/feedback_variable/FV in feedback) SSdbcore.RunQuery( - "INSERT INTO [format_table_name("feedback")] VALUES (null, Now(), :round_id, :variable, :value, :details)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("feedback")] VALUES (null, Now(), :round_id, :variable, :value, :details)", list( "round_id" = "[round_id]", "variable" = "[FV.get_variable()]", diff --git a/code/game/objects/materials.dm b/code/game/objects/materials.dm index 8f6e73ce51c..7a8c5573657 100644 --- a/code/game/objects/materials.dm +++ b/code/game/objects/materials.dm @@ -91,7 +91,12 @@ if(islist(material_parts)) var/list/parts = list() for(var/key in material_parts) - parts[key] = RSmaterials.fetch(key) + var/datum/prototype/material/result = RSmaterials.fetch_or_defer(key) + switch(result) + if(REPOSITORY_FETCH_DEFER) + // todo: handle this + result = null + parts[key] = result update_material_multi(parts) else if(material_parts == MATERIAL_DEFAULT_DISABLED) else if(material_parts == MATERIAL_DEFAULT_ABSTRACTED) @@ -99,7 +104,12 @@ // skip specifying parts because abstracted update_material_multi() else - update_material_single((material_parts = RSmaterials.fetch(material_parts))) + var/datum/prototype/material/result = RSmaterials.fetch_or_defer(material_parts) + switch(result) + if(REPOSITORY_FETCH_DEFER) + // todo: handle this + result = null + update_material_single((material_parts = result)) /** * forces a material update diff --git a/code/game/objects/structures/barricade.dm b/code/game/objects/structures/barricade.dm index e490a2965fa..e34557966bd 100644 --- a/code/game/objects/structures/barricade.dm +++ b/code/game/objects/structures/barricade.dm @@ -13,7 +13,12 @@ /obj/structure/barricade/Initialize(mapload, datum/prototype/material/material_like) if(!isnull(material_like)) - set_primary_material(RSmaterials.fetch(material_like)) + var/resolved_material = RSmaterials.fetch_or_defer(material_like) + switch(resolved_material) + if(REPOSITORY_FETCH_DEFER) + // todo: handle + else + set_primary_material(resolved_material) return ..() /obj/structure/barricade/update_material_single(datum/prototype/material/material) diff --git a/code/game/objects/structures/low_wall.dm b/code/game/objects/structures/low_wall.dm index e3419164af5..e3d4b324711 100644 --- a/code/game/objects/structures/low_wall.dm +++ b/code/game/objects/structures/low_wall.dm @@ -48,9 +48,14 @@ GLOBAL_LIST_INIT(wallframe_typecache, typecacheof(list( paint_color = COLOR_WALL_GUNMETAL stripe_color = COLOR_WALL_GUNMETAL -/obj/structure/wall_frame/Initialize(mapload, material) - if(!isnull(material)) - set_primary_material(RSmaterials.fetch(material)) +/obj/structure/wall_frame/Initialize(mapload, datum/prototype/material/material_like) + if(!isnull(material_like)) + var/resolved_material = RSmaterials.fetch_or_defer(material_like) + switch(resolved_material) + if(REPOSITORY_FETCH_DEFER) + // todo: handle + else + set_primary_material(resolved_material) . = ..() update_overlays() diff --git a/code/game/objects/structures/props/puzzledoor.dm b/code/game/objects/structures/props/puzzledoor.dm index 66c6b173682..a73e4cd1631 100644 --- a/code/game/objects/structures/props/puzzledoor.dm +++ b/code/game/objects/structures/props/puzzledoor.dm @@ -36,7 +36,7 @@ /obj/machinery/door/blast/puzzle/Initialize(mapload) . = ..() - implicit_material = RSmaterials.fetch(/datum/prototype/material/alienalloy/dungeonium) + implicit_material = RSmaterials.fetch_local_or_throw(/datum/prototype/material/alienalloy/dungeonium) if(locks.len) return var/check_range = world.view * checkrange_mult diff --git a/code/game/objects/structures/simple_doors.dm b/code/game/objects/structures/simple_doors.dm index 273b68e575f..09824f2a693 100644 --- a/code/game/objects/structures/simple_doors.dm +++ b/code/game/objects/structures/simple_doors.dm @@ -15,9 +15,14 @@ var/isSwitchingStates = 0 var/oreAmount = 7 -/obj/structure/simple_door/Initialize(mapload, material) - if(!isnull(material)) - set_primary_material(RSmaterials.fetch(material)) +/obj/structure/simple_door/Initialize(mapload, datum/prototype/material/material_like) + if(!isnull(material_like)) + var/resolved_material = RSmaterials.fetch_or_defer(material_like) + switch(resolved_material) + if(REPOSITORY_FETCH_DEFER) + // todo: handle + else + set_primary_material(resolved_material) return ..() /obj/structure/simple_door/update_material_single(datum/prototype/material/material) diff --git a/code/game/statistics.dm b/code/game/statistics.dm index e8f73892bd6..2a67a26b905 100644 --- a/code/game/statistics.dm +++ b/code/game/statistics.dm @@ -9,7 +9,7 @@ log_game("SQL ERROR during population polling. Failed to connect.") else var/datum/db_query/query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("population")] (playercount, admincount, time) VALUES (:pc, :ac, NOW())", + "INSERT INTO [DB_PREFIX_TABLE_NAME("population")] (playercount, admincount, time) VALUES (:pc, :ac, NOW())", list( "pc" = sanitizeSQL(playercount), "ac" = sanitizeSQL(admincount), @@ -48,7 +48,7 @@ log_game("SQL ERROR during death reporting. Failed to connect.") else var/datum/db_query/query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("death")] (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("death")] (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES \ (:name, :key, :job, :special, :pod, :time, :laname, :lakey, :gender, :bruteloss, :fireloss, :brainloss, :oxyloss, :coord)", list( "name" = sqlname, @@ -98,7 +98,7 @@ log_game("SQL ERROR during death reporting. Failed to connect.") else var/datum/db_query/query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("death")] (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("death")] (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES \ (:name, :key, :job, :special, :pod, :time, :laname, :lakey, :geender, :bruteloss, :fireloss, :brainloss, :oxyloss, :coord)", list( "name" = sqlname, @@ -146,7 +146,7 @@ else var/datum/db_query/max_query = SSdbcore.RunQuery( - "SELECT MAX(roundid) AS max_round_id FROM [format_table_name("feedback")]", + "SELECT MAX(roundid) AS max_round_id FROM [DB_PREFIX_TABLE_NAME("feedback")]", list(), ) @@ -168,7 +168,7 @@ var/value = item.get_value() var/datum/db_query/query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("feedback")] (id, roundid, time, variable, value) VALUES (null, :rid, Now(), :var, :val)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("feedback")] (id, roundid, time, variable, value) VALUES (null, :rid, Now(), :var, :val)", list( "rid" = newroundid, "var" = sanitizeSQL(variable), diff --git a/code/game/turfs/simulated/floor/floor.dm b/code/game/turfs/simulated/floor/floor.dm index 793169f852d..98b2f44c557 100644 --- a/code/game/turfs/simulated/floor/floor.dm +++ b/code/game/turfs/simulated/floor/floor.dm @@ -73,7 +73,7 @@ CRASH("additional arg detected in /floor Initialize. turfs do not have init arguments as ChangeTurf does not accept them.") var/datum/prototype/flooring/set_flooring_to - if(initial_flooring && (set_flooring_to = RSflooring.fetch(initial_flooring))) + if(initial_flooring && (set_flooring_to = RSflooring.fetch_local_or_throw(initial_flooring))) set_flooring(set_flooring_to, TRUE) else // todo: these are only here under else because set flooring will trigger it diff --git a/code/game/turfs/simulated/floor_types/water.dm b/code/game/turfs/simulated/floor_types/water.dm index 3e1fd52501f..beaee03cc0e 100644 --- a/code/game/turfs/simulated/floor_types/water.dm +++ b/code/game/turfs/simulated/floor_types/water.dm @@ -24,7 +24,7 @@ /turf/simulated/floor/water/Initialize(mapload) . = ..() - var/datum/prototype/flooring/F = RSflooring.fetch(/datum/prototype/flooring/water) + var/datum/prototype/flooring/F = RSflooring.fetch_local_or_throw(/datum/prototype/flooring/water) footstep_sounds = F?.footstep_sounds update_icon() diff --git a/code/game/turfs/simulated/wall/materials.dm b/code/game/turfs/simulated/wall/materials.dm index 507a17b1032..1867c7a0725 100644 --- a/code/game/turfs/simulated/wall/materials.dm +++ b/code/game/turfs/simulated/wall/materials.dm @@ -1,7 +1,7 @@ /turf/simulated/wall/proc/init_materials(datum/prototype/material/outer = material_outer, datum/prototype/material/reinforcing = material_reinf, datum/prototype/material/girder = material_girder) - outer = RSmaterials.fetch(outer) - reinforcing = RSmaterials.fetch(reinforcing) - girder = RSmaterials.fetch(girder) + outer = RSmaterials.fetch_local_or_throw(outer) + reinforcing = RSmaterials.fetch_local_or_throw(reinforcing) + girder = RSmaterials.fetch_local_or_throw(girder) if(!isnull(outer)) material_outer = outer diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 6d439de3b5a..93ceaf8ba58 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -682,3 +682,8 @@ thing.update_hiding_underfloor( (thing.hides_underfloor != OBJ_UNDERFLOOR_NEVER) && we_should_cover, ) + +//* VV *// + +/turf/vv_delete() + ScrapeAway() diff --git a/code/modules/admin/DB ban/functions.dm b/code/modules/admin/DB ban/functions.dm index 0f195dd1d71..e332f48c88f 100644 --- a/code/modules/admin/DB ban/functions.dm +++ b/code/modules/admin/DB ban/functions.dm @@ -72,7 +72,7 @@ computerid = "" if(isnull(ip)) ip = "" - var/sql = "INSERT INTO [format_table_name("ban")] \ + var/sql = "INSERT INTO [DB_PREFIX_TABLE_NAME("ban")] \ (`id`,`bantime`,`serverip`,`bantype`,`reason`,`job`,`duration`,`rounds`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`,`edits`,`unbanned`,`unbanned_datetime`,`unbanned_ckey`,`unbanned_computerid`,`unbanned_ip`) \ VALUES (null, Now(), :serverip, :type, :reason, :job, :duration, :rounds, Now() + INTERVAL :duration MINUTE, :ckey, :cid, :ip, :a_ckey, :a_cid, :a_ip, :who, :adminwho, '', null, null, null, null, null)" SSdbcore.RunQuery( @@ -133,7 +133,7 @@ else bantype_sql = "bantype = '[bantype_str]'" - var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = :ckey AND [bantype_sql] AND (unbanned is null OR unbanned = false)" + var/sql = "SELECT id FROM [DB_PREFIX_TABLE_NAME("ban")] WHERE ckey = :ckey AND [bantype_sql] AND (unbanned is null OR unbanned = false)" if(job) sql += " AND job = :job" @@ -183,7 +183,7 @@ return var/datum/db_query/query = SSdbcore.RunQuery( - "SELECT ckey, duration, reason FROM [format_table_name("ban")] WHERE id = :id", + "SELECT ckey, duration, reason FROM [DB_PREFIX_TABLE_NAME("ban")] WHERE id = :id", list( "id" = banid ) @@ -215,7 +215,7 @@ return SSdbcore.RunQuery( - "UPDATE [format_table_name("ban")] SET reason = :reason, \ + "UPDATE [DB_PREFIX_TABLE_NAME("ban")] SET reason = :reason, \ edits = CONCAT(edits, '- :ckey changed ban reason from \\\":oldreason\\\" to \\\":reason\\\"
') \ WHERE id = :id", list( @@ -233,7 +233,7 @@ to_chat(usr, "Cancelled") return SSdbcore.RunQuery( - "UPDATE [format_table_name("ban")] SET duration = :duration, \ + "UPDATE [DB_PREFIX_TABLE_NAME("ban")] SET duration = :duration, \ edits = CONCAT(edits, '- :ckey changed ban duration from :oldduration to :duration
'), expiration_time = DATE_ADD(bantime, INTERVAL :duration MINUTE) \ WHERE id = :id", list( @@ -264,7 +264,7 @@ var/pckey var/datum/db_query/query = SSdbcore.RunQuery( - "SELECT ckey FROM [format_table_name("ban")] WHERE id = :id", + "SELECT ckey FROM [DB_PREFIX_TABLE_NAME("ban")] WHERE id = :id", list( "id" = id ) @@ -292,7 +292,7 @@ message_admins("[key_name_admin(usr)] has lifted [pckey]'s ban.",1) SSdbcore.RunQuery( - "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = :ckey, unbanned_computerid = :cid, unbanned_ip = :ip WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = :ckey, unbanned_computerid = :cid, unbanned_ip = :ip WHERE id = :id", list( "ckey" = unban_ckey, "cid" = unban_computerid, @@ -458,7 +458,7 @@ var/datum/db_query/select_query = SSdbcore.RunQuery( "SELECT id, bantime, bantype, reason, job, duration, expiration_time, ckey, a_ckey, unbanned, unbanned_ckey, unbanned_datetime, edits, ip, computerid \ - FROM [format_table_name("ban")] \ + FROM [DB_PREFIX_TABLE_NAME("ban")] \ WHERE 1 [playersearch] [adminsearch] [ipsearch] [cidsearch] [bantypesearch] ORDER BY bantime DESC LIMIT 100", search_params ) diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 2eb8325ac57..42bfc1dc365 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -88,7 +88,7 @@ cidquery = " OR computerid = ':cid' " var/datum/db_query/query = SSdbcore.RunQuery( - "SELECT ckey, ip, computerid, a_ckey, reason, expiration_time, duration, bantime, bantype FROM [format_table_name("ban")] WHERE (ckey = :ckey [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)", + "SELECT ckey, ip, computerid, a_ckey, reason, expiration_time, duration, bantime, bantype FROM [DB_PREFIX_TABLE_NAME("ban")] WHERE (ckey = :ckey [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)", list( "ckey" = ckeytext, "ip" = address, diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 29500fb71be..58583b9bad2 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -113,7 +113,7 @@ var/list/admin_ranks = list() //list of all ranks with associated rights return var/datum/db_query/query = SSdbcore.RunQuery( - "SELECT ckey, rank, level, flags FROM [format_table_name("admin")]", + "SELECT ckey, rank, level, flags FROM [DB_PREFIX_TABLE_NAME("admin")]", list() ) diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index 147e5c3cd7c..65148f569a9 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -74,7 +74,7 @@ DEBUG //Job permabans var/datum/db_query/query = SSdbcore.RunQuery( - "SELECT ckey, job FROM [format_table_name("ban")] WHERE bantype = 'JOB_PERMABAN' AND isnull(unbanned)", + "SELECT ckey, job FROM [DB_PREFIX_TABLE_NAME("ban")] WHERE bantype = 'JOB_PERMABAN' AND isnull(unbanned)", list() ) @@ -86,7 +86,7 @@ DEBUG //Job tempbans var/datum/db_query/query1 = SSdbcore.RunQuery( - "SELECT ckey, job FROM [format_table_name("ban")] WHERE bantype = 'JOB_TEMPBAN' AND isnull(unbanned) AND expiration_time > Now()", + "SELECT ckey, job FROM [DB_PREFIX_TABLE_NAME("ban")] WHERE bantype = 'JOB_TEMPBAN' AND isnull(unbanned) AND expiration_time > Now()", list() ) diff --git a/code/modules/admin/permissionverbs/permissionedit.dm b/code/modules/admin/permissionverbs/permissionedit.dm index fad21524481..d3a701a4055 100644 --- a/code/modules/admin/permissionverbs/permissionedit.dm +++ b/code/modules/admin/permissionverbs/permissionedit.dm @@ -78,7 +78,7 @@ return var/datum/db_query/select_query = SSdbcore.RunQuery( - "SELECT id FROM [format_table_name("admin")] WHERE ckey = :ckey", + "SELECT id FROM [DB_PREFIX_TABLE_NAME("admin")] WHERE ckey = :ckey", list( "ckey" = adm_ckey ) @@ -92,14 +92,14 @@ if(new_admin) SSdbcore.RunQuery( - "INSERT INTO [format_table_name("admin")] (id, ckey, rank, level, flags) VALUES (null, :ckey, :rank, -1, 0)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("admin")] (id, ckey, rank, level, flags) VALUES (null, :ckey, :rank, -1, 0)", list( "ckey" = adm_ckey, "rank" = new_rank ) ) SSdbcore.RunQuery( - "INSERT INTO [format_table_name("admin_log")] (id, datetime, adminckey, adminip, log) VALUES (NULL, NOW(), :ckey, :ip, :logstr)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("admin_log")] (id, datetime, adminckey, adminip, log) VALUES (NULL, NOW(), :ckey, :ip, :logstr)", list( "ckey" = sanitizeSQL(usr.ckey), "ip" = sanitizeSQL(usr.client.address), @@ -110,14 +110,14 @@ else if(!isnull(admin_id) && isnum(admin_id)) SSdbcore.RunQuery( - "UPDATE [format_table_name("admin")] SET rank = :rank WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("admin")] SET rank = :rank WHERE id = :id", list( "rank" = new_rank, "id" = admin_id ) ) SSdbcore.RunQuery( - "INSERT INTO [format_table_name("admin_log")] (id, datetime, adminckey, adminip, log) VALUES (NULL, Now(), :ckey, :addr, :log)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("admin_log")] (id, datetime, adminckey, adminip, log) VALUES (NULL, Now(), :ckey, :addr, :log)", list( "ckey" = usr.ckey, "addr" = usr.client.address, @@ -155,7 +155,7 @@ return var/datum/db_query/select_query = SSdbcore.RunQuery( - "SELECT id, flags FROM [format_table_name("admin")] WHERE ckey = :ckey", + "SELECT id, flags FROM [DB_PREFIX_TABLE_NAME("admin")] WHERE ckey = :ckey", list( "ckey" = adm_ckey ) @@ -172,14 +172,14 @@ if(admin_rights & new_permission) //This admin already has this permission, so we are removing it. SSdbcore.RunQuery( - "UPDATE [format_table_name("admin")] SET flags = :flags WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("admin")] SET flags = :flags WHERE id = :id", list( "flags" = admin_rights & ~new_permission, "id" = admin_id ) ) SSdbcore.RunQuery( - "INSERT INTO [format_table_name("admin_log")] (id, datetime, adminckey, adminip, log) VALUES (NULL, Now(), :ckey, :addr, :log)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("admin_log")] (id, datetime, adminckey, adminip, log) VALUES (NULL, Now(), :ckey, :addr, :log)", list( "ckey" = usr.ckey, "addr" = usr.client.address, @@ -189,14 +189,14 @@ to_chat(usr, "Permission removed.") else //This admin doesn't have this permission, so we are adding it. SSdbcore.RunQuery( - "UPDATE [format_table_name("admin")] SET flags = :flags WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("admin")] SET flags = :flags WHERE id = :id", list( "flags" = admin_rights | new_permission, "id" = admin_id ) ) SSdbcore.RunQuery( - "INSERT INTO [format_table_name("admin_log")] (id, datetime, adminckey, adminip, log) VALUES (NULL, Now(), :ckey, :addr, :log)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("admin_log")] (id, datetime, adminckey, adminip, log) VALUES (NULL, Now(), :ckey, :addr, :log)", list( "ckey" = usr.ckey, "addr" = usr.client.address, diff --git a/code/modules/admin/verbs/check_customitem_activity.dm b/code/modules/admin/verbs/check_customitem_activity.dm index 02e577d13e4..6b329a5fadb 100644 --- a/code/modules/admin/verbs/check_customitem_activity.dm +++ b/code/modules/admin/verbs/check_customitem_activity.dm @@ -55,7 +55,7 @@ var/inactive_keys = "None
" var/list/inactive_ckeys = list() if(ckeys_with_customitems.len) var/datum/db_query/query_inactive = SSdbcore.RunQuery( - "SELECT ckey, lastseen FROM [format_table_name("player_lookup")] WHERE datediff(Now(), lastseen) > 60", + "SELECT ckey, lastseen FROM [DB_PREFIX_TABLE_NAME("player_lookup")] WHERE datediff(Now(), lastseen) > 60", list() ) while(query_inactive.NextRow()) @@ -69,7 +69,7 @@ var/inactive_keys = "None
" if(ckeys_with_customitems.len) for(var/cur_ckey in ckeys_with_customitems) var/datum/db_query/query_inactive = SSdbcore.RunQuery( - "SELECT ckey FROM [format_table_name("player_lookup")] WHERE ckey = :ckey", + "SELECT ckey FROM [DB_PREFIX_TABLE_NAME("player_lookup")] WHERE ckey = :ckey", list( "ckey" = cur_ckey ) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index fa96e39a4c4..5b40ba3bc97 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -687,16 +687,6 @@ message_admins(log) log_admin(log) -/client/proc/reload_configuration() - set category = "Debug" - set name = "Reload Configuration" - set desc = "Force config reload to world default" - if(!check_rights(R_DEBUG)) - return - if(alert(usr, "Are you absolutely sure you want to reload the configuration from the default path on the disk, wiping any in-round modificatoins?", "Really reset?", "No", "Yes") == "Yes") - config.admin_reload() - load_configuration() //for legacy - /datum/admins/proc/quick_nif() set category = "Fun" set name = "Quick NIF" diff --git a/code/modules/admin/verbs/debug/reload_configuration.dm b/code/modules/admin/verbs/debug/reload_configuration.dm new file mode 100644 index 00000000000..fd80f16133f --- /dev/null +++ b/code/modules/admin/verbs/debug/reload_configuration.dm @@ -0,0 +1,12 @@ + +/client/proc/reload_configuration() + set category = "Debug" + set name = "Reload Configuration" + set desc = "Force config reload to world default" + if(!check_rights(R_DEBUG)) + return + if(alert(usr, "Are you absolutely sure you want to reload the configuration from the default path on the disk, wiping any in-round modificatoins?", "Really reset?", "No", "Yes") == "Yes") + log_and_message_admins("[key_name(usr)] reloaded server configuration.") + config.admin_reload() + Configuration.admin_reload() + load_configuration() //for legacy diff --git a/code/modules/admin/view_variables/admin_delete.dm b/code/modules/admin/view_variables/admin_delete.dm index 7157a1cce2b..16c84012c99 100644 --- a/code/modules/admin/view_variables/admin_delete.dm +++ b/code/modules/admin/view_variables/admin_delete.dm @@ -16,9 +16,9 @@ //SSblackbox.record_feedback("tally", "admin_verb", 1, "Delete") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! if(isturf(D)) var/turf/T = D - T.ScrapeAway() + T.vv_delete() else vv_update_display(D, "deleted", VV_MSG_DELETED) - qdel(D) + D.vv_delete() if(!QDELETED(D)) vv_update_display(D, "deleted", "") diff --git a/code/modules/artwork/items/poster.dm b/code/modules/artwork/items/poster.dm index be689b6a4f9..d1be0da56d4 100644 --- a/code/modules/artwork/items/poster.dm +++ b/code/modules/artwork/items/poster.dm @@ -26,7 +26,7 @@ poster_design_id = pick(RSposter_designs.fetch_by_tag_mutable(poster_random_tag)) if(poster_design_id != src.poster_design_id) src.poster_design_id = poster_design_id - set_poster_design(RSposter_designs.fetch(poster_design_id)) + set_poster_design(RSposter_designs.fetch_local_or_throw(poster_design_id)) /obj/item/poster/proc/set_poster_design(datum/prototype/poster_design/design) src.name = "rolled-up-poster - [design.name]" diff --git a/code/modules/artwork/structures/poster.dm b/code/modules/artwork/structures/poster.dm index 4b707581a18..0c2f3147a1e 100644 --- a/code/modules/artwork/structures/poster.dm +++ b/code/modules/artwork/structures/poster.dm @@ -41,7 +41,7 @@ poster_design_id = pick(RSposter_designs.fetch_by_tag_mutable(poster_random_tag)) if(poster_design_id != src.poster_design_id) src.poster_design_id = poster_design_id - set_poster_design(RSposter_designs.fetch(poster_design_id)) + set_poster_design(RSposter_designs.fetch_local_or_throw(poster_design_id)) /obj/structure/poster/proc/set_poster_design(datum/prototype/poster_design/design) src.name = "rolled-up-poster - [design.name]" diff --git a/code/modules/artwork/structures/sculpting_block.dm b/code/modules/artwork/structures/sculpting_block.dm index d9e53ef0ced..b8598d4cbbd 100644 --- a/code/modules/artwork/structures/sculpting_block.dm +++ b/code/modules/artwork/structures/sculpting_block.dm @@ -78,9 +78,15 @@ /// sculpting mask for our block var/icon/sculpting_rolldown_mask -/obj/structure/sculpting_block/Initialize(mapload, material) +/obj/structure/sculpting_block/Initialize(mapload, datum/prototype/material/material_like) // todo: materials system - src.material = RSmaterials.fetch(material || src.material) + if(!isnull(material_like)) + var/resolved_material = RSmaterials.fetch_or_defer(material_like) + switch(resolved_material) + if(REPOSITORY_FETCH_DEFER) + // todo: handle + else + src.material = resolved_material || RSmaterials.fetch_local_or_throw(/datum/prototype/material/steel) // todo: if it autoinit'd, don't do this reset_sculpting() return ..() diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index bb1edf8d647..8d20b0cbcad 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -467,7 +467,7 @@ var/sql_system_ckey = sanitizeSQL(system_ckey) var/sql_ckey = sanitizeSQL(ckey) //check to see if we noted them in the last day. - var/datum/DBQuery/query_get_notes = SSdbcore.NewQuery("SELECT id FROM [format_table_name("messages")] WHERE type = 'note' AND targetckey = '[sql_ckey]' AND adminckey = '[sql_system_ckey]' AND timestamp + INTERVAL 1 DAY < NOW() AND deleted = 0 AND expire_timestamp > NOW()") + var/datum/DBQuery/query_get_notes = SSdbcore.NewQuery("SELECT id FROM [DB_PREFIX_TABLE_NAME("messages")] WHERE type = 'note' AND targetckey = '[sql_ckey]' AND adminckey = '[sql_system_ckey]' AND timestamp + INTERVAL 1 DAY < NOW() AND deleted = 0 AND expire_timestamp > NOW()") if(!query_get_notes.Execute()) qdel(query_get_notes) return @@ -476,7 +476,7 @@ return qdel(query_get_notes) //regardless of above, make sure their last note is not from us, as no point in repeating the same note over and over. - query_get_notes = SSdbcore.NewQuery("SELECT adminckey FROM [format_table_name("messages")] WHERE targetckey = '[sql_ckey]' AND deleted = 0 AND expire_timestamp > NOW() ORDER BY timestamp DESC LIMIT 1") + query_get_notes = SSdbcore.NewQuery("SELECT adminckey FROM [DB_PREFIX_TABLE_NAME("messages")] WHERE targetckey = '[sql_ckey]' AND deleted = 0 AND expire_timestamp > NOW() ORDER BY timestamp DESC LIMIT 1") if(!query_get_notes.Execute()) qdel(query_get_notes) return diff --git a/code/modules/client/connection.dm b/code/modules/client/connection.dm index 5e45f624d36..8f52aebf6ec 100644 --- a/code/modules/client/connection.dm +++ b/code/modules/client/connection.dm @@ -7,7 +7,7 @@ return var/datum/db_query/lookup = SSdbcore.NewQuery( - "SELECT id FROM [format_table_name("player_lookup")] WHERE ckey = :ckey", + "SELECT id FROM [DB_PREFIX_TABLE_NAME("player_lookup")] WHERE ckey = :ckey", list( "ckey" = ckey, ) @@ -20,7 +20,7 @@ if(sql_id) var/datum/db_query/update = SSdbcore.NewQuery( - "UPDATE [format_table_name("player_lookup")] SET lastseen = Now(), ip = :ip, computerid = :computerid, lastadminrank = :lastadminrank WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("player_lookup")] SET lastseen = Now(), ip = :ip, computerid = :computerid, lastadminrank = :lastadminrank WHERE id = :id", list( "ip" = address, "computerid" = computer_id, @@ -33,7 +33,7 @@ else //New player!! Need to insert all the stuff var/datum/db_query/insert = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("player_lookup")] (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, :ckey, Now(), Now(), :ip, :cid, :rank)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("player_lookup")] (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, :ckey, Now(), Now(), :ip, :cid, :rank)", list( "ckey" = ckey, "ip" = address, @@ -55,7 +55,7 @@ return SSdbcore.RunQuery( - "INSERT INTO [format_table_name("connection_log")] (id, datetime, serverip, ckey, ip, computerid) VALUES (null, Now(), :server, :ckey, :ip, :cid)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("connection_log")] (id, datetime, serverip, ckey, ip, computerid) VALUES (null, Now(), :server, :ckey, :ip, :cid)", list( "server" = "[world.internet_address]:[world.port]", "ckey" = ckey, diff --git a/code/modules/client/data/client_data.dm b/code/modules/client/data/client_data.dm index 8b74a28b368..b1f70aec30e 100644 --- a/code/modules/client/data/client_data.dm +++ b/code/modules/client/data/client_data.dm @@ -136,7 +136,7 @@ GLOBAL_LIST_EMPTY(client_data) playtime_mutex = FALSE return var/datum/db_query/query = SSdbcore.NewQuery( - "SELECT `roleid`, `minutes` FROM [format_table_name("playtime")] WHERE player = :player", + "SELECT `roleid`, `minutes` FROM [DB_PREFIX_TABLE_NAME("playtime")] WHERE player = :player", list( "player" = player_id, ) diff --git a/code/modules/client/data/player_data.dm b/code/modules/client/data/player_data.dm index dfbc4c6faa9..591ed3bb33b 100644 --- a/code/modules/client/data/player_data.dm +++ b/code/modules/client/data/player_data.dm @@ -94,7 +94,7 @@ GLOBAL_LIST_EMPTY(player_data) return var/datum/db_query/lookup lookup = SSdbcore.ExecuteQuery( - "SELECT id, playerid, firstseen FROM [format_table_name("player_lookup")] WHERE ckey = :ckey", + "SELECT id, playerid, firstseen FROM [DB_PREFIX_TABLE_NAME("player_lookup")] WHERE ckey = :ckey", list( "ckey" = ckey ) @@ -111,7 +111,7 @@ GLOBAL_LIST_EMPTY(player_data) lookup_pid = text2num(lookup_pid) qdel(lookup) lookup = SSdbcore.ExecuteQuery( - "SELECT id, flags, datediff(Now(), firstseen), firstseen, misc FROM [format_table_name("player")] WHERE id = :id", + "SELECT id, flags, datediff(Now(), firstseen), firstseen, misc FROM [DB_PREFIX_TABLE_NAME("player")] WHERE id = :id", list( "id" = lookup_pid ) @@ -147,7 +147,7 @@ GLOBAL_LIST_EMPTY(player_data) var/datum/db_query/insert if(migrate_firstseen) insert = SSdbcore.ExecuteQuery( - "INSERT INTO [format_table_name("player")] (flags, firstseen, lastseen, misc) VALUES (:flags, :fs, Now(), :misc)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("player")] (flags, firstseen, lastseen, misc) VALUES (:flags, :fs, Now(), :misc)", list( "flags" = player_flags, "fs" = migrate_firstseen, @@ -157,7 +157,7 @@ GLOBAL_LIST_EMPTY(player_data) player_first_seen = migrate_firstseen else insert = SSdbcore.ExecuteQuery( - "INSERT INTO [format_table_name("player")] (flags, firstseen, lastseen, misc) VALUES (:flags, Now(), Now(), :misc)", + "INSERT INTO [DB_PREFIX_TABLE_NAME("player")] (flags, firstseen, lastseen, misc) VALUES (:flags, Now(), Now(), :misc)", list( "flags" = player_flags, "misc" = safe_json_encode(player_misc), @@ -173,7 +173,7 @@ GLOBAL_LIST_EMPTY(player_data) qdel(insert) // now update lookup insert = SSdbcore.ExecuteQuery( - "UPDATE [format_table_name("player_lookup")] SET playerid = :pid WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("player_lookup")] SET playerid = :pid WHERE id = :id", list( "id" = lookup_id, "pid" = insert_id @@ -211,7 +211,7 @@ GLOBAL_LIST_EMPTY(player_data) /datum/player_data/proc/_save() qdel(SSdbcore.ExecuteQuery( - "UPDATE [format_table_name("player")] SET flags = :flags, misc = :misc WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("player")] SET flags = :flags, misc = :misc WHERE id = :id", list( "flags" = player_flags, "id" = player_id, @@ -232,7 +232,7 @@ GLOBAL_LIST_EMPTY(player_data) if(!block_on_available()) return FALSE qdel(SSdbcore.ExecuteQuery( - "UPDATE [format_table_name("player")] SET lastseen = Now() WHERE id = :id", + "UPDATE [DB_PREFIX_TABLE_NAME("player")] SET lastseen = Now() WHERE id = :id", list( "id" = player_id, ) diff --git a/code/modules/client/game_preferences/game_preferences.dm b/code/modules/client/game_preferences/game_preferences.dm index c54d71592dc..667a98c8d4e 100644 --- a/code/modules/client/game_preferences/game_preferences.dm +++ b/code/modules/client/game_preferences/game_preferences.dm @@ -369,7 +369,7 @@ usr = null var/datum/db_query/query = SSdbcore.NewQuery( - "SELECT `toggles`, `entries`, `misc`, `keybinds`, `version` FROM [format_table_name("game_preferences")] \ + "SELECT `toggles`, `entries`, `misc`, `keybinds`, `version` FROM [DB_PREFIX_TABLE_NAME("game_preferences")] \ WHERE `player` = :player", list( "player" = authoritative_player_id, @@ -413,7 +413,7 @@ usr = null var/datum/db_query/query = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("game_preferences")] \ + "INSERT INTO [DB_PREFIX_TABLE_NAME("game_preferences")] \ (`player`, `toggles`, `entries`, `misc`, `keybinds`, `version`, `modified`) VALUES \ (:player, :toggles, :entries, :misc, :keybinds, :version, Now()) ON DUPLICATE KEY UPDATE \ `player` = VALUES(player), `toggles` = VALUES(toggles), `entries` = VALUES(entries), `misc` = VALUES(misc), \ diff --git a/code/modules/language/language.dm b/code/modules/language/language.dm index be809af403a..ecb4d2eada4 100644 --- a/code/modules/language/language.dm +++ b/code/modules/language/language.dm @@ -232,7 +232,7 @@ // Language handling. /mob/proc/add_language(var/language) - var/datum/prototype/language/new_language = RSlanguages.fetch(language) || RSlanguages.legacy_resolve_language_name(language) + var/datum/prototype/language/new_language = RSlanguages.fetch_or_defer(language) || RSlanguages.legacy_resolve_language_name(language) if(!istype(new_language) || (new_language in languages)) return 0 @@ -241,12 +241,12 @@ return 1 /mob/proc/remove_language(var/rem_language) - var/datum/prototype/language/L = RSlanguages.fetch(rem_language) + var/datum/prototype/language/L = RSlanguages.fetch_or_defer(rem_language) . = (L in languages) languages.Remove(L) /mob/living/remove_language(rem_language) - var/datum/prototype/language/L = RSlanguages.fetch(rem_language) + var/datum/prototype/language/L = RSlanguages.fetch_or_defer(rem_language) if(default_language == L) default_language = null return ..() diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 3980cecb08d..c9b60e2e4b9 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -49,7 +49,7 @@ AUTHORTITLECATEGORYSS13BN"} var/datum/db_query/query = SSdbcore.RunQuery( - "SELECT author, title, category, id FROM [format_table_name("library")] WHERE author LIKE '%:author%' AND title LIKE '%:title%'[category == "Any"? "" : " AND category = :category"]", + "SELECT author, title, category, id FROM [DB_PREFIX_TABLE_NAME("library")] WHERE author LIKE '%:author%' AND title LIKE '%:title%'[category == "Any"? "" : " AND category = :category"]", category == "Any"? list("author" = author, "title" = title) : list("author" = author, "title" = title, "category" = category) ) @@ -229,7 +229,7 @@
TITLE&1 | ForEach-Object { - $str = "$_" - if ($_.GetType() -eq [System.Management.Automation.ErrorRecord]) { - $str = $str.TrimEnd("`r`n") - } - $str | Out-File -Encoding utf8 -Append $Log - $str | Out-Host -} + +& $PythonExe -u $args + exit $LastExitCode diff --git a/tools/ci/ci_config.txt b/tools/ci/ci_config.txt index e7e05a8fe21..b7a9a95df65 100644 --- a/tools/ci/ci_config.txt +++ b/tools/ci/ci_config.txt @@ -2,7 +2,7 @@ SQL_ENABLED SQL_ADDRESS 127.0.0.1 SQL_USER root SQL_PORT 3306 -SQL_PASSWORD +SQL_PASSWORD root SQL_DATABASE ss13_ci SQL_SERVER_PREFIX rp_ diff --git a/tools/ci/config.toml b/tools/ci/config.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/ci/install/install_flyway.sh b/tools/ci/install/install_flyway.sh new file mode 100644 index 00000000000..7c0d1973038 --- /dev/null +++ b/tools/ci/install/install_flyway.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +source dependencies.sh + +if [ -d "~/flyway/flyway-$FLYWAY_VERSION" ]; then + echo "Using cached flyway $FLYWAY_VERSION." +else + echo "Pulling flyway $FLYWAY_VERSION." + rm -rf ~/flyway/flyway-$FLYWAY_VERSION + mkdir -p ~/flyway + cd ~/flyway + curl "https://download.red-gate.com/maven/release/com/redgate/flyway/flyway-commandline/$FLYWAY_VERSION/flyway-commandline-$FLYWAY_VERSION-linux-x64.tar.gz" | tar -xvz + cd ~ +fi diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index da75f5597f7..1ff65243830 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -7,6 +7,7 @@ mkdir ci_test/data #test config cp tools/ci/ci_config.txt ci_test/config/config.txt +cp tools/ci/config.toml ci_test/config/config.toml cd ci_test DreamDaemon citadel.dmb -close -trusted -verbose -params "log-directory=ci" diff --git a/tools/deploy.sh b/tools/deploy.sh index ac0a4892adc..baa4de5b529 100755 --- a/tools/deploy.sh +++ b/tools/deploy.sh @@ -11,6 +11,7 @@ fi mkdir -p \ $1/_mapload \ + $1/config.default \ $1/maps \ $1/icons \ $1/sound \ @@ -26,6 +27,8 @@ fi cp citadel.dmb citadel.rsc $1/ # mapload: has basemap.dmm, runtime loaded cp -r _mapload/* $1/_mapload/ +# default configs. self-explanatory. +cp -r config.default/* $1/config.default/ # maps: map .dmms and potential assets, runtime loaded cp -r maps/* $1/maps/ # icons: .dmi assets, runtime loaded diff --git a/tools/requirements.txt b/tools/requirements.txt index 032ffe66e72..adaf3cf43d9 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,6 +1,6 @@ -pygit2==1.7.2 -bidict==0.22.0 -Pillow==10.0.1 +pygit2==1.15 +bidict==0.23.1 +Pillow==11.0.0 # changelogs PyYaml==6.0.1 diff --git a/tools/setup_dev_db/README.md b/tools/setup_dev_db/README.md new file mode 100644 index 00000000000..486b901966c --- /dev/null +++ b/tools/setup_dev_db/README.md @@ -0,0 +1,30 @@ +# Dev Database Setup Tool + +Requires Python 3.12+ + +Sets up a database for use in development, automatically downloading a portable version of MariaDB and Flyway as needed. + +Only works on windows right now, if you're on linux you should know how to set up a database. + +## What this is not for. + +This is **not** for developing the database. + +This sounds counterintuitive, but this script pretty much just blindly runs migrations. If a migration fails, there's nothing it can do about it unless there's an undo migration (which is usually not the case). + +## WARNING + +**Do not, under any circumstances, use or attempt to modify this tool to run in production.** + +If you do, and you get breached / bad things happen, I will not provide any help or condolences. This is purely a dev tool. **It is the responsibility of the server owner to set up a proper production database and to maintain it.** + +## Defaults + +* The default root password is set to `password`. +* The default database is not secured at all. + +## License + +This entire folder is under the MIT license. + +The bootstrap system used to run Python, however, is under AGPL, as it's from /tg/. diff --git a/tools/setup_dev_db/invoke.py b/tools/setup_dev_db/invoke.py new file mode 100644 index 00000000000..6737f5ac2e8 --- /dev/null +++ b/tools/setup_dev_db/invoke.py @@ -0,0 +1,160 @@ +import argparse; +import codecs; +import encodings; +import io; +import os; +import subprocess; +import signal; +import sys; +import time; +import threading; + +keep_running: bool = True + +def on_interrupt(signo, frame): + global keep_running + keep_running = False + print("ctrl+C caught!") + +signal.signal(signal.SIGINT, on_interrupt) + +def log_message(source: str, string: str, end: str = "\n"): + print('%s: %s' % (source, string), end=end) + sys.stdout.flush() + +def thread_pipe_dump(source: str, pipe: io.TextIOWrapper): + while True: + for line in pipe.readlines(): + log_message(source, line, end="") + time.sleep(0.001) + +if __name__ == "__main__": + argparser = argparse.ArgumentParser( + prog="setup.ps1", + usage="setup.ps1 --port [port] --dbname [dbname]", + ) + argparser.add_argument("--mysqld", type=str) + argparser.add_argument("--mysql_admin", type=str) + argparser.add_argument("--flyway", type=str) + argparser.add_argument("--migrations", type=str) + argparser.add_argument("--dataDir", type=str) + argparser.add_argument("--port", required=False, default=3306, type=int) + argparser.add_argument("--dbname", required=False, default="ss13", type=str) + argparser.add_argument("--no_migrations", required=False, action="store_true", default=False) + + # we slice it, as being invoked from bootstrap consumes this script's file path as the first arg + effective_args: list[str] = sys.argv[1:] + + if len(effective_args) == 0: + argparser.print_help() + exit(1) + + parsed_args = argparser.parse_args(effective_args) + + PATH_TO_MYSQLD: str = parsed_args.mysqld + PATH_TO_MYSQL_ADMIN: str = parsed_args.mysql_admin + PATH_TO_FLYWAY: str = parsed_args.flyway + PATH_TO_MIGRATIONS: str = parsed_args.migrations + USE_DATADIR: str = parsed_args.dataDir + USE_PORT: int = parsed_args.port + USE_DATABASE: str = parsed_args.dbname + + log_message("setup_dev_db", "WARNING: This is a very, very lazy Python app! Logs are not necessarily in order of occurence; the script is just a very, very dumb while(True) loop that is just jank enough to work. Do not use this for production") + log_message("setup_dev_db", 'Using port %d and setting up on database %s. Use --port and --dbname to override!' % (USE_PORT, USE_DATABASE)) + log_message("setup_dev_db", 'Using data directory %s.' % (USE_DATADIR)) + log_message("setup_dev_db", "Starting processes...") + + log_message("setup_dev_db", "Starting mysqld...") + mysqld: subprocess.Popen | None = subprocess.Popen( + [ + PATH_TO_MYSQLD, + '--datadir', + USE_DATADIR, + "--console", + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + os.set_blocking(mysqld.stdout.fileno(), False) + mysqld_out_dump = threading.Thread(target=thread_pipe_dump, args=("mysqld-out", mysqld.stdout), daemon=True) + mysqld_out_dump.start() + + log_message("setup_dev_db", "Creating database...") + create_db_run: subprocess.CompletedProcess = subprocess.run( + [ + PATH_TO_MYSQL_ADMIN, + "--user=root", + '--password=password', + "create", + USE_DATABASE, + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + log_message("mariadb_admin", create_db_run.stdout or "\n", end="") + + flyway: subprocess.Popen | None = None + if not parsed_args.no_migrations: + log_message("setup_dev_db", "Starting flyway and migrating...") + flyway = subprocess.Popen( + [ + PATH_TO_FLYWAY, + "-user=root", + "-password=password", + '-url=jdbc:mariadb://localhost:%d/%s' % (USE_PORT, USE_DATABASE), + '-locations=filesystem:%s' % (PATH_TO_MIGRATIONS), + "migrate", + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + os.set_blocking(flyway.stdout.fileno(), False) + flyway_out_dump = threading.Thread(target=thread_pipe_dump, args=("flyway-out", flyway.stdout), daemon=True) + flyway_out_dump.start() + + + # main loop + while keep_running == True: + # pull outputs + polled: list[str] + exited: int | None + + if mysqld != None: + exited = mysqld.poll() + if exited != None: + mysqld = None + log_message("setup_dev_db", 'mysqld exited with code %d' % (exited)) + + if flyway != None: + exited = flyway.poll() + if exited != None: + flyway = None + log_message("setup_dev_db", 'flyway exited with code %d' % (exited)) + + if flyway == None and mysqld == None: + keep_running = False + + # "this is async right" + # "yeah" + # pulls the cover off + # "what the hell, this is just an infinite loop!" + time.sleep(0.001) + + log_message("setup_dev_db", 'exiting...') + + # exit mysqld and flyway + if mysqld != None: + mysqld.terminate() + if flyway != None: + flyway.terminate() + + # block on mysqld/flyway exiting + if mysqld != None: + mysqld_exitcode: int | None = mysqld.wait() + log_message("setup_dev_db", 'mysqld exited with code %d' % (mysqld_exitcode)) + if flyway != None: + flyway_exitcode: int | None = flyway.wait() + log_message("setup_dev_db", 'flyway exited with code %d' % (flyway_exitcode)) diff --git a/tools/setup_dev_db/setup.ps1 b/tools/setup_dev_db/setup.ps1 new file mode 100644 index 00000000000..b6712444cc4 --- /dev/null +++ b/tools/setup_dev_db/setup.ps1 @@ -0,0 +1,85 @@ +function ExtractVersion { + param([string] $Path, [string] $Key) + foreach ($Line in Get-Content $Path) { + if ($Line.StartsWith("export $Key=")) { + return $Line.Substring("export $Key=".Length) + } + } + throw "Couldn't find value for $Key in $Path" +} + +function ResolveMariaDBURL { + param([string] $Version) + return "https://mirror.rackspace.com/mariadb//mariadb-$Version/winx64-packages/mariadb-$Version-winx64.zip" +} + +function ResolveFlywayURL { + param([string] $Version) + return "https://download.red-gate.com/maven/release/com/redgate/flyway/flyway-commandline/$Version/flyway-commandline-$Version-windows-x64.zip" +} + +# Path is a /path/to/folder +# Archive will be temporarily downloaded as /path/to/folder.zip +function PullURLAndUnpack { + param([string] $URL, [string] $Path) + if(Test-Path "$Path.zip" -PathType Leaf) { + Remove-Item -Path "$Path.zip" + } + Write-Output "Pulling $URL to $Path.zip" + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest ` + "$URL" ` + -OutFile "$Path.zip" ` + -ErrorAction Stop + $ProgressPreference = 'Continue' + Write-Output "Expanding $Path.zip to $Path" + Expand-Archive "$Path.zip" -DestinationPath "$Path" +} + +$ToolRoot = Split-Path $script:MyInvocation.MyCommand.Path + +$MARIADB_VERSION = ExtractVersion -Path "$ToolRoot/../../dependencies.sh" -Key "MARIADB_VERSION" +$FLYWAY_VERSION = ExtractVersion -Path "$ToolRoot/../../dependencies.sh" -Key "FLYWAY_VERSION" + +$MARIADB_FOLDER = "$ToolRoot/.cache/mariadb/$MARIADB_VERSION" +$MARIADB_BIN_FOLDER = "$MARIADB_FOLDER/mariadb-$MARIADB_VERSION-winx64/bin" +$FLYWAY_FOLDER = "$ToolRoot/.cache/flyway/$FLYWAY_VERSION" + +$MYSQLD_PATH = "$MARIADB_BIN_FOLDER/mariadbd.exe" +$MYSQLD_ADMIN_PATH = "$MARIADB_BIN_FOLDER/mariadb-admin.exe" +$FLYWAY_PATH = "$FLYWAY_FOLDER/flyway-$FLYWAY_VERSION/flyway.cmd" + +# GET mariadb IF NOT EXISTS + +if(!(Test-Path $MYSQLD_PATH -PathType Leaf)) { + $MARIADB_URL = ResolveMariaDBURL $MARIADB_VERSION + PullURLAndUnpack $MARIADB_URL $MARIADB_FOLDER + if(!(Test-Path $MYSQLD_PATH -PathType Leaf)) { + Write-Error "Failed to find '$MYSQLD_PATH' after unpacking." + exit 1 + } +} + +# GET flyway IF NOT EXISTS + +if(!(Test-Path $FLYWAY_PATH -PathType Leaf)) { + $FLYWAY_URL = ResolveFlywayURL $FLYWAY_VERSION + PullURLAndUnpack $FLYWAY_URL $FLYWAY_FOLDER + if(!(Test-Path $FLYWAY_PATH -PathType Leaf)) { + Write-Error "Failed to find '$FLYWAY_PATH' after unpacking." + exit 1 + } +} + +# run database + +$DATABASE_DATA_DIR_RAW = "$ToolRoot/../../data/setup_dev_db" +if(!(Test-Path $DATABASE_DATA_DIR_RAW -PathType Container)) { + New-Item $DATABASE_DATA_DIR_RAW -ItemType Directory + $DATABASE_DATA_DIR = Resolve-Path "$ToolRoot/../../data/setup_dev_db" + Write-Output "Bootstrapping database with data directory '$DATABASE_DATA_DIR'." + & $MARIADB_BIN_FOLDER/mariadb-install-db.exe -d $DATABASE_DATA_DIR -p password -D +} +$DATABASE_DATA_DIR = Resolve-Path "$ToolRoot/../../data/setup_dev_db" + +& $ToolRoot/../bootstrap/python_.ps1 $ToolRoot/invoke.py --dataDir $DATABASE_DATA_DIR --mysqld $MYSQLD_PATH --flyway $FLYWAY_PATH --migrations "../../sql/migrations" --mysql_admin $MYSQLD_ADMIN_PATH $args