diff --git a/.editorconfig b/.editorconfig index 72e85029ef6..58fd1cf290f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,6 +15,9 @@ indent_size = 2 [*.py] indent_style = space +[*.sql] +indent_style = space + [*.md] trim_trailing_whitespace = false diff --git a/.github/workflows/byond.yml b/.github/workflows/byond.yml index 33d0948b55c..5eccfc12957 100644 --- a/.github/workflows/byond.yml +++ b/.github/workflows/byond.yml @@ -317,7 +317,7 @@ jobs: services: mariadb: - image: mariadb:10.4 + image: mariadb:10.11 ports: - 3306 env: @@ -440,7 +440,7 @@ jobs: services: mariadb: - image: mariadb:10.4 + image: mariadb:10.11 ports: - 3306 env: @@ -565,7 +565,7 @@ jobs: services: mariadb: - image: mariadb:10.4 + image: mariadb:10.11 ports: - 3306 env: @@ -689,7 +689,7 @@ jobs: services: mariadb: - image: mariadb:10.4 + image: mariadb:10.11 ports: - 3306 env: diff --git a/SQL/migrate-2023/V006__loadout_overhaul.sql b/SQL/migrate-2023/V006__loadout_overhaul.sql new file mode 100644 index 00000000000..eec52a8429d --- /dev/null +++ b/SQL/migrate-2023/V006__loadout_overhaul.sql @@ -0,0 +1,179 @@ +-- +-- Overhauls the way the loadout is stored +-- +-- -------------------------------------------------------- +-- Server version: 10.11.5 +-- Server OS: debian-linux-gnu +-- HeidiSQL Version: 12.3.0.6589 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for procedure convert_characters_gear +DELIMITER // +CREATE PROCEDURE `convert_characters_gear`( + IN `parameter_char_id` INT +) + MODIFIES SQL DATA + SQL SECURITY INVOKER + COMMENT 'Converts the character gear from the V1 (only one gear set) and V2 (multiple gear sets) storage formats to the ss13_characters_gear table' +BEGIN + DECLARE info_gear_version VARCHAR(50) DEFAULT NULL; + DECLARE value_fetch_geardata MEDIUMTEXT DEFAULT NULL; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + END; + + SELECT gear INTO value_fetch_geardata FROM ss13_characters WHERE id = parameter_char_id; + + IF JSON_VALID(value_fetch_geardata) THEN + SET info_gear_version = "valid json"; + + IF JSON_CONTAINS_PATH(value_fetch_geardata, 'one', '$.1','$.2','$.3') THEN + SET info_gear_version = "version 2"; + + START TRANSACTION; + DELETE FROM ss13_characters_gear WHERE char_id = parameter_char_id; + + IF JSON_CONTAINS_PATH(value_fetch_geardata, 'one', '$.1') THEN + CALL `convert_characters_gear_step2`(parameter_char_id, 1, JSON_EXTRACT(value_fetch_geardata, '$.1')); + END IF; + + IF JSON_CONTAINS_PATH(value_fetch_geardata, 'one', '$.2') THEN + CALL `convert_characters_gear_step2`(parameter_char_id, 2, JSON_EXTRACT(value_fetch_geardata, '$.2')); + END IF; + + IF JSON_CONTAINS_PATH(value_fetch_geardata, 'one', '$.3') THEN + CALL `convert_characters_gear_step2`(parameter_char_id, 3, JSON_EXTRACT(value_fetch_geardata, '$.3')); + END IF; + COMMIT; + + ELSE + SET info_gear_version = "version 1"; + START TRANSACTION; + DELETE FROM ss13_characters_gear WHERE char_id = parameter_char_id; + CALL `convert_characters_gear_step2`(parameter_char_id, 1, value_fetch_geardata); + COMMIT; + END IF; + + ELSE + SET info_gear_version = "invalid"; + END IF; + +END// +DELIMITER ; + +-- Dumping structure for procedure convert_characters_gear_all +DELIMITER // +CREATE PROCEDURE `convert_characters_gear_all`() + MODIFIES SQL DATA + SQL SECURITY INVOKER + COMMENT 'Runs convert_characters_gear for all characters' +BEGIN + DECLARE done BOOLEAN DEFAULT FALSE; + DECLARE _id BIGINT UNSIGNED; + DECLARE cur CURSOR FOR SELECT id FROM ss13_characters; + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE; + + OPEN cur; + + charLoop: LOOP + FETCH cur INTO _id; + IF done THEN + LEAVE charLoop; + END IF; + CALL convert_characters_gear(_id); + END LOOP charLoop; + + CLOSE cur; +END// +DELIMITER ; + +-- Dumping structure for procedure convert_characters_gear_step2 +DELIMITER // +CREATE PROCEDURE `convert_characters_gear_step2`( + IN `parameter_char_id` INT, + IN `parameter_gear_slot_id` INT, + IN `parameter_gear_string` MEDIUMTEXT +) + MODIFIES SQL DATA + SQL SECURITY INVOKER + COMMENT 'Converts a characters gear to the new (v3) storage system. Requires a char id, the current gear_slot and and the gear string to convert' +BEGIN + INSERT INTO ss13_characters_gear (char_id, slot, NAME, tweaks) + SELECT + parameter_char_id AS char_id, + parameter_gear_slot_id AS gear_slot, + JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(parameter_gear_string), CONCAT('$[', geardata.rowid-1, ']'))) AS gearname, + IF(STRCMP(geardata.geartweaks,'null')=0,NULL,geardata.geartweaks) + FROM JSON_TABLE(parameter_gear_string, '$.*' COLUMNS ( + rowid FOR ORDINALITY, + geartweaks JSON PATH '$' + )) geardata; +END// +DELIMITER ; + +-- Dumping structure for table ss13_characters_gear +CREATE TABLE IF NOT EXISTS `ss13_characters_gear` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `char_id` int(11) NOT NULL, + `slot` tinyint(4) NOT NULL, + `name` varchar(255) NOT NULL, + `tweaks` mediumtext DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + KEY `char_id_gear_slot` (`char_id`,`slot`) USING BTREE, + CONSTRAINT `fk_ss13_characters_char_id` FOREIGN KEY (`char_id`) REFERENCES `ss13_characters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `json_valid_tweaks` CHECK (json_valid(`tweaks`)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Data exporting was unselected. + +-- Dumping structure for procedure update_character_gear +DELIMITER // +CREATE PROCEDURE `update_character_gear`( + IN `parameter_char_id` INT, + IN `parameter_gear_slot_id` INT, + IN `parameter_gear_string` MEDIUMTEXT +) + MODIFIES SQL DATA + SQL SECURITY INVOKER + COMMENT 'Updates a specific gear slot of a character to the supplied gear string' +BEGIN + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + END; + + START TRANSACTION; + + DELETE FROM ss13_characters_gear WHERE char_id = parameter_char_id AND slot = parameter_gear_slot_id; + + INSERT INTO ss13_characters_gear (char_id, slot, NAME, tweaks) + SELECT + parameter_char_id AS char_id, + parameter_gear_slot_id AS gear_slot, + JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(parameter_gear_string), CONCAT('$[', geardata.rowid-1, ']'))) AS gearname, + IF(STRCMP(geardata.geartweaks,'null')=0,NULL,geardata.geartweaks) + FROM JSON_TABLE(parameter_gear_string, '$.*' COLUMNS ( + rowid FOR ORDINALITY, + geartweaks JSON PATH '$' + )) geardata; + + COMMIT; +END// +DELIMITER ; + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index f775e7cdd28..0e715b6356c 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -39,8 +39,6 @@ // Some arbitrary defines to be used by self-pruning global lists. (see master_controller) #define PROCESS_KILL 26 // Used to trigger removal from a processing list. -#define MAX_GEAR_COST 15 // Used in chargen for accessory loadout limit. - // Preference toggles. #define SOUND_ADMINHELP 0x1 #define SOUND_MIDI 0x2 diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 22653db3b58..5e4fcf94f79 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -225,6 +225,7 @@ GLOBAL_LIST_EMPTY(gamemode_cache) var/character_slots = 10 // The number of available character slots var/loadout_slots = 3 // The number of loadout slots per character + var/loadout_cost = 15 // The maximum cost of the loadout per slot var/max_maint_drones = 5 //This many drones can spawn, var/allow_drone_spawn = 1 //assuming the admin allow them to. @@ -843,6 +844,9 @@ GENERAL_PROTECT_DATUM(/datum/configuration) if("loadout_slots") GLOB.config.loadout_slots = text2num(value) + if("loadout_cost") + GLOB.config.loadout_cost = text2num(value) + if("allow_drone_spawn") GLOB.config.allow_drone_spawn = text2num(value) diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm index ae0fdd71029..71bb02ccb6a 100644 --- a/code/modules/client/preference_setup/general/01_basic.dm +++ b/code/modules/client/preference_setup/general/01_basic.dm @@ -115,7 +115,7 @@ "ckey" = PREF_CLIENT_CKEY ) -/datum/category_item/player_setup_item/general/basic/load_special() +/datum/category_item/player_setup_item/general/basic/load_character_special() pref.can_edit_name = TRUE pref.can_edit_ipc_tag = TRUE @@ -134,7 +134,7 @@ if(GLOB.config.ipc_timelock_active) pref.can_edit_ipc_tag = FALSE else - log_world("ERROR: SQL CHARACTER LOAD: Logic error, general/basic/load_special() didn't return any rows when it should have. Character ID: [pref.current_character].") + log_world("ERROR: SQL CHARACTER LOAD: Logic error, general/basic/load_character_special() didn't return any rows when it should have. Character ID: [pref.current_character].") /datum/category_item/player_setup_item/general/basic/sanitize_character() if(!pref.species) diff --git a/code/modules/client/preference_setup/general/07_psionics.dm b/code/modules/client/preference_setup/general/07_psionics.dm index ad207faabf7..12f9df76aa1 100644 --- a/code/modules/client/preference_setup/general/07_psionics.dm +++ b/code/modules/client/preference_setup/general/07_psionics.dm @@ -48,7 +48,7 @@ "ckey" = PREF_CLIENT_CKEY ) -/datum/category_item/player_setup_item/general/psionics/load_special(savefile/S) +/datum/category_item/player_setup_item/general/psionics/load_character_special(savefile/S) if(!pref.psionics) pref.psionics = "{}" diff --git a/code/modules/client/preference_setup/loadout/loadout.dm b/code/modules/client/preference_setup/loadout/loadout.dm index 7d59dbb7c35..4708680ad02 100644 --- a/code/modules/client/preference_setup/loadout/loadout.dm +++ b/code/modules/client/preference_setup/loadout/loadout.dm @@ -41,27 +41,58 @@ var/list/gear_datums = list() /datum/category_item/player_setup_item/loadout/load_character(var/savefile/S) S["gear"] >> pref.gear S["gear_list"] >> pref.gear_list - if(pref.gear_list!=null && pref.gear_slot!=null) - pref.gear = pref.gear_list["[pref.gear_slot]"] - else - S["gear"] >> pref.gear + +/datum/category_item/player_setup_item/loadout/load_character_special(var/savefile/S) + pref.gear_modified = FALSE + //Query the ss13_characters_gear table + if(GLOB.config.sql_saves && establish_db_connection(GLOB.dbcon)) + var/fetched_gear = list() + + var/DBQuery/character_gear_query = GLOB.dbcon.NewQuery("SELECT slot, name, tweaks FROM ss13_characters_gear WHERE char_id = :char_id:") + character_gear_query.Execute(list("char_id"=pref.current_character)) + while(character_gear_query.NextRow()) + CHECK_TICK + var/slot = character_gear_query.item[1] + var/name = character_gear_query.item[2] + + if(!fetched_gear[slot]) + fetched_gear[slot] = list() + + var/tweaks = list() + if(character_gear_query.item[3]) + tweaks = json_decode(character_gear_query.item[3]) + + fetched_gear[slot][name]=tweaks + + pref.gear_list = fetched_gear + return + +/datum/category_item/player_setup_item/loadout/save_character_special(var/savefile/S) + //Persist the loadout to the database + if(GLOB.config.sql_saves && establish_db_connection(GLOB.dbcon) && pref.gear_modified) + var/gear_string = json_encode(pref.gear_list["[pref.gear_slot]"]) + var/DBQuery/character_gear_save_query = GLOB.dbcon.NewQuery("CALL `update_character_gear`(:char_id:, :slot_id:, :gear_data:)") + character_gear_save_query.Execute(list("char_id"=pref.current_character,"slot_id"=pref.gear_slot,"gear_data"=gear_string)) + pref.gear_modified = FALSE + return /datum/category_item/player_setup_item/loadout/save_character(var/savefile/S) pref.gear_list["[pref.gear_slot]"] = pref.gear to_file(S["gear_list"], pref.gear_list) to_file(S["gear_slot"], pref.gear_slot) +/datum/category_item/player_setup_item/loadout/gather_load_query() + return list("ss13_characters" = list("vars" = list("gear_slot"), "args" = list("id"))) + /datum/category_item/player_setup_item/loadout/gather_load_parameters() return list("id" = pref.current_character) -/datum/category_item/player_setup_item/loadout/gather_load_query() - return list("ss13_characters" = list("vars" = list("gear" = "gear_list", "gear_slot"), "args" = list("id"))) - /datum/category_item/player_setup_item/loadout/gather_save_query() - return list("ss13_characters" = list("gear", "gear_slot", "id" = 1, "ckey" = 1)) + return list("ss13_characters" = list("gear_slot", "id" = 1, "ckey" = 1)) /datum/category_item/player_setup_item/loadout/gather_save_parameters() - return list("gear" = json_encode(pref.gear_list), "gear_slot" = pref.gear_slot, "id" = pref.current_character, "ckey" = PREF_CLIENT_CKEY) + return list("gear_slot" = pref.gear_slot, "id" = pref.current_character, "ckey" = PREF_CLIENT_CKEY) + /datum/category_item/player_setup_item/loadout/proc/valid_gear_choices(var/max_cost) . = list() @@ -131,7 +162,7 @@ var/list/gear_datums = list() pref.gear -= gear_name else var/datum/gear/G = gear_datums[gear_name] - if(total_cost + G.cost > MAX_GEAR_COST) + if(total_cost + G.cost > GLOB.config.loadout_cost) pref.gear -= gear_name to_chat(preference_mob, "You cannot afford to take \the [gear_name]") else @@ -146,13 +177,13 @@ var/list/gear_datums = list() total_cost += G.cost var/fcolor = "#3366CC" - if(total_cost < MAX_GEAR_COST) + if(total_cost < GLOB.config.loadout_cost) fcolor = "#E67300" . = list() . += "