diff --git a/code/datums/components/mind_linker.dm b/code/datums/components/mind_linker.dm index 2a6c20b2160..2b8d3899173 100644 --- a/code/datums/components/mind_linker.dm +++ b/code/datums/components/mind_linker.dm @@ -10,10 +10,6 @@ var/network_name = "Mind Link" /// The color of the network when talking in chat var/chat_color - /// The message sent to someone when linked up. - var/link_message - /// The message sent to someone when unlinked. - var/unlink_message /// A list of all signals that will call qdel() on our component if triggered. Optional. var/list/signals_which_destroy_us /// A callback invoked after an unlink is done. Optional. @@ -24,25 +20,25 @@ var/speech_action_icon_state = "link_speech" /// The icon background for the speech action handed out. var/speech_action_background_icon_state = "bg_alien" - /// The master's linking action, which allows them to link people to the network. - var/datum/action/linker_action + /// The border icon state for the speech action handed out. + var/speech_action_overlay_state = "bg_alien_border" /// The master's speech action. The owner of the link shouldn't lose this as long as the link remains. - var/datum/action/innate/linked_speech/master_speech + VAR_FINAL/datum/action/innate/linked_speech/master_speech /// An assoc list of [mob/living]s to [datum/action/innate/linked_speech]s. All the mobs that are linked to our network. - var/list/mob/living/linked_mobs = list() + VAR_FINAL/list/mob/living/linked_mobs = list() /datum/component/mind_linker/Initialize( + // Customization related settings network_name = "Mind Link", chat_color = "#008CA2", - linker_action_path, - link_message, - unlink_message, - signals_which_destroy_us, - datum/callback/post_unlink_callback, speech_action_icon = 'icons/mob/actions/actions_slime.dmi', speech_action_icon_state = "link_speech", speech_action_background_icon_state = "bg_alien", - ) + speech_action_overlay_state = "bg_alien_border", + // Optional + signals_which_destroy_us, + datum/callback/post_unlink_callback, +) if(!isliving(parent)) return COMPONENT_INCOMPATIBLE @@ -51,34 +47,22 @@ src.network_name = network_name src.chat_color = chat_color - src.link_message = link_message || "You are now connected to [owner.real_name]'s [network_name]." - src.unlink_message = unlink_message || "You are no longer connected to [owner.real_name]'s [network_name]." + src.speech_action_icon = speech_action_icon + src.speech_action_icon_state = speech_action_icon_state + src.speech_action_background_icon_state = speech_action_background_icon_state if(islist(signals_which_destroy_us)) src.signals_which_destroy_us = signals_which_destroy_us if(post_unlink_callback) src.post_unlink_callback = post_unlink_callback - src.speech_action_icon = speech_action_icon - src.speech_action_icon_state = speech_action_icon_state - src.speech_action_background_icon_state = speech_action_background_icon_state - - if(ispath(linker_action_path)) - linker_action = new linker_action_path(src) - linker_action.Grant(owner) - else - stack_trace("[type] was created without a valid linker_action_path. No one will be able to link to it.") - master_speech = new(src) master_speech.Grant(owner) - to_chat(owner, span_boldnotice("You establish a [network_name], allowing you to link minds to communicate telepathically.")) - /datum/component/mind_linker/Destroy(force, silent) for(var/mob/living/remaining_mob as anything in linked_mobs) unlink_mob(remaining_mob) linked_mobs.Cut() - QDEL_NULL(linker_action) QDEL_NULL(master_speech) QDEL_NULL(post_unlink_callback) return ..() @@ -99,10 +83,6 @@ /datum/component/mind_linker/proc/link_mob(mob/living/to_link) if(QDELETED(to_link) || to_link.stat == DEAD) return FALSE - if(HAS_TRAIT(to_link, TRAIT_MINDSHIELD)) // Mindshield implant - no dice - return FALSE - if(to_link.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) - return FALSE if(linked_mobs[to_link]) return FALSE @@ -110,20 +90,19 @@ if(to_link == owner) return FALSE - to_chat(to_link, span_notice(link_message)) - to_chat(owner, span_notice("You connect [to_link]'s mind to your [network_name].")) - - for(var/mob/living/other_link as anything in linked_mobs) - to_chat(other_link, span_notice("You feel a new presence within [owner.real_name]'s [network_name].")) - var/datum/action/innate/linked_speech/new_link = new(src) new_link.Grant(to_link) linked_mobs[to_link] = new_link - RegisterSignals(to_link, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MINDSHIELD_IMPLANTED), PROC_REF(unlink_mob)) + RegisterSignals(to_link, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING), PROC_REF(sig_unlink_mob)) return TRUE +/datum/component/mind_linker/proc/sig_unlink_mob(mob/living/to_unlink) + SIGNAL_HANDLER + + unlink_mob(to_unlink) + /** * Unlinks [to_unlink] from our network, deleting their speech action * and cleaning up anything involved. @@ -131,25 +110,17 @@ * Also invokes post_unlink_callback, if supplied. */ /datum/component/mind_linker/proc/unlink_mob(mob/living/to_unlink) - SIGNAL_HANDLER - if(!linked_mobs[to_unlink]) - return + return FALSE - to_chat(to_unlink, span_warning(unlink_message)) post_unlink_callback?.Invoke(to_unlink) - UnregisterSignal(to_unlink, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MINDSHIELD_IMPLANTED)) + UnregisterSignal(to_unlink, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING)) var/datum/action/innate/linked_speech/old_link = linked_mobs[to_unlink] linked_mobs -= to_unlink qdel(old_link) - - var/mob/living/owner = parent - - to_chat(owner, span_warning("You feel someone disconnect from your [network_name].")) - for(var/mob/living/other_link as anything in linked_mobs) - to_chat(other_link, span_warning("You feel a pressence disappear from [owner.real_name]'s [network_name].")) + return TRUE /** * Signal proc sent from any signals given to us initialize. @@ -164,6 +135,84 @@ qdel(src) +/// Subtype of mind linker (I know) which is more active rather than passive, +/// which involves the master linking people manually rather than people being added automatically. +/datum/component/mind_linker/active_linking + /// The message sent to someone when linked up. + var/link_message + /// The message sent to someone when unlinked. + var/unlink_message + /// The master's linking action, which allows them to link people to the network. + VAR_FINAL/datum/action/linker_action + +/datum/component/mind_linker/active_linking/Initialize( + // Customization related settings + network_name = "Mind Link", + chat_color = "#008CA2", + speech_action_icon = 'icons/mob/actions/actions_slime.dmi', + speech_action_icon_state = "link_speech", + speech_action_background_icon_state = "bg_alien", + speech_action_overlay_state = "bg_alien_border", + // Optional + signals_which_destroy_us, + datum/callback/post_unlink_callback, + // Optional for this subtype + link_message, + unlink_message, + // Required for this subtype + linker_action_path, +) + + . = ..() + if(. == COMPONENT_INCOMPATIBLE) + return + + var/mob/living/owner = parent + src.link_message = link_message || "You are now connected to [owner.real_name]'s [network_name]." + src.unlink_message = unlink_message || "You are no longer connected to [owner.real_name]'s [network_name]." + + if(ispath(linker_action_path)) + linker_action = new linker_action_path(src) + linker_action.Grant(owner) + else + stack_trace("[type] was created without a valid linker_action_path. No one will be able to link to it.") + + to_chat(owner, span_boldnotice("You establish a [network_name], allowing you to link minds to communicate telepathically.")) + +/datum/component/mind_linker/active_linking/Destroy() + QDEL_NULL(linker_action) + return ..() + +/datum/component/mind_linker/active_linking/link_mob(mob/living/to_link) + if(HAS_TRAIT(to_link, TRAIT_MINDSHIELD)) // Mindshield implant - no dice + return FALSE + if(to_link.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) + return FALSE + + . = ..() + if(!.) + return + + RegisterSignal(to_link, COMSIG_MINDSHIELD_IMPLANTED, PROC_REF(sig_unlink_mob)) + var/mob/living/owner = parent + to_chat(to_link, span_notice(link_message)) + to_chat(owner, span_notice("You connect [to_link]'s mind to your [network_name].")) + for(var/mob/living/other_link as anything in linked_mobs) + to_chat(other_link, span_notice("You feel a new presence within [owner.real_name]'s [network_name].")) + +/datum/component/mind_linker/active_linking/unlink_mob(mob/living/to_unlink) + . = ..() + if(!.) + return + + UnregisterSignal(to_unlink, COMSIG_MINDSHIELD_IMPLANTED) + var/mob/living/owner = parent + to_chat(to_unlink, span_warning(unlink_message)) + to_chat(owner, span_warning("You feel someone disconnect from your [network_name].")) + for(var/mob/living/other_link as anything in linked_mobs) + to_chat(other_link, span_warning("You feel a pressence disappear from [owner.real_name]'s [network_name].")) + +// Used in mind linker to talk to everyone in the network. /datum/action/innate/linked_speech name = "Mind Link Speech" desc = "Send a psychic message to everyone connected to your Link." diff --git a/code/datums/proximity_monitor/fields/gravity.dm b/code/datums/proximity_monitor/fields/gravity.dm index 181e74157cd..0f6e8ba9d55 100644 --- a/code/datums/proximity_monitor/fields/gravity.dm +++ b/code/datums/proximity_monitor/fields/gravity.dm @@ -1,3 +1,4 @@ +// Proximity monitor applies forced gravity to all turfs in range. /datum/proximity_monitor/advanced/gravity edge_is_a_field = TRUE var/gravity_value = 0 @@ -10,9 +11,10 @@ /datum/proximity_monitor/advanced/gravity/setup_field_turf(turf/target) . = ..() - if (isnull(modified_turfs[target])) + if(!isnull(modified_turfs[target])) + return + if(HAS_TRAIT(target, TRAIT_FORCED_GRAVITY)) return - target.AddElement(/datum/element/forced_gravity, gravity_value) modified_turfs[target] = gravity_value @@ -20,5 +22,44 @@ . = ..() if(isnull(modified_turfs[target])) return - target.RemoveElement(/datum/element/forced_gravity, modified_turfs[target]) + var/grav_value = modified_turfs[target] || 0 + target.RemoveElement(/datum/element/forced_gravity, grav_value) modified_turfs -= target + +// Subtype which pops up a balloon alert when a mob enters the field +/datum/proximity_monitor/advanced/gravity/warns_on_entrance + /// This is a list of mob refs that have recently entered the field. + /// We track it so that we don't spam a player who is stutter stepping in and out with balloon alerts. + var/list/recently_warned + +/datum/proximity_monitor/advanced/gravity/warns_on_entrance/setup_field_turf(turf/target) + . = ..() + for(var/mob/living/guy in target) + warn_mob(guy, target) + +/datum/proximity_monitor/advanced/gravity/warns_on_entrance/cleanup_field_turf(turf/target) + . = ..() + for(var/mob/living/guy in target) + warn_mob(guy, target) + +/datum/proximity_monitor/advanced/gravity/warns_on_entrance/field_edge_crossed(atom/movable/movable, turf/location) + . = ..() + if(isliving(movable)) + warn_mob(movable, location) + +/datum/proximity_monitor/advanced/gravity/warns_on_entrance/field_edge_uncrossed(atom/movable/movable, turf/location) + . = ..() + if(isliving(movable)) + warn_mob(movable, location) + +/datum/proximity_monitor/advanced/gravity/warns_on_entrance/proc/warn_mob(mob/living/to_warn, turf/location) + var/mob_ref_key = REF(to_warn) + if(mob_ref_key in recently_warned) + return + + location.balloon_alert(to_warn, "gravity [(location in modified_turfs) ? "shifts!" : "reverts..."]") + LAZYADD(recently_warned, mob_ref_key) + addtimer(CALLBACK(src, PROC_REF(clear_recent_warning), mob_ref_key), 3 SECONDS) + +/datum/proximity_monitor/advanced/gravity/warns_on_entrance/proc/clear_recent_warning(mob_ref_key) + LAZYREMOVE(recently_warned, mob_ref_key) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 4321b6f83dd..bf500dc75f2 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -561,8 +561,6 @@ var/mob/living/simple_animal/hostile/space_dragon/S = new (pick(spawn_locs)) player_mind.transfer_to(S) - player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_dragon)) - player_mind.special_role = ROLE_SPACE_DRAGON player_mind.add_antag_datum(/datum/antagonist/space_dragon) playsound(S, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) diff --git a/code/modules/antagonists/space_dragon/carp_rift.dm b/code/modules/antagonists/space_dragon/carp_rift.dm index 4d86dab3343..e87ceb3f071 100644 --- a/code/modules/antagonists/space_dragon/carp_rift.dm +++ b/code/modules/antagonists/space_dragon/carp_rift.dm @@ -18,13 +18,13 @@ if(!dragon) return var/area/rift_location = get_area(owner) - if(!(rift_location.area_flags & VALID_TERRITORY)) - to_chat(owner, span_warning("You can't summon a rift here! Try summoning somewhere secure within the station!")) + if(!(rift_location in dragon.chosen_rift_areas)) + owner.balloon_alert(owner, "can't summon a rift here!") return for(var/obj/structure/carp_rift/rift as anything in dragon.rift_list) var/area/used_location = get_area(rift) if(used_location == rift_location) - to_chat(owner, span_warning("You've already summoned a rift in this area! You have to summon again somewhere else!")) + owner.balloon_alert(owner, "already summoned a rift here!") return var/turf/rift_spawn_turf = get_turf(dragon) if(isopenspaceturf(rift_spawn_turf)) @@ -64,7 +64,7 @@ light_color = LIGHT_COLOR_PURPLE light_range = 10 anchored = TRUE - density = FALSE + density = TRUE plane = MASSIVE_OBJ_PLANE /// The amount of time the rift has charged for. var/time_charged = 0 @@ -77,11 +77,13 @@ /// Current charge state of the rift. var/charge_state = CHARGE_ONGOING /// The interval for adding additional space carp spawns to the rift. - var/carp_interval = 60 + var/carp_interval = 45 /// The time since an extra carp was added to the ghost role spawning pool. var/last_carp_inc = 0 /// A list of all the ckeys which have used this carp rift to spawn in as carps. var/list/ckey_list = list() + /// Gravity aura for the rift, makes all turfs nearby forced grav. + var/datum/proximity_monitor/advanced/gravity/warns_on_entrance/gravity_aura /datum/armor/structure_carp_rift energy = 100 @@ -98,14 +100,25 @@ AddComponent( \ /datum/component/aura_healing, \ - range = 0, \ + range = 1, \ simple_heal = 5, \ limit_to_trait = TRAIT_HEALS_FROM_CARP_RIFTS, \ healing_color = COLOR_BLUE, \ ) + gravity_aura = new( + /* host = */src, + /* range = */15, + /* ignore_if_not_on_turf = */TRUE, + /* gravity = */1, + ) + START_PROCESSING(SSobj, src) +/obj/structure/carp_rift/Destroy() + QDEL_NULL(gravity_aura) + return ..() + // Carp rifts always take heavy explosion damage. Discourages the use of maxcaps // and favours more weaker explosives to destroy the portal // as they have the same effect on the portal. @@ -183,7 +196,7 @@ if(time_charged >= max_charge) charge_state = CHARGE_COMPLETED var/area/A = get_area(src) - priority_announce("Spatial object has reached peak energy charge in [initial(A.name)], please stand-by.", "Central Command Wildlife Observations") + priority_announce("Spatial object has reached peak energy charge in [initial(A.name)], please stand-by.", "Central Command Wildlife Observations", has_important_message = TRUE) atom_integrity = INFINITY icon_state = "carp_rift_charged" set_light_color(LIGHT_COLOR_DIM_YELLOW) @@ -230,12 +243,13 @@ to_chat(user, span_warning("The rift already summoned enough carp!")) return FALSE - if(!dragon) + if(isnull(dragon)) return var/mob/living/newcarp = new dragon.minion_to_spawn(loc) newcarp.faction = dragon.owner.current.faction newcarp.AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) newcarp.AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") + dragon.wavespeak?.link_mob(newcarp) if(!is_listed) ckey_list += user.ckey diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm index 7ebcd4dde0c..25fb005ca61 100644 --- a/code/modules/antagonists/space_dragon/space_dragon.dm +++ b/code/modules/antagonists/space_dragon/space_dragon.dm @@ -21,9 +21,13 @@ /// Whether or not Space Dragon has completed their objective, and thus triggered the ending sequence. var/objective_complete = FALSE /// What mob to spawn from ghosts using this dragon's rifts - var/minion_to_spawn = /mob/living/basic/carp + var/minion_to_spawn = /mob/living/basic/carp/advanced /// What AI mobs to spawn from this dragon's rifts var/ai_to_spawn = /mob/living/basic/carp + /// Wavespeak mind linker, to allow telepathy between dragon and carps + var/datum/component/mind_linker/wavespeak + /// What areas are we allowed to place rifts in? + var/list/chosen_rift_areas = list() /datum/antagonist/space_dragon/greet() . = ..() @@ -33,16 +37,42 @@ Today, we will snuff out one of those lights.") to_chat(owner, span_boldwarning("You have five minutes to find a safe location to place down the first rift. If you take longer than five minutes to place a rift, you will be returned from whence you came.")) owner.announce_objectives() - SEND_SOUND(owner.current, sound('sound/magic/demon_attack1.ogg')) + owner.current.playsound_local(get_turf(owner.current), 'sound/magic/demon_attack1.ogg', 80) /datum/antagonist/space_dragon/forge_objectives() + var/static/list/area/allowed_areas + if(!allowed_areas) + // Areas that will prove a challeng for the dragon and are provocative to the crew. + allowed_areas = typecacheof(list( + /area/station/command, + /area/station/engineering, + /area/station/science, + /area/station/security, + )) + + var/list/possible_areas = typecache_filter_list(get_sorted_areas(), allowed_areas) + for(var/area/possible_area as anything in possible_areas) + if(initial(possible_area.outdoors) || !(possible_area.area_flags & VALID_TERRITORY)) + possible_areas -= possible_area + + for(var/i in 1 to 5) + chosen_rift_areas += pick_n_take(possible_areas) + var/datum/objective/summon_carp/summon = new - summon.dragon = src objectives += summon + summon.owner = owner + summon.update_explanation_text() /datum/antagonist/space_dragon/on_gain() forge_objectives() rift_ability = new() + owner.special_role = ROLE_SPACE_DRAGON + owner.set_assigned_role(SSjob.GetJobType(/datum/job/space_dragon)) + return ..() + +/datum/antagonist/space_dragon/on_removal() + owner.special_role = null + owner.set_assigned_role(SSjob.GetJobType(/datum/job/unassigned)) return ..() /datum/antagonist/space_dragon/apply_innate_effects(mob/living/mob_override) @@ -52,6 +82,15 @@ antag.faction |= FACTION_CARP // Give the ability over if we have one rift_ability?.Grant(antag) + wavespeak = antag.AddComponent( \ + /datum/component/mind_linker, \ + network_name = "Wavespeak", \ + chat_color = "#635BAF", \ + signals_which_destroy_us = list(COMSIG_LIVING_DEATH), \ + speech_action_icon = 'icons/mob/actions/actions_space_dragon.dmi', \ + speech_action_icon_state = "wavespeak", \ + ) + RegisterSignal(wavespeak, COMSIG_PARENT_QDELETING, PROC_REF(clear_wavespeak)) /datum/antagonist/space_dragon/remove_innate_effects(mob/living/mob_override) var/mob/living/antag = mob_override || owner.current @@ -59,11 +98,14 @@ UnregisterSignal(antag, COMSIG_LIVING_DEATH) antag.faction -= FACTION_CARP rift_ability?.Remove(antag) + QDEL_NULL(wavespeak) /datum/antagonist/space_dragon/Destroy() rift_list = null carp = null QDEL_NULL(rift_ability) + QDEL_NULL(wavespeak) + chosen_rift_areas.Cut() return ..() /datum/antagonist/space_dragon/get_preview_icon() @@ -77,6 +119,10 @@ return icon +/datum/antagonist/space_dragon/proc/clear_wavespeak() + SIGNAL_HANDLER + wavespeak = null + /** * Checks to see if we need to do anything with the current state of the dragon's rifts. * @@ -132,11 +178,10 @@ objective_complete = TRUE permanant_empower() var/datum/objective/summon_carp/main_objective = locate() in objectives - if(main_objective) - main_objective.completed = TRUE + main_objective?.completed = TRUE priority_announce("A large amount of lifeforms have been detected approaching [station_name()] at extreme speeds. \ - Remaining crew are advised to evacuate as soon as possible.", "Central Command Wildlife Observations") - sound_to_playing_players('sound/creatures/space_dragon_roar.ogg') + Remaining crew are advised to evacuate as soon as possible.", "Central Command Wildlife Observations", has_important_message = TRUE) + sound_to_playing_players('sound/creatures/space_dragon_roar.ogg', volume = 75) for(var/obj/structure/carp_rift/rift as anything in rift_list) rift.carp_stored = 999999 rift.time_charged = rift.max_charge @@ -177,8 +222,19 @@ owner.current.remove_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) /datum/objective/summon_carp - var/datum/antagonist/space_dragon/dragon - explanation_text = "Summon and protect the rifts to flood the station with carp." + explanation_text = "Summon 3 rifts in order to flood the station with carp." + +/datum/objective/summon_carp/update_explanation_text() + var/datum/antagonist/space_dragon/dragon_owner = owner.has_antag_datum(/datum/antagonist/space_dragon) + if(isnull(dragon_owner)) + return + + var/list/converted_names = list() + for(var/area/possible_area as anything in dragon_owner.chosen_rift_areas) + converted_names += possible_area.get_original_area_name() + + explanation_text = initial(explanation_text) + explanation_text += " Your possible rift locations are: [english_list(converted_names)]" /datum/antagonist/space_dragon/roundend_report() var/list/parts = list() @@ -197,7 +253,17 @@ parts += "The [name] was successful!" else parts += "The [name] has failed!" - if(carp.len) - parts += "The [name] was assisted by:" - parts += printplayerlist(carp) + + if(length(carp)) + parts += "
The [name] was assisted by:" + parts += "" + return "
[parts.Join("
")]
" diff --git a/code/modules/events/ghost_role/space_dragon.dm b/code/modules/events/ghost_role/space_dragon.dm index 74db3f84e48..379b5d7e07b 100644 --- a/code/modules/events/ghost_role/space_dragon.dm +++ b/code/modules/events/ghost_role/space_dragon.dm @@ -33,8 +33,6 @@ var/mob/living/simple_animal/hostile/space_dragon/dragon = new (spawn_location) dragon.key = key - dragon.mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_dragon)) - dragon.mind.special_role = ROLE_SPACE_DRAGON dragon.mind.add_antag_datum(/datum/antagonist/space_dragon) playsound(dragon, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) message_admins("[ADMIN_LOOKUPFLW(dragon)] has been made into a Space Dragon by an event.") diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index 4d0897647a6..ddeccd93103 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -256,4 +256,8 @@ disk_overlay = mutable_appearance('icons/mob/simple/carp.dmi', "disk_overlay") new_overlays += disk_overlay +/mob/living/basic/carp/advanced + health = 40 + obj_damage = 15 + #undef RARE_CAYENNE_CHANCE diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index 29ff7ab1561..68773e9a41e 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -686,10 +686,11 @@ project_action = new(src) project_action.Grant(grant_to) - grant_to.AddComponent(/datum/component/mind_linker, \ + grant_to.AddComponent( \ + /datum/component/mind_linker/active_linking, \ network_name = "Slime Link", \ - linker_action_path = /datum/action/innate/link_minds, \ signals_which_destroy_us = list(COMSIG_SPECIES_LOSS), \ + linker_action_path = /datum/action/innate/link_minds, \ ) //Species datums don't normally implement destroy, but JELLIES SUCK ASS OUT OF A STEEL STRAW @@ -813,7 +814,7 @@ return FALSE return TRUE - + #undef JELLY_REGEN_RATE #undef JELLY_REGEN_RATE_EMPTY #undef BLOOD_VOLUME_LOSE_NUTRITION diff --git a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm index 3340d280bb9..9533e407a09 100644 --- a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm +++ b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm @@ -73,14 +73,16 @@ var/on_unlink_message = "Your mind shatters as [src]'s Mansus Link leaves your mind." - AddComponent(/datum/component/mind_linker, \ + AddComponent( \ + /datum/component/mind_linker/active_linking, \ network_name = "Mansus Link", \ chat_color = "#568b00", \ + post_unlink_callback = CALLBACK(src, PROC_REF(after_unlink)), \ + speech_action_background_icon_state = "bg_heretic", \ + speech_action_overlay_state = "bg_heretic_border", \ linker_action_path = /datum/action/cooldown/spell/pointed/manse_link, \ link_message = on_link_message, \ unlink_message = on_unlink_message, \ - post_unlink_callback = CALLBACK(src, PROC_REF(after_unlink)), \ - speech_action_background_icon_state = "bg_heretic", \ ) /mob/living/simple_animal/hostile/heretic_summon/raw_prophet/attack_animal(mob/living/simple_animal/user, list/modifiers) diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index 93c77b8d810..8916696a72a 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -24,8 +24,8 @@ name = "Space Dragon" desc = "A vile, leviathan-esque creature that flies in the most unnatural way. Looks slightly similar to a space carp." gender = NEUTER - maxHealth = 320 - health = 320 + maxHealth = 400 + health = 400 damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0.5, OXY = 1) combat_mode = TRUE speed = 0 @@ -283,7 +283,7 @@ for(var/obj/machinery/door/D in T.contents) if(D.density) return - delayFire += 1.5 + delayFire += 1.0 addtimer(CALLBACK(src, PROC_REF(dragon_fire_line), T), delayFire) /** @@ -295,25 +295,52 @@ * Arguments: * * turf/T - The turf to trigger the effects on. */ -/mob/living/simple_animal/hostile/space_dragon/proc/dragon_fire_line(turf/T) +/mob/living/simple_animal/hostile/space_dragon/proc/dragon_fire_line(turf/fire_turf) var/list/hit_list = list() hit_list += src - new /obj/effect/hotspot(T) - T.hotspot_expose(700,50,1) - for(var/mob/living/L in T.contents) - if(L in hit_list) + new /obj/effect/hotspot(fire_turf) + fire_turf.hotspot_expose(700,50,1) + for(var/mob/living/living_target in fire_turf.contents) + if(living_target.faction_check_mob(src) && living_target != src) + hit_list += living_target + start_carp_speedboost(living_target) + if(living_target in hit_list) continue - if(L.mind?.has_antag_datum(/datum/antagonist/space_carp)) + if(living_target.mind?.has_antag_datum(/datum/antagonist/space_carp)) continue - hit_list += L - L.adjustFireLoss(30) - to_chat(L, span_userdanger("You're hit by [src]'s fire breath!")) + hit_list += living_target + living_target.adjustFireLoss(30) + to_chat(living_target, span_userdanger("You're hit by [src]'s fire breath!")) // deals damage to mechs - for(var/obj/vehicle/sealed/mecha/M in T.contents) - if(M in hit_list) + for(var/obj/vehicle/sealed/mecha/mech_target in fire_turf.contents) + if(mech_target in hit_list) continue - hit_list += M - M.take_damage(50, BRUTE, MELEE, 1) + hit_list += mech_target + mech_target.take_damage(50, BRUTE, MELEE, 1) + +/** + * Applies the speed boost to carps when hit by space dragon's flame breath + * + * Applies the dragon rage effect to carps temporarily, giving them a glow and a speed boost. + * This lasts for 8 seconds. + * Arguments: + * * mob/living/target - The carp being affected. + */ +/mob/living/simple_animal/hostile/space_dragon/proc/start_carp_speedboost(mob/living/target) + target.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 2)) + target.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) + addtimer(CALLBACK(src, PROC_REF(end_carp_speedboost), target), 8 SECONDS) + +/** + * Remove the speed boost from carps when hit by space dragon's flame breath + * + * Removes the dragon rage effect from carps, removing their glow and speed boost. + * Arguments: + * * mob/living/target - The carp being affected. + */ +/mob/living/simple_animal/hostile/space_dragon/proc/end_carp_speedboost(mob/living/target) + target.remove_filter("anger_glow") + target.remove_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) /** * Handles consuming and storing consumed things inside Space Dragon @@ -325,7 +352,7 @@ */ /mob/living/simple_animal/hostile/space_dragon/proc/eat(atom/movable/A) if(A && A.loc != src) - playsound(src, 'sound/magic/demon_attack1.ogg', 100, TRUE) + playsound(src, 'sound/magic/demon_attack1.ogg', 60, TRUE) visible_message(span_warning("[src] swallows [A] whole!")) A.forceMove(src) return TRUE @@ -357,7 +384,7 @@ /mob/living/simple_animal/hostile/space_dragon/proc/useGust(timer) if(timer != 10) pixel_y = pixel_y + 2; - addtimer(CALLBACK(src, PROC_REF(useGust), timer + 1), 1.5) + addtimer(CALLBACK(src, PROC_REF(useGust), timer + 1), 1.2) return pixel_y = 0 icon_state = "spacedragon_gust_2" @@ -366,20 +393,22 @@ overlay.appearance_flags = RESET_COLOR add_overlay(overlay) playsound(src, 'sound/effects/gravhit.ogg', 100, TRUE) - var/gust_locs = spiral_range_turfs(gust_distance, get_turf(src)) - var/list/hit_things = list() - for(var/turf/T in gust_locs) - for(var/mob/living/L in T.contents) - if(L == src) - continue - hit_things += L - visible_message(span_boldwarning("[L] is knocked back by the gust!")) - to_chat(L, span_userdanger("You're knocked back by the gust!")) - var/dir_to_target = get_dir(get_turf(src), get_turf(L)) - var/throwtarget = get_edge_target_turf(target, dir_to_target) - L.safe_throw_at(throwtarget, 10, 1, src) - L.Paralyze(50) + for (var/mob/living/candidate in view(gust_distance, src)) + if(candidate == src || candidate.faction_check_mob(src)) + continue + visible_message(span_boldwarning("[candidate] is knocked back by the gust!")) + to_chat(candidate, span_userdanger("You're knocked back by the gust!")) + var/dir_to_target = get_dir(get_turf(src), get_turf(candidate)) + var/throwtarget = get_edge_target_turf(target, dir_to_target) + candidate.safe_throw_at(throwtarget, 10, 1, src) + candidate.Paralyze(50) addtimer(CALLBACK(src, PROC_REF(reset_status)), 4 + ((tiredness * tiredness_mult) / 10)) tiredness = tiredness + (gust_tiredness * tiredness_mult) +/mob/living/simple_animal/hostile/space_dragon/spawn_with_antag + +/mob/living/simple_animal/hostile/space_dragon/spawn_with_antag/mind_initialize() + . = ..() + mind.add_antag_datum(/datum/antagonist/space_dragon) + #undef DARKNESS_THRESHOLD diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 87e2f5760cf..a666233d0b8 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -261,6 +261,7 @@ /mob/living/simple_animal/hostile/smspider, /mob/living/simple_animal/hostile/smspider/overcharged, /mob/living/simple_animal/hostile/space_dragon, + /mob/living/simple_animal/hostile/space_dragon/spawn_with_antag, /mob/living/simple_animal/hostile/vatbeast, /mob/living/simple_animal/hostile/venus_human_trap, /mob/living/simple_animal/hostile/wizard, diff --git a/icons/mob/actions/actions_space_dragon.dmi b/icons/mob/actions/actions_space_dragon.dmi index a4e33eef1eb..48d73fd6f47 100644 Binary files a/icons/mob/actions/actions_space_dragon.dmi and b/icons/mob/actions/actions_space_dragon.dmi differ