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 += "
"
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 += "
AUTHOR
TITLE
CATEGORY
SS13BN
"
-
- 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 += "
"
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] += "
\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 += "
"
@@ -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 += "
"
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 += "