diff --git a/BSQL.dll b/BSQL.dll deleted file mode 100644 index 861492c8b4..0000000000 Binary files a/BSQL.dll and /dev/null differ diff --git a/SQL/ban_conversion_2018-10-28.py b/SQL/ban_conversion_2018-10-28.py new file mode 100644 index 0000000000..26d928bfd1 --- /dev/null +++ b/SQL/ban_conversion_2018-10-28.py @@ -0,0 +1,174 @@ +#Python 3+ Script for converting ban table format as of 2018-10-28 made by Jordie0608 +# +#Before starting ensure you have installed the mysqlclient package https://github.com/PyMySQL/mysqlclient-python +#It can be downloaded from command line with pip: +#pip install mysqlclient +# +#You will also have to create a new ban table for inserting converted data to per the schema: +#CREATE TABLE `ban` ( +# `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, +# `bantime` DATETIME NOT NULL, +# `server_ip` INT(10) UNSIGNED NOT NULL, +# `server_port` SMALLINT(5) UNSIGNED NOT NULL, +# `round_id` INT(11) UNSIGNED NOT NULL, +# `role` VARCHAR(32) NULL DEFAULT NULL, +# `expiration_time` DATETIME NULL DEFAULT NULL, +# `applies_to_admins` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0', +# `reason` VARCHAR(2048) NOT NULL, +# `ckey` VARCHAR(32) NULL DEFAULT NULL, +# `ip` INT(10) UNSIGNED NULL DEFAULT NULL, +# `computerid` VARCHAR(32) NULL DEFAULT NULL, +# `a_ckey` VARCHAR(32) NOT NULL, +# `a_ip` INT(10) UNSIGNED NOT NULL, +# `a_computerid` VARCHAR(32) NOT NULL, +# `who` VARCHAR(2048) NOT NULL, +# `adminwho` VARCHAR(2048) NOT NULL, +# `edits` TEXT NULL DEFAULT NULL, +# `unbanned_datetime` DATETIME NULL DEFAULT NULL, +# `unbanned_ckey` VARCHAR(32) NULL DEFAULT NULL, +# `unbanned_ip` INT(10) UNSIGNED NULL DEFAULT NULL, +# `unbanned_computerid` VARCHAR(32) NULL DEFAULT NULL, +# `unbanned_round_id` INT(11) UNSIGNED NULL DEFAULT NULL, +# PRIMARY KEY (`id`), +# KEY `idx_ban_isbanned` (`ckey`,`role`,`unbanned_datetime`,`expiration_time`), +# KEY `idx_ban_isbanned_details` (`ckey`,`ip`,`computerid`,`role`,`unbanned_datetime`,`expiration_time`), +# KEY `idx_ban_count` (`bantime`,`a_ckey`,`applies_to_admins`,`unbanned_datetime`,`expiration_time`) +#) ENGINE=InnoDB DEFAULT CHARSET=latin1; +#This is to prevent the destruction of existing data and allow rollbacks to be performed in the event of an error during conversion +#Once conversion is complete remember to rename the old and new ban tables; it's up to you if you want to keep the old table +# +#To view the parameters for this script, execute it with the argument --help +#All the positional arguments are required, remember to include prefixes in your table names if you use them +#An example of the command used to execute this script from powershell: +#python ban_conversion_2018-10-28.py "localhost" "root" "password" "feedback" "SS13_ban" "SS13_ban_new" +#I found that this script would complete conversion of 35000 rows in approximately 20 seconds, results will depend on the size of your ban table and computer used +# +#The script has been tested to complete with tgstation's ban table as of 2018-09-02 02:19:56 +#In the event of an error the new ban table is automatically truncated +#The source table is never modified so you don't have to worry about losing any data due to errors +#Some additional error correction is performed to fix problems specific to legacy and invalid data in tgstation's ban table, these operations are tagged with a 'TG:' comment +#Even if you don't have any of these specific problems in your ban table the operations won't have matter as they have an insignificant effect on runtime +# +#While this script is safe to run with your game server(s) active, any bans created after the script has started won't be converted +#You will also have to ensure that the code and table names are updated between rounds as neither will be compatible + +import MySQLdb +import argparse +import sys +from datetime import datetime + +def parse_role(bantype, job): + if bantype in ("PERMABAN", "TEMPBAN", "ADMIN_PERMABAN", "ADMIN_TEMPBAN"): + role = "Server" + else: + #TG: Some legacy jobbans are missing the last character from their job string. + job_name_fixes = {"A":"AI", "Captai":"Captain", "Cargo Technicia":"Cargo Technician", "Chaplai":"Chaplain", "Che":"Chef", "Chemis":"Chemist", "Chief Enginee":"Chief Engineer", "Chief Medical Office":"Chief Medical Officer", "Cybor":"Cyborg", "Detectiv":"Detective", "Head of Personne":"Head of Personnel", "Head of Securit":"Head of Security", "Mim":"Mime", "pA":"pAI", "Quartermaste":"Quartermaster", "Research Directo":"Research Director", "Scientis":"Scientist", "Security Office":"Security Officer", "Station Enginee":"Station Engineer", "Syndicat":"Syndicate", "Warde":"Warden"} + keep_job_names = ("AI", "Head of Personnel", "Head of Security", "OOC", "pAI") + if job in job_name_fixes: + role = job_name_fixes[job] + #Some job names we want to keep the same as .title() would return a different string. + elif job in keep_job_names: + role = job + #And then there's this asshole. + elif job == "servant of Ratvar": + role = "Servant of Ratvar" + else: + role = job.title() + return role + +def parse_admin(bantype): + if bantype in ("ADMIN_PERMABAN", "ADMIN_TEMPBAN"): + return 1 + else: + return 0 + +def parse_datetime(bantype, expiration_time): + if bantype in ("PERMABAN", "JOB_PERMABAN", "ADMIN_PERMABAN"): + expiration_time = None + #TG: two bans with an invalid expiration_time due to admins setting the duration to approx. 19 billion years, I'm going to count them as permabans. + elif expiration_time == "0000-00-00 00:00:00": + expiration_time = None + elif not expiration_time: + expiration_time = None + return expiration_time + +def parse_not_null(field): + if not field: + field = 0 + return field + +def parse_for_empty(field): + if not field: + field = None + #TG: Several bans from 2012, probably from clients disconnecting while a ban was being made. + elif field == "BLANK CKEY ERROR": + field = None + return field + +if sys.version_info[0] < 3: + raise Exception("Python must be at least version 3 for this script.") +current_round = 0 +parser = argparse.ArgumentParser() +parser.add_argument("address", help="MySQL server address (use localhost for the current computer)") +parser.add_argument("username", help="MySQL login username") +parser.add_argument("password", help="MySQL login username") +parser.add_argument("database", help="Database name") +parser.add_argument("curtable", help="Name of the current ban table (remember prefixes if you use them)") +parser.add_argument("newtable", help="Name of the new table to insert to, can't be same as the source table (remember prefixes)") +args = parser.parse_args() +db=MySQLdb.connect(host=args.address, user=args.username, passwd=args.password, db=args.database) +cursor=db.cursor() +current_table = args.curtable +new_table = args.newtable +#TG: Due to deleted rows and a legacy ban import being inserted from id 3140 id order is not contiguous or in line with date order. While technically valid, it's confusing and I don't like that. +#TG: So instead of just running through to MAX(id) we're going to reorder the records by bantime as we go. +cursor.execute("SELECT id FROM " + current_table + " ORDER BY bantime ASC") +id_list = cursor.fetchall() +start_time = datetime.now() +print("Beginning conversion at {0}".format(start_time.strftime("%Y-%m-%d %H:%M:%S"))) +try: + for current_id in id_list: + if current_id[0] % 5000 == 0: + cur_time = datetime.now() + print("Reached row ID {0} Duration: {1}".format(current_id[0], cur_time - start_time)) + cursor.execute("SELECT * FROM " + current_table + " WHERE id = %s", [current_id[0]]) + query_row = cursor.fetchone() + if not query_row: + continue + else: + #TG: bans with an empty reason which were somehow created with almost every field being null or empty, we can't do much but skip this + if not query_row[6]: + continue + bantime = query_row[1] + server_ip = query_row[2] + server_port = query_row[3] + round_id = query_row[4] + applies_to_admins = parse_admin(query_row[5]) + reason = query_row[6] + role = parse_role(query_row[5], query_row[7]) + expiration_time = parse_datetime(query_row[5], query_row[9]) + ckey = parse_for_empty(query_row[10]) + computerid = parse_for_empty(query_row[11]) + ip = parse_for_empty(query_row[12]) + a_ckey = parse_not_null(query_row[13]) + a_computerid = parse_not_null(query_row[14]) + a_ip = parse_not_null(query_row[15]) + who = query_row[16] + adminwho = query_row[17] + edits = parse_for_empty(query_row[18]) + unbanned_datetime = parse_datetime(None, query_row[20]) + unbanned_ckey = parse_for_empty(query_row[21]) + unbanned_computerid = parse_for_empty(query_row[22]) + unbanned_ip = parse_for_empty(query_row[23]) + cursor.execute("INSERT INTO " + new_table + " (bantime, server_ip, server_port, round_id, role, expiration_time, applies_to_admins, reason, ckey, ip, computerid, a_ckey, a_ip, a_computerid, who, adminwho, edits, unbanned_datetime, unbanned_ckey, unbanned_ip, unbanned_computerid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", (bantime, server_ip, server_port, round_id, role, expiration_time, applies_to_admins, reason, ckey, ip, computerid, a_ckey, a_ip, a_computerid, who, adminwho, edits, unbanned_datetime, unbanned_ckey, unbanned_ip, unbanned_computerid)) + db.commit() + end_time = datetime.now() + print("Conversion completed at {0}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + print("Script duration: {0}".format(end_time - start_time)) +except Exception as e: + end_time = datetime.now() + print("Error encountered on row ID {0} at {1}".format(current_id[0], datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + print("Script duration: {0}".format(end_time - start_time)) + cursor.execute("TRUNCATE {0} ".format(new_table)) + raise e +cursor.close() diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt index 56f05e84f1..410223a005 100644 --- a/SQL/database_changelog.txt +++ b/SQL/database_changelog.txt @@ -1,13 +1,256 @@ Any time you make a change to the schema files, remember to increment the database schema version. Generally increment the minor number, major should be reserved for significant changes to the schema. Both values go up to 255. -The latest database version is 4.7; The query to update the schema revision table is: +The latest database version is 5.12; The query to update the schema revision table is: -INSERT INTO `schema_revision` (`major`, `minor`) VALUES (4, 7); +INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 12); or -INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (4, 7); +INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 12); In any query remember to add a prefix to the table names if you use one. +#################################################################################################### +NOTICE! BANS AND OTHERS ARENT SET TO THEIR LATEST FORMAT YET AS THE BACKEND DOES NOT SUPPORT IT YET! +#################################################################################################### + +----------------------------------------------------- + +Version 5.12, 29 December 2020, by Missfox +Modified table `messages`, adding column `playtime` to show the user's playtime when the note was created. + +ALTER TABLE `messages` ADD `playtime` INT(11) NULL DEFAULT(NULL) AFTER `severity` + +----------------------------------------------------- + +Version 5.11, 7 September 2020, by bobbahbrown, MrStonedOne, and Jordie0608 + +Adds indices to support search operations on the adminhelp ticket tables. This is to support improved performance on Atlanta Ned's Statbus. + +CREATE INDEX `idx_ticket_act_recip` (`action`, `recipient`) +CREATE INDEX `idx_ticket_act_send` (`action`, `sender`) +CREATE INDEX `idx_ticket_tic_rid` (`ticket`, `round_id`) +CREATE INDEX `idx_ticket_act_time_rid` (`action`, `timestamp`, `round_id`) + +----------------------------------------------------- + +Version 5.10, 7 August 2020, by oranges + +Changes how the discord verification process works. +Adds the discord_links table, and migrates discord id entries from player table to the discord links table in a once off operation and then removes the discord id +on the player table + +START TRANSACTION; + +DROP TABLE IF EXISTS `discord_links`; +CREATE TABLE `discord_links` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ckey` VARCHAR(32) NOT NULL, + `discord_id` BIGINT(20) DEFAULT NULL, + `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `one_time_token` VARCHAR(100) NOT NULL, + `valid` BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +INSERT INTO `discord_links` (`ckey`, `discord_id`, `one_time_token`, `valid`) SELECT `ckey`, `discord_id`, CONCAT("presync_from_player_table_", `ckey`), TRUE FROM `player` WHERE discord_id IS NOT NULL; + +ALTER TABLE `player` DROP COLUMN `discord_id`; + +COMMIT; + +----------------------------------------------------- + +Version 5.9, 19 April 2020, by Jordie0608 +Updates and improvements to poll handling. +Added the `deleted` column to tables 'poll_option', 'poll_textreply' and 'poll_vote' and the columns `created_datetime`, `subtitle`, `allow_revoting` and `deleted` to 'poll_question'. +Changes table 'poll_question' column `createdby_ckey` to be NOT NULL and index `idx_pquest_time_admin` to be `idx_pquest_time_deleted_id` and 'poll_textreply' column `adminrank` to have no default. +Added procedure `set_poll_deleted` that's called when deleting a poll to set deleted to true on each poll table where rows matching a poll_id argument. + +ALTER TABLE `poll_option` + ADD COLUMN `deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `default_percentage_calc`; + +ALTER TABLE `poll_question` + CHANGE COLUMN `createdby_ckey` `createdby_ckey` VARCHAR(32) NOT NULL AFTER `multiplechoiceoptions`, + ADD COLUMN `created_datetime` datetime NOT NULL AFTER `polltype`, + ADD COLUMN `subtitle` VARCHAR(255) NULL DEFAULT NULL AFTER `question`, + ADD COLUMN `allow_revoting` TINYINT(1) UNSIGNED NOT NULL AFTER `dontshow`, + ADD COLUMN `deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `allow_revoting`, + DROP INDEX `idx_pquest_time_admin`, + ADD INDEX `idx_pquest_time_deleted_id` (`starttime`, `endtime`, `deleted`, `id`); + +ALTER TABLE `poll_textreply` + CHANGE COLUMN `adminrank` `adminrank` varchar(32) NOT NULL AFTER `replytext`, + ADD COLUMN `deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `adminrank`; + +ALTER TABLE `poll_vote` + ADD COLUMN `deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `rating`; + +DELIMITER $$ +CREATE PROCEDURE `set_poll_deleted`( + IN `poll_id` INT +) +SQL SECURITY INVOKER +BEGIN +UPDATE `poll_question` SET deleted = 1 WHERE id = poll_id; +UPDATE `poll_option` SET deleted = 1 WHERE pollid = poll_id; +UPDATE `poll_vote` SET deleted = 1 WHERE pollid = poll_id; +UPDATE `poll_textreply` SET deleted = 1 WHERE pollid = poll_id; +END +$$ +DELIMITER ; + +----------------------------------------------------- + +Version 5.8, 7 April 2020, by Jordie0608 +Modified table `messages`, adding column `deleted_ckey` to record who deleted a message. + +ALTER TABLE `messages` ADD COLUMN `deleted_ckey` VARCHAR(32) NULL DEFAULT NULL AFTER `deleted`; + +----------------------------------------------------- + +Version 5.7, 10 January 2020 by Atlanta-Ned +Added ticket table for tracking ahelp tickets in the database. + +DROP TABLE IF EXISTS `ticket`; +CREATE TABLE `ticket` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `ticket` smallint(11) unsigned NOT NULL, + `action` varchar(20) NOT NULL DEFAULT 'Message', + `message` text NOT NULL, + `timestamp` datetime NOT NULL, + `recipient` varchar(32) DEFAULT NULL, + `sender` varchar(32) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +----------------------------------------------------- + +Version 5.6, 6 December 2019 by Anturke +Added achievement_name and achievement_description columns to achievement_metadata table. + + +ALTER TABLE `achievement_metadata` ADD COLUMN (`achievement_name` VARCHAR(64) NULL DEFAULT NULL, `achievement_description` VARCHAR(512) NULL DEFAULT NULL); + +----------------------------------------------------- + +Version 5.5, 26 October 2019 by Anturke +Added achievement_metadata table. + +DROP TABLE IF EXISTS `achievement_metadata`; +CREATE TABLE `achievement_metadata` ( + `achievement_key` VARCHAR(32) NOT NULL, + `achievement_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0, + `achievement_type` enum('achievement','score','award') NULL DEFAULT NULL, + PRIMARY KEY (`achievement_key`) +) ENGINE=InnoDB; + + +----------------------------------------------------- + +Version 5.4, 5 October 2019 by Anturke +Added achievements table. +See hub migration verb in _achievement_data.dm for details on migrating. + +CREATE TABLE `achievements` ( + `ckey` VARCHAR(32) NOT NULL, + `achievement_key` VARCHAR(32) NOT NULL, + `value` INT NULL, + `last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`,`achievement_key`) +) ENGINE=InnoDB; + +---------------------------------------------------- + +Version 5.3, 6 July 2019, by Atlanta-Ned +Added a `feedback` column to the admin table, used for linking to individual admin feedback threads. Currently this is only used for statistics tracking tools such as Statbus and isn't used by the game. + +ALTER TABLE `admin` ADD `feedback` VARCHAR(255) NULL DEFAULT NULL AFTER `rank`; + +---------------------------------------------------- + +Version 5.2, 30 May 2019, by AffectedArc07 +Added a field to the `player` table to track ckey and discord ID relationships + +ALTER TABLE `player` + ADD COLUMN `discord_id` BIGINT NULL DEFAULT NULL AFTER `flags`; +---------------------------------------------------- + +Version 5.1, 25 Feb 2018, by MrStonedOne +Added four tables to enable storing of stickybans in the database since byond can lose them, and to enable disabling stickybans for a round without depending on a crash free round. Existing stickybans are automagically imported to the tables. + +CREATE TABLE `stickyban` ( + `ckey` VARCHAR(32) NOT NULL, + `reason` VARCHAR(2048) NOT NULL, + `banning_admin` VARCHAR(32) NOT NULL, + `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB; + +CREATE TABLE `stickyban_matched_ckey` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ckey` VARCHAR(32) NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `exempt` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`stickyban`, `matched_ckey`) +) ENGINE=InnoDB; + +CREATE TABLE `stickyban_matched_ip` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ip` INT UNSIGNED NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_ip`) +) ENGINE=InnoDB; + +CREATE TABLE `stickyban_matched_cid` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_cid` VARCHAR(32) NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_cid`) +) ENGINE=InnoDB; + +---------------------------------------------------- + +Version 5.0, 28 October 2018, by Jordie0608 +Modified ban table to remove the need for the `bantype` column, a python script is used to migrate data to this new format. + +See the file 'ban_conversion_2018-10-28.py' for instructions on how to use the script. + +A new ban table can be created with the query: +CREATE TABLE `ban` ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `bantime` DATETIME NOT NULL, + `server_ip` INT(10) UNSIGNED NOT NULL, + `server_port` SMALLINT(5) UNSIGNED NOT NULL, + `round_id` INT(11) UNSIGNED NOT NULL, + `role` VARCHAR(32) NULL DEFAULT NULL, + `expiration_time` DATETIME NULL DEFAULT NULL, + `applies_to_admins` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0', + `reason` VARCHAR(2048) NOT NULL, + `ckey` VARCHAR(32) NULL DEFAULT NULL, + `ip` INT(10) UNSIGNED NULL DEFAULT NULL, + `computerid` VARCHAR(32) NULL DEFAULT NULL, + `a_ckey` VARCHAR(32) NOT NULL, + `a_ip` INT(10) UNSIGNED NOT NULL, + `a_computerid` VARCHAR(32) NOT NULL, + `who` VARCHAR(2048) NOT NULL, + `adminwho` VARCHAR(2048) NOT NULL, + `edits` TEXT NULL DEFAULT NULL, + `unbanned_datetime` DATETIME NULL DEFAULT NULL, + `unbanned_ckey` VARCHAR(32) NULL DEFAULT NULL, + `unbanned_ip` INT(10) UNSIGNED NULL DEFAULT NULL, + `unbanned_computerid` VARCHAR(32) NULL DEFAULT NULL, + `unbanned_round_id` INT(11) UNSIGNED NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_ban_isbanned` (`ckey`,`role`,`unbanned_datetime`,`expiration_time`), + KEY `idx_ban_isbanned_details` (`ckey`,`ip`,`computerid`,`role`,`unbanned_datetime`,`expiration_time`), + KEY `idx_ban_count` (`bantime`,`a_ckey`,`applies_to_admins`,`unbanned_datetime`,`expiration_time`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + ---------------------------------------------------- Version 4.7, 18 August 2018, by CitrusGender @@ -50,8 +293,7 @@ Added table `role_time_log` and triggers `role_timeTlogupdate`, `role_timeTlogin CREATE TABLE `role_time_log` ( `id` BIGINT NOT NULL AUTO_INCREMENT , `ckey` VARCHAR(32) NOT NULL , `job` VARCHAR(128) NOT NULL , `delta` INT NOT NULL , `datetime` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , PRIMARY KEY (`id`), INDEX (`ckey`), INDEX (`job`), INDEX (`datetime`)) ENGINE = InnoDB; -DELIMITER -$$ +DELIMITER $$ CREATE TRIGGER `role_timeTlogupdate` AFTER UPDATE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes); END $$ @@ -61,7 +303,7 @@ $$ CREATE TRIGGER `role_timeTlogdelete` AFTER DELETE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes); END $$ - +DELIMITER ; ---------------------------------------------------- Version 4.2, 17 April 2018, by Jordie0608 diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 34baaaa4c6..9a9847a372 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -19,8 +19,9 @@ DROP TABLE IF EXISTS `admin`; CREATE TABLE `admin` ( `ckey` varchar(32) NOT NULL, `rank` varchar(32) NOT NULL, + `feedback` varchar(255) DEFAULT NULL, PRIMARY KEY (`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -40,7 +41,7 @@ CREATE TABLE `admin_log` ( `target` varchar(32) NOT NULL, `log` varchar(1000) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -56,7 +57,7 @@ CREATE TABLE `admin_ranks` ( `exclude_flags` smallint(5) unsigned NOT NULL, `can_edit_flags` smallint(5) unsigned NOT NULL, PRIMARY KEY (`rank`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -67,11 +68,11 @@ DROP TABLE IF EXISTS `ban`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `ban` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `bantime` datetime NOT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) NOT NULL, + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `bantime` DATETIME NOT NULL, + `server_ip` INT(10) UNSIGNED NOT NULL, + `server_port` SMALLINT(5) UNSIGNED NOT NULL, + `round_id` INT(11) UNSIGNED NOT NULL, `bantype` enum('PERMABAN','TEMPBAN','JOB_PERMABAN','JOB_TEMPBAN','ADMIN_PERMABAN','ADMIN_TEMPBAN') NOT NULL, `reason` varchar(2048) NOT NULL, `job` varchar(32) DEFAULT NULL, @@ -95,7 +96,7 @@ CREATE TABLE `ban` ( KEY `idx_ban_checkban` (`ckey`,`bantype`,`expiration_time`,`unbanned`,`job`), KEY `idx_ban_isbanned` (`ckey`,`ip`,`computerid`,`bantype`,`expiration_time`,`unbanned`), KEY `idx_ban_count` (`id`,`a_ckey`,`bantype`,`expiration_time`,`unbanned`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -115,7 +116,7 @@ CREATE TABLE `connection_log` ( `ip` int(10) unsigned NOT NULL, `computerid` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -152,7 +153,7 @@ CREATE TABLE `death` ( `last_words` varchar(255) DEFAULT NULL, `suicide` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -171,7 +172,7 @@ CREATE TABLE `feedback` ( `version` tinyint(3) unsigned NOT NULL, `json` json NOT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -187,7 +188,7 @@ CREATE TABLE `ipintel` ( `intel` double NOT NULL DEFAULT '0', PRIMARY KEY (`ip`), KEY `idx_ipintel` (`ip`,`intel`,`date`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -206,7 +207,7 @@ CREATE TABLE `legacy_population` ( `server_port` smallint(5) unsigned NOT NULL, `round_id` int(11) unsigned NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -231,7 +232,7 @@ CREATE TABLE `library` ( KEY `idx_lib_id_del` (`id`,`deleted`), KEY `idx_lib_del_title` (`deleted`,`title`), KEY `idx_lib_search` (`deleted`,`author`,`title`,`category`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -255,14 +256,16 @@ CREATE TABLE `messages` ( `secret` tinyint(1) unsigned NOT NULL, `expire_timestamp` datetime DEFAULT NULL, `severity` enum('high','medium','minor','none') DEFAULT NULL, + `playtime` int(11) unsigned NULL DEFAULT NULL, `lasteditor` varchar(32) DEFAULT NULL, `edits` text, `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', + `deleted_ckey` VARCHAR(32) NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_msg_ckey_time` (`targetckey`,`timestamp`, `deleted`), KEY `idx_msg_type_ckeys_time` (`type`,`targetckey`,`adminckey`,`timestamp`, `deleted`), KEY `idx_msg_type_ckey_time_odr` (`type`,`targetckey`,`timestamp`, `deleted`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -299,7 +302,7 @@ CREATE TABLE IF NOT EXISTS `role_time_log` ( KEY `ckey` (`ckey`), KEY `job` (`job`), KEY `datetime` (`datetime`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -324,7 +327,7 @@ CREATE TABLE `player` ( PRIMARY KEY (`ckey`), KEY `idx_player_cid_ckey` (`computerid`,`ckey`), KEY `idx_player_ip_ckey` (`ip`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -344,9 +347,10 @@ CREATE TABLE `poll_option` ( `descmid` varchar(32) DEFAULT NULL, `descmax` varchar(32) DEFAULT NULL, `default_percentage_calc` tinyint(1) unsigned NOT NULL DEFAULT '1', + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_pop_pollid` (`pollid`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -359,19 +363,23 @@ DROP TABLE IF EXISTS `poll_question`; CREATE TABLE `poll_question` ( `id` int(11) NOT NULL AUTO_INCREMENT, `polltype` enum('OPTION','TEXT','NUMVAL','MULTICHOICE','IRV') NOT NULL, + `created_datetime` datetime NOT NULL, `starttime` datetime NOT NULL, `endtime` datetime NOT NULL, `question` varchar(255) NOT NULL, + `subtitle` varchar(255) DEFAULT NULL, `adminonly` tinyint(1) unsigned NOT NULL, `multiplechoiceoptions` int(2) DEFAULT NULL, - `createdby_ckey` varchar(32) DEFAULT NULL, + `createdby_ckey` varchar(32) NOT NULL, `createdby_ip` int(10) unsigned NOT NULL, `dontshow` tinyint(1) unsigned NOT NULL, + `allow_revoting` tinyint(1) unsigned NOT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_pquest_question_time_ckey` (`question`,`starttime`,`endtime`,`createdby_ckey`,`createdby_ip`), - KEY `idx_pquest_time_admin` (`starttime`,`endtime`,`adminonly`), + KEY `idx_pquest_time_deleted_id` (`starttime`,`endtime`, `deleted`, `id`), KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -388,10 +396,11 @@ CREATE TABLE `poll_textreply` ( `ckey` varchar(32) NOT NULL, `ip` int(10) unsigned NOT NULL, `replytext` varchar(2048) NOT NULL, - `adminrank` varchar(32) NOT NULL DEFAULT 'Player', + `adminrank` varchar(32) NOT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_ptext_pollid_ckey` (`pollid`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -410,10 +419,11 @@ CREATE TABLE `poll_vote` ( `ip` int(10) unsigned NOT NULL, `adminrank` varchar(32) NOT NULL, `rating` int(2) DEFAULT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_pvote_pollid_ckey` (`pollid`,`ckey`), KEY `idx_pvote_optionid_ckey` (`optionid`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -438,7 +448,7 @@ CREATE TABLE `round` ( `map_name` VARCHAR(32) NULL, `station_name` VARCHAR(80) NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -451,9 +461,113 @@ CREATE TABLE `schema_revision` ( `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=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- +-- Table structure for table `stickyban` +-- +DROP TABLE IF EXISTS `stickyban`; +CREATE TABLE `stickyban` ( + `ckey` VARCHAR(32) NOT NULL, + `reason` VARCHAR(2048) NOT NULL, + `banning_admin` VARCHAR(32) NOT NULL, + `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `stickyban_matched_ckey` +-- +DROP TABLE IF EXISTS `stickyban_matched_ckey`; +CREATE TABLE `stickyban_matched_ckey` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ckey` VARCHAR(32) NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `exempt` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`stickyban`, `matched_ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `stickyban_matched_ip` +-- +DROP TABLE IF EXISTS `stickyban_matched_ip`; +CREATE TABLE `stickyban_matched_ip` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ip` INT UNSIGNED NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_ip`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `stickyban_matched_cid` +-- +DROP TABLE IF EXISTS `stickyban_matched_cid`; +CREATE TABLE `stickyban_matched_cid` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_cid` VARCHAR(32) NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_cid`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `achievements` +-- +DROP TABLE IF EXISTS `achievements`; +CREATE TABLE `achievements` ( + `ckey` VARCHAR(32) NOT NULL, + `achievement_key` VARCHAR(32) NOT NULL, + `value` INT NULL, + `last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`,`achievement_key`) +) ENGINE=InnoDB; + +DROP TABLE IF EXISTS `achievement_metadata`; +CREATE TABLE `achievement_metadata` ( + `achievement_key` VARCHAR(32) NOT NULL, + `achievement_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0, + `achievement_type` enum('achievement','score','award') NULL DEFAULT NULL, + `achievement_name` VARCHAR(64) NULL DEFAULT NULL, + `achievement_description` VARCHAR(512) NULL DEFAULT NULL, + PRIMARY KEY (`achievement_key`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `ticket` +-- +DROP TABLE IF EXISTS `ticket`; +CREATE TABLE `ticket` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `ticket` smallint(11) unsigned NOT NULL, + `action` varchar(20) NOT NULL DEFAULT 'Message', + `message` text NOT NULL, + `timestamp` datetime NOT NULL, + `recipient` varchar(32) DEFAULT NULL, + `sender` varchar(32) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_ticket_act_recip` (`action`, `recipient`), + KEY `idx_ticket_act_send` (`action`, `sender`), + KEY `idx_ticket_tic_rid` (`ticket`, `round_id`), + KEY `idx_ticket_act_time_rid` (`action`, `timestamp`, `round_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; DELIMITER $$ +CREATE PROCEDURE `set_poll_deleted`( + IN `poll_id` INT +) +SQL SECURITY INVOKER +BEGIN +UPDATE `poll_question` SET deleted = 1 WHERE id = poll_id; +UPDATE `poll_option` SET deleted = 1 WHERE pollid = poll_id; +UPDATE `poll_vote` SET deleted = 1 WHERE pollid = poll_id; +UPDATE `poll_textreply` SET deleted = 1 WHERE pollid = poll_id; +END +$$ CREATE TRIGGER `role_timeTlogupdate` AFTER UPDATE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes); END $$ @@ -463,6 +577,21 @@ $$ CREATE TRIGGER `role_timeTlogdelete` AFTER DELETE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes); END $$ +DELIMITER ; + +-- +-- Table structure for table `discord_links` +-- +DROP TABLE IF EXISTS `discord_links`; +CREATE TABLE `discord_links` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ckey` VARCHAR(32) NOT NULL, + `discord_id` BIGINT(20) DEFAULT NULL, + `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `one_time_token` VARCHAR(100) NOT NULL, + `valid` BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index 5cb57a9582..654f45f4f2 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -19,8 +19,9 @@ DROP TABLE IF EXISTS `SS13_admin`; CREATE TABLE `SS13_admin` ( `ckey` varchar(32) NOT NULL, `rank` varchar(32) NOT NULL, + `feedback` varchar(255) DEFAULT NULL, PRIMARY KEY (`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -40,7 +41,7 @@ CREATE TABLE `SS13_admin_log` ( `target` varchar(32) NOT NULL, `log` varchar(1000) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -56,7 +57,7 @@ CREATE TABLE `SS13_admin_ranks` ( `exclude_flags` smallint(5) unsigned NOT NULL, `can_edit_flags` smallint(5) unsigned NOT NULL, PRIMARY KEY (`rank`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -67,11 +68,11 @@ DROP TABLE IF EXISTS `SS13_ban`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `SS13_ban` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `bantime` datetime NOT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) NOT NULL, + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `bantime` DATETIME NOT NULL, + `server_ip` INT(10) UNSIGNED NOT NULL, + `server_port` SMALLINT(5) UNSIGNED NOT NULL, + `round_id` INT(11) UNSIGNED NOT NULL, `bantype` enum('PERMABAN','TEMPBAN','JOB_PERMABAN','JOB_TEMPBAN','ADMIN_PERMABAN','ADMIN_TEMPBAN') NOT NULL, `reason` varchar(2048) NOT NULL, `job` varchar(32) DEFAULT NULL, @@ -95,7 +96,7 @@ CREATE TABLE `SS13_ban` ( KEY `idx_ban_checkban` (`ckey`,`bantype`,`expiration_time`,`unbanned`,`job`), KEY `idx_ban_isbanned` (`ckey`,`ip`,`computerid`,`bantype`,`expiration_time`,`unbanned`), KEY `idx_ban_count` (`id`,`a_ckey`,`bantype`,`expiration_time`,`unbanned`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -115,7 +116,7 @@ CREATE TABLE `SS13_connection_log` ( `ip` int(10) unsigned NOT NULL, `computerid` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -152,7 +153,7 @@ CREATE TABLE `SS13_death` ( `last_words` varchar(255) DEFAULT NULL, `suicide` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -171,7 +172,7 @@ CREATE TABLE `SS13_feedback` ( `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL, `json` json NOT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -187,7 +188,7 @@ CREATE TABLE `SS13_ipintel` ( `intel` double NOT NULL DEFAULT '0', PRIMARY KEY (`ip`), KEY `idx_ipintel` (`ip`,`intel`,`date`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -206,7 +207,7 @@ CREATE TABLE `SS13_legacy_population` ( `server_port` smallint(5) unsigned NOT NULL, `round_id` int(11) unsigned NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -231,7 +232,7 @@ CREATE TABLE `SS13_library` ( KEY `idx_lib_id_del` (`id`,`deleted`), KEY `idx_lib_del_title` (`deleted`,`title`), KEY `idx_lib_search` (`deleted`,`author`,`title`,`category`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -255,14 +256,16 @@ CREATE TABLE `SS13_messages` ( `secret` tinyint(1) unsigned NOT NULL, `expire_timestamp` datetime DEFAULT NULL, `severity` enum('high','medium','minor','none') DEFAULT NULL, + `playtime` int(11) unsigned NULL DEFAULT NULL, `lasteditor` varchar(32) DEFAULT NULL, `edits` text, `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', + `deleted_ckey` VARCHAR(32) NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_msg_ckey_time` (`targetckey`,`timestamp`, `deleted`), KEY `idx_msg_type_ckeys_time` (`type`,`targetckey`,`adminckey`,`timestamp`, `deleted`), KEY `idx_msg_type_ckey_time_odr` (`type`,`targetckey`,`timestamp`, `deleted`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -299,7 +302,7 @@ CREATE TABLE IF NOT EXISTS `SS13_role_time_log` ( KEY `ckey` (`ckey`), KEY `job` (`job`), KEY `datetime` (`datetime`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -324,7 +327,7 @@ CREATE TABLE `SS13_player` ( PRIMARY KEY (`ckey`), KEY `idx_player_cid_ckey` (`computerid`,`ckey`), KEY `idx_player_ip_ckey` (`ip`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -344,9 +347,10 @@ CREATE TABLE `SS13_poll_option` ( `descmid` varchar(32) DEFAULT NULL, `descmax` varchar(32) DEFAULT NULL, `default_percentage_calc` tinyint(1) unsigned NOT NULL DEFAULT '1', + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_pop_pollid` (`pollid`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -359,19 +363,23 @@ DROP TABLE IF EXISTS `SS13_poll_question`; CREATE TABLE `SS13_poll_question` ( `id` int(11) NOT NULL AUTO_INCREMENT, `polltype` enum('OPTION','TEXT','NUMVAL','MULTICHOICE','IRV') NOT NULL, + `created_datetime` datetime NOT NULL, `starttime` datetime NOT NULL, `endtime` datetime NOT NULL, `question` varchar(255) NOT NULL, + `subtitle` varchar(255) DEFAULT NULL, `adminonly` tinyint(1) unsigned NOT NULL, `multiplechoiceoptions` int(2) DEFAULT NULL, - `createdby_ckey` varchar(32) DEFAULT NULL, + `createdby_ckey` varchar(32) NOT NULL, `createdby_ip` int(10) unsigned NOT NULL, `dontshow` tinyint(1) unsigned NOT NULL, + `allow_revoting` tinyint(1) unsigned NOT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_pquest_question_time_ckey` (`question`,`starttime`,`endtime`,`createdby_ckey`,`createdby_ip`), - KEY `idx_pquest_time_admin` (`starttime`,`endtime`,`adminonly`), + KEY `idx_pquest_time_deleted_id` (`starttime`,`endtime`, `deleted`, `id`), KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -388,10 +396,11 @@ CREATE TABLE `SS13_poll_textreply` ( `ckey` varchar(32) NOT NULL, `ip` int(10) unsigned NOT NULL, `replytext` varchar(2048) NOT NULL, - `adminrank` varchar(32) NOT NULL DEFAULT 'Player', + `adminrank` varchar(32) NOT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_ptext_pollid_ckey` (`pollid`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -410,10 +419,11 @@ CREATE TABLE `SS13_poll_vote` ( `ip` int(10) unsigned NOT NULL, `adminrank` varchar(32) NOT NULL, `rating` int(2) DEFAULT NULL, + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_pvote_pollid_ckey` (`pollid`,`ckey`), KEY `idx_pvote_optionid_ckey` (`optionid`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -438,7 +448,7 @@ CREATE TABLE `SS13_round` ( `map_name` VARCHAR(32) NULL, `station_name` VARCHAR(80) NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -451,9 +461,113 @@ CREATE TABLE `SS13_schema_revision` ( `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=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- +-- Table structure for table `SS13_stickyban` +-- +DROP TABLE IF EXISTS `SS13_stickyban`; +CREATE TABLE `SS13_stickyban` ( + `ckey` VARCHAR(32) NOT NULL, + `reason` VARCHAR(2048) NOT NULL, + `banning_admin` VARCHAR(32) NOT NULL, + `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `SS13_stickyban_matched_ckey` +-- +DROP TABLE IF EXISTS `SS13_stickyban_matched_ckey`; +CREATE TABLE `SS13_stickyban_matched_ckey` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ckey` VARCHAR(32) NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `exempt` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`stickyban`, `matched_ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `SS13_stickyban_matched_ip` +-- +DROP TABLE IF EXISTS `SS13_stickyban_matched_ip`; +CREATE TABLE `SS13_stickyban_matched_ip` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ip` INT UNSIGNED NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_ip`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `SS13_stickyban_matched_cid` +-- +DROP TABLE IF EXISTS `SS13_stickyban_matched_cid`; +CREATE TABLE `SS13_stickyban_matched_cid` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_cid` VARCHAR(32) NOT NULL, + `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_cid`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `SS13_achievements` +-- +DROP TABLE IF EXISTS `SS13_achievements`; +CREATE TABLE `SS13_achievements` ( + `ckey` VARCHAR(32) NOT NULL, + `achievement_key` VARCHAR(32) NOT NULL, + `value` INT NULL, + `last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`,`achievement_key`) +) ENGINE=InnoDB; + +DROP TABLE IF EXISTS `SS13_achievement_metadata`; +CREATE TABLE `SS13_achievement_metadata` ( + `achievement_key` VARCHAR(32) NOT NULL, + `achievement_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0, + `achievement_type` enum('achievement','score','award') NULL DEFAULT NULL, + `achievement_name` VARCHAR(64) NULL DEFAULT NULL, + `achievement_description` VARCHAR(512) NULL DEFAULT NULL, + PRIMARY KEY (`achievement_key`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `SS13_ticket` +-- +DROP TABLE IF EXISTS `SS13_ticket`; +CREATE TABLE `SS13_ticket` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `ticket` smallint(11) unsigned NOT NULL, + `action` varchar(20) NOT NULL DEFAULT 'Message', + `message` text NOT NULL, + `timestamp` datetime NOT NULL, + `recipient` varchar(32) DEFAULT NULL, + `sender` varchar(32) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_ticket_act_recip` (`action`, `recipient`), + KEY `idx_ticket_act_send` (`action`, `sender`), + KEY `idx_ticket_tic_rid` (`ticket`, `round_id`), + KEY `idx_ticket_act_time_rid` (`action`, `timestamp`, `round_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; DELIMITER $$ +CREATE PROCEDURE `set_poll_deleted`( + IN `poll_id` INT +) +SQL SECURITY INVOKER +BEGIN +UPDATE `SS13_poll_question` SET deleted = 1 WHERE id = poll_id; +UPDATE `SS13_poll_option` SET deleted = 1 WHERE pollid = poll_id; +UPDATE `SS13_poll_vote` SET deleted = 1 WHERE pollid = poll_id; +UPDATE `SS13_poll_textreply` SET deleted = 1 WHERE pollid = poll_id; +END +$$ CREATE TRIGGER `SS13_role_timeTlogupdate` AFTER UPDATE ON `SS13_role_time` FOR EACH ROW BEGIN INSERT into SS13_role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes); END $$ @@ -463,6 +577,21 @@ $$ CREATE TRIGGER `SS13_role_timeTlogdelete` AFTER DELETE ON `SS13_role_time` FOR EACH ROW BEGIN INSERT into SS13_role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes); END $$ +DELIMITER ; + +-- +-- Table structure for table `discord_links` +-- +DROP TABLE IF EXISTS `SS13_discord_links`; +CREATE TABLE `SS13_discord_links` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ckey` VARCHAR(32) NOT NULL, + `discord_id` BIGINT(20) DEFAULT NULL, + `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `one_time_token` VARCHAR(100) NOT NULL, + `valid` BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm b/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm index d440e2d15b..1f258fad77 100644 --- a/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm +++ b/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm @@ -294,7 +294,7 @@ /turf/template_noop, /area/space/nearstation) "N" = ( -/obj/item/book/random/triple, +/obj/item/book/random, /turf/open/floor/plasteel, /area/ruin/space/has_grav/powered/ancient_shuttle) "O" = ( diff --git a/_maps/RandomZLevels/away_mission/jungleresort.dmm b/_maps/RandomZLevels/away_mission/jungleresort.dmm index 4afe638edb..9ff92b619a 100644 --- a/_maps/RandomZLevels/away_mission/jungleresort.dmm +++ b/_maps/RandomZLevels/away_mission/jungleresort.dmm @@ -413,10 +413,6 @@ /obj/item/toy/figure/chef, /turf/open/floor/wood, /area/awaymission/jungleresort) -"gC" = ( -/obj/item/clothing/head/rice_hat/cursed, -/turf/open/floor/plating/dirt/jungle, -/area/awaymission/jungleresort) "gK" = ( /obj/structure/table/wood, /obj/item/gun/ballistic/automatic/c20r/toy/unrestricted, @@ -13828,7 +13824,7 @@ io du YF io -gC +io io io ia @@ -26666,4 +26662,3 @@ bG bG bG "} - diff --git a/_maps/map_files/CogStation/CogStation.dmm b/_maps/map_files/CogStation/CogStation.dmm index e41f6c7d89..84968dc961 100644 --- a/_maps/map_files/CogStation/CogStation.dmm +++ b/_maps/map_files/CogStation/CogStation.dmm @@ -42885,7 +42885,7 @@ /area/science/xenobiology) "bPd" = ( /obj/structure/table/reinforced, -/obj/item/book/random/triple, +/obj/item/book/random, /turf/open/floor/engine, /area/science/xenobiology) "bPe" = ( diff --git a/code/__DEFINES/bsql.config.dm b/code/__DEFINES/bsql.config.dm deleted file mode 100644 index 3f2e8c4d70..0000000000 --- a/code/__DEFINES/bsql.config.dm +++ /dev/null @@ -1,6 +0,0 @@ -#define BSQL_EXTERNAL_CONFIGURATION -#define BSQL_DEL_PROC(path) ##path/Destroy() -#define BSQL_DEL_CALL(obj) qdel(##obj) -#define BSQL_IS_DELETED(obj) (QDELETED(obj)) -#define BSQL_PROTECT_DATUM(path) GENERAL_PROTECT_DATUM(##path) -#define BSQL_ERROR(message) SSdbcore.ReportError(message) diff --git a/code/__DEFINES/bsql.dm b/code/__DEFINES/bsql.dm deleted file mode 100644 index 8f2040449a..0000000000 --- a/code/__DEFINES/bsql.dm +++ /dev/null @@ -1,135 +0,0 @@ -//BSQL - DMAPI -#define BSQL_VERSION "v1.3.0.0" - -//types of connections -#define BSQL_CONNECTION_TYPE_MARIADB "MySql" -#define BSQL_CONNECTION_TYPE_SQLSERVER "SqlServer" - -#define BSQL_DEFAULT_TIMEOUT 5 -#define BSQL_DEFAULT_THREAD_LIMIT 50 - -//Call this before rebooting or shutting down your world to clean up gracefully. This invalidates all active connection and operation datums -/world/proc/BSQL_Shutdown() - return - -/* -Called whenever a library call is made with verbose information, override and do with as you please - message: English debug message -*/ -/world/proc/BSQL_Debug(msg) - return - -/* -Create a new database connection, does not perform the actual connect - connection_type: The BSQL connection_type to use - asyncTimeout: The timeout to use for normal operations, 0 for infinite, defaults to BSQL_DEFAULT_TIMEOUT - blockingTimeout: The timeout to use for blocking operations, must be less than or equal to asyncTimeout, 0 for infinite, defaults to asyncTimeout - threadLimit: The limit of additional threads BSQL will run simultaneously, defaults to BSQL_DEFAULT_THREAD_LIMIT -*/ -/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit) - return ..() - -/* -Starts an operation to connect to a database. Should only have 1 successful call - ipaddress: The ip/hostname of the target server - port: The port of the target server - username: The username to login to the target server - password: The password for the target server - database: Optional database to connect to. Must be used when trying to do database operations, `USE x` is not sufficient - Returns: A /datum/BSQL_Operation representing the connection or null if an error occurred -*/ -/datum/BSQL_Connection/proc/BeginConnect(ipaddress, port, username, password, database) - return - -/* -Properly quotes a string for use by the database. The connection must be open for this proc to succeed - str: The string to quote - Returns: The string quoted on success, null on error -*/ -/datum/BSQL_Connection/proc/Quote(str) - return - -/* -Starts an operation for a query - query: The text of the query. Only one query allowed per invocation, no semicolons - Returns: A /datum/BSQL_Operation/Query representing the running query and subsequent result set or null if an error occurred - - Note for MariaDB: The underlying connection is pooled. In order to use connection state based properties (i.e. LAST_INSERT_ID()) you can guarantee multiple queries will use the same connection by running BSQL_DEL_CALL(query) on the finished /datum/BSQL_Operation/Query and then creating the next one with another call to BeginQuery() with no sleeps in between -*/ -/datum/BSQL_Connection/proc/BeginQuery(query) - return - -/* -Checks if the operation is complete. This, in some cases must be called multiple times with false return before a result is present regardless of timespan. For best performance check it once per tick - - Returns: TRUE if the operation is complete, FALSE if it's not, null on error -*/ -/datum/BSQL_Operation/proc/IsComplete() - return - -/* -Blocks the entire game until the given operation completes. IsComplete should not be checked after calling this to avoid potential side effects. - -Returns: TRUE on success, FALSE if the operation wait time exceeded the connection's blockingTimeout setting -*/ -/datum/BSQL_Operation/proc/WaitForCompletion() - return - -/* -Get the error message associated with an operation. Should not be used while IsComplete() returns FALSE - - Returns: The error message, if any. null otherwise -*/ -/datum/BSQL_Operation/proc/GetError() - return - -/* -Get the error code associated with an operation. Should not be used while IsComplete() returns FALSE - - Returns: The error code, if any. null otherwise -*/ -/datum/BSQL_Operation/proc/GetErrorCode() - return - -/* -Gets an associated list of column name -> value representation of the most recent row in the query. Only valid if IsComplete() returns TRUE. If this returns null and no errors are present there are no more results in the query. Important to note that once IsComplete() returns TRUE it must not be called again without checking this or the row values may be lost - - Returns: An associated list of column name -> value for the row. Values will always be either strings or null -*/ -/datum/BSQL_Operation/Query/proc/CurrentRow() - return - - -/* -Code configuration options below - -Define this to avoid modifying this file but the following defines must be declared somewhere else before BSQL/includes.dm is included -*/ -#ifndef BSQL_EXTERNAL_CONFIGURATION - -//Modify this if you disagree with byond's GC schemes. Ensure this is called for all connections and operations when they are deleted or they will leak native resources until /world/proc/BSQL_Shutdown() is called -#define BSQL_DEL_PROC(path) ##path/Del() - -//The equivalent of calling del() in your codebase -#define BSQL_DEL_CALL(obj) del(##obj) - -//Returns TRUE if an object is delete -#define BSQL_IS_DELETED(obj) (obj == null) - -//Modify this to add protections to the connection and query datums -#define BSQL_PROTECT_DATUM(path) - -//Modify this to change up error handling for the library -#define BSQL_ERROR(message) CRASH("BSQL: [##message]") - -#endif - -/* -Copyright 2018 Jordan Brown - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ diff --git a/code/__DEFINES/configuration.dm b/code/__DEFINES/configuration.dm index 0428a16828..9915563cab 100644 --- a/code/__DEFINES/configuration.dm +++ b/code/__DEFINES/configuration.dm @@ -22,3 +22,5 @@ #define POLICYCONFIG_ON_DEFIB_LATE "ON_DEFIB_LATE" /// Displayed to pyroclastic slimes on spawn #define POLICYCONFIG_ON_PYROCLASTIC_SENTIENT "PYROCLASTIC_SLIME" +/// Displayed to pAIs on spawn +#define POLICYCONFIG_PAI "PAI_SPAWN" diff --git a/code/__DEFINES/instruments.dm b/code/__DEFINES/instruments.dm index 3c414f87f4..69d2a60e51 100644 --- a/code/__DEFINES/instruments.dm +++ b/code/__DEFINES/instruments.dm @@ -19,7 +19,7 @@ #define INSTRUMENT_EXP_FALLOFF_MAX 10 /// Minimum volume for when the sound is considered dead. -#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 0 +#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 1 #define SUSTAIN_LINEAR 1 #define SUSTAIN_EXPONENTIAL 2 diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index 57484ae85b..9403eca2da 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -86,8 +86,8 @@ #define EMOTE_OMNI 4 //Don't set this very much higher then 1024 unless you like inviting people in to dos your server with message spam -#define MAX_MESSAGE_LEN 2048 //Citadel edit: What's the WORST that could happen? -#define MAX_FLAVOR_LEN 4096 //double the maximum message length. +#define MAX_MESSAGE_LEN 4096 //Citadel edit: What's the WORST that could happen? +#define MAX_FLAVOR_LEN 4096 #define MAX_TASTE_LEN 40 //lick... vore... ew... #define MAX_NAME_LEN 42 #define MAX_BROADCAST_LEN 512 diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm index 3225f14d8c..2562bfe4d3 100644 --- a/code/__DEFINES/tgs.dm +++ b/code/__DEFINES/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "5.2.10" +#define TGS_DMAPI_VERSION "5.2.9" // All functions and datums outside this document are subject to change with any version and should not be relied on. @@ -67,7 +67,7 @@ #define TGS_EVENT_REPO_CHECKOUT 1 /// When the repository performs a fetch operation. No parameters #define TGS_EVENT_REPO_FETCH 2 -/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user +/// When the repository merges a pull request. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user #define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3 /// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path #define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4 @@ -190,21 +190,21 @@ /// Represents a merge of a GitHub pull request. /datum/tgs_revision_information/test_merge - /// The test merge number. + /// The pull request number. var/number - /// The test merge source's title when it was merged. + /// The pull request title when it was merged. var/title - /// The test merge source's body when it was merged. + /// The pull request body when it was merged. var/body - /// The Username of the test merge source's author. + /// The GitHub username of the pull request's author. var/author - /// An http URL to the test merge source. + /// An http URL to the pull request. var/url - /// The SHA of the test merge when that was merged. + /// The SHA of the pull request when that was merged. var/pull_request_commit - /// ISO 8601 timestamp of when the test merge was created on TGS. + /// ISO 8601 timestamp of when the pull request was merged. var/time_merged - /// Optional comment left by the TGS user who initiated the merge. + /// (Nullable) Comment left by the TGS user who initiated the merge.. var/comment /// Represents a connected chat channel. diff --git a/code/__HELPERS/chat.dm b/code/__HELPERS/chat.dm new file mode 100644 index 0000000000..57824b6286 --- /dev/null +++ b/code/__HELPERS/chat.dm @@ -0,0 +1,74 @@ +/* + +Here's how to use the chat system with configs + +send2adminchat is a simple function that broadcasts to admin channels + +send2chat is a bit verbose but can be very specific + +The second parameter is a string, this string should be read from a config. +What this does is dictacte which TGS4 channels can be sent to. + +For example if you have the following channels in tgs4 set up +- Channel 1, Tag: asdf +- Channel 2, Tag: bombay,asdf +- Channel 3, Tag: Hello my name is asdf +- Channel 4, No Tag +- Channel 5, Tag: butts + +and you make the call: + +send2chat("I sniff butts", CONFIG_GET(string/where_to_send_sniff_butts)) + +and the config option is set like: + +WHERE_TO_SEND_SNIFF_BUTTS asdf + +It will be sent to channels 1 and 2 + +Alternatively if you set the config option to just: + +WHERE_TO_SEND_SNIFF_BUTTS + +it will be sent to all connected chats. + +In TGS3 it will always be sent to all connected designated game chats. +*/ + +/** + * Sends a message to TGS chat channels. + * + * message - The message to send. + * channel_tag - Required. If "", the message with be sent to all connected (Game-type for TGS3) channels. Otherwise, it will be sent to TGS4 channels with that tag (Delimited by ','s). + */ +/proc/send2chat(message, channel_tag) + if(channel_tag == null || !world.TgsAvailable()) + return + + var/datum/tgs_version/version = world.TgsVersion() + if(channel_tag == "" || version.suite == 3) + world.TgsTargetedChatBroadcast(message, FALSE) + return + + var/list/channels_to_use = list() + for(var/I in world.TgsChatChannelInfo()) + var/datum/tgs_chat_channel/channel = I + var/list/applicable_tags = splittext(channel.custom_tag, ",") + if(channel_tag in applicable_tags) + channels_to_use += channel + + if(channels_to_use.len) + world.TgsChatBroadcast(message, channels_to_use) + +/** + * Sends a message to TGS admin chat channels. + * + * category - The category of the mssage. + * message - The message to send. + */ +/proc/send2adminchat(category, message, embed_links = FALSE) + category = replacetext(replacetext(category, "\proper", ""), "\improper", "") + message = replacetext(replacetext(message, "\proper", ""), "\improper", "") + // if(!embed_links) + // message = GLOB.has_discord_embeddable_links.Replace(replacetext(message, "`", ""), " ```$1``` ") + world.TgsTargetedChatBroadcast("[category] | [message]", TRUE) diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 39c1b9103f..658ed135e0 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -239,7 +239,7 @@ var/survival_rate = GLOB.joined_player_list.len ? "[PERCENT(popcount[POPCOUNT_SURVIVORS]/GLOB.joined_player_list.len)]%" : "there's literally no player" - send2irc("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]") + send2adminchat("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]") if(length(CONFIG_GET(keyed_list/cross_server))) send_news_report() @@ -277,6 +277,8 @@ SSpersistence.station_was_destroyed = TRUE if(!mode.allow_persistence_save) SSpersistence.station_persistence_save_disabled = TRUE + else + SSpersistence.SaveTCGCards() SSpersistence.CollectData() //stop collecting feedback during grifftime @@ -624,11 +626,9 @@ var/list/sql_admins = list() for(var/i in GLOB.protected_admins) var/datum/admins/A = GLOB.protected_admins[i] - var/sql_ckey = sanitizeSQL(A.target) - var/sql_rank = sanitizeSQL(A.rank.name) - sql_admins += list(list("ckey" = "'[sql_ckey]'", "rank" = "'[sql_rank]'")) + sql_admins += list(list("ckey" = A.target, "rank" = A.rank.name)) SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE) - var/datum/DBQuery/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank") + var/datum/db_query/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank") query_admin_rank_update.Execute() qdel(query_admin_rank_update) @@ -661,15 +661,20 @@ flags += "can_edit_flags" if(!flags.len) continue - var/sql_rank = sanitizeSQL(R.name) var/flags_to_check = flags.Join(" != [R_EVERYTHING] AND ") + " != [R_EVERYTHING]" - var/datum/DBQuery/query_check_everything_ranks = SSdbcore.NewQuery("SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = '[sql_rank]' AND ([flags_to_check])") + var/datum/db_query/query_check_everything_ranks = SSdbcore.NewQuery( + "SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank AND ([flags_to_check])", + list("rank" = R.name) + ) if(!query_check_everything_ranks.Execute()) qdel(query_check_everything_ranks) return if(query_check_everything_ranks.NextRow()) //no row is returned if the rank already has the correct flag value var/flags_to_update = flags.Join(" = [R_EVERYTHING], ") + " = [R_EVERYTHING]" - var/datum/DBQuery/query_update_everything_ranks = SSdbcore.NewQuery("UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = '[sql_rank]'") + var/datum/db_query/query_update_everything_ranks = SSdbcore.NewQuery( + "UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = :rank", + list("rank" = R.name) + ) if(!query_update_everything_ranks.Execute()) qdel(query_update_everything_ranks) return diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 7c251edd88..53a6f54062 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -13,10 +13,6 @@ * SQL sanitization */ -// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts. -/proc/sanitizeSQL(t) - return SSdbcore.Quote("[t]") - /proc/format_table_name(table as text) return CONFIG_GET(string/feedback_tableprefix) + table diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 71bbfe64fe..d0bb17efd2 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1579,33 +1579,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) for(var/i in 1 to items_list[each_item]) new each_item(where_to) -//sends a message to chat -//config_setting should be one of the following -//null - noop -//empty string - use TgsTargetBroadcast with admin_only = FALSE -//other string - use TgsChatBroadcast with the tag that matches config_setting, only works with TGS4, if using TGS3 the above method is used -/proc/send2chat(message, config_setting) - if(config_setting == null) - return - - UNTIL(GLOB.tgs_initialized) - if(!world.TgsAvailable()) - return - - var/datum/tgs_version/version = world.TgsVersion() - if(config_setting == "" || version.suite == 3) - world.TgsTargetedChatBroadcast(message, FALSE) - return - - var/list/channels_to_use = list() - for(var/I in world.TgsChatChannelInfo()) - var/datum/tgs_chat_channel/channel = I - if(channel.tag == config_setting) - channels_to_use += channel - - if(channels_to_use.len) - world.TgsChatBroadcast() - //Checks to see if either the victim has a garlic necklace or garlic in their blood /proc/blood_sucking_checks(var/mob/living/carbon/target, check_neck, check_blood) //Bypass this if the target isnt carbon. diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index f376ba50d7..ac6ea4e25c 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -132,6 +132,9 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NODROP" = TRAIT_NODROP, "TRAIT_NO_TELEPORT" = TRAIT_NO_TELEPORT, "TRAIT_SPOOKY_THROW" = TRAIT_SPOOKY_THROW + ), + /datum/mind = list( + "TRAIT_CLOWN_MENTALITY" = TRAIT_CLOWN_MENTALITY ) )) diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 59b78577af..f153b5965c 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -48,7 +48,7 @@ to_chat(src, "You're experiencing a bug. Reconnect immediately to fix it. Admins have been notified.") if(REALTIMEOFDAY >= chnotify + 9000) chnotify = REALTIMEOFDAY - send2irc_adminless_only("NOCHEAT", message) + send2tgs_adminless_only("NOCHEAT", message) return var/list/modifiers = params2list(params) @@ -113,7 +113,7 @@ A.AICtrlClick(src) /mob/living/silicon/ai/AltClickOn(var/atom/A) A.AIAltClick(src) - + /* The following criminally helpful code is just the previous code cleaned up; diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 4d985c7234..63da60d7b5 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -275,6 +275,11 @@ /datum/config_entry/flag/panic_bunker // prevents people the server hasn't seen before from connecting +/datum/config_entry/number/panic_bunker_living // living time in minutes that a player needs to pass the panic bunker + +/datum/config_entry/string/panic_bunker_message + config_entry_value = "Sorry but the server is currently not accepting connections from never before seen players." + /datum/config_entry/number/notify_new_player_age // how long do we notify admins of a new player min_val = -1 diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm index bacccefc61..5d236045fd 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -5,10 +5,10 @@ SUBSYSTEM_DEF(blackbox) runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME init_order = INIT_ORDER_BLACKBOX - var/list/feedback = list() //list of datum/feedback_variable + var/list/feedback = list() //list of datum/feedback_variable var/list/first_death = list() //the first death of this round, assoc. vars keep track of different things var/triggertime = 0 - var/sealed = FALSE //time to stop tracking stats? + var/sealed = FALSE //time to stop tracking stats? var/list/versions = list("antagonists" = 3, "admin_secrets_fun_used" = 2, "explosion" = 2, @@ -28,12 +28,12 @@ SUBSYSTEM_DEF(blackbox) //poll population /datum/controller/subsystem/blackbox/fire() - set waitfor = FALSE //for population query + set waitfor = FALSE //for population query CheckPlayerCount() if(CONFIG_GET(flag/use_exp_tracking)) - if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check + if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check update_exp(10,FALSE) /datum/controller/subsystem/blackbox/proc/CheckPlayerCount() @@ -43,7 +43,17 @@ SUBSYSTEM_DEF(blackbox) return var/playercount = LAZYLEN(GLOB.player_list) var/admincount = GLOB.admins.len - var/datum/DBQuery/query_record_playercount = SSdbcore.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) VALUES ([playercount], [admincount], '[SQLtime()]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]')") + var/datum/db_query/query_record_playercount = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) + VALUES (:playercount, :admincount, :time, INET_ATON(:server_ip), :server_port, :round_id) + "}, list( + "playercount" = playercount, + "admincount" = admincount, + "time" = SQLtime(), + "server_ip" = world.internet_address || "0", + "server_port" = "[world.port]", + "round_id" = GLOB.round_id, + )) query_record_playercount.Execute() qdel(query_record_playercount) @@ -87,24 +97,23 @@ SUBSYSTEM_DEF(blackbox) if (!SSdbcore.Connect()) return - // var/list/special_columns = list( - // "datetime" = "NOW()" - // ) + var/list/special_columns = list( + "datetime" = "NOW()" + ) var/list/sqlrowlist = list() for (var/datum/feedback_variable/FV in feedback) sqlrowlist += list(list( - "datetime" = "Now()", //legacy "round_id" = GLOB.round_id, - "key_name" = sanitizeSQL(FV.key), + "key_name" = FV.key, "key_type" = FV.key_type, "version" = versions[FV.key] || 1, - "json" = sanitizeSQL(json_encode(FV.json)) + "json" = json_encode(FV.json) )) if (!length(sqlrowlist)) return - SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE)//, special_columns = special_columns) + SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns) /datum/controller/subsystem/blackbox/proc/Seal() if(sealed) @@ -162,13 +171,13 @@ feedback data can be recorded in 5 formats: used for simple single-string records i.e. the current map further calls to the same key will append saved data unless the overwrite argument is true or it already exists when encoded calls made with overwrite will lack square brackets - calls: SSblackbox.record_feedback("text", "example", 1, "sample text") + calls: SSblackbox.record_feedback("text", "example", 1, "sample text") SSblackbox.record_feedback("text", "example", 1, "other text") json: {"data":["sample text","other text"]} "amount" used to record simple counts of data i.e. the number of ahelps received further calls to the same key will add or subtract (if increment argument is a negative) from the saved amount - calls: SSblackbox.record_feedback("amount", "example", 8) + calls: SSblackbox.record_feedback("amount", "example", 8) SSblackbox.record_feedback("amount", "example", 2) json: {"data":10} "tally" @@ -176,7 +185,7 @@ feedback data can be recorded in 5 formats: further calls to the same key will: add or subtract from the saved value of the data key if it already exists append the key and it's value if it doesn't exist - calls: SSblackbox.record_feedback("tally", "example", 1, "sample data") + calls: SSblackbox.record_feedback("tally", "example", 1, "sample data") SSblackbox.record_feedback("tally", "example", 4, "sample data") SSblackbox.record_feedback("tally", "example", 2, "other data") json: {"data":{"sample data":5,"other data":2}} @@ -188,19 +197,19 @@ feedback data can be recorded in 5 formats: further calls to the same key will: add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position append the key and it's value if it doesn't exist - calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot")) + calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot")) SSblackbox.record_feedback("nested tally", "example", 2, list("fruit", "orange", "orange")) SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange", "apricot")) SSblackbox.record_feedback("nested tally", "example", 10, list("fruit", "red", "apple")) SSblackbox.record_feedback("nested tally", "example", 1, list("vegetable", "orange", "carrot")) json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10}},"vegetable":{"orange":{"carrot":1}}}} tracking values associated with a number can't merge with a nesting value, trying to do so will append the list - call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange")) + call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange")) json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10},"orange":3},"vegetable":{"orange":{"carrot":1}}}} "associative" used to record text that's associated with a value i.e. coordinates further calls to the same key will append a new list to existing data - calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4)) + calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4)) SSblackbox.record_feedback("associative", "example", 1, list("number" = 7, "text" = "example", "other text" = "sample")) json: {"data":{"1":{"text":"example","path":"/obj/item","number":"4"},"2":{"number":"7","text":"example","other text":"sample"}}} @@ -275,7 +284,7 @@ Versioning /datum/feedback_variable/New(new_key, new_key_type) key = new_key key_type = new_key_type -/* + /datum/controller/subsystem/blackbox/proc/LogAhelp(ticket, action, message, recipient, sender) if(!SSdbcore.Connect()) return @@ -286,7 +295,7 @@ Versioning "}, list("ticket" = ticket, "action" = action, "message" = message, "recipient" = recipient, "sender" = sender, "server_ip" = world.internet_address || "0", "server_port" = world.port, "round_id" = GLOB.round_id, "time" = SQLtime())) query_log_ahelp.Execute() qdel(query_log_ahelp) -*/ + /datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L) set waitfor = FALSE @@ -302,51 +311,39 @@ Versioning first_death["area"] = "[AREACOORD(L)]" first_death["damage"] = "[L.getBruteLoss()]/[L.getFireLoss()]/[L.getToxLoss()]/[L.getOxyLoss()]/[L.getCloneLoss()]" first_death["last_words"] = L.last_words - var/sqlname = L.real_name - var/sqlkey = L.ckey - var/sqljob = L.mind.assigned_role - var/sqlspecial = L.mind.special_role - var/sqlpod = get_area_name(L, TRUE) - var/laname = L.lastattacker - var/lakey = L.lastattackerckey - var/sqlbrute = L.getBruteLoss() - var/sqlfire = L.getFireLoss() - var/sqlbrain = L.getOrganLoss(ORGAN_SLOT_BRAIN) - var/sqloxy = L.getOxyLoss() - var/sqltox = L.getToxLoss() - var/sqlclone = L.getCloneLoss() - var/sqlstamina = L.getStaminaLoss() - var/x_coord = L.x - var/y_coord = L.y - var/z_coord = L.z - var/last_words = L.last_words - var/suicide = L.suiciding - var/map = SSmapping.config.map_name if(!SSdbcore.Connect()) return - sqlname = sanitizeSQL(sqlname) - sqlkey = sanitizeSQL(sqlkey) - sqljob = sanitizeSQL(sqljob) - sqlspecial = sanitizeSQL(sqlspecial) - sqlpod = sanitizeSQL(sqlpod) - laname = sanitizeSQL(laname) - lakey = sanitizeSQL(lakey) - sqlbrute = sanitizeSQL(sqlbrute) - sqlfire = sanitizeSQL(sqlfire) - sqlbrain = sanitizeSQL(sqlbrain) - sqloxy = sanitizeSQL(sqloxy) - sqltox = sanitizeSQL(sqltox) - sqlclone = sanitizeSQL(sqlclone) - sqlstamina = sanitizeSQL(sqlstamina) - x_coord = sanitizeSQL(x_coord) - y_coord = sanitizeSQL(y_coord) - z_coord = sanitizeSQL(z_coord) - last_words = sanitizeSQL(last_words) - suicide = sanitizeSQL(suicide) - map = sanitizeSQL(map) - var/datum/DBQuery/query_report_death = SSdbcore.NewQuery("INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide) VALUES ('[sqlpod]', '[x_coord]', '[y_coord]', '[z_coord]', '[map]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', [GLOB.round_id], '[SQLtime()]', '[sqljob]', '[sqlspecial]', '[sqlname]', '[sqlkey]', '[laname]', '[lakey]', [sqlbrute], [sqlfire], [sqlbrain], [sqloxy], [sqltox], [sqlclone], [sqlstamina], '[last_words]', [suicide])") + var/datum/db_query/query_report_death = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide) + VALUES (:pod, :x_coord, :y_coord, :z_coord, :map, INET_ATON(:internet_address), :port, :round_id, :time, :job, :special, :name, :key, :laname, :lakey, :brute, :fire, :brain, :oxy, :tox, :clone, :stamina, :last_words, :suicide) + "}, list( + "name" = L.real_name, + "key" = L.ckey, + "job" = L.mind.assigned_role, + "special" = L.mind.special_role, + "pod" = get_area_name(L, TRUE), + "laname" = L.lastattacker, + "lakey" = L.lastattackerckey, + "brute" = L.getBruteLoss(), + "fire" = L.getFireLoss(), + "brain" = L.getOrganLoss(ORGAN_SLOT_BRAIN) || BRAIN_DAMAGE_DEATH, //getOrganLoss returns null without a brain but a value is required for this column + "oxy" = L.getOxyLoss(), + "tox" = L.getToxLoss(), + "clone" = L.getCloneLoss(), + "stamina" = L.getStaminaLoss(), + "x_coord" = L.x, + "y_coord" = L.y, + "z_coord" = L.z, + "last_words" = L.last_words, + "suicide" = L.suiciding, + "map" = SSmapping.config.map_name, + "internet_address" = world.internet_address || "0", + "port" = "[world.port]", + "round_id" = GLOB.round_id, + "time" = SQLtime(), + )) if(query_report_death) query_report_death.Execute(async = TRUE) qdel(query_report_death) diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm index c779c9f26d..b6b750fbf4 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -3,7 +3,7 @@ SUBSYSTEM_DEF(dbcore) flags = SS_BACKGROUND wait = 1 MINUTES init_order = INIT_ORDER_DBCORE - var/const/FAILED_DB_CONNECTION_CUTOFF = 5 + var/failed_connection_timeout = 0 var/schema_mismatch = 0 var/db_minor = 0 @@ -13,8 +13,7 @@ SUBSYSTEM_DEF(dbcore) var/last_error var/list/active_queries = list() - var/datum/BSQL_Connection/connection - var/datum/BSQL_Operation/connectOperation + var/connection // Arbitrary handle returned from rust_g. /datum/controller/subsystem/dbcore/Initialize() //We send warnings to the admins during subsystem init, as the clients will be New'd and messages @@ -29,7 +28,7 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/fire() for(var/I in active_queries) - var/datum/DBQuery/Q = I + var/datum/db_query/Q = I if(world.time - Q.last_activity_time > (5 MINUTES)) message_admins("Found undeleted query, please check the server logs and notify coders.") log_sql("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]") @@ -39,24 +38,25 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/Recover() connection = SSdbcore.connection - connectOperation = SSdbcore.connectOperation /datum/controller/subsystem/dbcore/Shutdown() //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/DBQuery/query_round_shutdown = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = '[sanitizeSQL(SSticker.end_state)]' WHERE id = [GLOB.round_id]") + var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id", + list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id) + ) query_round_shutdown.Execute() qdel(query_round_shutdown) if(IsConnected()) Disconnect() - world.BSQL_Shutdown() //nu /datum/controller/subsystem/dbcore/can_vv_get(var_name) - return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && var_name != NAMEOF(src, connectOperation) && ..() + return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..() /datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value) - if(var_name == NAMEOF(src, connection) || var_name == NAMEOF(src, connectOperation)) + if(var_name == NAMEOF(src, connection)) return FALSE return ..() @@ -64,7 +64,11 @@ SUBSYSTEM_DEF(dbcore) if(IsConnected()) return TRUE - if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore. + if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter + failed_connections = 0 + + if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds. + failed_connection_timeout = world.time + 50 return FALSE if(!CONFIG_GET(flag/sql_enabled)) @@ -75,32 +79,33 @@ SUBSYSTEM_DEF(dbcore) var/db = CONFIG_GET(string/feedback_database) var/address = CONFIG_GET(string/address) var/port = CONFIG_GET(number/port) + var/timeout = max(CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout)) + var/thread_limit = CONFIG_GET(number/bsql_thread_limit) - connection = new /datum/BSQL_Connection(BSQL_CONNECTION_TYPE_MARIADB, CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout), CONFIG_GET(number/bsql_thread_limit)) - var/error - if(QDELETED(connection)) - connection = null - error = last_error + var/result = json_decode(rustg_sql_connect_pool(json_encode(list( + "host" = address, + "port" = port, + "user" = user, + "pass" = pass, + "db_name" = db, + "read_timeout" = timeout, + "write_timeout" = timeout, + "max_threads" = thread_limit, + )))) + . = (result["status"] == "ok") + if (.) + connection = result["handle"] else - SSdbcore.last_error = null - connectOperation = connection.BeginConnect(address, port, user, pass, db) - if(SSdbcore.last_error) - CRASH(SSdbcore.last_error) - UNTIL(connectOperation.IsComplete()) - error = connectOperation.GetError() - . = !error - if (!.) - last_error = error - log_sql("Connect() failed | [error]") + connection = null + last_error = result["data"] + log_sql("Connect() failed | [last_error]") ++failed_connections - QDEL_NULL(connection) - QDEL_NULL(connectOperation) /datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() if(CONFIG_GET(flag/sql_enabled)) if(Connect()) log_world("Database connection established.") - var/datum/DBQuery/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 [format_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]) @@ -120,47 +125,46 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/proc/SetRoundID() if(!Connect()) return - var/datum/DBQuery/query_round_initialize = SSdbcore.NewQuery("INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]')") - query_round_initialize.Execute() + 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)", + list("internet_address" = world.internet_address || "0", "port" = "[world.port]") + ) + query_round_initialize.Execute(async = FALSE) + GLOB.round_id = "[query_round_initialize.last_insert_id]" qdel(query_round_initialize) - var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") - query_round_last_id.Execute() - if(query_round_last_id.NextRow()) - GLOB.round_id = query_round_last_id.item[1] - qdel(query_round_last_id) /datum/controller/subsystem/dbcore/proc/SetRoundStart() if(!Connect()) return - var/datum/DBQuery/query_round_start = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = [GLOB.round_id]") + var/datum/db_query/query_round_start = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id", + list("round_id" = GLOB.round_id) + ) query_round_start.Execute() qdel(query_round_start) /datum/controller/subsystem/dbcore/proc/SetRoundEnd() if(!Connect()) return - var/sql_station_name = sanitizeSQL(station_name()) - var/datum/DBQuery/query_round_end = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = '[sanitizeSQL(SSticker.mode_result)]', station_name = '[sql_station_name]' WHERE id = [GLOB.round_id]") + var/datum/db_query/query_round_end = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id", + list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id) + ) query_round_end.Execute() qdel(query_round_end) /datum/controller/subsystem/dbcore/proc/Disconnect() failed_connections = 0 - QDEL_NULL(connectOperation) - QDEL_NULL(connection) + if (connection) + rustg_sql_disconnect_pool(connection) + connection = null /datum/controller/subsystem/dbcore/proc/IsConnected() - if(!CONFIG_GET(flag/sql_enabled)) + if (!CONFIG_GET(flag/sql_enabled)) return FALSE - //block until any connect operations finish - var/datum/BSQL_Connection/_connection = connection - var/datum/BSQL_Operation/op = connectOperation - UNTIL(QDELETED(_connection) || op.IsComplete()) - return !QDELETED(connection) && !op.GetError() - -/datum/controller/subsystem/dbcore/proc/Quote(str) - if(connection) - return connection.Quote(str) + if (!connection) + return FALSE + return json_decode(rustg_sql_connected(connection))["status"] == "online" /datum/controller/subsystem/dbcore/proc/ErrorMsg() if(!CONFIG_GET(flag/sql_enabled)) @@ -170,32 +174,34 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/proc/ReportError(error) last_error = error -/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query) +/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments) if(IsAdminAdvancedProcCall()) log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked") message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked") return FALSE - return new /datum/DBQuery(sql_query, connection) + return new /datum/db_query(connection, sql_query, arguments) /datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) if (!islist(querys)) - if (!istype(querys, /datum/DBQuery)) + if (!istype(querys, /datum/db_query)) CRASH("Invalid query passed to QuerySelect: [querys]") querys = list(querys) for (var/thing in querys) - var/datum/DBQuery/query = thing + var/datum/db_query/query = thing if (warn) - INVOKE_ASYNC(query, /datum/DBQuery.proc/warn_execute) + INVOKE_ASYNC(query, /datum/db_query.proc/warn_execute) else - INVOKE_ASYNC(query, /datum/DBQuery.proc/Execute) + INVOKE_ASYNC(query, /datum/db_query.proc/Execute) for (var/thing in querys) - var/datum/DBQuery/query = thing + var/datum/db_query/query = thing UNTIL(!query.in_progress) if (qdel) qdel(query) + + /* Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. Rows missing columns present in other rows will resolve to SQL NULL @@ -203,137 +209,135 @@ You are expected to do your own escaping of the data, and expected to provide yo The duplicate_key arg can be true to automatically generate this part of the query or set to a string that is appended to the end of the query Ignore_errors instructes mysql to continue inserting rows if some of them have errors. - the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored + the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables, It was included because it is still supported in mariadb. It does not work with duplicate_key and the mysql server ignores it in those cases */ -/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE) +/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE, special_columns = null) if (!table || !rows || !istype(rows)) return + + // Prepare column list var/list/columns = list() - var/list/sorted_rows = list() - + var/list/has_question_mark = list() for (var/list/row in rows) - var/list/sorted_row = list() - sorted_row.len = columns.len for (var/column in row) - var/idx = columns[column] - if (!idx) - idx = columns.len + 1 - columns[column] = idx - sorted_row.len = columns.len + columns[column] = "?" + has_question_mark[column] = TRUE + for (var/column in special_columns) + columns[column] = special_columns[column] + has_question_mark[column] = findtext(special_columns[column], "?") - sorted_row[idx] = row[column] - sorted_rows[++sorted_rows.len] = sorted_row + // Prepare SQL query full of placeholders + var/list/query_parts = list("INSERT") + if (delayed) + query_parts += " DELAYED" + if (ignore_errors) + query_parts += " IGNORE" + query_parts += " INTO " + query_parts += table + query_parts += "\n([columns.Join(", ")])\nVALUES" + + var/list/arguments = list() + var/has_row = FALSE + for (var/list/row in rows) + if (has_row) + query_parts += "," + query_parts += "\n (" + var/has_col = FALSE + for (var/column in columns) + if (has_col) + query_parts += ", " + if (has_question_mark[column]) + var/name = "p[arguments.len]" + query_parts += replacetext(columns[column], "?", ":[name]") + arguments[name] = row[column] + else + query_parts += columns[column] + has_col = TRUE + query_parts += ")" + has_row = TRUE if (duplicate_key == TRUE) var/list/column_list = list() for (var/column in columns) column_list += "[column] = VALUES([column])" - duplicate_key = "ON DUPLICATE KEY UPDATE [column_list.Join(", ")]\n" - else if (duplicate_key == FALSE) - duplicate_key = null + query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]" + else if (duplicate_key != FALSE) + query_parts += duplicate_key - if (ignore_errors) - ignore_errors = " IGNORE" - else - ignore_errors = null - - if (delayed) - delayed = " DELAYED" - else - delayed = null - - var/list/sqlrowlist = list() - var/len = columns.len - for (var/list/row in sorted_rows) - if (length(row) != len) - row.len = len - for (var/value in row) - if (value == null) - value = "NULL" - sqlrowlist += "([row.Join(", ")])" - - sqlrowlist = " [sqlrowlist.Join(",\n ")]" - var/datum/DBQuery/Query = NewQuery("INSERT[delayed][ignore_errors] INTO [table]\n([columns.Join(", ")])\nVALUES\n[sqlrowlist]\n[duplicate_key]") + var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments) if (warn) . = Query.warn_execute(async) else . = Query.Execute(async) qdel(Query) -/datum/DBQuery - var/sql // The sql query being executed. - var/list/item //list of data values populated by NextRow() +/datum/db_query + // Inputs + var/connection + var/sql + var/arguments + // Status information + var/in_progress + var/last_error var/last_activity var/last_activity_time - var/last_error - var/skip_next_is_complete - var/in_progress - var/datum/BSQL_Connection/connection - var/datum/BSQL_Operation/Query/query + // Output + var/list/list/rows + var/next_row_to_take = 1 + var/affected + var/last_insert_id -/datum/DBQuery/New(sql_query, datum/BSQL_Connection/connection) + var/list/item //list of data values populated by NextRow() + +/datum/db_query/New(connection, sql, arguments) SSdbcore.active_queries[src] = TRUE Activity("Created") item = list() - src.connection = connection - sql = sql_query -/datum/DBQuery/Destroy() + src.connection = connection + src.sql = sql + src.arguments = arguments + +/datum/db_query/Destroy() Close() SSdbcore.active_queries -= src return ..() -/datum/DBQuery/CanProcCall(proc_name) +/datum/db_query/CanProcCall(proc_name) //fuck off kevinz return FALSE -/datum/DBQuery/proc/SetQuery(new_sql) - if(in_progress) - CRASH("Attempted to set new sql while waiting on active query") - Close() - sql = new_sql - -/datum/DBQuery/proc/Activity(activity) +/datum/db_query/proc/Activity(activity) last_activity = activity last_activity_time = world.time -/datum/DBQuery/proc/warn_execute(async = FALSE) +/datum/db_query/proc/warn_execute(async = TRUE) . = Execute(async) if(!.) to_chat(usr, "A SQL error occurred during this operation, check the server logs.") -/datum/DBQuery/proc/Execute(async = FALSE, log_error = TRUE) +/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE) Activity("Execute") if(in_progress) CRASH("Attempted to start a new query while waiting on the old one") - if(QDELETED(connection)) + if(!SSdbcore.IsConnected()) last_error = "No connection!" return FALSE var/start_time - var/timed_out if(!async) start_time = REALTIMEOFDAY Close() - query = connection.BeginQuery(sql) - if(!async) - timed_out = !query.WaitForCompletion() - else - in_progress = TRUE - UNTIL(query.IsComplete()) - in_progress = FALSE - skip_next_is_complete = TRUE - var/error = QDELETED(query) ? "Query object deleted!" : query.GetError() - last_error = error - . = !error + . = run_query(async) + var/timed_out = !. && findtext(last_error, "Operation timed out") if(!. && log_error) - log_sql("[error] | Query used: [sql]") + log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]") if(!async && timed_out) log_query_debug("Query execution started at [start_time]") log_query_debug("Query execution ended at [REALTIMEOFDAY]") @@ -341,44 +345,51 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table log_query_debug("Query used: [sql]") slow_query_check() -/datum/DBQuery/proc/slow_query_check() +/datum/db_query/proc/run_query(async) + var/job_result_str + + if (async) + var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments)) + in_progress = TRUE + UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET) + in_progress = FALSE + + if (job_result_str == RUSTG_JOB_ERROR) + last_error = job_result_str + return FALSE + else + job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments)) + + var/result = json_decode(job_result_str) + switch (result["status"]) + if ("ok") + rows = result["rows"] + affected = result["affected"] + last_insert_id = result["last_insert_id"] + return TRUE + if ("err") + last_error = result["data"] + return FALSE + if ("offline") + last_error = "offline" + return FALSE + +/datum/db_query/proc/slow_query_check() message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") -/datum/DBQuery/proc/NextRow(async) +/datum/db_query/proc/NextRow(async = TRUE) Activity("NextRow") - UNTIL(!in_progress) - if(!skip_next_is_complete) - if(!async) - query.WaitForCompletion() - else - in_progress = TRUE - UNTIL(query.IsComplete()) - in_progress = FALSE + + if (rows && next_row_to_take <= rows.len) + item = rows[next_row_to_take] + next_row_to_take++ + return !!item else - skip_next_is_complete = FALSE + return FALSE - last_error = query.GetError() - var/list/results = query.CurrentRow() - . = results != null - - item.Cut() - //populate item array - for(var/I in results) - item += results[I] - -/datum/DBQuery/proc/ErrorMsg() +/datum/db_query/proc/ErrorMsg() return last_error -/datum/DBQuery/proc/Close() - item.Cut() - QDEL_NULL(query) - -/world/BSQL_Debug(message) - if(!CONFIG_GET(flag/bsql_debug)) - return - - //strip sensitive stuff - if(findtext(message, ": CreateConnection(")) - message = "CreateConnection CENSORED" - - log_sql("BSQL_DEBUG: [message]") +/datum/db_query/proc/Close() + rows = null + item = null diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index 0ba6076be2..c090d7367c 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -496,9 +496,15 @@ SUBSYSTEM_DEF(job) H.equip_to_slot_if_possible(binder, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE) for(var/card_type in H.client.prefs.tcg_cards) if(card_type) - var/obj/item/tcg_card/card = new(get_turf(H), card_type, H.client.prefs.tcg_cards[card_type]) - card.forceMove(binder) - binder.cards.Add(card) + if(islist(H.client.prefs.tcg_cards[card_type])) + for(var/duplicate in H.client.prefs.tcg_cards[card_type]) + var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate) + card.forceMove(binder) + binder.cards.Add(card) + else + var/obj/item/tcg_card/card = new(get_turf(H), card_type, H.client.prefs.tcg_cards[card_type]) + card.forceMove(binder) + binder.cards.Add(card) binder.check_for_exodia() if(length(H.client.prefs.tcg_decks)) binder.decks = H.client.prefs.tcg_decks @@ -508,9 +514,15 @@ SUBSYSTEM_DEF(job) H.equip_to_slot_if_possible(binder, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE) for(var/card_type in N.client.prefs.tcg_cards) if(card_type) - var/obj/item/tcg_card/card = new(get_turf(H), card_type, N.client.prefs.tcg_cards[card_type]) - card.forceMove(binder) - binder.cards.Add(card) + if(islist(H.client.prefs.tcg_cards[card_type])) + for(var/duplicate in N.client.prefs.tcg_cards[card_type]) + var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate) + card.forceMove(binder) + binder.cards.Add(card) + else + var/obj/item/tcg_card/card = new(get_turf(H), card_type, N.client.prefs.tcg_cards[card_type]) + card.forceMove(binder) + binder.cards.Add(card) binder.check_for_exodia() if(length(N.client.prefs.tcg_decks)) binder.decks = N.client.prefs.tcg_decks diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 90908bbde0..b5dfec3c44 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -286,7 +286,9 @@ SUBSYSTEM_DEF(mapping) setup_station_z_index() if(SSdbcore.Connect()) - var/datum/DBQuery/query_round_map_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET map_name = '[config.map_name]' WHERE id = [GLOB.round_id]") + var/datum/db_query/query_round_map_name = SSdbcore.NewQuery({" + UPDATE [format_table_name("round")] SET map_name = :map_name WHERE id = :round_id + "}, list("map_name" = config.map_name, "round_id" = GLOB.round_id)) query_round_map_name.Execute() qdel(query_round_map_name) diff --git a/code/controllers/subsystem/persistence/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm index 9b2c019db4..d494561d0f 100644 --- a/code/controllers/subsystem/persistence/_persistence.dm +++ b/code/controllers/subsystem/persistence/_persistence.dm @@ -88,7 +88,6 @@ SUBSYSTEM_DEF(persistence) SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. SavePaintings() SaveScars() - SaveTCGCards() /** * Loads persistent data relevant to the current map: Objects, etc. diff --git a/code/controllers/subsystem/stickyban.dm b/code/controllers/subsystem/stickyban.dm index 0c71777bc0..c61ea9943e 100644 --- a/code/controllers/subsystem/stickyban.dm +++ b/code/controllers/subsystem/stickyban.dm @@ -60,15 +60,10 @@ SUBSYSTEM_DEF(stickyban) /datum/controller/subsystem/stickyban/proc/Populatedbcache() var/newdbcache = list() //so if we runtime or the db connection dies we don't kill the existing cache - // var/datum/db_query/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey") - // var/datum/db_query/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched") - // var/datum/db_query/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched") - // var/datum/db_query/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched") - - var/datum/DBQuery/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey") - var/datum/DBQuery/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched") - var/datum/DBQuery/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched") - var/datum/DBQuery/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched") + var/datum/db_query/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey") + var/datum/db_query/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched") + var/datum/db_query/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched") + var/datum/db_query/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched") SSdbcore.QuerySelect(list(query_stickybans, query_ckey_matches, query_cid_matches, query_ip_matches)) @@ -161,25 +156,15 @@ SUBSYSTEM_DEF(stickyban) if (!ban["message"]) ban["message"] = "Evasion" - // TODO: USE NEW DB IMPLEMENTATION - var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery( - "INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES ([ckey], [ban["message"]], ban["admin"]))" + var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery( + "INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES (:ckey, :message, :admin)", + list("ckey" = ckey, "message" = ban["message"], "admin" = ban["admin"]) ) - - if (query_create_stickyban.warn_execute()) + if (!query_create_stickyban.warn_execute()) qdel(query_create_stickyban) return qdel(query_create_stickyban) - // var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery( - // "INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES (:ckey, :message, :admin)", - // list("ckey" = ckey, "message" = ban["message"], "admin" = ban["admin"]) - // ) - // if (!query_create_stickyban.warn_execute()) - // qdel(query_create_stickyban) - // return - // qdel(query_create_stickyban) - var/list/sqlckeys = list() var/list/sqlcids = list() var/list/sqlips = list() diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 198c380f41..884e209512 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -268,7 +268,7 @@ SUBSYSTEM_DEF(ticker) if(!GLOB.Debug2) if(!can_continue) log_game("[mode.name] failed pre_setup, cause: [mode.setup_error]") - send2irc("SSticker", "[mode.name] failed pre_setup, cause: [mode.setup_error]") + send2adminchat("SSticker", "[mode.name] failed pre_setup, cause: [mode.setup_error]") message_admins("[mode.name] failed pre_setup, cause: [mode.setup_error]") QDEL_NULL(mode) to_chat(world, "Error setting up [GLOB.master_mode]. Reverting to pre-game lobby.") @@ -334,7 +334,7 @@ SUBSYSTEM_DEF(ticker) var/list/adm = get_admin_counts() var/list/allmins = adm["present"] - send2irc("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]:" : "of"] [hide_mode ? "secret":"[mode.name]"] has started[allmins.len ? ".":" with no active admins online!"]") + send2adminchat("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]:" : "of"] [hide_mode ? "secret":"[mode.name]"] has started[allmins.len ? ".":" with no active admins online!"]") setup_done = TRUE for(var/i in GLOB.start_landmarks_list) diff --git a/code/datums/tgs_event_handler.dm b/code/datums/tgs_event_handler.dm index 731be64183..434450b9be 100644 --- a/code/datums/tgs_event_handler.dm +++ b/code/datums/tgs_event_handler.dm @@ -5,8 +5,8 @@ switch(event_code) if(TGS_EVENT_REBOOT_MODE_CHANGE) var/list/reboot_mode_lookup = list ("[TGS_REBOOT_MODE_NORMAL]" = "be normal", "[TGS_REBOOT_MODE_SHUTDOWN]" = "shutdown the server", "[TGS_REBOOT_MODE_RESTART]" = "hard restart the server") - var old_reboot_mode = args[2] - var new_reboot_mode = args[3] + var/old_reboot_mode = args[2] + var/new_reboot_mode = args[3] message_admins("TGS: Reboot will no longer [reboot_mode_lookup["[old_reboot_mode]"]], it will instead [reboot_mode_lookup["[new_reboot_mode]"]]") if(TGS_EVENT_PORT_SWAP) message_admins("TGS: Changing port from [world.port] to [args[2]]") @@ -28,12 +28,14 @@ var/datum/tgs_version/old_version = world.TgsVersion() var/datum/tgs_version/new_version = args[2] if(!old_version.Equals(new_version)) - to_chat(world, "TGS updated to v[old_version.deprefixed_parameter]") + to_chat(world, "TGS updated to v[new_version.deprefixed_parameter]") else message_admins("TGS: Back online") if(reattach_timer) deltimer(reattach_timer) reattach_timer = null + if(TGS_EVENT_WATCHDOG_SHUTDOWN) + to_chat_immediate(world, "Server is shutting down!") /datum/tgs_event_handler/impl/proc/LateOnReattach() message_admins("Warning: TGS hasn't notified us of it coming back for a full minute! Is there a problem?") diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 415a616c90..946090c571 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -90,7 +90,7 @@ if(!is_new_ckey) log_admin("AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).") message_admins("AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).") - send2irc("Panic Bunker", "AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).") + send2adminchat("Panic Bunker", "AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).") return "Success" /datum/world_topic/ahelp_relay diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 3d7eeb7a8a..8bef236c5e 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -81,30 +81,43 @@ ///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things /datum/game_mode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report. - //finalize_monster_hunters() Disabled for now if(!report) report = !CONFIG_GET(flag/no_intercept_report) addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) - if(prob(20)) //CIT CHANGE - adds a 20% chance for the security level to be the opposite of what it normally is + if(prob(20)) //cit-change flipseclevel = TRUE + + // if(CONFIG_GET(flag/reopen_roundstart_suicide_roles)) + // var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay) + // if(delay) + // delay = (delay SECONDS) + // else + // delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined. + // addtimer(CALLBACK(GLOBAL_PROC, .proc/reopen_roundstart_suicide_roles), delay) + if(SSdbcore.Connect()) - var/sql + var/list/to_set = list() + var/arguments = list() if(SSticker.mode) - sql += "game_mode = '[SSticker.mode]'" + to_set += "game_mode = :game_mode" + arguments["game_mode"] = SSticker.mode if(GLOB.revdata.originmastercommit) - if(sql) - sql += ", " - sql += "commit_hash = '[GLOB.revdata.originmastercommit]'" - if(sql) - var/datum/DBQuery/query_round_game_mode = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET [sql] WHERE id = [GLOB.round_id]") + to_set += "commit_hash = :commit_hash" + arguments["commit_hash"] = GLOB.revdata.originmastercommit + if(to_set.len) + arguments["round_id"] = GLOB.round_id + var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id", + arguments + ) query_round_game_mode.Execute() qdel(query_round_game_mode) if(report) addtimer(CALLBACK(src, .proc/send_intercept, 0), rand(waittime_l, waittime_h)) generate_station_goals() gamemode_ready = TRUE - return 1 + return TRUE ///Handles late-join antag assignments diff --git a/code/game/machinery/colormate.dm b/code/game/machinery/colormate.dm index 1f1c16248c..d059d492ae 100644 --- a/code/game/machinery/colormate.dm +++ b/code/game/machinery/colormate.dm @@ -44,7 +44,8 @@ icon_state = "colormate" /obj/machinery/gear_painter/Destroy() - inserted.forceMove(drop_location()) + if(inserted) //please i beg you do not drop nulls + inserted.forceMove(drop_location()) return ..() /obj/machinery/gear_painter/attackby(obj/item/I, mob/living/user) diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 280348368d..00ba621550 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -117,12 +117,6 @@ if(user.a_intent == INTENT_HARM) return ..() -//callback proc used on stacks use_tool to stop unnecessary amounts being wasted from spam clicking. -/obj/structure/frame/computer/proc/check_state(target_state) - if(state == target_state) - return TRUE - return FALSE - /obj/structure/frame/computer/deconstruct(disassembled = TRUE) if(!(flags_1 & NODECONSTRUCT_1)) if(state == 4) diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index b00edfd82c..d5bbcb0adc 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -21,6 +21,11 @@ circuit = null qdel(src) +//callback proc used on stacks use_tool to stop unnecessary amounts being wasted from spam clicking. +/obj/structure/frame/proc/check_state(target_state) + if(state == target_state) + return TRUE + return FALSE /obj/structure/frame/machine name = "machine frame" @@ -84,7 +89,7 @@ if(!P.tool_start_check(user, amount=5)) return to_chat(user, "You start to add cables to the frame...") - if(P.use_tool(src, user, 20, volume=50, amount=5)) + if(P.use_tool(src, user, 20, volume=50, amount=5, extra_checks = CALLBACK(src, .proc/check_state, 1))) to_chat(user, "You add cables to the frame.") state = 2 icon_state = "box_1" @@ -93,25 +98,23 @@ if(P.tool_behaviour == TOOL_SCREWDRIVER && !anchored) user.visible_message("[user] disassembles the frame.", \ "You start to disassemble the frame...", "You hear banging and clanking.") - if(P.use_tool(src, user, 40, volume=50)) - if(state == 1) - to_chat(user, "You disassemble the frame.") - var/obj/item/stack/sheet/metal/M = new (loc, 5) - M.add_fingerprint(user) - qdel(src) + if(P.use_tool(src, user, 40, volume=50, extra_checks = CALLBACK(src, .proc/check_state, 1))) + to_chat(user, "You disassemble the frame.") + var/obj/item/stack/sheet/metal/M = new (loc, 5) + M.add_fingerprint(user) + qdel(src) return if(P.tool_behaviour == TOOL_WRENCH) to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") - if(P.use_tool(src, user, 40, volume=75)) - if(state == 1) - to_chat(user, "You [anchored ? "un" : ""]secure [name].") - setAnchored(!anchored) + if(P.use_tool(src, user, 40, volume=75, extra_checks = CALLBACK(src, .proc/check_state, 1))) + to_chat(user, "You [anchored ? "un" : ""]secure [name].") + setAnchored(!anchored) return if(2) if(P.tool_behaviour == TOOL_WRENCH) to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") - if(P.use_tool(src, user, 40, volume=75)) + if(P.use_tool(src, user, 40, volume=75, extra_checks = CALLBACK(src, .proc/check_state, 2))) to_chat(user, "You [anchored ? "un" : ""]secure [name].") setAnchored(!anchored) return @@ -169,7 +172,7 @@ if(P.tool_behaviour == TOOL_WRENCH && !circuit.needs_anchored) to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") - if(P.use_tool(src, user, 40, volume=75)) + if(P.use_tool(src, user, 40, volume=75, extra_checks = CALLBACK(src, .proc/check_state, 3))) to_chat(user, "You [anchored ? "un" : ""]secure [name].") setAnchored(!anchored) return diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index d9444c3157..f4a65ce984 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1062,7 +1062,7 @@ to_chat(user, "The airlock's motors resist your efforts to force it!") else if(locked) to_chat(user, "The airlock's bolts prevent it from being forced!") - else if( !welded && !operating) + else if(!welded && !operating) if(!beingcrowbarred) //being fireaxe'd var/obj/item/fireaxe/axe = I if(!axe.wielded) @@ -1073,6 +1073,8 @@ INVOKE_ASYNC(src, (density ? .proc/open : .proc/close), 2) if(I.tool_behaviour == TOOL_CROWBAR) + if(!I.can_force_powered) + return if(hasPower() && isElectrified()) shock(user,100)//it's like sticking a forck in a power socket return diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index df64ca72a5..8a09bca0bf 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -116,6 +116,8 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb var/buffer = null var/show_wires = FALSE var/datum/integrated_io/selected_io = null //functional for integrated circuits. + //Special crowbar + var/can_force_powered = FALSE var/reach = 1 //In tiles, how far this weapon can reach; 1 for adjacent, which is default diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 9a722feb6a..e7a9d51ebe 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -122,7 +122,10 @@ /obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) src.pai = personality src.add_overlay("pai-null") - + var/list/policies = CONFIG_GET(keyed_list/policyconfig) + var/policy = policies[POLICYCONFIG_PAI] + if(policy) + to_chat(personality, policy) playsound(loc, 'sound/effects/pai_boot.ogg', 50, 1, -1) audible_message("\The [src] plays a cheerful startup noise!") diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index 3d1ea9e7a2..73299a2006 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -180,6 +180,11 @@ //Called from turf.dm turf/dblclick /obj/item/flamethrower/proc/flame_turf(turflist) + var/mob/living/carbon/human/user = loc + // no fun for pacifists + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to put others in danger!") + return if(!lit || operating) return operating = TRUE diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm index a541fb8fe3..44121d64d1 100644 --- a/code/game/objects/items/holy_weapons.dm +++ b/code/game/objects/items/holy_weapons.dm @@ -771,7 +771,10 @@ /obj/item/nullrod/tribal_knife/process() slowdown = rand(-2, 2) - + if(iscarbon(loc)) + var/mob/living/carbon/wielder = loc + if(wielder.is_holding(src)) + wielder.update_equipment_speed_mods() /obj/item/nullrod/pitchfork icon_state = "pitchfork0" diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm index 36f89e57f2..f0fd29adbc 100644 --- a/code/game/objects/items/tools/crowbar.dm +++ b/code/game/objects/items/tools/crowbar.dm @@ -88,6 +88,7 @@ usesound = 'sound/items/jaws_pry.ogg' force = 15 toolspeed = 0.25 + can_force_powered = TRUE /obj/item/crowbar/power/suicide_act(mob/user) user.visible_message("[user] is putting [user.p_their()] head in [src], it looks like [user.p_theyre()] trying to commit suicide!") diff --git a/code/modules/admin/DB_ban/functions.dm b/code/modules/admin/DB_ban/functions.dm index 39c4d2d939..4c00e8f010 100644 --- a/code/modules/admin/DB_ban/functions.dm +++ b/code/modules/admin/DB_ban/functions.dm @@ -80,8 +80,11 @@ var/client/banned_client = banned_mob?.client var/banned_mob_guest_key = had_banned_mob && IsGuestKey(banned_mob.key) banned_mob = null - var/sql_ckey = sanitizeSQL(ckey) - var/datum/DBQuery/query_add_ban_get_ckey = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_add_ban_get_ckey = SSdbcore.NewQuery({" + SELECT 1 + FROM [format_table_name("player")] + WHERE ckey = :ckey"}, + list("ckey" = ckey)) if(!query_add_ban_get_ckey.warn_execute()) qdel(query_add_ban_get_ckey) return @@ -122,10 +125,11 @@ else adminwho += ", [C]" - reason = sanitizeSQL(reason) - var/sql_a_ckey = sanitizeSQL(a_ckey) if(maxadminbancheck) - var/datum/DBQuery/query_check_adminban_amt = SSdbcore.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[sql_a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") + var/datum/db_query/query_check_adminban_amt = SSdbcore.NewQuery({" + SELECT count(id) AS num FROM [format_table_name("ban")] + WHERE (a_ckey = :a_ckey) AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) + "}, list("a_ckey" = a_ckey)) if(!query_check_adminban_amt.warn_execute()) qdel(query_check_adminban_amt) return @@ -143,13 +147,15 @@ computerid = "0" if(!ip) ip = "0.0.0.0" - var/sql_job = sanitizeSQL(job) - var/sql_computerid = sanitizeSQL(computerid) - var/sql_ip = sanitizeSQL(ip) - var/sql_a_computerid = sanitizeSQL(a_computerid) - var/sql_a_ip = sanitizeSQL(a_ip) - var/sql = "INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`round_id`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]', '[bantype_str]', '[reason]', '[sql_job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[sql_ckey]', '[sql_computerid]', INET_ATON('[sql_ip]'), '[sql_a_ckey]', '[sql_a_computerid]', INET_ATON('[sql_a_ip]'), '[who]', '[adminwho]')" - var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql) + var/datum/db_query/query_add_ban = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`round_id`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) + VALUES (Now(), INET_ATON(:internet_address), :port, :round_id, :bantype_str, :reason, :job, :duration, Now() + INTERVAL :expiration_time MINUTE, :ckey, :computerid, INET_ATON(:ip), :a_ckey, :a_computerid, INET_ATON(:a_ip), :who, :adminwho) + "}, list("internet_address" = world.internet_address ? world.internet_address : 0, + "port" = world.port, "round_id" = GLOB.round_id, "bantype_str" = bantype_str, "reason" = reason, + "job" = job, "duration" = duration ? "[duration]":"0", "expiration_time" = (duration > 0) ? duration : 0, + "ckey" = ckey, "computerid" = computerid, "ip" = ip, + "a_ckey" = a_ckey, "a_computerid" = a_computerid, "a_ip" = a_ip, "who" = who, "adminwho" = adminwho + )) if(!query_add_ban.warn_execute()) qdel(query_add_ban) return @@ -160,7 +166,7 @@ var/datum/admin_help/AH = admin_ticket_log(ckey, msg) if(announceinirc) - send2irc("BAN ALERT","[a_key] applied a [bantype_str] on [bankey]") + send2adminchat("BAN ALERT","[a_key] applied a [bantype_str] on [bankey]") if(kickbannedckey) if(AH) @@ -212,11 +218,11 @@ bantype_sql = "(bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now() ) )" else bantype_sql = "bantype = '[bantype_str]'" - var/sql_ckey = sanitizeSQL(ckey) - var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = '[sql_ckey]' AND [bantype_sql] AND (unbanned is null OR unbanned = false)" + var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = :ckey AND [bantype_sql] AND (unbanned is null OR unbanned = false)" + var/list/sql_args = list("ckey" = ckey) if(job) - var/sql_job = sanitizeSQL(job) - sql += " AND job = '[sql_job]'" + sql += " AND job = :job" + sql_args["job"] = job if(!SSdbcore.Connect()) return @@ -224,7 +230,7 @@ var/ban_id var/ban_number = 0 //failsafe - var/datum/DBQuery/query_unban_get_id = SSdbcore.NewQuery(sql) + var/datum/db_query/query_unban_get_id = SSdbcore.NewQuery(sql, sql_args) if(!query_unban_get_id.warn_execute()) qdel(query_unban_get_id) return @@ -258,7 +264,7 @@ to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), duration, reason FROM [format_table_name("ban")] WHERE id = [banid]") + var/datum/db_query/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), duration, reason FROM [format_table_name("ban")] WHERE id = [banid]") if(!query_edit_ban_get_details.warn_execute()) qdel(query_edit_ban_get_details) return @@ -278,19 +284,17 @@ return qdel(query_edit_ban_get_details) - reason = sanitizeSQL(reason) var/value switch(param) if("reason") if(!value) value = input("Insert the new reason for [p_key]'s ban", "New Reason", "[reason]", null) as null|text - value = sanitizeSQL(value) if(!value) to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [e_key] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]") + var/datum/db_query/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [e_key] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]") if(!query_edit_ban_reason.warn_execute()) qdel(query_edit_ban_reason) return @@ -303,7 +307,7 @@ to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [e_key] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") + var/datum/db_query/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [e_key] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") if(!query_edit_ban_duration.warn_execute()) qdel(query_edit_ban_duration) return @@ -333,7 +337,7 @@ var/ban_number = 0 //failsafe var/p_key - var/datum/DBQuery/query_unban_get_ckey = SSdbcore.NewQuery(sql) + var/datum/db_query/query_unban_get_ckey = SSdbcore.NewQuery(sql) if(!query_unban_get_ckey.warn_execute()) qdel(query_unban_get_ckey) return @@ -358,7 +362,7 @@ var/unban_ip = owner.address var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = INET_ATON('[unban_ip]') WHERE id = [id]" - var/datum/DBQuery/query_unban = SSdbcore.NewQuery(sql_update) + var/datum/db_query/query_unban = SSdbcore.NewQuery(sql_update) if(!query_unban.warn_execute()) qdel(query_unban) return @@ -448,20 +452,28 @@ if(adminckey || playerckey || ip || cid) var/list/searchlist = list() + var/list/searchlist_args = list() if(playerckey) - searchlist += "ckey = '[sanitizeSQL(ckey(playerckey))]'" + searchlist += "ckey = :playerckey" + searchlist_args["playerckey"] = playerckey if(adminckey) - searchlist += "a_ckey = '[sanitizeSQL(ckey(adminckey))]'" + searchlist += "a_ckey = :adminckey" + searchlist_args["adminckey"] = adminckey if(ip) - searchlist += "ip = INET_ATON('[sanitizeSQL(ip)]')" + searchlist += "ip = INET_ATON(:ip)" + searchlist_args["ip"] = ip if(cid) - searchlist += "computerid = '[sanitizeSQL(cid)]'" - var/search = searchlist.Join(" AND ") + searchlist += "computerid = :cid" + searchlist_args["cid"] = cid + var/search = searchlist.Join(" AND ") // x = x AND y = z var/bancount = 0 var/bansperpage = 15 var/pagecount = 0 page = text2num(page) - var/datum/DBQuery/query_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE [search]") + var/datum/db_query/query_count_bans = SSdbcore.NewQuery({" + SELECT COUNT(id) FROM [format_table_name("ban")] + WHERE [search] + "}, searchlist_args) if(!query_count_bans.warn_execute()) qdel(query_count_bans) return @@ -489,7 +501,11 @@ output += "OPTIONS" output += "" var/limit = " LIMIT [bansperpage * page], [bansperpage]" - var/datum/DBQuery/query_search_bans = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), unbanned, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_datetime, edits, round_id FROM [format_table_name("ban")] WHERE [search] ORDER BY bantime DESC[limit]") + + var/datum/db_query/query_search_bans = SSdbcore.NewQuery({" + SELECT id, bantime, bantype, reason, job, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), unbanned, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_datetime, edits, round_id + FROM [format_table_name("ban")] + WHERE [search] ORDER BY bantime DESC[limit]"}, searchlist_args) if(!query_search_bans.warn_execute()) qdel(query_search_bans) return diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 63facade2e..825e2d83c7 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -100,7 +100,18 @@ if(computer_id) cidquery = " OR computerid = '[computer_id]' " - var/datum/DBQuery/query_ban_check = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] WHERE (ckey = '[ckey]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') AND expiration_time > Now())) AND isnull(unbanned)") + var/datum/db_query/query_ban_check = SSdbcore.NewQuery({" + SELECT IFNULL((SELECT byond_key + FROM [format_table_name("player")] + WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] + WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] + WHERE (ckey = :ckey [ipquery] [cidquery]) + AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') + AND expiration_time > Now())) AND isnull(unbanned) + "}, list( + "ckey" = ckey + )) if(!query_ban_check.Execute(async = TRUE)) qdel(query_ban_check) key_cache[key] = 0 diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 39053b8e15..79ba4abae7 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -119,16 +119,12 @@ GLOBAL_PROTECT(protected_ranks) set waitfor = FALSE if(IsAdminAdvancedProcCall()) - to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.") + to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.", confidential = TRUE) return var/list/sql_ranks = list() for(var/datum/admin_rank/R in GLOB.protected_ranks) - var/sql_rank = sanitizeSQL(R.name) - var/sql_flags = sanitizeSQL(R.include_rights) - var/sql_exclude_flags = sanitizeSQL(R.exclude_rights) - var/sql_can_edit_flags = sanitizeSQL(R.can_edit_rights) - sql_ranks += list(list("rank" = "'[sql_rank]'", "flags" = "[sql_flags]", "exclude_flags" = "[sql_exclude_flags]", "can_edit_flags" = "[sql_can_edit_flags]")) + sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights)) SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE) //load our rank - > rights associations @@ -160,7 +156,7 @@ GLOBAL_PROTECT(protected_ranks) if(!no_update) sync_ranks_with_db() else - var/datum/DBQuery/query_load_admin_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") + var/datum/db_query/query_load_admin_ranks = SSdbcore.NewQuery("SELECT `rank`, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") if(!query_load_admin_ranks.Execute()) message_admins("Error loading admin ranks from database. Loading from backup.") log_sql("Error loading admin ranks from database. Loading from backup.") @@ -168,7 +164,7 @@ GLOBAL_PROTECT(protected_ranks) else while(query_load_admin_ranks.NextRow()) var/skip - var/rank_name = ckeyEx(query_load_admin_ranks.item[1]) + var/rank_name = query_load_admin_ranks.item[1] for(var/datum/admin_rank/R in GLOB.admin_ranks) if(R.name == rank_name) //this rank was already loaded from txt override skip = 1 @@ -234,20 +230,12 @@ GLOBAL_PROTECT(protected_ranks) for(var/datum/admin_rank/R in GLOB.admin_ranks) rank_names[R.name] = R //ckeys listed in admins.txt are always made admins before sql loading is attempted - var/list/lines = world.file2list("[global.config.directory]/admins.txt") - for(var/line in lines) - if(!length(line) || findtextEx(line, "#", 1, 2)) - continue - var/list/entry = splittext(line, "=") - if(entry.len < 2) - continue - var/ckey = ckey(entry[1]) - var/rank = ckeyEx(entry[2]) - if(!ckey || !rank) - continue - new /datum/admins(rank_names[rank], ckey, 0, 1) + var/admins_text = file2text("[global.config.directory]/admins.txt") + var/regex/admins_regex = new(@"^(?!#)(.+?)\s+=\s+(.+)", "gm") + while(admins_regex.Find(admins_text)) + new /datum/admins(rank_names[admins_regex.group[2]], ckey(admins_regex.group[1]), FALSE, TRUE) if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) - var/datum/DBQuery/query_load_admins = SSdbcore.NewQuery("SELECT ckey, rank FROM [format_table_name("admin")] ORDER BY rank") + var/datum/db_query/query_load_admins = SSdbcore.NewQuery("SELECT ckey, `rank` FROM [format_table_name("admin")] ORDER BY `rank`") if(!query_load_admins.Execute()) message_admins("Error loading admins from database. Loading from backup.") log_sql("Error loading admins from database. Loading from backup.") @@ -255,7 +243,7 @@ GLOBAL_PROTECT(protected_ranks) else while(query_load_admins.NextRow()) var/admin_ckey = ckey(query_load_admins.item[1]) - var/admin_rank = ckeyEx(query_load_admins.item[2]) + var/admin_rank = query_load_admins.item[2] var/skip if(rank_names[admin_rank] == null) message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].") diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index cae1c6b22e..8ebef00508 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -3,8 +3,11 @@ if(!M || !istype(M) || !M.ckey) return FALSE - if(!M.client) //no cache. fallback to a datum/DBQuery - var/datum/DBQuery/query_jobban_check_ban = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(M.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[sanitizeSQL(rank)]'") + if(!M.client) //no cache. fallback to a datum/db_query + var/datum/db_query/query_jobban_check_ban = SSdbcore.NewQuery({" + SELECT reason FROM [format_table_name("ban")] + WHERE ckey = :ckey AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = :rank + "}, list("ckey" = M.ckey, "rank" = rank)) if(!query_jobban_check_ban.warn_execute()) qdel(query_jobban_check_ban) return @@ -28,7 +31,10 @@ return if(C && istype(C)) C.jobbancache = list() - var/datum/DBQuery/query_jobban_build_cache = SSdbcore.NewQuery("SELECT job, reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(C.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") + var/datum/db_query/query_jobban_build_cache = SSdbcore.NewQuery({" + SELECT job, reason FROM [format_table_name("ban")] + WHERE ckey = :ckey AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) + "}, list("ckey" = C.ckey)) if(!query_jobban_build_cache.warn_execute()) qdel(query_jobban_build_cache) return diff --git a/code/modules/admin/create_poll.dm b/code/modules/admin/create_poll.dm index 9f002f92e0..e5391f0f43 100644 --- a/code/modules/admin/create_poll.dm +++ b/code/modules/admin/create_poll.dm @@ -34,8 +34,9 @@ var/endtime = input("Set end time for poll as format YYYY-MM-DD HH:MM:SS. All times in server time. HH:MM:SS is optional and 24-hour. Must be later than starting time for obvious reasons.", "Set end time", SQLtime()) as text if(!endtime) return - endtime = sanitizeSQL(endtime) - var/datum/DBQuery/query_validate_time = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[endtime]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[endtime]','%Y-%c-%d %T'), 0)") + var/datum/db_query/query_validate_time = SSdbcore.NewQuery({" + SELECT IF(STR_TO_DATE(:endtime,'%Y-%c-%d %T') > NOW(), STR_TO_DATE(:endtime,'%Y-%c-%d %T'), 0) + "}, list("endtime" = endtime)) if(!query_validate_time.warn_execute() || QDELETED(usr) || !src) qdel(query_validate_time) return @@ -63,11 +64,9 @@ dontshow = 0 else return - var/sql_ckey = sanitizeSQL(ckey) var/question = input("Write your question","Question") as message|null if(!question) return - question = sanitizeSQL(question) var/list/sql_option_list = list() if(polltype != POLLTYPE_TEXT) var/add_option = 1 @@ -75,7 +74,6 @@ var/option = input("Write your option","Option") as message|null if(!option) return - option = sanitizeSQL(option) var/default_percentage_calc = 0 if(polltype != POLLTYPE_IRV) switch(alert("Should this option be included by default when poll result percentages are generated?",,"Yes","No","Cancel")) @@ -92,34 +90,27 @@ var/descmax = "" if(polltype == POLLTYPE_RATING) minval = input("Set minimum rating value.","Minimum rating") as num|null - if(minval) - minval = sanitizeSQL(minval) - else if(minval == null) + if(minval == null) return maxval = input("Set maximum rating value.","Maximum rating") as num|null - if(maxval) - maxval = sanitizeSQL(maxval) if(minval >= maxval) to_chat(src, "Maximum rating value can't be less than or equal to minimum rating value") continue - else if(maxval == null) + if(maxval == null) return descmin = input("Optional: Set description for minimum rating","Minimum rating description") as message|null - if(descmin) - descmin = sanitizeSQL(descmin) - else if(descmin == null) + if(descmin == null) return descmid = input("Optional: Set description for median rating","Median rating description") as message|null - if(descmid) - descmid = sanitizeSQL(descmid) - else if(descmid == null) + if(descmid == null) return descmax = input("Optional: Set description for maximum rating","Maximum rating description") as message|null - if(descmax) - descmax = sanitizeSQL(descmax) - else if(descmax == null) + if(descmax == null) return - sql_option_list += list(list("text" = "'[option]'", "minval" = "'[minval]'", "maxval" = "'[maxval]'", "descmin" = "'[descmin]'", "descmid" = "'[descmid]'", "descmax" = "'[descmax]'", "default_percentage_calc" = "'[default_percentage_calc]'")) + sql_option_list += list(list( + "text" = option, "minval" = minval, "maxval" = maxval, + "descmin" = descmin, "descmid" = descmid, "descmax" = descmax, + "default_percentage_calc" = default_percentage_calc)) switch(alert(" ",,"Add option","Finish", "Cancel")) if("Add option") add_option = 1 @@ -129,14 +120,21 @@ return 0 var/m1 = "[key_name(usr)] has created a new server poll. Poll type: [polltype] - Admin Only: [adminonly ? "Yes" : "No"] - Question: [question]" var/m2 = "[key_name_admin(usr)] has created a new server poll. Poll type: [polltype] - Admin Only: [adminonly ? "Yes" : "No"]
Question: [question]" - var/datum/DBQuery/query_polladd_question = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_question")] (polltype, starttime, endtime, question, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, dontshow) VALUES ('[polltype]', '[starttime]', '[endtime]', '[question]', '[adminonly]', '[choice_amount]', '[sql_ckey]', INET_ATON('[address]'), '[dontshow]')") + var/datum/db_query/query_polladd_question = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_question")] (polltype, starttime, endtime, question, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, dontshow) + VALUES (:polltype, :starttime, :endtime, :question, :adminonly, :choice_amount, :ckey, INET_ATON(:address), :dontshow) + "}, list( + "polltype" = polltype, "starttime" = starttime, "endtime" = endtime, + "question" = question, "adminonly" = adminonly, "choice_amount" = choice_amount, + "ckey" = ckey, "address" = address, "dontshow" = dontshow + )) if(!query_polladd_question.warn_execute()) qdel(query_polladd_question) return qdel(query_polladd_question) if(polltype != POLLTYPE_TEXT) var/pollid = 0 - var/datum/DBQuery/query_get_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") + var/datum/db_query/query_get_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") if(!query_get_id.warn_execute()) qdel(query_get_id) return @@ -145,6 +143,6 @@ qdel(query_get_id) for(var/list/i in sql_option_list) i |= list("pollid" = "'[pollid]'") - SSdbcore.MassInsert(format_table_name("poll_option"), sql_option_list, warn = 1) + SSdbcore.MassInsert(format_table_name("poll_option"), sql_option_list, warn = TRUE) log_admin(m1) message_admins(m2) diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm index e0056b3e40..71c9a11acd 100644 --- a/code/modules/admin/ipintel.dm +++ b/code/modules/admin/ipintel.dm @@ -29,27 +29,27 @@ return if (!bypasscache) var/datum/ipintel/cachedintel = SSipintel.cache[ip] - if (cachedintel && cachedintel.is_valid()) + if (cachedintel?.is_valid()) cachedintel.cache = TRUE return cachedintel if(SSdbcore.Connect()) var/rating_bad = CONFIG_GET(number/ipintel_rating_bad) - var/datum/DBQuery/query_get_ip_intel = SSdbcore.NewQuery({" + var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery({" SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) FROM [format_table_name("ipintel")] WHERE - ip = INET_ATON('[ip]') + ip = INET_ATON(':ip') AND (( - intel < [rating_bad] + intel < :rating_bad AND - date + INTERVAL [CONFIG_GET(number/ipintel_save_good)] HOUR > NOW() + date + INTERVAL :save_good HOUR > NOW() ) OR ( - intel >= [rating_bad] + intel >= :rating_bad AND - date + INTERVAL [CONFIG_GET(number/ipintel_save_bad)] HOUR > NOW() + date + INTERVAL :save_bad HOUR > NOW() )) - "}) + "}, list("ip" = ip, "rating_bad" = rating_bad, "save_good" = CONFIG_GET(number/ipintel_save_good), "save_bad" = CONFIG_GET(number/ipintel_save_bad))) if(!query_get_ip_intel.Execute()) qdel(query_get_ip_intel) return @@ -67,12 +67,15 @@ if (updatecache && res.intel >= 0) SSipintel.cache[ip] = res if(SSdbcore.Connect()) - var/datum/DBQuery/query_add_ip_intel = SSdbcore.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()") + var/datum/db_query/query_add_ip_intel = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON(:ip), :intel) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()", + list("ip" = ip, "intel" = res.intel) + ) query_add_ip_intel.Execute() qdel(query_add_ip_intel) -/proc/ip_intel_query(ip, var/retryed=0) +/proc/ip_intel_query(ip, retryed=0) . = -1 //default if (!ip) return @@ -131,8 +134,3 @@ /proc/log_ipintel(text) log_game("IPINTEL: [text]") debug_admins("IPINTEL: [text]") - - - - - diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm index 05f7465b03..9f5dc00a48 100644 --- a/code/modules/admin/permissionedit.dm +++ b/code/modules/admin/permissionedit.dm @@ -15,21 +15,14 @@ else output += "
\[Log\]
\[Management\]" if(action == 1) - var/list/searchlist = list(" WHERE ") - if(target) - searchlist += "ckey = '[sanitizeSQL(target)]'" - if(operation) - if(target) - searchlist += " AND " - searchlist += "operation = '[sanitizeSQL(operation)]'" - var/search - if(searchlist.len > 1) - search = searchlist.Join("") var/logcount = 0 var/logssperpage = 20 var/pagecount = 0 page = text2num(page) - var/datum/DBQuery/query_count_admin_logs = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("admin_log")][search]") + var/datum/db_query/query_count_admin_logs = SSdbcore.NewQuery( + "SELECT COUNT(id) FROM [format_table_name("admin_log")] WHERE (:target IS NULL OR adminckey = :target) AND (:operation IS NULL OR operation = :operation)", + list("target" = target, "operation" = operation) + ) if(!query_count_admin_logs.warn_execute()) qdel(query_count_admin_logs) return @@ -43,8 +36,20 @@ logcount -= logssperpage pagecount++ output += "|" - var/limit = " LIMIT [logssperpage * page], [logssperpage]" - var/datum/DBQuery/query_search_admin_logs = SSdbcore.NewQuery("SELECT datetime, round_id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), operation, IF(ckey IS NULL, target, byond_key), log FROM [format_table_name("admin_log")] LEFT JOIN [format_table_name("player")] ON target = ckey[search] ORDER BY datetime DESC[limit]") + var/datum/db_query/query_search_admin_logs = SSdbcore.NewQuery({" + SELECT + datetime, + round_id, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + operation, + IF(ckey IS NULL, target, byond_key), + log + FROM [format_table_name("admin_log")] + LEFT JOIN [format_table_name("player")] ON target = ckey + WHERE (:target IS NULL OR ckey = :target) AND (:operation IS NULL OR operation = :operation) + ORDER BY datetime DESC + LIMIT :skip, :take + "}, list("target" = target, "operation" = operation, "skip" = logssperpage * page, "take" = logssperpage)) if(!query_search_admin_logs.warn_execute()) qdel(query_search_admin_logs) return @@ -59,7 +64,7 @@ qdel(query_search_admin_logs) if(action == 2) output += "

