diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm index 891746c663d7..07a8d3211098 100644 --- a/code/__DEFINES/icon_smoothing.dm +++ b/code/__DEFINES/icon_smoothing.dm @@ -114,9 +114,13 @@ DEFINE_BITFIELD(smoothing_flags, list( #define SMOOTH_GROUP_LOWERED_PLASTEEL S_TURF(61) #define SMOOTH_GROUP_FISSURE S_TURF(62) +// Monkestation edit start +#define SMOOTH_GROUP_FLOOR_BLOODLING S_TURF(63) ////turf/open/floor/bloodling + #define SMOOTH_GROUP_MUSHROOM S_TURF(63) -#define MAX_S_TURF 63 //Always match this value with the one above it. +#define MAX_S_TURF 64 //Always match this value with the one above it. +// Monkestation edit end #define S_OBJ(num) ("-" + #num + ",") /* /obj included */ diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 90a4d75dc872..ad3d254b08d7 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -17,6 +17,7 @@ #define ROLE_TRAITOR "Traitor" #define ROLE_WIZARD "Wizard" #define ROLE_CLOCK_CULTIST "Clock Cultist" //monkestation edit +#define ROLE_BLOODLING "Bloodling" // monkestation edit #define ROLE_OPFOR_CANDIDATE "OPFOR Candidate" #define ROLE_ASSAULT_OPERATIVE "Assault Operative" @@ -75,6 +76,7 @@ #define ROLE_LAVALAND "Lavaland" #define ROLE_LAZARUS_BAD "Slaved Revived Mob" #define ROLE_LAZARUS_GOOD "Friendly Revived Mob" +#define ROLE_BLOODLING_THRALL "Bloodling Thrall" // monkestation edit #define ROLE_CLOWN_OPERATIVE "Clown Operative" #define ROLE_FREE_GOLEM "Free Golem" @@ -134,6 +136,7 @@ GLOBAL_LIST_INIT(special_roles, list( ROLE_CLOCK_CULTIST = 14, ROLE_BLOODSUCKER = 0, ROLE_ASSAULT_OPERATIVE = 14, + ROLE_BLOODLING = 15, // Midround ROLE_ABDUCTOR = 0, diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index c1e4c9da7b95..adb47030fe1a 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -60,6 +60,8 @@ #define MODE_CUSTOM_SAY_ERASE_INPUT "erase_input" +#define MODE_BLOODLING "bloodling" // monkestation edit + //Spans. Robot speech, italics, etc. Applied in compose_message(). #define SPAN_ROBOT "robot" #define SPAN_YELL "yell" diff --git a/code/__DEFINES/~monkestation/antagonists.dm b/code/__DEFINES/~monkestation/antagonists.dm index 353559f0d41f..7eab787cada6 100644 --- a/code/__DEFINES/~monkestation/antagonists.dm +++ b/code/__DEFINES/~monkestation/antagonists.dm @@ -53,3 +53,18 @@ #define BORER_HIDING (1<<3) /// If the borer can produce eggs without a host #define BORER_ALONE_PRODUCTION (1<<4) + +/// If the given mob is a bloodling +#define IS_BLOODLING(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/bloodling)) + +/// If the given mob is a bloodling thrall +#define IS_BLOODLING_THRALL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/changeling/bloodling_thrall)) + +/// If the given mob is a simplemob bloodling thrall +#define IS_SIMPLEMOB_BLOODLING_THRALL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/infested_thrall)) + +/// If the given mob is a bloodling thrall or bloodling +#define IS_BLOODLING_OR_THRALL(mob) (IS_BLOODLING(mob) || IS_BLOODLING_THRALL(mob) || IS_SIMPLEMOB_BLOODLING_THRALL(mob)) + +/// Antagonist panel groups +#define ANTAG_GROUP_BLOODLING "Bloodling" diff --git a/icons/mob/huds/antag_hud.dmi b/icons/mob/huds/antag_hud.dmi index 0d5d95f3bb9d..d764ffa16116 100644 Binary files a/icons/mob/huds/antag_hud.dmi and b/icons/mob/huds/antag_hud.dmi differ diff --git a/monkestation/code/__DEFINES/mobfactions.dm b/monkestation/code/__DEFINES/mobfactions.dm new file mode 100644 index 000000000000..01b00a157d28 --- /dev/null +++ b/monkestation/code/__DEFINES/mobfactions.dm @@ -0,0 +1,4 @@ +// Antagonist factions + +/// Bloodlings and their minions +#define FACTION_BLOODLING "bloodling" diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/absorb_biomass.dm b/monkestation/code/modules/antagonists/bloodling/abilities/absorb_biomass.dm new file mode 100644 index 000000000000..c5d4d77d5933 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/absorb_biomass.dm @@ -0,0 +1,98 @@ +/datum/action/cooldown/mob_cooldown/bloodling/absorb + name = "Absorb Biomass" + desc = "Allows you to absorb a dead carbon or living mob close to you." + button_icon_state = "absorb" + /// If the bloodling is currently absorbing + var/is_absorbing = FALSE + /// Items we can absorb + var/list/absorbable_types = list( + /obj/effect/decal/cleanable/blood, + /obj/item/food. + ) + +/datum/action/cooldown/mob_cooldown/bloodling/absorb/PreActivate(atom/target) + if(owner == target) + return FALSE + + if(is_absorbing) + owner.balloon_alert(owner, "Already absorbing!") + return FALSE + + if(is_type_in_list(target, absorbable_types)) + return ..() + + if(!ismob(target)) + owner.balloon_alert(owner, "doesn't work on non-mobs!") + return FALSE + + var/mob/living/mob_to_absorb = target + + if(!HAS_TRAIT(mob_to_absorb, MOB_ORGANIC)) + owner.balloon_alert(owner, "doesn't work on non-organics!") + return FALSE + + if(!iscarbon(mob_to_absorb)) + return ..() + + var/mob/living/carbon/carbon_to_absorb = target + if(!carbon_to_absorb.stat == DEAD) + owner.balloon_alert(owner, "only works on dead carbons!") + return FALSE + return ..() + +/datum/action/cooldown/mob_cooldown/bloodling/absorb/Activate(atom/target) + . = ..() + var/mob/living/basic/bloodling/our_mob = owner + /// How long it takes to absorb something + var/absorb_time = 5 SECONDS + /// How much biomass is gained from absorbing something + var/biomass_gain = 10 + + our_mob.balloon_alert(our_mob, "You begin absorbing [target]!") + + if(is_type_in_list(target, absorbable_types)) + our_mob.add_biomass(biomass_gain) + qdel(target) + our_mob.visible_message( + span_alertalien("[our_mob] wraps its tendrils around [target]. It absorbs it!"), + span_noticealien("You wrap your tendrils around [target] and absorb it!"), + ) + return TRUE + + var/mob/living/mob_to_absorb = target + + // This prevents the mob from being dragged away from the bloodling during the process + mob_to_absorb.AddComponentFrom(REF(src), /datum/component/leash, our_mob, 1) + + if(!iscarbon(mob_to_absorb)) + biomass_gain = max(mob_to_absorb.getMaxHealth() * 0.5, biomass_gain) + if(biomass_gain > 150) + our_mob.balloon_alert(our_mob, "[target] is too large!") + return FALSE + + if(biomass_gain < 10) + biomass_gain = 10 + else + var/mob/living/carbon/carbon_to_absorb = target + if(ismonkey(carbon_to_absorb)) + biomass_gain = 50 + else + biomass_gain = 100 + absorb_time = 10 SECONDS + + // Setting this to true means they cant target the same person multiple times, or other people since it allows for speedrunning + is_absorbing = TRUE + + if(!do_after(owner, absorb_time, mob_to_absorb)) + mob_to_absorb.RemoveComponentSource(REF(src), /datum/component/leash) + return FALSE + + is_absorbing = FALSE + our_mob.add_biomass(biomass_gain) + mob_to_absorb.gib() + our_mob.visible_message( + span_alertalien("[our_mob] wraps its tendrils around [target]. It absorbs it!"), + span_noticealien("You wrap your tendrils around [target] and absorb it!"), + ) + playsound(our_mob, 'sound/items/eatfood.ogg', 20) + return TRUE diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/ascension.dm b/monkestation/code/modules/antagonists/bloodling/abilities/ascension.dm new file mode 100644 index 000000000000..01082ecb37f3 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/ascension.dm @@ -0,0 +1,105 @@ +/datum/action/cooldown/bloodling/ascension + name = "Ascend" + desc = "We reach our last form...Mass consumption is required. Costs 500 Biomass and takes 5 minutes for you to ascend." + button_icon_state = "ascend" + biomass_cost = 500 + var/static/datum/dimension_theme/chosen_theme + var/list/responses = list("Yes", "No") + +/datum/action/cooldown/bloodling/ascension/Activate(atom/target) + var/mob/living/basic/bloodling/proper/our_mob = owner + // Adds 500 biomass back + our_mob.add_biomass(500) + var/tgui_response = tgui_alert(our_mob, "Are you prepared to ascend?", "Ascension", responses, 0) + if(tgui_response == "No") + return + var/turf/our_turf = our_mob.get_turf + priority_announce("ALERT: LEVEL 4 BIOHAZARD MORPHING IN [our_turf.get_area]. STOP IT AT ALL COSTS.", "Biohazard") + our_mob.evolution(6) + // Waits 5 minutes before calling the ascension + addtimer(CALLBACK(src, PROC_REF(ascend), our_mob), 5 MINUTES) + return TRUE + +/datum/action/cooldown/bloodling/ascension/proc/ascend(mob/living/basic/bloodling) + // Calls the shuttle + SSshuttle.requestEvac(src, "ALERT: LEVEL 4 BIOHAZARD DETECTED. ORGANISM CONTAINMENT HAS FAILED. EVACUATE REMAINING PERSONEL.") + + if(isnull(chosen_theme)) + chosen_theme = new /datum/dimension_theme/bloodling() + var/turf/start_turf = get_turf(bloodling) + var/greatest_dist = 0 + var/list/turfs_to_transform = list() + for (var/turf/transform_turf as anything in GLOB.station_turfs) + if (!chosen_theme.can_convert(transform_turf)) + continue + var/dist = get_dist(start_turf, transform_turf) + if (dist > greatest_dist) + greatest_dist = dist + if (!turfs_to_transform["[dist]"]) + turfs_to_transform["[dist]"] = list() + turfs_to_transform["[dist]"] += transform_turf + + if (chosen_theme.can_convert(start_turf)) + chosen_theme.apply_theme(start_turf) + + for (var/iterator in 1 to greatest_dist) + if(!turfs_to_transform["[iterator]"]) + continue + addtimer(CALLBACK(src, PROC_REF(transform_area), turfs_to_transform["[iterator]"]), (5 SECONDS) * iterator) + +/datum/action/cooldown/bloodling/ascension/proc/transform_area(list/turfs) + for (var/turf/transform_turf as anything in turfs) + if (!chosen_theme.can_convert(transform_turf)) + continue + chosen_theme.apply_theme(transform_turf) + CHECK_TICK + + +/turf/open/misc/bloodling + name = "nerve threads" + icon = 'monkestation/code/modules/antagonists/bloodling/sprites/flesh_tile.dmi' + icon_state = "flesh_tile-0" + base_icon_state = "flesh_tile" + baseturfs = /turf/open/floor/plating + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_BLOODLING + canSmoothWith = SMOOTH_GROUP_FLOOR_BLOODLING + layer = HIGH_TURF_LAYER + underfloor_accessibility = UNDERFLOOR_HIDDEN + +/turf/open/misc/bloodling/Initialize(mapload) + . = ..() + if(is_station_level(z)) + GLOB.station_turfs += src + + var/matrix/translation = new + translation.Translate(-9, -9) + transform = translation + QUEUE_SMOOTH(src) + + +/turf/open/misc/bloodling/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) + . = ..() + if (!.) + return + + if(!smoothing_flags) + return + + var/matrix/translation = new + translation.Translate(-9, -9) + transform = translation + + underlay_appearance.transform = transform + +/datum/dimension_theme/bloodling + icon = 'icons/obj/food/meat.dmi' + icon_state = "meat" + sound = 'sound/items/eatfood.ogg' + replace_floors = list(/turf/open/misc/bloodling = 1) + replace_walls = /turf/closed/wall/material/meat + window_colour = "#5c0c0c" + replace_objs = list(\ + /obj/machinery/atmospherics/components/unary/vent_scrubber = list(/obj/structure/meateor_fluff/eyeball = 1), \ + /obj/machinery/atmospherics/components/unary/vent_pump = list(/obj/structure/meateor_fluff/eyeball = 1),) + diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/bloodling_abilities.dm b/monkestation/code/modules/antagonists/bloodling/abilities/bloodling_abilities.dm new file mode 100644 index 000000000000..fb8697ff7b01 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/bloodling_abilities.dm @@ -0,0 +1,81 @@ +/datum/action/cooldown/mob_cooldown/bloodling + name = "debug" + desc = "Yell at coders if you see this" + button_icon = 'monkestation/code/modules/antagonists/bloodling/sprites/bloodling_abilities.dmi' + background_icon = 'monkestation/icons/mob/actions/backgrounds.dmi' + background_icon_state = "bg_bloodling" + /// The biomass cost of the ability + var/biomass_cost = 0 + +/datum/action/cooldown/mob_cooldown/bloodling/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + // Basically we only want bloodlings to have this + if(!istype(owner, /mob/living/basic/bloodling)) + stack_trace("A non-bloodling mob has obtained a bloodling action!") + return FALSE + + var/mob/living/basic/bloodling/our_mob = owner + if(our_mob.biomass <= biomass_cost) + return FALSE + + return TRUE + +/datum/action/cooldown/mob_cooldown/bloodling/PreActivate(atom/target) + if(get_dist(owner, target) > 1) + return FALSE + + var/mob/living/basic/bloodling/our_mob = owner + . = ..() + if(!.) + return FALSE + + // Since bloodlings evolve it may result in them or their abilities going away + // so we can just return true here + if(QDELETED(src) || QDELETED(owner)) + return TRUE + + if(click_to_activate && our_mob.biomass < biomass_cost) + unset_click_ability(owner, refund_cooldown = FALSE) + + our_mob.add_biomass(-biomass_cost) + + return TRUE + +// A non mob version for certain abilities (mainly hide, build, slam, shriek, whiplash) +/datum/action/cooldown/bloodling + name = "debug" + desc = "Yell at coders if you see this" + button_icon = 'monkestation/code/modules/antagonists/bloodling/sprites/bloodling_abilities.dmi' + background_icon = 'monkestation/icons/mob/actions/backgrounds.dmi' + background_icon_state = "bg_bloodling" + // The biomass cost of the ability + var/biomass_cost = 0 + +/datum/action/cooldown/bloodling/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + // Basically we only want bloodlings to have this + if(!istype(owner, /mob/living/basic/bloodling)) + return FALSE + var/mob/living/basic/bloodling/our_mob = owner + if(our_mob.biomass <= biomass_cost) + return FALSE + // Hardcoded for the bloodling biomass system. So it will not function on non-bloodlings + return istype(owner, /mob/living/basic/bloodling) + +/datum/action/cooldown/bloodling/PreActivate(atom/target) + var/mob/living/basic/bloodling/our_mob = owner + . = ..() + if(!.) + return FALSE + // Since bloodlings evolve it may result in them or their abilities going away + // so we can just return true here + if(QDELETED(src) || QDELETED(owner)) + return TRUE + + our_mob.add_biomass(-biomass_cost) + + return TRUE diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/bloodling_hivespeak.dm b/monkestation/code/modules/antagonists/bloodling/abilities/bloodling_hivespeak.dm new file mode 100644 index 000000000000..9fd138a74da6 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/bloodling_hivespeak.dm @@ -0,0 +1,49 @@ +/datum/action/cooldown/bloodling_hivespeak + name = "Hivespeak" + desc = "Whispered words that all in your hive can hear." + button_icon_state = "hivemind" + background_icon = 'monkestation/icons/mob/actions/backgrounds.dmi' + background_icon_state = "bg_bloodling" + +/datum/action/cooldown/bloodling_hivespeak/IsAvailable(feedback = FALSE) + if(IS_BLOODLING_OR_THRALL(owner)) + return TRUE + return ..() + +/datum/action/cooldown/bloodling_hivespeak/Activate() + var/input = tgui_input_text(usr, "Message to tell your hive", "Commune of Hive") + if(!input || !IsAvailable(feedback = TRUE)) + return + + var/list/filter_result = CAN_BYPASS_FILTER(usr) ? null : is_ic_filtered(input) + if(filter_result) + REPORT_CHAT_FILTER_TO_USER(usr, filter_result) + return + + var/list/soft_filter_result = CAN_BYPASS_FILTER(usr) ? null : is_soft_ic_filtered(input) + if(soft_filter_result) + if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes") + return + message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[html_encode(input)]\"") + log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term. Message: \"[input]\"") + commune(usr, input) + +/datum/action/cooldown/bloodling_hivespeak/proc/commune(mob/living/user, message) + var/title = "Thrall" + var/span = "noticealien" + if(!message) + return + if(user.mind && IS_BLOODLING(user)) + span = "alertalien" + title = "Bloodling the" + + var/my_message = "Hivespeak:
[title] [findtextEx(user.name, user.real_name) ? user.name : "[user.real_name] (as [user.name])"]:
[message]
" + for(var/player in GLOB.player_list) + var/mob/reciever = player + if(IS_BLOODLING_OR_THRALL(reciever)) + to_chat(reciever, my_message) + else if(reciever in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(reciever, user) + to_chat(reciever, "[link] [my_message]") + + user.log_talk(message, LOG_SAY, tag="bloodling_speak") diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/build.dm b/monkestation/code/modules/antagonists/bloodling/abilities/build.dm new file mode 100644 index 000000000000..13af0b5b7f1c --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/build.dm @@ -0,0 +1,38 @@ +/datum/action/cooldown/bloodling/build + name = "Mold Flesh" + desc = "Use your biomass to forge creatures or structures." + button_icon_state = "build" + biomass_cost = 30 + /// A list of all structures we can make. + var/static/list/structures = list( + "rat warren" = /obj/structure/bloodling/rat_warren, + "harvester" = /mob/living/basic/bloodling/minion/harvester, + "wall of flesh" = /mob/living/basic/bloodling/minion/wall, + ) + +// Snowflake to check for what we build +/datum/action/cooldown/bloodling/build/proc/check_for_duplicate() + for(var/blocker_name in structures) + blocker_name = structures[blocker_name] + if(locate(blocker_name) in get_turf(src)) + to_chat(owner, span_warning("There is already shaped flesh here!")) + return FALSE + + return TRUE + +/datum/action/cooldown/bloodling/build/Activate(atom/target) + var/choice = tgui_input_list(owner, "Select a shape to mold", "Flesh Construction", structures) + if(isnull(choice) || QDELETED(src) || QDELETED(owner) || !check_for_duplicate() || !IsAvailable(feedback = TRUE)) + return FALSE + + var/atom/choice_path = structures[choice] + if(!ispath(choice_path)) + return FALSE + + owner.visible_message( + span_notice("[owner] vomits up a torrent of flesh and begins to shape it."), + span_notice("You mold a [choice] out of your flesh."), + ) + + new choice_path(get_turf(owner)) + return TRUE diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/devour.dm b/monkestation/code/modules/antagonists/bloodling/abilities/devour.dm new file mode 100644 index 000000000000..04d1b85940ed --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/devour.dm @@ -0,0 +1,48 @@ +/datum/action/cooldown/mob_cooldown/bloodling/devour + name = "Devour Limb" + desc = "Allows you to consume a creatures limb." + button_icon_state = "devour" + cooldown_time = 20 SECONDS + +/datum/action/cooldown/mob_cooldown/bloodling/devour/PreActivate(atom/target) + var/mob/living/mob = target + if(!iscarbon(mob)) + owner.balloon_alert(owner, "only works on carbons!") + return FALSE + return ..() + +/datum/action/cooldown/mob_cooldown/bloodling/devour/Activate(atom/target) + StartCooldown() + var/mob/living/basic/bloodling/our_mob = owner + var/list/candidate_for_removal = list() + var/mob/living/carbon/carbon_target = target + + // Loops over the limbs of our target carbon, this is so stuff like the head, chest or unremovable body parts arent destroyed + for(var/obj/item/bodypart/bodypart in carbon_target.bodyparts) + if(bodypart.body_zone == BODY_ZONE_HEAD) + continue + if(bodypart.body_zone == BODY_ZONE_CHEST) + continue + if(bodypart.bodypart_flags & BODYPART_UNREMOVABLE) + continue + candidate_for_removal += bodypart.body_zone + + if(!length(candidate_for_removal)) + return FALSE + + var/limb_to_remove = pick(candidate_for_removal) + var/obj/item/bodypart/target_part = carbon_target.get_bodypart(limb_to_remove) + + if(isnull(target_part)) + return FALSE + + target_part.dismember() + qdel(target_part) + our_mob.add_biomass(5) + + our_mob.visible_message( + span_alertalien("[our_mob] snaps its maw over [target]s [target_part] and swiftly devours it!"), + span_noticealien("You devour [target]s [target_part]!"), + ) + playsound(our_mob, 'sound/items/eatfood.ogg', 20) + return TRUE diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/dissonant_shriek.dm b/monkestation/code/modules/antagonists/bloodling/abilities/dissonant_shriek.dm new file mode 100644 index 000000000000..e8d4494726b8 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/dissonant_shriek.dm @@ -0,0 +1,18 @@ +/datum/action/cooldown/bloodling/dissonant_shriek + name = "Dissonant Shriek" + desc = "We release a sound that disrupts nearby electronics. Costs 20 biomass." + button_icon_state = "dissonant_shriek" + biomass_cost = 30 + +/datum/action/cooldown/bloodling/dissonant_shriek/Activate(atom/target) + ..() + if(owner.movement_type & VENTCRAWLING) + owner.balloon_alert(owner, "can't shriek in pipes!") + return FALSE + empulse(get_turf(owner), 2, 5, 1) + for(var/obj/machinery/light/light_break in range(5, usr)) + light_break.on = TRUE + light_break.break_light_tube() + stoplag() + + return TRUE diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/give_life.dm b/monkestation/code/modules/antagonists/bloodling/abilities/give_life.dm new file mode 100644 index 000000000000..70992b154491 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/give_life.dm @@ -0,0 +1,42 @@ +/datum/action/cooldown/mob_cooldown/bloodling/give_life + name = "Give Life" + desc = "Bestow the gift of life onto the ignorant." + button_icon_state = "give_life" + +/datum/action/cooldown/mob_cooldown/bloodling/give_life/PreActivate(atom/target) + if(!ismob(target)) + owner.balloon_alert(owner, "only works on mobs!") + return FALSE + + var/mob/living/mob_target = target + if(mob_target.mind && !mob_target.stat == DEAD) + owner.balloon_alert(owner, "only works on non-sentient alive mobs!") + return FALSE + + if(iscarbon(mob_target)) + owner.balloon_alert(owner, "doesn't work on carbons!") + return FALSE + return ..() + +/datum/action/cooldown/mob_cooldown/bloodling/give_life/Activate(atom/target) + var/mob/living/target_mob = target + + var/list/candidates = SSpolling.poll_ghost_candidates( + "Would you like to be a [target_mob] servant of [owner]?", + ROLE_BLOODLING_THRALL, + ROLE_BLOODLING_THRALL, + 10 SECONDS, + target_mob, + POLL_IGNORE_SHUTTLE_DENIZENS, + alert_pic = target_mob + ) + if(!LAZYLEN(candidates)) + owner.balloon_alert(owner, "[target_mob] rejects your generous gift...for now...") + return FALSE + target_mob.ghostize(FALSE) + var/mob/dead/observer/candie = pick_n_take(candidates) + message_admins("[key_name_admin(candie)] has taken control of ([key_name_admin(target_mob)])") + target_mob.key = candie.key + target_mob.mind.add_antag_datum(/datum/antagonist/changeling/bloodling_thrall) + return TRUE + diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/heal.dm b/monkestation/code/modules/antagonists/bloodling/abilities/heal.dm new file mode 100644 index 000000000000..407bd3b34fba --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/heal.dm @@ -0,0 +1,37 @@ +/datum/action/cooldown/mob_cooldown/bloodling/heal + name = "Heal" + desc = "Allows you to heal or revive a humanoid thrall. Costs 50 biomass." + button_icon_state = "mend" + biomass_cost = 50 + +/datum/action/cooldown/mob_cooldown/bloodling/heal/PreActivate(atom/target) + if(!ismob(target)) + return FALSE + + var/mob/living/targetted_mob = target + if(!iscarbon(targetted_mob)) + return FALSE + + if(!IS_BLOODLING_THRALL(targetted_mob)) + return FALSE + return ..() + +/datum/action/cooldown/mob_cooldown/bloodling/heal/Activate(atom/target) + var/mob/living/carbon/carbon_mob = target + if(!do_after(owner, 2 SECONDS)) + return FALSE + + // A bit of everything healing not much but helpful + carbon_mob.adjustBruteLoss(-40) + carbon_mob.adjustToxLoss(-40) + carbon_mob.adjustFireLoss(-40) + carbon_mob.adjustOxyLoss(-40) + + if(carbon_mob.stat != DEAD) + return TRUE + + carbon_mob.revive() + // Any oxygen damage they suffered whilst in crit + carbon_mob.adjustOxyLoss(-200) + return TRUE + diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/hide.dm b/monkestation/code/modules/antagonists/bloodling/abilities/hide.dm new file mode 100644 index 000000000000..a4b5ed4f8d5f --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/hide.dm @@ -0,0 +1,7 @@ +/datum/action/cooldown/sneak/bloodling + name = "Hide" + panel = "alien" + desc = "Blend into the shadows to stalk your prey." + button_icon_state = "alien_sneak" + background_icon = 'monkestation/icons/mob/actions/backgrounds.dmi' + background_icon_state = "bg_bloodling" diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/infect.dm b/monkestation/code/modules/antagonists/bloodling/abilities/infect.dm new file mode 100644 index 000000000000..7c7010c8c2da --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/infect.dm @@ -0,0 +1,71 @@ +/datum/action/cooldown/bloodling_infect + name = "Infect" + desc = "Allows us to make someone our thrall, this consumes our host body and reveals our true form." + button_icon_state = "infest" + ///if we're currently infecting + var/is_infecting = FALSE + +/datum/action/cooldown/bloodling_infect/Activate(atom/target) + if(is_infecting) + owner.balloon_alert(owner, "already infecting!") + return + + if(!owner.pulling) + owner.balloon_alert(owner, "needs grab!") + return + + if(!iscarbon(owner.pulling)) + owner.balloon_alert(owner, "not a humanoid!") + return + + + if(owner.grab_state <= GRAB_NECK) + owner.balloon_alert(owner, "needs tighter grip!") + return + + is_infecting = TRUE + var/mob/living/old_body = owner + + var/mob/living/carbon/human/carbon_mob = owner.pulling + + + var/infest_time = 10 SECONDS + + if(HAS_TRAIT(carbon_mob, TRAIT_MINDSHIELD)) + infest_time *= 2 + + if(!do_after(owner, infest_time)) + return FALSE + + if(carbon_mob.stat == DEAD) + // This cures limbs and anything, the target is made a changeling through this process anyhow + carbon_mob.revive(ADMIN_HEAL_ALL) + + if(!carbon_mob.mind) + var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates( + "Would you like to be a [carbon_mob] servant of [owner]?", + ROLE_BLOODLING_THRALL, + ROLE_BLOODLING_THRALL, + 10 SECONDS, + carbon_mob, + POLL_IGNORE_SHUTTLE_DENIZENS, + alert_pic = carbon_mob + ) + + if(!LAZYLEN(candidates)) + return FALSE + + var/mob/dead/observer/chosen = pick(candidates) + carbon_mob.key = chosen.key + + var/datum/antagonist/changeling/bloodling_thrall/thrall = carbon_mob.mind.add_antag_datum(/datum/antagonist/changeling/bloodling_thrall) + thrall.set_master(owner) + + var/mob/living/basic/bloodling/proper/tier1/bloodling = new /mob/living/basic/bloodling/proper/tier1/(old_body.loc) + owner.mind.transfer_to(bloodling) + old_body.gib() + var/datum/antagonist/bloodling_datum = IS_BLOODLING(bloodling) + for(var/datum/objective/objective in bloodling_datum.objectives) + objective.update_explanation_text() + playsound(get_turf(bloodling), 'sound/ambience/antag/blobalert.ogg', 50, FALSE) + qdel(src) diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/infest.dm b/monkestation/code/modules/antagonists/bloodling/abilities/infest.dm new file mode 100644 index 000000000000..d559b8eb6731 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/infest.dm @@ -0,0 +1,49 @@ +/datum/action/cooldown/mob_cooldown/bloodling/infest + name = "Infest" + desc = "Allows you to infest a living creature, turning them into a thrall. Can be used on mindshielded people but it takes longer. Costs 75 biomass." + button_icon_state = "infest" + biomass_cost = 75 + +/datum/action/cooldown/mob_cooldown/bloodling/infest/PreActivate(atom/target) + if(!ismob(target)) + owner.balloon_alert(owner, "doesn't work on non-mobs!") + return FALSE + + var/mob/living/alive_mob = target + if(isnull(alive_mob.mind)) + owner.balloon_alert(owner, "doesn't work on mindless mobs!") + return FALSE + + if(IS_BLOODLING_OR_THRALL(alive_mob)) + return FALSE + + if(alive_mob.stat == DEAD) + owner.balloon_alert(owner, "doesn't work on dead mobs!") + return FALSE + return ..() + +/datum/action/cooldown/mob_cooldown/bloodling/infest/Activate(atom/target) + var/mob/living/mob = target + var/infest_time = 30 SECONDS + + // If they are standing on the ascended bloodling tiles it takes 1/3rd of the time to infest them + if(isturf(get_turf(mob), /turf/open/misc/bloodling)) + infest_time = 10 SECONDS + + if(iscarbon(mob)) + var/mob/living/carbon/human/carbon_mob = target + infest_time *= 2 + + if(HAS_TRAIT(carbon_mob, TRAIT_MINDSHIELD)) + infest_time *= 4 + + if(!do_after(owner, infest_time)) + return FALSE + var/datum/antagonist/changeling/bloodling_thrall/thrall = carbon_mob.mind.add_antag_datum(/datum/antagonist/changeling/bloodling_thrall) + thrall.set_master(owner) + else + if(!do_after(owner, infest_time)) + return FALSE + var/datum/antagonist/infested_thrall/thrall = mob.mind.add_antag_datum(/datum/antagonist/infested_thrall) + thrall.set_master(owner) + return TRUE diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/transfer_biomass.dm b/monkestation/code/modules/antagonists/bloodling/abilities/transfer_biomass.dm new file mode 100644 index 000000000000..7b4e7f88c5ad --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/transfer_biomass.dm @@ -0,0 +1,26 @@ +/datum/action/cooldown/mob_cooldown/bloodling/transfer_biomass + name = "Transfer Biomass" + desc = "Transfer biomass to another organism." + button_icon_state = "transfer" + +/datum/action/cooldown/mob_cooldown/bloodling/transfer_biomass/PreActivate(atom/target) + var/mob/living/mob = target + if(!istype(mob, /mob/living/basic/bloodling)) + owner.balloon_alert(owner, "only works on bloodlings!") + return FALSE + return ..() + +/datum/action/cooldown/mob_cooldown/bloodling/transfer_biomass/Activate(atom/target) + var/mob/living/basic/bloodling/our_mob = owner + var/mob/living/basic/bloodling/donation_target = target + + var/amount = tgui_input_number(our_mob, "Amount", "Transfer Biomass to [donation_target]", max_value = our_mob.biomass) + if(QDELETED(donation_target) || QDELETED(src) || QDELETED(our_mob) || !IsAvailable(feedback = TRUE) || isnull(amount) || amount <= 0) + return FALSE + + donation_target.add_biomass(amount) + our_mob.add_biomass(-amount) + + to_chat(donation_target, span_noticealien("[our_mob] has transferred [amount] biomass to you.")) + to_chat(our_mob, span_noticealien("You transfer [amount] biomass to [donation_target].")) + return TRUE diff --git a/monkestation/code/modules/antagonists/bloodling/abilities/whiplash.dm b/monkestation/code/modules/antagonists/bloodling/abilities/whiplash.dm new file mode 100644 index 000000000000..0663aa50d0ce --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/abilities/whiplash.dm @@ -0,0 +1,53 @@ +/datum/action/cooldown/spell/aoe/repulse/bloodling + name = "Whiplash" + desc = "Grow whiplike appendages and throw back nearby attackers." + background_icon = 'monkestation/icons/mob/actions/backgrounds.dmi' + background_icon_state = "bg_bloodling" + button_icon = 'monkestation/code/modules/antagonists/bloodling/sprites/bloodling_abilities.dmi' + button_icon_state = "whiplash" + sound = 'sound/magic/tail_swing.ogg' + + spell_requirements = NONE + + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + invocation_type = INVOCATION_NONE + antimagic_flags = NONE + aoe_radius = 2 + + sparkle_path = /obj/effect/temp_visual/bloodling_tentacle + + /// Since this isn't a bloodling subtype ability we need to recode the cost here + var/biomass_cost = 25 + +/obj/effect/temp_visual/bloodling_tentacle + icon = 'monkestation/code/modules/antagonists/bloodling/sprites/bloodling_sprites.dmi' + icon_state = "tentacle_effect" + duration = 4 + +/datum/action/cooldown/spell/aoe/repulse/bloodling/IsAvailable(feedback = FALSE) + . = ..() + if(!.) + return FALSE + // Basically we only want bloodlings to have this + if(!istype(owner, /mob/living/basic/bloodling)) + return FALSE + var/mob/living/basic/bloodling/our_mob = owner + if(our_mob.biomass <= biomass_cost) + return FALSE + return TRUE + +/datum/action/cooldown/spell/aoe/repulse/bloodling/PreActivate(atom/target) + var/mob/living/basic/bloodling/our_mob = owner + // Parent calls Activate(), so if parent returns TRUE, + // it means the activation happened successfuly by this point + . = ..() + if(!.) + return FALSE + // Since bloodlings evolve it may result in them or their abilities going away + // so we can just return true here + if(QDELETED(src) || QDELETED(owner)) + return TRUE + + our_mob.add_biomass(-biomass_cost) + + return TRUE diff --git a/monkestation/code/modules/antagonists/bloodling/bloodling.dm b/monkestation/code/modules/antagonists/bloodling/bloodling.dm new file mode 100644 index 000000000000..a09d25851e40 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/bloodling.dm @@ -0,0 +1,29 @@ +/datum/antagonist/bloodling + name = "\improper Bloodling" + roundend_category = "Bloodlings" + antagpanel_category = ANTAG_GROUP_BLOODLING + job_rank = ROLE_BLOODLING + antag_moodlet = /datum/mood_event/focused + antag_hud_name = "bloodling" + hijack_speed = 0.5 + suicide_cry = "CONSUME!! CLAIM!! THERE WILL BE ANOTHER!!" + show_name_in_check_antagonists = TRUE + + // If this bloodling is ascended or not + var/is_ascended = FALSE + +/datum/antagonist/bloodling/on_gain() + forge_objectives() + owner.current.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue. We are a horrific blob of flesh who can manifest a million tongues. + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ling_alert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) + // The midround version of this antag begins as a bloodling, not as a human + if(!ishuman(owner.current)) + return ..() + var/datum/action/cooldown/bloodling_infect/infect = new /datum/action/cooldown/bloodling_infect() + infect.Grant(owner.current) + return ..() + +/datum/antagonist/bloodling/forge_objectives() + var/datum/objective/bloodling_ascend/ascend_objective = new + ascend_objective.owner = owner + objectives += ascend_objective diff --git a/monkestation/code/modules/antagonists/bloodling/bloodling_hud.dm b/monkestation/code/modules/antagonists/bloodling/bloodling_hud.dm new file mode 100644 index 000000000000..396129bdf7bb --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/bloodling_hud.dm @@ -0,0 +1,25 @@ + +/datum/hud/bloodling/New(mob/living/owner) + . = ..() + var/atom/movable/screen/using + + action_intent = new /atom/movable/screen/combattoggle/flashy() + action_intent.hud = src + action_intent.icon = ui_style + action_intent.screen_loc = ui_combat_toggle + static_inventory += action_intent + + using = new /atom/movable/screen/language_menu() + using.icon = ui_style + using.hud = src + using.update_appearance() + static_inventory += using + + using = new /atom/movable/screen/navigate + using.screen_loc = ui_alien_navigate_menu + using.hud = src + static_inventory += using + + healthdoll = new /atom/movable/screen/healthdoll/living() + healthdoll.hud = src + infodisplay += healthdoll diff --git a/monkestation/code/modules/antagonists/bloodling/bloodling_structures.dm b/monkestation/code/modules/antagonists/bloodling/bloodling_structures.dm new file mode 100644 index 000000000000..a46270094027 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/bloodling_structures.dm @@ -0,0 +1,59 @@ +/obj/structure/bloodling + name = "Abstract bloodling structure" + max_integrity = 100 + icon = 'monkestation/code/modules/antagonists/bloodling/sprites/bloodling_sprites.dmi' + +/obj/structure/bloodling/run_atom_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == MELEE) + switch(damage_type) + if(BRUTE) + damage_amount *= 0.5 + if(BURN) + damage_amount *= 3 + return ..() + +/obj/structure/bloodling/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(loc, 'sound/effects/attackblob.ogg', 100, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + if(damage_amount) + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/bloodling/rat_warren + name = "Rat warren" + desc = "A pool of biomass and primordial soup, you hear a faint chittering from it." + max_integrity = 100 + icon_state = "ratwarren" + ///the minimum time it takes for a rat to spawn + var/minimum_rattime = 1 MINUTES + ///the maximum time it takes for a rat to spawn + var/maximum_rattime = 3 MINUTES + //the cooldown between each rat + COOLDOWN_DECLARE(rattime) + +/obj/structure/bloodling/rat_warren/Initialize(mapload) + . = ..() + + //start the cooldown + COOLDOWN_START(src, rattime, rand(minimum_rattime, maximum_rattime)) + + //start processing + START_PROCESSING(SSobj, src) + +/obj/structure/bloodling/rat_warren/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/structure/bloodling/rat_warren/process() + //we need to have a cooldown, so check and then add + if(!COOLDOWN_FINISHED(src, rattime)) + return + COOLDOWN_START(src, rattime, rand(minimum_rattime, maximum_rattime)) + + var/turf/our_turf = src.loc + new /mob/living/basic/mouse(our_turf) + our_turf.add_liquid_list(list(/datum/reagent/toxin/mutagen = 15), TRUE) diff --git a/monkestation/code/modules/antagonists/bloodling/infested_thrall.dm b/monkestation/code/modules/antagonists/bloodling/infested_thrall.dm new file mode 100644 index 000000000000..f703633749fc --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/infested_thrall.dm @@ -0,0 +1,75 @@ +/datum/antagonist/changeling/bloodling_thrall + name = "\improper Changeling Thrall" + roundend_category = "bloodling thralls" + antagpanel_category = ANTAG_GROUP_BLOODLING + job_rank = ROLE_BLOODLING_THRALL + antag_moodlet = /datum/mood_event/focused + antag_hud_name = "infested_thrall" + hijack_speed = 0 + suicide_cry = "FOR THE MASTER!!" + genetic_points = 5 + total_genetic_points = 5 + + // This thralls master + var/master = null + +/datum/antagonist/changeling/bloodling_thrall/purchase_power(datum/action/changeling/sting_path) + if(istype(sting_path, /datum/action/changeling/fakedeath)) + to_chat(owner.current, span_warning("We are unable to evolve that ability")) + return FALSE + return ..() + +/datum/antagonist/changeling/bloodling_thrall/create_innate_actions() + for(var/datum/action/changeling/path as anything in all_powers) + if(initial(path.dna_cost) != 0) + continue + var/datum/action/changeling/innate_ability = new path() + if(istype(innate_ability, /datum/action/changeling/fakedeath)) + continue + innate_powers += innate_ability + innate_ability.on_purchase(owner.current, TRUE) + var/datum/action/cooldown/bloodling_hivespeak/hivetalk = new() + hivetalk.Grant(owner.current) + +/datum/antagonist/changeling/bloodling_thrall/proc/set_master(mob/living/basic/bloodling/master) + to_chat(owner, span_info("Your master is [master], they have granted you this gift. Obey their commands. Praise be the living flesh.")) + src.master = master + +/datum/antagonist/changeling/bloodling_thrall/forge_objectives() + var/datum/objective/bloodling_thrall/serve_objective = new + serve_objective.owner = owner + objectives += serve_objective + +/datum/antagonist/infested_thrall + name = "\improper Infested Thrall" + roundend_category = "bloodling thralls" + antagpanel_category = ANTAG_GROUP_BLOODLING + job_rank = ROLE_BLOODLING_THRALL + antag_moodlet = /datum/mood_event/focused + antag_hud_name = "infested_thrall" + hijack_speed = 0 + suicide_cry = "FOR THE MASTER!!" + + // This thralls master + var/master = null + +/datum/antagonist/infested_thrall/on_gain() + forge_objectives() + owner.current.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue. We are a horrific blob of flesh who can manifest a million tongues. + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ling_alert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) + return ..() + +/datum/antagonist/changeling/bloodling_thrall/create_innate_actions() + var/datum/action/cooldown/bloodling_hivespeak/hivetalk = new() + hivetalk.Grant(owner.current) + +/datum/antagonist/infested_thrall/forge_objectives() + var/datum/objective/bloodling_thrall/serve_objective = new + serve_objective.owner = owner + objectives += serve_objective + if(master) + serve_objective.update_explanation_text() + +/datum/antagonist/infested_thrall/proc/set_master(mob/living/basic/bloodling/master) + to_chat(owner, span_info("Your master is [master], they have granted you this gift. Obey their commands. Praise be the living flesh.")) + src.master = master diff --git a/monkestation/code/modules/antagonists/bloodling/mobs/bloodling_mob.dm b/monkestation/code/modules/antagonists/bloodling/mobs/bloodling_mob.dm new file mode 100644 index 000000000000..cb96f5f8ecb3 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/mobs/bloodling_mob.dm @@ -0,0 +1,297 @@ +/mob/living/basic/bloodling + name = "abstract bloodling" + desc = "A disgusting mass of code and flesh. Report this as an issue if you see it." + icon = 'icons/mob/simple/arachnoid.dmi' + icon_state = "maint_spider" + icon_living = "maint_spider" + icon_dead = "maint_spider_dead" + gender = NEUTER + health = 50 + maxHealth = 50 + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_verb_continuous = "chomps" + attack_verb_simple = "chomp" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + obj_damage = 0 + speed = 2.8 + environment_smash = ENVIRONMENT_SMASH_NONE + mob_biotypes = MOB_ORGANIC + speak_emote = list("spews") + basic_mob_flags = FLAMMABLE_MOB | DEL_ON_DEATH + faction = list(FACTION_BLOODLING) + pass_flags = PASSTABLE + attack_sound = 'sound/effects/attackblob.ogg' + /// Loot this mob drops on death. + var/list/loot = list(/obj/effect/gibspawner/generic) + /// The amount of biomass our bloodling has + var/biomass = 1 + /// The maximum amount of biomass a bloodling can gain + var/biomass_max = 1 + /// The abilities this bloodling starts with + var/list/initial_powers = list( + /datum/action/cooldown/mob_cooldown/bloodling/absorb, + ) + +/mob/living/basic/bloodling/Initialize(mapload) + . = ..() + create_abilities() + AddElement(/datum/element/death_drops, loot) + +/mob/living/basic/bloodling/get_status_tab_items() + . = ..() + . += "Current Biomass: [biomass >= biomass_max ? biomass : "[biomass] / [biomass_max]"] B" + +/// Used for adding biomass to every bloodling type +/// ARGUEMENTS: +/// amount-The amount of biomass to be added or subtracted +/mob/living/basic/bloodling/proc/add_biomass(amount) + if(biomass + amount >= biomass_max) + biomass = biomass_max + balloon_alert(src, "already maximum biomass") + return + + biomass += amount + +/// Creates the bloodlings abilities +/mob/living/basic/bloodling/proc/create_abilities() + for(var/datum/action/path as anything in initial_powers) + var/datum/action/bloodling_action = new path() + bloodling_action.Grant(src) + +//////////////////// The actual bloodling mob //////////////////// +/mob/living/basic/bloodling/proper + name = "mass of flesh" + desc = "An abomination of some spawn. A mess of tendrils, mouths and chitin, whatever created it was not merciful." + maxHealth = INFINITE // Bloodlings have unlimited health, instead biomass acts as their health + health = INFINITE + sight = SEE_SELF|SEE_MOBS + hud_type = /datum/hud/bloodling + + biomass_max = 750 + /// The evolution level our bloodling is on + var/evolution_level = 0 + +/mob/living/basic/bloodling/proper/Initialize(mapload) + . = ..() + + // All evolutions over 2 (3,4,5) are spess proof + if(evolution_level > 2) + ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + +/mob/living/basic/bloodling/proper/adjust_health(amount, updating_health = TRUE, forced = FALSE) + . = amount + + add_biomass(-amount) + + return . + +/mob/living/basic/bloodling/proper/update_health_hud() + . = ..() + var/biomass_next_evo + + switch(evolution_level) + if(1) + biomass_next_evo = 75 + if(2) + biomass_next_evo = 125 + if(3) + biomass_next_evo = 175 + if(4) + biomass_next_evo = 225 + if(5) + biomass_next_evo = biomass_max + + if(hud_used?.action_intent) + hud_used.action_intent.maptext = MAPTEXT("Your biomass: [biomass] / [biomass_next_evo] \n") + hud_used.action_intent.maptext_height = 400 + hud_used.action_intent.maptext_width = 400 + hud_used.action_intent.maptext_y = 64 + hud_used.action_intent.maptext_x = -64 + +// Bloodlings health and damage needs updating when biomass is added +/mob/living/basic/bloodling/proper/add_biomass(amount) + . = ..() + if(biomass <= 0) + gib() + // Damage is based on biomass, and handled here + obj_damage = biomass * 0.2 + // less than 5 damage would be very bad + if(biomass > 50) + melee_damage_lower = biomass * 0.1 + melee_damage_upper = biomass * 0.1 + update_health_hud() + check_evolution() + +/// Checks if we should evolve, and also calls the evolution proc +/mob/living/basic/bloodling/proper/proc/check_evolution() + if((75 > biomass) && (evolution_level != 1)) + evolution(1) + return TRUE + if((125 > biomass) && (biomass >= 75) && (evolution_level != 2)) + evolution(2) + return TRUE + if((175 > biomass) && (biomass >= 125) && (evolution_level != 3)) + evolution(3) + return TRUE + if((225 > biomass) && (biomass >= 175) && (evolution_level != 4)) + evolution(4) + return TRUE + if((biomass >= 225) && (evolution_level != 5)) + evolution(5) + return TRUE + return FALSE + +/// Creates the mob for us to then mindswap into +/mob/living/basic/bloodling/proper/proc/evolution(tier) + /// What bloodling we are going to spawn + var/new_bloodling = null + if(evolution_level > tier) + visible_message( + span_alertalien("[src] begins to shrink!"), + span_noticealien("You devolve!"), + ) + + else + visible_message( + span_alertalien("[src] begins to grow!"), + span_noticealien("You evolve!"), + ) + + switch(tier) + if(1) + new_bloodling = new /mob/living/basic/bloodling/proper/tier1/(src.loc) + if(2) + new_bloodling = new /mob/living/basic/bloodling/proper/tier2(src.loc) + if(3) + new_bloodling = new /mob/living/basic/bloodling/proper/tier3(src.loc) + if(4) + new_bloodling = new /mob/living/basic/bloodling/proper/tier4(src.loc) + if(5) + new_bloodling = new /mob/living/basic/bloodling/proper/tier5(src.loc) + if(6) + new_bloodling = new /mob/living/basic/bloodling/proper/ascending(src.loc) + evolution_mind_change(new_bloodling) + + +/mob/living/basic/bloodling/proper/proc/evolution_mind_change(mob/living/basic/bloodling/proper/new_bloodling) + new_bloodling.setDir(dir) + if(numba) + new_bloodling.numba = numba + new_bloodling.set_name() + new_bloodling.name = name + new_bloodling.real_name = real_name + if(mind) + mind.name = new_bloodling.real_name + mind.transfer_to(new_bloodling) + // Runs = instead of add_biomass because the tier 1 bloodling has 50 biomass to start with + new_bloodling.biomass = biomass + qdel(src) + +/mob/living/basic/bloodling/proper/Destroy() + UnregisterSignal(src, COMSIG_MOB_APPLY_DAMAGE) + + return ..() + +/mob/living/basic/bloodling/proper/tier1 + evolution_level = 1 + initial_powers = list( + /datum/action/cooldown/mob_cooldown/bloodling/absorb, + /datum/action/cooldown/sneak/bloodling, + /datum/action/cooldown/bloodling_hivespeak, + ) + speed = 0.5 + +/mob/living/basic/bloodling/proper/tier1/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + add_biomass(50) + +/mob/living/basic/bloodling/proper/tier2 + icon_state = "guard" + icon_living = "guard" + evolution_level = 2 + initial_powers = list( + /datum/action/cooldown/mob_cooldown/bloodling/absorb, + /datum/action/cooldown/sneak/bloodling, + /datum/action/cooldown/mob_cooldown/bloodling/infest, + /datum/action/cooldown/bloodling/build, + /datum/action/cooldown/bloodling_hivespeak, + ) + speed = 1 + +/mob/living/basic/bloodling/proper/tier3 + icon_state = "scout" + icon_living = "scout" + evolution_level = 3 + initial_powers = list( + /datum/action/cooldown/mob_cooldown/bloodling/absorb, + /datum/action/cooldown/mob_cooldown/bloodling/infest, + /datum/action/cooldown/bloodling/build, + /datum/action/cooldown/mob_cooldown/bloodling/devour, + /datum/action/cooldown/bloodling_hivespeak, + ) + speed = 1.5 + +/mob/living/basic/bloodling/proper/tier4 + icon_state = "ambush" + icon_living = "ambush" + evolution_level = 4 + initial_powers = list( + /datum/action/cooldown/mob_cooldown/bloodling/absorb, + /datum/action/cooldown/mob_cooldown/bloodling/infest, + /datum/action/cooldown/bloodling/build, + /datum/action/cooldown/mob_cooldown/bloodling/devour, + /datum/action/cooldown/bloodling/dissonant_shriek, + /datum/action/cooldown/spell/aoe/repulse/bloodling, + /datum/action/cooldown/mob_cooldown/bloodling/transfer_biomass, + /datum/action/cooldown/mob_cooldown/bloodling/heal, + /datum/action/cooldown/mob_cooldown/bloodling/give_life, + /datum/action/cooldown/bloodling_hivespeak, + ) + speed = 2 + +/mob/living/basic/bloodling/proper/tier5 + icon_state = "hunter" + icon_living = "hunter" + evolution_level = 5 + initial_powers = list( + /datum/action/cooldown/mob_cooldown/bloodling/absorb, + /datum/action/cooldown/bloodling/ascension, + /datum/action/cooldown/mob_cooldown/bloodling/infest, + /datum/action/cooldown/bloodling/build, + /datum/action/cooldown/mob_cooldown/bloodling/devour, + /datum/action/cooldown/bloodling/dissonant_shriek, + /datum/action/cooldown/spell/aoe/repulse/bloodling, + /datum/action/cooldown/mob_cooldown/bloodling/transfer_biomass, + /datum/action/cooldown/mob_cooldown/bloodling/heal, + /datum/action/cooldown/mob_cooldown/bloodling/give_life, + /datum/action/cooldown/bloodling_hivespeak, + ) + speed = 2.5 + +/mob/living/basic/bloodling/proper/ascending + icon = 'icons/mob/simple/meteor_heart.dmi' + icon_state = "heart" + icon_living = "heart" + evolution_level = 6 + initial_powers = list( + /datum/action/cooldown/mob_cooldown/bloodling/absorb, + /datum/action/cooldown/bloodling/ascension, + /datum/action/cooldown/mob_cooldown/bloodling/infest, + /datum/action/cooldown/bloodling/build, + /datum/action/cooldown/mob_cooldown/bloodling/devour, + /datum/action/cooldown/bloodling/dissonant_shriek, + /datum/action/cooldown/spell/aoe/repulse/bloodling, + /datum/action/cooldown/mob_cooldown/bloodling/transfer_biomass, + /datum/action/cooldown/mob_cooldown/bloodling/heal, + /datum/action/cooldown/mob_cooldown/bloodling/give_life, + /datum/action/cooldown/bloodling_hivespeak, + ) + speed = 0 + move_resist = INFINITY + +/mob/living/basic/bloodling/proper/ascending/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_IMMOBILIZED, REF(src)) diff --git a/monkestation/code/modules/antagonists/bloodling/mobs/minions.dm b/monkestation/code/modules/antagonists/bloodling/mobs/minions.dm new file mode 100644 index 000000000000..d904b016f8a8 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/mobs/minions.dm @@ -0,0 +1,45 @@ +/mob/living/basic/bloodling/minion + name = "minion" + desc = "A mass of code in a vague sprite. Report if you see this." + + icon = 'monkestation/code/modules/antagonists/bloodling/sprites/bloodling_sprites.dmi' + biomass = 0 + biomass_max = 200 + damage_coeff = list(BRUTE = 1, BURN = 1.25, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1) + initial_powers = list( + /datum/action/cooldown/mob_cooldown/bloodling/absorb, + /datum/action/cooldown/mob_cooldown/bloodling/devour, + /datum/action/cooldown/spell/aoe/repulse/bloodling, + /datum/action/cooldown/mob_cooldown/bloodling/transfer_biomass, + /datum/action/cooldown/bloodling_hivespeak, + ) + +/mob/living/basic/bloodling/minion/harvester + name = "harvester" + desc = "A mass of flesh with two large scything talons." + + health = 100 + maxHealth = 100 + melee_damage_lower = 15 + melee_damage_upper = 15 + speed = 0.5 + wound_bonus = -40 + bare_wound_bonus = 5 + sharpness = SHARP_EDGED + +/mob/living/basic/bloodling/minion/harvester/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + +/mob/living/basic/bloodling/minion/wall + name = "wall of flesh" + desc = "A blobby mass of flesh of large size." + + icon_state = "tank" + icon_living = "tank" + icon_dead = "tank_dead" + health = 200 + maxHealth = 200 + melee_damage_lower = 10 + melee_damage_upper = 10 + speed = 2.5 diff --git a/monkestation/code/modules/antagonists/bloodling/objectives.dm b/monkestation/code/modules/antagonists/bloodling/objectives.dm new file mode 100644 index 000000000000..1124cee4da75 --- /dev/null +++ b/monkestation/code/modules/antagonists/bloodling/objectives.dm @@ -0,0 +1,29 @@ +/datum/objective/bloodling_ascend + name = "ascend" + martyr_compatible = TRUE + admin_grantable = FALSE + explanation_text = "Use the infect ability on a human you are strangling to burst as a bloodling and begin your ascension!" + +/datum/objective/bloodling_ascend/update_explanation_text() + ..() + explanation_text = "Ascend as the ultimate being" + +/datum/objective/bloodling_ascend/check_completion() + var/datum/antagonist/bloodling/bloodling = IS_BLOODLING(owner.current) + if (!bloodling.is_ascended) + return FALSE + return TRUE + +/datum/objective/bloodling_thrall + name = "serve" + martyr_compatible = TRUE + admin_grantable = FALSE + explanation_text = "Serve your master!" + +/datum/objective/bloodling_thrall/update_explanation_text() + ..() + var/datum/antagonist/infested_thrall/our_owner = owner + if(our_owner.master) + explanation_text = "Serve your master [our_owner.master]!" + else + explanation_text = "Serve your master!" diff --git a/monkestation/code/modules/antagonists/bloodling/sprites/bloodling_abilities.dmi b/monkestation/code/modules/antagonists/bloodling/sprites/bloodling_abilities.dmi new file mode 100644 index 000000000000..edb0d0d85725 Binary files /dev/null and b/monkestation/code/modules/antagonists/bloodling/sprites/bloodling_abilities.dmi differ diff --git a/monkestation/code/modules/antagonists/bloodling/sprites/bloodling_sprites.dmi b/monkestation/code/modules/antagonists/bloodling/sprites/bloodling_sprites.dmi new file mode 100644 index 000000000000..170a71634fbd Binary files /dev/null and b/monkestation/code/modules/antagonists/bloodling/sprites/bloodling_sprites.dmi differ diff --git a/monkestation/code/modules/antagonists/bloodling/sprites/flesh_tile.dmi b/monkestation/code/modules/antagonists/bloodling/sprites/flesh_tile.dmi new file mode 100644 index 000000000000..669a60633e0e Binary files /dev/null and b/monkestation/code/modules/antagonists/bloodling/sprites/flesh_tile.dmi differ diff --git a/monkestation/code/modules/storytellers/converted_events/solo/bloodling.dm b/monkestation/code/modules/storytellers/converted_events/solo/bloodling.dm new file mode 100644 index 000000000000..539d0f4c32a9 --- /dev/null +++ b/monkestation/code/modules/storytellers/converted_events/solo/bloodling.dm @@ -0,0 +1,39 @@ +/datum/round_event_control/antagonist/solo/bloodling + antag_flag = ROLE_BLOODLING + antag_datum = /datum/antagonist/bloodling + tags = list(TAG_COMBAT, TAG_TEAM_ANTAG) + protected_roles = list( + JOB_CAPTAIN, + JOB_HEAD_OF_PERSONNEL, + JOB_CHIEF_ENGINEER, + JOB_CHIEF_MEDICAL_OFFICER, + JOB_RESEARCH_DIRECTOR, + JOB_DETECTIVE, + JOB_HEAD_OF_SECURITY, + JOB_PRISONER, + JOB_SECURITY_OFFICER, + JOB_SECURITY_ASSISTANT, + JOB_WARDEN, + ) + restricted_roles = list( + JOB_AI, + JOB_CYBORG + ) + enemy_roles = list( + JOB_CAPTAIN, + JOB_HEAD_OF_SECURITY, + JOB_DETECTIVE, + JOB_WARDEN, + JOB_SECURITY_OFFICER, + JOB_SECURITY_ASSISTANT, + ) + required_enemies = 3 + weight = 4 + max_occurrences = 1 + maximum_antags = 2 + denominator = 30 + +/datum/round_event_control/antagonist/solo/bloodling/roundstart + name = "Bloodling" + roundstart = TRUE + earliest_start = 0 SECONDS diff --git a/monkestation/code/modules/storytellers/converted_events/solo/bloodsuckers.dm b/monkestation/code/modules/storytellers/converted_events/solo/bloodsuckers.dm index 30ec0ef49fbf..1f53c64eba42 100644 --- a/monkestation/code/modules/storytellers/converted_events/solo/bloodsuckers.dm +++ b/monkestation/code/modules/storytellers/converted_events/solo/bloodsuckers.dm @@ -1,6 +1,6 @@ /datum/round_event_control/antagonist/solo/bloodsucker antag_flag = ROLE_BLOODSUCKER - tags = list(TAG_COMBAT, TAG_MAGICAL) + tags = list(TAG_COMBAT, TAG_ALIEN) antag_datum = /datum/antagonist/bloodsucker protected_roles = list( JOB_CAPTAIN, diff --git a/monkestation/icons/mob/actions/backgrounds.dmi b/monkestation/icons/mob/actions/backgrounds.dmi index b990ae03b877..e31885ab0753 100644 Binary files a/monkestation/icons/mob/actions/backgrounds.dmi and b/monkestation/icons/mob/actions/backgrounds.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 03b1a9440bdc..738e38f96999 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5796,6 +5796,7 @@ #include "interface\fonts\tiny_unicode.dm" #include "interface\fonts\vcr_osd_mono.dm" #include "monkestation\code\__DEFINES\_module_defines.dm" +#include "monkestation\code\__DEFINES\mobfactions.dm" #include "monkestation\code\__DEFINES\projectile.dm" #include "monkestation\code\__DEFINES\signals.dm" #include "monkestation\code\__HELPERS\_lists.dm" @@ -6034,6 +6035,27 @@ #include "monkestation\code\modules\antagonists\abductor\equipment\glands\trauma.dm" #include "monkestation\code\modules\antagonists\abductor\machinery\dispenser.dm" #include "monkestation\code\modules\antagonists\battlecruiser\battlecruiser.dm" +#include "monkestation\code\modules\antagonists\bloodling\bloodling.dm" +#include "monkestation\code\modules\antagonists\bloodling\bloodling_hud.dm" +#include "monkestation\code\modules\antagonists\bloodling\bloodling_structures.dm" +#include "monkestation\code\modules\antagonists\bloodling\infested_thrall.dm" +#include "monkestation\code\modules\antagonists\bloodling\objectives.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\absorb_biomass.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\ascension.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\bloodling_abilities.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\bloodling_hivespeak.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\build.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\devour.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\dissonant_shriek.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\give_life.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\heal.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\hide.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\infect.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\infest.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\transfer_biomass.dm" +#include "monkestation\code\modules\antagonists\bloodling\abilities\whiplash.dm" +#include "monkestation\code\modules\antagonists\bloodling\mobs\bloodling_mob.dm" +#include "monkestation\code\modules\antagonists\bloodling\mobs\minions.dm" #include "monkestation\code\modules\antagonists\borers\code\cortical_borer_chems.dm" #include "monkestation\code\modules\antagonists\borers\code\focus_datum.dm" #include "monkestation\code\modules\antagonists\borers\code\status_effects.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/bloodling.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/bloodling.ts new file mode 100644 index 000000000000..82bbe1ca3a4e --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/bloodling.ts @@ -0,0 +1,17 @@ +import { Antagonist, Category } from '../base'; +import { multiline } from 'common/string'; + +const Bloodling: Antagonist = { + key: 'bloodling', + name: 'Bloodling', + description: [ + multiline` + You are a horrific abomination of flesh. + Scrape the station free of biomass and evolve to your ultimate form. + Having infested Space Station 13, you will twist it to your whims. + `, + ], + category: Category.Roundstart, +}; + +export default Bloodling;