From 60134ea8aef3f8d189eae4f65dab094de1f7d928 Mon Sep 17 00:00:00 2001 From: Ghommie <42542238+Ghommie@users.noreply.github.com> Date: Wed, 25 Dec 2019 03:34:42 +0100 Subject: [PATCH 01/46] [s] Fixes singularity pulls duping rods out of engine floors. --- code/game/turfs/simulated/floor.dm | 32 ++++++++----------- .../game/turfs/simulated/floor/fancy_floor.dm | 2 +- .../game/turfs/simulated/floor/reinf_floor.dm | 12 ++++--- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm index 194014b61a..f335846f06 100644 --- a/code/game/turfs/simulated/floor.dm +++ b/code/game/turfs/simulated/floor.dm @@ -177,7 +177,7 @@ I.play_tool_sound(src, 80) return remove_tile(user, silent) -/turf/open/floor/proc/remove_tile(mob/user, silent = FALSE, make_tile = TRUE) +/turf/open/floor/proc/remove_tile(mob/user, silent = FALSE, make_tile = TRUE, forced = FALSE) if(broken || burnt) broken = 0 burnt = 0 @@ -191,24 +191,20 @@ return make_plating() /turf/open/floor/singularity_pull(S, current_size) - ..() - if(current_size == STAGE_THREE) - if(prob(30)) + . = ..() + switch(current_size) + if(STAGE_THREE) + if(floor_tile && prob(30)) + remove_tile() + if(STAGE_FOUR) + if(floor_tile && prob(50)) + remove_tile() + if(STAGE_FIVE to INFINITY) if(floor_tile) - new floor_tile(src) - make_plating() - else if(current_size == STAGE_FOUR) - if(prob(50)) - if(floor_tile) - new floor_tile(src) - make_plating() - else if(current_size >= STAGE_FIVE) - if(floor_tile) - if(prob(70)) - new floor_tile(src) - make_plating() - else if(prob(50)) - ReplaceWithLattice() + if(prob(70)) + remove_tile() + else if(prob(50)) + ReplaceWithLattice() /turf/open/floor/narsie_act(force, ignore_mobs, probability = 20) . = ..() diff --git a/code/game/turfs/simulated/floor/fancy_floor.dm b/code/game/turfs/simulated/floor/fancy_floor.dm index 82f1a88253..f38a8a3d9b 100644 --- a/code/game/turfs/simulated/floor/fancy_floor.dm +++ b/code/game/turfs/simulated/floor/fancy_floor.dm @@ -44,7 +44,7 @@ C.play_tool_sound(src, 80) return remove_tile(user, silent, (C.tool_behaviour == TOOL_SCREWDRIVER)) -/turf/open/floor/wood/remove_tile(mob/user, silent = FALSE, make_tile = TRUE) +/turf/open/floor/wood/remove_tile(mob/user, silent = FALSE, make_tile = TRUE, forced = FALSE) if(broken || burnt) broken = 0 burnt = 0 diff --git a/code/game/turfs/simulated/floor/reinf_floor.dm b/code/game/turfs/simulated/floor/reinf_floor.dm index b04f89f8be..09625968f8 100644 --- a/code/game/turfs/simulated/floor/reinf_floor.dm +++ b/code/game/turfs/simulated/floor/reinf_floor.dm @@ -76,14 +76,16 @@ /turf/open/floor/engine/singularity_pull(S, current_size) ..() - if(current_size >= STAGE_FIVE) + if(current_size >= STAGE_FIVE && prob(30)) if(floor_tile) - if(prob(30)) - new floor_tile(src) - make_plating() - else if(prob(30)) + remove_tile(forced = TRUE) + else ReplaceWithLattice() +/turf/open/floor/engine/remove_tile(mob/user, silent = FALSE, make_tile = TRUE, forced = FALSE) + if(forced) + return ..() + /turf/open/floor/engine/attack_paw(mob/user) return attack_hand(user) From be98704abecb77fe5b40af464546acf7ce861393 Mon Sep 17 00:00:00 2001 From: Putnam Date: Sun, 5 Jan 2020 04:19:21 -0800 Subject: [PATCH 02/46] weird super secret vote --- code/controllers/configuration/configuration.dm | 4 +++- .../configuration/entries/game_options.dm | 3 +++ code/controllers/subsystem/ticker.dm | 2 +- code/controllers/subsystem/vote.dm | 12 ++++++++++-- config/game_options.txt | 5 ++++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index 14954524fa..25f05af55b 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -236,7 +236,6 @@ if(!(M.config_tag in modes)) // ensure each mode is added only once modes += M.config_tag mode_names[M.config_tag] = M.name - probabilities[M.config_tag] = M.probability mode_reports[M.config_tag] = M.generate_report() if(probabilities[M.config_tag]>0) mode_false_report_weight[M.config_tag] = M.false_report_weight @@ -342,6 +341,9 @@ if(probabilities[M.config_tag]<=0) qdel(M) continue + if(M.config_tag in SSvote.stored_gamemode_votes && SSvote.stored_gamemode_votes[M.config_tag] < Get(/datum/config_entry/number/dropped_modes)) + qdel(M) + continue if(min_pop[M.config_tag]) M.required_players = min_pop[M.config_tag] if(max_pop[M.config_tag]) diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 61b5788ef8..d045a92c83 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -392,3 +392,6 @@ /datum/config_entry/flag/allow_clockwork_marauder_on_station config_entry_value = TRUE + +/datum/config_entry/number/dropped_modes + config_entry_value = 3 diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 45c8e3c8fe..76ebfb6765 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -486,7 +486,7 @@ SUBSYSTEM_DEF(ticker) if(dynamic) SSvote.initiate_vote("dynamic","server",hideresults=TRUE,votesystem=SCORE_VOTING,forced=TRUE,vote_time = 2 MINUTES) else - SSvote.initiate_vote("roundtype","server",hideresults=TRUE,votesystem=PLURALITY_VOTING,forced=TRUE, vote_time = 2 MINUTES) + SSvote.initiate_vote("roundtype","server",hideresults=TRUE,votesystem=RANKED_CHOICE_VOTING,forced=TRUE, vote_time = 2 MINUTES) /datum/controller/subsystem/ticker/Recover() current_state = SSticker.current_state diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 573c89a5af..2563aadf71 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -116,10 +116,8 @@ SUBSYSTEM_DEF(vote) var/opposite_pref = d[j][i] if(pref_number>opposite_pref) p[i][j] = d[i][j] - p[j][i] = 0 else p[i][j] = 0 - p[j][i] = d[i][j] for(var/i in 1 to choices.len) for(var/j in 1 to choices.len) if(i != j) @@ -248,6 +246,10 @@ SUBSYSTEM_DEF(vote) if("roundtype") //CIT CHANGE - adds the roundstart extended/secret vote if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") + if(choices["secret"] > choices["extended"]) + . = "secret" + else + . = "extended" GLOB.master_mode = . SSticker.save_mode(.) message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]") @@ -382,6 +384,12 @@ SUBSYSTEM_DEF(vote) choices |= M if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote choices.Add("secret", "extended") + var/list/modes_to_add = config.votable_modes + var/list/probabilities = CONFIG_GET(keyed_list/probability) + for(var/tag in modes_to_add) + if(probabilities[tag] <= 0) + modes_to_add -= tag + choices.Add(modes_to_add) if("dynamic") for(var/T in config.storyteller_cache) var/datum/dynamic_storyteller/S = T diff --git a/config/game_options.txt b/config/game_options.txt index 34c8ca48fd..99d6b143f8 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -578,4 +578,7 @@ DYNAMIC_VOTING ## Choose which Engine to start the round with, comment to remove an Engine from the rotation BOX_RANDOM_ENGINE Engine SM BOX_RANDOM_ENGINE Engine Tesla -BOX_RANDOM_ENGINE Engine Singulo \ No newline at end of file +BOX_RANDOM_ENGINE Engine Singulo + +## Number of modes dropped by the secret vote during mode selection, after vote. +DROPPED_MODES 3 From c0f38aa27fe1b7e75b956eaab1d77766fb9c6f86 Mon Sep 17 00:00:00 2001 From: Trilbyspaceclone <30435998+Trilbyspaceclone@users.noreply.github.com> Date: Sun, 5 Jan 2020 11:40:58 -0500 Subject: [PATCH 03/46] Update food_reagents.dm --- code/modules/reagents/chemistry/reagents/food_reagents.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 64e693a326..8936866305 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -155,7 +155,7 @@ reagent_state = SOLID color = "#FFFFFF" // rgb: 255, 255, 255 taste_mult = 1.5 // stop sugar drowning out other flavours - nutriment_factor = 10 * REAGENTS_METABOLISM + nutriment_factor = 5 * REAGENTS_METABOLISM metabolization_rate = 2 * REAGENTS_METABOLISM overdose_threshold = 200 // Hyperglycaemic shock taste_description = "sweetness" From c438d39eb3aa669bb6c57ef044c765bec7c92acc Mon Sep 17 00:00:00 2001 From: Artur Date: Sun, 5 Jan 2020 22:32:29 +0100 Subject: [PATCH 04/46] Initial Commit --- code/game/gamemodes/bloodsucker/bloodsucker.dm | 2 ++ code/game/machinery/cloning.dm | 2 ++ .../antagonists/bloodsucker/datum_bloodsucker.dm | 4 ++-- .../bloodsucker/objects/bloodsucker_crypt.dm | 16 ++++++---------- code/modules/antagonists/revenant/revenant.dm | 1 + 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/code/game/gamemodes/bloodsucker/bloodsucker.dm b/code/game/gamemodes/bloodsucker/bloodsucker.dm index e784fd836d..ca2ebfe00d 100644 --- a/code/game/gamemodes/bloodsucker/bloodsucker.dm +++ b/code/game/gamemodes/bloodsucker/bloodsucker.dm @@ -9,6 +9,8 @@ var/list/vassal_allowed_antags = list(/datum/antagonist/brother, /datum/antagonist/traitor, /datum/antagonist/traitor/internal_affairs, /datum/antagonist/survivalist, \ /datum/antagonist/rev, /datum/antagonist/nukeop, /datum/antagonist/pirate, /datum/antagonist/cult, /datum/antagonist/abductee) // The antags you're allowed to be if turning Vassal. +/proc/isvamp(mob/living/M) + return istype(M) && M.mind && M.mind.has_antag_datum(/datum/antagonist/bloodsucker) /datum/game_mode/bloodsucker name = "bloodsucker" diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index e683d67be2..97b260f3bf 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -155,6 +155,8 @@ mess = TRUE update_icon() return FALSE + if(isvamp(clonemind)) //If the mind is a bloodsucker + return FALSE attempting = TRUE //One at a time!! countdown.start() diff --git a/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm b/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm index 97b4437298..33e374314f 100644 --- a/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm +++ b/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm @@ -35,8 +35,8 @@ var/warn_sun_burn = FALSE // So we only get the sun burn message once per day. var/had_toxlover = FALSE // LISTS - var/static/list/defaultTraits = list (TRAIT_STABLEHEART, TRAIT_NOBREATH, TRAIT_SLEEPIMMUNE, TRAIT_NOCRITDAMAGE, TRAIT_RESISTCOLD, TRAIT_RADIMMUNE, TRAIT_VIRUSIMMUNE, TRAIT_NIGHT_VISION, \ - TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_NONATURALHEAL, TRAIT_NOMARROW, TRAIT_NOPULSE, TRAIT_NOCLONE) + var/static/list/defaultTraits = list (TRAIT_STABLEHEART, TRAIT_NOBREATH, TRAIT_SLEEPIMMUNE, TRAIT_NOCRITDAMAGE, TRAIT_RESISTCOLD, TRAIT_RADIMMUNE, TRAIT_NIGHT_VISION, \ + TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_NONATURALHEAL, TRAIT_NOMARROW, TRAIT_NOPULSE) // NOTES: TRAIT_AGEUSIA <-- Doesn't like flavors. // REMOVED: TRAIT_NODEATH // TO ADD: diff --git a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm index 3493622945..1a5cea9449 100644 --- a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm +++ b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm @@ -130,7 +130,7 @@ /obj/structure/bloodsucker/vassalrack/MouseDrop_T(atom/movable/O, mob/user) if(!O.Adjacent(src) || O == user || !isliving(O) || !isliving(user) || useLock || has_buckled_mobs() || user.incapacitated()) return - if(!anchored && user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)) + if(!anchored && isvamp(user)) to_chat(user, "Until this rack is secured in place, it cannot serve its purpose.") return // PULL TARGET: Remember if I was pullin this guy, so we can restore this @@ -183,7 +183,7 @@ /obj/structure/bloodsucker/vassalrack/user_unbuckle_mob(mob/living/M, mob/user) // Attempt Unbuckle - if(!user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)) + if(!isvamp(user)) if(M == user) M.visible_message("[user] tries to release themself from the rack!",\ "You attempt to release yourself from the rack!") // For sound if not seen --> "You hear a squishy wet noise.") @@ -453,7 +453,7 @@ /obj/structure/bloodsucker/candelabrum/examine(mob/user) . = ..() - if((user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)) || isobserver(user)) + if((isvamp()) || isobserver(user)) . += {"This is a magical candle which drains at the sanity of mortals who are not under your command while it is active."} . += {"You can alt click on it from any range to turn it on remotely, or simply be next to it and click on it to turn it on and off normally."} /* if(user.mind.has_antag_datum(ANTAG_DATUM_VASSAL) @@ -461,15 +461,13 @@ You can turn it on and off by clicking on it while you are next to it"} */ /obj/structure/bloodsucker/candelabrum/attack_hand(mob/user) - var/datum/antagonist/bloodsucker/V = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) //I wish there was a better way to do this var/datum/antagonist/vassal/T = user.mind.has_antag_datum(ANTAG_DATUM_VASSAL) - if(istype(V) || istype(T)) + if(isvamp(user) || istype(T)) toggle() /obj/structure/bloodsucker/candelabrum/AltClick(mob/user) - var/datum/antagonist/bloodsucker/V = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) // Bloodsuckers can turn their candles on from a distance. SPOOOOKY. - if(istype(V)) + if(isvamp(user)) toggle() /obj/structure/bloodsucker/candelabrum/proc/toggle(mob/user) @@ -486,9 +484,7 @@ if(lit) for(var/mob/living/carbon/human/H in viewers(7, src)) var/datum/antagonist/vassal/T = H.mind.has_antag_datum(ANTAG_DATUM_VASSAL) - var/datum/antagonist/bloodsucker/V = H.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) - if(V || T) //We dont want vassals or vampires affected by this - return + if(!isvamp(H) || !T) //We dont want vassals or vampires affected by this H.hallucination = 20 SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm index d7d88a9b6a..b98fa04504 100644 --- a/code/modules/antagonists/revenant/revenant.dm +++ b/code/modules/antagonists/revenant/revenant.dm @@ -26,6 +26,7 @@ spacewalk = TRUE sight = SEE_SELF throwforce = 0 + blood_volume = 0 see_in_dark = 8 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE From 1afd3d76cb7725e8c2e09b5bb3e71271eafb2922 Mon Sep 17 00:00:00 2001 From: Ghommie <42542238+Ghommie@users.noreply.github.com> Date: Mon, 6 Jan 2020 01:18:11 +0100 Subject: [PATCH 05/46] Fixes lattice's examine. --- code/game/objects/structures/lattice.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm index a7eaea0d16..c41d6d32af 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -18,7 +18,7 @@ // flags = CONDUCT_1 /obj/structure/lattice/examine(mob/user) - ..() + . = ..() . += deconstruction_hints(user) /obj/structure/lattice/proc/deconstruction_hints(mob/user) From da830d3449c1b932af73b7c62788bd3c5a391356 Mon Sep 17 00:00:00 2001 From: Putnam Date: Sun, 5 Jan 2020 19:49:54 -0800 Subject: [PATCH 06/46] Added scores for my own dastardly reasons --- code/controllers/subsystem/vote.dm | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 2563aadf71..cb1a13541e 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -15,6 +15,7 @@ SUBSYSTEM_DEF(vote) var/vote_system = PLURALITY_VOTING var/question = null var/list/choices = list() + var/list/scores = list() var/list/choice_descs = list() // optional descriptions var/list/voted = list() var/list/voting = list() @@ -54,6 +55,7 @@ SUBSYSTEM_DEF(vote) choice_descs.Cut() voted.Cut() voting.Cut() + scores.Cut() obfuscated = FALSE //CIT CHANGE - obfuscated votes remove_action_buttons() @@ -178,6 +180,22 @@ SUBSYSTEM_DEF(vote) score.Cut(median_pos,median_pos+1) choices[score_name]++ +/datum/controller/subsystem/vote/proc/calculate_scores(var/blackbox_text) + var/list/scores_by_choice = list() + for(var/choice in choices) + scores_by_choice[choice] = list() + for(var/ckey in voted) + var/list/this_vote = voted[ckey] + for(var/choice in this_vote) + sorted_insert(scores_by_choice[choice],this_vote[choice],/proc/cmp_numeric_asc) + var/middle_score = round(GLOB.vote_score_options.len/2,1) + for(var/score_name in scores_by_choice) + var/list/score = scores_by_choice[score_name] + for(var/S in score) + scores[score_name] += S-middle_score + SSblackbox.record_feedback("nested tally","voting",scores[score_name],list(blackbox_text,"Total scores",score_name)) + + /datum/controller/subsystem/vote/proc/announce_result() var/vote_title_text var/text @@ -191,6 +209,7 @@ SUBSYSTEM_DEF(vote) calculate_condorcet_votes(vote_title_text) if(vote_system == SCORE_VOTING) calculate_majority_judgement_vote(vote_title_text) + calculate_scores(vote_title_text) var/list/winners = get_result() var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic" if(winners.len > 0) From 6d3345d166e9972caa9af7ddcf1c10fa7e1e2e82 Mon Sep 17 00:00:00 2001 From: r4d6 Date: Sun, 5 Jan 2020 23:32:44 -0500 Subject: [PATCH 07/46] Add the Playback Device --- code/modules/assembly/playback.dm | 50 +++++++++++++++++++++++++++++++ code/modules/vending/assist.dm | 1 + tgstation.dme | 1 + 3 files changed, 52 insertions(+) create mode 100644 code/modules/assembly/playback.dm diff --git a/code/modules/assembly/playback.dm b/code/modules/assembly/playback.dm new file mode 100644 index 0000000000..088f186adc --- /dev/null +++ b/code/modules/assembly/playback.dm @@ -0,0 +1,50 @@ +/obj/item/assembly/playback + name = "playback device" + desc = "A small electronic device able to record a voice sample, and repeat that sample when it receive a signal." + icon_state = "radio" + materials = list(MAT_METAL=500, MAT_GLASS=50) + flags_1 = HEAR_1 + attachable = TRUE + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + var/listening = FALSE + var/recorded = "" //the activation message + var/languages + +/obj/item/assembly/playback/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(speaker == src) + return + + if(listening && !radio_freq) + record_speech(speaker, raw_message, message_language) + +/obj/item/assembly/playback/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) + recorded = raw_message + listening = FALSE + languages = message_language + say("Activation message is '[recorded]'.", language = message_language) + +/obj/item/assembly/playback/activate() + if(recorded == "") // Why say anything when there isn't anything to say + return FALSE + say("[recorded]", language = languages) // Repeat the message in the language it was said in + return TRUE + +/obj/item/assembly/playback/proc/record() + if(!secured || holder) + return FALSE + listening = !listening + say("[listening ? "Now" : "No longer"] recording input.") + return TRUE + +/obj/item/assembly/playback/attack_self(mob/user) + if(!user) + return FALSE + record() + return TRUE + +/obj/item/assembly/playback/toggle_secure() + . = ..() + listening = FALSE \ No newline at end of file diff --git a/code/modules/vending/assist.dm b/code/modules/vending/assist.dm index 280bcb5ca2..0d0dd40b36 100644 --- a/code/modules/vending/assist.dm +++ b/code/modules/vending/assist.dm @@ -1,6 +1,7 @@ /obj/machinery/vending/assist products = list(/obj/item/assembly/prox_sensor = 7, /obj/item/assembly/igniter = 6, + /obj/item/assembly/playback = 4, /obj/item/assembly/signaler = 6, /obj/item/wirecutters = 3, /obj/item/stock_parts/cell/crap = 6, diff --git a/tgstation.dme b/tgstation.dme index 7e7d69d6f1..6818de8d04 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -1440,6 +1440,7 @@ #include "code\modules\assembly\igniter.dm" #include "code\modules\assembly\infrared.dm" #include "code\modules\assembly\mousetrap.dm" +#include "code\modules\assembly\playback.dm" #include "code\modules\assembly\proximity.dm" #include "code\modules\assembly\shock_kit.dm" #include "code\modules\assembly\signaler.dm" From f6099f341bb1f568bfd7aff4a89a5259033b5520 Mon Sep 17 00:00:00 2001 From: r4d6 Date: Sun, 5 Jan 2020 23:55:31 -0500 Subject: [PATCH 08/46] Allow Voice Analyzers to recognize languages --- code/modules/assembly/voice.dm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index f72f726988..36a58b2a70 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -15,6 +15,7 @@ verb_exclaim = "beeps" var/listening = FALSE var/recorded = "" //the activation message + var/languages // The Message's language var/mode = 1 var/static/list/modes = list("inclusive", "exclusive", @@ -33,23 +34,25 @@ if(listening && !radio_freq) record_speech(speaker, raw_message, message_language) else - if(check_activation(speaker, raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) + if(message_language == languages) // If it isn't in the same language as the message, don't try to find the message + if(check_activation(speaker, raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) /obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) + languages = message_language // Assign the message's language to a variable to use it elsewhere switch(mode) if(INCLUSIVE_MODE) recorded = raw_message listening = FALSE - say("Activation message is '[recorded]'.", message_language) + say("Activation message is '[recorded]'.", language = languages) // Say the message in the language it was said in if(EXCLUSIVE_MODE) recorded = raw_message listening = FALSE - say("Activation message is '[recorded]'.", message_language) + say("Activation message is '[recorded]'.", language = languages) if(RECOGNIZER_MODE) recorded = speaker.GetVoice() listening = FALSE - say("Your voice pattern is saved.", message_language) + say("Your voice pattern is saved.", language = languages) if(VOICE_SENSOR_MODE) if(length(raw_message)) addtimer(CALLBACK(src, .proc/pulse, 0), 10) From 72dded26f27c60642908eeec51d5200be96e0171 Mon Sep 17 00:00:00 2001 From: Putnam Date: Mon, 6 Jan 2020 02:06:19 -0800 Subject: [PATCH 09/46] cold-blooded trait --- code/datums/traits/neutral.dm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm index 911929896b..f0a925b9d1 100644 --- a/code/datums/traits/neutral.dm +++ b/code/datums/traits/neutral.dm @@ -121,3 +121,12 @@ mob_trait = TRAIT_EXHIBITIONIST gain_text = "You feel like exposing yourself to the world." lose_text = "Indecent exposure doesn't sound as charming to you anymore." + +/datum/quirk/coldblooded + name = "Cold-blooded" + desc = "Your body doesn't create its own internal heat, requiring external heat regulation." + value = 0 + medical_record_text = "Patient is ectothermic." + mob_trait = TRAIT_COLDBLOODED + gain_text = "You feel cold-blooded." + lose_text = "You feel more warm-blooded." From 00d0d8c415a669cfd849a099cc58592b453e35e4 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 6 Jan 2020 12:31:54 +0100 Subject: [PATCH 10/46] Whoops indentation --- .../antagonists/bloodsucker/objects/bloodsucker_crypt.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm index 1a5cea9449..e17531afcb 100644 --- a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm +++ b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm @@ -485,8 +485,8 @@ for(var/mob/living/carbon/human/H in viewers(7, src)) var/datum/antagonist/vassal/T = H.mind.has_antag_datum(ANTAG_DATUM_VASSAL) if(!isvamp(H) || !T) //We dont want vassals or vampires affected by this - H.hallucination = 20 - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle) + H.hallucination = 20 + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // OTHER THINGS TO USE: HUMAN BLOOD. /obj/effect/decal/cleanable/blood From b0cb6aab5c4d81881c1b3824d3edbc558161cc91 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 6 Jan 2020 17:26:36 +0100 Subject: [PATCH 11/46] Final commit hopefully --- code/datums/status_effects/debuffs.dm | 6 +++++ .../bloodsucker/bloodsucker_life.dm | 26 +++++++++---------- .../bloodsucker/datum_bloodsucker.dm | 4 +-- .../antagonists/bloodsucker/datum_vassal.dm | 5 ---- .../bloodsucker/items/bloodsucker_organs.dm | 5 ++++ .../bloodsucker/objects/bloodsucker_crypt.dm | 7 ++--- .../bloodsucker/powers/bs_masquerade.dm | 13 +++++++--- .../bloodsucker/powers/bs_mesmerize.dm | 5 +--- 8 files changed, 41 insertions(+), 30 deletions(-) diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index f5f012e7f9..c3f74ad44e 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -99,6 +99,12 @@ id = "Mesmerize" alert_type = /obj/screen/alert/status_effect/mesmerized +/datum/status_effect/no_combat_mode/mesmerize/on_apply() + ADD_TRAIT(owner, TRAIT_MUTE, "mesmerize") + +/datum/status_effect/no_combat_mode/mesmerize/on_remove() + REMOVE_TRAIT(owner, TRAIT_MUTE, "mesmerize") + /obj/screen/alert/status_effect/mesmerized name = "Mesmerized" desc = "You cant tear your sight from who is in front of you...Their gaze is simply too enthralling.." diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm index 9174692b49..95c85e7344 100644 --- a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm +++ b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm @@ -187,19 +187,19 @@ /datum/antagonist/bloodsucker/proc/HandleDeath() // FINAL DEATH // Fire Damage? (above double health) - if (owner.current.getFireLoss_nonProsthetic() >= owner.current.getMaxHealth() * 2) + if(owner.current.getFireLoss_nonProsthetic() >= owner.current.getMaxHealth() * 1.5) FinalDeath() return // Staked while "Temp Death" or Asleep - if (owner.current.StakeCanKillMe() && owner.current.AmStaked()) + if(owner.current.StakeCanKillMe() && owner.current.AmStaked()) FinalDeath() return // Not "Alive"? - if (!owner.current || !isliving(owner.current) || isbrain(owner.current) || !get_turf(owner.current)) + if(!owner.current || !isliving(owner.current) || isbrain(owner.current) || !get_turf(owner.current)) FinalDeath() return // Missing Brain or Heart? - if (!owner.current.HaveBloodsuckerBodyparts()) + if(!owner.current.HaveBloodsuckerBodyparts()) FinalDeath() return // Disable Powers: Masquerade * NOTE * This should happen as a FLAW! @@ -212,19 +212,19 @@ var/total_toxloss = owner.current.getToxLoss() //This is neater than just putting it in total_damage var/total_damage = total_brute + total_burn + total_toxloss // Died? Convert to Torpor (fake death) - if (owner.current.stat >= DEAD) + if(owner.current.stat >= DEAD) Torpor_Begin() to_chat(owner, "Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor.") if (poweron_masquerade == TRUE) to_chat(owner, "Your wounds will not heal until you disable the Masquerade power.") // End Torpor: else // No damage, OR toxin healed AND brute healed and NOT in coffin (since you cannot heal burn) - if (total_damage <= 0 || total_toxloss <= 0 && total_brute <= 0 && !istype(owner.current.loc, /obj/structure/closet/crate/coffin)) + if(total_damage <= 0 || total_toxloss <= 0 && total_brute <= 0 && !istype(owner.current.loc, /obj/structure/closet/crate/coffin)) // Not Daytime, Not in Torpor - if (!SSticker.mode.is_daylight() && HAS_TRAIT_FROM(owner.current, TRAIT_DEATHCOMA, "bloodsucker")) + if(!SSticker.mode.is_daylight() && HAS_TRAIT_FROM(owner.current, TRAIT_DEATHCOMA, "bloodsucker")) Torpor_End() // Fake Unconscious - if (poweron_masquerade == TRUE && total_damage >= owner.current.getMaxHealth() - HEALTH_THRESHOLD_FULLCRIT) + if(poweron_masquerade == TRUE && total_damage >= owner.current.getMaxHealth() - HEALTH_THRESHOLD_FULLCRIT) owner.current.Unconscious(20,1) //HEALTH_THRESHOLD_CRIT 0 @@ -241,8 +241,8 @@ owner.current.update_sight() owner.current.reload_fullscreen() // Disable ALL Powers - for (var/datum/action/bloodsucker/power in powers) - if (power.active && !power.can_use_in_torpor) + for(var/datum/action/bloodsucker/power in powers) + if(power.active && !power.can_use_in_torpor) power.DeactivatePower() @@ -281,7 +281,7 @@ // Free my Vassals! FreeAllVassals() // Elders get Dusted - if (vamplevel >= 4) // (vamptitle) + if(vamplevel >= 4) // (vamptitle) owner.current.visible_message("[owner.current]'s skin crackles and dries, their skin and bones withering to dust. A hollow cry whips from what is now a sandy pile of remains.", \ "Your soul escapes your withering body as the abyss welcomes you to your Final Death.", \ "You hear a dry, crackling sound.") @@ -306,7 +306,7 @@ if (!isliving(src)) return var/mob/living/L = src - if (!L.AmBloodsucker()) + if(!L.AmBloodsucker()) return // We're a vamp? Try to eat food... var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) @@ -315,7 +315,7 @@ /datum/antagonist/bloodsucker/proc/handle_eat_human_food(var/food_nutrition) // Called from snacks.dm and drinks.dm set waitfor = FALSE - if (!owner.current || !iscarbon(owner.current)) + if(!owner.current || !iscarbon(owner.current)) return var/mob/living/carbon/C = owner.current // Remove Nutrition, Give Bad Food diff --git a/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm b/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm index 33e374314f..844b523135 100644 --- a/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm +++ b/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm @@ -36,7 +36,7 @@ var/had_toxlover = FALSE // LISTS var/static/list/defaultTraits = list (TRAIT_STABLEHEART, TRAIT_NOBREATH, TRAIT_SLEEPIMMUNE, TRAIT_NOCRITDAMAGE, TRAIT_RESISTCOLD, TRAIT_RADIMMUNE, TRAIT_NIGHT_VISION, \ - TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_NONATURALHEAL, TRAIT_NOMARROW, TRAIT_NOPULSE) + TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_NONATURALHEAL, TRAIT_NOMARROW, TRAIT_NOPULSE, TRAIT_VIRUSIMMUNE) // NOTES: TRAIT_AGEUSIA <-- Doesn't like flavors. // REMOVED: TRAIT_NODEATH // TO ADD: @@ -334,7 +334,7 @@ datum/antagonist/bloodsucker/proc/SpendRank() // Assign True Reputation if(vamplevel == 4) SelectReputation(am_fledgling = FALSE, forced = TRUE) - to_chat(owner.current, "You are now a rank [vamplevel] Bloodsucker. Your strength, resistence, health, feed rate, regen rate, and maximum blood have all increased!") + to_chat(owner.current, "You are now a rank [vamplevel] Bloodsucker. Your strength, health, feed rate, regen rate, and maximum blood have all increased!") to_chat(owner.current, "Your existing powers have all ranked up as well!") update_hud(TRUE) owner.current.playsound_local(null, 'sound/effects/pope_entry.ogg', 25, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head. diff --git a/code/modules/antagonists/bloodsucker/datum_vassal.dm b/code/modules/antagonists/bloodsucker/datum_vassal.dm index 71ee0bcc1d..716b7ff223 100644 --- a/code/modules/antagonists/bloodsucker/datum_vassal.dm +++ b/code/modules/antagonists/bloodsucker/datum_vassal.dm @@ -56,11 +56,6 @@ var/obj/item/organ/eyes/vassal/E = new E.Insert(owner.current) -/obj/item/organ/eyes/vassal/ - lighting_alpha = 180 // LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE <--- This is too low a value at 128. We need to SEE what the darkness is so we can hide in it. - see_in_dark = 12 - flash_protect = -1 //These eyes are weaker to flashes, but let you see in the dark - /datum/antagonist/vassal/proc/remove_thrall_eyes() var/obj/item/organ/eyes/E = new E.Insert(owner.current) diff --git a/code/modules/antagonists/bloodsucker/items/bloodsucker_organs.dm b/code/modules/antagonists/bloodsucker/items/bloodsucker_organs.dm index 5a905857c5..e4e26fe034 100644 --- a/code/modules/antagonists/bloodsucker/items/bloodsucker_organs.dm +++ b/code/modules/antagonists/bloodsucker/items/bloodsucker_organs.dm @@ -51,6 +51,11 @@ return "no" // Bloodsuckers don't have a heartbeat at all when stopped (default is "an unstable") // EYES // +/obj/item/organ/eyes/vassal/ + lighting_alpha = 180 // LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE <--- This is too low a value at 128. We need to SEE what the darkness is so we can hide in it. + see_in_dark = 12 + flash_protect = -1 //These eyes are weaker to flashes, but let you see in the dark + /obj/item/organ/eyes/vassal/bloodsucker flash_protect = 2 //Eye healing isnt working properly sight_flags = SEE_MOBS // Taken from augmented_eyesight.dm diff --git a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm index e17531afcb..f66ce4a208 100644 --- a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm +++ b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm @@ -484,9 +484,10 @@ if(lit) for(var/mob/living/carbon/human/H in viewers(7, src)) var/datum/antagonist/vassal/T = H.mind.has_antag_datum(ANTAG_DATUM_VASSAL) - if(!isvamp(H) || !T) //We dont want vassals or vampires affected by this - H.hallucination = 20 - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle) + if(isvamp(H) || T) //We dont want vassals or vampires affected by this + return + H.hallucination = 20 + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // OTHER THINGS TO USE: HUMAN BLOOD. /obj/effect/decal/cleanable/blood diff --git a/code/modules/antagonists/bloodsucker/powers/bs_masquerade.dm b/code/modules/antagonists/bloodsucker/powers/bs_masquerade.dm index 6ee17b3014..0435ddccd5 100644 --- a/code/modules/antagonists/bloodsucker/powers/bs_masquerade.dm +++ b/code/modules/antagonists/bloodsucker/powers/bs_masquerade.dm @@ -51,14 +51,17 @@ REMOVE_TRAIT(user, TRAIT_COLDBLOODED, "bloodsucker") REMOVE_TRAIT(user, TRAIT_NOHARDCRIT, "bloodsucker") REMOVE_TRAIT(user, TRAIT_NOSOFTCRIT, "bloodsucker") + REMOVE_TRAIT(user, TRAIT_VIRUSIMMUNE, "bloodsucker") var/obj/item/organ/heart/vampheart/H = user.getorganslot(ORGAN_SLOT_HEART) - + var/obj/item/organ/eyes/vassal/bloodsucker/E = user.getorganslot(ORGAN_SLOT_EYES) + E.flash_protect = 0 + // WE ARE ALIVE! // bloodsuckerdatum.poweron_masquerade = TRUE while(bloodsuckerdatum && ContinueActive(user)) // HEART - if (istype(H)) + if(istype(H)) H.FakeStart() // PASSIVE (done from LIFE) @@ -67,7 +70,7 @@ // Don't Heal // Pay Blood Toll (if awake) - if (user.stat == CONSCIOUS) + if(user.stat == CONSCIOUS) bloodsuckerdatum.AddBloodVolume(-0.2) sleep(20) // Check every few ticks that we haven't disabled this power @@ -89,9 +92,13 @@ ADD_TRAIT(user, TRAIT_COLDBLOODED, "bloodsucker") ADD_TRAIT(user, TRAIT_NOHARDCRIT, "bloodsucker") ADD_TRAIT(user, TRAIT_NOSOFTCRIT, "bloodsucker") + ADD_TRAIT(user, TRAIT_VIRUSIMMUNE, "bloodsucker") // HEART var/obj/item/organ/heart/H = user.getorganslot(ORGAN_SLOT_HEART) + var/obj/item/organ/eyes/vassal/bloodsucker/E = user.getorganslot(ORGAN_SLOT_EYES) H.Stop() + E.flash_protect = 2 + to_chat(user, "Your heart beats one final time, while your skin dries out and your icy pallor returns.") diff --git a/code/modules/antagonists/bloodsucker/powers/bs_mesmerize.dm b/code/modules/antagonists/bloodsucker/powers/bs_mesmerize.dm index 94bc0e11d0..7962c7d403 100644 --- a/code/modules/antagonists/bloodsucker/powers/bs_mesmerize.dm +++ b/code/modules/antagonists/bloodsucker/powers/bs_mesmerize.dm @@ -89,17 +89,14 @@ if(istype(target)) target.Stun(40) //Utterly useless without this, its okay since there are so many checks to go through - target.silent = 45 //Shhhh little lamb target.apply_status_effect(STATUS_EFFECT_MESMERIZE, 45) //So you cant rotate with combat mode, plus fancy status alert if(do_mob(user, target, 40, 0, TRUE, extra_checks=CALLBACK(src, .proc/ContinueActive, user, target))) PowerActivatedSuccessfully() // PAY COST! BEGIN COOLDOWN! var/power_time = 90 + level_current * 12 - target.silent = power_time + 20 - target.apply_status_effect(STATUS_EFFECT_MESMERIZE, 100 + level_current * 15) + target.apply_status_effect(STATUS_EFFECT_MESMERIZE, power_time + 80) to_chat(user, "[target] is fixed in place by your hypnotic gaze.") target.Stun(power_time) - //target.silent += power_time / 10 // Silent isn't based on ticks. target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // target.changeNext_move(power_time) // check click.dm target.notransform = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze. spawn(power_time) From 8a193304ee9d4eb29cbaa1e2804cab0d1e4dd8d3 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 6 Jan 2020 20:22:39 +0100 Subject: [PATCH 12/46] Buffs torpor without coffin --- code/modules/antagonists/bloodsucker/bloodsucker_life.dm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm index 95c85e7344..7defe8b0a3 100644 --- a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm +++ b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm @@ -97,7 +97,7 @@ var/fireheal = 0 // BURN: Heal in Coffin while Fakedeath, or when damage above maxhealth (you can never fully heal fire) var/amInCoffinWhileTorpor = istype(C.loc, /obj/structure/closet/crate/coffin) && (mult == 0 || HAS_TRAIT(C, TRAIT_DEATHCOMA)) // Check for mult 0 OR death coma. (mult 0 means we're testing from coffin) if(amInCoffinWhileTorpor) - mult *= 5 // Increase multiplier if we're sleeping in a coffin. + mult *= 3 // Increase multiplier if we're sleeping in a coffin. fireheal = min(C.getFireLoss_nonProsthetic(), regenRate) // NOTE: Burn damage ONLY heals in torpor. costMult = 0.25 C.ExtinguishMob() @@ -118,6 +118,8 @@ if(bruteheal + fireheal + toxinheal > 0) // Just a check? Don't heal/spend, and return. if(mult == 0) return TRUE + if(stat >= UNCONSCIOUS) //Faster regeneration while unconcious, so you dont have to wait all day + mult *= 2 // We have damage. Let's heal (one time) C.adjustBruteLoss(-bruteheal * mult, forced = TRUE)// Heal BRUTE / BURN in random portions throughout the body. C.adjustFireLoss(-fireheal * mult, forced = TRUE) From 4fc6aa10be8c5a7c8ad550cc53eeb2ff7ef61f12 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 6 Jan 2020 20:29:10 +0100 Subject: [PATCH 13/46] Adds a cooldown for revival --- code/modules/antagonists/bloodsucker/bloodsucker_life.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm index 7defe8b0a3..7487d3c0b9 100644 --- a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm +++ b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm @@ -217,6 +217,7 @@ if(owner.current.stat >= DEAD) Torpor_Begin() to_chat(owner, "Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor.") + sleep(30) //To avoid spam if (poweron_masquerade == TRUE) to_chat(owner, "Your wounds will not heal until you disable the Masquerade power.") // End Torpor: @@ -228,7 +229,6 @@ // Fake Unconscious if(poweron_masquerade == TRUE && total_damage >= owner.current.getMaxHealth() - HEALTH_THRESHOLD_FULLCRIT) owner.current.Unconscious(20,1) - //HEALTH_THRESHOLD_CRIT 0 //HEALTH_THRESHOLD_FULLCRIT -30 //HEALTH_THRESHOLD_DEAD -100 From 41f96c0dc9196e4c5af91a25397dd5b220ac4612 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 6 Jan 2020 20:31:55 +0100 Subject: [PATCH 14/46] Whoopsie --- code/modules/antagonists/bloodsucker/bloodsucker_life.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm index 7487d3c0b9..62f529d4cd 100644 --- a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm +++ b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm @@ -118,7 +118,7 @@ if(bruteheal + fireheal + toxinheal > 0) // Just a check? Don't heal/spend, and return. if(mult == 0) return TRUE - if(stat >= UNCONSCIOUS) //Faster regeneration while unconcious, so you dont have to wait all day + if(stat) //Faster regeneration while unconcious, so you dont have to wait all day mult *= 2 // We have damage. Let's heal (one time) C.adjustBruteLoss(-bruteheal * mult, forced = TRUE)// Heal BRUTE / BURN in random portions throughout the body. From d283532d970a6d9670f06f1730f34419b49d8568 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 6 Jan 2020 20:49:14 +0100 Subject: [PATCH 15/46] Removes tresspass handcuff removal --- .../modules/antagonists/bloodsucker/powers/bs_trespass.dm | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/code/modules/antagonists/bloodsucker/powers/bs_trespass.dm b/code/modules/antagonists/bloodsucker/powers/bs_trespass.dm index c650a6af15..159c7b20b7 100644 --- a/code/modules/antagonists/bloodsucker/powers/bs_trespass.dm +++ b/code/modules/antagonists/bloodsucker/powers/bs_trespass.dm @@ -89,13 +89,7 @@ user.invisibility = INVISIBILITY_MAXIMUM // LOSE CUFFS - if(user.handcuffed) - var/obj/O = user.handcuffed - user.dropItemToGround(O) - if(user.legcuffed) - var/obj/O = user.legcuffed - user.dropItemToGround(O) - + // Wait... sleep(mist_delay / 2) From 70a1a0a9c1530e4c603403aec1ddea578f9cb564 Mon Sep 17 00:00:00 2001 From: Putnam Date: Tue, 7 Jan 2020 00:39:14 -0800 Subject: [PATCH 16/46] better way to do it tbh --- .../configuration/configuration.dm | 2 +- .../configuration/entries/game_options.dm | 3 +++ code/controllers/subsystem/ticker.dm | 11 ++++++++-- code/controllers/subsystem/vote.dm | 21 +++++++++++++------ config/game_options.txt | 5 ++++- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index 25f05af55b..730a3f17f4 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -341,7 +341,7 @@ if(probabilities[M.config_tag]<=0) qdel(M) continue - if(M.config_tag in SSvote.stored_gamemode_votes && SSvote.stored_gamemode_votes[M.config_tag] < Get(/datum/config_entry/number/dropped_modes)) + if(M.config_tag in SSvote.stored_modetier_results && SSvote.stored_modetier_results[M.config_tag] < Get(/datum/config_entry/number/dropped_modes)) qdel(M) continue if(min_pop[M.config_tag]) diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index d045a92c83..d67027b93d 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -393,5 +393,8 @@ /datum/config_entry/flag/allow_clockwork_marauder_on_station config_entry_value = TRUE +/datum/config_entry/flag/modetier_voting + config_entry_value = TRUE + /datum/config_entry/number/dropped_modes config_entry_value = 3 diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 76ebfb6765..cf8affdc7a 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -221,6 +221,12 @@ SUBSYSTEM_DEF(ticker) var/init_start = world.timeofday //Create and announce mode var/list/datum/game_mode/runnable_modes + if(SSvote.mode && (SSvote.mode == "roundtype" || SSvote.mode == "dynamic" || SSvote.mode == "modetiers")) + SSvote.result() + SSpersistence.SaveSavedVotes() + for(var/client/C in SSvote.voting) + C << browse(null, "window=vote;can_close=0") + SSvote.reset() if(GLOB.master_mode == "random" || GLOB.master_mode == "secret") runnable_modes = config.get_runnable_modes() @@ -484,9 +490,10 @@ SUBSYSTEM_DEF(ticker) SSticker.modevoted = TRUE var/dynamic = CONFIG_GET(flag/dynamic_voting) if(dynamic) - SSvote.initiate_vote("dynamic","server",hideresults=TRUE,votesystem=SCORE_VOTING,forced=TRUE,vote_time = 2 MINUTES) + SSvote.initiate_vote("dynamic","server",hideresults=TRUE,votesystem=SCORE_VOTING,forced=TRUE,vote_time = 20 MINUTES) else - SSvote.initiate_vote("roundtype","server",hideresults=TRUE,votesystem=RANKED_CHOICE_VOTING,forced=TRUE, vote_time = 2 MINUTES) + SSvote.initiate_vote("roundtype","server",hideresults=TRUE,votesystem=PLURALITY_VOTING,forced=TRUE, \ + vote_time = (CONFIG_GET(flag/modetier_voting) ? 1 MINUTES : 20 MINUTES)) /datum/controller/subsystem/ticker/Recover() current_state = SSticker.current_state diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index cb1a13541e..f82954276e 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -27,6 +27,8 @@ SUBSYSTEM_DEF(vote) var/list/stored_gamemode_votes = list() //Basically the last voted gamemode is stored here for end-of-round use. + var/list/stored_modetier_results = list() // The aggregated tier list of the modes available in secret. + /datum/controller/subsystem/vote/fire() //called by master_controller if(mode) if(end_time < world.time) @@ -34,7 +36,8 @@ SUBSYSTEM_DEF(vote) SSpersistence.SaveSavedVotes() for(var/client/C in voting) C << browse(null, "window=vote;can_close=0") - reset() + if(end_time < world.time) // result() can change this + reset() else if(next_pop < world.time) var/datum/browser/client_popup for(var/client/C in voting) @@ -209,7 +212,6 @@ SUBSYSTEM_DEF(vote) calculate_condorcet_votes(vote_title_text) if(vote_system == SCORE_VOTING) calculate_majority_judgement_vote(vote_title_text) - calculate_scores(vote_title_text) var/list/winners = get_result() var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic" if(winners.len > 0) @@ -251,6 +253,8 @@ SUBSYSTEM_DEF(vote) var/admintext = "Obfuscated results" if(vote_system == RANKED_CHOICE_VOTING) admintext += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!" + else if(vote_system == SCORE_VOTING) + admintext += "\nIt should be noted that this is not a raw tally of votes but the number of runoffs done by majority judgement!" for(var/i=1,i<=choices.len,i++) var/votes = choices[choices[i]] admintext += "\n[choices[i]]: [votes]" @@ -265,14 +269,16 @@ SUBSYSTEM_DEF(vote) if("roundtype") //CIT CHANGE - adds the roundstart extended/secret vote if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") - if(choices["secret"] > choices["extended"]) - . = "secret" - else - . = "extended" GLOB.master_mode = . SSticker.save_mode(.) message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]") log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].") + if(CONFIG_GET(flag/modetier_voting)) + reset() + started_time = 0 + initiate_vote("mode tiers","server",hideresults=FALSE,votesystem=RANKED_CHOICE_VOTING,forced=TRUE, vote_time = 30 MINUTES) + to_chat(world,"The vote will end right as the round starts.") + return . if("restart") if(. == "Restart Round") restart = 1 @@ -283,6 +289,8 @@ SUBSYSTEM_DEF(vote) restart = 1 else GLOB.master_mode = . + if("mode tiers") + stored_modetier_results = choices.Copy() if("dynamic") if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") @@ -403,6 +411,7 @@ SUBSYSTEM_DEF(vote) choices |= M if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote choices.Add("secret", "extended") + if("mode tiers") var/list/modes_to_add = config.votable_modes var/list/probabilities = CONFIG_GET(keyed_list/probability) for(var/tag in modes_to_add) diff --git a/config/game_options.txt b/config/game_options.txt index 99d6b143f8..ef9296cd3b 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -580,5 +580,8 @@ BOX_RANDOM_ENGINE Engine SM BOX_RANDOM_ENGINE Engine Tesla BOX_RANDOM_ENGINE Engine Singulo -## Number of modes dropped by the secret vote during mode selection, after vote. +## Whether or not there's a mode tier list vote after the secret/extended vote. +MODETIER_VOTING + +## Number of modes dropped by the modetier vote during mode selection, after vote. DROPPED_MODES 3 From 5fae814e42416c55c35e82689261c075ada7b712 Mon Sep 17 00:00:00 2001 From: Seris02 Date: Tue, 7 Jan 2020 17:52:46 +0800 Subject: [PATCH 17/46] did it thanks --- .../objects/structures/crates_lockers/closets/cardboardbox.dm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm index aad68b2166..645d1e5d7a 100644 --- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm +++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm @@ -22,7 +22,9 @@ if(opened || move_delay || user.stat || user.IsStun() || user.IsKnockdown() || user.IsUnconscious() || !isturf(loc) || !has_gravity(loc)) return move_delay = TRUE - if(step(src, direction)) + var/oldloc = loc + step(src, direction) + if(oldloc != loc) addtimer(CALLBACK(src, .proc/ResetMoveDelay), (use_mob_movespeed ? user.movement_delay() : CONFIG_GET(number/movedelay/walk_delay)) * move_speed_multiplier) else ResetMoveDelay() From ab2292dfd5903378d26afe29c144eb66ce7f1f94 Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Tue, 7 Jan 2020 04:48:39 -0800 Subject: [PATCH 18/46] Fail2Topic --- code/__HELPERS/_logging.dm | 5 +- .../configuration/entries/fail2topic.dm | 15 +++ code/controllers/master.dm | 4 +- code/controllers/subsystem.dm | 4 +- code/controllers/subsystem/fail2topic.dm | 107 ++++++++++++++++++ code/game/world.dm | 10 ++ code/modules/tgs/v3210/commands.dm | 32 +++--- config/config.txt | 12 ++ tgstation.dme | 4 +- 9 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 code/controllers/configuration/entries/fail2topic.dm create mode 100644 code/controllers/subsystem/fail2topic.dm diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 3ee77d3edc..241e9b906e 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -78,7 +78,6 @@ if (CONFIG_GET(flag/log_manifest)) WRITE_LOG(GLOB.world_manifest_log, "[ckey] \\ [body.real_name] \\ [mind.assigned_role] \\ [mind.special_role ? mind.special_role : "NONE"] \\ [latejoin ? "LATEJOIN":"ROUNDSTART"]") - /proc/log_say(text) if (CONFIG_GET(flag/log_say)) WRITE_LOG(GLOB.world_game_log, "SAY: [text]") @@ -121,7 +120,6 @@ if (CONFIG_GET(flag/log_vote)) WRITE_LOG(GLOB.world_game_log, "VOTE: [text]") - /proc/log_topic(text) WRITE_LOG(GLOB.world_game_log, "TOPIC: [text]") @@ -141,6 +139,9 @@ if (CONFIG_GET(flag/log_job_debug)) WRITE_LOG(GLOB.world_job_debug_log, "JOB: [text]") +/proc/log_ss(subsystem, text) + WRITE_LOG(GLOB.subsystem_log, "[subsystem]: [text]") + /* Log to both DD and the logfile. */ /proc/log_world(text) #ifdef USE_CUSTOM_ERROR_HANDLER diff --git a/code/controllers/configuration/entries/fail2topic.dm b/code/controllers/configuration/entries/fail2topic.dm new file mode 100644 index 0000000000..665a55dd0f --- /dev/null +++ b/code/controllers/configuration/entries/fail2topic.dm @@ -0,0 +1,15 @@ +/datum/config_entry/number/fail2topic_rate_limit + config_entry_value = 10 //deciseconds + +/datum/config_entry/number/fail2topic_max_fails + config_entry_value = 5 + +/datum/config_entry/string/fail2topic_rule_name + config_entry_value = "_dd_fail2topic" + protection = CONFIG_ENTRY_LOCKED //affects physical server configuration, no touchies!! + +/datum/config_entry/flag/fail2topic_enabled + config_entry_value = TRUE + +/datum/config_entry/number/topic_max_size + config_entry_value = 8192 diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 125da84a30..7244212630 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -54,7 +54,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/static/restart_clear = 0 var/static/restart_timeout = 0 var/static/restart_count = 0 - + var/static/random_seed //current tick limit, assigned before running a subsystem. @@ -69,7 +69,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(!random_seed) random_seed = (TEST_RUN_PARAMETER in world.params) ? 29051994 : rand(1, 1e9) rand_seed(random_seed) - + var/list/_subsystems = list() subsystems = _subsystems if (Master != src) diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 4fe0812c56..3be4f36270 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -155,6 +155,8 @@ if(SS_SLEEPING) state = SS_PAUSING +/datum/controller/subsystem/proc/subsystem_log(msg) + return log_subsystem(name, msg) //used to initialize the subsystem AFTER the map has loaded /datum/controller/subsystem/Initialize(start_timeofday) @@ -162,7 +164,7 @@ var/time = (REALTIMEOFDAY - start_timeofday) / 10 var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!" to_chat(world, "[msg]") - log_world(msg) + log_subsystem("INIT", msg) return time //hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc. diff --git a/code/controllers/subsystem/fail2topic.dm b/code/controllers/subsystem/fail2topic.dm new file mode 100644 index 0000000000..01b6d225b9 --- /dev/null +++ b/code/controllers/subsystem/fail2topic.dm @@ -0,0 +1,107 @@ +SUBSYSTEM_DEF(fail2topic) + name = "Fail2Topic" + init_order = SS_INIT_MISC_FIRST + flags = SS_FIRE_IN_LOBBY | SS_BACKGROUND + + var/list/rate_limiting = list() + var/list/fail_counts = list() + var/list/active_bans = list() + + var/rate_limit + var/max_fails + var/rule_name + var/enabled = FALSE + +/datum/controller/subsystem/fail2topic/Initialize(timeofday) + rate_limit = CONFIG_GET(number/fail2topic_rate_limit) + max_fails = CONFIG_GET(number/fail2topic_max_fails) + rule_name = CONFIG_GET(string/fail2topic_rule_name) + enabled = CONFIG_GET(flag/fail2topic_enabled) + + DropFirewallRule() // Clear the old bans if any still remain + + if (world.system_type == UNIX && enabled) + enabled = FALSE + subsystem_log("DISABLED - UNIX systems are not supported.") + if(!enabled) + flags |= SS_NO_FIRE + can_fire = FALSE + + return ..() + +/datum/controller/subsystem/fail2topic/fire() + while (rate_limiting.len) + var/ip = rate_limiting[1] + var/last_attempt = rate_limiting[ip] + + if (world.time - last_attempt > rate_limit) + rate_limiting -= ip + fail_counts -= ip + + if (MC_TICK_CHECK) + return + +/datum/controller/subsystem/fail2topic/Shutdown() + DropFirewallRule() + +/datum/controller/subsystem/fail2topic/proc/IsRateLimited(ip) + var/last_attempt = rate_limiting[ip] + + if (config?.api_rate_limit_whitelist[ip]) + return FALSE + + if (active_bans[ip]) + return TRUE + + rate_limiting[ip] = world.time + + if (isnull(last_attempt)) + return FALSE + + if (world.time - last_attempt > rate_limit) + fail_counts -= ip + return FALSE + else + var/failures = fail_counts[ip] + + if (isnull(failures)) + fail_counts[ip] = 1 + return FALSE + else if (failures > max_fails) + BanFromFirewall(ip) + return TRUE + else + fail_counts[ip] = failures + 1 + return TRUE + +/datum/controller/subsystem/fail2topic/proc/BanFromFirewall(ip) + if (!enabled) + return + + active_bans[ip] = world.time + fail_counts -= ip + rate_limiting -= ip + + . = shell("netsh advfirewall firewall add rule name=\"[rule_name]\" dir=in interface=any action=block remoteip=[ip]") + + if (.) + subsystem_log("Failed to ban [ip]. Exit code: [.].") + else if (isnull(.)) + subsystem_log("Failed to invoke shell to ban [ip].") + else + subsystem_log("Banned [ip].") + +/datum/controller/subsystem/fail2topic/proc/DropFirewallRule() + if (!enabled) + return + + active_bans = list() + + . = shell("netsh advfirewall firewall delete rule name=\"[rule_name]\"") + + if (.) + subsystem_log("Failed to drop firewall rule. Exit code: [.].") + else if (isnull(.)) + subsystem_log("Failed to invoke shell for firewall rule drop.") + else + subsystem_log("Firewall rule dropped.") diff --git a/code/game/world.dm b/code/game/world.dm index 4043f15f6f..99701c34dd 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -112,6 +112,7 @@ GLOBAL_VAR(restart_counter) GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" + GLOB.subsystem_log = "[GLOB.log_directory]/subsystem.log" #ifdef UNIT_TESTS GLOB.test_log = file("[GLOB.log_directory]/tests.log") @@ -126,6 +127,7 @@ GLOBAL_VAR(restart_counter) start_log(GLOB.world_qdel_log) start_log(GLOB.world_runtime_log) start_log(GLOB.world_job_debug_log) + start_log(GLOB.subsystem_log) GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently if(fexists(GLOB.config_error_log)) @@ -143,6 +145,14 @@ GLOBAL_VAR(restart_counter) /world/Topic(T, addr, master, key) TGS_TOPIC //redirect to server tools if necessary + if(!SSfail2topic) + return "Server not initialized." + else if(!SSfail2topic.IsRateLimited(addr)) + return "Rate limited." + + if(length(T) > CONFIG_GET(number/topic_max_size)) + return "Payload too large!" + var/static/list/topic_handlers = TopicHandlers() var/list/input = params2list(T) diff --git a/code/modules/tgs/v3210/commands.dm b/code/modules/tgs/v3210/commands.dm index 71d7e32366..e674fd4e78 100644 --- a/code/modules/tgs/v3210/commands.dm +++ b/code/modules/tgs/v3210/commands.dm @@ -19,7 +19,7 @@ TGS_ERROR_LOG("Custom command [command_name] can't be used as it is empty or contains illegal characters!") warned_command_names[command_name] = TRUE continue - + if(command_name_types[command_name]) if(warnings_only) TGS_ERROR_LOG("Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!") @@ -55,24 +55,24 @@ The MIT License Copyright (c) 2017 Jordan Brown -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ diff --git a/config/config.txt b/config/config.txt index e71c2587b7..fafb3e5791 100644 --- a/config/config.txt +++ b/config/config.txt @@ -473,3 +473,15 @@ DISABLE_HIGH_POP_MC_MODE_AMOUNT 60 ## For reference, Goonstation uses a resolution of 21x15 for it's widescreen mode. ## Do note that changing this value will affect the title screen. The title screen will have to be updated manually if this is changed. DEFAULT_VIEW 21x15 + +### FAIL2TOPIC: +### Automated IP bans for world/Topic() spammers +## Enabled +FAIL2TOPIC_ENABLED +## Minimum wait time in deciseconds between valid requests +FAIL2TOPIC_RATE_LIMIT 10 +## Number of requests after breaching rate limit that triggers a ban +FAIL2TOPIC_MAX_FAILS 5 +## Firewall rule name used on physical server +FAIL2TOPIC_RULE_NAME _dd_fail2topic + diff --git a/tgstation.dme b/tgstation.dme index 1a79c32dcb..38645a3030 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -229,6 +229,7 @@ #include "code\controllers\configuration\entries\dbconfig.dm" #include "code\controllers\configuration\entries\donator.dm" #include "code\controllers\configuration\entries\dynamic.dm" +#include "code\controllers\configuration\entries\fail2topic.dm" #include "code\controllers\configuration\entries\game_options.dm" #include "code\controllers\configuration\entries\general.dm" #include "code\controllers\subsystem\acid.dm" @@ -245,6 +246,7 @@ #include "code\controllers\subsystem\dcs.dm" #include "code\controllers\subsystem\disease.dm" #include "code\controllers\subsystem\events.dm" +#include "code\controllers\subsystem\fail2topic.dm" #include "code\controllers\subsystem\fire_burning.dm" #include "code\controllers\subsystem\garbage.dm" #include "code\controllers\subsystem\icon_smooth.dm" @@ -470,8 +472,8 @@ #include "code\datums\elements\_element.dm" #include "code\datums\elements\cleaning.dm" #include "code\datums\elements\earhealing.dm" -#include "code\datums\elements\wuv.dm" #include "code\datums\elements\ghost_role_eligibility.dm" +#include "code\datums\elements\wuv.dm" #include "code\datums\helper_datums\events.dm" #include "code\datums\helper_datums\getrev.dm" #include "code\datums\helper_datums\icon_snapshot.dm" From fa7dcad49a884ae23b3392bf9ade24e971e6c3fd Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Tue, 7 Jan 2020 04:54:11 -0800 Subject: [PATCH 19/46] compile --- code/__DEFINES/subsystems.dm | 1 + code/__HELPERS/_logging.dm | 2 +- code/_globalvars/logging.dm | 2 ++ .../configuration/entries/fail2topic.dm | 4 ++++ code/controllers/subsystem/fail2topic.dm | 14 ++++++++++---- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index c194e578c9..5c54843df2 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -47,6 +47,7 @@ // Subsystems shutdown in the reverse of the order they initialize in // The numbers just define the ordering, they are meaningless otherwise. +#define INIT_ORDER_FAIL2TOPIC 22 #define INIT_ORDER_TITLE 20 #define INIT_ORDER_GARBAGE 19 #define INIT_ORDER_DBCORE 18 diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 241e9b906e..4dd590d9a1 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -139,7 +139,7 @@ if (CONFIG_GET(flag/log_job_debug)) WRITE_LOG(GLOB.world_job_debug_log, "JOB: [text]") -/proc/log_ss(subsystem, text) +/proc/log_subsystem(subsystem, text) WRITE_LOG(GLOB.subsystem_log, "[subsystem]: [text]") /* Log to both DD and the logfile. */ diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 5ca3513e66..01d5051dba 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -30,6 +30,8 @@ GLOBAL_VAR(world_virus_log) GLOBAL_PROTECT(world_virus_log) GLOBAL_VAR(world_map_error_log) GLOBAL_PROTECT(world_map_error_log) +GLOBAL_VAR(subsystem_log) +GLOBAL_PROTECT(subsystem_log) GLOBAL_LIST_EMPTY(bombers) GLOBAL_PROTECT(bombers) diff --git a/code/controllers/configuration/entries/fail2topic.dm b/code/controllers/configuration/entries/fail2topic.dm index 665a55dd0f..7e5bfd7ee8 100644 --- a/code/controllers/configuration/entries/fail2topic.dm +++ b/code/controllers/configuration/entries/fail2topic.dm @@ -13,3 +13,7 @@ /datum/config_entry/number/topic_max_size config_entry_value = 8192 + +/datum/config_entry/keyed_list/topic_rate_limit_whitelist + key_type = KEY_MODE_TEXT + value_type = VALUE_MODE_FLAG diff --git a/code/controllers/subsystem/fail2topic.dm b/code/controllers/subsystem/fail2topic.dm index 01b6d225b9..0c2936e936 100644 --- a/code/controllers/subsystem/fail2topic.dm +++ b/code/controllers/subsystem/fail2topic.dm @@ -1,7 +1,8 @@ SUBSYSTEM_DEF(fail2topic) name = "Fail2Topic" - init_order = SS_INIT_MISC_FIRST - flags = SS_FIRE_IN_LOBBY | SS_BACKGROUND + init_order = INIT_ORDER_FAIL2TOPIC + flags = SS_BACKGROUND + runlevels = ALL var/list/rate_limiting = list() var/list/fail_counts = list() @@ -47,8 +48,13 @@ SUBSYSTEM_DEF(fail2topic) /datum/controller/subsystem/fail2topic/proc/IsRateLimited(ip) var/last_attempt = rate_limiting[ip] - if (config?.api_rate_limit_whitelist[ip]) - return FALSE + var/static/datum/config_entry/keyed_list/topic_rate_limit_whitelist/cached_whitelist_entry + if(!istype(cached_whitelist_entry)) + cached_whitelist_entry = CONFIG_GET(keyed_list/topic_rate_limit_whitelist) + + if(istype(cached_whitelist_entry)) + if(cached_whitelist_entry.config_entry_value[ip]) + return FALSE if (active_bans[ip]) return TRUE From 0114f9e2236091b900f3d220d56108a05d279346 Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Tue, 7 Jan 2020 04:54:32 -0800 Subject: [PATCH 20/46] whitelist --- code/controllers/configuration/entries/fail2topic.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/controllers/configuration/entries/fail2topic.dm b/code/controllers/configuration/entries/fail2topic.dm index 7e5bfd7ee8..7ed09b378a 100644 --- a/code/controllers/configuration/entries/fail2topic.dm +++ b/code/controllers/configuration/entries/fail2topic.dm @@ -15,5 +15,5 @@ config_entry_value = 8192 /datum/config_entry/keyed_list/topic_rate_limit_whitelist - key_type = KEY_MODE_TEXT - value_type = VALUE_MODE_FLAG + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG From 686601334eddc706525b8ce00c9aeafbb9bfd58b Mon Sep 17 00:00:00 2001 From: deathride58 Date: Tue, 7 Jan 2020 08:52:13 -0500 Subject: [PATCH 21/46] suggested changes --- code/controllers/subsystem/fail2topic.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/controllers/subsystem/fail2topic.dm b/code/controllers/subsystem/fail2topic.dm index 0c2936e936..a589ae2462 100644 --- a/code/controllers/subsystem/fail2topic.dm +++ b/code/controllers/subsystem/fail2topic.dm @@ -72,7 +72,7 @@ SUBSYSTEM_DEF(fail2topic) if (isnull(failures)) fail_counts[ip] = 1 - return FALSE + return TRUE else if (failures > max_fails) BanFromFirewall(ip) return TRUE From cde9946e2569c9c95edaefa5798bb7b5dc0c0ac7 Mon Sep 17 00:00:00 2001 From: deathride58 Date: Tue, 7 Jan 2020 09:00:30 -0500 Subject: [PATCH 22/46] is this why wrench refuses to work --- code/game/world.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/world.dm b/code/game/world.dm index 99701c34dd..86de9c53d0 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -147,7 +147,7 @@ GLOBAL_VAR(restart_counter) if(!SSfail2topic) return "Server not initialized." - else if(!SSfail2topic.IsRateLimited(addr)) + else if(SSfail2topic.IsRateLimited(addr)) return "Rate limited." if(length(T) > CONFIG_GET(number/topic_max_size)) From 6f2cf0b63669a7b06fb58f1b9de96212c968e8fb Mon Sep 17 00:00:00 2001 From: deathride58 Date: Tue, 7 Jan 2020 10:04:09 -0500 Subject: [PATCH 23/46] [s] adds a cooldown to area creation attempts --- code/__HELPERS/areas.dm | 7 +++++++ code/modules/mob/mob_defines.dm | 1 + 2 files changed, 8 insertions(+) diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm index 1f5b82f7bf..4b52187e13 100644 --- a/code/__HELPERS/areas.dm +++ b/code/__HELPERS/areas.dm @@ -43,6 +43,13 @@ var/static/blacklisted_areas = typecacheof(list( /area/space, )) + + if(creator) + if(creator.create_area_cooldown >= world.time) + to_chat(creator, "You're trying to create a new area a little too fast.") + return + creator.create_area_cooldown = world.time + 10 + var/list/turfs = detect_room(get_turf(creator), area_or_turf_fail_types) if(!turfs) to_chat(creator, "The new area must be completely airtight and not a part of a shuttle.") diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index c7365210ab..695084a794 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -29,6 +29,7 @@ var/obj/machinery/machine = null var/next_move = null + var/create_area_cooldown var/notransform = null //Carbon var/eye_blind = 0 //Carbon var/eye_blurry = 0 //Carbon From 7141223f4949a57a749f79820a7a8b9b93135cb3 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 7 Jan 2020 18:39:13 +0100 Subject: [PATCH 24/46] All ready now, forgot how stat worked --- code/modules/antagonists/bloodsucker/bloodsucker_life.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm index 62f529d4cd..b224ec76e2 100644 --- a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm +++ b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm @@ -97,7 +97,7 @@ var/fireheal = 0 // BURN: Heal in Coffin while Fakedeath, or when damage above maxhealth (you can never fully heal fire) var/amInCoffinWhileTorpor = istype(C.loc, /obj/structure/closet/crate/coffin) && (mult == 0 || HAS_TRAIT(C, TRAIT_DEATHCOMA)) // Check for mult 0 OR death coma. (mult 0 means we're testing from coffin) if(amInCoffinWhileTorpor) - mult *= 3 // Increase multiplier if we're sleeping in a coffin. + mult *= 4 // Increase multiplier if we're sleeping in a coffin. fireheal = min(C.getFireLoss_nonProsthetic(), regenRate) // NOTE: Burn damage ONLY heals in torpor. costMult = 0.25 C.ExtinguishMob() @@ -118,7 +118,7 @@ if(bruteheal + fireheal + toxinheal > 0) // Just a check? Don't heal/spend, and return. if(mult == 0) return TRUE - if(stat) //Faster regeneration while unconcious, so you dont have to wait all day + if(owner.current.stat >= UNCONSCIOUS) //Faster regeneration while unconcious, so you dont have to wait all day mult *= 2 // We have damage. Let's heal (one time) C.adjustBruteLoss(-bruteheal * mult, forced = TRUE)// Heal BRUTE / BURN in random portions throughout the body. From 81f1349cea305154aa04baaef299537236652598 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 7 Jan 2020 18:41:17 +0100 Subject: [PATCH 25/46] Nerf Stamina and cloneloss --- code/modules/antagonists/bloodsucker/bloodsucker_life.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm index b224ec76e2..bbb8aeca30 100644 --- a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm +++ b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm @@ -84,8 +84,8 @@ //It is called from your coffin on close (by you only) if(poweron_masquerade == TRUE || owner.current.AmStaked()) return FALSE - owner.current.adjustStaminaLoss(-2 + (regenRate * -10) * mult, 0) // Humans lose stamina damage really quickly. Vamps should heal more. - owner.current.adjustCloneLoss(-1 * (regenRate * 4) * mult, 0) + owner.current.adjustStaminaLoss(-2 + (regenRate * 8) * mult, 0) // Humans lose stamina damage really quickly. Vamps should heal more. + owner.current.adjustCloneLoss(-0.1 * (regenRate * 2) * mult, 0) owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * (regenRate * 4) * mult) //adjustBrainLoss(-1 * (regenRate * 4) * mult, 0) // No Bleeding if(ishuman(owner.current)) //NOTE Current bleeding is horrible, not to count the amount of blood ballistics delete. From 97a9ad4714aff72fe12307190f366307d610ba85 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 7 Jan 2020 19:22:39 +0100 Subject: [PATCH 26/46] Nerfs cloak some more --- .../antagonists/bloodsucker/powers/bs_cloak.dm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/code/modules/antagonists/bloodsucker/powers/bs_cloak.dm b/code/modules/antagonists/bloodsucker/powers/bs_cloak.dm index cea942e26d..9126638fea 100644 --- a/code/modules/antagonists/bloodsucker/powers/bs_cloak.dm +++ b/code/modules/antagonists/bloodsucker/powers/bs_cloak.dm @@ -9,8 +9,9 @@ bloodsucker_can_buy = TRUE amToggle = TRUE warn_constant_cost = TRUE + var/was_running - var/light_min = 0.5 // If lum is above this, no good. + var/light_min = 0.2 // If lum is above this, no good. /datum/action/bloodsucker/cloak/CheckCanUse(display_error) . = ..() @@ -26,18 +27,16 @@ /datum/action/bloodsucker/cloak/ActivatePower() var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) var/mob/living/user = owner - var/was_running = (user.m_intent == MOVE_INTENT_RUN) + was_running = (user.m_intent == MOVE_INTENT_RUN) if(was_running) user.toggle_move_intent() ADD_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness") while(bloodsuckerdatum && ContinueActive(user) || user.m_intent == MOVE_INTENT_RUN) // Pay Blood Toll (if awake) - owner.alpha = max(0, owner.alpha - min(75, 20 + 15 * level_current)) + owner.alpha = max(20, owner.alpha - min(75, 10 + 5 * level_current)) bloodsuckerdatum.AddBloodVolume(-0.2) sleep(5) // Check every few ticks that we haven't disabled this power // Return to Running (if you were before) - if(was_running && user.m_intent != MOVE_INTENT_RUN) - user.toggle_move_intent() /datum/action/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target) if (!..()) @@ -55,3 +54,5 @@ ..() REMOVE_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness") user.alpha = 255 + if(was_running && user.m_intent != MOVE_INTENT_RUN) + user.toggle_move_intent() From cff6a094d74c87b6eb4cfb65a036a416707d2914 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 7 Jan 2020 21:21:12 +0100 Subject: [PATCH 27/46] Aaand buffs stakes --- .../antagonists/bloodsucker/items/bloodsucker_stake.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm b/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm index 9db4cae1ff..623fc691c0 100644 --- a/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm +++ b/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm @@ -78,7 +78,7 @@ var/mob/living/carbon/C = target // Needs to be Down/Slipped in some way to Stake. if(!C.can_be_staked() || target == user) - to_chat(user, "You cant stake [target] when they are moving moving about! They have to be laying down!") + to_chat(user, "You cant stake [target] when they are moving moving about! They have to be laying down or grabbed by the neck!") return // Oops! Can't. if(HAS_TRAIT(C, TRAIT_PIERCEIMMUNE)) @@ -113,7 +113,7 @@ // Can this target be staked? If someone stands up before this is complete, it fails. Best used on someone stationary. /mob/living/carbon/proc/can_be_staked() //return resting || IsKnockdown() || IsUnconscious() || (stat && (stat != SOFT_CRIT || pulledby)) || (has_trait(TRAIT_FAKEDEATH)) || resting || IsStun() || IsFrozen() || (pulledby && pulledby.grab_state >= GRAB_NECK) - return (src.resting || src.lying) + return (resting || lying || IsUnconscious() || pulledby && pulledby.grab_state >= GRAB_NECK) // ABOVE: Taken from update_mobility() in living.dm /obj/item/stake/hardened From 1e5d6719ee523126f4597a4d0000b966e018c15a Mon Sep 17 00:00:00 2001 From: keronshb Date: Tue, 7 Jan 2020 15:52:29 -0500 Subject: [PATCH 28/46] Fixes Dermal Button Sprites Should fix the missing sprite issue for the Dermal Button nanites --- icons/mob/actions/actions_items.dmi | Bin 9131 -> 11500 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/mob/actions/actions_items.dmi b/icons/mob/actions/actions_items.dmi index 39e2ea05841ff315803975587345cbc9d77ed745..43c9d20a936042a54c6a91c93db346dd15ee7c5f 100644 GIT binary patch literal 11500 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 literal 9131 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