Admin ckeys with invalid ranks

" - var/datum/DBQuery/query_check_admin_errors = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].rank FROM [format_table_name("admin")] LEFT JOIN [format_table_name("admin_ranks")] ON [format_table_name("admin_ranks")].rank = [format_table_name("admin")].rank WHERE [format_table_name("admin_ranks")].rank IS NULL") + var/datum/db_query/query_check_admin_errors = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].`rank` FROM [format_table_name("admin")] LEFT JOIN [format_table_name("admin_ranks")] ON [format_table_name("admin_ranks")].`rank` = [format_table_name("admin")].`rank` WHERE [format_table_name("admin_ranks")].`rank` IS NULL") if(!query_check_admin_errors.warn_execute()) qdel(query_check_admin_errors) return @@ -70,7 +75,7 @@ output += "
" qdel(query_check_admin_errors) output += "

Unused ranks

" - var/datum/DBQuery/query_check_unused_rank = SSdbcore.NewQuery("SELECT [format_table_name("admin_ranks")].rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] LEFT JOIN [format_table_name("admin")] ON [format_table_name("admin")].rank = [format_table_name("admin_ranks")].rank WHERE [format_table_name("admin")].rank IS NULL") + var/datum/db_query/query_check_unused_rank = SSdbcore.NewQuery("SELECT [format_table_name("admin_ranks")].`rank`, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] LEFT JOIN [format_table_name("admin")] ON [format_table_name("admin")].`rank` = [format_table_name("admin_ranks")].`rank` WHERE [format_table_name("admin")].`rank` IS NULL") if(!query_check_unused_rank.warn_execute()) qdel(query_check_unused_rank) return @@ -130,7 +135,7 @@ log_admin("[key_name(usr)] attempted to edit admin permissions without sufficient rights.") return if(IsAdminAdvancedProcCall()) - to_chat(usr, "Admin Edit blocked: Advanced ProcCall detected.") + to_chat(usr, "Admin Edit blocked: Advanced ProcCall detected.", confidential = TRUE) return var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/permissions) permissions_assets.send(src) @@ -145,19 +150,19 @@ skip = TRUE if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_admins) && task == "rank") if(admin_ckey in GLOB.protected_admins) - to_chat(usr, "Editing the rank of this admin is blocked by server configuration.") + to_chat(usr, "Editing the rank of this admin is blocked by server configuration.", confidential = TRUE) return if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && task == "permissions") if(D.rank in GLOB.protected_ranks) - to_chat(usr, "Editing the flags of this rank is blocked by server configuration.") + to_chat(usr, "Editing the flags of this rank is blocked by server configuration.", confidential = TRUE) return if(CONFIG_GET(flag/load_legacy_ranks_only) && (task == "add" || task == "rank" || task == "permissions")) - to_chat(usr, "Database rank loading is disabled, only temporary changes can be made to a rank's permissions and permanently creating a new rank is blocked.") + to_chat(usr, "Database rank loading is disabled, only temporary changes can be made to a rank's permissions and permanently creating a new rank is blocked.", confidential = TRUE) legacy_only = TRUE if(check_rights(R_DBRANKS, FALSE)) if(!skip) if(!SSdbcore.Connect()) - to_chat(usr, "Unable to connect to database, changes are temporary only.") + to_chat(usr, "Unable to connect to database, changes are temporary only.", confidential = TRUE) use_db = FALSE else use_db = alert("Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", "Permanent", "Temporary", "Cancel") @@ -165,7 +170,6 @@ return if(use_db == "Permanent") use_db = TRUE - admin_ckey = sanitizeSQL(admin_ckey) else use_db = FALSE if(QDELETED(usr)) @@ -209,26 +213,34 @@ if(!.) return FALSE if(!admin_ckey && (. in GLOB.admin_datums+GLOB.deadmins)) - to_chat(usr, "[admin_key] is already an admin.") + to_chat(usr, "[admin_key] is already an admin.", confidential = TRUE) return FALSE if(use_db) - . = sanitizeSQL(.) //if an admin exists without a datum they won't be caught by the above - var/datum/DBQuery/query_admin_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin")] WHERE ckey = '[.]'") + var/datum/db_query/query_admin_in_db = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("admin")] WHERE ckey = :ckey", + list("ckey" = .) + ) if(!query_admin_in_db.warn_execute()) qdel(query_admin_in_db) return FALSE if(query_admin_in_db.NextRow()) qdel(query_admin_in_db) - to_chat(usr, "[admin_key] already listed in admin database. Check the Management tab if they don't appear in the list of admins.") + to_chat(usr, "[admin_key] already listed in admin database. Check the Management tab if they don't appear in the list of admins.", confidential = TRUE) return FALSE qdel(query_admin_in_db) - var/datum/DBQuery/query_add_admin = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin")] (ckey, rank) VALUES ('[.]', 'NEW ADMIN')") + var/datum/db_query/query_add_admin = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("admin")] (ckey, `rank`) VALUES (:ckey, 'NEW ADMIN')", + list("ckey" = .) + ) if(!query_add_admin.warn_execute()) qdel(query_add_admin) return FALSE qdel(query_add_admin) - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'add admin', '[.]', 'New admin added: [.]')") + var/datum/db_query/query_add_admin_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'add admin', :target, CONCAT('New admin added: ', :target)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "target" = .)) if(!query_add_admin_log.warn_execute()) qdel(query_add_admin_log) return FALSE @@ -243,12 +255,18 @@ var/m1 = "[key_name_admin(usr)] removed [admin_key] from the admins list [use_db ? "permanently" : "temporarily"]" var/m2 = "[key_name(usr)] removed [admin_key] from the admins list [use_db ? "permanently" : "temporarily"]" if(use_db) - var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("DELETE FROM [format_table_name("admin")] WHERE ckey = '[admin_ckey]'") + var/datum/db_query/query_add_rank = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("admin")] WHERE ckey = :ckey", + list("ckey" = admin_ckey) + ) if(!query_add_rank.warn_execute()) qdel(query_add_rank) return qdel(query_add_rank) - var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'remove admin', '[admin_ckey]', 'Admin removed: [admin_ckey]')") + var/datum/db_query/query_add_rank_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'remove admin', :admin_ckey, CONCAT('Admin removed: ', :admin_ckey)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "admin_ckey" = admin_ckey)) if(!query_add_rank_log.warn_execute()) qdel(query_add_rank_log) return @@ -271,6 +289,14 @@ log_admin("[key_name(usr)] forcefully deadmined [admin_key]") D.deactivate() //after logs so the deadmined admin can see the message. +/datum/admins/proc/auto_deadmin() + to_chat(owner, "You are now a normal player.", confidential = TRUE) + var/old_owner = owner + deactivate() + message_admins("[old_owner] deadmined via auto-deadmin config.") + log_admin("[old_owner] deadmined via auto-deadmin config.") + return TRUE + /datum/admins/proc/change_admin_rank(admin_ckey, admin_key, use_db, datum/admins/D, legacy_only) var/datum/admin_rank/R var/list/rank_names = list() @@ -281,7 +307,7 @@ rank_names[R.name] = R var/new_rank = input("Please select a rank", "New rank") as null|anything in rank_names if(new_rank == "*New Rank*") - new_rank = ckeyEx(input("Please input a new rank", "New custom rank") as text|null) + new_rank = input("Please input a new rank", "New custom rank") as text|null if(!new_rank) return R = rank_names[new_rank] @@ -294,10 +320,12 @@ var/m1 = "[key_name_admin(usr)] edited the admin rank of [admin_key] to [new_rank] [use_db ? "permanently" : "temporarily"]" var/m2 = "[key_name(usr)] edited the admin rank of [admin_key] to [new_rank] [use_db ? "permanently" : "temporarily"]" if(use_db) - new_rank = sanitizeSQL(new_rank) //if a player was tempminned before having a permanent change made to their rank they won't yet be in the db var/old_rank - var/datum/DBQuery/query_admin_in_db = SSdbcore.NewQuery("SELECT rank FROM [format_table_name("admin")] WHERE ckey = '[admin_ckey]'") + var/datum/db_query/query_admin_in_db = SSdbcore.NewQuery( + "SELECT `rank` FROM [format_table_name("admin")] WHERE ckey = :admin_ckey", + list("admin_ckey" = admin_ckey) + ) if(!query_admin_in_db.warn_execute()) qdel(query_admin_in_db) return @@ -308,29 +336,44 @@ old_rank = query_admin_in_db.item[1] qdel(query_admin_in_db) //similarly if a temp rank is created it won't be in the db if someone is permanently changed to it - var/datum/DBQuery/query_rank_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin_ranks")] WHERE rank = '[new_rank]'") + var/datum/db_query/query_rank_in_db = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("admin_ranks")] WHERE `rank` = :new_rank", + list("new_rank" = new_rank) + ) if(!query_rank_in_db.warn_execute()) qdel(query_rank_in_db) return if(!query_rank_in_db.NextRow()) QDEL_NULL(query_rank_in_db) - var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_ranks")] (rank, flags, exclude_flags, can_edit_flags) VALUES ('[new_rank]', '0', '0', '0')") + var/datum/db_query/query_add_rank = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_ranks")] (`rank`, flags, exclude_flags, can_edit_flags) + VALUES (:new_rank, '0', '0', '0') + "}, list("new_rank" = new_rank)) if(!query_add_rank.warn_execute()) qdel(query_add_rank) return qdel(query_add_rank) - var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'add rank', '[new_rank]', 'New rank added: [new_rank]')") + var/datum/db_query/query_add_rank_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'add rank', :new_rank, CONCAT('New rank added: ', :new_rank)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "new_rank" = new_rank)) if(!query_add_rank_log.warn_execute()) qdel(query_add_rank_log) return qdel(query_add_rank_log) qdel(query_rank_in_db) - var/datum/DBQuery/query_change_rank = SSdbcore.NewQuery("UPDATE [format_table_name("admin")] SET rank = '[new_rank]' WHERE ckey = '[admin_ckey]'") + var/datum/db_query/query_change_rank = SSdbcore.NewQuery( + "UPDATE [format_table_name("admin")] SET `rank` = :new_rank WHERE ckey = :admin_ckey", + list("new_rank" = new_rank, "admin_ckey" = admin_ckey) + ) if(!query_change_rank.warn_execute()) qdel(query_change_rank) return qdel(query_change_rank) - var/datum/DBQuery/query_change_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'change admin rank', '[admin_ckey]', 'Rank of [admin_ckey] changed from [old_rank] to [new_rank]')") + var/datum/db_query/query_change_rank_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'change admin rank', :target, CONCAT('Rank of ', :target, ' changed from ', :old_rank, ' to ', :new_rank)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "target" = admin_ckey, "old_rank" = old_rank, "new_rank" = new_rank)) if(!query_change_rank_log.warn_execute()) qdel(query_change_rank_log) return @@ -357,11 +400,15 @@ return var/m1 = "[key_name_admin(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]" var/m2 = "[key_name(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]" - if(use_db || legacy_only) + if(use_db && !legacy_only) + var/rank_name = D.rank.name var/old_flags var/old_exclude_flags var/old_can_edit_flags - var/datum/DBQuery/query_get_rank_flags = SSdbcore.NewQuery("SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = '[D.rank.name]'") + var/datum/db_query/query_get_rank_flags = SSdbcore.NewQuery( + "SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE `rank` = :rank_name", + list("rank_name" = rank_name) + ) if(!query_get_rank_flags.warn_execute()) qdel(query_get_rank_flags) return @@ -370,12 +417,19 @@ old_exclude_flags = text2num(query_get_rank_flags.item[2]) old_can_edit_flags = text2num(query_get_rank_flags.item[3]) qdel(query_get_rank_flags) - var/datum/DBQuery/query_change_rank_flags = SSdbcore.NewQuery("UPDATE [format_table_name("admin_ranks")] SET flags = '[new_flags]', exclude_flags = '[new_exclude_flags]', can_edit_flags = '[new_can_edit_flags]' WHERE rank = '[D.rank.name]'") + var/datum/db_query/query_change_rank_flags = SSdbcore.NewQuery( + "UPDATE [format_table_name("admin_ranks")] SET flags = :new_flags, exclude_flags = :new_exclude_flags, can_edit_flags = :new_can_edit_flags WHERE `rank` = :rank_name", + list("new_flags" = new_flags, "new_exclude_flags" = new_exclude_flags, "new_can_edit_flags" = new_can_edit_flags, "rank_name" = rank_name) + ) if(!query_change_rank_flags.warn_execute()) qdel(query_change_rank_flags) return qdel(query_change_rank_flags) - var/datum/DBQuery/query_change_rank_flags_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'change rank flags', '[D.rank.name]', 'Permissions of [D.rank.name] changed from[rights2text(old_flags," ")][rights2text(old_exclude_flags," ", "-")][rights2text(old_can_edit_flags," ", "*")] to[rights2text(new_flags," ")][rights2text(new_exclude_flags," ", "-")][rights2text(new_can_edit_flags," ", "*")]')") + var/log_message = "Permissions of [rank_name] changed from[rights2text(old_flags," ")][rights2text(old_exclude_flags," ", "-")][rights2text(old_can_edit_flags," ", "*")] to[rights2text(new_flags," ")][rights2text(new_exclude_flags," ", "-")][rights2text(new_can_edit_flags," ", "*")]" + var/datum/db_query/query_change_rank_flags_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'change rank flags', :rank_name, :log) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "rank_name" = rank_name, "log" = log_message)) if(!query_change_rank_flags_log.warn_execute()) qdel(query_change_rank_flags_log) return @@ -418,33 +472,41 @@ return for(var/datum/admin_rank/R in GLOB.admin_ranks) if(R.name == admin_rank && (!(R.rights & usr.client.holder.rank.can_edit_rights) == R.rights)) - to_chat(usr, "You don't have edit rights to all the rights this rank has, rank deletion not permitted.") + to_chat(usr, "You don't have edit rights to all the rights this rank has, rank deletion not permitted.", confidential = TRUE) return if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && (admin_rank in GLOB.protected_ranks)) - to_chat(usr, "Deletion of protected ranks is not permitted, it must be removed from admin_ranks.txt.") + to_chat(usr, "Deletion of protected ranks is not permitted, it must be removed from admin_ranks.txt.", confidential = TRUE) return if(CONFIG_GET(flag/load_legacy_ranks_only)) - to_chat(usr, "Rank deletion not permitted while database rank loading is disabled.") + to_chat(usr, "Rank deletion not permitted while database rank loading is disabled.", confidential = TRUE) return - admin_rank = sanitizeSQL(admin_rank) - var/datum/DBQuery/query_admins_with_rank = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin")] WHERE rank = '[admin_rank]'") + var/datum/db_query/query_admins_with_rank = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("admin")] WHERE `rank` = :admin_rank", + list("admin_rank" = admin_rank) + ) if(!query_admins_with_rank.warn_execute()) qdel(query_admins_with_rank) return if(query_admins_with_rank.NextRow()) qdel(query_admins_with_rank) - to_chat(usr, "Error: Rank deletion attempted while rank still used; Tell a coder, this shouldn't happen.") + to_chat(usr, "Error: Rank deletion attempted while rank still used; Tell a coder, this shouldn't happen.", confidential = TRUE) return qdel(query_admins_with_rank) if(alert("Are you sure you want to remove [admin_rank]?","Confirm Removal","Do it","Cancel") == "Do it") var/m1 = "[key_name_admin(usr)] removed rank [admin_rank] permanently" var/m2 = "[key_name(usr)] removed rank [admin_rank] permanently" - var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("DELETE FROM [format_table_name("admin_ranks")] WHERE rank = '[admin_rank]'") + var/datum/db_query/query_add_rank = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("admin_ranks")] WHERE `rank` = :admin_rank", + list("admin_rank" = admin_rank) + ) if(!query_add_rank.warn_execute()) qdel(query_add_rank) return qdel(query_add_rank) - var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'remove rank', '[admin_rank]', 'Rank removed: [admin_rank]')") + var/datum/db_query/query_add_rank_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'remove rank', :admin_rank, CONCAT('Rank removed: ', :admin_rank)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "admin_rank" = admin_rank)) if(!query_add_rank_log.warn_execute()) qdel(query_add_rank_log) return @@ -455,11 +517,13 @@ /datum/admins/proc/sync_lastadminrank(admin_ckey, admin_key, datum/admins/D) var/sqlrank = "Player" if (D) - sqlrank = sanitizeSQL(D.rank.name) - admin_ckey = sanitizeSQL(admin_ckey) - var/datum/DBQuery/query_sync_lastadminrank = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET lastadminrank = '[sqlrank]' WHERE ckey = '[admin_ckey]'") + sqlrank = D.rank.name + var/datum/db_query/query_sync_lastadminrank = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET lastadminrank = :rank WHERE ckey = :ckey", + list("rank" = sqlrank, "ckey" = admin_ckey) + ) if(!query_sync_lastadminrank.warn_execute()) qdel(query_sync_lastadminrank) return qdel(query_sync_lastadminrank) - to_chat(usr, "Sync of [admin_key] successful.") + to_chat(usr, "Sync of [admin_key] successful.", confidential = TRUE) diff --git a/code/modules/admin/sql_message_system.dm b/code/modules/admin/sql_message_system.dm index 4218d4238f..3ba309bc24 100644 --- a/code/modules/admin/sql_message_system.dm +++ b/code/modules/admin/sql_message_system.dm @@ -1,6 +1,6 @@ /proc/create_message(type, target_key, admin_ckey, text, timestamp, server, secret, logged = 1, browse, expiry, note_severity) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return if(!type) return @@ -9,8 +9,11 @@ var/new_key = input(usr,"Who would you like to create a [type] for?","Enter a key or ckey",null) as null|text if(!new_key) return - var/new_ckey = sanitizeSQL(ckey(new_key)) - var/datum/DBQuery/query_find_ckey = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ckey = '[new_ckey]'") + var/new_ckey = ckey(new_key) + var/datum/db_query/query_find_ckey = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = new_ckey) + ) if(!query_find_ckey.warn_execute()) qdel(query_find_ckey) return @@ -23,29 +26,24 @@ target_key = new_key if(QDELETED(usr)) return - if(target_ckey) - target_ckey = sanitizeSQL(target_ckey) if(!target_key) target_key = target_ckey if(!admin_ckey) admin_ckey = usr.ckey if(!admin_ckey) return - admin_ckey = sanitizeSQL(admin_ckey) if(!target_ckey) target_ckey = admin_ckey if(!text) text = input(usr,"Write your [type]","Create [type]") as null|message if(!text) return - text = sanitizeSQL(text) if(!timestamp) timestamp = SQLtime() if(!server) var/ssqlname = CONFIG_GET(string/serversqlname) if (ssqlname) server = ssqlname - server = sanitizeSQL(server) if(isnull(secret)) switch(alert("Hide note from being viewed by players?", "Secret note?","Yes","No","Cancel")) if("Yes") @@ -59,15 +57,17 @@ var/expire_time = input("Set expiry time for [type] as format YYYY-MM-DD HH:MM:SS. All times in server time. HH:MM:SS is optional and 24-hour. Must be later than current time for obvious reasons.", "Set expiry time", SQLtime()) as null|text if(!expire_time) return - expire_time = sanitizeSQL(expire_time) - var/datum/DBQuery/query_validate_expire_time = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[expire_time]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[expire_time]','%Y-%c-%d %T'), 0)") + var/datum/db_query/query_validate_expire_time = SSdbcore.NewQuery( + "SELECT IF(STR_TO_DATE(:expire_time,'%Y-%c-%d %T') > NOW(), STR_TO_DATE(:expire_time,'%Y-%c-%d %T'), 0)", + list("expire_time" = expire_time) + ) if(!query_validate_expire_time.warn_execute()) qdel(query_validate_expire_time) return if(query_validate_expire_time.NextRow()) var/checktime = text2num(query_validate_expire_time.item[1]) if(!checktime) - to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.") + to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.", confidential = TRUE) qdel(query_validate_expire_time) return expiry = query_validate_expire_time.item[1] @@ -76,8 +76,23 @@ note_severity = input("Set the severity of the note.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") if(!note_severity) return - note_severity = sanitizeSQL(note_severity) - var/datum/DBQuery/query_create_message = SSdbcore.NewQuery("INSERT INTO [format_table_name("messages")] (type, targetckey, adminckey, text, timestamp, server, server_ip, server_port, round_id, secret, expire_timestamp, severity) VALUES ('[type]', '[target_ckey]', '[admin_ckey]', '[text]', '[timestamp]', '[server]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]','[secret]', [expiry ? "'[expiry]'" : "NULL"], [note_severity ? "'[note_severity]'" : "NULL"])") + var/datum/db_query/query_create_message = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("messages")] (type, targetckey, adminckey, text, timestamp, server, server_ip, server_port, round_id, secret, expire_timestamp, severity) + VALUES (:type, :target_ckey, :admin_ckey, :text, :timestamp, :server, INET_ATON(:internet_address), :port, :round_id, :secret, :expiry, :note_severity) + "}, list( + "type" = type, + "target_ckey" = target_ckey, + "admin_ckey" = admin_ckey, + "text" = text, + "timestamp" = timestamp, + "server" = server, + "internet_address" = world.internet_address || "0", + "port" = "[world.port]", + "round_id" = GLOB.round_id, + "secret" = secret, + "expiry" = expiry || null, + "note_severity" = note_severity, + )) var/pm = "[key_name(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""]: [text]" var/header = "[key_name_admin(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""]" if(!query_create_message.warn_execute()) @@ -96,7 +111,7 @@ /proc/delete_message(message_id, logged = 1, browse) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) @@ -106,7 +121,11 @@ var/text var/user_key_name = key_name(usr) var/user_name_admin = key_name_admin(usr) - var/datum/DBQuery/query_find_del_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), text FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/deleted_by_ckey = usr.ckey + var/datum/db_query/query_find_del_message = SSdbcore.NewQuery( + "SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), text FROM [format_table_name("messages")] WHERE id = :id AND deleted = 0", + list("id" = message_id) + ) if(!query_find_del_message.warn_execute()) qdel(query_find_del_message) return @@ -115,7 +134,10 @@ target_key = query_find_del_message.item[2] text = query_find_del_message.item[3] qdel(query_find_del_message) - var/datum/DBQuery/query_del_message = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET deleted = 1 WHERE id = [message_id]") + var/datum/db_query/query_del_message = SSdbcore.NewQuery( + "UPDATE [format_table_name("messages")] SET deleted = 1, deleted_ckey = :deleted_ckey WHERE id = :id", + list("deleted_ckey" = deleted_by_ckey, "id" = message_id) + ) if(!query_del_message.warn_execute()) qdel(query_del_message) return @@ -132,16 +154,24 @@ /proc/edit_message(message_id, browse) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) return - var/editor_ckey = sanitizeSQL(usr.ckey) - var/editor_key = sanitizeSQL(usr.key) + var/editor_ckey = usr.ckey + var/editor_key = usr.key var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_find_edit_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), text FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/datum/db_query/query_find_edit_message = SSdbcore.NewQuery({" + SELECT + type, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), + text + FROM [format_table_name("messages")] + WHERE id = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_message.warn_execute()) qdel(query_find_edit_message) return @@ -154,9 +184,12 @@ if(!new_text) qdel(query_find_edit_message) return - new_text = sanitizeSQL(new_text) - var/edit_text = sanitizeSQL("Edited by [editor_key] on [SQLtime()] from
[old_text]
to
[new_text]
") - var/datum/DBQuery/query_edit_message = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET text = '[new_text]', lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0") + var/edit_text = "Edited by [editor_key] on [SQLtime()] from
[old_text]
to
[new_text]
" + var/datum/db_query/query_edit_message = SSdbcore.NewQuery({" + UPDATE [format_table_name("messages")] + SET text = :text, lasteditor = :lasteditor, edits = CONCAT(IFNULL(edits,''),:edit_text) + WHERE id = :id AND deleted = 0 + "}, list("text" = new_text, "lasteditor" = editor_ckey, "edit_text" = edit_text, "id" = message_id)) if(!query_edit_message.warn_execute()) qdel(query_edit_message) return @@ -171,16 +204,24 @@ /proc/edit_message_expiry(message_id, browse) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) return - var/editor_ckey = sanitizeSQL(usr.ckey) - var/editor_key = sanitizeSQL(usr.key) + var/editor_ckey = usr.ckey + var/editor_key = usr.key var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_find_edit_expiry_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), expire_timestamp FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/datum/db_query/query_find_edit_expiry_message = SSdbcore.NewQuery({" + SELECT + type, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + expire_timestamp + FROM [format_table_name("messages")] + WHERE id = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_expiry_message.warn_execute()) qdel(query_find_edit_expiry_message) return @@ -197,8 +238,9 @@ if(expire_time == "-1") new_expiry = "non-expiring" else - expire_time = sanitizeSQL(expire_time) - var/datum/DBQuery/query_validate_expire_time_edit = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[expire_time]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[expire_time]','%Y-%c-%d %T'), 0)") + var/datum/db_query/query_validate_expire_time_edit = SSdbcore.NewQuery({" + SELECT IF(STR_TO_DATE(:expire_time,'%Y-%c-%d %T') > NOW(), STR_TO_DATE(:expire_time,'%Y-%c-%d %T'), 0) + "}, list("expire_time" = expire_time)) if(!query_validate_expire_time_edit.warn_execute()) qdel(query_validate_expire_time_edit) qdel(query_find_edit_expiry_message) @@ -206,14 +248,18 @@ if(query_validate_expire_time_edit.NextRow()) var/checktime = text2num(query_validate_expire_time_edit.item[1]) if(!checktime) - to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.") + to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.", confidential = TRUE) qdel(query_validate_expire_time_edit) qdel(query_find_edit_expiry_message) return new_expiry = query_validate_expire_time_edit.item[1] qdel(query_validate_expire_time_edit) - var/edit_text = sanitizeSQL("Expiration time edited by [editor_key] on [SQLtime()] from [old_expiry] to [new_expiry]
") - var/datum/DBQuery/query_edit_message_expiry = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET expire_timestamp = [expire_time == "-1" ? "NULL" : "'[new_expiry]'"], lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0") + var/edit_text = "Expiration time edited by [editor_key] on [SQLtime()] from [old_expiry] to [new_expiry]
" + var/datum/db_query/query_edit_message_expiry = SSdbcore.NewQuery({" + UPDATE [format_table_name("messages")] + SET expire_timestamp = :expire_time, lasteditor = :lasteditor, edits = CONCAT(IFNULL(edits,''),:edit_text) + WHERE id = :id AND deleted = 0 + "}, list("expire_time" = (expire_time == "-1" ? null : new_expiry), "lasteditor" = editor_ckey, "edit_text" = edit_text, "id" = message_id)) if(!query_edit_message_expiry.warn_execute()) qdel(query_edit_message_expiry) qdel(query_find_edit_expiry_message) @@ -229,14 +275,22 @@ /proc/edit_message_severity(message_id) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) return var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_find_edit_note_severity = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), severity FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/datum/db_query/query_find_edit_note_severity = SSdbcore.NewQuery({" + SELECT + type, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + severity + FROM [format_table_name("messages")] + WHERE id = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_note_severity.warn_execute()) qdel(query_find_edit_note_severity) return @@ -247,15 +301,19 @@ var/old_severity = query_find_edit_note_severity.item[4] if(!old_severity) old_severity = "NA" - var/editor_key = sanitizeSQL(usr.key) - var/editor_ckey = sanitizeSQL(usr.ckey) + var/editor_key = usr.key + var/editor_ckey = usr.ckey var/new_severity = input("Set the severity of the note.", "Severity", null, null) as null|anything in list("high", "medium", "minor", "none") //lowercase for edit log consistency if(!new_severity) qdel(query_find_edit_note_severity) return - new_severity = sanitizeSQL(new_severity) - var/edit_text = sanitizeSQL("Note severity edited by [editor_key] on [SQLtime()] from [old_severity] to [new_severity]
") - var/datum/DBQuery/query_edit_note_severity = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET severity = '[new_severity]', lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0") + new_severity = new_severity + var/edit_text = "Note severity edited by [editor_key] on [SQLtime()] from [old_severity] to [new_severity]
" + var/datum/db_query/query_edit_note_severity = SSdbcore.NewQuery({" + UPDATE [format_table_name("messages")] + SET severity = :severity, lasteditor = :lasteditor, edits = CONCAT(IFNULL(edits,''),:edit_text) + WHERE id = :id AND deleted = 0 + "}, list("severity" = new_severity, "lasteditor" = editor_ckey, "edit_text" = edit_text, "id" = message_id)) if(!query_edit_note_severity.warn_execute(async = TRUE)) qdel(query_edit_note_severity) qdel(qdel(query_find_edit_note_severity)) @@ -268,16 +326,24 @@ /proc/toggle_message_secrecy(message_id) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) return - var/editor_ckey = sanitizeSQL(usr.ckey) - var/editor_key = sanitizeSQL(usr.key) + var/editor_ckey = usr.ckey + var/editor_key = usr.key var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_find_message_secret = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), secret FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/datum/db_query/query_find_message_secret = SSdbcore.NewQuery({" + SELECT + type, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), + secret + FROM [format_table_name("messages")] + WHERE id = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_message_secret.warn_execute()) qdel(query_find_message_secret) return @@ -287,7 +353,11 @@ var/admin_key = query_find_message_secret.item[3] var/secret = text2num(query_find_message_secret.item[4]) var/edit_text = "Made [secret ? "not secret" : "secret"] by [editor_key] on [SQLtime()]
" - var/datum/DBQuery/query_message_secret = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET secret = NOT secret, lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id]") + var/datum/db_query/query_message_secret = SSdbcore.NewQuery({" + UPDATE [format_table_name("messages")] + SET secret = NOT secret, lasteditor = :lasteditor, edits = CONCAT(IFNULL(edits,''),:edit_text) + WHERE id = :id + "}, list("lasteditor" = editor_ckey, "edit_text" = edit_text, "id" = message_id)) if(!query_message_secret.warn_execute()) qdel(query_find_message_secret) qdel(query_message_secret) @@ -298,11 +368,9 @@ browse_messages(target_ckey = ckey(target_key), agegate = TRUE) qdel(query_find_message_secret) -/proc/browse_messages(type, target_ckey, index, linkless = FALSE, filter, agegate = FALSE, override = FALSE) - if((!override || IsAdminAdvancedProcCall()) && !check_rights(R_SENSITIVE)) - return +/proc/browse_messages(type, target_ckey, index, linkless = FALSE, filter, agegate = FALSE) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return var/list/output = list() var/ruler = "
" @@ -329,7 +397,20 @@ else output += "Filter offline clients" output += ruler - var/datum/DBQuery/query_get_type_messages = SSdbcore.NewQuery("SELECT id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), targetckey, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, server, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), expire_timestamp FROM [format_table_name("messages")] WHERE type = '[type]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL)") + var/datum/db_query/query_get_type_messages = SSdbcore.NewQuery({" + SELECT + id, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + targetckey, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + text, + timestamp, + server, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), + expire_timestamp + FROM [format_table_name("messages")] + WHERE type = :type AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) + "}, list("type" = type)) if(!query_get_type_messages.warn_execute()) qdel(query_get_type_messages) return @@ -362,9 +443,24 @@ output += "
[text]
" qdel(query_get_type_messages) if(target_ckey) - target_ckey = sanitizeSQL(target_ckey) var/target_key - var/datum/DBQuery/query_get_messages = SSdbcore.NewQuery("SELECT type, secret, id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, server, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), DATEDIFF(NOW(), timestamp), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), expire_timestamp, severity FROM [format_table_name("messages")] WHERE type <> 'memo' AND targetckey = '[target_ckey]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) ORDER BY timestamp DESC") + var/datum/db_query/query_get_messages = SSdbcore.NewQuery({" + SELECT + type, + secret, + id, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + text, + timestamp, + server, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), + DATEDIFF(NOW(), timestamp), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + expire_timestamp, severity + FROM [format_table_name("messages")] + WHERE type <> 'memo' AND targetckey = :targetckey AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) + ORDER BY timestamp DESC + "}, list("targetckey" = target_ckey)) if(!query_get_messages.warn_execute()) qdel(query_get_messages) return @@ -442,7 +538,9 @@ notedata += data qdel(query_get_messages) if(!target_key) - var/datum/DBQuery/query_get_message_key = SSdbcore.NewQuery("SELECT byond_key FROM [format_table_name("player")] WHERE ckey = '[target_ckey]'") + var/datum/db_query/query_get_message_key = SSdbcore.NewQuery({" + SELECT byond_key FROM [format_table_name("player")] WHERE ckey = :ckey + "}, list("ckey" = target_ckey)) if(!query_get_message_key.warn_execute()) qdel(query_get_message_key) return @@ -479,8 +577,6 @@ var/search output += "
Add messageAdd watchlist entryAdd note
" output += ruler - if(!isnum(index)) - index = sanitizeSQL(index) switch(index) if(1) search = "^." @@ -488,7 +584,17 @@ search = "^\[^\[:alpha:\]\]" else search = "^[index]" - var/datum/DBQuery/query_list_messages = SSdbcore.NewQuery("SELECT DISTINCT targetckey, (SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey) FROM [format_table_name("messages")] WHERE type <> 'memo' AND targetckey REGEXP '[search]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) ORDER BY targetckey") + var/datum/db_query/query_list_messages = SSdbcore.NewQuery({" + SELECT DISTINCT + targetckey, + (SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey) + FROM [format_table_name("messages")] + WHERE type <> 'memo' + AND targetckey REGEXP :search + AND deleted = 0 + AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) + ORDER BY targetckey + "}, list("search" = search)) if(!query_list_messages.warn_execute()) qdel(query_list_messages) return @@ -512,17 +618,24 @@ /proc/get_message_output(type, target_ckey) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return if(!type) return var/output - if(target_ckey) - target_ckey = sanitizeSQL(target_ckey) - var/query = "SELECT id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor) FROM [format_table_name("messages")] WHERE type = '[type]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL)" - if(type == "message" || type == "watchlist entry") - query += " AND targetckey = '[target_ckey]'" - var/datum/DBQuery/query_get_message_output = SSdbcore.NewQuery(query) + var/datum/db_query/query_get_message_output = SSdbcore.NewQuery({" + SELECT + id, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + text, + timestamp, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor) + FROM [format_table_name("messages")] + WHERE type = :type + AND deleted = 0 + AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) + AND ((type != 'message' AND type != 'watchlist entry') OR targetckey = :targetckey) + "}, list("targetckey" = target_ckey, "type" = type)) if(!query_get_message_output.warn_execute()) qdel(query_get_message_output) return @@ -536,7 +649,10 @@ if("message") output += "Admin message left by [admin_key] on [timestamp]" output += "
[text]
" - var/datum/DBQuery/query_message_read = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET type = 'message sent' WHERE id = [message_id]") + var/datum/db_query/query_message_read = SSdbcore.NewQuery( + "UPDATE [format_table_name("messages")] SET type = 'message sent' WHERE id = :id", + list("id" = message_id) + ) if(!query_message_read.warn_execute()) qdel(query_get_message_output) qdel(query_message_read) @@ -544,7 +660,7 @@ qdel(query_message_read) if("watchlist entry") message_admins("Notice: [key_name_admin(target_ckey)] has been on the watchlist since [timestamp] and has just connected - Reason: [text]") - send2irc_adminless_only("Watchlist", "[key_name(target_ckey)] is on the watchlist and has just connected - Reason: [text]") + send2tgs_adminless_only("Watchlist", "[key_name(target_ckey)] is on the watchlist and has just connected - Reason: [text]") if("memo") output += "Memo by [admin_key] on [timestamp]" if(editor_key) @@ -576,7 +692,7 @@ var/timestamp = note.group[1] notetext = note.group[2] var/admin_ckey = note.group[3] - var/datum/DBQuery/query_convert_time = SSdbcore.NewQuery("SELECT ADDTIME(STR_TO_DATE('[timestamp]','%d-%b-%Y'), '0')") + var/datum/db_query/query_convert_time = SSdbcore.NewQuery("SELECT ADDTIME(STR_TO_DATE(:timestamp,'%d-%b-%Y'), '0')", list("timestamp" = timestamp)) if(!query_convert_time.Execute()) qdel(query_convert_time) return @@ -591,7 +707,7 @@ /*alternatively this proc can be run once to pass through every note and attempt to convert it before deleting the file, if done then AUTOCONVERT_NOTES should be turned off this proc can take several minutes to execute fully if converting and cause DD to hang if converting a lot of notes; it's not advised to do so while a server is live /proc/mass_convert_notes() - to_chat(world, "Beginning mass note conversion") + to_chat(world, "Beginning mass note conversion", confidential = TRUE) var/savefile/notesfile = new(NOTESFILE) if(!notesfile) log_game("Error: Cannot access [NOTESFILE]") @@ -599,7 +715,7 @@ this proc can take several minutes to execute fully if converting and cause DD t notesfile.cd = "/" for(var/ckey in notesfile.dir) convert_notes_sql(ckey) - to_chat(world, "Deleting NOTESFILE") + to_chat(world, "Deleting NOTESFILE", confidential = TRUE) fdel(NOTESFILE) - to_chat(world, "Finished mass note conversion, remember to turn off AUTOCONVERT_NOTES")*/ + to_chat(world, "Finished mass note conversion, remember to turn off AUTOCONVERT_NOTES", confidential = TRUE)*/ #undef NOTESFILE diff --git a/code/modules/admin/stickyban.dm b/code/modules/admin/stickyban.dm index ef0bfe8e70..57487f3144 100644 --- a/code/modules/admin/stickyban.dm +++ b/code/modules/admin/stickyban.dm @@ -32,15 +32,11 @@ return ban["message"] = "[reason]" - if(SSdbcore.Connect()) // todo: second wave - // var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery({" - // INSERT INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) - // VALUES (:ckey, :message, :banning_admin) - // "}, list("ckey" = ckey, "message" = ban["message"], "banning_admin" = usr.ckey)) - var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery({" + if(SSdbcore.Connect()) + var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery({" INSERT INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) - VALUES ([ckey], [ban["message"]], [usr.ckey]) - "}) + VALUES (:ckey, :message, :banning_admin) + "}, list("ckey" = ckey, "message" = ban["message"], "banning_admin" = usr.ckey)) if (query_create_stickyban.warn_execute()) ban["fromdb"] = TRUE qdel(query_create_stickyban) @@ -74,19 +70,14 @@ SSstickyban.cache -= ckey if (SSdbcore.Connect()) - // SSdbcore.QuerySelect(list( - // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = :ckey", list("ckey" = ckey)), - // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey", list("ckey" = ckey)), - // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = :ckey", list("ckey" = ckey)), - // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = :ckey", list("ckey" = ckey)) - // ), warn = TRUE, qdel = TRUE) SSdbcore.QuerySelect(list( - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = [ckey]"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = [ckey]"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = [ckey]"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = [ckey]") + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = :ckey", list("ckey" = ckey)), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey", list("ckey" = ckey)), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = :ckey", list("ckey" = ckey)), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = :ckey", list("ckey" = ckey)) ), warn = TRUE, qdel = TRUE) + log_admin_private("[key_name(usr)] removed [ckey]'s stickyban") message_admins("[key_name_admin(usr)] removed [ckey]'s stickyban") @@ -128,12 +119,9 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - // var/datum/db_query/query_remove_stickyban_alt = SSdbcore.NewQuery( - // "DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey AND matched_ckey = :alt", - // list("ckey" = ckey, "alt" = alt) - // ) - var/datum/DBQuery/query_remove_stickyban_alt = SSdbcore.NewQuery( - "DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = [ckey] AND matched_ckey = [alt]" + var/datum/db_query/query_remove_stickyban_alt = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey AND matched_ckey = :alt", + list("ckey" = ckey, "alt" = alt) ) query_remove_stickyban_alt.warn_execute() qdel(query_remove_stickyban_alt) @@ -165,12 +153,9 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - // var/datum/db_query/query_edit_stickyban = SSdbcore.NewQuery( - // "UPDATE [format_table_name("stickyban")] SET reason = :reason WHERE ckey = :ckey", - // list("reason" = reason, "ckey" = ckey) - // ) - var/datum/DBQuery/query_edit_stickyban = SSdbcore.NewQuery( - "UPDATE [format_table_name("stickyban")] SET reason = [reason] WHERE ckey = [ckey]" + var/datum/db_query/query_edit_stickyban = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban")] SET reason = :reason WHERE ckey = :ckey", + list("reason" = reason, "ckey" = ckey) ) query_edit_stickyban.warn_execute() qdel(query_edit_stickyban) @@ -218,12 +203,9 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - // var/datum/db_query/query_exempt_stickyban_alt = SSdbcore.NewQuery( - // "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = :ckey AND matched_ckey = :alt", - // list("ckey" = ckey, "alt" = alt) - // ) - var/datum/DBQuery/query_exempt_stickyban_alt = SSdbcore.NewQuery( - "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = [ckey] AND matched_ckey = [alt]" + var/datum/db_query/query_exempt_stickyban_alt = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = :ckey AND matched_ckey = :alt", + list("ckey" = ckey, "alt" = alt) ) query_exempt_stickyban_alt.warn_execute() qdel(query_exempt_stickyban_alt) @@ -271,12 +253,9 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - // var/datum/db_query/query_unexempt_stickyban_alt = SSdbcore.NewQuery( - // "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = :ckey AND matched_ckey = :alt", - // list("ckey" = ckey, "alt" = alt) - // ) - var/datum/DBQuery/query_unexempt_stickyban_alt = SSdbcore.NewQuery( - "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = [ckey] AND matched_ckey = [alt]" + var/datum/db_query/query_unexempt_stickyban_alt = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = :ckey AND matched_ckey = :alt", + list("ckey" = ckey, "alt" = alt) ) query_unexempt_stickyban_alt.warn_execute() qdel(query_unexempt_stickyban_alt) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 2efc04d5cc..cd29da186c 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1269,8 +1269,10 @@ else if(href_list["messageedits"]) if(!check_rights(R_ADMIN)) return - var/message_id = sanitizeSQL("[href_list["messageedits"]]") - var/datum/DBQuery/query_get_message_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("messages")] WHERE id = '[message_id]'") + var/datum/db_query/query_get_message_edits = SSdbcore.NewQuery( + "SELECT edits FROM [format_table_name("messages")] WHERE id = :message_id", + list("message_id" = href_list["messageedits"]) + ) if(!query_get_message_edits.warn_execute()) qdel(query_get_message_edits) return @@ -2967,16 +2969,19 @@ to_chat(usr, "The client chosen is an admin! Cannot mentorize.") return if(SSdbcore.Connect()) - var/datum/DBQuery/query_get_mentor = SSdbcore.NewQuery("SELECT id FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") + var/datum/db_query/query_get_mentor = SSdbcore.NewQuery( + "SELECT id FROM [format_table_name("mentor")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!query_get_mentor.warn_execute()) return if(query_get_mentor.NextRow()) to_chat(usr, "[ckey] is already a mentor.") return - var/datum/DBQuery/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')") + var/datum/db_query/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')") if(!query_add_mentor.warn_execute()) return - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');") + var/datum/db_query/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');") if(!query_add_admin_log.warn_execute()) return else @@ -3000,10 +3005,10 @@ C.mentor_datum = null GLOB.mentors -= C if(SSdbcore.Connect()) - var/datum/DBQuery/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") + var/datum/db_query/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") if(!query_remove_mentor.warn_execute()) return - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed mentor [ckey]');") + var/datum/db_query/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed mentor [ckey]');") if(!query_add_admin_log.warn_execute()) return else diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index efcd529ddd..e5ec2547e5 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -205,7 +205,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) MessageNoRecipient(msg) //send it to irc if nobody is on and tell us how many were on - var/admin_number_present = send2irc_adminless_only(initiator_ckey, "Ticket #[id]: [name]") + var/admin_number_present = send2tgs_adminless_only(initiator_ckey, "Ticket #[id]: [name]") log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") if(admin_number_present <= 0) to_chat(C, "No active admins are online, your adminhelp was sent to the admin irc.") @@ -222,7 +222,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) /datum/admin_help/proc/AddInteraction(formatted_message) if(heard_by_no_admins && usr && usr.ckey != initiator_ckey) heard_by_no_admins = FALSE - send2irc(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") + send2adminchat(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") _interactions += "[TIME_STAMP("hh:mm:ss", FALSE)]: [formatted_message]" //Removes the ahelp verb and returns it after 2 minutes @@ -573,7 +573,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) . = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list()) for(var/client/X in GLOB.admins) .["total"] += X - if(requiredflags != 0 && !check_rights_for(X, requiredflags)) + if(requiredflags != NONE && !check_rights_for(X, requiredflags)) .["noflags"] += X else if(X.is_afk()) .["afk"] += X @@ -582,7 +582,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) else .["present"] += X -/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN) +/proc/send2tgs_adminless_only(source, msg, requiredflags = R_BAN) var/list/adm = get_admin_counts(requiredflags) var/list/activemins = adm["present"] . = activemins.len @@ -596,15 +596,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) final = "[msg] - No admins online" else final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] " - send2irc(source,final) + send2adminchat(source,final) send2otherserver(source,final) - -/proc/send2irc(msg,msg2) - msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "") - msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "") - world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE) - /** * Sends a message to a set of cross-communications-enabled servers using world topic calls * @@ -648,6 +642,10 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) if (!server_url) CRASH("Invalid cross comms config: [server_name]") world.Export("[server_url]?[list2params(message)]") +<<<<<<< HEAD +======= + +>>>>>>> origin/master /proc/ircadminwho() var/list/message = list("Admins: ") diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index ab0b0d933a..3a708ef182 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -165,7 +165,7 @@ to_chat(src, "PM to-Admins: [rawmsg]", confidential = TRUE) var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to External: [keywordparsedmsg]") ircreplyamount-- - send2irc("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg) + send2adminchat("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg) else var/badmin = FALSE //Lets figure out if an admin is getting bwoinked. diff --git a/code/modules/admin/verbs/panicbunker.dm b/code/modules/admin/verbs/panicbunker.dm index daaf15c70f..92bc840c5a 100644 --- a/code/modules/admin/verbs/panicbunker.dm +++ b/code/modules/admin/verbs/panicbunker.dm @@ -13,7 +13,7 @@ if (new_pb && !SSdbcore.Connect()) message_admins("The Database is not connected! Panic bunker will not work until the connection is reestablished.") SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Panic Bunker", "[new_pb ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - send2irc("Panic Bunker", "[key_name(usr)] has toggled the Panic Bunker, it is now [new_pb ? "enabled" : "disabled"].") + send2adminchat("Panic Bunker", "[key_name(usr)] has toggled the Panic Bunker, it is now [new_pb ? "enabled" : "disabled"].") /client/proc/addbunkerbypass(ckeytobypass as text) set category = "Special Verbs" @@ -28,7 +28,7 @@ SSpersistence.SavePanicBunker() //we can do this every time, it's okay log_admin("[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.") message_admins("[key_name_admin(usr)] has added [ckeytobypass] to the current round's bunker bypass list.") - send2irc("Panic Bunker", "[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.") + send2adminchat("Panic Bunker", "[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.") /client/proc/revokebunkerbypass(ckeytobypass as text) set category = "Special Verbs" @@ -42,4 +42,4 @@ SSpersistence.SavePanicBunker() log_admin("[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.") message_admins("[key_name_admin(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.") - send2irc("Panic Bunker", "[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.") + send2adminchat("Panic Bunker", "[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.") diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index ba4578add9..9c0b19cd83 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -433,31 +433,21 @@ destroy_objective.find_target() objectives += destroy_objective else - if(prob(70)) - var/datum/objective/assassinate/once/kill_objective = new - kill_objective.owner = owner - if(team_mode) //No backstabbing while in a team - kill_objective.find_target_by_role(role = ROLE_CHANGELING, role_type = 1, invert = 1) - else - kill_objective.find_target() - objectives += kill_objective - - /*else - var/datum/objective/maroon/maroon_objective = new - maroon_objective.owner = owner - if(team_mode) - maroon_objective.find_target_by_role(role = ROLE_CHANGELING, role_type = 1, invert = 1) - else - maroon_objective.find_target() - objectives += maroon_objective*/ + var/datum/objective/assassinate/once/kill_objective = new + kill_objective.owner = owner + if(team_mode) //No backstabbing while in a team + kill_objective.find_target_by_role(role = ROLE_CHANGELING, role_type = 1, invert = 1) + else + kill_objective.find_target() + objectives += kill_objective - if (!(locate(/datum/objective/escape) in objectives) && escape_objective_possible) - var/datum/objective/escape/escape_with_identity/identity_theft = new - identity_theft.owner = owner - identity_theft.target = kill_objective.target - identity_theft.update_explanation_text() - objectives += identity_theft - escape_objective_possible = FALSE + if(!(locate(/datum/objective/escape) in objectives) && escape_objective_possible && prob(50)) + var/datum/objective/escape/escape_with_identity/identity_theft = new + identity_theft.owner = owner + identity_theft.target = kill_objective.target + identity_theft.update_explanation_text() + objectives += identity_theft + escape_objective_possible = FALSE if (!(locate(/datum/objective/escape) in objectives) && escape_objective_possible) if(prob(50)) diff --git a/code/modules/antagonists/disease/disease_abilities.dm b/code/modules/antagonists/disease/disease_abilities.dm index 496d11bcbc..fc53575bd8 100644 --- a/code/modules/antagonists/disease/disease_abilities.dm +++ b/code/modules/antagonists/disease/disease_abilities.dm @@ -191,8 +191,6 @@ new /datum/disease_ability/symptom/powerful/youth /datum/disease_ability/action/sneeze name = "Voluntary Sneezing" actions = list(/datum/action/cooldown/disease_sneeze) - cost = 2 - required_total_points = 3 short_desc = "Force the host you are following to sneeze, spreading your infection to those in front of them." long_desc = "Force the host you are following to sneeze with extra force, spreading your infection to any victims in a 4 meter cone in front of your host.
Cooldown: 20 seconds" @@ -229,8 +227,6 @@ new /datum/disease_ability/symptom/powerful/youth /datum/disease_ability/action/infect name = "Secrete Infection" actions = list(/datum/action/cooldown/disease_infect) - cost = 2 - required_total_points = 3 short_desc = "Cause all objects your host is touching to become infectious for a limited time, spreading your infection to anyone who touches them." long_desc = "Cause the host you are following to excrete an infective substance from their pores, causing all objects touching their skin to transmit your infection to anyone who touches them for the next 30 seconds. This includes the floor, if they are not wearing shoes, and any items they are holding, if they are not wearing gloves.
Cooldown: 40 seconds" @@ -271,23 +267,20 @@ new /datum/disease_ability/symptom/powerful/youth //healing costs more so you have to techswitch from naughty disease otherwise we'd have friendly disease for easy greentext (no fun!) /datum/disease_ability/symptom/mild - cost = 2 - required_total_points = 4 category = "Symptom (Weak)" /datum/disease_ability/symptom/medium - cost = 4 - required_total_points = 8 category = "Symptom" /datum/disease_ability/symptom/medium/heal cost = 5 + required_total_points = 5 malefit = -1 category = "Symptom (+)" /datum/disease_ability/symptom/powerful cost = 4 - required_total_points = 16 + required_total_points = 10 category = "Symptom (Strong)" /datum/disease_ability/symptom/powerful/heal diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm index ba79cca07a..bb95a8bdf3 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm @@ -566,7 +566,7 @@ human_user.adjustBruteLoss(-10, FALSE) human_user.adjustFireLoss(-10, FALSE) human_user.adjustStaminaLoss(-10, FALSE) - human_user.adjustToxLoss(-10, FALSE) + human_user.adjustToxLoss(-10, FALSE, TRUE) human_user.adjustOxyLoss(-10) /obj/effect/proc_holder/spell/pointed/manse_link diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm index 7b4152872b..5dc42855e5 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm @@ -184,7 +184,7 @@ var/mob/living/carbon/human/human_user = user human_user.adjustBruteLoss(-6, FALSE) human_user.adjustFireLoss(-6, FALSE) - human_user.adjustToxLoss(-6, FALSE) + human_user.adjustToxLoss(-6, FALSE, TRUE) human_user.adjustOxyLoss(-6, FALSE) human_user.adjustStaminaLoss(-20) diff --git a/code/modules/antagonists/traitor/classes/subterfuge.dm b/code/modules/antagonists/traitor/classes/subterfuge.dm index ea073f38c1..73dc455a45 100644 --- a/code/modules/antagonists/traitor/classes/subterfuge.dm +++ b/code/modules/antagonists/traitor/classes/subterfuge.dm @@ -12,11 +12,10 @@ mode = SSticker.mode assassin_prob = max(0,mode.threat_level-40) if(prob(assassin_prob)) - if(prob(assassin_prob)) - var/datum/objective/assassinate/once/kill_objective = new - kill_objective.owner = T.owner - kill_objective.find_target() - T.add_objective(kill_objective) + var/datum/objective/assassinate/once/kill_objective = new + kill_objective.owner = T.owner + kill_objective.find_target() + T.add_objective(kill_objective) else var/list/weights = list() weights["sabo"] = length(subtypesof(/datum/sabotage_objective)) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 7f073567c5..743931fbdd 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -256,6 +256,8 @@ /datum/gas_reaction/fusion/react(datum/gas_mixture/air, datum/holder) var/turf/open/location + if (isopenturf(holder)) + return if (istype(holder,/datum/pipeline)) //Find the tile the reaction is occuring on, or a random part of the network if it's a pipenet. var/datum/pipeline/fusion_pipenet = holder location = get_turf(pick(fusion_pipenet.members)) diff --git a/code/modules/bsql/LICENSE b/code/modules/bsql/LICENSE deleted file mode 100644 index 2bee290914..0000000000 --- a/code/modules/bsql/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright 2018 Jordan Brown - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/code/modules/bsql/core/connection.dm b/code/modules/bsql/core/connection.dm deleted file mode 100644 index fb8f729390..0000000000 --- a/code/modules/bsql/core/connection.dm +++ /dev/null @@ -1,68 +0,0 @@ -/datum/BSQL_Connection - var/id - var/connection_type - -BSQL_PROTECT_DATUM(/datum/BSQL_Connection) - -/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit) - if(asyncTimeout == null) - asyncTimeout = BSQL_DEFAULT_TIMEOUT - if(blockingTimeout == null) - blockingTimeout = asyncTimeout - if(threadLimit == null) - threadLimit = BSQL_DEFAULT_THREAD_LIMIT - - src.connection_type = connection_type - - world._BSQL_InitCheck(src) - - var/error = world._BSQL_Internal_Call("CreateConnection", connection_type, "[asyncTimeout]", "[blockingTimeout]", "[threadLimit]") - if(error) - BSQL_ERROR(error) - return - - id = world._BSQL_Internal_Call("GetConnection") - if(!id) - BSQL_ERROR("BSQL library failed to provide connect operation for connection id [id]([connection_type])!") - -BSQL_DEL_PROC(/datum/BSQL_Connection) - var/error - if(id) - error = world._BSQL_Internal_Call("ReleaseConnection", id) - . = ..() - if(error) - BSQL_ERROR(error) - -/datum/BSQL_Connection/BeginConnect(ipaddress, port, username, password, database) - var/error = world._BSQL_Internal_Call("OpenConnection", id, ipaddress, "[port]", username, password, database) - if(error) - BSQL_ERROR(error) - return - - var/op_id = world._BSQL_Internal_Call("GetOperation") - if(!op_id) - BSQL_ERROR("Library failed to provide connect operation for connection id [id]([connection_type])!") - return - - return new /datum/BSQL_Operation(src, op_id) - - -/datum/BSQL_Connection/BeginQuery(query) - var/error = world._BSQL_Internal_Call("NewQuery", id, query) - if(error) - BSQL_ERROR(error) - return - - var/op_id = world._BSQL_Internal_Call("GetOperation") - if(!op_id) - BSQL_ERROR("Library failed to provide query operation for connection id [id]([connection_type])!") - return - - return new /datum/BSQL_Operation/Query(src, op_id) - -/datum/BSQL_Connection/Quote(str) - if(!str) - return null; - . = world._BSQL_Internal_Call("QuoteString", id, "[str]") - if(!.) - BSQL_ERROR("Library failed to provide quote for [str]!") diff --git a/code/modules/bsql/core/library.dm b/code/modules/bsql/core/library.dm deleted file mode 100644 index 9b58ba314b..0000000000 --- a/code/modules/bsql/core/library.dm +++ /dev/null @@ -1,43 +0,0 @@ -/world/proc/_BSQL_Internal_Call(func, ...) - var/list/call_args = args.Copy(2) - BSQL_Debug("_BSQL_Internal_Call(): [args[1]]([call_args.Join(", ")])") - . = call(_BSQL_Library_Path(), func)(arglist(call_args)) - BSQL_Debug("Result: [. == null ? "NULL" : "\"[.]\""]") - -/world/proc/_BSQL_Library_Path() - return system_type == MS_WINDOWS ? "BSQL.dll" : "libBSQL.so" - -/world/proc/_BSQL_InitCheck(datum/BSQL_Connection/caller) - var/static/library_initialized = FALSE - if(_BSQL_Initialized()) - return - var/libPath = _BSQL_Library_Path() - if(!fexists(libPath)) - BSQL_DEL_CALL(caller) - BSQL_ERROR("Could not find [libPath]!") - return - - var/version = _BSQL_Internal_Call("Version") - if(version != BSQL_VERSION) - BSQL_DEL_CALL(caller) - BSQL_ERROR("BSQL DMAPI version mismatch! Expected [BSQL_VERSION], got [version == null ? "NULL" : version]!") - return - - var/result = _BSQL_Internal_Call("Initialize") - if(result) - BSQL_DEL_CALL(caller) - BSQL_ERROR(result) - return - _BSQL_Initialized(TRUE) - -/world/proc/_BSQL_Initialized(new_val) - var/static/bsql_library_initialized = FALSE - if(new_val != null) - bsql_library_initialized = new_val - return bsql_library_initialized - -/world/BSQL_Shutdown() - if(!_BSQL_Initialized()) - return - _BSQL_Internal_Call("Shutdown") - _BSQL_Initialized(FALSE) diff --git a/code/modules/bsql/core/operation.dm b/code/modules/bsql/core/operation.dm deleted file mode 100644 index a2cdbbe1ee..0000000000 --- a/code/modules/bsql/core/operation.dm +++ /dev/null @@ -1,47 +0,0 @@ -/datum/BSQL_Operation - var/datum/BSQL_Connection/connection - var/id - -BSQL_PROTECT_DATUM(/datum/BSQL_Operation) - -/datum/BSQL_Operation/New(datum/BSQL_Connection/connection, id) - src.connection = connection - src.id = id - -BSQL_DEL_PROC(/datum/BSQL_Operation) - var/error - if(!BSQL_IS_DELETED(connection)) - error = world._BSQL_Internal_Call("ReleaseOperation", connection.id, id) - . = ..() - if(error) - BSQL_ERROR(error) - -/datum/BSQL_Operation/IsComplete() - if(BSQL_IS_DELETED(connection)) - return TRUE - var/result = world._BSQL_Internal_Call("OpComplete", connection.id, id) - if(!result) - BSQL_ERROR("Error fetching operation [id] for connection [connection.id]!") - return - return result == "DONE" - -/datum/BSQL_Operation/GetError() - if(BSQL_IS_DELETED(connection)) - return "Connection deleted!" - return world._BSQL_Internal_Call("GetError", connection.id, id) - -/datum/BSQL_Operation/GetErrorCode() - if(BSQL_IS_DELETED(connection)) - return -2 - return text2num(world._BSQL_Internal_Call("GetErrorCode", connection.id, id)) - -/datum/BSQL_Operation/WaitForCompletion() - if(BSQL_IS_DELETED(connection)) - return - var/error = world._BSQL_Internal_Call("BlockOnOperation", connection.id, id) - if(error) - if(error == "Operation timed out!") //match this with the implementation - return FALSE - BSQL_ERROR("Error waiting for operation [id] for connection [connection.id]! [error]") - return - return TRUE diff --git a/code/modules/bsql/core/query.dm b/code/modules/bsql/core/query.dm deleted file mode 100644 index fc09fb06b0..0000000000 --- a/code/modules/bsql/core/query.dm +++ /dev/null @@ -1,35 +0,0 @@ -/datum/BSQL_Operation/Query - var/last_result_json - var/list/last_result - -BSQL_PROTECT_DATUM(/datum/BSQL_Operation/Query) - -/datum/BSQL_Operation/Query/CurrentRow() - return last_result - -/datum/BSQL_Operation/Query/IsComplete() - //whole different ballgame here - if(BSQL_IS_DELETED(connection)) - return TRUE - var/result = world._BSQL_Internal_Call("ReadyRow", connection.id, id) - switch(result) - if("DONE") - //load the data - LoadQueryResult() - return TRUE - if("NOTDONE") - return FALSE - else - BSQL_ERROR(result) - -/datum/BSQL_Operation/Query/WaitForCompletion() - . = ..() - if(.) - LoadQueryResult() - -/datum/BSQL_Operation/Query/proc/LoadQueryResult() - last_result_json = world._BSQL_Internal_Call("GetRow", connection.id, id) - if(last_result_json) - last_result = json_decode(last_result_json) - else - last_result = null diff --git a/code/modules/bsql/includes.dm b/code/modules/bsql/includes.dm deleted file mode 100644 index d05dcb6451..0000000000 --- a/code/modules/bsql/includes.dm +++ /dev/null @@ -1,4 +0,0 @@ -#include "core\connection.dm" -#include "core\library.dm" -#include "core\operation.dm" -#include "core\query.dm" diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm index 9c15e75cd6..a158f0f1c1 100644 --- a/code/modules/cargo/packs/misc.dm +++ b/code/modules/cargo/packs/misc.dm @@ -42,18 +42,21 @@ /datum/supply_pack/misc/book_crate name = "Book Crate" - desc = "Surplus from the Nanotrasen Archives, these five books are sure to be good reads." + desc = "Surplus from the Nanotrasen Archives, these seven books are sure to be good reads." + // cost = CARGO_CRATE_VALUE * 3 cost = 1500 contains = list(/obj/item/book/codex_gigas, /obj/item/book/manual/random/, /obj/item/book/manual/random/, /obj/item/book/manual/random/, - /obj/item/book/random/triple) + /obj/item/book/random, + /obj/item/book/random, + /obj/item/book/random) crate_type = /obj/structure/closet/crate/wooden /datum/supply_pack/misc/paper name = "Bureaucracy Crate" - desc = "High stacks of papers on your desk Are a big problem - make it Pea-sized with these bureaucratic supplies! Contains five pens, some camera film, hand labeler supplies, a paper bin, three folders, two clipboards and two stamps as well as a briefcase."//that was too forced + desc = "High stacks of papers on your desk Are a big problem - make it Pea-sized with these bureaucratic supplies! Contains six pens, some camera film, hand labeler supplies, a paper bin, a carbon paper bin, three folders, a laser pointer, two clipboards and two stamps."//that was too forced cost = 1500 contains = list(/obj/structure/filingcabinet/chestdrawer/wheeled, /obj/item/camera_film, @@ -61,9 +64,11 @@ /obj/item/hand_labeler_refill, /obj/item/hand_labeler_refill, /obj/item/paper_bin, + /obj/item/paper_bin/carbon, /obj/item/pen/fourcolor, /obj/item/pen/fourcolor, /obj/item/pen, + /obj/item/pen/fountain, /obj/item/pen/blue, /obj/item/pen/red, /obj/item/folder/blue, @@ -73,7 +78,7 @@ /obj/item/clipboard, /obj/item/stamp, /obj/item/stamp/denied, - /obj/item/storage/briefcase) + /obj/item/laser_pointer/purple) crate_name = "bureaucracy crate" /datum/supply_pack/misc/captain_pen @@ -94,6 +99,30 @@ crate_type = /obj/structure/closet/crate/wooden crate_name = "calligraphy crate" +/datum/supply_pack/misc/toner + name = "Toner Crate" + desc = "Spent too much ink printing butt pictures? Fret not, with these six toner refills, you'll be printing butts 'till the cows come home!'" + cost = 200 * 4 + contains = list(/obj/item/toner, + /obj/item/toner, + /obj/item/toner, + /obj/item/toner, + /obj/item/toner, + /obj/item/toner) + crate_name = "toner crate" + +/datum/supply_pack/misc/toner_large + name = "Toner Crate (Large)" + desc = "Tired of changing toner cartridges? These six extra heavy duty refills contain roughly five times as much toner as the base model!" + cost = 200 * 6 + contains = list(/obj/item/toner/large, + /obj/item/toner/large, + /obj/item/toner/large, + /obj/item/toner/large, + /obj/item/toner/large, + /obj/item/toner/large) + crate_name = "large toner crate" + ////////////////////////////////////////////////////////////////////////////// //////////////////////////////// Entertainment /////////////////////////////// ////////////////////////////////////////////////////////////////////////////// diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 7c286c7985..8add693d48 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -419,7 +419,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if (nnpa >= 0) message_admins("New user: [key_name_admin(src)] is connecting here for the first time.") if (CONFIG_GET(flag/irc_first_connection_alert)) - send2irc_adminless_only("New-user", "[key_name(src)] is connecting for the first time!") + send2tgs_adminless_only("New-user", "[key_name(src)] is connecting for the first time!") else if (isnum(cached_player_age) && cached_player_age < nnpa) message_admins("New user: [key_name_admin(src)] just connected with an age of [cached_player_age] day[(player_age==1?"":"s")]") if(CONFIG_GET(flag/use_account_age_for_jobs) && account_age >= 0) @@ -427,7 +427,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(account_age >= 0 && account_age < nnpa) message_admins("[key_name_admin(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].") if (CONFIG_GET(flag/irc_first_connection_alert)) - send2irc_adminless_only("new_byond_user", "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].") + send2tgs_adminless_only("new_byond_user", "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].") get_message_output("watchlist entry", ckey) check_ip_intel() validate_key_in_db() @@ -523,7 +523,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( "Forever alone :("\ ) - send2irc("Server", "[cheesy_message] (No admins online)") + send2adminchat("Server", "[cheesy_message] (No admins online)") QDEL_LIST_ASSOC_VAL(char_render_holders) if(movingmob != null) movingmob.client_mobs_in_contents -= mob @@ -538,14 +538,21 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( return if(!SSdbcore.Connect()) return - var/sql_ckey = sanitizeSQL(src.ckey) - var/datum/DBQuery/query_get_related_ip = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ip = INET_ATON('[address]') AND ckey != '[sql_ckey]'") - query_get_related_ip.Execute() + var/datum/db_query/query_get_related_ip = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("player")] WHERE ip = INET_ATON(:address) AND ckey != :ckey", + list("address" = address, "ckey" = ckey) + ) + if(!query_get_related_ip.Execute()) + qdel(query_get_related_ip) + return related_accounts_ip = "" while(query_get_related_ip.NextRow()) related_accounts_ip += "[query_get_related_ip.item[1]], " qdel(query_get_related_ip) - var/datum/DBQuery/query_get_related_cid = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE computerid = '[computer_id]' AND ckey != '[sql_ckey]'") + var/datum/db_query/query_get_related_cid = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("player")] WHERE computerid = :computerid AND ckey != :ckey", + list("computerid" = computer_id, "ckey" = ckey) + ) if(!query_get_related_cid.Execute()) qdel(query_get_related_cid) return @@ -559,45 +566,40 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( else if (!GLOB.deadmins[ckey] && check_randomizer(connectiontopic)) return - var/sql_ip = sanitizeSQL(address) - var/sql_computerid = sanitizeSQL(computer_id) - var/sql_admin_rank = sanitizeSQL(admin_rank) var/new_player - var/datum/DBQuery/query_client_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_client_in_db = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!query_client_in_db.Execute()) qdel(query_client_in_db) return - if(!query_client_in_db.NextRow()) //new user detected - if(!holder && !GLOB.deadmins[ckey]) - if(CONFIG_GET(flag/panic_bunker) && !(ckey in GLOB.bunker_passthrough)) - log_access("Failed Login: [key] - New account attempting to connect during panic bunker") - message_admins("Failed Login: [key] - New account attempting to connect during panic bunker") - to_chat(src, "You must first join the Discord to verify your account before joining this server.
To do so, read the rules and post a request in the #station-access-requests channel under the \"Main server\" category in the Discord server linked here: https://discord.gg/E6SQuhz
If you have already done so, wait a few minutes then try again; sometimes the server needs to fully load before you can join.
") //CIT CHANGE - makes the panic bunker disconnect message point to the discord - var/list/connectiontopic_a = params2list(connectiontopic) - var/list/panic_addr = CONFIG_GET(string/panic_server_address) - if(panic_addr && !connectiontopic_a["redirect"]) - var/panic_name = CONFIG_GET(string/panic_server_name) - to_chat(src, "Sending you to [panic_name ? panic_name : panic_addr].") - winset(src, null, "command=.options") - src << link("[panic_addr]?redirect=1") - qdel(query_client_in_db) - qdel(src) - return - new_player = 1 - account_join_date = sanitizeSQL(findJoinDate()) - var/sql_key = sanitizeSQL(key) - var/datum/DBQuery/query_add_player = SSdbcore.NewQuery("INSERT INTO [format_table_name("player")] (`ckey`, `byond_key`, `firstseen`, `firstseen_round_id`, `lastseen`, `lastseen_round_id`, `ip`, `computerid`, `lastadminrank`, `accountjoindate`) VALUES ('[sql_ckey]', '[sql_key]', Now(), '[GLOB.round_id]', Now(), '[GLOB.round_id]', INET_ATON('[sql_ip]'), '[sql_computerid]', '[sql_admin_rank]', [account_join_date ? "'[account_join_date]'" : "NULL"])") - if(!query_add_player.Execute()) - qdel(query_client_in_db) - qdel(query_add_player) - return - qdel(query_add_player) - if(!account_join_date) - account_join_date = "Error" - account_age = -1 - else if(ckey in GLOB.bunker_passthrough) - GLOB.bunker_passthrough -= ckey + //If we aren't an admin, and the flag is set + if(CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[ckey] && !(ckey in GLOB.bunker_passthrough)) + var/living_recs = CONFIG_GET(number/panic_bunker_living) + //Relies on pref existing, but this proc is only called after that occurs, so we're fine. + var/minutes = get_exp_living(pure_numeric = TRUE) + if(minutes <= living_recs) // && !CONFIG_GET(flag/panic_bunker_interview) + var/reject_message = "Failed Login: [key] - Account attempting to connect during panic bunker, but they do not have the required living time [minutes]/[living_recs]" + log_access(reject_message) + message_admins("[reject_message]") + var/message = CONFIG_GET(string/panic_bunker_message) + message = replacetext(message, "%minutes%", living_recs) + to_chat(src, message) + var/list/connectiontopic_a = params2list(connectiontopic) + var/list/panic_addr = CONFIG_GET(string/panic_server_address) + if(panic_addr && !connectiontopic_a["redirect"]) + var/panic_name = CONFIG_GET(string/panic_server_name) + to_chat(src, "Sending you to [panic_name ? panic_name : panic_addr].") + winset(src, null, "command=.options") + src << link("[panic_addr]?redirect=1") + qdel(query_client_in_db) + qdel(src) + return + + if(!query_client_in_db.NextRow()) + new_player = 1 if(CONFIG_GET(flag/age_verification)) //setup age verification if(!set_db_player_flags()) message_admins(usr, "ERROR: Unable to read player flags from database. Please check logs.") @@ -609,9 +611,24 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( update_flag_db(DB_FLAG_AGE_CONFIRMATION_COMPLETE, TRUE) else update_flag_db(DB_FLAG_AGE_CONFIRMATION_INCOMPLETE, TRUE) - + account_join_date = findJoinDate() + var/datum/db_query/query_add_player = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("player")] (`ckey`, `byond_key`, `firstseen`, `firstseen_round_id`, `lastseen`, `lastseen_round_id`, `ip`, `computerid`, `lastadminrank`, `accountjoindate`) + VALUES (:ckey, :key, Now(), :round_id, Now(), :round_id, INET_ATON(:ip), :computerid, :adminrank, :account_join_date) + "}, list("ckey" = ckey, "key" = key, "round_id" = GLOB.round_id, "ip" = address, "computerid" = computer_id, "adminrank" = admin_rank, "account_join_date" = account_join_date || null)) + if(!query_add_player.Execute()) + qdel(query_client_in_db) + qdel(query_add_player) + return + qdel(query_add_player) + if(!account_join_date) + account_join_date = "Error" + account_age = -1 qdel(query_client_in_db) - var/datum/DBQuery/query_get_client_age = SSdbcore.NewQuery("SELECT firstseen, DATEDIFF(Now(),firstseen), accountjoindate, DATEDIFF(Now(),accountjoindate) FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_get_client_age = SSdbcore.NewQuery( + "SELECT firstseen, DATEDIFF(Now(),firstseen), accountjoindate, DATEDIFF(Now(),accountjoindate) FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!query_get_client_age.Execute()) qdel(query_get_client_age) return @@ -622,11 +639,14 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( account_join_date = query_get_client_age.item[3] account_age = text2num(query_get_client_age.item[4]) if(!account_age) - account_join_date = sanitizeSQL(findJoinDate()) + account_join_date = findJoinDate() if(!account_join_date) account_age = -1 else - var/datum/DBQuery/query_datediff = SSdbcore.NewQuery("SELECT DATEDIFF(Now(),'[account_join_date]')") + var/datum/db_query/query_datediff = SSdbcore.NewQuery( + "SELECT DATEDIFF(Now(), :account_join_date)", + list("account_join_date" = account_join_date) + ) if(!query_datediff.Execute()) qdel(query_datediff) return @@ -635,14 +655,20 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( qdel(query_datediff) qdel(query_get_client_age) if(!new_player) - var/datum/DBQuery/query_log_player = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET lastseen = Now(), lastseen_round_id = '[GLOB.round_id]', ip = INET_ATON('[sql_ip]'), computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]', accountjoindate = [account_join_date ? "'[account_join_date]'" : "NULL"] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_log_player = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET lastseen = Now(), lastseen_round_id = :round_id, ip = INET_ATON(:ip), computerid = :computerid, lastadminrank = :admin_rank, accountjoindate = :account_join_date WHERE ckey = :ckey", + list("round_id" = GLOB.round_id, "ip" = address, "computerid" = computer_id, "admin_rank" = admin_rank, "account_join_date" = account_join_date || null, "ckey" = ckey) + ) if(!query_log_player.Execute()) qdel(query_log_player) return qdel(query_log_player) if(!account_join_date) account_join_date = "Error" - var/datum/DBQuery/query_log_connection = SSdbcore.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`server_ip`,`server_port`,`round_id`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')),'[world.port]','[GLOB.round_id]','[sql_ckey]',INET_ATON('[sql_ip]'),'[sql_computerid]')") + var/datum/db_query/query_log_connection = SSdbcore.NewQuery({" + INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`server_ip`,`server_port`,`round_id`,`ckey`,`ip`,`computerid`) + VALUES(null,Now(),INET_ATON(:internet_address),:port,:round_id,:ckey,INET_ATON(:ip),:computerid) + "}, list("internet_address" = world.internet_address || "0", "port" = world.port, "round_id" = GLOB.round_id, "ckey" = ckey, "ip" = address, "computerid" = computer_id)) query_log_connection.Execute() qdel(query_log_connection) @@ -666,9 +692,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( CRASH("Age check regex failed for [src.ckey]") /client/proc/validate_key_in_db() - var/sql_ckey = sanitizeSQL(ckey) var/sql_key - var/datum/DBQuery/query_check_byond_key = SSdbcore.NewQuery("SELECT byond_key FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_check_byond_key = SSdbcore.NewQuery( + "SELECT byond_key FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!query_check_byond_key.Execute()) qdel(query_check_byond_key) return @@ -684,8 +712,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(F) var/regex/R = regex("\\tkey = \"(.+)\"") if(R.Find(F)) - var/web_key = sanitizeSQL(R.group[1]) - var/datum/DBQuery/query_update_byond_key = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET byond_key = '[web_key]' WHERE ckey = '[sql_ckey]'") + var/web_key = R.group[1] + var/datum/db_query/query_update_byond_key = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET byond_key = :byond_key WHERE ckey = :ckey", + list("byond_key" = web_key, "ckey" = ckey) + ) query_update_byond_key.Execute() qdel(query_update_byond_key) else @@ -702,8 +733,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( var/static/tokens = list() var/static/cidcheck_failedckeys = list() //to avoid spamming the admins if the same guy keeps trying. var/static/cidcheck_spoofckeys = list() - var/sql_ckey = sanitizeSQL(ckey) - var/datum/DBQuery/query_cidcheck = SSdbcore.NewQuery("SELECT computerid FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_cidcheck = SSdbcore.NewQuery( + "SELECT computerid FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) query_cidcheck.Execute() var/lastcid @@ -722,7 +755,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( sleep(15 SECONDS) //Longer sleep here since this would trigger if a client tries to reconnect manually because the inital reconnect failed - //we sleep after telling the client to reconnect, so if we still exist something is up + //we sleep after telling the client to reconnect, so if we still exist something is up log_access("Forced disconnect: [key] [computer_id] [address] - CID randomizer check") qdel(src) @@ -736,7 +769,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if (!cidcheck_failedckeys[ckey]) message_admins("[key_name(src)] has been detected as using a cid randomizer. Connection rejected.") - send2irc_adminless_only("CidRandomizer", "[key_name(src)] has been detected as using a cid randomizer. Connection rejected.") + send2tgs_adminless_only("CidRandomizer", "[key_name(src)] has been detected as using a cid randomizer. Connection rejected.") cidcheck_failedckeys[ckey] = TRUE note_randomizer_user() @@ -747,7 +780,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( else if (cidcheck_failedckeys[ckey]) message_admins("[key_name_admin(src)] has been allowed to connect after showing they removed their cid randomizer") - send2irc_adminless_only("CidRandomizer", "[key_name(src)] has been allowed to connect after showing they removed their cid randomizer.") + send2tgs_adminless_only("CidRandomizer", "[key_name(src)] has been allowed to connect after showing they removed their cid randomizer.") cidcheck_failedckeys -= ckey if (cidcheck_spoofckeys[ckey]) message_admins("[key_name_admin(src)] has been allowed to connect after appearing to have attempted to spoof a cid randomizer check because it appears they aren't spoofing one this time") @@ -778,10 +811,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( add_system_note("CID-Error", "Detected as using a cid randomizer.") /client/proc/add_system_note(system_ckey, message) - 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/db_query/query_get_notes = SSdbcore.NewQuery( + "SELECT id FROM [format_table_name("messages")] WHERE type = 'note' AND targetckey = :targetckey AND adminckey = :adminckey AND timestamp + INTERVAL 1 DAY < NOW() AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL)", + list("targetckey" = ckey, "adminckey" = system_ckey) + ) if(!query_get_notes.Execute()) qdel(query_get_notes) return @@ -790,7 +824,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( 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 [format_table_name("messages")] WHERE targetckey = :targetckey AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) ORDER BY timestamp DESC LIMIT 1", + list("targetckey" = ckey) + ) if(!query_get_notes.Execute()) qdel(query_get_notes) return diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index aaebe9a24a..9afc852f6b 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -1370,9 +1370,11 @@ GLOBAL_LIST_EMPTY(preferences_datums) /datum/preferences/proc/process_link(mob/user, list/href_list) if(href_list["jobbancheck"]) - var/job = sanitizeSQL(href_list["jobbancheck"]) - var/sql_ckey = sanitizeSQL(user.ckey) - var/datum/DBQuery/query_get_jobban = SSdbcore.NewQuery("SELECT reason, bantime, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey) FROM [format_table_name("ban")] WHERE ckey = '[sql_ckey]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[job]'") + var/job = href_list["jobbancheck"] + var/datum/db_query/query_get_jobban = SSdbcore.NewQuery({" + SELECT reason, bantime, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey) + FROM [format_table_name("ban")] WHERE ckey = :ckey AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = :job + "}, list("ckey" = user.ckey, "job" = job)) if(!query_get_jobban.warn_execute()) qdel(query_get_jobban) return @@ -1387,7 +1389,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) if(text2num(duration) > 0) text += ". The ban is for [duration] minutes and expires on [expiration_time] (server time)" text += ".
" - to_chat(user, text) + to_chat(user, text, confidential = TRUE) qdel(query_get_jobban) return diff --git a/code/modules/client/verbs/aooc.dm b/code/modules/client/verbs/aooc.dm index 1a019bba80..182975d192 100644 --- a/code/modules/client/verbs/aooc.dm +++ b/code/modules/client/verbs/aooc.dm @@ -13,7 +13,7 @@ GLOBAL_VAR_INIT(normal_aooc_colour, "#ce254f") if(!mob) return - if(!(prefs.toggles & CHAT_OOC)) + if(!(prefs.chat_toggles & CHAT_OOC)) to_chat(src, " You have OOC muted.") return if(jobban_isbanned(mob, "OOC")) diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index e6663f8bb2..5b110e9255 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -179,7 +179,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") to_chat(usr, "Sorry, that function is not enabled on this server.") return - browse_messages(null, usr.ckey, null, TRUE, override = TRUE) + browse_messages(null, usr.ckey, null, TRUE) /client/proc/self_playtime() set name = "View tracked playtime" diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index 4728001699..a558abbfe8 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -220,8 +220,8 @@ parry_max_attacks = INFINITY parry_failed_cooldown_duration = 2.25 SECONDS parry_failed_stagger_duration = 2.25 SECONDS - parry_cooldown = 3 SECONDS - parry_failed_clickcd_duration = 0.5 SECONDS + parry_cooldown = 0 + parry_failed_clickcd_duration = 0 /obj/item/clothing/gloves/botanic_leather name = "botanist's leather gloves" diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index 8580b153b2..3e72765234 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -1040,6 +1040,12 @@ icon_state = "wbreakpoly" item_state = "wbreakpoly" +/obj/item/clothing/suit/toggle/wbreakpoly/on_toggle(mob/user) + if(suittoggled) + to_chat(usr, "You zip up [src].") + else + to_chat(usr, "You unzip [src].") + /obj/item/clothing/suit/toggle/wbreakpoly/polychromic/ComponentInitialize() . = ..() AddElement(/datum/element/polychromic, list("#464F65", "#916035", "#474747"), 3) diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm index aa22834670..98d6809d64 100644 --- a/code/modules/clothing/suits/toggles.dm +++ b/code/modules/clothing/suits/toggles.dm @@ -110,6 +110,9 @@ suit_toggle(user) return TRUE +/obj/item/clothing/suit/toggle/proc/on_toggle(mob/user) // override this, not suit_toggle, which does checks + to_chat(usr, "You toggle [src]'s [togglename].") + /obj/item/clothing/suit/toggle/ui_action_click() suit_toggle() @@ -119,7 +122,7 @@ if(!can_use(usr)) return 0 - to_chat(usr, "You toggle [src]'s [togglename].") + on_toggle(usr) if(src.suittoggled) src.icon_state = "[initial(icon_state)]" src.suittoggled = FALSE diff --git a/code/modules/clothing/under/suits.dm b/code/modules/clothing/under/suits.dm index b9f55e695a..7f0ecf3d70 100644 --- a/code/modules/clothing/under/suits.dm +++ b/code/modules/clothing/under/suits.dm @@ -138,3 +138,27 @@ icon_state = "greyturtle" item_state = "greyturtle" can_adjust = FALSE + +/obj/item/clothing/under/suit/turtle/purple + name = "purple turtleneck" + icon_state = "turtle_sci" + item_state = "turtle_sci" + can_adjust = FALSE + +/obj/item/clothing/under/suit/turtle/orange + name = "orange turtleneck" + icon_state = "turtle_eng" + item_state = "turtle_eng" + can_adjust = FALSE + +/obj/item/clothing/under/suit/turtle/red + name = "red turtleneck" + icon_state = "turtle_sec" + item_state = "turtle_sec" + can_adjust = FALSE + +/obj/item/clothing/under/suit/turtle/blue + name = "blue turtleneck" + icon_state = "turtle_med" + item_state = "turtle_med" + can_adjust = FALSE diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm index 1da03623e6..df00f873b5 100644 --- a/code/modules/events/holiday/vday.dm +++ b/code/modules/events/holiday/vday.dm @@ -21,27 +21,6 @@ new /obj/item/reagent_containers/food/snacks/candyheart(B) new /obj/item/storage/fancy/heart_box(B) - var/list/valentines = list() - for(var/mob/living/M in GLOB.player_list) - if(!M.stat && M.client && M.mind && !HAS_TRAIT(M, TRAIT_NO_MIDROUND_ANTAG)) - valentines |= M - - - while(valentines.len) - var/mob/living/L = pick_n_take(valentines) - if(valentines.len) - var/mob/living/date = pick_n_take(valentines) - - - forge_valentines_objective(L, date) - forge_valentines_objective(date, L) - - if(valentines.len && prob(4)) - var/mob/living/notgoodenough = pick_n_take(valentines) - forge_valentines_objective(notgoodenough, date) - else - L.mind.add_antag_datum(/datum/antagonist/heartbreaker) - /proc/forge_valentines_objective(mob/living/lover,mob/living/date,var/chemLove = FALSE) lover.mind.special_role = "valentine" if (chemLove == TRUE) diff --git a/code/modules/events/supermatter_surge.dm b/code/modules/events/supermatter_surge.dm new file mode 100644 index 0000000000..d54fc4dcd2 --- /dev/null +++ b/code/modules/events/supermatter_surge.dm @@ -0,0 +1,23 @@ +/datum/round_event_control/supermatter_surge + name = "Supermatter Surge" + typepath = /datum/round_event/supermatter_surge + weight = 20 + max_occurrences = 4 + earliest_start = 10 MINUTES + +/datum/round_event_control/supermatter_surge/canSpawnEvent() + if(GLOB.main_supermatter_engine?.has_been_powered) + return ..() + +/datum/round_event/supermatter_surge + var/power = 2000 + +/datum/round_event/supermatter_surge/setup() + power = rand(200,4000) + +/datum/round_event/supermatter_surge/announce() + if(power > 800 || prob(round(power/8))) + priority_announce("Class [round(power/500) + 1] supermatter surge detected. Intervention may be required.", "Anomaly Alert") + +/datum/round_event/supermatter_surge/start() + GLOB.main_supermatter_engine.matter_power += power diff --git a/code/modules/food_and_drinks/food/snacks_meat.dm b/code/modules/food_and_drinks/food/snacks_meat.dm index 05a0da2793..152740f932 100644 --- a/code/modules/food_and_drinks/food/snacks_meat.dm +++ b/code/modules/food_and_drinks/food/snacks_meat.dm @@ -435,7 +435,7 @@ name = "alien drone cube" desc = "Just add water and run!" tastes = list("the jungle" = 1, "acid" = 1) - dried_being = /mob/living/carbon/alien/humanoid/drone + dried_being = /mob/living/simple_animal/hostile/alien/sentinel/cube /obj/item/reagent_containers/food/snacks/cube/goat name = "goat cube" diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index e186364cff..aca727ad8d 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -97,6 +97,7 @@ if(myseed.mutatelist.len > 0) myseed.instability = (myseed.instability/2) mutatespecie() + return BULLET_ACT_HIT else return ..() diff --git a/code/modules/jobs/job_exp.dm b/code/modules/jobs/job_exp.dm index 5ff791c5f3..d9b98eea0a 100644 --- a/code/modules/jobs/job_exp.dm +++ b/code/modules/jobs/job_exp.dm @@ -1,7 +1,6 @@ GLOBAL_LIST_EMPTY(exp_to_update) GLOBAL_PROTECT(exp_to_update) - // Procs /datum/job/proc/required_playtime_remaining(client/C) if(!C) @@ -57,6 +56,7 @@ GLOBAL_PROTECT(exp_to_update) amount += explist[job] return amount +// todo: port tgui exp /client/proc/get_exp_report() if(!CONFIG_GET(flag/use_exp_tracking)) return "Tracking is disabled in the server configuration file." @@ -121,12 +121,11 @@ GLOBAL_PROTECT(exp_to_update) return_text += "" return return_text - -/client/proc/get_exp_living() - if(!prefs.exp) - return "No data" +/client/proc/get_exp_living(pure_numeric = FALSE) + if(!prefs.exp || !prefs.exp[EXP_TYPE_LIVING]) + return pure_numeric ? 0 : "No data" var/exp_living = text2num(prefs.exp[EXP_TYPE_LIVING]) - return get_exp_format(exp_living) + return pure_numeric ? exp_living : get_exp_format(exp_living) /proc/get_exp_format(expnum) if(expnum > 60) @@ -148,7 +147,7 @@ GLOBAL_PROTECT(exp_to_update) set waitfor = FALSE var/list/old_minutes = GLOB.exp_to_update GLOB.exp_to_update = null - SSdbcore.MassInsert(format_table_name("role_time"), old_minutes, "ON DUPLICATE KEY UPDATE minutes = minutes + VALUES(minutes)") + SSdbcore.MassInsert(format_table_name("role_time"), old_minutes, duplicate_key = "ON DUPLICATE KEY UPDATE minutes = minutes + VALUES(minutes)") //resets a client's exp to what was in the db. /client/proc/set_exp_from_db() @@ -156,7 +155,10 @@ GLOBAL_PROTECT(exp_to_update) return -1 if(!SSdbcore.Connect()) return -1 - var/datum/DBQuery/exp_read = SSdbcore.NewQuery("SELECT job, minutes FROM [format_table_name("role_time")] WHERE ckey = '[sanitizeSQL(ckey)]'") + var/datum/db_query/exp_read = SSdbcore.NewQuery( + "SELECT job, minutes FROM [format_table_name("role_time")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!exp_read.Execute(async = TRUE)) qdel(exp_read) return -1 @@ -188,7 +190,10 @@ GLOBAL_PROTECT(exp_to_update) else prefs.db_flags |= newflag - var/datum/DBQuery/flag_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET flags = '[prefs.db_flags]' WHERE ckey='[sanitizeSQL(ckey)]'") + var/datum/db_query/flag_update = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET flags=:flags WHERE ckey=:ckey", + list("flags" = "[prefs.db_flags]", "ckey" = ckey) + ) if(!flag_update.Execute()) qdel(flag_update) @@ -256,8 +261,8 @@ GLOBAL_PROTECT(exp_to_update) CRASH("invalid job value [jtype]:[jvalue]") LAZYINITLIST(GLOB.exp_to_update) GLOB.exp_to_update.Add(list(list( - "job" = "'[sanitizeSQL(jtype)]'", - "ckey" = "'[sanitizeSQL(ckey)]'", + "job" = jtype, + "ckey" = ckey, "minutes" = jvalue))) prefs.exp[jtype] += jvalue addtimer(CALLBACK(SSblackbox,/datum/controller/subsystem/blackbox/proc/update_exp_db),20,TIMER_OVERRIDE|TIMER_UNIQUE) @@ -268,7 +273,10 @@ GLOBAL_PROTECT(exp_to_update) if(!SSdbcore.Connect()) return FALSE - var/datum/DBQuery/flags_read = SSdbcore.NewQuery("SELECT flags FROM [format_table_name("player")] WHERE ckey='[ckey]'") + var/datum/db_query/flags_read = SSdbcore.NewQuery( + "SELECT flags FROM [format_table_name("player")] WHERE ckey=:ckey", + list("ckey" = ckey) + ) if(!flags_read.Execute(async = TRUE)) qdel(flags_read) diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm index ef0ee2ee77..9d5dbe8f63 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -1,3 +1,7 @@ +#define BOOKCASE_UNANCHORED 0 +#define BOOKCASE_ANCHORED 1 +#define BOOKCASE_FINISHED 2 + /* Library Items * * Contains: @@ -17,69 +21,85 @@ desc = "A great place for storing knowledge." anchored = FALSE density = TRUE - opacity = 0 + opacity = FALSE resistance_flags = FLAMMABLE max_integrity = 200 armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - var/state = 0 - var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book, /obj/item/gun/magic/wand/book) //Things allowed in the bookcase + var/state = BOOKCASE_UNANCHORED + /// When enabled, books_to_load number of random books will be generated for this bookcase when first interacted with. + var/load_random_books = FALSE + /// The category of books to pick from when populating random books. + var/random_category = null + /// How many random books to generate. + var/books_to_load = 0 /obj/structure/bookcase/examine(mob/user) . = ..() if(!anchored) . += "The bolts on the bottom are unsecured." - if(anchored) + else . += "It's secured in place with bolts." switch(state) - if(0) + if(BOOKCASE_UNANCHORED) . += "There's a small crack visible on the back panel." - if(1) + if(BOOKCASE_ANCHORED) . += "There's space inside for a wooden shelf." - if(2) + if(BOOKCASE_FINISHED) . += "There's a small crack visible on the shelf." /obj/structure/bookcase/Initialize(mapload) . = ..() if(!mapload) return - state = 2 - icon_state = "book-0" - anchored = TRUE + set_anchored(TRUE) + state = BOOKCASE_FINISHED for(var/obj/item/I in loc) - if(istype(I, /obj/item/book)) - I.forceMove(src) + if(!isbook(I)) + continue + I.forceMove(src) + update_icon() + +/obj/structure/bookcase/set_anchored(anchorvalue) + . = ..() + if(isnull(.)) + return + state = anchorvalue + if(!anchorvalue) //in case we were vareditted or uprooted by a hostile mob, ensure we drop all our books instead of having them disappear till we're rebuild. + var/atom/Tsec = drop_location() + for(var/obj/I in contents) + if(!isbook(I)) + continue + I.forceMove(Tsec) update_icon() /obj/structure/bookcase/attackby(obj/item/I, mob/user, params) switch(state) - if(0) + if(BOOKCASE_UNANCHORED) if(I.tool_behaviour == TOOL_WRENCH) if(I.use_tool(src, user, 20, volume=50)) to_chat(user, "You wrench the frame into place.") - anchored = TRUE - state = 1 - if(I.tool_behaviour == TOOL_CROWBAR) + set_anchored(TRUE) + else if(I.tool_behaviour == TOOL_CROWBAR) if(I.use_tool(src, user, 20, volume=50)) to_chat(user, "You pry the frame apart.") deconstruct(TRUE) - if(1) + if(BOOKCASE_ANCHORED) if(istype(I, /obj/item/stack/sheet/mineral/wood)) var/obj/item/stack/sheet/mineral/wood/W = I if(W.get_amount() >= 2) W.use(2) to_chat(user, "You add a shelf.") - state = 2 - icon_state = "book-0" - if(I.tool_behaviour == TOOL_WRENCH) + state = BOOKCASE_FINISHED + update_icon() + else if(I.tool_behaviour == TOOL_WRENCH) I.play_tool_sound(src, 100) to_chat(user, "You unwrench the frame.") - anchored = FALSE - state = 0 + set_anchored(FALSE) - if(2) + if(BOOKCASE_FINISHED) var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) - if(is_type_in_list(I, allowed_books)) + if(isbook(I)) if(!user.transferItemToLoc(I, src)) return update_icon() @@ -107,19 +127,22 @@ I.play_tool_sound(src, 100) to_chat(user, "You pry the shelf out.") new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) - state = 1 - icon_state = "bookempty" + state = BOOKCASE_ANCHORED + update_icon() else return ..() -/obj/structure/bookcase/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags) - . = ..() - if(. || !istype(user)) + +/obj/structure/bookcase/on_attack_hand(mob/living/user) + if(!istype(user)) return + if(load_random_books) + create_random_books(books_to_load, src, FALSE, random_category) + load_random_books = FALSE if(contents.len) - var/obj/item/book/choice = input("Which book would you like to remove from the shelf?") as null|obj in contents + var/obj/item/book/choice = input(user, "Which book would you like to remove from the shelf?") as null|obj in sortNames(contents.Copy()) if(choice) - if(!CHECK_MOBILITY(user, MOBILITY_USE) || !in_range(loc, user)) + if(!(user.mobility_flags & MOBILITY_USE) || user.stat != CONSCIOUS || !in_range(loc, user)) return if(ishuman(user)) if(!user.get_active_held_item()) @@ -128,36 +151,25 @@ choice.forceMove(drop_location()) update_icon() -/obj/structure/bookcase/attack_ghost(mob/dead/observer/user) - . = ..() - if(!length(contents)) - to_chat(user, "It's empty!") - return - var/obj/item/book/choice = input("Which book would you like to read?") as null|obj in contents - if(choice) - if(!istype(choice)) //spellbook, cult tome, or the one weird bible storage - to_chat(user,"A mysterious force is keeping you from reading that.") - return - choice.attack_ghost(user) /obj/structure/bookcase/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood(loc, 4) - for(var/obj/item/book/B in contents) - B.forceMove(get_turf(src)) - qdel(src) + var/atom/Tsec = drop_location() + new /obj/item/stack/sheet/mineral/wood(Tsec, 4) + for(var/obj/item/I in contents) + if(!isbook(I)) + continue + I.forceMove(Tsec) + return ..() /obj/structure/bookcase/update_icon_state() - icon_state = "book-[min(length(contents), 5)]" - - -/obj/structure/bookcase/manuals/medical - name = "medical manuals bookcase" - -/obj/structure/bookcase/manuals/medical/Initialize() - . = ..() - new /obj/item/book/manual/wiki/medical_cloning(src) - update_icon() + if(state == BOOKCASE_UNANCHORED || state == BOOKCASE_ANCHORED) + icon_state = "bookempty" + return + var/amount = contents.len + if(load_random_books) + amount += books_to_load + icon_state = "book-[clamp(amount, 0, 5)]" /obj/structure/bookcase/manuals/engineering @@ -198,34 +210,27 @@ var/dat //Actual page content var/due_date = 0 //Game time in 1/10th seconds var/author //Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - var/unique = 0 //0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified + var/unique = FALSE //false - Normal book, true - Should not be treated as normal book, unable to be copied, unable to be modified var/title //The real name of the book. var/window_size = null // Specific window size for the book, i.e: "1920x1080", Size x Width + /obj/item/book/attack_self(mob/user) - if(is_blind(user)) - to_chat(user, "As you are trying to read, you suddenly feel very stupid!") - return - if(ismonkey(user)) - to_chat(user, "You skim through the book but can't comprehend any of it.") + if(!user.can_read(src)) return if(dat) - show_to(user) - user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + user << browse("Penned by [author].
" + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") + user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + // SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) + onclose(user, "book") else to_chat(user, "This book is completely blank!") -/obj/item/book/attack_ghost(mob/dead/observer/O) - . = ..() - show_to(O) - -/obj/item/book/proc/show_to(mob/user) - user << browse("Penned by [author].
" + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") /obj/item/book/attackby(obj/item/I, mob/user, params) if(istype(I, /obj/item/pen)) - if(is_blind(user)) - to_chat(user, " As you are trying to write on the book, you suddenly feel very stupid!") + if(user.is_blind()) + to_chat(user, "As you are trying to write on the book, you suddenly feel very stupid!") return if(unique) to_chat(user, "These pages don't seem to take the ink well! Looks like you can't modify it.") @@ -243,10 +248,10 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if (length(newtitle) > 20) - to_chat(user, "That title won't fit on the cover!") + to_chat(user, "That title won't fit on the cover!") return if(!newtitle) - to_chat(user, "That title is invalid.") + to_chat(user, "That title is invalid.") return else name = newtitle @@ -256,7 +261,7 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if(!content) - to_chat(user, "The content is invalid.") + to_chat(user, "The content is invalid.") return else dat += content @@ -265,7 +270,7 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if(!newauthor) - to_chat(user, "The name is invalid.") + to_chat(user, "The name is invalid.") return else author = newauthor @@ -275,32 +280,32 @@ else if(istype(I, /obj/item/barcodescanner)) var/obj/item/barcodescanner/scanner = I if(!scanner.computer) - to_chat(user, "[I]'s screen flashes: 'No associated computer found!'") + to_chat(user, "[I]'s screen flashes: 'No associated computer found!'") else switch(scanner.mode) if(0) scanner.book = src - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer.'") if(1) scanner.book = src scanner.computer.buffer_book = name - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") if(2) scanner.book = src for(var/datum/borrowbook/b in scanner.computer.checkouts) if(b.bookname == name) scanner.computer.checkouts.Remove(b) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") return - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") if(3) scanner.book = src for(var/obj/item/book in scanner.computer.inventory) if(book == src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") return scanner.computer.inventory.Add(src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") else if(istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER) to_chat(user, "You begin to carve out [title]...") @@ -361,3 +366,8 @@ else to_chat(user, "No associated computer found. Only local scans will function properly.") to_chat(user, "\n") + + +#undef BOOKCASE_UNANCHORED +#undef BOOKCASE_ANCHORED +#undef BOOKCASE_FINISHED diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 3953f5e28c..1125d15bca 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -25,12 +25,13 @@ var/title var/category = "Any" var/author - var/SQLquery - clockwork = TRUE //it'd look weird + var/search_page = 0 + COOLDOWN_DECLARE(library_visitor_topic_cooldown) + clockwork = TRUE /obj/machinery/computer/libraryconsole/ui_interact(mob/user) . = ..() - var/dat = "" // + var/list/dat = list() // switch(screenstate) if(0) dat += "

