mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-26 20:57:06 +01:00
341 lines
11 KiB
Plaintext
341 lines
11 KiB
Plaintext
//* This file is explicitly licensed under the MIT license. *//
|
|
//* Copyright (c) 2025 Citadel Station Developers *//
|
|
|
|
/atom/movable/laser_designator_target
|
|
name = "rangefinding laser"
|
|
desc = "Stop staring at the laser and start staring at the sky, because \
|
|
something's probably about to fall out of it."
|
|
plane = ABOVE_LIGHTING_PLANE
|
|
layer = ABOVE_LIGHTING_LAYER_MAIN
|
|
icon = 'icons/modules/sectors/air_support/rangefinder.dmi'
|
|
icon_state = "turf-laser"
|
|
/// allow weapons guidance
|
|
// TODO: unimplemented
|
|
var/allow_weapons_guidance = FALSE
|
|
/// we are visible
|
|
var/visible_dot
|
|
|
|
/atom/movable/laser_designator_target/Initialize(mapload, allow_weapons_guidance, visible_dot)
|
|
src.allow_weapons_guidance = allow_weapons_guidance
|
|
src.visible_dot = visible_dot
|
|
alpha = visible_dot ? initial(alpha) : 0
|
|
|
|
var/datum/component/high_altitude_signal/signal_comp = AddComponent(/datum/component/high_altitude_signal, "rangefinding laser")
|
|
signal_comp.on_get_effective_turf = CALLBACK(src, PROC_REF(get_effective_turf))
|
|
signal_comp.require_effective_turf_high_altitude_visibility = TRUE
|
|
|
|
if(ismovable(loc))
|
|
var/atom/movable/thingy = loc
|
|
thingy.vis_contents += src
|
|
return ..()
|
|
|
|
/atom/movable/laser_designator_target/Destroy()
|
|
if(ismovable(loc))
|
|
var/atom/movable/thingy = loc
|
|
thingy.vis_contents -= src
|
|
return ..()
|
|
|
|
/atom/movable/laser_designator_target/proc/get_effective_turf()
|
|
SIGNAL_HANDLER
|
|
if(isturf(loc))
|
|
return loc
|
|
if(ismovable(loc) && isturf(loc.loc))
|
|
return loc.loc
|
|
return null
|
|
|
|
/atom/movable/laser_designator_target/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
|
..()
|
|
if(ismovable(old_loc))
|
|
var/atom/movable/old_thingy = old_loc
|
|
old_thingy.vis_contents -= src
|
|
if(ismovable(loc))
|
|
var/atom/movable/new_thingy = loc
|
|
new_thingy.vis_contents += src
|
|
|
|
/**
|
|
* citadel rp's rangefinders / LDs work somewhat differently from CM's
|
|
*
|
|
* mostly because we're not balancing this for CAS, we're balancing this for utility functions
|
|
*
|
|
* short list:
|
|
*
|
|
* * the zoom works very differently
|
|
* * LDs can track targets
|
|
* * LDs are not usually visible to the target
|
|
* * (backend) rangefinders are designed in a way to be usable as a component of another object, which is usually not what i do for this use case but whatever
|
|
*/
|
|
/obj/item/rangefinder
|
|
name = "rangefinder"
|
|
desc = "A handy pair of binoculars used to perform rangefinding."
|
|
icon = 'icons/modules/sectors/air_support/rangefinder.dmi'
|
|
icon_state = "rangefinder"
|
|
base_icon_state = "rangefinder"
|
|
weight_volume = WEIGHT_VOLUME_SMALL
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
|
|
/// can be used as a rangefinder
|
|
var/is_rangefinder = TRUE
|
|
/// can be used as a laser designator
|
|
var/is_designator = FALSE
|
|
|
|
/// laser designation time
|
|
var/laser_time = 7.5 SECONDS
|
|
/// laser designation is visible to target
|
|
///
|
|
/// TODO: laser designation should be visible to user if this is off
|
|
var/laser_visible = TRUE
|
|
/// allow weapon guidance - if you turn this on without turning on laser_visible i will replace your eyelids with lemons
|
|
var/laser_weapons_guidance = TRUE
|
|
|
|
var/drop_laser_on_unzoom = FALSE
|
|
var/drop_laser_on_move = TRUE
|
|
|
|
/// active laser designator target
|
|
var/atom/movable/laser_designator_target/active_laser_target
|
|
var/turf/active_laser_last_self_turf
|
|
|
|
/// active icon dot color
|
|
var/icon_dot_color
|
|
|
|
/// currently being zoomed
|
|
/// * THIS IS NOT NECESSARILY THE PERSON HOLDING US.
|
|
var/mob/currently_zoomed_in
|
|
|
|
var/zoom_range_in_tiles = 21
|
|
|
|
var/sfx_lasing_start = /datum/soundbyte/nightvision_chargeup
|
|
var/sfx_lasing_start_vol = 35
|
|
var/sfx_lasing_start_vary = FALSE
|
|
|
|
var/sfx_lasing_lock = /datum/soundbyte/target_lock_1
|
|
var/sfx_lasing_lock_vol = 35
|
|
var/sfx_lasing_lock_vary = FALSE
|
|
|
|
var/last_laser_retrace
|
|
|
|
var/atom/lasing_target
|
|
var/datum/event_args/actor/lasing_actor
|
|
|
|
/obj/item/rangefinder/Initialize(mapload)
|
|
. = ..()
|
|
update_icon()
|
|
|
|
/obj/item/rangefinder/Destroy()
|
|
QDEL_NULL(active_laser_target)
|
|
stop_zooming()
|
|
STOP_PROCESSING(SSprocessing, src)
|
|
return ..()
|
|
|
|
/obj/item/rangefinder/update_icon()
|
|
cut_overlays()
|
|
if(active_laser_target)
|
|
icon_dot_color = "#ff0000"
|
|
else if(is_rangefinder || is_designator)
|
|
icon_dot_color = "#00aa00"
|
|
var/new_inhand_state = "[base_icon_state][currently_zoomed_in ? "_eyes" : ""]"
|
|
if(new_inhand_state != inhand_state)
|
|
inhand_state = new_inhand_state
|
|
update_worn_icon()
|
|
. = ..()
|
|
if(icon_dot_color)
|
|
var/image/dot_overlay = image(icon, "[base_icon_state]-dot")
|
|
dot_overlay.color = icon_dot_color
|
|
add_overlay(dot_overlay)
|
|
|
|
/obj/item/rangefinder/proc/set_icon_dot_color(new_color)
|
|
icon_dot_color = new_color
|
|
update_icon()
|
|
|
|
/obj/item/rangefinder/on_attack_self(datum/event_args/actor/e_args)
|
|
. = ..()
|
|
if(. & CLICKCHAIN_FLAGS_INTERACT_ABORT)
|
|
return
|
|
// listen no TK fuckery allowed
|
|
if(e_args.performer != inv_inside.owner)
|
|
return
|
|
if(currently_zoomed_in == e_args.performer)
|
|
stop_zooming()
|
|
else
|
|
start_zooming(inv_inside.owner)
|
|
|
|
/obj/item/rangefinder/on_inv_unequipped(mob/wearer, datum/inventory/inventory, slot_id_or_index, inv_op_flags, datum/event_args/actor/actor)
|
|
stop_zooming()
|
|
..()
|
|
|
|
/obj/item/rangefinder/proc/create_laser_designator_target(atom/target)
|
|
if(active_laser_target)
|
|
destroy_laser_designator_target()
|
|
active_laser_target = new(target, laser_weapons_guidance, laser_visible)
|
|
active_laser_last_self_turf = get_turf(src)
|
|
START_PROCESSING(SSprocessing, src)
|
|
|
|
/obj/item/rangefinder/proc/destroy_laser_designator_target()
|
|
if(!active_laser_target)
|
|
return
|
|
if(!QDELETED(active_laser_target))
|
|
qdel(active_laser_target)
|
|
active_laser_target = null
|
|
STOP_PROCESSING(SSprocessing, src)
|
|
|
|
/obj/item/rangefinder/proc/on_laser_designator_target_deleted(datum/source)
|
|
destroy_laser_designator_target()
|
|
|
|
/obj/item/rangefinder/proc/reconsider_laser_designation()
|
|
if(drop_laser_on_move)
|
|
if(get_turf(src) != active_laser_last_self_turf)
|
|
destroy_laser_designator_target()
|
|
return
|
|
active_laser_last_self_turf = get_turf(src)
|
|
var/turf/target_turf = get_turf(active_laser_target)
|
|
var/list/turf/traced = trace_laser_designation(target_turf)
|
|
last_laser_retrace = world.time
|
|
for(var/turf/T as anything in traced)
|
|
if(T.has_opaque_atom)
|
|
destroy_laser_designator_target()
|
|
return
|
|
|
|
/obj/item/rangefinder/proc/trace_laser_designation(turf/target) as /list
|
|
var/turf/our_turf = get_turf(src)
|
|
if(!our_turf)
|
|
return null
|
|
return getline(our_turf, target)
|
|
|
|
/obj/item/rangefinder/proc/start_zooming(mob/viewing)
|
|
if(currently_zoomed_in)
|
|
stop_zooming()
|
|
currently_zoomed_in = viewing
|
|
currently_zoomed_in.AddComponent(/datum/component/mob_zoom_binding/freezoom, null, null, 14)
|
|
currently_zoomed_in.visible_message(
|
|
SPAN_NOTICE("[currently_zoomed_in] looks through [src]."),
|
|
SPAN_NOTICE("You start looking through [src]."),
|
|
range = MESSAGE_RANGE_COMBAT_SUBTLE,
|
|
)
|
|
update_icon()
|
|
return TRUE
|
|
|
|
/obj/item/rangefinder/proc/stop_zooming()
|
|
if(!currently_zoomed_in)
|
|
return
|
|
if(drop_laser_on_unzoom)
|
|
destroy_laser_designator_target()
|
|
currently_zoomed_in.DelComponent(/datum/component/mob_zoom_binding)
|
|
currently_zoomed_in.visible_message(
|
|
SPAN_NOTICE("[currently_zoomed_in] looks up from [src]."),
|
|
SPAN_NOTICE("You stop looking through [src]."),
|
|
range = MESSAGE_RANGE_COMBAT_SUBTLE,
|
|
)
|
|
currently_zoomed_in = null
|
|
update_icon()
|
|
|
|
/obj/item/rangefinder/afterattack(atom/target, mob/user, clickchain_flags, list/params)
|
|
. = ..()
|
|
if(. & CLICKCHAIN_DO_NOT_PROPAGATE)
|
|
return
|
|
if(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY)
|
|
return
|
|
to_chat(user, SPAN_NOTICE("[icon2html(src, user)] [rangefinder_output(user, target)]"))
|
|
|
|
/obj/item/rangefinder/proc/rangefinder_output(mob/user, atom/target)
|
|
var/turf/source_turf = get_turf(user)
|
|
var/turf/target_turf = get_turf(target)
|
|
var/list/coords = SSmapping.get_virtual_coords_x_y(target_turf)
|
|
var/dist = target.z == user.z ? get_turf_euclidean_dist(source_turf, target_turf) : SSmapping.get_virtual_horizontal_euclidean_dist(source_turf, target_turf)
|
|
return "rangefinder estimate [round(dist, 0.1)]m @ [coords[1]]x, [coords[2]]y"
|
|
|
|
/obj/item/rangefinder/item_ctrl_click_interaction_chain(datum/event_args/actor/clickchain/clickchain, clickchain_flags, obj/item/active_item)
|
|
. = ..()
|
|
if(. & CLICKCHAIN_FLAGS_INTERACT_ABORT)
|
|
return
|
|
if(!is_designator)
|
|
return .
|
|
attempt_clickchain_laser_designate(clickchain, clickchain_flags)
|
|
return . | CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
|
|
|
|
/**
|
|
* this doesn't check if we're a laser designator!
|
|
*/
|
|
/obj/item/rangefinder/proc/attempt_clickchain_laser_designate(datum/event_args/actor/clickchain/clickchain, clickchain_flags)
|
|
// already lasing
|
|
if(lasing_target)
|
|
clickchain.chat_feedback(
|
|
SPAN_WARNING("[src] is busy!"),
|
|
target = src,
|
|
)
|
|
return
|
|
if(active_laser_target)
|
|
destroy_laser_designator_target()
|
|
|
|
var/list/traced
|
|
var/turf/target_turf
|
|
var/atom/target = clickchain.target
|
|
|
|
target_turf = get_turf(target)
|
|
traced = trace_laser_designation(target_turf)
|
|
|
|
var/no_los = FALSE
|
|
for(var/turf/T as anything in traced)
|
|
if(T.has_opaque_atom)
|
|
no_los = TRUE
|
|
break
|
|
|
|
if(no_los)
|
|
clickchain.chat_feedback(
|
|
SPAN_WARNING("Your line of sight to [target] is blocked by something."),
|
|
target = src,
|
|
)
|
|
return TRUE
|
|
|
|
var/effective_delay = laser_time
|
|
|
|
clickchain.visible_feedback(
|
|
target = src,
|
|
range = MESSAGE_RANGE_COMBAT_SUBTLE,
|
|
visible = SPAN_WARNING("[currently_zoomed_in] levels [src] and begins to aim its targeting laser."),
|
|
otherwise_self = SPAN_WARNING("You start [src]'s target acquisition process, aiming at [target]."),
|
|
)
|
|
|
|
if(effective_delay > 0.5 SECONDS)
|
|
playsound(src, sfx_lasing_start, sfx_lasing_start_vol, sfx_lasing_start_vary, -4)
|
|
|
|
lasing_target = target
|
|
lasing_actor = clickchain
|
|
do
|
|
. = do_after(clickchain.performer, effective_delay, src, additional_checks = CALLBACK(src, PROC_REF(continue_to_laser)))
|
|
while(FALSE)
|
|
lasing_actor = null
|
|
lasing_target = null
|
|
if(!.)
|
|
return
|
|
|
|
clickchain.chat_feedback(
|
|
// TODO: the icon2html is bad
|
|
SPAN_WARNING("[icon2html(src, clickchain.performer)]: Target locked, currently at [rangefinder_output(clickchain.performer, target)].")
|
|
)
|
|
|
|
create_laser_designator_target(target)
|
|
|
|
playsound(src, sfx_lasing_lock, sfx_lasing_lock_vol, sfx_lasing_lock_vary, -4)
|
|
|
|
/obj/item/rangefinder/proc/continue_to_laser(list/do_after_args)
|
|
if(world.time < last_laser_retrace + 0.35 SECONDS)
|
|
return TRUE
|
|
last_laser_retrace = world.time
|
|
var/list/turfs = trace_laser_designation(lasing_target)
|
|
for(var/turf/T as anything in turfs)
|
|
if(T.has_opaque_atom)
|
|
lasing_actor?.chat_feedback(
|
|
SPAN_WARNING("Something on [T] breaks your targeting laser's beam."),
|
|
target = T,
|
|
)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/item/rangefinder/process(delta_time)
|
|
if(active_laser_target)
|
|
reconsider_laser_designation()
|
|
|
|
/obj/item/rangefinder/laser_designator
|
|
name = "laser designator"
|
|
desc = "An upgraded rangefinder that can mark an entity with a laser beam visible from high altitude."
|
|
is_designator = TRUE
|