diff --git a/.travis.yml b/.travis.yml index 694cfb654e4..2c2b9dec61d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,6 @@ jobs: - find . -name "*.json" -not -path "./nano/node_modules/*" -print0 | xargs -0 python3 ./tools/travis/json_verifier.py - tools/travis/build_nanoui.sh - tools/travis/check_grep.sh - - tools/travis/check_changelogs.sh - python3 tools/travis/check_line_endings.py - ~/dreamchecker diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql index 837f8f440d6..a447c7993ba 100644 --- a/SQL/paradise_schema.sql +++ b/SQL/paradise_schema.sql @@ -594,4 +594,18 @@ CREATE TABLE `connection_log` ( `ip` varchar(32) NOT NULL, `computerid` varchar(32) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- +-- Table structure for table `changelog` +-- +DROP TABLE IF EXISTS `changelog`; +CREATE TABLE `changelog` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `pr_number` INT(11) NOT NULL, + `date_merged` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `author` VARCHAR(32) NOT NULL, + `cl_type` ENUM('FIX','WIP','TWEAK','SOUNDADD','SOUNDDEL','CODEADD','CODEDEL','IMAGEADD','IMAGEDEL','SPELLCHECK','EXPERIMENT') NOT NULL, + `cl_entry` TEXT NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/SQL/paradise_schema_prefixed.sql b/SQL/paradise_schema_prefixed.sql index b43141f6784..6096f0547e6 100644 --- a/SQL/paradise_schema_prefixed.sql +++ b/SQL/paradise_schema_prefixed.sql @@ -591,4 +591,18 @@ CREATE TABLE `SS13_connection_log` ( `ip` varchar(32) NOT NULL, `computerid` varchar(32) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- +-- Table structure for table `SS13_changelog` +-- +DROP TABLE IF EXISTS `SS13_changelog`; +CREATE TABLE `SS13_changelog` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `pr_number` INT(11) NOT NULL, + `date_merged` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `author` VARCHAR(32) NOT NULL, + `cl_type` ENUM('FIX','WIP','TWEAK','SOUNDADD','SOUNDDEL','CODEADD','CODEDEL','IMAGEADD','IMAGEDEL','SPELLCHECK','EXPERIMENT') NOT NULL, + `cl_entry` TEXT NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/SQL/updates/10-11.sql b/SQL/updates/10-11.sql index 9564d3efaad..b2718db85e6 100644 --- a/SQL/updates/10-11.sql +++ b/SQL/updates/10-11.sql @@ -38,4 +38,4 @@ ALTER TABLE feedback.population CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4 ALTER TABLE feedback.privacy CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE feedback.vpn_whitelist CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE feedback.watch CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -ALTER TABLE feedback.whitelist CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; \ No newline at end of file +ALTER TABLE feedback.whitelist CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/SQL/updates/11-12.sql b/SQL/updates/11-12.sql new file mode 100644 index 00000000000..f6e9ac01baa --- /dev/null +++ b/SQL/updates/11-12.sql @@ -0,0 +1,13 @@ +#Updating the SQL from version 11 to version 12. -AffectedArc07 +#Creating a table for the new changelog system + +DROP TABLE IF EXISTS `changelog`; +CREATE TABLE IF NOT EXISTS `changelog` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `pr_number` INT(11) NOT NULL, + `date_merged` TIMESTAMP NOT NULL DEFAULT current_timestamp(), + `author` VARCHAR(32) NOT NULL, + `cl_type` ENUM('FIX','WIP','TWEAK','SOUNDADD','SOUNDDEL','CODEADD','CODEDEL','IMAGEADD','IMAGEDEL','SPELLCHECK','EXPERIMENT') NOT NULL, + `cl_entry` TEXT NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/_maps/map_files/RandomRuins/SpaceRuins/abandonedzoo.dmm b/_maps/map_files/RandomRuins/SpaceRuins/abandonedzoo.dmm index 62a22425926..43aa3cd98c7 100644 --- a/_maps/map_files/RandomRuins/SpaceRuins/abandonedzoo.dmm +++ b/_maps/map_files/RandomRuins/SpaceRuins/abandonedzoo.dmm @@ -832,6 +832,10 @@ /obj/item/shard, /turf/space, /area/space/nearstation) +"Hv" = ( +/obj/item/gps/ruin, +/turf/simulated/wall/r_wall, +/area/ruin/unpowered) (1,1,1) = {" aa @@ -973,7 +977,7 @@ aa at at aF -at +Hv aU ay ay diff --git a/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm b/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm index 22d4c0798aa..17d49d666bc 100644 --- a/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm +++ b/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm @@ -269,6 +269,10 @@ icon_state = "freezerfloor" }, /area/ruin/powered) +"Y" = ( +/obj/item/gps/ruin, +/turf/simulated/wall/r_wall, +/area/ruin/powered) (1,1,1) = {" a @@ -960,7 +964,7 @@ b f f p -f +Y f f j diff --git a/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm b/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm index 2a5c0eae533..b4e36942d6a 100644 --- a/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm +++ b/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm @@ -240,6 +240,13 @@ /obj/structure/shuttle/engine/propulsion, /turf/simulated/shuttle/plating, /area/ruin/powered) +"U" = ( +/obj/item/gps/ruin, +/turf/simulated/shuttle/wall{ + tag = "icon-swall12"; + icon_state = "swall12" + }, +/area/ruin/powered) (1,1,1) = {" a @@ -313,7 +320,7 @@ a c g l -q +U r u t diff --git a/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm b/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm index fef87df8950..ebfaf3b94b4 100644 --- a/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm +++ b/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm @@ -209,7 +209,6 @@ "aI" = ( /obj/structure/table/wood, /obj/item/toy/character/wizard, -/obj/item/paper/spells, /turf/simulated/floor/carpet, /area/ruin/unpowered) "aJ" = ( diff --git a/_maps/map_files/cyberiad/z2.dmm b/_maps/map_files/cyberiad/z2.dmm index 2b52c172daa..7abe416f576 100644 --- a/_maps/map_files/cyberiad/z2.dmm +++ b/_maps/map_files/cyberiad/z2.dmm @@ -2060,7 +2060,16 @@ icon_opened = "cabinet_open"; icon_state = "cabinet_closed" }, +/obj/item/clothing/under/color/lightpurple, +/obj/item/clothing/under/color/purple, /obj/item/storage/backpack/satchel, +/obj/item/storage/backpack/satchel, +/obj/item/clothing/head/wizard/red, +/obj/item/clothing/suit/wizrobe/red, +/obj/item/clothing/head/wizard, +/obj/item/clothing/suit/wizrobe, +/obj/item/clothing/shoes/sandal, +/obj/item/clothing/shoes/sandal, /turf/unsimulated/floor{ dir = 9; icon_state = "carpetside" @@ -2148,8 +2157,6 @@ /area/wizard_station) "fC" = ( /obj/structure/table/wood, -/obj/item/trash/tray, -/obj/item/paper/spells, /turf/unsimulated/floor{ dir = 10; icon_state = "carpetside" @@ -3248,10 +3255,10 @@ /area/shuttle/assault_pod) "iw" = ( /obj/structure/rack, +/obj/item/clothing/under/plasmaman/wizard, +/obj/item/clothing/head/helmet/space/plasmaman/wizard, /obj/item/clothing/mask/breath, -/obj/item/clothing/mask/breath/vox, -/obj/item/tank/plasma/plasmaman, -/obj/item/tank/emergency_oxygen/vox, +/obj/item/tank/plasma/plasmaman/belt/full, /turf/unsimulated/floor{ icon_state = "grimy" }, @@ -3895,10 +3902,8 @@ /area/centcom/gamma) "ke" = ( /obj/structure/rack, -/obj/item/clothing/suit/wizrobe/red, -/obj/item/clothing/shoes/sandal, -/obj/item/clothing/head/wizard/red, -/obj/item/twohanded/staff, +/obj/item/tank/emergency_oxygen/vox, +/obj/item/clothing/mask/breath/vox, /turf/unsimulated/floor{ icon_state = "grimy" }, @@ -5006,6 +5011,20 @@ name = "grass" }, /area/centcom/control) +"nc" = ( +/obj/machinery/door/airlock/centcom{ + name = "Gamma Armory"; + opacity = 1; + req_access_txt = "114" + }, +/obj/machinery/door/poddoor/impassable{ + id_tag = "CCGAMMA"; + name = "Gamma Security" + }, +/turf/simulated/floor/plasteel{ + icon_state = "dark" + }, +/area/centcom/gamma) "nd" = ( /obj/structure/flora/ausbushes/lavendergrass, /turf/unsimulated/floor{ @@ -5381,7 +5400,7 @@ }, /area/syndicate_mothership) "nQ" = ( -/obj/machinery/door/poddoor{ +/obj/machinery/door/poddoor/impassable{ id_tag = "GRAVPULTS"; name = "Gravity Catapults" }, @@ -5391,7 +5410,7 @@ }, /area/centcom/specops) "nR" = ( -/obj/machinery/door/poddoor{ +/obj/machinery/door/poddoor/impassable{ id_tag = "GRAVPULTS"; name = "Gravity Catapults" }, @@ -5408,7 +5427,7 @@ }, /area/syndicate_mothership) "nT" = ( -/obj/machinery/door/poddoor{ +/obj/machinery/door/poddoor/impassable{ id_tag = "ASSAULT"; name = "Assault Armor" }, @@ -5854,7 +5873,7 @@ }, /area/centcom/gamma) "oR" = ( -/obj/machinery/door/poddoor{ +/obj/machinery/door/poddoor/impassable{ id_tag = "SPECOPS"; name = "Ready Room" }, @@ -5979,7 +5998,7 @@ opacity = 1; req_access_txt = "114" }, -/obj/machinery/door/poddoor{ +/obj/machinery/door/poddoor/impassable{ id_tag = "specopsoffice"; name = "Super Privacy Shutters" }, @@ -6118,7 +6137,7 @@ /turf/simulated/floor/mech_bay_recharge_floor, /area/shuttle/gamma/space) "pv" = ( -/obj/machinery/door/poddoor{ +/obj/machinery/door/poddoor/impassable{ id_tag = "CCTELE"; name = "Specops Teleporter" }, @@ -6358,7 +6377,7 @@ /area/centcom/specops) "pX" = ( /obj/machinery/door_control{ - desc = "A remote control switch to block view of the singularity."; + desc = "A remote control switch to connect the ready room to the rest of Centcom."; icon_state = "doorctrl0"; id = "SPECOPS"; name = "Ready Room"; @@ -29607,7 +29626,7 @@ fm fm md md -oO +nc fm pv sX diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 956930aae70..2f98b6ee7b5 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -35,7 +35,6 @@ #define CANPUSH 8 #define PASSEMOTES 16 //Mob has a cortical borer or holders inside of it that need to see emotes. #define GOTTAGOFAST 32 -#define GOTTAGOFAST_METH 64 #define IGNORESLOWDOWN 128 #define GODMODE 4096 #define FAKEDEATH 8192 //Replaces stuff like changeling.changeling_fakedeath diff --git a/code/__DEFINES/lighting.dm b/code/__DEFINES/lighting.dm index 01dbc2f1c2b..34528090e86 100644 --- a/code/__DEFINES/lighting.dm +++ b/code/__DEFINES/lighting.dm @@ -90,3 +90,27 @@ #define FLASH_LIGHT_DURATION 2 #define FLASH_LIGHT_POWER 3 #define FLASH_LIGHT_RANGE 3.8 + +/// Returns the red part of a #RRGGBB hex sequence as number +#define GETREDPART(hexa) hex2num(copytext(hexa, 2, 4)) + +/// Returns the green part of a #RRGGBB hex sequence as number +#define GETGREENPART(hexa) hex2num(copytext(hexa, 4, 6)) + +/// Returns the blue part of a #RRGGBB hex sequence as number +#define GETBLUEPART(hexa) hex2num(copytext(hexa, 6, 8)) + +/// Parse the hexadecimal color into lumcounts of each perspective. +#define PARSE_LIGHT_COLOR(source) \ +do { \ + if (source.light_color) { \ + var/__light_color = source.light_color; \ + source.lum_r = GETREDPART(__light_color) / 255; \ + source.lum_g = GETGREENPART(__light_color) / 255; \ + source.lum_b = GETBLUEPART(__light_color) / 255; \ + } else { \ + source.lum_r = 1; \ + source.lum_g = 1; \ + source.lum_b = 1; \ + }; \ +} while (FALSE) diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 39a56566315..cf27ec09143 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -319,7 +319,7 @@ #define INVESTIGATE_BOMB "bombs" // The SQL version required by this version of the code -#define SQL_VERSION 11 +#define SQL_VERSION 12 // Vending machine stuff #define CAT_NORMAL 1 diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 8de2f30ad2f..2217271a2e7 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -417,16 +417,6 @@ return 0 return 1 - -/proc/GetRedPart(const/hexa) - return hex2num(copytext(hexa, 2, 4)) - -/proc/GetGreenPart(const/hexa) - return hex2num(copytext(hexa, 4, 6)) - -/proc/GetBluePart(const/hexa) - return hex2num(copytext(hexa, 6, 8)) - /proc/lavaland_equipment_pressure_check(turf/T) . = FALSE if(!istype(T)) @@ -438,13 +428,6 @@ if(pressure <= LAVALAND_EQUIPMENT_EFFECT_PRESSURE) . = TRUE -/proc/GetHexColors(const/hexa) - return list( - GetRedPart(hexa), - GetGreenPart(hexa), - GetBluePart(hexa), - ) - /proc/MinutesToTicks(var/minutes as num) return minutes * 60 * 10 diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm index 5cf36541eeb..ea2cf1290fd 100644 --- a/code/_globalvars/configuration.dm +++ b/code/_globalvars/configuration.dm @@ -4,7 +4,6 @@ GLOBAL_VAR(host) GLOBAL_VAR(join_motd) GLOBAL_VAR(join_tos) GLOBAL_VAR_INIT(game_version, "ParaCode") -GLOBAL_VAR_INIT(changelog_hash, md5('html/changelog.html')) //used to check if the CL changed GLOBAL_VAR_INIT(game_year, (text2num(time2text(world.realtime, "YYYY")) + 544)) GLOBAL_VAR_INIT(aliens_allowed, 1) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 6bfd72413de..20e50bb3054 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -261,12 +261,12 @@ if(incapacitated()) return var/face_dir = get_cardinal_dir(src, A) - if(forced_look == face_dir) + if(!face_dir || forced_look == face_dir || A == src) forced_look = null - to_chat(src, "You are no longer facing any direction.") + to_chat(src, "Cancelled direction lock.") return forced_look = face_dir - to_chat(src, "You are now facing [dir2text(forced_look)].") + to_chat(src, "You are now facing [dir2text(forced_look)]. To cancel this, shift-middleclick yourself.") /* Middle shift-control-click @@ -274,12 +274,12 @@ */ /mob/proc/MiddleShiftControlClickOn(atom/A) var/face_uid = A.UID() - if(forced_look == face_uid) + if(forced_look == face_uid || A == src) forced_look = null - to_chat(src, "You are no longer facing [A].") + to_chat(src, "Cancelled direction lock.") return forced_look = face_uid - to_chat(src, "You are now facing [A].") + to_chat(src, "You are now facing [A]. To cancel this, shift-middleclick yourself.") // In case of use break glass /* diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index d2a91da4020..e27002cf118 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -58,7 +58,7 @@ else return 1 var/obj/item/organ/external/O = M.get_organ(user.zone_selected) - if((is_sharp(src) || (isscrewdriver(src) && O.is_robotic())) && user.a_intent == INTENT_HELP) + if((is_sharp(src) || (isscrewdriver(src) && O?.is_robotic())) && user.a_intent == INTENT_HELP) if(!attempt_initiate_surgery(src, M, user)) return FALSE else diff --git a/code/controllers/master.dm b/code/controllers/master.dm index f64e566e097..a6026823ec9 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -146,7 +146,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new msg = "The [BadBoy.name] subsystem was the last to fire for 2 controller restarts. It will be recovered now and disabled if it happens again." FireHim = TRUE if(3) - msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined." + msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined. The following implications are now in effect: [BadBoy.offline_implications]" BadBoy.flags |= SS_NO_FIRE if(msg) to_chat(GLOB.admins, "[msg]") diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index f257f2b4895..4a125003389 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -35,6 +35,8 @@ var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out! + var/offline_implications = "None" // What are the implications of this SS being offlined? + //Do not override ///datum/controller/subsystem/New() diff --git a/code/controllers/subsystem/acid.dm b/code/controllers/subsystem/acid.dm index 40063b00a87..119d82113d7 100644 --- a/code/controllers/subsystem/acid.dm +++ b/code/controllers/subsystem/acid.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(acid) priority = FIRE_PRIORITY_ACID flags = SS_NO_INIT|SS_BACKGROUND runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + offline_implications = "Objects will no longer react to acid. No immediate action is needed." var/list/currentrun = list() var/list/processing = list() diff --git a/code/controllers/subsystem/afk.dm b/code/controllers/subsystem/afk.dm index 6ab1d67a816..9122e6a16c6 100644 --- a/code/controllers/subsystem/afk.dm +++ b/code/controllers/subsystem/afk.dm @@ -5,6 +5,7 @@ SUBSYSTEM_DEF(afk) name = "AFK Watcher" wait = 300 flags = SS_BACKGROUND + offline_implications = "Players will no longer be marked as AFK. No immediate action is needed." var/list/afk_players = list() // Associative list. ckey as key and AFK state as value @@ -17,27 +18,27 @@ SUBSYSTEM_DEF(afk) for(var/mob/living/carbon/human/H in GLOB.living_mob_list) if(!H.ckey) // Useless non ckey creatures continue - - var/turf/T + + var/turf/T // Only players and players with the AFK watch enabled // No dead, unconcious, restrained, people without jobs, people on other Z levels than the station or antags if(!H.client || !H.client.prefs.afk_watch || !H.mind || \ H.stat || H.restrained() || !H.job || H.mind.special_role || \ !is_station_level((T = get_turf(H)).z)) // Assign the turf as last. Small optimization - if(afk_players[H.ckey]) + if(afk_players[H.ckey]) toRemove += H.ckey continue - + var/mins_afk = round(H.client.inactivity / 600) if(mins_afk < config.warn_afk_minimum) if(afk_players[H.ckey]) toRemove += H.ckey continue - + if(!afk_players[H.ckey]) afk_players[H.ckey] = AFK_WARNED warn(H, "You are AFK for [mins_afk] minutes. You will be cryod after [config.auto_cryo_afk] total minutes and fully despawned after [config.auto_despawn_afk] total minutes. Please move or click in game if you want to avoid being despawned.") - else + else var/area/A = T.loc // Turfs loc is the area if(afk_players[H.ckey] == AFK_WARNED) if(mins_afk >= config.auto_cryo_afk && A.can_get_auto_cryod) @@ -50,14 +51,14 @@ SUBSYSTEM_DEF(afk) afk_players[H.ckey] = AFK_CRYOD msg_admins(H, mins_afk, T, "put into cryostorage") warn(H, "You are AFK for [mins_afk] minutes and have been moved to cryostorage. After being AFK for [config.auto_despawn_afk] total minutes you will be fully despawned. Please eject yourself (right click, eject) out of the cryostorage if you want to avoid being despawned.") - + else if(mins_afk >= config.auto_despawn_afk) var/obj/machinery/cryopod/P = H.loc msg_admins(H, mins_afk, T, "forcefully despawned") warn(H, "You are have been despawned after being AFK for [mins_afk] minutes.") toRemove += H.ckey P.despawn_occupant() - + removeFromWatchList(toRemove) /datum/controller/subsystem/afk/proc/warn(mob/living/carbon/human/H, text) diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 6f2a451ef76..80ce66d13ec 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -14,6 +14,7 @@ SUBSYSTEM_DEF(air) wait = 5 flags = SS_BACKGROUND runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + offline_implications = "Turfs will no longer process atmos, and all atmospheric machines (including cryotubes) will no longer function. Shuttle call recommended." var/cost_turfs = 0 var/cost_groups = 0 var/cost_highpressure = 0 diff --git a/code/controllers/subsystem/alarm.dm b/code/controllers/subsystem/alarm.dm index bac32a449de..289adf6de40 100644 --- a/code/controllers/subsystem/alarm.dm +++ b/code/controllers/subsystem/alarm.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(alarms) name = "Alarms" init_order = INIT_ORDER_ALARMS // 2 + offline_implications = "Alarms (Power, camera, fire, etc) will no longer be checked. No immediate action is needed." var/datum/alarm_handler/atmosphere/atmosphere_alarm = new() var/datum/alarm_handler/burglar/burglar_alarm = new() var/datum/alarm_handler/camera/camera_alarm = new() diff --git a/code/controllers/subsystem/changelog.dm b/code/controllers/subsystem/changelog.dm new file mode 100644 index 00000000000..98188599abd --- /dev/null +++ b/code/controllers/subsystem/changelog.dm @@ -0,0 +1,263 @@ +/* + Subsystem core for ParadiseSS13 changelogs + Author: AffectedArc07 + + Basically this SS extracts changelogs from the past 30 days from the database, and cleanly formats them into HTML that the players can see + It only runs the extraction on initialize to ensure that the changelog doesnt change mid round, and to reduce the amount of DB calls that need to be done + The changelog entries are generated from the PHP scripts in tools/githubChangelogProcessor.php + + This SS also handles the checking of player CL dates and informing them if it has changed + +*/ + +SUBSYSTEM_DEF(changelog) + name = "Changelog" + flags = SS_NO_FIRE + var/current_cl_timestamp = "0" // Timestamp is seconds since UNIX epoch (1st January 1970). ITs also a string because BYOND doesnt like big numbers. + var/ss_ready = FALSE // Is the SS ready? We dont want to run procs if we have not generated yet + var/list/startup_clients_button = list() // Clients who connected before initialization who need their button color updating + var/list/startup_clients_open = list() // Clients who connected before initialization who need the CL opening + var/changelogHTML = "" // HTML that the changelog will use to display + +/datum/controller/subsystem/changelog/Initialize() + // This entire subsystem relies on SQL being here. + if(!GLOB.dbcon.IsConnected()) + return ..() + + var/DBQuery/latest_cl_date = GLOB.dbcon.NewQuery("SELECT UNIX_TIMESTAMP(date_merged) AS ut FROM [format_table_name("changelog")] ORDER BY date_merged DESC LIMIT 1") + if(!latest_cl_date.Execute()) + var/err = latest_cl_date.ErrorMsg() + log_game("SQL ERROR during SSchangelog initialization L24. Error: \[[err]\]\n") + message_admins("SQL ERROR during SSchangelog initialization L24. Error: \[[err]\]\n") + // Abort if we cant do this + return ..() + + while(latest_cl_date.NextRow()) + current_cl_timestamp = latest_cl_date.item[1] + + if(!GenerateChangelogHTML()) // if this failed to generate + to_chat(world, "WARNING: Changelog failed to generate. Please inform a coder/server dev") + return ..() + + ss_ready = TRUE + // Now we can alert anyone who wanted to check the changelog + for(var/x in startup_clients_button) + var/client/C = x + UpdatePlayerChangelogButton(C) + + // Now we can alert anyone who wanted to check the changelog + for(var/client/C in startup_clients_open) + OpenChangelog(C) + + return ..() + + +/datum/controller/subsystem/changelog/proc/UpdatePlayerChangelogDate(client/C) + if(!ss_ready) + return // Only return here, we dont have to worry about a queue list because this will be called from ShowChangelog() + // Technically this is only for the date but we can also do the UI button at the same time + var/datum/preferences/P = GLOB.preferences_datums[C.ckey] + if(P.toggles & UI_DARKMODE) + winset(C, "rpane.changelog", "background-color=#40628a;font-color=#ffffff;font-style=none") + else + winset(C, "rpane.changelog", "background-color=none;font-style=none") + C.prefs.lastchangelog = current_cl_timestamp + var/DBQuery/updatePlayerCLTime = GLOB.dbcon.NewQuery("UPDATE [format_table_name("player")] SET lastchangelog='[sanitizeSQL(current_cl_timestamp)]' WHERE ckey='[C.ckey]'") + if(!updatePlayerCLTime.Execute()) + var/err = updatePlayerCLTime.ErrorMsg() + log_game("SQL ERROR during lastchangelog updating. Error: \[[err]\]\n") + message_admins("SQL ERROR during lastchangelog updating. Error: \[[err]\]\n") + to_chat(C, "Couldn't update your last seen changelog, please try again later.") + return FALSE + return TRUE + +/datum/controller/subsystem/changelog/proc/UpdatePlayerChangelogButton(client/C) + // If SQL aint even enabled, just set the button to default style + if(!GLOB.dbcon.IsConnected()) + if(C.prefs.toggles & UI_DARKMODE) + winset(C, "rpane.changelog", "background-color=#40628a;text-color=#FFFFFF") + else + winset(C, "rpane.changelog", "background-color=none;text-color=#000000") + return + + // If SQL is enabled but we aint ready, queue them up, and use the default style + if(!ss_ready) + startup_clients_button |= C + if(C.prefs.toggles & UI_DARKMODE) + winset(C, "rpane.changelog", "background-color=#40628a;text-color=#FFFFFF") + else + winset(C, "rpane.changelog", "background-color=none;text-color=#000000") + return + + // Sanity check to ensure clients still exist (If a client DCs mid startup this would runtime) + if(C && C.prefs) + // If we are ready, process the button style + if(C.prefs.lastchangelog != current_cl_timestamp) + winset(C, "rpane.changelog", "background-color=#bb7700;text-color=#FFFFFF;font-style=bold") + to_chat(C, "Changelog has changed since your last visit.") + else + if(C.prefs.toggles & UI_DARKMODE) + winset(C, "rpane.changelog", "background-color=#40628a;text-color=#FFFFFF") + else + winset(C, "rpane.changelog", "background-color=none;text-color=#000000") + + +/datum/controller/subsystem/changelog/proc/OpenChangelog(client/C) + // If SQL isnt enabled, dont even queue them, just tell them it wont work + if(!GLOB.dbcon.IsConnected()) + to_chat(C, "This server is not running with an SQL backend. Changelog is unavailable.") + return + + // If SQL is enabled but we aint ready, queue them up + if(!ss_ready) + startup_clients_open |= C + to_chat(C, "The changelog system is still initializing. The changelog will open for you once it has initialized.") + return + + UpdatePlayerChangelogDate(C) + UpdatePlayerChangelogButton(C) + + var/datum/browser/cl_popup = new(C.mob, "changelog", "Changelog", 700, 800) + cl_popup.set_content(changelogHTML) + cl_popup.open() + +/client/verb/changes() + set name = "Changelog" + set desc = "View the changelog." + set category = "OOC" + // Just invoke the actual CL thing + SSchangelog.OpenChangelog(src) + +// Helper to turn CL types into a fontawesome icon instead of an image +// The colors are #28a745 for green, #fd7e14 for orange, and #dc3545 for red. +// These colours are from bootstrap and look good with black and white +/datum/controller/subsystem/changelog/proc/Text2Icon(text) + switch(text) + if("FIX") + return "" // Fixes are white because while they are good, they have no negative coutnerpart + if("WIP") + return "" // WIP stuff is orange because new code is good but its not done yet + if("TWEAK") + return "" // Tweaks are white because they could be good or bad, and theres no specific add or remove + if("SOUNDADD") + return "" // Sound additions are green because its something new + if("SOUNDDEL") + return "" // Sound removals are red because something has been removed + if("CODEADD") + return "" // Code additions are green because its something new + if("CODEDEL") + return "" // Code removals are red becuase someting has been removed + if("IMAGEADD") + return "" // Image additions are green because something has been added + if("IMAGEDEL") + return "" // Image removals are red because something has been removed + if("SPELLCHECK") + return "" // Spellcheck is white because theres no dedicated negative to it, so theres no red for it to collate with + if("EXPERIMENT") + return "" // Experimental stuff is orange because while its a new feature, its unstable + else // Just incase the DB somehow breaks + return "" // Same here + +// This proc is the star of the show +/datum/controller/subsystem/changelog/proc/GenerateChangelogHTML() + // Modify the code below to modify the header of the changelog + var/changelog_header = {" + + ParadiseSS13 Changelog + +
+

Paradise Station Changelog

+

Forum - Wiki - GitHub

+
+ "} + + var/list/prs_to_process = list() + // Grab all from last 30 days + var/DBQuery/pr_list_query = GLOB.dbcon.NewQuery("SELECT DISTINCT pr_number FROM changelog WHERE date_merged BETWEEN NOW() - INTERVAL 30 DAY AND NOW() ORDER BY date_merged DESC") + if(!pr_list_query.Execute()) + var/err = pr_list_query.ErrorMsg() + log_game("SQL ERROR during CL generation L143. Error: \[[err]\]\n") + message_admins("SQL ERROR during CL generation L143. Error: \[[err]\]\n") + return FALSE + + while(pr_list_query.NextRow()) + prs_to_process += text2num(pr_list_query.item[1]) + + // Load in the header + changelogHTML += changelog_header + + // Make blocks for all the PRs + for(var/pr_number in prs_to_process) + // Initial declarations + var/pr_block = "" // HTML for the changelog section + var/author = "" // Author of the PR + var/merge_date = "" // Timestamp of when the PR was merged + + // Now we gather the data from the DB + // Also we probably dont need to sanitize the PR number but you never know + var/DBQuery/pr_meta = GLOB.dbcon.NewQuery("SELECT author,DATE(date_merged) AS date FROM changelog WHERE pr_number = [sanitizeSQL(pr_number)] LIMIT 1") + if(!pr_meta.Execute()) + var/err = pr_meta.ErrorMsg() + log_game("SQL ERROR during CL generation L190. Error: \[[err]\]\n") + message_admins("SQL ERROR during CL generation L190. Error: \[[err]\]\n") + return FALSE + + while(pr_meta.NextRow()) + author = pr_meta.item[1] + merge_date = pr_meta.item[2] + + // Now for each actual entry + var/DBQuery/db_entries = GLOB.dbcon.NewQuery("SELECT cl_type, cl_entry FROM changelog WHERE pr_number = [sanitizeSQL(pr_number)]") + if(!db_entries.Execute()) + var/err = db_entries.ErrorMsg() + log_game("SQL ERROR during CL generation L204. Error: \[[err]\]\n") + message_admins("SQL ERROR during CL generation L204. Error: \[[err]\]\n") + return FALSE + + + // Now we make a changelog block + pr_block += "
" + // If the github URL in the config has a trailing slash, it doesnt matter here, thankfully github accepts having a double slash: https://github.com/org/repo//pull/1 + pr_block += "

#[pr_number] by [author] (Merged on [merge_date])" + + while(db_entries.NextRow()) + pr_block += "

[Text2Icon(db_entries.item[1])] [db_entries.item[2]]

" + + pr_block += "

" + + changelogHTML += pr_block + + // Make sure we return TRUE so we know it worked + return TRUE + + +// Topic handler so that PRs and forums and stuff open in another window +/datum/controller/subsystem/changelog/Topic(href, href_list) + // Handler to open pages in your browser instead of inside the CL window + // Yes usr.client is gross here but src is the subsystem + // Takes the page to open as an argument + if(href_list["openPage"]) + switch(href_list["openPage"]) + if("forum") + usr.client.forum() + if("wiki") + // Wiki needs snowflake because it has no cancel button + if(config.wikiurl) + if(alert("This will open the wiki in your browser. Are you sure?",,"Yes","No")=="No") + return + usr.client.wiki("") // Blank arg is important here + else + to_chat(usr, "The Wiki URL is not set in the server configuration. Please inform the server host.") + + if("github") + usr.client.github() + // Takes a PR number as argument + if(href_list["openPR"]) + if(config.githuburl) + if(alert("This will open PR #[href_list["openPR"]] in your browser. Are you sure?",,"Yes","No")=="No") + return + var/url = "[config.githuburl]/pull/[href_list["openPR"]]" + usr << link(url) + else + to_chat(usr, "The GitHub URL is not set in the server configuration. PRs cannot be opened from changelog view. Please inform the server host.") + diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm index dbe38634e6a..4eb468a0952 100644 --- a/code/controllers/subsystem/chat.dm +++ b/code/controllers/subsystem/chat.dm @@ -4,6 +4,7 @@ SUBSYSTEM_DEF(chat) wait = 1 priority = FIRE_PRIORITY_CHAT init_order = INIT_ORDER_CHAT + offline_implications = "Chat messages will no longer be cleanly queued. No immediate action is needed." var/list/payload = list() diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm index 3612faa4a7e..ad77904bac7 100644 --- a/code/controllers/subsystem/events.dm +++ b/code/controllers/subsystem/events.dm @@ -2,6 +2,7 @@ SUBSYSTEM_DEF(events) name = "Events" init_order = INIT_ORDER_EVENTS runlevels = RUNLEVEL_GAME + offline_implications = "Random events will no longer happen. No immediate action is needed." // Report events at the end of the rouund var/report_at_round_end = 0 diff --git a/code/controllers/subsystem/fires.dm b/code/controllers/subsystem/fires.dm index aafc5ed48d5..dc419fffeb5 100644 --- a/code/controllers/subsystem/fires.dm +++ b/code/controllers/subsystem/fires.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(fires) priority = FIRE_PRIOTITY_BURNING flags = SS_NO_INIT|SS_BACKGROUND runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + offline_implications = "Objects will no longer react to fires. No immediate action is needed." var/list/currentrun = list() var/list/processing = list() diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 4bc3965e6cf..cbf17d05fbf 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -5,6 +5,7 @@ SUBSYSTEM_DEF(garbage) flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY init_order = INIT_ORDER_GARBAGE + offline_implications = "Garbage collection is no longer functional, and objects will not be qdel'd. Immediate server restart recommended." var/list/collection_timeout = list(0, 2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level diff --git a/code/controllers/subsystem/icon_smooth.dm b/code/controllers/subsystem/icon_smooth.dm index 531b6af2e78..289505a153c 100644 --- a/code/controllers/subsystem/icon_smooth.dm +++ b/code/controllers/subsystem/icon_smooth.dm @@ -4,6 +4,7 @@ SUBSYSTEM_DEF(icon_smooth) wait = 1 priority = FIRE_PRIOTITY_SMOOTHING flags = SS_TICKER + offline_implications = "Objects will no longer smooth together properly. No immediate action is needed." var/list/smooth_queue = list() diff --git a/code/controllers/subsystem/idlenpcpool.dm b/code/controllers/subsystem/idlenpcpool.dm index 42df941e10f..d61b90f1239 100644 --- a/code/controllers/subsystem/idlenpcpool.dm +++ b/code/controllers/subsystem/idlenpcpool.dm @@ -4,6 +4,7 @@ SUBSYSTEM_DEF(idlenpcpool) priority = FIRE_PRIORITY_IDLE_NPC wait = 60 runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + offline_implications = "Idle simple animals will no longer process. Shuttle call recommended." var/list/currentrun = list() var/static/list/idle_mobs_by_zlevel[][] @@ -39,4 +40,4 @@ SUBSYSTEM_DEF(idlenpcpool) if(SA.stat != DEAD) SA.consider_wakeup() if(MC_TICK_CHECK) - return + return diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm index f379deec72b..9dd6fce1554 100644 --- a/code/controllers/subsystem/input.dm +++ b/code/controllers/subsystem/input.dm @@ -5,6 +5,7 @@ SUBSYSTEM_DEF(input) flags = SS_TICKER priority = FIRE_PRIORITY_INPUT runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY + offline_implications = "Player input will no longer be recognised. Immediate server restart recommended." var/list/macro_sets var/list/movement_keys @@ -24,7 +25,7 @@ SUBSYSTEM_DEF(input) // This is for when macro sets are eventualy datumized /datum/controller/subsystem/input/proc/setup_default_macro_sets() var/list/static/default_macro_sets - + if(default_macro_sets) macro_sets = default_macro_sets return diff --git a/code/controllers/subsystem/jobs.dm b/code/controllers/subsystem/jobs.dm index 6f7894ca428..1d6fd410f9b 100644 --- a/code/controllers/subsystem/jobs.dm +++ b/code/controllers/subsystem/jobs.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(jobs) init_order = INIT_ORDER_JOBS // 12 wait = 3000 // 5 minutes (Deciseconds) runlevels = RUNLEVEL_GAME + offline_implications = "Job playtime hours will no longer be logged. No immediate action is needed." //List of all jobs var/list/occupations = list() diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index 7441d855f22..3fc92a3346a 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -7,6 +7,7 @@ SUBSYSTEM_DEF(lighting) wait = 2 init_order = INIT_ORDER_LIGHTING flags = SS_TICKER + offline_implications = "Lighting will no longer update. Shuttle call recommended." /datum/controller/subsystem/lighting/stat_entry() ..("L:[GLOB.lighting_update_lights.len]|C:[GLOB.lighting_update_corners.len]|O:[GLOB.lighting_update_objects.len]") diff --git a/code/controllers/subsystem/machinery.dm b/code/controllers/subsystem/machinery.dm index 9a73c1dbabb..7e78e2b9df1 100644 --- a/code/controllers/subsystem/machinery.dm +++ b/code/controllers/subsystem/machinery.dm @@ -6,6 +6,7 @@ SUBSYSTEM_DEF(machines) name = "Machines" init_order = INIT_ORDER_MACHINES flags = SS_KEEP_TIMING + offline_implications = "Machinery will no longer process. Shuttle call recommended." var/list/processing = list() var/list/currentrun = list() diff --git a/code/controllers/subsystem/mobs.dm b/code/controllers/subsystem/mobs.dm index c21b8f0760a..8345822ca80 100644 --- a/code/controllers/subsystem/mobs.dm +++ b/code/controllers/subsystem/mobs.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(mobs) priority = FIRE_PRIORITY_MOBS flags = SS_KEEP_TIMING runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + offline_implications = "Mobs will no longer process. Immediate server restart recommended." var/list/currentrun = list() var/static/list/clients_by_zlevel[][] diff --git a/code/controllers/subsystem/nano_mob_hunter.dm b/code/controllers/subsystem/nano_mob_hunter.dm index a634f091013..9094bf9fe2b 100644 --- a/code/controllers/subsystem/nano_mob_hunter.dm +++ b/code/controllers/subsystem/nano_mob_hunter.dm @@ -2,6 +2,7 @@ SUBSYSTEM_DEF(mob_hunt) name = "Nano-Mob Hunter GO Server" init_order = INIT_ORDER_NANOMOB priority = FIRE_PRIORITY_NANOMOB // Low priority, no need for MC_TICK_CHECK due to extremely low performance impact. + offline_implications = "Nano-Mob Hunter will no longer spawn mobs. No immediate action is needed." var/max_normal_spawns = 15 //change this to adjust the number of normal spawns that can exist at one time. trapped spawns (from traitors) don't count towards this var/list/normal_spawns = list() var/max_trap_spawns = 15 //change this to adjust the number of trap spawns that can exist at one time. traps spawned beyond this point clear the oldest traps diff --git a/code/controllers/subsystem/nanoui.dm b/code/controllers/subsystem/nanoui.dm index c8cbbc62013..ef1b6ee03fe 100644 --- a/code/controllers/subsystem/nanoui.dm +++ b/code/controllers/subsystem/nanoui.dm @@ -4,6 +4,7 @@ SUBSYSTEM_DEF(nanoui) flags = SS_NO_INIT priority = FIRE_PRIORITY_NANOUI runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + offline_implications = "All NanoUIs will no longer process. Shuttle call recommended." var/list/currentrun = list() var/list/open_uis = list() // A list of open UIs, grouped by src_object and ui_key. diff --git a/code/controllers/subsystem/nightshift.dm b/code/controllers/subsystem/nightshift.dm index e13f49fd1c2..deffcb6bddf 100644 --- a/code/controllers/subsystem/nightshift.dm +++ b/code/controllers/subsystem/nightshift.dm @@ -4,6 +4,7 @@ SUBSYSTEM_DEF(nightshift) priority = FIRE_PRIORITY_NIGHTSHIFT wait = 600 flags = SS_NO_TICK_CHECK + offline_implications = "The game will no longer shift between day and night lighting. No immediate action is needed." var/nightshift_active = FALSE var/nightshift_start_time = 702000 //7:30 PM, station time diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm index 5eaca5d19c4..79541c199a4 100644 --- a/code/controllers/subsystem/npcpool.dm +++ b/code/controllers/subsystem/npcpool.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(npcpool) flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND priority = FIRE_PRIORITY_NPC runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + offline_implications = "Simple animals will no longer process. Shuttle call recommended." var/list/currentrun = list() diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm index c35f1adee65..95b9e925b5d 100644 --- a/code/controllers/subsystem/overlays.dm +++ b/code/controllers/subsystem/overlays.dm @@ -4,6 +4,7 @@ SUBSYSTEM_DEF(overlays) wait = 1 priority = FIRE_PRIORITY_OVERLAYS init_order = INIT_ORDER_OVERLAY + offline_implications = "Overlays may look strange. No immediate action is needed." var/list/queue var/list/stats diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm index 039b6d2b008..9135f62b03f 100644 --- a/code/controllers/subsystem/parallax.dm +++ b/code/controllers/subsystem/parallax.dm @@ -4,6 +4,7 @@ SUBSYSTEM_DEF(parallax) flags = SS_POST_FIRE_TIMING | SS_BACKGROUND priority = FIRE_PRIORITY_PARALLAX runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + offline_implications = "Space parallax will no longer move around. No immediate action is needed." var/list/currentrun var/planet_x_offset = 128 var/planet_y_offset = 128 diff --git a/code/controllers/subsystem/shuttles.dm b/code/controllers/subsystem/shuttles.dm index 14a9b507977..247d71d05b8 100644 --- a/code/controllers/subsystem/shuttles.dm +++ b/code/controllers/subsystem/shuttles.dm @@ -6,6 +6,7 @@ SUBSYSTEM_DEF(shuttle) init_order = INIT_ORDER_SHUTTLE flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK runlevels = RUNLEVEL_SETUP | RUNLEVEL_GAME + offline_implications = "Shuttles will no longer function and cargo will not generate points. Immediate server restart recommended." var/list/mobile = list() var/list/stationary = list() var/list/transit = list() diff --git a/code/controllers/subsystem/spacedrift.dm b/code/controllers/subsystem/spacedrift.dm index fcc62a2fa50..d9c46f9c9d7 100644 --- a/code/controllers/subsystem/spacedrift.dm +++ b/code/controllers/subsystem/spacedrift.dm @@ -4,6 +4,7 @@ SUBSYSTEM_DEF(spacedrift) wait = 5 flags = SS_NO_INIT|SS_KEEP_TIMING runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + offline_implications = "Mobs will no longer respect a lack of gravity. No immediate action is needed." var/list/currentrun = list() var/list/processing = list() diff --git a/code/controllers/subsystem/sun.dm b/code/controllers/subsystem/sun.dm index b9843c57b68..3f8e15df05a 100644 --- a/code/controllers/subsystem/sun.dm +++ b/code/controllers/subsystem/sun.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(sun) wait = 600 flags = SS_NO_TICK_CHECK init_order = INIT_ORDER_SUN + offline_implications = "Solar panels will no longer rotate. No immediate action is needed." var/angle var/dx var/dy diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm index 74dbf4c4d93..04ddf40f442 100644 --- a/code/controllers/subsystem/throwing.dm +++ b/code/controllers/subsystem/throwing.dm @@ -7,6 +7,7 @@ SUBSYSTEM_DEF(throwing) wait = 1 flags = SS_NO_INIT|SS_KEEP_TIMING|SS_TICKER runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + offline_implications = "Thrown objects may not react properly. Shuttle call recommended." var/list/currentrun var/list/processing = list() diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index b092271fc96..825eab93a31 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -5,6 +5,7 @@ SUBSYSTEM_DEF(ticker) priority = FIRE_PRIORITY_TICKER flags = SS_KEEP_TIMING runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME + offline_implications = "The game is no longer aware of when the round ends. Immediate server restart recommended." var/round_start_time = 0 var/const/restart_timeout = 600 @@ -513,6 +514,11 @@ SUBSYSTEM_DEF(ticker) //Ask the event manager to print round end information SSevents.RoundEnd() + //make big obvious note in game logs that round ended + log_game("///////////////////////////////////////////////////////") + log_game("///////////////////// ROUND ENDED /////////////////////") + log_game("///////////////////////////////////////////////////////") + // Add AntagHUD to everyone, see who was really evil the whole time! for(var/datum/atom_hud/antag/H in GLOB.huds) for(var/m in GLOB.player_list) diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 1caa3d7819a..7bb6a496274 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -9,6 +9,7 @@ SUBSYSTEM_DEF(timer) init_order = INIT_ORDER_TIMER flags = SS_TICKER|SS_NO_INIT + offline_implications = "The game will no longer process timers. Immediate server restart recommended." var/list/second_queue = list() //awe, yes, you've had first queue, but what about second queue? var/list/hashes = list() diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 28456c9b5c5..b0110258da7 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(vote) wait = 10 flags = SS_KEEP_TIMING|SS_NO_INIT runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + offline_implications = "Votes (Endround shuttle) will no longer function. Shuttle call recommended." var/initiator = null var/started_time = null diff --git a/code/controllers/subsystem/weather.dm b/code/controllers/subsystem/weather.dm index ac2f0c59313..7b08bd3ca48 100644 --- a/code/controllers/subsystem/weather.dm +++ b/code/controllers/subsystem/weather.dm @@ -9,6 +9,7 @@ SUBSYSTEM_DEF(weather) flags = SS_BACKGROUND wait = 10 runlevels = RUNLEVEL_GAME + offline_implications = "Ash storms will no longer trigger. No immediate action is needed." var/list/processing = list() var/list/eligible_zlevels = list() var/list/next_hit_by_zlevel = list() //Used by barometers to know when the next storm is coming diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm index cf39d91a866..5759ab32de6 100644 --- a/code/datums/diseases/advance/symptoms/sensory.dm +++ b/code/datums/diseases/advance/symptoms/sensory.dm @@ -62,4 +62,4 @@ Bonus M.reagents.add_reagent("oculine", 20) else if(prob(SYMPTOM_ACTIVATION_PROB * 5)) - to_chat(M, "[pick("Your eyes feel great.","You feel like your eyes can focus more clearly.", "You don't feel the need to blink.","Your ears feel great.","Your healing feels more acute.")]") + to_chat(M, "[pick("Your eyes feel great.","You feel like your eyes can focus more clearly.", "You don't feel the need to blink.","Your ears feel great.","Your hearing feels more acute.")]") diff --git a/code/datums/gas_mixture.dm b/code/datums/gas_mixture.dm index 35cc970aa67..63b70b512ee 100644 --- a/code/datums/gas_mixture.dm +++ b/code/datums/gas_mixture.dm @@ -645,7 +645,7 @@ What are the archived variables for? ((nitrogen < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.nitrogen) || (nitrogen > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.nitrogen))) return 0 if((abs(carbon_dioxide-sample.carbon_dioxide) > MINIMUM_AIR_TO_SUSPEND) && \ - ((carbon_dioxide < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.carbon_dioxide) || (oxygen > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.carbon_dioxide))) + ((carbon_dioxide < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.carbon_dioxide) || (carbon_dioxide > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.carbon_dioxide))) return 0 if((abs(toxins-sample.toxins) > MINIMUM_AIR_TO_SUSPEND) && \ ((toxins < (1-MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.toxins) || (toxins > (1+MINIMUM_AIR_RATIO_TO_SUSPEND)*sample.toxins))) diff --git a/code/datums/log_viewer.dm b/code/datums/log_viewer.dm index 70f7d832924..67eb3d21d86 100644 --- a/code/datums/log_viewer.dm +++ b/code/datums/log_viewer.dm @@ -112,6 +112,9 @@ dat += "Mobs being used:" for(var/i in selected_mobs) var/mob/M = i + if(QDELETED(M)) + selected_mobs -= i + continue dat += "[M.name]" dat += "Add Mob" dat += "Clear All Mobs" diff --git a/code/datums/mind.dm b/code/datums/mind.dm index add68cd51a7..2a3a32d8119 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -63,8 +63,6 @@ var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src var/hasSoul = TRUE - var/rev_cooldown = 0 - var/isholy = FALSE // is this person a chaplain or admin role allowed to use bibles var/isblessed = FALSE // is this person blessed by a chaplain? var/num_blessed = 0 // for prayers diff --git a/code/datums/outfits/plasmamen.dm b/code/datums/outfits/plasmamen.dm index 376bf2af90c..94206c0ae3a 100644 --- a/code/datums/outfits/plasmamen.dm +++ b/code/datums/outfits/plasmamen.dm @@ -176,3 +176,9 @@ head = /obj/item/clothing/head/helmet/space/plasmaman/blueshield uniform = /obj/item/clothing/under/plasmaman/blueshield + +/datum/outfit/plasmaman/wizard + name = "Wizard Plasmaman" + + head = /obj/item/clothing/head/helmet/space/plasmaman/wizard + uniform = /obj/item/clothing/under/plasmaman/wizard diff --git a/code/datums/spells/area_teleport.dm b/code/datums/spells/area_teleport.dm index 8a3585febc0..0d24a984014 100644 --- a/code/datums/spells/area_teleport.dm +++ b/code/datums/spells/area_teleport.dm @@ -79,6 +79,8 @@ target.forceMove(pick(L)) playsound(get_turf(user), sound2, 50,1) + user.update_action_buttons_icon() //Update action buttons as some spells might now be castable + return /obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null) diff --git a/code/datums/spells/lichdom.dm b/code/datums/spells/lichdom.dm index 94ad0960e17..9175fd677fb 100644 --- a/code/datums/spells/lichdom.dm +++ b/code/datums/spells/lichdom.dm @@ -28,7 +28,7 @@ config.continuous_rounds = 0 return ..() -/obj/effect/proc_holder/spell/targeted/lichdom/cast(list/targets,mob/user = usr) +/obj/effect/proc_holder/spell/targeted/lichdom/cast(list/targets, mob/user = usr) if(!config.continuous_rounds) existence_stops_round_end = 1 config.continuous_rounds = 1 @@ -36,7 +36,7 @@ for(var/mob/M in targets) var/list/hand_items = list() if(iscarbon(M)) - hand_items = list(M.get_active_hand(),M.get_inactive_hand()) + hand_items = list(M.get_active_hand(), M.get_inactive_hand()) if(marked_item && !stat_allowed) //sanity, shouldn't happen without badminry marked_item = null @@ -74,12 +74,9 @@ var/mob/old_body = current_body var/turf/body_turf = get_turf(old_body) current_body = lich - lich.Weaken(10+10*resurrections) + lich.Weaken(10 + 10 * resurrections) ++resurrections - lich.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(lich), slot_shoes) - lich.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(lich), slot_w_uniform) - lich.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(lich), slot_wear_suit) - lich.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(lich), slot_head) + equip_lich(lich) if(old_body && old_body.loc) if(iscarbon(old_body)) @@ -127,7 +124,10 @@ H.unEquip(H.head) H.unEquip(H.shoes) H.unEquip(H.head) - H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(H), slot_wear_suit) - H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), slot_head) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(H), slot_shoes) - H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), slot_w_uniform) + equip_lich(H) + +/obj/effect/proc_holder/spell/targeted/lichdom/proc/equip_lich(mob/living/carbon/human/H) + H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(H), slot_wear_suit) + H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), slot_head) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(H), slot_shoes) + H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), slot_w_uniform) diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm index 896f4f999a9..74abf104465 100644 --- a/code/datums/status_effects/status_effect.dm +++ b/code/datums/status_effects/status_effect.dm @@ -41,6 +41,9 @@ LAZYREMOVE(owner.status_effects, src) on_remove() owner = null + if(linked_alert) + linked_alert.attached_effect = null + linked_alert = null return ..() /datum/status_effect/process() @@ -85,6 +88,13 @@ desc = "You don't feel any different..." var/datum/status_effect/attached_effect +/obj/screen/alert/status_effect/Destroy() + if(attached_effect) + attached_effect.linked_alert = null + attached_effect = null + return ..() + + ////////////////// // HELPER PROCS // ////////////////// diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm index c58aed9a89d..059eed3541c 100644 --- a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm +++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm @@ -421,13 +421,13 @@ return FALSE /obj/structure/lattice/catwalk/swarmer_act(mob/living/simple_animal/hostile/swarmer/S) - . = ..() var/turf/here = get_turf(src) for(var/A in here.contents) var/obj/structure/cable/C = A if(istype(C)) to_chat(S, "Disrupting the power grid would bring no benefit to us. Aborting.") return FALSE + return ..() /obj/item/deactivated_swarmer/IntegrateAmount() return 50 diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm index 863ce6405cc..a7eb5d135e9 100644 --- a/code/game/gamemodes/wizard/spellbook.dm +++ b/code/game/gamemodes/wizard/spellbook.dm @@ -6,34 +6,36 @@ var/category = "Offensive" var/log_name = "XX" //What it shows up as in logs var/cost = 2 - var/refundable = 1 + var/refundable = TRUE var/surplus = -1 // -1 for infinite, not used by anything atm var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell var/buy_word = "Learn" var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook /datum/spellbook_entry/proc/IsSpellAvailable() // For config prefs / gamemode restrictions - these are round applied - return 1 + return TRUE /datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user, obj/item/spellbook/book) // Specific circumstances - if(book.uses= aspell.level_max) to_chat(user, "This spell cannot be improved further.") - return 0 + return FALSE else aspell.name = initial(aspell.name) aspell.spell_level++ - aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) + aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min) / aspell.level_max) if(aspell.charge_max < aspell.charge_counter) aspell.charge_counter = aspell.charge_max switch(aspell.spell_level) @@ -51,37 +53,39 @@ aspell.name = "Instant [aspell.name]" if(aspell.spell_level >= aspell.level_max) to_chat(user, "This spell cannot be strengthened any further.") - return 1 + return TRUE //No same spell found - just learn it - feedback_add_details("wizard_spell_learned",log_name) - user.mind.AddSpell(S) - to_chat(user, "You have learned [S.name].") - return 1 + feedback_add_details("wizard_spell_learned", log_name) + user.mind.AddSpell(newspell) + to_chat(user, "You have learned [newspell.name].") + return TRUE /datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user, obj/item/spellbook/book) if(!refundable) - return 0 + return FALSE if(!S) S = new spell_type() for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) if(initial(S.name) == initial(aspell.name)) - return 1 - return 0 + return TRUE + return FALSE /datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) //return point value or -1 for failure var/area/wizard_station/A = locate() if(!(user in A.contents)) - to_chat(user, "You can only refund spells at the wizard lair") + to_chat(user, "You can only refund spells at the wizard lair.") return -1 - if(!S) + if(!S) //This happens when the spell's source is from another spellbook, from loadouts, or adminery, this create a new template temporary spell S = new spell_type() var/spell_levels = 0 for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) if(initial(S.name) == initial(aspell.name)) spell_levels = aspell.spell_level user.mind.spell_list.Remove(aspell) - QDEL_NULL(S) - return cost * (spell_levels+1) + qdel(aspell) + if(S) //If we created a temporary spell above, delete it now. + qdel(S) + return cost * (spell_levels + 1) return -1 /datum/spellbook_entry/proc/GetInfo() @@ -571,6 +575,48 @@ category = "Summons" limit = 1 +//Spell loadouts datum, list of loadouts is in wizloadouts.dm +/datum/spellbook_entry/loadout + name = "Standard Loadout" + cost = 10 + category = "Standard" + refundable = FALSE + buy_word = "Summon" + var/list/items_path = list() + var/list/spells_path = list() + var/destroy_spellbook = FALSE //Destroy the spellbook when bought, for loadouts containing non-standard items/spells, otherwise wiz can refund spells + +/datum/spellbook_entry/loadout/GetInfo() + var/dat = "" + dat += "[name]" + if(cost > 0) + dat += " Cost:[cost]
" + else + dat += " No Cost
" + dat += "[desc]
" + return dat + +/datum/spellbook_entry/loadout/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + if(destroy_spellbook) + var/response = alert(user, "The [src] loadout cannot be refunded once bought. Are you sure this is what you want?", "No refunds!", "No", "Yes") + if(response == "No") + return FALSE + to_chat(user, "[book] crumbles to ashes as you acquire its knowledge.") + qdel(book) + else if(items_path.len) + var/response = alert(user, "The [src] loadout contains items that will not be refundable if bought. Are you sure this is what you want?", "No refunds!", "No", "Yes") + if(response == "No") + return FALSE + if(items_path.len) + var/obj/item/storage/box/wizard/B = new(src) + for(var/path in items_path) + new path(B) + user.put_in_hands(B) + for(var/path in spells_path) + var/obj/effect/proc_holder/spell/S = new path() + LearnSpell(user, book, S) + return TRUE + /obj/item/spellbook name = "spell book" desc = "The legendary book of spells of the wizard." @@ -587,12 +633,13 @@ var/mob/living/carbon/human/owner var/list/datum/spellbook_entry/entries = list() var/list/categories = list() - var/list/main_categories = list("Spells", "Magical Items") + var/list/main_categories = list("Spells", "Magical Items", "Loadouts") var/list/spell_categories = list("Offensive", "Defensive", "Mobility", "Assistance", "Rituals") var/list/item_categories = list("Artefacts", "Weapons and Armors", "Staves", "Summons") + var/list/loadout_categories = list("Standard", "Unique") /obj/item/spellbook/proc/initialize() - var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon + var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - /datum/spellbook_entry/loadout for(var/T in entry_types) var/datum/spellbook_entry/E = new T if(E.IsSpellAvailable()) @@ -676,6 +723,12 @@ if("Summons") dat += "Magical items geared towards bringing in outside forces to aid you.

" dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
" + if("Standard") + dat += "These battle-tested spell sets are easy to use and provide good balance between offense and defense.

" + dat += "They all cost, and are worth, 10 spell points. You are able to refund any of the spells included as long as you stay in the wizard den.
" + if("Unique") + dat += "These esoteric loadouts usually contain spells or items that cannot be bought elsewhere in this spellbook.

" + dat += "Recommended for experienced wizards looking for something new. No refunds once purchased!
" return dat /obj/item/spellbook/proc/wrap(content) @@ -720,23 +773,24 @@ cat_dat[main_category] = "
" dat += "
  • [main_category]
  • " dat += "" + dat += "