Search Settings


" @@ -43,13 +44,43 @@ dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
" else if(QDELETED(user)) return - else if(!SQLquery) - dat += "ERROR: Malformed search request. Please contact your system administrator for assistance.
" else dat += "" dat += "" - - var/datum/DBQuery/query_library_list_books = SSdbcore.NewQuery(SQLquery) + var/bookcount = 0 + var/booksperpage = 20 + var/datum/db_query/query_library_count_books = SSdbcore.NewQuery({" + SELECT COUNT(id) FROM [format_table_name("library")] + WHERE isnull(deleted) + AND author LIKE CONCAT('%',:author,'%') + AND title LIKE CONCAT('%',:title,'%') + AND (:category = 'Any' OR category = :category) + "}, list("author" = author, "title" = title, "category" = category)) + if(!query_library_count_books.warn_execute()) + qdel(query_library_count_books) + return + if(query_library_count_books.NextRow()) + bookcount = text2num(query_library_count_books.item[1]) + qdel(query_library_count_books) + if(bookcount > booksperpage) + dat += "Page: " + var/pagecount = 1 + var/list/pagelist = list() + while(bookcount > 0) + pagelist += "[pagecount == search_page + 1 ? "\[[pagecount]\]" : "\[[pagecount]\]"]" + bookcount -= booksperpage + pagecount++ + dat += pagelist.Join(" | ") + search_page = text2num(search_page) + var/datum/db_query/query_library_list_books = SSdbcore.NewQuery({" + SELECT author, title, category, id + FROM [format_table_name("library")] + WHERE isnull(deleted) + AND author LIKE CONCAT('%',:author,'%') + AND title LIKE CONCAT('%',:title,'%') + AND (:category = 'Any' OR category = :category) + LIMIT :skip, :take + "}, list("author" = author, "title" = title, "category" = category, "skip" = booksperpage * search_page, "take" = booksperpage)) if(!query_library_list_books.Execute()) dat += "ERROR: Unable to retrieve book listings. Please contact your system administrator for assistance.
" else @@ -65,12 +96,15 @@ dat += "
AUTHORTITLECATEGORYSS13BN

