mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-13 11:12:14 +00:00
## About The Pull Request I recently played a game where I rotated my skeleton model while rotating my own character at the same time and it being in sync gave me the realization on how cool it would be if the Coroner was able to simply control the skeleton body. I find skeleton displays very funny and I want to see more funny things happen with them, so I thought this would be a good place to start, with the benefits that it also works on mannequins and statues too so they aren't left out. Basically, while it is unanchored, if you have a statue/mannequin grabbed, it will change its direction as you do, and speak the same words you do. Your own messages can only be heard if the person is directly next to you, revealing that it was you talking through it all along. I was originally gonna add this to the simple rotation component but moved off when I decided to add talking through it, I left in the code improvements I made to the component though since it is one of the oldest components and hasn't been touched in a while. Video demonstration (before I added the person also talking, just ignore that missing) https://github.com/tgstation/tgstation/assets/53777086/27242fc3-9649-418d-95cb-b31619319e97 While fixing the Toilet bong's rotation stuff I noticed a lot of it wasn't up to proper code standards so I went over it and fixed issues I had with it. It now doesn't give text saying you found something nasty to species that still likes mice (like flypeople), and fixed its update appearance to match the codebase standard set by the introduction of ``update_appearance`` many years ago. ## Why It's Good For The Game It's a funny small idea I had and got inspired to add, it's a niche mechanic that I think fits the aesthetic I am going for with Coroner and also give a funny interaction with the human-like inanimate objects. ## Changelog 🆑 fix: Species that can eat mice don't get disgusted from seeing one in the toilet bong. add: Grabbing an unwrenched statue/mannequin/skeleton model will now move its direction as you move yours, and you can talk through it. /🆑
138 lines
5.1 KiB
Plaintext
138 lines
5.1 KiB
Plaintext
/datum/component/simple_rotation
|
|
/// Additional stuff to do after rotation
|
|
var/datum/callback/post_rotation
|
|
/// Rotation flags for special behavior
|
|
var/rotation_flags = NONE
|
|
|
|
/**
|
|
* Adds the ability to rotate an object by Alt-click or using Right-click verbs.
|
|
*
|
|
* args:
|
|
* * rotation_flags (optional) Bitflags that determine behavior for rotation (defined at the top of this file)
|
|
* * post_rotation (optional) Callback proc that is used after the object is rotated (sound effects, balloon alerts, etc.)
|
|
**/
|
|
/datum/component/simple_rotation/Initialize(rotation_flags = NONE, post_rotation)
|
|
if(!ismovable(parent))
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
var/atom/movable/source = parent
|
|
source.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
|
|
|
|
src.rotation_flags = rotation_flags
|
|
src.post_rotation = post_rotation || CALLBACK(src, PROC_REF(default_post_rotation))
|
|
|
|
/datum/component/simple_rotation/RegisterWithParent()
|
|
RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(rotate_left))
|
|
RegisterSignal(parent, COMSIG_CLICK_ALT_SECONDARY, PROC_REF(rotate_right))
|
|
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(ExamineMessage))
|
|
RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item))
|
|
return ..()
|
|
|
|
/datum/component/simple_rotation/PostTransfer()
|
|
//Because of the callbacks which we don't track cleanly we can't transfer this
|
|
//item cleanly, better to let the new of the new item create a new rotation datum
|
|
//instead (there's no real state worth transferring)
|
|
return COMPONENT_NOTRANSFER
|
|
|
|
/datum/component/simple_rotation/UnregisterFromParent()
|
|
UnregisterSignal(parent, list(
|
|
COMSIG_CLICK_ALT,
|
|
COMSIG_CLICK_ALT_SECONDARY,
|
|
COMSIG_ATOM_EXAMINE,
|
|
COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM,
|
|
))
|
|
return ..()
|
|
|
|
/datum/component/simple_rotation/Destroy()
|
|
post_rotation = null
|
|
return ..()
|
|
|
|
/datum/component/simple_rotation/proc/ExamineMessage(datum/source, mob/user, list/examine_list)
|
|
SIGNAL_HANDLER
|
|
if(rotation_flags & ROTATION_REQUIRE_WRENCH)
|
|
examine_list += span_notice("This requires a wrench to be rotated.")
|
|
|
|
/datum/component/simple_rotation/proc/rotate_right(datum/source, mob/user)
|
|
SIGNAL_HANDLER
|
|
rotate(user, ROTATION_CLOCKWISE)
|
|
|
|
/datum/component/simple_rotation/proc/rotate_left(datum/source, mob/user)
|
|
SIGNAL_HANDLER
|
|
rotate(user, ROTATION_COUNTERCLOCKWISE)
|
|
|
|
/datum/component/simple_rotation/proc/rotate(mob/user, degrees)
|
|
if(QDELETED(user))
|
|
CRASH("[src] is being rotated [user ? "with a qdeleting" : "without a"] user")
|
|
if(!istype(user))
|
|
CRASH("[src] is being rotated without a user of the wrong type: [user.type]")
|
|
if(!isnum(degrees))
|
|
CRASH("[src] is being rotated without providing the amount of degrees needed")
|
|
|
|
if(!can_be_rotated(user, degrees) || !can_user_rotate(user, degrees))
|
|
return
|
|
|
|
var/obj/rotated_obj = parent
|
|
rotated_obj.setDir(turn(rotated_obj.dir, degrees))
|
|
if(rotation_flags & ROTATION_REQUIRE_WRENCH)
|
|
playsound(rotated_obj, 'sound/items/ratchet.ogg', 50, TRUE)
|
|
|
|
post_rotation.Invoke(user, degrees)
|
|
|
|
/datum/component/simple_rotation/proc/can_user_rotate(mob/user, degrees)
|
|
if(isliving(user) && user.can_perform_action(parent, NEED_DEXTERITY))
|
|
return TRUE
|
|
if((rotation_flags & ROTATION_GHOSTS_ALLOWED) && isobserver(user) && CONFIG_GET(flag/ghost_interaction))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/datum/component/simple_rotation/proc/can_be_rotated(mob/user, degrees, silent=FALSE)
|
|
var/obj/rotated_obj = parent
|
|
if(!rotated_obj.Adjacent(user))
|
|
silent = TRUE
|
|
|
|
if(rotation_flags & ROTATION_REQUIRE_WRENCH)
|
|
if(!isliving(user))
|
|
return FALSE
|
|
var/obj/item/tool = user.get_active_held_item()
|
|
if(!tool || tool.tool_behaviour != TOOL_WRENCH)
|
|
if(!silent)
|
|
rotated_obj.balloon_alert(user, "need a wrench!")
|
|
return FALSE
|
|
if(!(rotation_flags & ROTATION_IGNORE_ANCHORED) && rotated_obj.anchored)
|
|
if(istype(rotated_obj, /obj/structure/window) && !silent)
|
|
rotated_obj.balloon_alert(user, "need to unscrew!")
|
|
else if(!silent)
|
|
rotated_obj.balloon_alert(user, "need to unwrench!")
|
|
return FALSE
|
|
|
|
if(rotation_flags & ROTATION_NEEDS_ROOM)
|
|
var/target_dir = turn(rotated_obj.dir, degrees)
|
|
var/obj/structure/window/rotated_window = rotated_obj
|
|
var/fulltile = istype(rotated_window) ? rotated_window.fulltile : FALSE
|
|
if(!valid_build_direction(rotated_obj.loc, target_dir, is_fulltile = fulltile))
|
|
if(!silent)
|
|
rotated_obj.balloon_alert(user, "can't rotate in that direction!")
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/datum/component/simple_rotation/proc/default_post_rotation(mob/user, degrees)
|
|
return
|
|
|
|
// maybe we don't need the item context proc but instead the hand one? since we don't need to check held_item
|
|
/datum/component/simple_rotation/proc/on_requesting_context_from_item(atom/source, list/context, obj/item/held_item, mob/user)
|
|
SIGNAL_HANDLER
|
|
|
|
var/rotation_screentip = FALSE
|
|
|
|
if(can_be_rotated(user, ROTATION_CLOCKWISE, silent=TRUE))
|
|
context[SCREENTIP_CONTEXT_ALT_LMB] = "Rotate left"
|
|
rotation_screentip = TRUE
|
|
if(can_be_rotated(user, ROTATION_COUNTERCLOCKWISE, silent=TRUE))
|
|
context[SCREENTIP_CONTEXT_ALT_RMB] = "Rotate right"
|
|
rotation_screentip = TRUE
|
|
|
|
if(rotation_screentip)
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
return NONE
|