MbcemB 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 From 633294c590e363007282466bca57c935b2976dbb Mon Sep 17 00:00:00 2001 From: deathride58 Date: Tue, 7 Jan 2020 20:03:44 -0500 Subject: [PATCH 29/46] adds a basic sanity check to server_hop --- code/datums/world_topic.dm | 30 +++++++++++++++--------------- code/game/world.dm | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 0c43d33a4b..74b5222950 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -25,16 +25,16 @@ var/key_valid var/require_comms_key = FALSE -/datum/world_topic/proc/TryRun(list/input) +/datum/world_topic/proc/TryRun(list/input, addr) key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) if(require_comms_key && !key_valid) return "Bad Key" input -= "key" - . = Run(input) + . = Run(input, addr) if(islist(.)) . = list2params(.) -/datum/world_topic/proc/Run(list/input) +/datum/world_topic/proc/Run(list/input, addr) CRASH("Run() not implemented for [type]!") // TOPICS @@ -43,7 +43,7 @@ keyword = "ping" log = FALSE -/datum/world_topic/ping/Run(list/input) +/datum/world_topic/ping/Run(list/input, addr) . = 0 for (var/client/C in GLOB.clients) ++. @@ -52,7 +52,7 @@ keyword = "playing" log = FALSE -/datum/world_topic/playing/Run(list/input) +/datum/world_topic/playing/Run(list/input, addr) return GLOB.player_list.len /datum/world_topic/pr_announce @@ -60,7 +60,7 @@ require_comms_key = TRUE var/static/list/PRcounts = list() //PR id -> number of times announced this round -/datum/world_topic/pr_announce/Run(list/input) +/datum/world_topic/pr_announce/Run(list/input, addr) var/list/payload = json_decode(input["payload"]) var/id = "[payload["pull_request"]["id"]]" if(!PRcounts[id]) @@ -78,14 +78,14 @@ keyword = "Ahelp" require_comms_key = TRUE -/datum/world_topic/ahelp_relay/Run(list/input) +/datum/world_topic/ahelp_relay/Run(list/input, addr) relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") /datum/world_topic/comms_console keyword = "Comms_Console" require_comms_key = TRUE -/datum/world_topic/comms_console/Run(list/input) +/datum/world_topic/comms_console/Run(list/input, addr) minor_announce(input["message"], "Incoming message from [input["message_sender"]]") for(var/obj/machinery/computer/communications/CM in GLOB.machines) CM.overrideCooldown() @@ -94,16 +94,16 @@ keyword = "News_Report" require_comms_key = TRUE -/datum/world_topic/news_report/Run(list/input) +/datum/world_topic/news_report/Run(list/input, addr) minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") /datum/world_topic/server_hop keyword = "server_hop" -/datum/world_topic/server_hop/Run(list/input) +/datum/world_topic/server_hop/Run(list/input, addr) var/expected_key = input[keyword] for(var/mob/dead/observer/O in GLOB.player_list) - if(O.key == expected_key) + if(O.key == expected_key && O?.client.address == addr) if(O.client) new /obj/screen/splash(O.client, TRUE) break @@ -112,14 +112,14 @@ keyword = "adminmsg" require_comms_key = TRUE -/datum/world_topic/adminmsg/Run(list/input) +/datum/world_topic/adminmsg/Run(list/input, addr) return IrcPm(input[keyword], input["msg"], input["sender"]) /datum/world_topic/namecheck keyword = "namecheck" require_comms_key = TRUE -/datum/world_topic/namecheck/Run(list/input) +/datum/world_topic/namecheck/Run(list/input, addr) //Oh this is a hack, someone refactor the functionality out of the chat command PLS var/datum/tgs_chat_command/namecheck/NC = new var/datum/tgs_chat_user/user = new @@ -131,13 +131,13 @@ keyword = "adminwho" require_comms_key = TRUE -/datum/world_topic/adminwho/Run(list/input) +/datum/world_topic/adminwho/Run(list/input, addr) return ircadminwho() /datum/world_topic/status keyword = "status" -/datum/world_topic/status/Run(list/input) +/datum/world_topic/status/Run(list/input, addr) . = list() .["version"] = GLOB.game_version .["mode"] = "hidden" //CIT CHANGE - hides the gamemode in topic() calls to prevent meta'ing the gamemode diff --git a/code/game/world.dm b/code/game/world.dm index 4043f15f6f..2700746220 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -159,7 +159,7 @@ GLOBAL_VAR(restart_counter) return handler = new handler() - return handler.TryRun(input) + return handler.TryRun(input, addr) /world/proc/AnnouncePR(announcement, list/payload) var/static/list/PRcounts = list() //PR id -> number of times announced this round From f0af57d7d66aa2c72c020b4b59afbf4f432d2c6c Mon Sep 17 00:00:00 2001 From: deathride58 Date: Tue, 7 Jan 2020 20:22:06 -0500 Subject: [PATCH 30/46] wrong question mark place aaa --- code/datums/world_topic.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 74b5222950..602ef7f5f5 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -103,7 +103,7 @@ /datum/world_topic/server_hop/Run(list/input, addr) var/expected_key = input[keyword] for(var/mob/dead/observer/O in GLOB.player_list) - if(O.key == expected_key && O?.client.address == addr) + if(O.key == expected_key && O.client?.address == addr) if(O.client) new /obj/screen/splash(O.client, TRUE) break From 89fcde9927147c6b6d23aaf16e5d1e3a5481f6ca Mon Sep 17 00:00:00 2001 From: deathride58 Date: Tue, 7 Jan 2020 20:24:08 -0500 Subject: [PATCH 31/46] im like 3 iq dont bulli --- code/datums/world_topic.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 602ef7f5f5..e01b525e02 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -103,8 +103,8 @@ /datum/world_topic/server_hop/Run(list/input, addr) var/expected_key = input[keyword] for(var/mob/dead/observer/O in GLOB.player_list) - if(O.key == expected_key && O.client?.address == addr) - if(O.client) + if(O.key == expected_key) + if(O.client?.address == addr) new /obj/screen/splash(O.client, TRUE) break From fdce8e2b6904b6e4c65353070b0fc09ddf8c384b Mon Sep 17 00:00:00 2001 From: Putnam Date: Wed, 8 Jan 2020 03:55:23 -0800 Subject: [PATCH 32/46] dang --- code/controllers/subsystem/ticker.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index cf8affdc7a..0948e428ff 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -221,7 +221,7 @@ SUBSYSTEM_DEF(ticker) var/init_start = world.timeofday //Create and announce mode var/list/datum/game_mode/runnable_modes - if(SSvote.mode && (SSvote.mode == "roundtype" || SSvote.mode == "dynamic" || SSvote.mode == "modetiers")) + if(SSvote.mode && (SSvote.mode == "roundtype" || SSvote.mode == "dynamic" || SSvote.mode == "mode tiers")) SSvote.result() SSpersistence.SaveSavedVotes() for(var/client/C in SSvote.voting) From 039248dac71c0a10e38f391c370baece3d053d9f Mon Sep 17 00:00:00 2001 From: Artur Date: Wed, 8 Jan 2020 20:29:30 +0100 Subject: [PATCH 33/46] Kills legion core healing --- code/modules/mob/living/carbon/human/human.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 0007233975..90daa2633a 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -816,7 +816,7 @@ for(var/datum/mutation/human/HM in dna.mutations) if(HM.quality != POSITIVE) dna.remove_mutation(HM.name) - if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio)) + if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio) && !isvamp()) blood_volume = (BLOOD_VOLUME_NORMAL*blood_ratio) ..() From e16f1d2f2117f612641015f9b0e78f92f1db0662 Mon Sep 17 00:00:00 2001 From: Artur Date: Wed, 8 Jan 2020 21:01:37 +0100 Subject: [PATCH 34/46] Makes regen coles not heal instead of affecting aheal --- .../mining/equipment/regenerative_core.dm | 291 +++++++++--------- code/modules/mob/living/carbon/human/human.dm | 2 +- 2 files changed, 148 insertions(+), 145 deletions(-) diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm index 4429c8426f..1e4244165f 100644 --- a/code/modules/mining/equipment/regenerative_core.dm +++ b/code/modules/mining/equipment/regenerative_core.dm @@ -1,144 +1,147 @@ -/*********************Hivelord stabilizer****************/ -/obj/item/hivelordstabilizer - name = "stabilizing serum" - icon = 'icons/obj/chemical.dmi' - icon_state = "bottle19" - desc = "Inject certain types of monster organs with this stabilizer to preserve their healing powers indefinitely." - w_class = WEIGHT_CLASS_TINY - -/obj/item/hivelordstabilizer/afterattack(obj/item/organ/M, mob/user) - . = ..() - var/obj/item/organ/regenerative_core/C = M - if(!istype(C, /obj/item/organ/regenerative_core)) - to_chat(user, "The stabilizer only works on certain types of monster organs, generally regenerative in nature.") - return ..() - - C.preserved() - to_chat(user, "You inject the [M] with the stabilizer. It will no longer go inert.") - qdel(src) - -/************************Hivelord core*******************/ -/obj/item/organ/regenerative_core - name = "regenerative core" - desc = "All that remains of a hivelord. It can be used to heal completely, but it will rapidly decay into uselessness." - icon_state = "roro core 2" - item_flags = NOBLUDGEON - slot = "hivecore" - force = 0 - actions_types = list(/datum/action/item_action/organ_action/use) - var/inert = 0 - var/preserved = 0 - -/obj/item/organ/regenerative_core/Initialize() - . = ..() - addtimer(CALLBACK(src, .proc/inert_check), 2400) - -/obj/item/organ/regenerative_core/proc/inert_check() - if(!preserved) - go_inert() - -/obj/item/organ/regenerative_core/proc/preserved(implanted = 0) - inert = FALSE - preserved = TRUE - update_icon() - desc = "All that remains of a hivelord. It is preserved, allowing you to use it to heal completely without danger of decay." - if(implanted) - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "implanted")) - else - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "stabilizer")) - -/obj/item/organ/regenerative_core/proc/go_inert() - inert = TRUE - name = "decayed regenerative core" - desc = "All that remains of a hivelord. It has decayed, and is completely useless." - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "inert")) - update_icon() - -/obj/item/organ/regenerative_core/ui_action_click() - if(inert) - to_chat(owner, "[src] breaks down as it tries to activate.") - else - owner.revive(full_heal = 1) - owner.log_message("[owner] used an implanted [src] to heal themselves! Keep fighting, it's just a flesh wound!", LOG_ATTACK, color="green") //Logging for implanted legion core use - qdel(src) - -/obj/item/organ/regenerative_core/on_life() - ..() - if(owner.health < owner.crit_threshold) - ui_action_click() - -/obj/item/organ/regenerative_core/afterattack(atom/target, mob/user, proximity_flag) - . = ..() - if(proximity_flag && ishuman(target)) - var/mob/living/carbon/human/H = target - if(inert) - to_chat(user, "[src] has decayed and can no longer be used to heal.") - return - else - if(H.stat == DEAD) - to_chat(user, "[src] are useless on the dead.") - return - if(H != user) - H.visible_message("[user] forces [H] to apply [src]... [H.p_they()] quickly regenerate all injuries!") - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "other")) - else - to_chat(user, "You start to smear [src] on yourself. It feels and smells disgusting, but you feel amazingly refreshed in mere moments.") - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self")) - H.revive(full_heal = 1) - qdel(src) - user.log_message("[user] used [src] to heal [H]! Wake the fuck up, Samurai!", LOG_ATTACK, color="green") //Logging for 'old' style legion core use, when clicking on a sprite of yourself or another. - -/obj/item/organ/regenerative_core/attack_self(mob/user) //Knouli's first hack! Allows for the use of the core in hand rather than needing to click on the target, yourself, to selfheal. Its a rip of the proc just above - but skips on distance check and only uses 'user' rather than 'target' - if(ishuman(user)) //Check if user is human, no need for distance check as it's self heal - var/mob/living/carbon/human/H = user //Set H to user rather than target - if(inert) //Inert cores are useless - to_chat(user, "[src] has decayed and can no longer be used to heal.") - return - else //Skip on check if the target to be healed is dead as, if you are dead, you're not going to be able to use it on yourself! - to_chat(user, "You start to smear [src] on yourself. It feels and smells disgusting, but you feel amazingly refreshed in mere moments.") - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self")) - H.revive(full_heal = 1) - qdel(src) - H.log_message("[H] used [src] to heal themselves! Making use of Knouli's sexy and intelligent use-in-hand proc!", LOG_ATTACK, color="green") //Logging for 'new' style legion core use, when using the core in-hand. - - -/obj/item/organ/regenerative_core/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE) - . = ..() - if(!preserved && !inert) - preserved(TRUE) - owner.visible_message("[src] stabilizes as it's inserted.") - -/obj/item/organ/regenerative_core/Remove(mob/living/carbon/M, special = 0) - if(!inert && !special) - owner.visible_message("[src] rapidly decays as it's removed.") - go_inert() - return ..() - -/obj/item/organ/regenerative_core/prepare_eat() - return null - -/*************************Legion core********************/ -/obj/item/organ/regenerative_core/legion - desc = "A strange rock that crackles with power. It can be used to heal completely, but it will rapidly decay into uselessness." - icon_state = "legion_soul" - -/obj/item/organ/regenerative_core/legion/Initialize() - . = ..() - update_icon() - -/obj/item/organ/regenerative_core/update_icon() - icon_state = inert ? "legion_soul_inert" : "legion_soul" - cut_overlays() - if(!inert && !preserved) - add_overlay("legion_soul_crackle") - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/organ/regenerative_core/legion/go_inert() - ..() - desc = "[src] has become inert. It has decayed, and is completely useless." - -/obj/item/organ/regenerative_core/legion/preserved(implanted = 0) - ..() - desc = "[src] has been stabilized. It is preserved, allowing you to use it to heal completely without danger of decay." +/*********************Hivelord stabilizer****************/ +/obj/item/hivelordstabilizer + name = "stabilizing serum" + icon = 'icons/obj/chemical.dmi' + icon_state = "bottle19" + desc = "Inject certain types of monster organs with this stabilizer to preserve their healing powers indefinitely." + w_class = WEIGHT_CLASS_TINY + +/obj/item/hivelordstabilizer/afterattack(obj/item/organ/M, mob/user) + . = ..() + var/obj/item/organ/regenerative_core/C = M + if(!istype(C, /obj/item/organ/regenerative_core)) + to_chat(user, "The stabilizer only works on certain types of monster organs, generally regenerative in nature.") + return ..() + + C.preserved() + to_chat(user, "You inject the [M] with the stabilizer. It will no longer go inert.") + qdel(src) + +/************************Hivelord core*******************/ +/obj/item/organ/regenerative_core + name = "regenerative core" + desc = "All that remains of a hivelord. It can be used to heal completely, but it will rapidly decay into uselessness." + icon_state = "roro core 2" + item_flags = NOBLUDGEON + slot = "hivecore" + force = 0 + actions_types = list(/datum/action/item_action/organ_action/use) + var/inert = 0 + var/preserved = 0 + +/obj/item/organ/regenerative_core/Initialize() + . = ..() + addtimer(CALLBACK(src, .proc/inert_check), 2400) + +/obj/item/organ/regenerative_core/proc/inert_check() + if(!preserved) + go_inert() + +/obj/item/organ/regenerative_core/proc/preserved(implanted = 0) + inert = FALSE + preserved = TRUE + update_icon() + desc = "All that remains of a hivelord. It is preserved, allowing you to use it to heal completely without danger of decay." + if(implanted) + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "implanted")) + else + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "stabilizer")) + +/obj/item/organ/regenerative_core/proc/go_inert() + inert = TRUE + name = "decayed regenerative core" + desc = "All that remains of a hivelord. It has decayed, and is completely useless." + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "inert")) + update_icon() + +/obj/item/organ/regenerative_core/ui_action_click() + if(inert) + to_chat(owner, "[src] breaks down as it tries to activate.") + else + owner.revive(full_heal = 1) + owner.log_message("[owner] used an implanted [src] to heal themselves! Keep fighting, it's just a flesh wound!", LOG_ATTACK, color="green") //Logging for implanted legion core use + qdel(src) + +/obj/item/organ/regenerative_core/on_life() + ..() + if(owner.health < owner.crit_threshold) + ui_action_click() + +/obj/item/organ/regenerative_core/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(proximity_flag && ishuman(target)) + var/mob/living/carbon/human/H = target + if(inert) + to_chat(user, "[src] has decayed and can no longer be used to heal.") + return + if(isvamp(user)) + to_chat(user, "[src] breaks down as it fails to heal your unholy self") + return + else + if(H.stat == DEAD) + to_chat(user, "[src] are useless on the dead.") + return + if(H != user) + H.visible_message("[user] forces [H] to apply [src]... [H.p_they()] quickly regenerate all injuries!") + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "other")) + else + to_chat(user, "You start to smear [src] on yourself. It feels and smells disgusting, but you feel amazingly refreshed in mere moments.") + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self")) + H.revive(full_heal = 1) + qdel(src) + user.log_message("[user] used [src] to heal [H]! Wake the fuck up, Samurai!", LOG_ATTACK, color="green") //Logging for 'old' style legion core use, when clicking on a sprite of yourself or another. + +/obj/item/organ/regenerative_core/attack_self(mob/user) //Knouli's first hack! Allows for the use of the core in hand rather than needing to click on the target, yourself, to selfheal. Its a rip of the proc just above - but skips on distance check and only uses 'user' rather than 'target' + if(ishuman(user)) //Check if user is human, no need for distance check as it's self heal + var/mob/living/carbon/human/H = user //Set H to user rather than target + if(inert) //Inert cores are useless + to_chat(user, "[src] has decayed and can no longer be used to heal.") + return + else //Skip on check if the target to be healed is dead as, if you are dead, you're not going to be able to use it on yourself! + to_chat(user, "You start to smear [src] on yourself. It feels and smells disgusting, but you feel amazingly refreshed in mere moments.") + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self")) + H.revive(full_heal = 1) + qdel(src) + H.log_message("[H] used [src] to heal themselves! Making use of Knouli's sexy and intelligent use-in-hand proc!", LOG_ATTACK, color="green") //Logging for 'new' style legion core use, when using the core in-hand. + + +/obj/item/organ/regenerative_core/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE) + . = ..() + if(!preserved && !inert) + preserved(TRUE) + owner.visible_message("[src] stabilizes as it's inserted.") + +/obj/item/organ/regenerative_core/Remove(mob/living/carbon/M, special = 0) + if(!inert && !special) + owner.visible_message("[src] rapidly decays as it's removed.") + go_inert() + return ..() + +/obj/item/organ/regenerative_core/prepare_eat() + return null + +/*************************Legion core********************/ +/obj/item/organ/regenerative_core/legion + desc = "A strange rock that crackles with power. It can be used to heal completely, but it will rapidly decay into uselessness." + icon_state = "legion_soul" + +/obj/item/organ/regenerative_core/legion/Initialize() + . = ..() + update_icon() + +/obj/item/organ/regenerative_core/update_icon() + icon_state = inert ? "legion_soul_inert" : "legion_soul" + cut_overlays() + if(!inert && !preserved) + add_overlay("legion_soul_crackle") + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/organ/regenerative_core/legion/go_inert() + ..() + desc = "[src] has become inert. It has decayed, and is completely useless." + +/obj/item/organ/regenerative_core/legion/preserved(implanted = 0) + ..() + desc = "[src] has been stabilized. It is preserved, allowing you to use it to heal completely without danger of decay." diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 90daa2633a..0007233975 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -816,7 +816,7 @@ for(var/datum/mutation/human/HM in dna.mutations) if(HM.quality != POSITIVE) dna.remove_mutation(HM.name) - if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio) && !isvamp()) + if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio)) blood_volume = (BLOOD_VOLUME_NORMAL*blood_ratio) ..() From cc174d91ecdd09a725ab620f60e784c9acfac5af Mon Sep 17 00:00:00 2001 From: Arturlang Date: Wed, 8 Jan 2020 22:02:50 +0200 Subject: [PATCH 35/46] Reviewer suggestion Co-Authored-By: Lin --- code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm b/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm index 623fc691c0..b7c90523b6 100644 --- a/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm +++ b/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm @@ -78,7 +78,7 @@ var/mob/living/carbon/C = target // Needs to be Down/Slipped in some way to Stake. if(!C.can_be_staked() || target == user) - to_chat(user, "You cant stake [target] when they are moving moving about! They have to be laying down or grabbed by the neck!") + to_chat(user, "You can't stake [target] when they are moving about! They have to be laying down or grabbed by the neck!") return // Oops! Can't. if(HAS_TRAIT(C, TRAIT_PIERCEIMMUNE)) From 65d9f13abe58fa0280f4ff410566e5ef6f48d609 Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 15:30:39 -0600 Subject: [PATCH 36/46] Automatic changelog generation for PR #10452 [ci skip] --- html/changelogs/AutoChangeLog-pr-10452.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10452.yml diff --git a/html/changelogs/AutoChangeLog-pr-10452.yml b/html/changelogs/AutoChangeLog-pr-10452.yml new file mode 100644 index 0000000000..e51b9ff752 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10452.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Lattices can be examined yet again." From 465145a0a37208556a7c91c1aa0b77995b358183 Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 15:31:33 -0600 Subject: [PATCH 37/46] Automatic changelog generation for PR #10285 [ci skip] --- html/changelogs/AutoChangeLog-pr-10285.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10285.yml diff --git a/html/changelogs/AutoChangeLog-pr-10285.yml b/html/changelogs/AutoChangeLog-pr-10285.yml new file mode 100644 index 0000000000..89b0bbae43 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10285.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Fixed singularity pulls duping rods out of engine floors." From 4a5a89ffdc3cf3f5fb48f5666890bb6dbd21e980 Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 15:31:58 -0600 Subject: [PATCH 38/46] Automatic changelog generation for PR #10467 [ci skip] --- html/changelogs/AutoChangeLog-pr-10467.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10467.yml diff --git a/html/changelogs/AutoChangeLog-pr-10467.yml b/html/changelogs/AutoChangeLog-pr-10467.yml new file mode 100644 index 0000000000..7fa379f088 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10467.yml @@ -0,0 +1,4 @@ +author: "Seris02" +delete-after: True +changes: + - bugfix: "cardboard box speed" From 146b8f898cef51cdbe1725e2ce6fca524d311e7d Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 15:33:56 -0600 Subject: [PATCH 39/46] Automatic changelog generation for PR #10457 [ci skip] --- html/changelogs/AutoChangeLog-pr-10457.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10457.yml diff --git a/html/changelogs/AutoChangeLog-pr-10457.yml b/html/changelogs/AutoChangeLog-pr-10457.yml new file mode 100644 index 0000000000..203842b0b6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10457.yml @@ -0,0 +1,5 @@ +author: "r4d6" +delete-after: True +changes: + - rscadd: "Added a playback device" + - bugfix: "Made the Voice Analyzer actually care about languages" From 3894d85c308e39e7d0d6bcf52f30bac0eab82103 Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 15:34:30 -0600 Subject: [PATCH 40/46] Automatic changelog generation for PR #10459 [ci skip] --- html/changelogs/AutoChangeLog-pr-10459.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10459.yml diff --git a/html/changelogs/AutoChangeLog-pr-10459.yml b/html/changelogs/AutoChangeLog-pr-10459.yml new file mode 100644 index 0000000000..77de8eb193 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10459.yml @@ -0,0 +1,4 @@ +author: "Putnam" +delete-after: True +changes: + - rscadd: "Cold-blooded quirk" From 9ed90235abb3f7fc9cf22333563882083baba680 Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 15:34:45 -0600 Subject: [PATCH 41/46] Automatic changelog generation for PR #10472 [ci skip] --- html/changelogs/AutoChangeLog-pr-10472.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10472.yml diff --git a/html/changelogs/AutoChangeLog-pr-10472.yml b/html/changelogs/AutoChangeLog-pr-10472.yml new file mode 100644 index 0000000000..9404d70b7b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10472.yml @@ -0,0 +1,4 @@ +author: "keronshb" +delete-after: True +changes: + - bugfix: "fixed the missing icons from Dermal Button nanites" From ebf87a173bb15b03620b4fddd9ae3cc1cef28d88 Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 15:40:42 -0600 Subject: [PATCH 42/46] Automatic changelog generation for PR #10442 [ci skip] --- html/changelogs/AutoChangeLog-pr-10442.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10442.yml diff --git a/html/changelogs/AutoChangeLog-pr-10442.yml b/html/changelogs/AutoChangeLog-pr-10442.yml new file mode 100644 index 0000000000..f407ae9e15 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10442.yml @@ -0,0 +1,4 @@ +author: "Trilbyspaceclone" +delete-after: True +changes: + - tweak: "Halfs the nutriments in sugar" From d9783d2bba0201ab00da397a385ac1489c5c26a6 Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 15:41:28 -0600 Subject: [PATCH 43/46] Automatic changelog generation for PR #10440 [ci skip] --- html/changelogs/AutoChangeLog-pr-10440.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10440.yml diff --git a/html/changelogs/AutoChangeLog-pr-10440.yml b/html/changelogs/AutoChangeLog-pr-10440.yml new file mode 100644 index 0000000000..e942f4056f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10440.yml @@ -0,0 +1,5 @@ +author: "Putnam3145" +delete-after: True +changes: + - rscadd: "Added a sort of \"game mode ban\" by way of having people rank their game modes favorite to least favorite after the secret/extended vote." + - bugfix: "Turns out the schulze scoring was written wrong and it was setting things to 0 that shouldn't have been, so that's fixed." From df572996b366b84063e994875aa1adf8af37a64e Mon Sep 17 00:00:00 2001 From: Putnam Date: Wed, 8 Jan 2020 15:40:35 -0800 Subject: [PATCH 44/46] fix limb damage --- code/modules/surgery/bodyparts/bodyparts.dm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/code/modules/surgery/bodyparts/bodyparts.dm b/code/modules/surgery/bodyparts/bodyparts.dm index a1b74942e0..63a392bf06 100644 --- a/code/modules/surgery/bodyparts/bodyparts.dm +++ b/code/modules/surgery/bodyparts/bodyparts.dm @@ -172,9 +172,8 @@ var/total_damage = brute + burn if(total_damage > can_inflict) - var/excess = total_damage - can_inflict - brute = round(brute * (excess / total_damage),DAMAGE_PRECISION) - burn = round(burn * (excess / total_damage),DAMAGE_PRECISION) + brute = round(brute * (max_damage / total_damage),DAMAGE_PRECISION) + burn = round(burn * (max_damage / total_damage),DAMAGE_PRECISION) brute_dam += brute burn_dam += burn From 5dd58950abfde504abd76ed553539b3262951b3b Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 20:04:47 -0600 Subject: [PATCH 45/46] Automatic changelog generation for PR #10483 [ci skip] --- html/changelogs/AutoChangeLog-pr-10483.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10483.yml diff --git a/html/changelogs/AutoChangeLog-pr-10483.yml b/html/changelogs/AutoChangeLog-pr-10483.yml new file mode 100644 index 0000000000..1f016b1591 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10483.yml @@ -0,0 +1,4 @@ +author: "Putnam3145" +delete-after: True +changes: + - bugfix: "Limb damage works now" From f4e508615dc902227644ac88bac35b17e575af7e Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Wed, 8 Jan 2020 21:16:06 -0600 Subject: [PATCH 46/46] Automatic changelog generation for PR #10473 [ci skip] --- html/changelogs/AutoChangeLog-pr-10473.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-10473.yml diff --git a/html/changelogs/AutoChangeLog-pr-10473.yml b/html/changelogs/AutoChangeLog-pr-10473.yml new file mode 100644 index 0000000000..f1821a3cce --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10473.yml @@ -0,0 +1,4 @@ +author: "Bhijn" +delete-after: True +changes: + - bugfix: "server_hop can no longer be used to remotely lobotomize a spaceman"