" dat += "\[Go Back\]
" var/datum/browser/popup = new(user, "publiclibrary", name, 600, 400) - popup.set_content(dat) + popup.set_content(jointext(dat, "")) popup.open() /obj/machinery/computer/libraryconsole/Topic(href, href_list) + if(!COOLDOWN_FINISHED(src, library_visitor_topic_cooldown)) + return + COOLDOWN_START(src, library_visitor_topic_cooldown, 1 SECONDS) . = ..() - if(..()) + if(.) usr << browse(null, "window=publiclibrary") onclose(usr, "publiclibrary") return @@ -81,29 +115,24 @@ title = sanitize(newtitle) else title = null - title = sanitizeSQL(title) if(href_list["setcategory"]) var/newcategory = input("Choose a category to search for:") in list("Any", "Fiction", "Non-Fiction", "Adult", "Reference", "Religion") if(newcategory) category = sanitize(newcategory) else category = "Any" - category = sanitizeSQL(category) if(href_list["setauthor"]) var/newauthor = input("Enter an author to search for:") as text|null if(newauthor) author = sanitize(newauthor) else author = null - author = sanitizeSQL(author) if(href_list["search"]) - SQLquery = "SELECT author, title, category, id FROM [format_table_name("library")] WHERE isnull(deleted) AND " - if(category == "Any") - SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%'" - else - SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'" screenstate = 1 + if(href_list["bookpagecount"]) + search_page = text2num(href_list["bookpagecount"]) + if(href_list["back"]) screenstate = 0 @@ -120,44 +149,12 @@ var/getdate var/duedate -/* - * Cachedbook datum - */ -/datum/cachedbook // Datum used to cache the SQL DB books locally in order to achieve a performance gain. - var/id - var/title - var/author - var/category - -GLOBAL_LIST(cachedbooks) // List of our cached book datums - - -/proc/load_library_db_to_cache() - if(GLOB.cachedbooks) - return - if(!SSdbcore.Connect()) - return - GLOB.cachedbooks = list() - var/datum/DBQuery/query_library_cache = SSdbcore.NewQuery("SELECT id, author, title, category FROM [format_table_name("library")] WHERE isnull(deleted)") - if(!query_library_cache.Execute()) - qdel(query_library_cache) - return - while(query_library_cache.NextRow()) - var/datum/cachedbook/newbook = new() - newbook.id = query_library_cache.item[1] - newbook.author = query_library_cache.item[2] - newbook.title = query_library_cache.item[3] - newbook.category = query_library_cache.item[4] - GLOB.cachedbooks += newbook - qdel(query_library_cache) - - - #define PRINTER_COOLDOWN 60 /* * Library Computer - * After 860 days, it's finally a buildable computer. + * After 860 days, it's finally a buildable computer.* + * * i cannot change maps because you are a buch of fucks who ignore map changes */ // TODO: Make this an actual /obj/machinery/computer that can be crafted from circuit boards and such // It is August 22nd, 2012... This TODO has already been here for months.. I wonder how long it'll last before someone does something about it. @@ -165,11 +162,15 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums /obj/machinery/computer/libraryconsole/bookmanagement name = "book inventory management console" desc = "Librarian's command station." - screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book verb_say = "beeps" verb_ask = "beeps" verb_exclaim = "beeps" pass_flags = PASSTABLE + + circuit = /obj/item/circuitboard/computer/libraryconsole + + // var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book + var/arcanecheckout = 0 var/buffer_book var/buffer_mob @@ -178,25 +179,9 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums var/list/inventory = list() var/checkoutperiod = 5 // In minutes var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive - var/list/libcomp_menu var/page = 1 //current page of the external archives - var/cooldown = 0 - -/obj/machinery/computer/libraryconsole/bookmanagement/proc/build_library_menu() - if(libcomp_menu) - return - load_library_db_to_cache() - if(!GLOB.cachedbooks) - return - libcomp_menu = list("") - - for(var/i in 1 to GLOB.cachedbooks.len) - var/datum/cachedbook/C = GLOB.cachedbooks[i] - var/page = round(i/250)+1 - if (libcomp_menu.len < page) - libcomp_menu.len = page - libcomp_menu[page] = "" - libcomp_menu[page] += "[C.author][C.title][C.category]\[Order\]\n" + var/printer_cooldown = 0 + COOLDOWN_DECLARE(library_console_topic_cooldown) /obj/machinery/computer/libraryconsole/bookmanagement/Initialize() . = ..() @@ -258,17 +243,37 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums dat += "(Return to main menu)
" if(4) dat += "

