From 9b7d23776c69d600283d2c2f0eb7b7b04bc257df Mon Sep 17 00:00:00 2001 From: oranges Date: Wed, 25 Feb 2015 12:25:21 +1300 Subject: [PATCH 01/13] Auto load required nanoui files on client login This is still an inprogress so please do not merge --- code/controllers/subsystem/nanoUI.dm | 21 ++++++++++++++++++++- code/modules/client/client procs.dm | 20 ++------------------ code/modules/nano/nanomanager.dm | 4 ++++ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/code/controllers/subsystem/nanoUI.dm b/code/controllers/subsystem/nanoUI.dm index f9083f7e8ed..45fc8f8c1f4 100644 --- a/code/controllers/subsystem/nanoUI.dm +++ b/code/controllers/subsystem/nanoUI.dm @@ -9,10 +9,29 @@ var/datum/subsystem/nano/SSnano var/list/open_uis = list() //a list of current open /nanoui UIs, grouped by src_object and ui_key var/list/processing_uis = list() //a list of current open /nanoui UIs, not grouped, for use in processing + //List of asset filenames to be sent to the client on user login + var/list/asset_files = list() + /datum/subsystem/nano/New() NEW_SS_GLOBAL(SSnano) + //Generate list of files to send to client for nano UI's + var/list/nano_asset_dirs = list(\ + "nano/css/",\ + "nano/images/",\ + "nano/js/",\ + "nano/templates/"\ + ) + var/list/filenames = null + for (var/path in nano_asset_dirs) + filenames = flist(path) + for(var/filename in filenames) + //Ignore directories + if(copytext(filename, length(filename)) != "/") + if(fexists(path + filename)) + asset_files.Add(fcopy_rsc(path + filename)) + /datum/subsystem/nano/stat_entry() stat(name, "[round(cost,0.001)]ds (CPU:[round(cpu,1)]%) [processing_uis.len]") @@ -27,4 +46,4 @@ var/datum/subsystem/nano/SSnano ui.process() ++i continue - processing_uis.Cut(i, i+1) \ No newline at end of file + processing_uis.Cut(i, i+1) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 3b3f2706221..c56dfa0f549 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -236,25 +236,9 @@ var/next_external_rsc = 0 //send resources to the client. It's here in its own proc so we can move it around easiliy if need be /client/proc/send_resources() + //Send nanoui files to client + SSnano.send_resources(src) getFiles( - 'nano/js/libraries.min.js', - 'nano/js/nano_update.js', - 'nano/js/nano_config.js', - 'nano/js/nano_base_helpers.js', - 'nano/css/shared.css', - 'nano/css/icons.css', - 'nano/templates/chem_dispenser.tmpl', - 'nano/templates/chem_heater.tmpl', - 'nano/templates/smes.tmpl', - 'nano/templates/apc.tmpl', - 'nano/templates/cryo.tmpl', - 'nano/templates/solar_control.tmpl', - 'nano/images/uiBackground.png', - 'nano/images/uiIcons16.png', - 'nano/images/uiIcons24.png', - 'nano/images/uiLinkPendingIcon.gif', - 'nano/images/uiNoticeBackground.jpg', - 'nano/images/uiTitleFluff.png', 'html/search.js', 'html/panels.css', 'html/browser/common.css', diff --git a/code/modules/nano/nanomanager.dm b/code/modules/nano/nanomanager.dm index c463787a17e..07a6a4570dc 100644 --- a/code/modules/nano/nanomanager.dm +++ b/code/modules/nano/nanomanager.dm @@ -182,3 +182,7 @@ return 1 // success +//Send all needed nano ui files to the client +/datum/subsystem/nano/proc/send_resources(client) + for(var/file in asset_files) + client << browse_rsc(file) From 48edc8b7ae24b78f69b5ad98f9e83e8a13fa0e9a Mon Sep 17 00:00:00 2001 From: Menshin Date: Wed, 25 Feb 2015 21:12:14 +0100 Subject: [PATCH 02/13] Fixes protolathe incorrectly checking material amount. --- code/modules/research/circuitprinter.dm | 8 ++-- code/modules/research/rdconsole.dm | 60 +++++++++++++------------ 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/code/modules/research/circuitprinter.dm b/code/modules/research/circuitprinter.dm index 958ed65f986..7335ea0dadb 100644 --- a/code/modules/research/circuitprinter.dm +++ b/code/modules/research/circuitprinter.dm @@ -61,13 +61,13 @@ using metal and glass, it uses glass and reagents (usually sulfuric acis). /obj/machinery/r_n_d/circuit_imprinter/proc/check_mat(datum/design/being_built, var/M) switch(M) if("$glass") - return (g_amount - (being_built.materials[M]/efficiency_coeff) >= 0) ? 1 : 0 + return (g_amount - (being_built.materials[M]/efficiency_coeff) >= 0) if("$gold") - return (gold_amount - (being_built.materials[M]/efficiency_coeff) >= 0) ? 1 : 0 + return (gold_amount - (being_built.materials[M]/efficiency_coeff) >= 0) if("$diamond") - return (diamond_amount - (being_built.materials[M]/efficiency_coeff) >= 0) ? 1 : 0 + return (diamond_amount - (being_built.materials[M]/efficiency_coeff) >= 0) else - return (reagents.has_reagent(M, (being_built.materials[M]/efficiency_coeff)) != 0) ? 1 : 0 + return (reagents.has_reagent(M, (being_built.materials[M]/efficiency_coeff)) != 0) /obj/machinery/r_n_d/circuit_imprinter/proc/TotalMaterials() diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index 5960753494e..3ececf69674 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -369,34 +369,38 @@ won't update every console in existence) but it's more of a hassle to do. Also, if (g2g) //If input is incorrect, nothing happens + var/enough_materials = 1 linked_lathe.busy = 1 flick("protolathe_n",linked_lathe) use_power(power) for(var/M in being_built.materials) - if(!linked_lathe.check_mat(being_built, M)) + if(linked_lathe.check_mat(being_built, M) < amount) src.visible_message("The [src.name] beeps, \"Not enough materials to complete prototype.\"") + enough_materials = 0 g2g = 0 break - switch(M) - if("$metal") - linked_lathe.m_amount = max(0, (linked_lathe.m_amount-(being_built.materials[M]/coeff * amount))) - if("$glass") - linked_lathe.g_amount = max(0, (linked_lathe.g_amount-(being_built.materials[M]/coeff * amount))) - if("$gold") - linked_lathe.gold_amount = max(0, (linked_lathe.gold_amount-(being_built.materials[M]/coeff * amount))) - if("$silver") - linked_lathe.silver_amount = max(0, (linked_lathe.silver_amount-(being_built.materials[M]/coeff * amount))) - if("$plasma") - linked_lathe.plasma_amount = max(0, (linked_lathe.plasma_amount-(being_built.materials[M]/coeff * amount))) - if("$uranium") - linked_lathe.uranium_amount = max(0, (linked_lathe.uranium_amount-(being_built.materials[M]/coeff * amount))) - if("$diamond") - linked_lathe.diamond_amount = max(0, (linked_lathe.diamond_amount-(being_built.materials[M]/coeff * amount))) - if("$bananium") - linked_lathe.clown_amount = max(0, (linked_lathe.clown_amount-(being_built.materials[M]/coeff * amount))) - else - linked_lathe.reagents.remove_reagent(M, being_built.materials[M]/coeff * amount) + if(enough_materials) + for(var/M in being_built.materials) + switch(M) + if("$metal") + linked_lathe.m_amount = max(0, (linked_lathe.m_amount-(being_built.materials[M]/coeff * amount))) + if("$glass") + linked_lathe.g_amount = max(0, (linked_lathe.g_amount-(being_built.materials[M]/coeff * amount))) + if("$gold") + linked_lathe.gold_amount = max(0, (linked_lathe.gold_amount-(being_built.materials[M]/coeff * amount))) + if("$silver") + linked_lathe.silver_amount = max(0, (linked_lathe.silver_amount-(being_built.materials[M]/coeff * amount))) + if("$plasma") + linked_lathe.plasma_amount = max(0, (linked_lathe.plasma_amount-(being_built.materials[M]/coeff * amount))) + if("$uranium") + linked_lathe.uranium_amount = max(0, (linked_lathe.uranium_amount-(being_built.materials[M]/coeff * amount))) + if("$diamond") + linked_lathe.diamond_amount = max(0, (linked_lathe.diamond_amount-(being_built.materials[M]/coeff * amount))) + if("$bananium") + linked_lathe.clown_amount = max(0, (linked_lathe.clown_amount-(being_built.materials[M]/coeff * amount))) + else + linked_lathe.reagents.remove_reagent(M, being_built.materials[M]/coeff * amount) var/P = being_built.build_path //lets save these values before the spawn() just in case. Nobody likes runtimes. var/R = being_built.reliability @@ -827,17 +831,17 @@ won't update every console in existence) but it's more of a hassle to do. Also, for(var/M in D.materials) t = linked_lathe.check_mat(D, M) temp_material += " | " - if (!t) + if (t < 1) temp_material += "[D.materials[M]/coeff] [CallMaterialName(M)]" else temp_material += " [D.materials[M]/coeff] [CallMaterialName(M)]" c = min(c,t) - if (c) + if (c >= 1) dat += "[D.name]" - if(c >= 5.0) + if(c >= 5) dat += "x5" - if(c >= 10.0) + if(c >= 10) dat += "x10" dat += "[temp_material]" else @@ -860,17 +864,17 @@ won't update every console in existence) but it's more of a hassle to do. Also, for(var/M in D.materials) t = linked_lathe.check_mat(D, M) temp_material += " | " - if (!t) + if (t < 1) temp_material += "[D.materials[M]/coeff] [CallMaterialName(M)]" else temp_material += " [D.materials[M]/coeff] [CallMaterialName(M)]" c = min(c,t) - if (c) + if (c >= 1) dat += "[D.name]" - if(c >= 5.0) + if(c >= 5) dat += "x5" - if(c >= 10.0) + if(c >= 10) dat += "x10" dat += "[temp_material]" else From 55512a69e01d7020fc1cd6e939ab716058e71191 Mon Sep 17 00:00:00 2001 From: Incoming Date: Wed, 25 Feb 2015 20:06:18 -0500 Subject: [PATCH 03/13] Fixes podmen always being sickenly off white --- code/modules/hydroponics/hydroponics.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 200a6349dfa..1ec754eb09e 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -842,7 +842,7 @@ obj/machinery/hydroponics/attackby(var/obj/item/O as obj, var/mob/user as mob) podman.ckey = ckey_holder podman.gender = blood_gender podman.faction |= factions - hardset_dna(podman,null,null,podman.real_name,blood_type,/datum/species/plant/pod,"#59CE00")//Discard SE's and UI's, podman cloning is inaccurate, and always make them a podman + hardset_dna(podman,null,null,podman.real_name,blood_type,/datum/species/plant/pod)//Discard SE's and UI's, podman cloning is inaccurate, and always make them a podman podman.set_cloned_appearance() else //else, one packet of seeds. maybe two From 5ac23f6a2da6dcd28238d9ae24fe8349fe0981fb Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Wed, 25 Feb 2015 23:27:03 -0800 Subject: [PATCH 04/13] Panic bunker + new player notification configs Panic bunker! Uses the power of the database to reject new ckeys that have never connected to the server(s) before. Admins are exempt, even while deadmin'ed, but they are also unlikely to be new ckeys except in rare cases that involve vv fuckary. New player notification is now configurable. Adds a optional default disabled irc notification of a players first time connecting. --- code/controllers/configuration.dm | 9 +++++++++ code/modules/admin/admin_verbs.dm | 6 ++++-- code/modules/admin/verbs/panicbunker.dm | 16 +++++++++++++++ code/modules/client/client procs.dm | 27 +++++++++++++++++++++---- config/config.txt | 14 ++++++++++++- tgstation.dme | 1 + 6 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 code/modules/admin/verbs/panicbunker.dm diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 7e9ea75db89..89333ee5fa2 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -85,6 +85,9 @@ var/humans_need_surnames = 0 var/allow_random_events = 0 // enables random events mid-round when set to 1 var/allow_ai = 0 // allow ai job + var/panic_bunker = 0 // prevents new people it hasn't seen before from connecting + var/notify_new_player_age = 0 // how long do we notify admins of a new player + var/irc_first_connection_alert = 0 // do we notify the irc channel when somebody is connecting for the first time? var/traitor_scaling_coeff = 6 //how much does the amount of players get divided by to determine traitors var/changeling_scaling_coeff = 6 //how much does the amount of players get divided by to determine changelings @@ -316,6 +319,12 @@ config.hard_popcap_message = value if("extreme_popcap_message") config.extreme_popcap_message = value + if("panic_bunker") + config.panic_bunker = 1 + if("notify_new_player_age") + config.notify_new_player_age = text2num(value) + if("irc_first_connection_alert") + config.irc_first_connection_alert = 1 else diary << "Unknown setting in configuration: '[name]'" diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 0434213187c..f35c93a3e89 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -100,7 +100,8 @@ var/list/admin_verbs_server = list( /datum/admins/proc/toggleAI, /client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/ /client/proc/cmd_debug_del_all, - /client/proc/toggle_random_events + /client/proc/toggle_random_events, + /client/proc/panicbunker ) var/list/admin_verbs_debug = list( /client/proc/restart_controller, @@ -190,7 +191,8 @@ var/list/admin_verbs_hideable = list( /proc/possess, /proc/release, /client/proc/reload_admins, - /client/proc/reset_all_tcs + /client/proc/reset_all_tcs, + /client/proc/panicbunker ) /client/proc/add_admin_verbs() diff --git a/code/modules/admin/verbs/panicbunker.dm b/code/modules/admin/verbs/panicbunker.dm new file mode 100644 index 00000000000..e9b12dc5ba2 --- /dev/null +++ b/code/modules/admin/verbs/panicbunker.dm @@ -0,0 +1,16 @@ +/client/proc/panicbunker() + set category = "Server" + set name = "Toggle Panic Bunker" + if (!config.sql_enabled) + usr << "The Database is not enabled!" + return + + if (!dbcon || !dbcon.IsConnected()) + usr << "The Database is not connected!" + return + + config.panic_bunker = (!config.panic_bunker) + log_admin("[key_name(usr)] has toggled the Panic Bunker, it is now [(config.panic_bunker?"on":"off")]") + message_admins("[key_name_admin(usr)] has toggled the Panic Bunker, it is now [(config.panic_bunker?"on":"off")]") + feedback_add_details("admin_verb","PANIC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 32ae0986467..51ca6ecf703 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -136,6 +136,24 @@ var/next_external_rsc = 0 add_verbs_from_config() set_client_age_from_db() + if (isnum(player_age) && player_age == -1) //first connection + if (config.panic_bunker && !holder && !(ckey in deadmins)) + src << "Sorry but the server is currently not accepting connections from never before seen players." + del(src) + return 0 + + if (config.notify_new_player_age >= 0) + message_admins("New user: [key_name_admin(src)] is connecting here for the first time.") + + if (config.irc_first_connection_alert) + send2irc("New user", "[key_name(src)] is connecting for the first time!") + + player_age = 0 // set it from -1 to 0 so the job selection code doesn't have a panic attack + + else if (isnum(player_age) && player_age < config.notify_new_player_age) + message_admins("New user: [key_name_admin(src)] just connected with an age of [player_age] day[(player_age==1?"":"s")]") + + if (!ticker || ticker.current_state == GAME_STATE_PREGAME) spawn (rand(10,150)) if (src) @@ -172,14 +190,15 @@ var/next_external_rsc = 0 var/sql_ckey = sanitizeSQL(src.ckey) var/DBQuery/query = dbcon.NewQuery("SELECT id, datediff(Now(),firstseen) as age FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") - query.Execute() + if (!query.Execute()) + return while (query.NextRow()) player_age = text2num(query.item[2]) return - //player not found. - player_age = 0 - message_admins("[key_name_admin(src)] is connecting here for the first time.") + + //no match mark it as a first connection for use in client/New() + player_age = -1 /client/proc/sync_client_with_db() diff --git a/config/config.txt b/config/config.txt index 412245906b1..e28c4ff6b2f 100644 --- a/config/config.txt +++ b/config/config.txt @@ -175,4 +175,16 @@ HARD_MESSAGE The server is currently serving a high number of users, You cannot #EXTREME_POPCAP 200 ## Message for extreme cap -EXTREME_MESSAGE The server is currently serving a high number of users, find alternative servers. \ No newline at end of file +EXTREME_MESSAGE The server is currently serving a high number of users, find alternative servers. + +## Notify admins when a new player connects for the first x days a player's been around. (0 for first connection only, -1 for never) +## Requres database +NOTIFY_NEW_PLAYER_AGE 0 + +## Notify the irc channel when a new player makes their first connection +## Requres database +#IRC_FIRST_CONNECTION_ALERT + +## Deny all new connections by ckeys we haven't seen before (exempts admins and only denies the connection if the database is enabled and connected) +## Requires database +#PANIC_BUNKER \ No newline at end of file diff --git a/tgstation.dme b/tgstation.dme index 6bce7153849..08724346457 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -798,6 +798,7 @@ #include "code\modules\admin\verbs\modifyvariables.dm" #include "code\modules\admin\verbs\one_click_antag.dm" #include "code\modules\admin\verbs\onlyone.dm" +#include "code\modules\admin\verbs\panicbunker.dm" #include "code\modules\admin\verbs\playsound.dm" #include "code\modules\admin\verbs\possess.dm" #include "code\modules\admin\verbs\pray.dm" From f707a7824d9c870f22eb75136e8442198a583725 Mon Sep 17 00:00:00 2001 From: Delimusca Date: Thu, 26 Feb 2015 00:19:30 -0700 Subject: [PATCH 05/13] fixes heater construction --- code/game/machinery/constructable_frame.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 941a445e17b..5e77e908e94 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -388,7 +388,7 @@ to destroy them and players will be able to make replacements. board_type = "machine" origin_tech = "materials=2;engineering=2" req_components = list( - /obj/item/weapon/stock_parts/manipulator = 1, + /obj/item/weapon/stock_parts/micro_laser = 1, /obj/item/weapon/stock_parts/console_screen = 1) //Almost the same recipe as destructive analyzer to give people choices. From f36300d483945b30641aa7e02296a521e63b00f9 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Thu, 26 Feb 2015 01:26:37 -0800 Subject: [PATCH 06/13] Fixes subsystems firing way to fast at round start. --- code/controllers/master_controller.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index d3a96e20ce2..7bee21bbb03 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -108,6 +108,7 @@ Note: you can set the datum's defined processing_interval to some integer to set /datum/controller/game_controller/proc/roundHasStarted() for(var/datum/subsystem/SS in subsystems) SS.can_fire = 1 + SS.next_fire = world.time /datum/controller/game_controller/proc/Recover() var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n" From b39f120f4b26a73d3a17ac09ccaf0c8f4cc51b06 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Thu, 26 Feb 2015 14:07:56 -0800 Subject: [PATCH 07/13] Makes the irc new player alert option behave like irc admin pm notices It will only trigger if the new player alert is enabled for admin display and no valid non-afk admins are on to see it. --- code/modules/admin/verbs/adminhelp.dm | 37 +++++++++++++++++---------- code/modules/client/client procs.dm | 7 ++--- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 09e3cc4484c..93da6704f1f 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -88,6 +88,23 @@ var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey"," msg = "HELP: [key_name(src, 1)] (?) (PP) (VV) (SM) (JMP) (CA) [ai_found ? " (CL)" : ""]: [msg]" //send this msg to all admins + + for(var/client/X in admins) + if(X.prefs.toggles & SOUND_ADMINHELP) + X << 'sound/effects/adminhelp.ogg' + X << msg + + + //show it to the person adminhelping too + src << "PM to-Admins: [original_msg]" + + //send it to irc if nobody is on and tell us how many were on + var/admin_number_present = send2irc_adminless_only(original_msg) + log_admin("HELP: [key_name(src)]: [original_msg] - heard by [admin_number_present] non-AFK admins who have +BAN.") + feedback_add_details("admin_verb","AH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN) var/admin_number_total = 0 //Total number of admins var/admin_number_afk = 0 //Holds the number of admins who are afk var/admin_number_ignored = 0 //Holds the number of admins without +BAN (so admins who are not really admins) @@ -95,30 +112,24 @@ var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey"," for(var/client/X in admins) admin_number_total++; var/invalid = 0 - if(!check_rights_for(X, R_BAN)) + if(requiredflags != 0 && !check_rights_for(X, requiredflags)) admin_number_ignored++ invalid = 1 if(X.is_afk()) admin_number_afk++ invalid = 1 + if(X.holder.fakekey) + admin_number_ignored++ + invalid = 1 if(invalid) admin_number_decrease++ - if(X.prefs.toggles & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' - X << msg - - //show it to the person adminhelping too - src << "PM to-Admins: [original_msg]" - var/admin_number_present = admin_number_total - admin_number_decrease //Number of admins who are neither afk nor invalid - log_admin("HELP: [key_name(src)]: [original_msg] - heard by [admin_number_present] non-AFK admins who have +BAN.") if(admin_number_present <= 0) if(!admin_number_afk && !admin_number_ignored) - send2irc(ckey, "[original_msg] - No admins online") + send2irc(source, "[msg] - No admins online") else - send2irc(ckey, "[original_msg] - All admins AFK ([admin_number_afk]/[admin_number_total]) or skipped ([admin_number_ignored]/[admin_number_total])") - feedback_add_details("admin_verb","AH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return + send2irc(source, "[msg] - All admins AFK ([admin_number_afk]/[admin_number_total]) or skipped ([admin_number_ignored]/[admin_number_total])") + return admin_number_present proc/send2irc(msg,msg2) if(config.useircbot) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 51ca6ecf703..4bc8b7f6504 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -90,7 +90,9 @@ var/list/external_rsc_urls var/next_external_rsc = 0 #endif + /client/New(TopicData) + TopicData = null //Prevent calls to client.Topic from connect if(connection != "seeker") //Invalid connection type. @@ -144,9 +146,8 @@ var/next_external_rsc = 0 if (config.notify_new_player_age >= 0) message_admins("New user: [key_name_admin(src)] is connecting here for the first time.") - - if (config.irc_first_connection_alert) - send2irc("New user", "[key_name(src)] is connecting for the first time!") + if (config.irc_first_connection_alert) + send2irc_adminless_only("New user", "[key_name(src)] is connecting for the first time!") player_age = 0 // set it from -1 to 0 so the job selection code doesn't have a panic attack From 8a798c59b6fba7d386fa2fbd5f722ad3f22a0b2a Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Thu, 26 Feb 2015 14:44:52 -0800 Subject: [PATCH 08/13] Adds panic bunker changelog --- .../MrStonedOne-panicBunker-PR-8006.yml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 html/changelogs/MrStonedOne-panicBunker-PR-8006.yml diff --git a/html/changelogs/MrStonedOne-panicBunker-PR-8006.yml b/html/changelogs/MrStonedOne-panicBunker-PR-8006.yml new file mode 100644 index 00000000000..4b4a1cc6a67 --- /dev/null +++ b/html/changelogs/MrStonedOne-panicBunker-PR-8006.yml @@ -0,0 +1,40 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# spellcheck (typo fixes) +# experiment +# tgs (TG-ported fixes?) +################################# + +# Your name. +author: MrStonedOne + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. Fuck you N3X15, should have made this work with tabs +# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit. +# Please surround your changes in single (') or double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: 'Adds a Panic Bunker!' + - tweak: 'This will prevent any player who has never connected before from connecting. (Admins are exempt)' + - tweak: 'Can be enabled per-round by any admin with +SERVER or for longer periods via the config.' + - recadd: 'Also adds some other config options relating to new joins, server operators should consult their configuration file for more details.' + \ No newline at end of file From af4c2d86d6a6428f345940ef524bf84ef73c2dd1 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Thu, 26 Feb 2015 16:02:26 -0800 Subject: [PATCH 09/13] Tweaks panic bunker verb logic a little bit Allows toggling even if the database is not connected, alerts admins instead. --- code/modules/admin/verbs/panicbunker.dm | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/code/modules/admin/verbs/panicbunker.dm b/code/modules/admin/verbs/panicbunker.dm index e9b12dc5ba2..ff530656cbf 100644 --- a/code/modules/admin/verbs/panicbunker.dm +++ b/code/modules/admin/verbs/panicbunker.dm @@ -5,12 +5,11 @@ usr << "The Database is not enabled!" return - if (!dbcon || !dbcon.IsConnected()) - usr << "The Database is not connected!" - return - config.panic_bunker = (!config.panic_bunker) + log_admin("[key_name(usr)] has toggled the Panic Bunker, it is now [(config.panic_bunker?"on":"off")]") - message_admins("[key_name_admin(usr)] has toggled the Panic Bunker, it is now [(config.panic_bunker?"on":"off")]") + message_admins("[key_name_admin(usr)] has toggled the Panic Bunker, it is now [(config.panic_bunker?"enabled":"disabled")].") + if (config.panic_bunker && (!dbcon || !dbcon.IsConnected())) + message_admins("The Database is not connected! Panic bunker will not work until the connection is reestablished.") feedback_add_details("admin_verb","PANIC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! From 5529222b624ac713fa319b3befa46c18b9c1d466 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Thu, 26 Feb 2015 16:48:43 -0800 Subject: [PATCH 10/13] Fixes guests being allowed to connect while panic bunker is active I was tempted to move all the panic bunker shit to isbanned(), but doing so would mean adding yet ANOTHER query that gets run on every connection, and run for every player on world restart. That just seemed like not worth the effort to just make panic bunker trigger a little bit earier. --- code/modules/admin/IsBanned.dm | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 4a6433fe273..4abe574216a 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -42,10 +42,15 @@ world/IsBanned(key,address,computer_id) return ..() //Guest Checking - if(!guests_allowed && IsGuestKey(key)) - log_access("Failed Login: [key] - Guests not allowed") - message_admins("Failed Login: [key] - Guests not allowed") - return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.") + if(IsGuestKey(key)) + if (!guests_allowed) + log_access("Failed Login: [key] - Guests not allowed") + message_admins("Failed Login: [key] - Guests not allowed") + return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.") + if (config.panic_bunker && dbcon && dbcon.IsConnected()) + log_access("Failed Login: [key] - Guests not allowed during panic bunker") + message_admins("Failed Login: [key] - Guests not allowed during panic bunker") + return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.") //Population Cap Checking if(config.extreme_popcap && living_player_count() >= config.extreme_popcap && !(ckey(key) in admin_datums)) From cf5d5794c705d9e6ec7ccf2c95f0b5c86966c247 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Thu, 26 Feb 2015 16:54:20 -0800 Subject: [PATCH 11/13] Adds failed login message for panic bunker. --- code/modules/client/client procs.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 4bc8b7f6504..100c7cb7202 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -140,6 +140,8 @@ var/next_external_rsc = 0 if (isnum(player_age) && player_age == -1) //first connection if (config.panic_bunker && !holder && !(ckey in deadmins)) + 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") src << "Sorry but the server is currently not accepting connections from never before seen players." del(src) return 0 From 8807c433aee3f8d93c50cb6ae9a32bc3fefc4453 Mon Sep 17 00:00:00 2001 From: paprka Date: Fri, 27 Feb 2015 04:21:56 -0800 Subject: [PATCH 12/13] modifies dark matter --- code/modules/reagents/Chemistry-Goon-Pyrotechnics.dm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/modules/reagents/Chemistry-Goon-Pyrotechnics.dm b/code/modules/reagents/Chemistry-Goon-Pyrotechnics.dm index 0d503e6164f..0f20253e345 100644 --- a/code/modules/reagents/Chemistry-Goon-Pyrotechnics.dm +++ b/code/modules/reagents/Chemistry-Goon-Pyrotechnics.dm @@ -101,10 +101,10 @@ id = "liquid_dark_matter" result = "liquid_dark_matter" required_reagents = list("stable_plasma" = 1, "radium" = 1, "carbon" = 1) - result_amount = 3 + result_amount = null required_temp = 474 -/datum/reagent/liquid_dark_matter/reaction_turf(var/turf/simulated/T, var/volume) +/*/datum/reagent/liquid_dark_matter/reaction_turf(var/turf/simulated/T, var/volume) if(istype(T, /turf/simulated/floor/)) goonchem_vortex(T, 0, 5, 3) return @@ -114,13 +114,13 @@ if(method == TOUCH) var/turf/simulated/T = get_turf(M) goonchem_vortex(T, 0, 5, 3) - return + return*/ //o god what the fuck goof /datum/chemical_reaction/liquid_dark_matter/on_reaction(var/datum/reagents/holder, var/created_volume) var/turf/simulated/T = get_turf(holder.my_atom) goonchem_vortex(T, 0, 5, 6) return -proc/goonchem_vortex(var/turf/simulated/T, var/setting_type, var/range, var/pull_times) +/proc/goonchem_vortex(var/turf/simulated/T, var/setting_type, var/range, var/pull_times) for(var/atom/movable/X in orange(range, T)) if(istype(X, /obj/effect)) continue //stop pulling smoke and hotspots please From d2f44872aac286f7f2a5d52b5241d6e3daff02a0 Mon Sep 17 00:00:00 2001 From: Mandurrrh Date: Fri, 27 Feb 2015 12:01:09 -0500 Subject: [PATCH 13/13] Adds emp, emag, and repairs for barsign. --- code/game/objects/structures/barsigns.dm | 135 +++++++++++++++++++++-- icons/obj/barsigns.dmi | Bin 64609 -> 70381 bytes 2 files changed, 127 insertions(+), 8 deletions(-) diff --git a/code/game/objects/structures/barsigns.dm b/code/game/objects/structures/barsigns.dm index 42c1f95c4b3..fbef8b501a6 100644 --- a/code/game/objects/structures/barsigns.dm +++ b/code/game/objects/structures/barsigns.dm @@ -5,18 +5,32 @@ icon_state = "empty" req_access = list(access_bar) var/list/barsigns=list() + var/list/hiddensigns + var/broken = 0 + var/emagged = 0 + var/state = 0 + var/prev_sign = "" + var/panel_open = 0 + + + /obj/structure/sign/barsign/New() ..() + //filling the barsigns list for(var/bartype in typesof(/datum/barsign) - /datum/barsign) var/datum/barsign/signinfo = new bartype - barsigns += signinfo + if(!signinfo.hidden) + barsigns += signinfo + //randomly assigning a sign set_sign(pick(barsigns)) + + /obj/structure/sign/barsign/proc/set_sign(var/datum/barsign/sign) if(!istype(sign)) return @@ -27,96 +41,201 @@ else desc = "It displays \"[name]\"." + + /obj/structure/sign/barsign/attack_ai(mob/user as mob) return src.attack_hand(user) + + /obj/structure/sign/barsign/attack_hand(mob/user as mob) if (!src.allowed(user)) user << "Access denied." return - + if (broken) + user << "The controls seem unresponsive." + return pick_sign() + + + +/obj/structure/sign/barsign/attackby(var/obj/item/I, var/mob/user) + if(!allowed(user)) + user << "Access denied." + return + if( istype(I, /obj/item/weapon/screwdriver)) + if(!panel_open) + user << "You open the maintenance panel." + set_sign(new /datum/barsign/hiddensigns/signoff) + panel_open = 1 + else + user << "You close the maintenance panel." + if(!broken && !emagged) + set_sign(pick(barsigns)) + else if(emagged) + set_sign(new /datum/barsign/hiddensigns/syndibarsign) + else + set_sign(new /datum/barsign/hiddensigns/empbarsign) + panel_open = 0 + + if(istype(I, /obj/item/stack/cable_coil) && panel_open) + var/obj/item/stack/cable_coil/C = I + if(emagged) //Emagged, not broken by EMP + user << "Sign has been damaged beyond repair." + return + else if(!broken) + user << "This sign is functioning properly." + return + + if(C.use(2)) + user << "You replace the burnt wiring." + broken = 0 + else + user << "You need at least two lengths of cable." + + + /obj/structure/sign/barsign/emp_act(severity) - var/prev_icon = icon_state - icon_state = "empbarsign" - sleep(300*severity) //measured in deci-seconds - icon_state = prev_icon + set_sign(new /datum/barsign/hiddensigns/empbarsign) + broken = 1 + + + + +/obj/structure/sign/barsign/emag_act(mob/user) + if(broken || emagged) + user << "Nothing interesting happens." + return + user << "You emag the barsign. Takeover in progress..." + sleep(100) //10 seconds + set_sign(new /datum/barsign/hiddensigns/syndibarsign) + emagged = 1 + req_access = list(access_syndicate) + + /obj/structure/sign/barsign/proc/pick_sign() var/picked_name = input("Available Signage", "Bar Sign") as null|anything in barsigns if(!picked_name) return - set_sign(picked_name) + //Code below is to define useless variables for datums. It errors without these + + /datum/barsign var/name = "Name" var/icon = "Icon" var/desc = "desc" + var/hidden = 0 + //Anything below this is where all the specific signs are. If people want to add more signs, add them below. + + /datum/barsign/maltesefalcon name = "Maltese Falcon" icon = "maltesefalcon" - desc = "The Maltese Faclon, Space Bar and Grill" + desc = "The Maltese Falcon, Space Bar and Grill" + /datum/barsign/thebark name = "The Bark" icon = "thebark" desc = "Ian's bar of choice" + /datum/barsign/harmbaton name = "The Harmbaton" icon = "theharmbaton" desc = "A great dining experience for both security members and assistants" + /datum/barsign/thesingulo name = "The Singulo" icon = "thesingulo" desc = "Where people go that'd rather not be called by their name" + /datum/barsign/thedrunkcarp name = "The Drunk Carp" icon = "thedrunkcarp" desc = "Don't drink and Swim" + /datum/barsign/scotchservinwill name = "Scotch Servin Willy's" icon = "scotchservinwill" desc = "Willy sure moved up in the world from clown to bartender" + /datum/barsign/officerbeersky name = "Officer Beersky's" icon = "officerbeersky" desc = "Man eat a dong, these drinks are great" + /datum/barsign/thecavern name = "The Cavern" icon = "thecavern" desc = "Fine drinks while listening to some fine tunes" + /datum/barsign/theouterspess name = "The Outer Spess" icon = "theouterspess" desc = "This bar isn't actually located in outer space" + /datum/barsign/slipperyshots name = "Slippery Shots" icon = "slipperyshots" desc = "Slippery slope to drunkeness with our shots!" + /datum/barsign/thegreytide name = "The Grey Tide" icon = "thegreytide" desc = "Abandon your toolboxing ways and enjoy a lazy beer!" + /datum/barsign/honkednloaded name = "Honked 'n' Loaded" icon = "honkednloaded" desc = "Honk." + + + + +/datum/barsign/hiddensigns + hidden = 1 + + +//Hidden signs list below this point + + + +/datum/barsign/hiddensigns/empbarsign + name = "Haywire Barsign" + icon = "empbarsign" + desc = "Something has gone very wrong." + + + +/datum/barsign/hiddensigns/syndibarsign + name = "Syndi Cat Takeover" + icon = "syndibarsign" + desc = "Syndicate or die." + + + +/datum/barsign/hiddensigns/signoff + name = "Bar Sign" + icon = "empty" + desc = "This sign doesn't seem to be on." \ No newline at end of file diff --git a/icons/obj/barsigns.dmi b/icons/obj/barsigns.dmi index ce25b56f08198b2323bd91535e554d462b221568..c6b04ee3d1451610b9472aad6b76f2cd876aa5c8 100644 GIT binary patch delta 14764 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 C$Q`Bt delta 8964 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 A>Hq)$