diff --git a/code/datums/components/mind_linker.dm b/code/datums/components/mind_linker.dm index 09e4abdb661..745bbd73f2e 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,8 +47,9 @@ 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 @@ -63,12 +60,6 @@ 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.") - /* ORIGINAL CODE master_speech = new(src) master_speech.Grant(owner) @@ -80,13 +71,10 @@ master_speech.Grant(owner) //SKYRAT EDIT END - 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 ..() @@ -127,20 +115,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. @@ -148,25 +135,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. @@ -181,6 +160,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 153a8bf529a..44a53ac00a5 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -574,8 +574,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 b0d990513a0..db353a21908 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -695,10 +695,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 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 f1c470564ae..090cb018197 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -262,6 +262,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 diff --git a/modular_skyrat/modules/modular_implants/code/nifsofts/hivemind.dm b/modular_skyrat/modules/modular_implants/code/nifsofts/hivemind.dm index 2601e154bd6..7f722dcb343 100644 --- a/modular_skyrat/modules/modular_implants/code/nifsofts/hivemind.dm +++ b/modular_skyrat/modules/modular_implants/code/nifsofts/hivemind.dm @@ -16,11 +16,11 @@ GLOBAL_LIST_EMPTY(hivemind_users) ui_icon = "users" ///The network that the user is currently hosting - var/datum/component/mind_linker/nif/user_network + var/datum/component/mind_linker/active_linking/nif/user_network ///What networks are the user connected to? var/list/network_list = list() ///What network is the user sending messages to? This is saved from the keyboard so the user doesn't have to change the channel every time. - var/datum/component/mind_linker/nif/active_network + var/datum/component/mind_linker/active_linking/nif/active_network ///The physical keyboard item being used to send messages var/obj/item/hivemind_keyboard/linked_keyboard ///What action is being used to summon the Keyboard? @@ -29,7 +29,7 @@ GLOBAL_LIST_EMPTY(hivemind_users) /datum/nifsoft/hivemind/New() . = ..() - user_network = linked_mob.AddComponent(/datum/component/mind_linker/nif, \ + user_network = linked_mob.AddComponent(/datum/component/mind_linker/active_linking/nif, \ network_name = "Hivemind Link", \ linker_action_path = /datum/action/innate/hivemind_config, \ ) @@ -54,7 +54,7 @@ GLOBAL_LIST_EMPTY(hivemind_users) linked_keyboard = null - for(var/datum/component/mind_linker/nif/hivemind as anything in network_list) + for(var/datum/component/mind_linker/active_linking/nif/hivemind as anything in network_list) hivemind.linked_mobs -= linked_mob var/mob/living/hivemind_owner = hivemind.parent @@ -90,7 +90,7 @@ GLOBAL_LIST_EMPTY(hivemind_users) /datum/action/innate/hivemind_config/Activate() . = ..() - var/datum/component/mind_linker/nif/link = target + var/datum/component/mind_linker/active_linking/nif/link = target var/choice = tgui_input_list(owner, "Chose your option", "Hivemind Configuration Menu", list("Link a user","Remove a user","Change Hivemind color","Change active Hivemind","Leave a Hivemind", "Toggle invites")) if(!choice) @@ -119,7 +119,7 @@ GLOBAL_LIST_EMPTY(hivemind_users) var/mob/living/carbon/human/user = owner var/datum/nifsoft/hivemind/hivemind = user.find_nifsoft(/datum/nifsoft/hivemind) - var/datum/component/mind_linker/nif/new_active_hivemind = tgui_input_list(user, "Choose a Hivemind to set as active.", "Switch Hivemind", hivemind.network_list) + var/datum/component/mind_linker/active_linking/nif/new_active_hivemind = tgui_input_list(user, "Choose a Hivemind to set as active.", "Switch Hivemind", hivemind.network_list) if(!new_active_hivemind) return FALSE @@ -137,7 +137,7 @@ GLOBAL_LIST_EMPTY(hivemind_users) var/list/network_list = hivemind.network_list network_list -= hivemind.user_network - var/datum/component/mind_linker/nif/hivemind_to_leave = tgui_input_list(user, "Choose a Hivemind to disconnect from.", "Remove Hivemind", network_list) + var/datum/component/mind_linker/active_linking/nif/hivemind_to_leave = tgui_input_list(user, "Choose a Hivemind to disconnect from.", "Remove Hivemind", network_list) if(!hivemind_to_leave) return FALSE @@ -181,21 +181,21 @@ GLOBAL_LIST_EMPTY(hivemind_users) ///Does the component check to see if the person being linked has a mindshield or anti-magic? var/linking_protection = TRUE -/datum/component/mind_linker/nif +/datum/component/mind_linker/active_linking/nif speech_action = FALSE linking_protection = FALSE ///What is the name of the hivemind? This is mostly here for the TGUI selection menus. var/name = "" -/datum/component/mind_linker/nif/New() +/datum/component/mind_linker/active_linking/nif/New() . = ..() var/mob/living/owner = parent name = owner.name + "'s " + network_name ///Lets the user add someone to their Hivemind through a choice menu that shows everyone that has the Hivemind NIFSoft. -/datum/component/mind_linker/nif/proc/invite_user() +/datum/component/mind_linker/active_linking/nif/proc/invite_user() var/list/hivemind_users = GLOB.hivemind_users.Copy() var/mob/living/carbon/human/owner = parent @@ -226,7 +226,7 @@ GLOBAL_LIST_EMPTY(hivemind_users) to_chat(owner, span_abductor("[person_to_add] has now been added to your Hivemind")) ///Removes a user from the list of connected people within a hivemind -/datum/component/mind_linker/nif/proc/remove_user() +/datum/component/mind_linker/active_linking/nif/proc/remove_user() var/mob/living/carbon/human/owner = parent var/mob/living/carbon/human/person_to_remove = tgui_input_list(owner, "Choose a person to remove from your Hivemind.", "Remove User", linked_mobs) @@ -244,7 +244,7 @@ GLOBAL_LIST_EMPTY(hivemind_users) to_chat(person_to_remove, span_abductor("You have now been removed from [owner]'s Hivemind.")) to_chat(owner, span_abductor("[person_to_remove] has now been removed from your Hivemind.")) -/datum/component/mind_linker/nif/proc/change_chat_color() +/datum/component/mind_linker/active_linking/nif/proc/change_chat_color() var/mob/living/carbon/human/owner = parent var/new_chat_color = input(owner, "", "Choose Color", COLOR_ASSEMBLY_GREEN) as color @@ -262,7 +262,7 @@ GLOBAL_LIST_EMPTY(hivemind_users) righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' inhand_icon_state = "electronic" ///What Hivemind are messages being sent to? - var/datum/component/mind_linker/nif/connected_network + var/datum/component/mind_linker/active_linking/nif/connected_network //Who owns the controller? var/datum/weakref/source_user