External Archive

" - build_library_menu() - - if(!GLOB.cachedbooks) + if(!SSdbcore.Connect()) dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." else + var/booksperpage = 50 + var/pagecount + var/datum/db_query/query_library_count_books = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("library")] WHERE isnull(deleted)") + if(!query_library_count_books.Execute()) + qdel(query_library_count_books) + return + if(query_library_count_books.NextRow()) + pagecount = CEILING(text2num(query_library_count_books.item[1]) / booksperpage, 1) + qdel(query_library_count_books) + var/list/booklist = list() + var/datum/db_query/query_library_get_books = SSdbcore.NewQuery({" + SELECT id, author, title, category + FROM [format_table_name("library")] + WHERE isnull(deleted) + LIMIT :skip, :take + "}, list("skip" = booksperpage * (page - 1), "take" = booksperpage)) + if(!query_library_get_books.Execute()) + qdel(query_library_get_books) + return + while(query_library_get_books.NextRow()) + booklist += "[query_library_get_books.item[2]][query_library_get_books.item[3]][query_library_get_books.item[4]]\[Order\]\n" dat += "(Order book by SS13BN)

" dat += "" dat += "" - dat += libcomp_menu[clamp(page,1,libcomp_menu.len)] - dat += "" + dat += jointext(booklist, "") + dat += "" dat += "
AUTHORTITLECATEGORY
<<<< >>>>
<<<< >>>>
" + qdel(query_library_get_books) dat += "
(Return to main menu)
" if(5) dat += "

