diff --git a/code/datums/components/connect_loc_behalf.dm b/code/datums/components/connect_loc_behalf.dm new file mode 100644 index 00000000000..6709668f772 --- /dev/null +++ b/code/datums/components/connect_loc_behalf.dm @@ -0,0 +1,66 @@ +/// This component behaves similar to connect_loc, hooking into a signal on a tracked object's turf +/// It has the ability to react to that signal on behalf of a separate listener however +/// This has great use, primarily for components, but it carries with it some overhead +/// So we do it separately as it needs to hold state which is very likely to lead to bugs if it remains as an element. +/datum/component/connect_loc_behalf + dupe_mode = COMPONENT_DUPE_UNIQUE + + /// An assoc list of signal -> procpath to register to the loc this object is on. + var/list/connections + var/atom/movable/tracked + var/atom/tracked_loc + +/datum/component/connect_loc_behalf/Initialize(atom/movable/tracked, list/connections) + . = ..() + if(!istype(tracked)) + return COMPONENT_INCOMPATIBLE + src.connections = connections + src.tracked = tracked + +/datum/component/connect_loc_behalf/RegisterWithParent() + RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + RegisterSignal(tracked, COMSIG_PARENT_QDELETING, PROC_REF(handle_tracked_qdel)) + update_signals() + +/datum/component/connect_loc_behalf/UnregisterFromParent() + unregister_signals() + UnregisterSignal(tracked, list( + COMSIG_MOVABLE_MOVED, + COMSIG_PARENT_QDELETING, + )) + + tracked = null + +/datum/component/connect_loc_behalf/proc/handle_tracked_qdel() + SIGNAL_HANDLER // COMSIG_PARENT_QDELETING + qdel(src) + +/datum/component/connect_loc_behalf/proc/update_signals() + unregister_signals() + //You may ask yourself, isn't this just silencing an error? + //The answer is yes, but there's no good cheap way to fix it + //What happens is the tracked object or hell the listener gets say, deleted, which makes targets[old_loc] return a null + //The null results in a bad index, because of course it does + //It's not a solvable problem though, since both actions, the destroy and the move, are sourced from the same signal send + //And sending a signal should be agnostic of the order of listeners + //So we need to either pick the order agnositic, or destroy safe + //And I picked destroy safe. Let's hope this is the right path! + if(isnull(tracked.loc)) + return + + tracked_loc = tracked.loc + + for(var/signal in connections) + parent.RegisterSignal(tracked_loc, signal, connections[signal]) + +/datum/component/connect_loc_behalf/proc/unregister_signals() + if(isnull(tracked_loc)) + return + + parent.UnregisterSignal(tracked_loc, connections) + + tracked_loc = null + +/datum/component/connect_loc_behalf/proc/on_moved(sigtype, atom/movable/tracked, atom/old_loc) + SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED + update_signals() diff --git a/code/datums/components/swarm.dm b/code/datums/components/swarm.dm new file mode 100644 index 00000000000..608359d4908 --- /dev/null +++ b/code/datums/components/swarm.dm @@ -0,0 +1,59 @@ +/datum/component/swarming + var/offset_x = 0 + var/offset_y = 0 + var/is_swarming = FALSE + var/list/swarm_members = list() + var/static/list/swarming_loc_connections = list( + COMSIG_ATOM_EXITED = PROC_REF(leave_swarm), + COMSIG_ATOM_ENTERED = PROC_REF(join_swarm) + ) + + +/datum/component/swarming/Initialize(max_x = 24, max_y = 24) + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + offset_x = rand(-max_x, max_x) + offset_y = rand(-max_y, max_y) + + AddComponent(/datum/component/connect_loc_behalf, parent, swarming_loc_connections) + +/datum/component/swarming/Destroy() + for(var/other in swarm_members) + var/datum/component/swarming/other_swarm = other + other_swarm.swarm_members -= src + if(!length(other_swarm.swarm_members)) + other_swarm.unswarm() + swarm_members = null + return ..() + +/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) + var/datum/component/swarming/other_swarm = arrived.GetComponent(/datum/component/swarming) + if(!other_swarm) + return + swarm() + swarm_members |= other_swarm + other_swarm.swarm() + other_swarm.swarm_members |= src + +/datum/component/swarming/proc/leave_swarm(datum/source, atom/movable/gone, direction) + var/datum/component/swarming/other_swarm = gone.GetComponent(/datum/component/swarming) + if(!other_swarm || !(other_swarm in swarm_members)) + return + swarm_members -= other_swarm + if(!length(swarm_members)) + unswarm() + other_swarm.swarm_members -= src + if(!length(other_swarm.swarm_members)) + other_swarm.unswarm() + +/datum/component/swarming/proc/swarm() + var/atom/movable/owner = parent + if(!is_swarming) + is_swarming = TRUE + animate(owner, pixel_x = owner.pixel_x + offset_x, pixel_y = owner.pixel_y + offset_y, time = 2) + +/datum/component/swarming/proc/unswarm() + var/atom/movable/owner = parent + if(is_swarming) + animate(owner, pixel_x = owner.pixel_x - offset_x, pixel_y = owner.pixel_y - offset_y, time = 2) + is_swarming = FALSE diff --git a/code/modules/mob/animations.dm b/code/modules/mob/animations.dm index 8d2f95a0fed..9d8df43ea97 100644 --- a/code/modules/mob/animations.dm +++ b/code/modules/mob/animations.dm @@ -195,12 +195,8 @@ note dizziness decrements automatically in the mob's Life() proc. else if(direction & WEST) pixel_x_diff = 8 - var/default_pixel_x = initial(pixel_x) - var/default_pixel_y = initial(pixel_y) - var/mob/mob = src - if(istype(mob)) - default_pixel_x = mob.default_pixel_x - default_pixel_y = mob.default_pixel_y + var/default_pixel_x = pixel_x + var/default_pixel_y = pixel_y animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = windup_time - 2) animate(pixel_x = default_pixel_x, pixel_y = default_pixel_y, time = 2) @@ -220,12 +216,8 @@ note dizziness decrements automatically in the mob's Life() proc. else if(direction & WEST) pixel_x_diff = -8 - var/default_pixel_x = initial(pixel_x) - var/default_pixel_y = initial(pixel_y) - var/mob/mob = src - if(istype(mob)) - default_pixel_x = mob.default_pixel_x - default_pixel_y = mob.default_pixel_y + var/default_pixel_x = pixel_x + var/default_pixel_y = pixel_y animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) animate(pixel_x = default_pixel_x, pixel_y = default_pixel_y, time = 2) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/jellyfish.dm b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/jellyfish.dm index 66666946631..d75f3ad4c2b 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/jellyfish.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/jellyfish.dm @@ -25,6 +25,7 @@ GLOBAL_VAR_INIT(jellyfish_count, 0) icon_dead = "space_jellyfish_dead" has_eye_glow = TRUE hovering = TRUE + density = FALSE faction = FACTION_JELLYFISH @@ -80,6 +81,16 @@ GLOBAL_VAR_INIT(jellyfish_count, 0) var/reproduction_cooldown = 0 +/mob/living/simple_mob/vore/alienanimals/space_jellyfish/Initialize() + . = ..() + AddComponent(/datum/component/swarming) + +// Allows this mob to swarm +/mob/living/simple_mob/vore/alienanimals/space_jellyfish/CanPass(atom/movable/mover, turf/target) + if(isliving(mover) && !istype(mover, /mob/living/simple_mob/vore/alienanimals/space_jellyfish) && mover.density == TRUE) + return FALSE + return ..() + /datum/say_list/jellyfish emote_see = list("flickers", "flashes", "looms","pulses","sways","shimmers hypnotically") diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm index a8e84ebd62c..aea882fcfde 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm @@ -68,6 +68,7 @@ icon_living = "guard" icon_dead = "guard_dead" has_eye_glow = TRUE + density = FALSE faction = FACTION_SPIDERS maxHealth = 200 @@ -119,6 +120,15 @@ allow_mind_transfer = TRUE +/mob/living/simple_mob/animal/giant_spider/Initialize() + . = ..() + AddComponent(/datum/component/swarming) + +/mob/living/simple_mob/animal/giant_spider/CanPass(atom/movable/mover, turf/target) + if(isliving(mover) && !istype(mover, /mob/living/simple_mob/animal/giant_spider) && mover.density == TRUE) + return FALSE + return ..() + /mob/living/simple_mob/animal/giant_spider/apply_melee_effects(var/atom/A) if(isliving(A)) var/mob/living/L = A diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm index a6dc4d25361..a476e9ea7d2 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm @@ -29,6 +29,7 @@ name = "space carp" desc = "A ferocious, fang-bearing creature that resembles a fish." catalogue_data = list(/datum/category_item/catalogue/fauna/carp) + icon = 'icons/mob/carp.dmi' icon_state = "carp" icon_living = "carp" icon_dead = "carp_dead" @@ -39,10 +40,13 @@ health = 25 movement_cooldown = -2 hovering = TRUE + density = FALSE - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "hits the" + vore_active = TRUE + vore_icons = SA_ICON_LIVING + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "hits" melee_damage_lower = 7 // About 14 DPS. melee_damage_upper = 7 @@ -60,6 +64,85 @@ var/knockdown_chance = 15 + var/random_color = FALSE + var/rarechance = 1 + + var/static/list/carp_colors = list(\ + "lightpurple" = "#c3b9f1", \ + "lightpink" = "#da77a8", \ + "green" = "#70ff25", \ + "grape" = "#df0afb", \ + "swamp" = "#e5e75a", \ + "turquoise" = "#04e1ed", \ + "brown" = "#ca805a", \ + "teal" = "#20e28e", \ + "lightblue" = "#4d88cc", \ + "rusty" = "#dd5f34", \ + "beige" = "#bbaeaf", \ + "yellow" = "#f3ca4a", \ + "blue" = "#09bae1", \ + "palegreen" = "#7ef099", \ + ) + var/static/list/carp_colors_rare = list(\ + "silver" = "#fdfbf3", \ + ) + +/mob/living/simple_mob/animal/space/carp/Initialize() + . = ..() + carp_randomify(rarechance) + update_icons() + AddComponent(/datum/component/swarming) + +// This is so carps can swarm +/mob/living/simple_mob/animal/space/carp/CanPass(atom/movable/mover, turf/target) + if(isliving(mover) && !istype(mover, /mob/living/simple_mob/animal/space/carp) && mover.density == TRUE) + return FALSE + return ..() + +/mob/living/simple_mob/animal/space/carp/proc/carp_randomify(rarechance) + if(random_color) + var/our_color + if(prob(rarechance)) + our_color = pick(carp_colors_rare) + add_atom_colour(carp_colors_rare[our_color], FIXED_COLOUR_PRIORITY) + else + our_color = pick(carp_colors) + add_atom_colour(carp_colors[our_color], FIXED_COLOUR_PRIORITY) + regenerate_icons() + +/mob/living/simple_mob/animal/space/carp/proc/add_carp_overlay() + if(!random_color) + return + var/mutable_appearance/base_overlay = mutable_appearance(icon, "base_mouth") + base_overlay.appearance_flags = RESET_COLOR + add_overlay(base_overlay) + +/mob/living/simple_mob/animal/space/carp/proc/add_dead_carp_overlay() + if(!random_color) + return + var/mutable_appearance/base_dead_overlay = mutable_appearance(icon, "base_dead_mouth") + base_dead_overlay.appearance_flags = RESET_COLOR + add_overlay(base_dead_overlay) + +/mob/living/simple_mob/animal/space/carp/death(gibbed) + . = ..() + if(!random_color || gibbed) + return + regenerate_icons() + +/mob/living/simple_mob/animal/space/carp/revive() + ..() + regenerate_icons() + +/mob/living/simple_mob/animal/space/carp/regenerate_icons() + ..() + if(!random_color) + return + if(stat != DEAD) + add_carp_overlay() + else + add_dead_carp_overlay() + /mob/living/simple_mob/animal/space/carp/apply_melee_effects(var/atom/A) if(isliving(A)) var/mob/living/L = A @@ -81,6 +164,7 @@ icon_state = "shark" icon_living = "shark" icon_dead = "shark_dead" + vore_icons = FALSE maxHealth = 50 health = 50 @@ -102,6 +186,7 @@ icon_dead = "megacarp_dead" icon_living = "megacarp" icon_state = "megacarp" + vore_icons = FALSE maxHealth = 230 health = 230 @@ -174,6 +259,7 @@ icon_gib = null meat_amount = 0 meat_type = null + vore_icons = FALSE mob_class = MOB_CLASS_PHOTONIC // Xeno-taser won't work on this as its not a 'real' carp. diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/viscerator.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/viscerator.dm index d1163abd426..98bd0a7ec22 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/viscerator.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/viscerator.dm @@ -32,7 +32,8 @@ health = 15 movement_cooldown = -2 - pass_flags = PASSTABLE + pass_flags = PASSTABLE | PASSMOB + a_intent = I_HURT mob_swap_flags = 0 mob_push_flags = 0 @@ -49,6 +50,10 @@ ai_holder_type = /datum/ai_holder/simple_mob/melee/evasive +/mob/living/simple_mob/mechanical/viscerator/Initialize() + . = ..() + AddComponent(/datum/component/swarming) + /mob/living/simple_mob/mechanical/viscerator/death() ..(null,"is smashed into pieces!") qdel(src) diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/bee.dm b/code/modules/mob/living/simple_mob/subtypes/vore/bee.dm index 86c3b588487..95d97b26dd9 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/bee.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/bee.dm @@ -46,6 +46,10 @@ allow_mind_transfer = TRUE +/mob/living/simple_mob/vore/bee/Initialize() + . = ..() + AddComponent(/datum/component/swarming) + /mob/living/simple_mob/vore/bee/Process_Spacemove(var/check_drift = 0) return 1 //No drifting in space for space bee! diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/zz_vore_overrides.dm b/code/modules/mob/living/simple_mob/subtypes/vore/zz_vore_overrides.dm index 0c51d048949..0607a2b8f71 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/zz_vore_overrides.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/zz_vore_overrides.dm @@ -119,21 +119,6 @@ melee_damage_upper = 15 attack_armor_pen = 0 -/mob/living/simple_mob/animal/space/carp - icon = 'icons/mob/vore.dmi' - vore_active = 1 - vore_icons = SA_ICON_LIVING - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "hits" - -/mob/living/simple_mob/animal/space/carp/large - vore_icons = 0 -/mob/living/simple_mob/animal/space/carp/large/huge - vore_icons = 0 -/mob/living/simple_mob/animal/space/carp/holodeck - vore_icons = 0 - /* //VOREStation AI Temporary removal /mob/living/simple_mob/hostile/creature/vore vore_active = 1 diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi index 7c54ea772a2..95a2dc3c4bf 100644 Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ diff --git a/icons/mob/carp.dmi b/icons/mob/carp.dmi new file mode 100644 index 00000000000..9ca9780ecbe Binary files /dev/null and b/icons/mob/carp.dmi differ diff --git a/icons/mob/vore.dmi b/icons/mob/vore.dmi index 33cc2020b06..167fd610977 100644 Binary files a/icons/mob/vore.dmi and b/icons/mob/vore.dmi differ diff --git a/vorestation.dme b/vorestation.dme index 23e06843550..0b544053f31 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -471,6 +471,7 @@ #include "code\datums\autolathe\tools_vr.dm" #include "code\datums\changelog\changelog.dm" #include "code\datums\components\_component.dm" +#include "code\datums\components\connect_loc_behalf.dm" #include "code\datums\components\connect_mob_behalf.dm" #include "code\datums\components\gargoyle.dm" #include "code\datums\components\material_container.dm" @@ -478,6 +479,7 @@ #include "code\datums\components\overlay_lighting.dm" #include "code\datums\components\recursive_move.dm" #include "code\datums\components\resize_guard.dm" +#include "code\datums\components\swarm.dm" #include "code\datums\components\crafting\crafting.dm" #include "code\datums\components\crafting\crafting_external.dm" #include "code\datums\components\crafting\recipes.dm"