diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index b7d54f04bbb..1a0f30ccf2e 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -250,6 +250,9 @@ ///from base of atom/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) #define COMSIG_ATOM_HITBY "atom_hitby" +///When the transform or an atom is varedited through vv topic. +#define COMSIG_ATOM_VV_MODIFY_TRANSFORM "atom_vv_modify_transform" + //from base of atom/movable/on_enter_storage(): (datum/component/storage/concrete/master_storage) #define COMSIG_STORAGE_ENTERED "storage_entered" //from base of atom/movable/on_exit_storage(): (datum/component/storage/concrete/master_storage) @@ -569,6 +572,9 @@ #define COMPONENT_CANT_INTERACT_WIRES (1<<0) #define COMSIG_MOB_EMOTED(emote_key) "mob_emoted_[emote_key]" +///from base of mob/update_transform() +#define COMSIG_LIVING_POST_UPDATE_TRANSFORM "living_post_update_transform" + ///from /obj/structure/door/crush(): (mob/living/crushed, /obj/machinery/door/crushing_door) #define COMSIG_LIVING_DOORCRUSHED "living_doorcrush" ///from base of mob/living/resist() (/mob/living) diff --git a/code/datums/components/clickbox.dm b/code/datums/components/clickbox.dm new file mode 100644 index 00000000000..72aa4ebdcc5 --- /dev/null +++ b/code/datums/components/clickbox.dm @@ -0,0 +1,95 @@ +#define CLICKBOX_LAYER FLOAT_LAYER-999 + +/** + * This component adds a near-invisible underlay to a movable target to expand the clickable surface of its icon without + * resorting to MOUSE_OPACITY_OPAQUE. + * The underlay ignores the parent's color and alpha and can be offset. + * The name is a portmanteau of "click" and "hitbox", because technically this isn't an hitbox, + * but it helps catch clicks, and I don't want to give it a loooong name like openspace_item_click_handler + */ +/datum/component/clickbox + dupe_mode = COMPONENT_DUPE_ALLOWED + /// The icon state of the underlay. + var/icon_state = "sphere" + /// The offsets of the underlay. + var/x_offset = 0 + var/y_offset = 0 + /// Maximum width/height of the underlay, in case the attached atom is scaled up. + var/max_scale = 2 + /// Minimum width/height of the underlay, in case the attached atom is scaled down. + var/min_scale = 0.5 + /// For simple animals that have different icon states when dead. + var/dead_state + /// the underlay that has been added to the parent. + var/mutable_appearance/clickbox_undelay + +/datum/component/clickbox/Initialize(icon_state = "sphere", x_offset = 0, y_offset = 0, max_scale = 2, min_scale = 0.5, dead_state) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + + src.icon_state = icon_state + src.x_offset = x_offset + src.y_offset = y_offset + src.max_scale = max_scale + src.min_scale = min_scale + + RegisterSignal(parent, COMSIG_ATOM_VV_MODIFY_TRANSFORM, .proc/on_modify_or_update_transform) + + var/clickbox_icon_state = icon_state + if(dead_state && isliving(parent)) + var/mob/living/living_parent = parent + src.dead_state = dead_state + RegisterSignal(living_parent, COMSIG_LIVING_POST_UPDATE_TRANSFORM, .proc/on_modify_or_update_transform) + RegisterSignal(living_parent, COMSIG_LIVING_DEATH, .proc/on_death) + RegisterSignal(living_parent, COMSIG_LIVING_REVIVE, .proc/on_revive) + if(living_parent.stat == DEAD) + clickbox_icon_state = dead_state + update_underlay(clickbox_icon_state) + +/datum/component/clickbox/UnregisterFromParent() + var/atom/movable/mov_parent = parent + UnregisterSignal(mov_parent, list(COMSIG_ATOM_VV_MODIFY_TRANSFORM, COMSIG_LIVING_POST_UPDATE_TRANSFORM, COMSIG_LIVING_DEATH, COMSIG_LIVING_REVIVE)) + mov_parent.underlays -= clickbox_undelay + +/// Removes the old underlay and adds a new one if conditions are met. The underlay is scaled up/down if necessary +/datum/component/clickbox/proc/update_underlay(clickbox_icon_state) + var/atom/movable/mov_parent = parent + if(!clickbox_icon_state) + clickbox_icon_state = clickbox_undelay?.icon_state || icon_state + mov_parent.underlays -= clickbox_undelay // Remove the previous underlay. + + var/width = abs(mov_parent.transform.a) // Negative values flip the parent, so abs() is good to have here. + var/height = abs(mov_parent.transform.e) // Idem. + + var/clickbox_width = 1 + if(width > max_scale) + clickbox_width = max_scale/width + else if(width < min_scale && width) + clickbox_width = min_scale/width + + var/clickbox_height = 1 + if(height > max_scale) + clickbox_height = max_scale/height + else if(height < min_scale && height) + clickbox_height = min_scale/height + + clickbox_undelay = mutable_appearance('icons/misc/clickbox.dmi', clickbox_icon_state, CLICKBOX_LAYER, alpha = 1, appearance_flags = RESET_COLOR|RESET_ALPHA) + clickbox_undelay.transform = clickbox_undelay.transform.Scale(clickbox_width, clickbox_height) + //Keeps the underlay more or less centered. + clickbox_undelay.pixel_x = x_offset * 1/clickbox_width + clickbox_undelay.pixel_y = y_offset * 1/clickbox_height + mov_parent.underlays += clickbox_undelay + +/datum/component/clickbox/proc/on_modify_or_update_transform(atom/source) + SIGNAL_HANDLER + update_underlay() + +/datum/component/clickbox/proc/on_death(mob/living/source) + SIGNAL_HANDLER + update_underlay(dead_state) + +/datum/component/clickbox/proc/on_revive(mob/living/source) + SIGNAL_HANDLER + update_underlay(icon_state) + +#undef CLICKBOX_LAYER diff --git a/code/game/atoms.dm b/code/game/atoms.dm index ed0c625bd80..06623b19a3b 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1276,21 +1276,28 @@ if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT)) var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate") var/matrix/M = transform + if(!result) + return switch(result) if("Scale") var/x = input(usr, "Choose x mod","Transform Mod") as null|num var/y = input(usr, "Choose y mod","Transform Mod") as null|num - if(!isnull(x) && !isnull(y)) - transform = M.Scale(x,y) + if(isnull(x) || isnull(y)) + return + transform = M.Scale(x,y) if("Translate") var/x = input(usr, "Choose x mod (negative = left, positive = right)","Transform Mod") as null|num var/y = input(usr, "Choose y mod (negative = down, positive = up)","Transform Mod") as null|num - if(!isnull(x) && !isnull(y)) - transform = M.Translate(x,y) + if(isnull(x) || isnull(y)) + return + transform = M.Translate(x,y) if("Rotate") var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num - if(!isnull(angle)) - transform = M.Turn(angle) + if(isnull(angle)) + return + transform = M.Turn(angle) + + SEND_SIGNAL(src, COMSIG_ATOM_VV_MODIFY_TRANSFORM) if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT)) var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid_update_icons.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid_update_icons.dm index 47947fa4036..bb2f49ae9ba 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid_update_icons.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid_update_icons.dm @@ -51,7 +51,7 @@ // update_icons() //Handled in update_transform(), leaving this here as a reminder update_transform() -/mob/living/carbon/alien/humanoid/update_transform() //The old method of updating lying/standing was update_icons(). Aliens still expect that. +/mob/living/carbon/alien/humanoid/perform_update_transform() //The old method of updating lying/standing was update_icons(). Aliens still expect that. . = ..() update_icons() diff --git a/code/modules/mob/living/carbon/alien/larva/larva_update_icons.dm b/code/modules/mob/living/carbon/alien/larva/larva_update_icons.dm index 4a939d61e6a..01d3615fe88 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva_update_icons.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva_update_icons.dm @@ -21,9 +21,9 @@ else icon_state = "larva[state]" -/mob/living/carbon/alien/larva/update_transform() //All this is handled in update_icons() - ..() - return update_icons() +/mob/living/carbon/alien/larva/perform_update_transform() //All this is handled in update_icons() + . = ..() + update_icons() /mob/living/carbon/alien/larva/update_inv_handcuffed() update_icons() //larva icon_state changes if cuffed/uncuffed. diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index fe5aa4a69bb..c775f5c6e3e 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -1,5 +1,5 @@ //IMPORTANT: Multiple animate() calls do not stack well, so try to do them all at once if you can. -/mob/living/carbon/update_transform() +/mob/living/carbon/perform_update_transform() var/matrix/ntransform = matrix(transform) //aka transform.Copy() var/final_pixel_y = pixel_y var/final_dir = dir diff --git a/code/modules/mob/living/living_update_icons.dm b/code/modules/mob/living/living_update_icons.dm new file mode 100644 index 00000000000..be0d4daab7f --- /dev/null +++ b/code/modules/mob/living/living_update_icons.dm @@ -0,0 +1,17 @@ + +/// Called whenever the mob is to be resized or when lying/standing up for carbons. +/mob/living/update_transform() + perform_update_transform() // carbon mobs do it differently than silicons and simple animals. + SEND_SIGNAL(src, COMSIG_LIVING_POST_UPDATE_TRANSFORM) // ...and we want the signal to be sent last. + +/mob/living/proc/perform_update_transform() + var/matrix/ntransform = matrix(transform) //aka transform.Copy() + var/changed = FALSE + + if(resize != RESIZE_DEFAULT_SIZE) + changed = TRUE + ntransform.Scale(resize) + resize = RESIZE_DEFAULT_SIZE + + if(changed) + animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 1e779abb539..a2c30affd8f 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -391,18 +391,6 @@ if (aicamera) return aicamera.selectpicture(user) -/mob/living/silicon/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/changed = 0 - if(resize != RESIZE_DEFAULT_SIZE) - changed++ - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2,easing = EASE_IN|EASE_OUT) - return ..() - /mob/living/silicon/is_literate() return TRUE diff --git a/code/modules/mob/living/simple_animal/hostile/bees.dm b/code/modules/mob/living/simple_animal/hostile/bees.dm index 9148760997c..24f2bcca5f1 100644 --- a/code/modules/mob/living/simple_animal/hostile/bees.dm +++ b/code/modules/mob/living/simple_animal/hostile/bees.dm @@ -36,7 +36,6 @@ move_to_delay = 0 obj_damage = 0 environment_smash = ENVIRONMENT_SMASH_NONE - mouse_opacity = MOUSE_OPACITY_OPAQUE pass_flags = PASSTABLE | PASSGRILLE | PASSMOB density = FALSE mob_size = MOB_SIZE_TINY @@ -65,6 +64,7 @@ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) generate_bee_visuals() AddElement(/datum/element/simple_flying) + AddComponent(/datum/component/clickbox, x_offset = -2, y_offset = -2) AddComponent(/datum/component/swarming) /mob/living/simple_animal/hostile/bee/mob_pickup(mob/living/L) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm index 912cadbaf0c..6a43f9bb966 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm @@ -8,7 +8,6 @@ icon_dead = "Hivelord_dead" icon_gib = "syndicate_gib" mob_biotypes = MOB_ORGANIC - mouse_opacity = MOUSE_OPACITY_OPAQUE move_to_delay = 14 ranged = 1 vision_range = 5 @@ -33,6 +32,12 @@ pass_flags = PASSTABLE loot = list(/obj/item/organ/regenerative_core) var/brood_type = /mob/living/simple_animal/hostile/asteroid/hivelordbrood + var/has_clickbox = TRUE + +/mob/living/simple_animal/hostile/asteroid/hivelord/Initialize() + . = ..() + if(has_clickbox) + AddComponent(/datum/component/clickbox, icon_state = "hivelord", max_scale = INFINITY, dead_state = "hivelord_dead") //they writhe so much. /mob/living/simple_animal/hostile/asteroid/hivelord/OpenFire(the_target) if(world.time >= ranged_cooldown) @@ -65,7 +70,6 @@ icon_aggro = "Hivelordbrood" icon_dead = "Hivelordbrood" icon_gib = "syndicate_gib" - mouse_opacity = MOUSE_OPACITY_OPAQUE move_to_delay = 1 friendly_verb_continuous = "buzzes near" friendly_verb_simple = "buzz near" @@ -87,12 +91,15 @@ pass_flags = PASSTABLE | PASSMOB density = FALSE del_on_death = 1 + var/clickbox_state = "hivelord" + var/clickbox_max_scale = INFINITY /mob/living/simple_animal/hostile/asteroid/hivelordbrood/Initialize() . = ..() addtimer(CALLBACK(src, .proc/death), 100) AddElement(/datum/element/simple_flying) AddComponent(/datum/component/swarming) + AddComponent(/datum/component/clickbox, icon_state = clickbox_state, max_scale = clickbox_max_scale) //Legion /mob/living/simple_animal/hostile/asteroid/hivelord/legion @@ -120,6 +127,7 @@ del_on_death = 1 stat_attack = HARD_CRIT robust_searching = 1 + has_clickbox = FALSE var/dwarf_mob = FALSE var/mob/living/carbon/human/stored_mob @@ -187,9 +195,10 @@ del_on_death = TRUE stat_attack = HARD_CRIT robust_searching = 1 + clickbox_state = "sphere" + clickbox_max_scale = 2 var/can_infest_dead = FALSE - /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/Life(delta_time = SSMOBS_DT, times_fired) . = ..() if(stat == DEAD || !isturf(loc)) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 3ed026e448b..20fa0be40b5 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -553,19 +553,6 @@ REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, RESTING_TRAIT) return ..() - -/mob/living/simple_animal/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/changed = FALSE - - if(resize != RESIZE_DEFAULT_SIZE) - changed = TRUE - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) - /mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion toggle_ai(AI_OFF) // To prevent any weirdness. can_have_ai = FALSE diff --git a/icons/misc/clickbox.dmi b/icons/misc/clickbox.dmi new file mode 100644 index 00000000000..1d371e23461 Binary files /dev/null and b/icons/misc/clickbox.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 458af4ab1e7..f018ed6612e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -566,6 +566,7 @@ #include "code\datums\components\butchering.dm" #include "code\datums\components\caltrop.dm" #include "code\datums\components\chasm.dm" +#include "code\datums\components\clickbox.dm" #include "code\datums\components\codeword_hearing.dm" #include "code\datums\components\combustible_flooder.dm" #include "code\datums\components\connect_loc_behalf.dm" @@ -2734,6 +2735,7 @@ #include "code\modules\mob\living\living_defines.dm" #include "code\modules\mob\living\living_movement.dm" #include "code\modules\mob\living\living_say.dm" +#include "code\modules\mob\living\living_update_icons.dm" #include "code\modules\mob\living\login.dm" #include "code\modules\mob\living\logout.dm" #include "code\modules\mob\living\status_procs.dm"