Upload a New Title

" @@ -321,33 +326,27 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums return null /obj/machinery/computer/libraryconsole/bookmanagement/proc/print_forbidden_lore(mob/user) - var/spook = pick("blood", "brass") - var/turf/T = get_turf(src) - if(spook == "blood") - new /obj/item/melee/cultblade/dagger(T) - else - new /obj/item/clockwork/slab(T) - - to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a [spook == "blood" ? "sinister dagger" : "strange metal tablet"] sitting on the desk. You don't even remember where it came from...") - user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) + new /obj/item/melee/cultblade/dagger(get_turf(src)) + to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a sinister dagger sitting on the desk. You don't even remember where it came from...") + user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) /obj/machinery/computer/libraryconsole/bookmanagement/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/barcodescanner)) var/obj/item/barcodescanner/scanner = W scanner.computer = src - to_chat(user, "[scanner]'s associated machine has been set to [src].") - audible_message("[src] lets out a low, short blip.") + to_chat(user, "[scanner]'s associated machine has been set to [src].") + audible_message("[src] lets out a low, short blip.") else return ..() /obj/machinery/computer/libraryconsole/bookmanagement/emag_act(mob/user) - . = ..() - if(!density || obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - return TRUE + if(density && !(obj_flags & EMAGGED)) + obj_flags |= EMAGGED /obj/machinery/computer/libraryconsole/bookmanagement/Topic(href, href_list) + if(!COOLDOWN_FINISHED(src, library_console_topic_cooldown)) + return + COOLDOWN_START(src, library_console_topic_cooldown, 1 SECONDS) if(..()) usr << browse(null, "window=library") onclose(usr, "library") @@ -385,7 +384,7 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums if(checkoutperiod < 1) checkoutperiod = 1 if(href_list["editbook"]) - buffer_book = stripped_input(usr, "Enter the book's title:") + buffer_book = stripped_input(usr, "Enter the book's title:", max_length = 45) if(href_list["editmob"]) buffer_mob = stripped_input(usr, "Enter the recipient's name:", max_length = MAX_NAME_LEN) if(href_list["checkout"]) @@ -404,7 +403,7 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums if(b && istype(b)) inventory.Remove(b) if(href_list["setauthor"]) - var/newauthor = stripped_input(usr, "Enter the author's name: ") + var/newauthor = stripped_input(usr, "Enter the author's name: ", max_length = 45) if(newauthor) scanner.cache.author = newauthor if(href_list["setcategory"]) @@ -419,14 +418,11 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums if (!SSdbcore.Connect()) alert("Connection to Archive has been severed. Aborting.") else - - var/sqltitle = sanitizeSQL(scanner.cache.name) - var/sqlauthor = sanitizeSQL(scanner.cache.author) - var/sqlcontent = sanitizeSQL(scanner.cache.dat) - var/sqlcategory = sanitizeSQL(upload_category) - var/sqlckey = sanitizeSQL(usr.ckey) var/msg = "[key_name(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs" - var/datum/DBQuery/query_library_upload = SSdbcore.NewQuery("INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', '[sqlckey]', Now(), '[GLOB.round_id]')") + var/datum/db_query/query_library_upload = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created) + VALUES (:author, :title, :content, :category, :ckey, Now(), :round_id) + "}, list("title" = scanner.cache.name, "author" = scanner.cache.author, "content" = scanner.cache.dat, "category" = upload_category, "ckey" = usr.ckey, "round_id" = GLOB.round_id)) if(!query_library_upload.Execute()) qdel(query_library_upload) alert("Database error encountered uploading to Archive") @@ -448,7 +444,7 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums GLOB.news_network.SubmitArticle(scanner.cache.dat, "[scanner.cache.name]", "Nanotrasen Book Club", null) alert("Upload complete. Your uploaded title is now available on station newscasters.") if(href_list["orderbyid"]) - if(cooldown > world.time) + if(printer_cooldown > world.time) say("Printer unavailable. Please allow a short time before attempting to print.") else var/orderid = input("Enter your order:") as num|null @@ -457,14 +453,17 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums href_list["targetid"] = num2text(orderid) if(href_list["targetid"]) - var/sqlid = sanitizeSQL(href_list["targetid"]) + var/id = href_list["targetid"] if (!SSdbcore.Connect()) alert("Connection to Archive has been severed. Aborting.") - if(cooldown > world.time) + if(printer_cooldown > world.time) say("Printer unavailable. Please allow a short time before attempting to print.") else - cooldown = world.time + PRINTER_COOLDOWN - var/datum/DBQuery/query_library_print = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE id=[sqlid] AND isnull(deleted)") + printer_cooldown = world.time + PRINTER_COOLDOWN + var/datum/db_query/query_library_print = SSdbcore.NewQuery( + "SELECT * FROM [format_table_name("library")] WHERE id=:id AND isnull(deleted)", + list("id" = id) + ) if(!query_library_print.Execute()) qdel(query_library_print) say("PRINTER ERROR! Failed to print document (0x0000000F)") @@ -480,24 +479,24 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums B.author = author B.dat = content B.icon_state = "book[rand(1,8)]" - visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") + visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") break qdel(query_library_print) if(href_list["printbible"]) - if(cooldown < world.time) + if(printer_cooldown < world.time) var/obj/item/storage/book/bible/B = new /obj/item/storage/book/bible(src.loc) if(GLOB.bible_icon_state && GLOB.bible_item_state) B.icon_state = GLOB.bible_icon_state B.item_state = GLOB.bible_item_state B.name = GLOB.bible_name B.deity_name = GLOB.deity - cooldown = world.time + PRINTER_COOLDOWN + printer_cooldown = world.time + PRINTER_COOLDOWN else say("Printer currently unavailable, please wait a moment.") if(href_list["printposter"]) - if(cooldown < world.time) + if(printer_cooldown < world.time) new /obj/item/poster/random_official(src.loc) - cooldown = world.time + PRINTER_COOLDOWN + printer_cooldown = world.time + PRINTER_COOLDOWN else say("Printer currently unavailable, please wait a moment.") add_fingerprint(usr) @@ -521,7 +520,10 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums else return ..() -/obj/machinery/libraryscanner/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) +/obj/machinery/libraryscanner/attack_hand(mob/user) + . = ..() + if(.) + return usr.set_machine(src) var/dat = "" // if(cache) @@ -584,14 +586,14 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums return if(!user.transferItemToLoc(P, src)) return - user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") - audible_message("[src] begins to hum as it warms up its printing drums.") + user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") + audible_message("[src] begins to hum as it warms up its printing drums.") busy = TRUE sleep(rand(200,400)) busy = FALSE if(P) if(!stat) - visible_message("[src] whirs as it prints and binds a new book.") + visible_message("[src] whirs as it prints and binds a new book.") var/obj/item/book/B = new(src.loc) B.dat = P.info B.name = "Print Job #" + "[rand(100, 999)]" diff --git a/code/modules/library/random_books.dm b/code/modules/library/random_books.dm index accd477387..d60609147a 100644 --- a/code/modules/library/random_books.dm +++ b/code/modules/library/random_books.dm @@ -10,78 +10,83 @@ /obj/item/book/random icon_state = "random_book" - var/amount = 1 - var/category = null + /// The category of books to pick from when creating this book. + var/random_category = null + /// If this book has already been 'generated' yet. + var/random_loaded = FALSE -/obj/item/book/random/Initialize() - ..() - create_random_books(amount, src.loc, TRUE, category) - return INITIALIZE_HINT_QDEL +/obj/item/book/random/Initialize(mapload) + . = ..() + icon_state = "book[rand(1,8)]" -/obj/item/book/random/triple - amount = 3 +/obj/item/book/random/attack_self() + if(!random_loaded) + create_random_books(1, loc, TRUE, random_category, src) + random_loaded = TRUE + return ..() /obj/structure/bookcase/random - var/category = null - var/book_count = 2 + load_random_books = TRUE + books_to_load = 2 icon_state = "random_bookcase" - anchored = TRUE - state = 2 /obj/structure/bookcase/random/Initialize(mapload) . = ..() - if(!book_count || !isnum(book_count)) - update_icon() - return - book_count += pick(-1,-1,0,1,1) - create_random_books(book_count, src, FALSE, category) + if(books_to_load && isnum(books_to_load)) + books_to_load += pick(-1,-1,0,1,1) update_icon() -/proc/create_random_books(amount = 2, location, fail_loud = FALSE, category = null) +/proc/create_random_books(amount, location, fail_loud = FALSE, category = null, obj/item/book/existing_book) . = list() if(!isnum(amount) || amount<1) return if (!SSdbcore.Connect()) - if(fail_loud || prob(5)) - var/obj/item/paper/P = new(location) - P.info = "There once was a book from Nantucket
But the database failed us, so f*$! it.
I tried to be good to you
Now this is an I.O.U
If you're feeling entitled, well, stuff it!

~" - P.update_icon() + if(existing_book && (fail_loud || prob(5))) + existing_book.author = "???" + existing_book.title = "Strange book" + existing_book.name = "Strange book" + existing_book.dat = "There once was a book from Nantucket
But the database failed us, so f*$! it.
I tried to be good to you
Now this is an I.O.U
If you're feeling entitled, well, stuff it!

~" return if(prob(25)) category = null - var/c = category? " AND category='[sanitizeSQL(category)]'" :"" - var/datum/DBQuery/query_get_random_books = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE isnull(deleted)[c] GROUP BY title ORDER BY rand() LIMIT [amount];") // isdeleted copyright (c) not me + var/datum/db_query/query_get_random_books = SSdbcore.NewQuery({" + SELECT author, title, content + FROM [format_table_name("library")] + WHERE isnull(deleted) AND (:category IS NULL OR category = :category) + ORDER BY rand() LIMIT :limit + "}, list("category" = category, "limit" = amount)) if(query_get_random_books.Execute()) while(query_get_random_books.NextRow()) - var/obj/item/book/B = new(location) - . += B - B.author = query_get_random_books.item[2] - B.title = query_get_random_books.item[3] - B.dat = query_get_random_books.item[4] + var/obj/item/book/B + B = existing_book ? existing_book : new(location) + B.author = query_get_random_books.item[1] + B.title = query_get_random_books.item[2] + B.dat = query_get_random_books.item[3] B.name = "Book: [B.title]" - B.icon_state= "book[rand(1,8)]" + if(!existing_book) + B.icon_state= "book[rand(1,8)]" qdel(query_get_random_books) /obj/structure/bookcase/random/fiction name = "bookcase (Fiction)" - category = "Fiction" + random_category = "Fiction" /obj/structure/bookcase/random/nonfiction name = "bookcase (Non-Fiction)" - category = "Non-fiction" + random_category = "Non-fiction" /obj/structure/bookcase/random/religion name = "bookcase (Religion)" - category = "Religion" + random_category = "Religion" /obj/structure/bookcase/random/adult name = "bookcase (Adult)" - category = "Adult" + random_category = "Adult" /obj/structure/bookcase/random/reference name = "bookcase (Reference)" - category = "Reference" + random_category = "Reference" var/ref_book_prob = 20 /obj/structure/bookcase/random/reference/Initialize(mapload) . = ..() - while(book_count > 0 && prob(ref_book_prob)) - book_count-- + while(books_to_load > 0 && prob(ref_book_prob)) + books_to_load-- new /obj/item/book/manual/random(src) diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index dbab6a558c..11f1d58c0c 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -54,24 +54,7 @@ output += "

[LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]

" if(!IsGuestKey(src.key)) - if (SSdbcore.Connect()) - var/isadmin = 0 - if(src.client && src.client.holder) - isadmin = 1 - var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")") - var/rs = REF(src) - if(query_get_new_polls.Execute()) - var/newpoll = 0 - if(query_get_new_polls.NextRow()) - newpoll = 1 - - if(newpoll) - output += "

