diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 03dab4cf4f9..c4b6647ef87 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -71,6 +71,13 @@ GLOBAL_LIST_INIT(heretic_start_knowledge,list(/datum/eldritch_knowledge/spell/ba #define PATH_FLESH "Flesh" #define PATH_VOID "Void" +/// Forces the blob to place the core where they currently are, ignoring any checks. +#define BLOB_FORCE_PLACEMENT -1 +/// Normal blob placement, does the regular checks to make sure the blob isn't placing itself in an invalid location +#define BLOB_NORMAL_PLACEMENT 0 +/// Selects a random location for the blob to be placed. +#define BLOB_RANDOM_PLACEMENT 1 + #define CONSTRUCT_JUGGERNAUT "Juggernaut" #define CONSTRUCT_WRAITH "Wraith" #define CONSTRUCT_ARTIFICER "Artificer" diff --git a/code/_onclick/hud/blob_overmind.dm b/code/_onclick/hud/blob_overmind.dm index 5af4f278788..dd3224c5500 100644 --- a/code/_onclick/hud/blob_overmind.dm +++ b/code/_onclick/hud/blob_overmind.dm @@ -49,7 +49,7 @@ if(isovermind(usr)) var/mob/camera/blob/B = usr if(!B.placed) - B.place_blob_core(0) + B.place_blob_core(BLOB_NORMAL_PLACEMENT) B.transport_core() /atom/movable/screen/blob/blobbernaut diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index df58865a661..ffb6679af7f 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -444,7 +444,7 @@ enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain") required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 - weight = 4 + weight = 2 cost = 10 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE @@ -453,6 +453,41 @@ var/body = applicant.become_overmind() return body +/// Infects a random player, making them explode into a blob. +/datum/dynamic_ruleset/midround/blob_infection + name = "Blob Infection" + antag_datum = /datum/antagonist/blob + antag_flag = ROLE_BLOB + protected_roles = list("Prisoner", "Security Officer", "Warden", "Detective", "Head of Security", "Captain") + restricted_roles = list("Cyborg", "AI", "Positronic Brain") + enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain") + required_enemies = list(2,2,1,1,1,1,1,0,0,0) + required_candidates = 1 + weight = 2 + cost = 10 + requirements = list(101,101,101,80,60,50,30,20,10,10) + repeatable = TRUE + +/datum/dynamic_ruleset/midround/blob_infection/trim_candidates() + ..() + candidates = living_players + for(var/mob/living/player as anything in candidates) + var/turf/player_turf = get_turf(player) + if(!player_turf || !is_station_level(player_turf.z)) + candidates -= player + continue + + if(player.mind && (player.mind.special_role || length(player.mind.antag_datums) > 0)) + candidates -= player + +/datum/dynamic_ruleset/midround/blob_infection/execute() + if(!candidates || !candidates.len) + return FALSE + var/mob/living/carbon/human/blob_antag = pick_n_take(candidates) + assigned += blob_antag.mind + blob_antag.mind.special_role = antag_flag + return ..() + ////////////////////////////////////////////// // // // XENOMORPH (GHOST) // diff --git a/code/modules/antagonists/blob/blob.dm b/code/modules/antagonists/blob/blob.dm index 216352809f8..a8c54453f68 100644 --- a/code/modules/antagonists/blob/blob.dm +++ b/code/modules/antagonists/blob/blob.dm @@ -19,13 +19,25 @@ return basic_report /datum/antagonist/blob/greet() + to_chat(owner.current, span_notice("You are the Blob!")) + owner.announce_objectives() if(!isovermind(owner.current)) - to_chat(owner,span_userdanger("You feel bloated.")) + to_chat(owner.current, span_notice("Use the pop ability to place your blob core! It is recommended you do this away from anyone else, as you'll be taking on the entire crew!")) + + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/blobalert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) /datum/antagonist/blob/on_gain() create_objectives() . = ..() +/datum/antagonist/blob/remove_innate_effects() + QDEL_NULL(pop_action) + return ..() + +/datum/antagonist/blob/farewell() + to_chat(owner.current, "You are no longer the Blob!") + return ..() + /datum/antagonist/blob/proc/create_objectives() var/datum/objective/blob_takeover/main = new main.owner = owner @@ -47,20 +59,51 @@ icon_icon = 'icons/mob/blob.dmi' button_icon_state = "blob" -/datum/action/innate/blobpop/Activate() + /// The time taken before this ability is automatically activated. + var/autoplace_time = OVERMIND_STARTING_AUTO_PLACE_TIME + +/datum/action/innate/blobpop/Grant(Target) + . = ..() + if(owner) + addtimer(CALLBACK(src, .proc/Activate, TRUE), autoplace_time, TIMER_UNIQUE|TIMER_OVERRIDE) + to_chat(owner, "You will automatically pop and place your blob core in [DisplayTimeText(autoplace_time)].") + +/datum/action/innate/blobpop/Activate(timer_activated = FALSE) var/mob/living/old_body = owner + if(!owner) + return + var/datum/antagonist/blob/blobtag = owner.mind.has_antag_datum(/datum/antagonist/blob) if(!blobtag) - Remove() + Remove(owner) return - var/mob/camera/blob/B = new /mob/camera/blob(get_turf(old_body), blobtag.starting_points_human_blob) - owner.mind.transfer_to(B) + + . = TRUE + var/turf/target_turf = get_turf(owner) + if(target_turf.density) + to_chat(owner, "This spot is too dense to place a blob core on!") + . = FALSE + var/area/target_area = get_area(target_turf) + if(isspaceturf(target_turf) || !(target_area?.area_flags & BLOBS_ALLOWED) || !is_station_level(target_turf.z)) + to_chat(owner, "You cannot place your core here!") + . = FALSE + + var/placement_override = BLOB_FORCE_PLACEMENT + if(!.) + if(!timer_activated) + return + placement_override = BLOB_RANDOM_PLACEMENT + to_chat(owner, "Because your current location is an invalid starting spot and you need to pop, you've been moved to a random location!") + + var/mob/camera/blob/blob_cam = new /mob/camera/blob(get_turf(old_body), blobtag.starting_points_human_blob) + owner.mind.transfer_to(blob_cam) old_body.gib() - B.place_blob_core(placement_override = TRUE, pop_override = TRUE) + blob_cam.place_blob_core(placement_override, pop_override = TRUE) + playsound(get_turf(blob_cam), 'sound/ambience/antag/blobalert.ogg', 50, FALSE) /datum/antagonist/blob/antag_listing_status() . = ..() if(owner?.current) - var/mob/camera/blob/B = owner.current - if(istype(B)) - . += "(Progress: [B.blobs_legit.len]/[B.blobwincount])" + var/mob/camera/blob/blob_cam = owner.current + if(istype(blob_cam)) + . += "(Progress: [length(blob_cam.blobs_legit)]/[blob_cam.blobwincount])" diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm index 5e7c5c59ef3..b843c7d9d89 100644 --- a/code/modules/antagonists/blob/overmind.dm +++ b/code/modules/antagonists/blob/overmind.dm @@ -114,7 +114,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) to_chat(src, span_big("You will automatically place your blob core in [DisplayTimeText(autoplace_max_time - world.time)].")) manualplace_min_time = 0 if(autoplace_max_time && world.time >= autoplace_max_time) - place_blob_core(1) + place_blob_core(BLOB_RANDOM_PLACEMENT) else qdel(src) else if(!victory_in_progress && (blobs_legit.len >= blobwincount)) diff --git a/code/modules/antagonists/blob/powers.dm b/code/modules/antagonists/blob/powers.dm index 7412cd5f687..46349c8e6bb 100644 --- a/code/modules/antagonists/blob/powers.dm +++ b/code/modules/antagonists/blob/powers.dm @@ -7,10 +7,10 @@ add_points(-cost) return TRUE -/mob/camera/blob/proc/place_blob_core(placement_override, pop_override = FALSE) - if(placed && placement_override != -1) +/mob/camera/blob/proc/place_blob_core(placement_override = BLOB_NORMAL_PLACEMENT, pop_override = FALSE) + if(placed && placement_override != BLOB_FORCE_PLACEMENT) return TRUE - if(!placement_override) + if(placement_override == BLOB_NORMAL_PLACEMENT) if(!pop_override) for(var/mob/living/M in range(7, src)) if(ROLE_BLOB in M.faction) @@ -45,7 +45,7 @@ if(!pop_override && world.time <= manualplace_min_time && world.time <= autoplace_max_time) to_chat(src, span_warning("It is too early to place your blob core!")) return FALSE - else if(placement_override == 1) + else if(placement_override == BLOB_RANDOM_PLACEMENT) var/turf/T = pick(GLOB.blobstart) forceMove(T) //got overrided? you're somewhere random, motherfucker if(placed && blob_core) diff --git a/config/dynamic.json b/config/dynamic.json index 93bbb139485..c503697963f 100644 --- a/config/dynamic.json +++ b/config/dynamic.json @@ -93,6 +93,10 @@ "weight": 0 }, + "Blob Infection": { + "weight": 0 + }, + "Alien Infestation": { "weight": 0 }, diff --git a/sound/ambience/antag/blobalert.ogg b/sound/ambience/antag/blobalert.ogg new file mode 100644 index 00000000000..c9fcfbee448 Binary files /dev/null and b/sound/ambience/antag/blobalert.ogg differ