Show Player Polls (NEW!)

" - else - output += "

Show Player Polls

" - qdel(query_get_new_polls) - if(QDELETED(src)) - return + output += playerpolls() output += "" @@ -81,6 +64,41 @@ popup.set_content(output) popup.open(FALSE) +/mob/dead/new_player/proc/playerpolls() + var/output = "" //hey tg why is this a list? + if (SSdbcore.Connect()) + var/isadmin = FALSE + if(client?.holder) + isadmin = TRUE + var/datum/db_query/query_get_new_polls = SSdbcore.NewQuery({" + SELECT id FROM [format_table_name("poll_question")] + WHERE (adminonly = 0 OR :isadmin = 1) + AND Now() BETWEEN starttime AND endtime + AND deleted = 0 + AND id NOT IN ( + SELECT pollid FROM [format_table_name("poll_vote")] + WHERE ckey = :ckey + AND deleted = 0 + ) + AND id NOT IN ( + SELECT pollid FROM [format_table_name("poll_textreply")] + WHERE ckey = :ckey + AND deleted = 0 + ) + "}, list("isadmin" = isadmin, "ckey" = ckey)) + var/rs = REF(src) + if(!query_get_new_polls.Execute()) + qdel(query_get_new_polls) + return + if(query_get_new_polls.NextRow()) + output += "

Show Player Polls (NEW!)

" + else + output += "

Show Player Polls

" + qdel(query_get_new_polls) + if(QDELETED(src)) + return + return output + /mob/dead/new_player/proc/age_gate() var/list/dat = list("
") dat += "Enter your date of birth here, to confirm that you are over 18.
" @@ -123,71 +141,76 @@ if(!client.set_db_player_flags()) message_admins("Blocked [src] from new player panel because age gate could not access player database flags.") return FALSE - else - var/dbflags = client.prefs.db_flags - if(dbflags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE) //they have not completed age gate - var/age_verification = age_gate() - if(age_verification != 1) - client.add_system_note("Automated-Age-Gate", "Failed automatic age gate process.") - //ban them and kick them - //parameters used by sql line, easier to read: - var/bantype_str = "ADMIN_PERMABAN" - var/reason = "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification." - var/duration = -1 - var/sql_ckey = sanitizeSQL(client.ckey) - var/computerid = client.computer_id - if(!computerid) - computerid = "0" - var/sql_computerid = sanitizeSQL(computerid) - var/ip = client.address - if(!ip) - ip = "0.0.0.0" - var/sql_ip = sanitizeSQL(ip) + if(!(client.prefs.db_flags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE)) //completed? Skip + return TRUE - //parameter not used as there's no job but i want to fill out all parameters for the insert line - var/sql_job + var/age_verification = age_gate() + //ban them and kick them + if(age_verification != 1) + // this isn't code, this is paragraphs. + var/player_ckey = ckey(client.ckey) + // record all admins and non-admins online at the time + var/list/clients_online = GLOB.clients.Copy() + var/list/admins_online = GLOB.admins.Copy() //list() // remove the GLOB.admins.Copy() and the comments if you want the pure admins_online check + // for(var/client/C in clients_online) + // if(C.holder) //deadmins aren't included since they wouldn't show up on adminwho + // admins_online += C + var/who = clients_online.Join(", ") + var/adminwho = admins_online.Join(", ") - // these are typically the banning admin's, but it's the system so we leave them null, but they're still here for the sake of a full set of values - var/sql_a_ckey - var/sql_a_computerid - var/sql_a_ip + var/datum/db_query/query_add_ban = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("ban")] + (bantime, server_ip, server_port , round_id, bantype, reason, job, duration, expiration_time, ckey, computerid, ip, a_ckey, a_computerid, a_ip, who, adminwho) + VALUES (Now(), INET_ATON(:server_ip), :server_port, :round_id, :bantype_str, :reason, :role, :duration, Now() + INTERVAL :duration MINUTE, :ckey, :computerid, INET_ATON(:ip), :a_ckey, :a_computerid, INET_ATON(:a_ip), :who, :adminwho)"}, + list( + // Server info + "server_ip" = world.internet_address || 0, + "server_port" = world.port, + "round_id" = GLOB.round_id, + // Client ban info + "bantype_str" = "ADMIN_PERMABAN", + "reason" = "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification.", + "role" = null, + "duration" = -1, + "ckey" = player_ckey, + "ip" = client.address || null, + "computerid" = client.computer_id || null, + // Admin banning info + "a_ckey" = "SYSTEM (Automated-Age-Gate)", // the server + "a_ip" = null, //key_name + "a_computerid" = "0", + "who" = who, + "adminwho" = adminwho + )) - // record all admins and non-admins online at the time - var/who - for(var/client/C in GLOB.clients) - if(!who) - who = "[C]" - else - who += ", [C]" + client.add_system_note("Automated-Age-Gate", "Failed automatic age gate process.") + if(!query_add_ban.Execute()) + // this is the part where you should panic. + qdel(query_add_ban) + message_admins("WARNING! Failed to ban [ckey] for failing the automatic age gate.") + send2tgs_adminless_only("WARNING! Failed to ban [ckey] for failing the automatic age gate.") + qdel(client) + return FALSE + qdel(query_add_ban) - var/adminwho - for(var/client/C in GLOB.admins) - if(!adminwho) - adminwho = "[C]" - else - adminwho += ", [C]" + create_message("note", player_ckey, "SYSTEM (Automated-Age-Gate)", "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification.", null, null, 0, 0, null, 0, "high") - var/sql = "INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`round_id`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]', '[bantype_str]', '[reason]', '[sql_job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[sql_ckey]', '[sql_computerid]', INET_ATON('[sql_ip]'), '[sql_a_ckey]', '[sql_a_computerid]', INET_ATON('[sql_a_ip]'), '[who]', '[adminwho]')" - var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql) - qdel(query_add_ban) + // announce this + message_admins("[ckey] has been banned for failing the automatic age gate.") + send2tgs_adminless_only("[ckey] has been banned for failing the automatic age gate.") - // announce this - message_admins("[html_encode(client.ckey)] has been banned for failing the automatic age gate.") - send2irc("[html_encode(client.ckey)] has been banned for failing the automatic age gate.") + // removing the client disconnects them + qdel(client) - // removing the client disconnects them - qdel(client) + return FALSE + //they claim to be of age, so allow them to continue and update their flags + client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_COMPLETE, TRUE) + client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_INCOMPLETE, FALSE) + //log this + message_admins("[ckey] has joined through the automated age gate process.") - return FALSE - else - //they claim to be of age, so allow them to continue and update their flags - client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_COMPLETE, TRUE) - client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_INCOMPLETE, FALSE) - //log this - message_admins("[ckey] has joined through the automated age gate process.") - return TRUE return TRUE /mob/dead/new_player/Topic(href, href_list[]) diff --git a/code/modules/mob/dead/new_player/poll.dm b/code/modules/mob/dead/new_player/poll.dm index 84f5e97a3b..9b6e22bc23 100644 --- a/code/modules/mob/dead/new_player/poll.dm +++ b/code/modules/mob/dead/new_player/poll.dm @@ -4,9 +4,13 @@ /mob/dead/new_player/proc/handle_player_polling() if(!SSdbcore.IsConnected()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/datum/DBQuery/query_poll_get = SSdbcore.NewQuery("SELECT id, question FROM [format_table_name("poll_question")] WHERE Now() BETWEEN starttime AND endtime [(client.holder ? "" : "AND adminonly = false")]") + var/datum/db_query/query_poll_get = SSdbcore.NewQuery({" + SELECT id, question + FROM [format_table_name("poll_question")] + WHERE Now() BETWEEN starttime AND endtime [(client.holder ? "" : "AND adminonly = false")] + "}) if(!query_poll_get.warn_execute()) qdel(query_poll_get) return @@ -27,9 +31,15 @@ if(!pollid) return if (!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/datum/DBQuery/query_poll_get_details = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]") + var/datum/db_query/query_poll_get_details = SSdbcore.NewQuery({" + SELECT starttime, endtime, question, polltype, multiplechoiceoptions + FROM [format_table_name("poll_question")] + WHERE id = :id + "}, list( + "id" = pollid + )) if(!query_poll_get_details.warn_execute()) qdel(query_poll_get_details) return @@ -47,7 +57,14 @@ qdel(query_poll_get_details) switch(polltype) if(POLLTYPE_OPTION) - var/datum/DBQuery/query_option_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_option_get_votes = SSdbcore.NewQuery({" + SELECT optionid + FROM [format_table_name("poll_vote")] + WHERE pollid = :id AND ckey = :ckey + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_option_get_votes.warn_execute()) qdel(query_option_get_votes) return @@ -56,7 +73,13 @@ votedoptionid = text2num(query_option_get_votes.item[1]) qdel(query_option_get_votes) var/list/datum/polloption/options = list() - var/datum/DBQuery/query_option_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + var/datum/db_query/query_option_options = SSdbcore.NewQuery({" + SELECT id, text + FROM [format_table_name("poll_option")] + WHERE pollid = :id + "}, list( + "id" = pollid + )) if(!query_option_options.warn_execute()) qdel(query_option_options) return @@ -92,7 +115,14 @@ src << browse(null ,"window=playerpolllist") src << browse(output,"window=playerpoll;size=500x250") if(POLLTYPE_TEXT) - var/datum/DBQuery/query_text_get_votes = SSdbcore.NewQuery("SELECT replytext FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_text_get_votes = SSdbcore.NewQuery({" + SELECT replytext + FROM [format_table_name("poll_textreply")] + WHERE pollid = :id AND ckey = :ckey + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_text_get_votes.warn_execute()) qdel(query_text_get_votes) return @@ -120,7 +150,13 @@ src << browse(null ,"window=playerpolllist") src << browse(output,"window=playerpoll;size=500x500") if(POLLTYPE_RATING) - var/datum/DBQuery/query_rating_get_votes = SSdbcore.NewQuery("SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v WHERE o.pollid = [pollid] AND v.ckey = '[ckey]' AND o.id = v.optionid") + var/datum/db_query/query_rating_get_votes = SSdbcore.NewQuery({" + SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v + WHERE o.pollid = :id AND v.ckey = :ckey AND o.id = v.optionid + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_rating_get_votes.warn_execute()) qdel(query_rating_get_votes) return @@ -140,7 +176,13 @@ output += "" var/minid = 999999 var/maxid = 0 - var/datum/DBQuery/query_rating_options = SSdbcore.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + var/datum/db_query/query_rating_options = SSdbcore.NewQuery({" + SELECT id, text, minval, maxval, descmin, descmid, descmax + FROM [format_table_name("poll_option")] + WHERE pollid = :id + "}, list( + "id" = pollid + )) if(!query_rating_options.warn_execute()) qdel(query_rating_options) return @@ -177,7 +219,14 @@ src << browse(null ,"window=playerpolllist") src << browse(output,"window=playerpoll;size=500x500") if(POLLTYPE_MULTI) - var/datum/DBQuery/query_multi_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_multi_get_votes = SSdbcore.NewQuery({" + SELECT optionid + FROM [format_table_name("poll_vote")] + WHERE pollid = :id AND ckey = :ckey + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_multi_get_votes.warn_execute()) qdel(query_multi_get_votes) return @@ -188,7 +237,13 @@ var/list/datum/polloption/options = list() var/maxoptionid = 0 var/minoptionid = 0 - var/datum/DBQuery/query_multi_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + var/datum/db_query/query_multi_options = SSdbcore.NewQuery({" + SELECT id, text + FROM [format_table_name("poll_option")] + WHERE pollid = :id + "}, list( + "id" = pollid + )) if(!query_multi_options.warn_execute()) qdel(query_multi_options) return @@ -231,8 +286,10 @@ if(POLLTYPE_IRV) var/datum/asset/irv_assets = get_asset_datum(/datum/asset/group/irv) irv_assets.send(src) - - var/datum/DBQuery/query_irv_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_irv_get_votes = SSdbcore.NewQuery({" + SELECT optionid FROM [format_table_name("poll_vote")] + WHERE pollid = :pollid AND ckey = :ckey AND deleted = 0 + "}, list("pollid" = pollid, "ckey" = ckey)) if(!query_irv_get_votes.warn_execute()) qdel(query_irv_get_votes) return @@ -244,7 +301,13 @@ var/list/datum/polloption/options = list() - var/datum/DBQuery/query_irv_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + var/datum/db_query/query_irv_options = SSdbcore.NewQuery({" + SELECT id, text + FROM [format_table_name("poll_option")] + WHERE pollid = :id + "}, list( + "id" = pollid + )) if(!query_irv_options.warn_execute()) qdel(query_irv_options) return @@ -352,16 +415,23 @@ if (text) table = "poll_textreply" if (!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/datum/DBQuery/query_hasvoted = SSdbcore.NewQuery("SELECT id FROM `[format_table_name(table)]` WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_hasvoted = SSdbcore.NewQuery({" + SELECT id + FROM `[format_table_name(table)]` + WHERE pollid = :id AND ckey = :ckey + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_hasvoted.warn_execute()) qdel(query_hasvoted) return if(query_hasvoted.NextRow()) qdel(query_hasvoted) if(!silent) - to_chat(usr, "You've already replied to this poll.") + to_chat(usr, "You've already replied to this poll.", confidential = TRUE) return TRUE qdel(query_hasvoted) return FALSE @@ -376,24 +446,31 @@ /mob/dead/new_player/proc/vote_rig_check() if (usr != src) if (!usr || !src) - return 0 + return FALSE //we gots ourselfs a dirty cheater on our hands! log_game("[key_name(usr)] attempted to rig the vote by voting as [key]") message_admins("[key_name_admin(usr)] attempted to rig the vote by voting as [key]") to_chat(usr, "You don't seem to be [key].") to_chat(src, "Something went horribly wrong processing your vote. Please contact an administrator, they should have gotten a message about this") - return 0 - return 1 + return FALSE + return TRUE /mob/dead/new_player/proc/vote_valid_check(pollid, holder, type) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 + return pollid = text2num(pollid) if (!pollid || pollid < 0) return 0 //validate the poll is actually the right type of poll and its still active - var/datum/DBQuery/query_validate_poll = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime AND polltype = '[type]' [(holder ? "" : "AND adminonly = false")]") + var/datum/db_query/query_validate_poll = SSdbcore.NewQuery({" + SELECT id + FROM [format_table_name("poll_question")] + WHERE id = :id AND Now() BETWEEN starttime AND endtime AND polltype = :type [(holder ? "" : "AND adminonly = false")] + "}, list( + "id" = pollid, + "type" = type + )) if(!query_validate_poll.warn_execute()) qdel(query_validate_poll) return 0 @@ -403,88 +480,66 @@ qdel(query_validate_poll) return 1 +/** + * Processes vote form data and saves results to the database for an IRV type poll. + * + */ /mob/dead/new_player/proc/vote_on_irv_poll(pollid, list/votelist) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - pollid = text2num(pollid) - if (!pollid || pollid < 0) - return 0 - if (!votelist || !istype(votelist) || !votelist.len) - return 0 - if (!client) - return 0 - //save these now so we can still process the vote if the client goes away while we process. - var/datum/admins/holder = client.holder - var/rank = "Player" - if (holder) - rank = holder.rank.name - var/ckey = client.ckey - var/address = client.address - - //validate the poll - if (!vote_valid_check(pollid, holder, POLLTYPE_IRV)) + return + if(IsAdminAdvancedProcCall()) + return + if(!vote_rig_check()) + return + if(!pollid) + return + // var/list/votelist = splittext(href_list["IRVdata"], ",") + if(!length(votelist)) + to_chat(src, "No ordering data found. Please try again or contact an administrator.") + var/admin_rank = "Player" + if(!QDELETED(client) && client.holder) + admin_rank = client.holder.rank.name + if (!vote_valid_check(pollid, client?.holder, POLLTYPE_IRV)) return 0 - //lets collect the options - var/datum/DBQuery/query_irv_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - if(!query_irv_id.warn_execute()) - qdel(query_irv_id) - return 0 - var/list/optionlist = list() - while (query_irv_id.NextRow()) - optionlist += text2num(query_irv_id.item[1]) - qdel(query_irv_id) + var/list/special_columns = list( + "datetime" = "NOW()", + "ip" = "INET_ATON(?)", + ) - //validate their votes are actually in the list of options and actually numbers - var/list/numberedvotelist = list() - for (var/vote in votelist) - vote = text2num(vote) - numberedvotelist += vote - if (!vote) //this is fine because voteid starts at 1, so it will never be 0 - to_chat(src, "Error: Invalid (non-numeric) votes in the vote data.") - return 0 - if (!(vote in optionlist)) - to_chat(src, "Votes for choices that do not appear to be in the poll detected.") - return 0 - if (!numberedvotelist.len) - to_chat(src, "Invalid vote data") - return 0 - - //lets add the vote, first we generate an insert statement. - - var/sqlrowlist = "" - for (var/vote in numberedvotelist) - if (sqlrowlist != "") - sqlrowlist += ", " //a comma (,) at the start of the first row to insert will trigger a SQL error - sqlrowlist += "(Now(), [pollid], [vote], '[sanitizeSQL(ckey)]', INET_ATON('[sanitizeSQL(address)]'), '[sanitizeSQL(rank)]')" - - //now lets delete their old votes (if any) - var/datum/DBQuery/query_irv_del_old = SSdbcore.NewQuery("DELETE FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_irv_del_old.warn_execute()) - qdel(query_irv_del_old) - return 0 - qdel(query_irv_del_old) - - //now to add the new ones. - var/datum/DBQuery/query_irv_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES [sqlrowlist]") - if(!query_irv_vote.warn_execute()) - qdel(query_irv_vote) - return 0 - qdel(query_irv_vote) - if(!QDELETED(src)) - src << browse(null,"window=playerpoll") - return 1 + var/sql_votes = list() + for(var/o in votelist) + var/voteid = text2num(o) + if(!voteid) + continue + sql_votes += list(list( + "pollid" = pollid, + "optionid" = voteid, + "ckey" = ckey, + "ip" = client.address, + "adminrank" = admin_rank + )) + //IRV results are calculated based on id order, we delete all of a user's votes to avoid potential errors caused by revoting and option editing + var/datum/db_query/query_delete_irv_votes = SSdbcore.NewQuery({" + UPDATE [format_table_name("poll_vote")] SET deleted = 1 WHERE pollid = :pollid AND ckey = :ckey + "}, list("pollid" = pollid, "ckey" = ckey)) + if(!query_delete_irv_votes.warn_execute()) + qdel(query_delete_irv_votes) + return + qdel(query_delete_irv_votes) + SSdbcore.MassInsert(format_table_name("poll_vote"), sql_votes, special_columns = special_columns) + return TRUE /mob/dead/new_player/proc/vote_on_poll(pollid, optionid) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 + return + if(!vote_rig_check()) + return + if(IsAdminAdvancedProcCall()) + return if(!pollid || !optionid) return //validate the poll @@ -493,10 +548,19 @@ var/voted = poll_check_voted(pollid) if(isnull(voted) || voted) //Failed or already voted. return - var/adminrank = sanitizeSQL(poll_rank()) + var/adminrank = poll_rank() if(!adminrank) return - var/datum/DBQuery/query_option_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')") + var/datum/db_query/query_option_vote = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) + VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank) + "}, list( + "pollid" = pollid, + "optionid" = optionid, + "ckey" = ckey, + "address" = client.address, + "adminrank" = adminrank + )) if(!query_option_vote.warn_execute()) qdel(query_option_vote) return @@ -506,11 +570,13 @@ return 1 /mob/dead/new_player/proc/log_text_poll_reply(pollid, replytext) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 + return + if(!vote_rig_check()) + return + if(IsAdminAdvancedProcCall()) + return if(!pollid) return //validate the poll @@ -522,18 +588,23 @@ var/voted = poll_check_voted(pollid, text = TRUE, silent = TRUE) if(isnull(voted)) return - var/adminrank = sanitizeSQL(poll_rank()) + var/adminrank = poll_rank() if(!adminrank) return - replytext = sanitizeSQL(replytext) if(!(length(replytext) > 0) || !(length(replytext) <= 8000)) to_chat(usr, "The text you entered was invalid or too long. Please correct the text and submit again.") return - var/datum/DBQuery/query_text_vote + var/datum/db_query/query_text_vote if(!voted) - query_text_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_textreply")] (datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (Now(), [pollid], '[ckey]', INET_ATON('[client.address]'), '[replytext]', '[adminrank]')") + query_text_vote = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_textreply")] (datetime, pollid, ckey, ip, replytext, adminrank) + VALUES (Now(), :pollid, :ckey, INET_ATON(:address), :replytext, :adminrank) + "}, list("pollid" = pollid, "ckey" = ckey, "address" = client.address, "replytext" = replytext, "adminrank" = adminrank)) else - query_text_vote = SSdbcore.NewQuery("UPDATE [format_table_name("poll_textreply")] SET datetime = Now(), ip = INET_ATON('[client.address]'), replytext = '[replytext]' WHERE pollid = '[pollid]' AND ckey = '[ckey]'") + query_text_vote = SSdbcore.NewQuery({" + UPDATE [format_table_name("poll_textreply")] + SET datetime = Now(), ip = INET_ATON(:address), replytext = :replytext WHERE pollid = :pollid AND ckey = :ckey + "}, list("address" = client.address, "replytext" = replytext, "pollid" = pollid, "ckey" = ckey)) if(!query_text_vote.warn_execute()) qdel(query_text_vote) return @@ -543,17 +614,26 @@ return 1 /mob/dead/new_player/proc/vote_on_numval_poll(pollid, optionid, rating) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 + return + if(!vote_rig_check()) + return + if(IsAdminAdvancedProcCall()) + return if(!pollid || !optionid || !rating) return //validate the poll if (!vote_valid_check(pollid, client.holder, POLLTYPE_RATING)) return 0 - var/datum/DBQuery/query_numval_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND ckey = '[ckey]'") + var/datum/db_query/query_numval_hasvoted = SSdbcore.NewQuery({" + SELECT id + FROM [format_table_name("poll_vote")] + WHERE optionid = :id AND ckey = :ckey + "}, list( + "id" = optionid, + "ckey" = ckey + )) if(!query_numval_hasvoted.warn_execute()) qdel(query_numval_hasvoted) return @@ -565,8 +645,10 @@ var/adminrank = "Player" if(client.holder) adminrank = client.holder.rank.name - adminrank = sanitizeSQL(adminrank) - var/datum/DBQuery/query_numval_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]', [(isnull(rating)) ? "null" : rating])") + var/datum/db_query/query_numval_vote = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) + VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank, :rating) + "}, list("pollid" = pollid, "optionid" = optionid, "ckey" = ckey, "address" = client.address, "adminrank" = adminrank, "rating" = isnull(rating) ? "null" : rating)) if(!query_numval_vote.warn_execute()) qdel(query_numval_vote) return @@ -575,46 +657,59 @@ usr << browse(null,"window=playerpoll") return 1 +/** + * Processes vote form data and saves results to the database for a multiple choice type poll. + * + */ /mob/dead/new_player/proc/vote_on_multi_poll(pollid, optionid) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - if(!pollid || !optionid) - return 1 + return + if(!vote_rig_check()) + return + if(IsAdminAdvancedProcCall()) + return //validate the poll - if (!vote_valid_check(pollid, client.holder, POLLTYPE_MULTI)) - return 0 - var/datum/DBQuery/query_multi_choicelen = SSdbcore.NewQuery("SELECT multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]") - if(!query_multi_choicelen.warn_execute()) - qdel(query_multi_choicelen) - return 1 - var/i - if(query_multi_choicelen.NextRow()) - i = text2num(query_multi_choicelen.item[1]) - qdel(query_multi_choicelen) - var/datum/DBQuery/query_multi_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_multi_hasvoted.warn_execute()) - qdel(query_multi_hasvoted) - return 1 - while(i) - if(query_multi_hasvoted.NextRow()) - i-- - else - break - qdel(query_multi_hasvoted) - if(!i) - return 2 - var/adminrank = "Player" - if(!QDELETED(client) && client.holder) - adminrank = client.holder.rank.name - adminrank = sanitizeSQL(adminrank) - var/datum/DBQuery/query_multi_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')") - if(!query_multi_vote.warn_execute()) - qdel(query_multi_vote) - return 1 - qdel(query_multi_vote) - if(!QDELETED(usr)) - usr << browse(null,"window=playerpoll") - return 0 + if(!vote_valid_check(pollid, client.holder, POLLTYPE_MULTI)) + return + if(!pollid || !optionid) + return + // if(length(href_list) > 2) + // href_list.Cut(1,3) //first two values aren't options + // else + // to_chat(src, "No options were selected.") + + var/special_columns = list( + "datetime" = "NOW()", + "ip" = "INET_ATON(?)", + ) + + var/sql_votes = list() + // var/vote_count = 0 + // for(var/h in href_list) + // if(vote_count == poll.options_allowed) + // to_chat(src, "Allowed option count exceeded, only the first [poll.options_allowed] selected options have been saved.") + // break + // vote_count++ + // var/datum/poll_option/option = locate(h) in poll.options + var/admin_rank = "Player" + if(!QDELETED(client) && client?.holder) + admin_rank = client.holder.rank.name + sql_votes += list(list( + "pollid" = pollid, + "optionid" = optionid, + "ckey" = ckey, + "ip" = client.address, + "adminrank" = admin_rank + )) + /*with revoting and poll editing possible there can be an edge case where a poll is changed to allow less multiple choice options than a user has already voted on + rather than trying to calculate which options should be updated and which deleted, we just delete all of a user's votes and re-insert as needed*/ + var/datum/db_query/query_delete_multi_votes = SSdbcore.NewQuery({" + UPDATE [format_table_name("poll_vote")] SET deleted = 1 WHERE pollid = :pollid AND ckey = :ckey + "}, list("pollid" = pollid, "ckey" = ckey)) + if(!query_delete_multi_votes.warn_execute()) + qdel(query_delete_multi_votes) + return + qdel(query_delete_multi_votes) + SSdbcore.MassInsert(format_table_name("poll_vote"), sql_votes, special_columns = special_columns) + return TRUE diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 85d91e992f..9f80b5a933 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -143,10 +143,10 @@ parry_efficiency_considered_successful = 0.01 parry_efficiency_to_counterattack = 0.01 parry_max_attacks = INFINITY - parry_failed_cooldown_duration = 3 SECONDS - parry_failed_stagger_duration = 2 SECONDS - parry_cooldown = 3 SECONDS - parry_failed_clickcd_duration = 0.8 SECONDS + parry_failed_cooldown_duration = 1.5 SECONDS + parry_failed_stagger_duration = 1 SECONDS + parry_cooldown = 0 + parry_failed_clickcd_duration = 0.8 parry_data = list( // yeah it's snowflake "UNARMED_PARRY_STAGGER" = 3 SECONDS, diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 432052d322..600045e9c5 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -80,6 +80,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) var/list/inherent_traits = list() var/inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID + var/list/blacklisted_quirks = list() // Quirks that will be removed upon gaining this species, to be defined by species + var/list/removed_quirks = list() // Quirks that got removed due to being blacklisted, and will be restored when on_species_loss() is called + var/attack_verb = "punch" // punch-specific attack verb var/sound/attack_sound = 'sound/weapons/punch1.ogg' var/sound/miss_sound = 'sound/weapons/punchmiss.ogg' @@ -342,6 +345,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) for(var/X in inherent_traits) ADD_TRAIT(C, X, SPECIES_TRAIT) + //lets remove those conflicting quirks + remove_blacklisted_quirks(C) + if(TRAIT_VIRUSIMMUNE in inherent_traits) for(var/datum/disease/A in C.diseases) A.cure(FALSE) @@ -395,6 +401,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) for(var/X in inherent_traits) REMOVE_TRAIT(C, X, SPECIES_TRAIT) + // lets restore the quirks that got removed when gaining this species + restore_quirks(C) + C.remove_movespeed_modifier(/datum/movespeed_modifier/species) if(mutant_bodyparts["meat_type"]) @@ -424,6 +433,26 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src) +// shamelessly inspired by antag_datum.remove_blacklisted_quirks() +/datum/species/proc/remove_blacklisted_quirks(mob/living/carbon/C) + var/mob/living/L = C.mind?.current + if(istype(L)) + var/list/my_quirks = L.client?.prefs.all_quirks.Copy() + SSquirks.filter_quirks(my_quirks, blacklisted_quirks) + for(var/q in L.roundstart_quirks) + var/datum/quirk/Q = q + if(!(SSquirks.quirk_name_by_path(Q.type) in my_quirks)) + L.remove_quirk(Q.type) + removed_quirks += Q.type + +// restore any quirks that we removed +/datum/species/proc/restore_quirks(mob/living/carbon/C) + var/mob/living/L = C.mind?.current + if(istype(L)) + for(var/q in removed_quirks) + L.add_quirk(q) + + /datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour) H.remove_overlay(HAIR_LAYER) var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD) @@ -1454,9 +1483,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) var/armor_block = target.run_armor_check(affecting, "melee") - if(HAS_TRAIT(user, TRAIT_MAULER)) // maulers get 15 armorpierce because if you're going to punch someone you might as well do a good job of it - armor_block = target.run_armor_check(affecting, "melee", armour_penetration = 15) // lot of good that sec jumpsuit did you - playsound(target.loc, user.dna.species.attack_sound, 25, 1, -1) target.visible_message("[user] [atk_verb]ed [target]!", \ "[user] [atk_verb]ed you!", null, COMBAT_MESSAGE_RANGE, null, \ @@ -1473,9 +1499,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) target.apply_damage(damage*1.5, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus) target.apply_damage(damage*0.5, STAMINA, affecting, armor_block) log_combat(user, target, "kicked") - else if(HAS_TRAIT(user, TRAIT_MAULER)) // mauler punches deal 1.1x raw damage + 1.3x stam damage, and have some armor pierce - target.apply_damage(damage*1.1, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus) - target.apply_damage(damage*1.3, STAMINA, affecting, armor_block) + else if(HAS_TRAIT(user, TRAIT_MAULER)) // mauler punches deal 1.2x raw damage but nstam + target.apply_damage(damage*1.2, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus) log_combat(user, target, "punched (mauler)") else //other attacks deal full raw damage + 2x in stamina damage target.apply_damage(damage, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus) diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index 385dd94f04..57a11481d7 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -37,6 +37,7 @@ armor = 20 // 120 damage to KO a zombie, which kills it speedmod = 1.6 // they're very slow mutanteyes = /obj/item/organ/eyes/night_vision/zombie + blacklisted_quirks = list(/datum/quirk/nonviolent) var/heal_rate = 1 var/regen_cooldown = 0 diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index a6fdfdc793..a6453e58b7 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -24,7 +24,7 @@ break var/msg = "[key_name_admin(src)] [ADMIN_JMP(src)] was found to have no .loc with an attached client, if the cause is unknown it would be wise to ask how this was accomplished." message_admins(msg) - send2irc_adminless_only("Mob", msg, R_ADMIN) + send2tgs_adminless_only("Mob", msg, R_ADMIN) log_game("[key_name(src)] was found to have no .loc with an attached client.") // This is a temporary error tracker to make sure we've caught everything diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm index c0423286c1..10f8aaf2f4 100644 --- a/code/modules/mob/living/living_active_parry.dm +++ b/code/modules/mob/living/living_active_parry.dm @@ -23,6 +23,9 @@ if(!(combat_flags & COMBAT_FLAG_PARRY_CAPABLE)) to_chat(src, "You are not something that can parry attacks.") return + if(!(mobility_flags & MOBILITY_STAND)) + to_chat(src, "You aren't able to parry without solid footing!") + return // Prioritize item, then martial art, then unarmed. // yanderedev else if time var/obj/item/using_item = get_active_held_item() diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm index 86467624b5..05d6eda435 100644 --- a/code/modules/mob/living/simple_animal/hostile/alien.dm +++ b/code/modules/mob/living/simple_animal/hostile/alien.dm @@ -78,6 +78,15 @@ projectiletype = /obj/item/projectile/neurotox projectilesound = 'sound/weapons/pierce.ogg' +/mob/living/simple_animal/hostile/alien/sentinel/cube + gold_core_spawnable = NO_SPAWN + health = 220 + maxHealth = 220 + melee_damage_lower = 20 + melee_damage_upper = 20 + del_on_death = TRUE + loot = list(/obj/effect/mob_spawn/alien/corpse/humanoid/sentinel) + /mob/living/simple_animal/hostile/alien/queen name = "alien queen" diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 73efad31a5..787c15a5b4 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -425,7 +425,7 @@ Difficulty: Very Hard /obj/machinery/anomalous_crystal/honk //Strips and equips you as a clown. I apologize for nothing observer_desc = "This crystal strips and equips its targets as clowns." - possible_methods = list(ACTIVATE_MOB_BUMP, ACTIVATE_SPEECH) + possible_methods = list(ACTIVATE_TOUCH) //Because We love AOE transformations! activation_sound = 'sound/items/bikehorn.ogg' /obj/machinery/anomalous_crystal/honk/ActivationReaction(mob/user) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index b2c0807222..9dd2441829 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -399,6 +399,14 @@ Difficulty: Medium crusher_loot = list() butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) +/mob/living/simple_animal/hostile/megafauna/dragon/lesser/transformed //ash drake balanced around player control + name = "transformed ash drake" + desc = "A sentient being transformed into an ash drake" + mob_size = MOB_SIZE_HUMAN //prevents crusher vulnerability + move_force = MOVE_FORCE_NORMAL //stops them from destroying and unanchoring shit by walking into it + environment_smash = ENVIRONMENT_SMASH_STRUCTURES //no we dont want sentient megafauna be able to delete the entire station in a minute flat + damage_coeff = list(BRUTE = 0.7, BURN = 0.5, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) //200 health but not locked to standard movespeed, needs armor befitting of a dragon + /mob/living/simple_animal/hostile/megafauna/dragon/lesser/grant_achievement(medaltype,scoretype) return @@ -414,7 +422,8 @@ Difficulty: Medium if(L in hit_list || L == source) continue hit_list += L - L.adjustFireLoss(20) + L.adjustFireLoss(5) + L.adjust_fire_stacks(6) to_chat(L, "You're hit by [source]'s fire breath!") // deals damage to mechs diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm index ae0f5ea3ca..446fe80c7d 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm @@ -42,6 +42,8 @@ A.GiveTarget(target) A.friends = friends A.faction = faction.Copy() + if(!A == /mob/living/simple_animal/hostile/poison/bees/toxin) + A.my_creator = type ranged_cooldown = world.time + ranged_cooldown_time /mob/living/simple_animal/hostile/asteroid/hivelord/AttackingTarget() @@ -88,6 +90,7 @@ density = FALSE del_on_death = 1 var/swarming = FALSE + var/my_creator = null /mob/living/simple_animal/hostile/asteroid/hivelordbrood/Initialize() . = ..() @@ -205,11 +208,7 @@ /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/infest(mob/living/carbon/human/H) visible_message("[name] burrows into the flesh of [H]!") - var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L - if(HAS_TRAIT(H, TRAIT_DWARF)) //dwarf legions aren't just fluff! - L = new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf(H.loc) - else - L = new(H.loc) + var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L = check_infest_type(H) visible_message("[L] staggers to [L.p_their()] feet!") H.death() H.adjustBruteLoss(1000) @@ -217,6 +216,20 @@ H.forceMove(L) qdel(src) +/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/check_infest_type(mob/living/carbon/human/human) + var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L + var/list/blacklisted_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf) + if(HAS_TRAIT(human, TRAIT_DWARF)) //dwarf legions aren't just fluff! + L = new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf(human.loc) + else if(my_creator) + if(my_creator in blacklisted_types) + L = new(human.loc) + else + L = new my_creator(human.loc) + else + L = new(human.loc) + return L + //Advanced Legion is slightly tougher to kill and can raise corpses (revive other legions) /mob/living/simple_animal/hostile/asteroid/hivelord/legion/advanced stat_attack = DEAD diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index e49bd5162b..fcaa77cac4 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -178,10 +178,7 @@ new /obj/effect/temp_visual/monkeyify/humanify(loc) - transformation_timer = addtimer(CALLBACK(src, .proc/finish_humanize, tr_flags), TRANSFORMATION_DURATION, TIMER_UNIQUE) - -/mob/living/carbon/proc/finish_humanize(tr_flags) - transformation_timer = null + sleep(TRANSFORMATION_DURATION) //This entire proc CANNOT be split into two var/list/stored_implants = list() var/list/int_organs = list() diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 32facb69f5..131a098258 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -9,6 +9,7 @@ var/light_on = FALSE integrity_failure = 0.5 max_integrity = 100 + rad_flags = RAD_PROTECT_CONTENTS armor = list("melee" = 0, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0) var/enabled = 0 // Whether the computer is turned on. diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index 438c000a1e..1e6318ecba 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -182,6 +182,7 @@ /obj/item/gun/energy/kinetic_accelerator/proc/reload() cell.give(cell.maxcharge) + process_chamber() if(!suppressed) playsound(src.loc, 'sound/weapons/kenetic_reload.ogg', 60, 1) else diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 45a87e690b..96985514b5 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -45,6 +45,13 @@ trippy = FALSE pH = 8 +//Nicotine is used as a pesticide IRL. +/datum/reagent/drug/nicotine/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(chems.has_reagent(type, 1)) + mytray.adjustToxic(round(chems.get_reagent_amount(type))) + mytray.adjustPests(-rand(1,2)) + /datum/reagent/drug/nicotine/on_mob_life(mob/living/carbon/M) if(prob(1)) var/smoke_message = pick("You feel relaxed.", "You feel calmed.","You feel alert.","You feel rugged.") diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 1975eede70..303fd981fb 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -49,6 +49,11 @@ var/brute_heal = 1 var/burn_heal = 0 +/datum/reagent/consumable/nutriment/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(chems.has_reagent(type, 1)) + mytray.adjustHealth(round(chems.get_reagent_amount(type) * 0.2)) + /datum/reagent/consumable/nutriment/on_mob_life(mob/living/carbon/M) if(!HAS_TRAIT(M, TRAIT_NO_PROCESS_FOOD)) if(prob(50)) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 0959fb58e0..65443b65c7 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -155,6 +155,12 @@ pH = 11 value = REAGENT_VALUE_COMMON +// Healing +/datum/reagent/medicine/cryoxadone/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + mytray.adjustHealth(round(chems.get_reagent_amount(type) * 3)) + mytray.adjustToxic(-round(chems.get_reagent_amount(type) * 3)) + /datum/reagent/medicine/cryoxadone/on_mob_life(mob/living/carbon/M) var/power = -0.00003 * (M.bodytemperature ** 2) + 3 if(M.bodytemperature < T0C) @@ -935,6 +941,12 @@ pH = 0 value = REAGENT_VALUE_RARE +// FEED ME SEYMOUR +/datum/reagent/medicine/strange_reagent/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(chems.has_reagent(type, 1)) + mytray.spawnplant() + /datum/reagent/medicine/strange_reagent/reaction_mob(mob/living/M, method=TOUCH, reac_volume) if(M.stat == DEAD) if(M.suiciding || M.hellbound) //they are never coming back diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 6552d82e95..fe8f923e1a 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -2545,7 +2545,7 @@ M.drowsyness = max(M.drowsyness-5, 0) M.AdjustAllImmobility(-40, FALSE) M.adjustStaminaLoss(-15, FALSE) - M.adjustToxLoss(-3, FALSE) + M.adjustToxLoss(-3, FALSE, TRUE) M.adjustOxyLoss(-3, FALSE) M.adjustBruteLoss(-3, FALSE) M.adjustFireLoss(-3, FALSE) diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index 3f0ebcb3e3..2e05f66cf8 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -32,6 +32,12 @@ value = REAGENT_VALUE_VERY_COMMON taste_description = "metal" +//It has stable IN THE NAME. IT WAS MADE FOR THIS MOMENT. +/datum/reagent/stabilizing_agent/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(myseed && chems.has_reagent(type, 1)) + myseed.adjust_instability(-1) + /datum/reagent/clf3 name = "Chlorine Trifluoride" description = "Makes a temporary 3x3 fireball when it comes into existence, so be careful when mixing. ClF3 applied to a surface burns things that wouldn't otherwise burn, sometimes through the very floors of the station and exposing it to the vacuum of space." @@ -167,6 +173,15 @@ taste_description = "burning" value = REAGENT_VALUE_COMMON +// Smells like victory... +/datum/reagent/napalm/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(chems.has_reagent(type, 1)) + if(!(myseed.resistance_flags & FIRE_PROOF)) + mytray.adjustHealth(-round(chems.get_reagent_amount(type) * 6)) + mytray.adjustToxic(round(chems.get_reagent_amount(type) * 7)) + mytray.adjustWeeds(-rand(5,9)) //At least give them a small reward if they bother. + /datum/reagent/napalm/on_mob_life(mob/living/carbon/M) M.adjust_fire_stacks(1) ..() diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index 15f9b31302..cc37fbbcc4 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -364,7 +364,9 @@ set waitfor = FALSE if(!SSdbcore.Connect()) return - var/datum/DBQuery/query_round_shuttle_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shuttle_name = '[name]' WHERE id = [GLOB.round_id]") + var/datum/db_query/query_round_shuttle_name = SSdbcore.NewQuery({" + UPDATE [format_table_name("round")] SET shuttle_name = :name WHERE id = :round_id + "}, list("name" = name, "round_id" = GLOB.round_id)) query_round_shuttle_name.Execute() qdel(query_round_shuttle_name) @@ -396,7 +398,7 @@ return mode = SHUTTLE_DOCKED setTimer(SSshuttle.emergencyDockTime) - send2irc("Server", "The Emergency Shuttle has docked with the station.") + send2adminchat("Server", "The Emergency Shuttle has docked with the station.") priority_announce("The Emergency Shuttle has docked with the station. You have [timeLeft(600)] minutes to board the Emergency Shuttle.", null, "shuttledock", "Priority") ShuttleDBStuff() diff --git a/code/modules/spells/spell_types/shapeshift.dm b/code/modules/spells/spell_types/shapeshift.dm index e513865246..67c2e3e941 100644 --- a/code/modules/spells/spell_types/shapeshift.dm +++ b/code/modules/spells/spell_types/shapeshift.dm @@ -78,7 +78,7 @@ desc = "Take on the shape a lesser ash drake." invocation = "RAAAAAAAAWR!" - shapeshift_type = /mob/living/simple_animal/hostile/megafauna/dragon/lesser + shapeshift_type = /mob/living/simple_animal/hostile/megafauna/dragon/lesser/transformed /obj/shapeshift_holder diff --git a/code/modules/surgery/advanced/toxichealing.dm b/code/modules/surgery/advanced/toxichealing.dm index 0e0fd10c1c..376fb43c31 100644 --- a/code/modules/surgery/advanced/toxichealing.dm +++ b/code/modules/surgery/advanced/toxichealing.dm @@ -24,6 +24,13 @@ /datum/surgery_step/toxichealing/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) user.visible_message("[user] starts rejuvenating some of [target]'s flesh back to life.", "You start knitting some of [target]'s flesh back to life.") +/datum/surgery_step/toxichealing/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) + if(..()) + while((target.getToxLoss() >= 1) || (target.getOxyLoss() >= 1)) + . = ..() + if(!.) + break + /datum/surgery_step/toxichealing/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) user.visible_message("[user] fixes some of [target]'s wounds.", "You succeed in fixing some of [target]'s wounds.") target.heal_bodypart_damage(0,0,30) //Heals stam diff --git a/code/modules/tcg/cards.dm b/code/modules/tcg/cards.dm index ff7d2fee2a..f5c7c47aaf 100644 --- a/code/modules/tcg/cards.dm +++ b/code/modules/tcg/cards.dm @@ -381,6 +381,14 @@ . = ..() LoadComponent(/datum/component/storage/concrete/tcg) +/obj/item/tcgcard_deck/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage/concrete/tcg) + STR.storage_flags = STORAGE_FLAGS_LEGACY_DEFAULT + STR.max_volume = DEFAULT_VOLUME_TINY * 30 + STR.max_w_class = DEFAULT_VOLUME_TINY + STR.max_items = 30 + /obj/item/tcgcard_deck/update_icon_state() . = ..() if(flipped) @@ -548,6 +556,7 @@ desc = "A TCG-branded card binder, specifically for your infinite collection of TCG cards!" icon = 'icons/obj/tcg/misc.dmi' icon_state = "binder" + w_class = WEIGHT_CLASS_SMALL var/list/cards = list() var/list/decks = list() @@ -796,7 +805,13 @@ var/list/card_types = list() for(var/obj/item/tcg_card/card in binder.cards) //if(!card.illegal) //Uncomment if you want to block syndie cards from saving - card_types[card.datum_type] = card.illegal + if(!(card.datum_type in card_types)) + card_types[card.datum_type] = card.illegal + else + if(islist(card_types[card.datum_type])) + card_types[card.datum_type] += card.illegal + else + card_types[card.datum_type] = list(card_types[card.datum_type], card.illegal) client.prefs.tcg_decks = binder.decks client.prefs.tcg_cards = card_types diff --git a/code/modules/tgs/v5/api.dm b/code/modules/tgs/v5/api.dm index 7a2ff694e0..466a986237 100644 --- a/code/modules/tgs/v5/api.dm +++ b/code/modules/tgs/v5/api.dm @@ -98,19 +98,18 @@ return json_encode(response) /datum/tgs_api/v5/OnTopic(T) + if(!initialized) + return FALSE //continue world/Topic + var/list/params = params2list(T) var/json = params[DMAPI5_TOPIC_DATA] if(!json) - return FALSE // continue to /world/Topic + return FALSE var/list/topic_parameters = json_decode(json) if(!topic_parameters) return TopicResponse("Invalid topic parameters json!"); - if(!initialized) - TGS_WARNING_LOG("Missed topic due to not being initialized: [T]") - return TRUE // too early to handle, but it's still our responsibility - var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] if(their_sCK != access_identifier) return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] from: [json]!"); diff --git a/code/modules/vore/eating/vorepanel.dm b/code/modules/vore/eating/vorepanel.dm index 6e3951e60a..5622ec0382 100644 --- a/code/modules/vore/eating/vorepanel.dm +++ b/code/modules/vore/eating/vorepanel.dm @@ -4,8 +4,8 @@ #define BELLIES_MAX 20 #define BELLIES_NAME_MIN 2 -#define BELLIES_NAME_MAX 12 -#define BELLIES_DESC_MAX 1024 +#define BELLIES_NAME_MAX 24 +#define BELLIES_DESC_MAX 4096 /mob/living/proc/insidePanel() set name = "Vore Panel" diff --git a/config/config.txt b/config/config.txt index c7bb36af0e..928b8de125 100644 --- a/config/config.txt +++ b/config/config.txt @@ -367,6 +367,13 @@ NOTIFY_NEW_PLAYER_ACCOUNT_AGE 1 ## Requires database #PANIC_BUNKER +## If a player connects during a bunker with less then or this amount of living time (Minutes), we deny the connection +#PANIC_BUNKER_LIVING 90 + +## The message the Panic Bunker gives when someone is rejected by it +## %minutes% is replaced with PANIC_BUNKER_LIVING on runtime, remove it if you don't want this +#PANIC_BUNKER_MESSAGE Sorry, but the server is currently not accepting connections from players with less than %minutes% minutes of living time. + ## If panic bunker is on and a player is rejected (see above), attempt to send them to this connected server (see below) instead. ## You probably want this to be the same as CROSS_SERVER_ADDRESS #PANIC_SERVER_ADDRESS byond://address:port diff --git a/config/policy.txt b/config/policy.txt index 610acd2be8..502b525ad0 100644 --- a/config/policy.txt +++ b/config/policy.txt @@ -3,7 +3,10 @@ ## ON_CLONE - displayed after a successful cloning operation to the cloned person ## ON_DEFIB_INTACT - displayed after defibbing before memory loss time threshold ## ON_DEFIB_LATE - displayed after defibbing post memory loss time threshold -## +## SDGF - displayed on SDGF clone spawning +## SDGF_GOOD - displayed on SDGF clone spawning, if the clone is loyal +## SDGF_BAD - displayed on SDGF clone spawning, if the clone is not loyal +## PAI - displayed on PAI personality being loaded ## EXAMPLE: ## POLICYCONFIG ON_CLONE insert text here span classes are fully supported diff --git a/html/changelog.html b/html/changelog.html index 56ecc7ee31..35e88ba5a8 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -50,6 +50,132 @@ -->
+

03 March 2021

+

MarinaGryphon updated:

+ +

Putnam3145 updated:

+ +

SandPoot updated:

+ +

dzahlus updated:

+ +

qweq12yt updated:

+ + +

02 March 2021

+

LetterN updated:

+ +

SandPoot updated:

+ + +

01 March 2021

+

SmArtKar updated:

+ +

Vynzill updated:

+ + +

28 February 2021

+

Putnam3145 updated:

+ +

R3dtail updated:

+ +

SandPoot updated:

+ +

dzahlus updated:

+ + +

27 February 2021

+

Hatterhat updated:

+ +

Putnam3145 updated:

+ +

TheObserver-sys updated:

+ +

kappa-sama updated:

+ +

keronshb updated:

+ +

kiwedespars updated:

+ +

silicons updated:

+ + +

26 February 2021

+

DeltaFire15 updated:

+ + +

25 February 2021

+

DeltaFire15 updated:

+ + +

24 February 2021

+

SandPoot updated:

+ +

silicons updated:

+ +

23 February 2021

keronshb updated: