mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-09 07:57:00 +00:00
384 lines
14 KiB
Plaintext
384 lines
14 KiB
Plaintext
GLOBAL_LIST_EMPTY(all_cataloguers)
|
|
|
|
/*
|
|
This is a special scanner which exists to give explorers something to do besides shoot things.
|
|
The scanner is able to be used on certain things in the world, and after a variable delay, the scan finishes,
|
|
giving the person who scanned it some fluff and information about what they just scanned,
|
|
as well as points that currently do nothing but measure epeen,
|
|
and will be used as currency in The Future(tm) to buy things explorers care about.
|
|
|
|
Scanning hostile mobs and objects is tricky since only mobs that are alive are scannable, so scanning
|
|
them requires careful position to stay out of harms way until the scan finishes. That is why
|
|
the person with the scanner gets a visual box that shows where they are allowed to move to
|
|
without inturrupting the scan.
|
|
*/
|
|
/obj/item/cataloguer
|
|
name = "cataloguer"
|
|
desc = "A hand-held device, used for compiling information about an object by scanning it. Alt+click to highlight scannable objects around you."
|
|
description_info = "This is a special device used to obtain information about objects and entities in the environment. \
|
|
To scan something, click on it with the scanner at a distance. \
|
|
Scanning something requires remaining within a certain radius of the object for a specific period of time, until the \
|
|
scan is finished. If the scan is interrupted, it can be resumed from where it was left off, if the same thing is \
|
|
scanned again."
|
|
icon = 'icons/obj/device.dmi'
|
|
icon_state = "cataloguer"
|
|
w_class = ITEMSIZE_NORMAL
|
|
origin_tech = list(TECH_MATERIAL = 2, TECH_DATA = 3, TECH_MAGNET = 3)
|
|
force = 0
|
|
slot_flags = SLOT_BELT
|
|
var/points_stored = 0 // Amount of 'exploration points' this device holds.
|
|
var/scan_range = 3 // How many tiles away it can scan. Changing this also changes the box size.
|
|
var/credit_sharing_range = 280 // If another person is within this radius, they will also be credited with a successful scan. Original was 14
|
|
var/datum/category_item/catalogue/displayed_data = null // Used for viewing a piece of data in the UI.
|
|
var/busy = FALSE // Set to true when scanning, to stop multiple scans.
|
|
var/debug = FALSE // If true, can view all catalogue data defined, regardless of unlock status.
|
|
var/datum/weakref/partial_scanned = null // Weakref of the thing that was last scanned if inturrupted. Used to allow for partial scans to be resumed.
|
|
var/partial_scan_time = 0 // How much to make the next scan shorter.
|
|
|
|
/obj/item/cataloguer/advanced
|
|
name = "advanced cataloguer"
|
|
icon = 'icons/obj/device.dmi'
|
|
icon_state = "adv_cataloguer"
|
|
desc = "A hand-held device, used for compiling information about an object by scanning it. This one is an upgraded model, \
|
|
with a scanner that both can scan from farther away, and with less time."
|
|
scan_range = 4
|
|
toolspeed = 0.8
|
|
|
|
// Able to see all defined catalogue data regardless of if it was unlocked, intended for testing.
|
|
/obj/item/cataloguer/debug
|
|
name = "omniscient cataloguer"
|
|
desc = "A hand-held cataloguer device that appears to be plated with gold. For some reason, it \
|
|
just seems to already know everything about narrowly defined pieces of knowledge one would find \
|
|
from nearby, perhaps due to being colored gold. Truly a epistemological mystery."
|
|
icon = 'icons/obj/device.dmi'
|
|
icon_state = "debug_cataloguer"
|
|
toolspeed = 0.1
|
|
scan_range = 7
|
|
debug = TRUE
|
|
|
|
|
|
/obj/item/cataloguer/Initialize(mapload)
|
|
GLOB.all_cataloguers += src
|
|
return ..()
|
|
|
|
/obj/item/cataloguer/Destroy()
|
|
GLOB.all_cataloguers -= src
|
|
displayed_data = null
|
|
return ..()
|
|
|
|
/obj/item/cataloguer/update_icon()
|
|
if(busy)
|
|
icon_state = "[initial(icon_state)]_active"
|
|
else
|
|
icon_state = initial(icon_state)
|
|
|
|
/obj/item/cataloguer/afterattack(atom/target, mob/user, proximity_flag)
|
|
// Things that invalidate the scan immediately.
|
|
if(busy)
|
|
to_chat(user, span_warning("\The [src] is already scanning something."))
|
|
return
|
|
|
|
if(isturf(target) && (!target.can_catalogue()))
|
|
var/turf/T = target
|
|
for(var/atom/A as anything in T) // If we can't scan the turf, see if we can scan anything on it, to help with aiming.
|
|
if(A.can_catalogue())
|
|
target = A
|
|
break
|
|
|
|
if(!target.can_catalogue(user)) // This will tell the user what is wrong.
|
|
return
|
|
|
|
if(get_dist(target, user) > scan_range)
|
|
to_chat(user, span_warning("You are too far away from \the [target] to catalogue it. Get closer."))
|
|
return
|
|
|
|
// Get how long the delay will be.
|
|
var/scan_delay = target.get_catalogue_delay() * toolspeed
|
|
if(partial_scanned)
|
|
if(partial_scanned.resolve() == target)
|
|
scan_delay -= partial_scan_time
|
|
to_chat(user, span_notice("Resuming previous scan."))
|
|
else
|
|
to_chat(user, span_warning("Scanning new target. Previous scan buffer cleared."))
|
|
|
|
// Start the special effects.
|
|
busy = TRUE
|
|
update_icon()
|
|
var/datum/beam/scan_beam = user.Beam(target, icon_state = "rped_upgrade", time = scan_delay)
|
|
var/filter = filter(type = "outline", size = 1, color = "#FFFFFF")
|
|
target.filters += filter
|
|
var/list/box_segments = list()
|
|
if(user.client)
|
|
box_segments = draw_box(target, scan_range, user.client)
|
|
color_box(box_segments, "#00FFFF", scan_delay)
|
|
|
|
playsound(src, 'sound/machines/beep.ogg', 50)
|
|
|
|
// The delay, and test for if the scan succeeds or not.
|
|
var/scan_start_time = world.time
|
|
if(do_after(user, scan_delay, target, timed_action_flags = IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE, max_distance = scan_range))
|
|
if(target.can_catalogue(user))
|
|
to_chat(user, span_notice("You successfully scan \the [target] with \the [src]."))
|
|
playsound(src, 'sound/machines/ping.ogg', 50)
|
|
catalogue_object(target, user)
|
|
else
|
|
// In case someone else scans it first, or it died, etc.
|
|
to_chat(user, span_warning("\The [target] is no longer valid to scan with \the [src]."))
|
|
playsound(src, 'sound/machines/buzz-two.ogg', 50)
|
|
|
|
partial_scanned = null
|
|
partial_scan_time = 0
|
|
else
|
|
to_chat(user, span_warning("You failed to finish scanning \the [target] with \the [src]."))
|
|
playsound(src, 'sound/machines/buzz-two.ogg', 50)
|
|
color_box(box_segments, "#FF0000", 3)
|
|
partial_scanned = WEAKREF(target)
|
|
partial_scan_time += world.time - scan_start_time // This is added to the existing value so two partial scans will add up correctly.
|
|
sleep(3)
|
|
busy = FALSE
|
|
|
|
// Now clean up the effects.
|
|
update_icon()
|
|
QDEL_NULL(scan_beam)
|
|
if(target)
|
|
target.filters -= filter
|
|
if(user.client) // If for some reason they logged out mid-scan the box will be gone anyways.
|
|
delete_box(box_segments, user.client)
|
|
|
|
// Todo: Display scanned information, increment points, etc.
|
|
/obj/item/cataloguer/proc/catalogue_object(atom/target, mob/living/user)
|
|
// Figure out who may have helped out.
|
|
var/list/contributers = list()
|
|
var/list/contributer_names = list()
|
|
for(var/mob/living/L as anything in GLOB.player_list)
|
|
if(L == user)
|
|
continue
|
|
if(!istype(L))
|
|
continue
|
|
if(get_dist(L, user) <= credit_sharing_range)
|
|
contributers += L
|
|
contributer_names += L.name
|
|
|
|
var/points_gained = 0
|
|
|
|
// Discover each datum available.
|
|
var/list/object_data = target.get_catalogue_data()
|
|
if(LAZYLEN(object_data))
|
|
for(var/data_type in object_data)
|
|
var/datum/category_item/catalogue/I = GLOB.catalogue_data.resolve_item(data_type)
|
|
if(istype(I))
|
|
var/list/discoveries = I.discover(user, list(user.name) + contributer_names) // If one discovery leads to another, the list returned will have all of them.
|
|
if(LAZYLEN(discoveries))
|
|
for(var/datum/category_item/catalogue/data as anything in discoveries)
|
|
points_gained += data.value
|
|
|
|
// Give out points.
|
|
if(points_gained)
|
|
// First, to us.
|
|
to_chat(user, span_notice("Gained [points_gained] points from this scan."))
|
|
adjust_points(points_gained)
|
|
|
|
// Now to our friends, if any.
|
|
if(contributers.len)
|
|
for(var/mob/M in contributers)
|
|
var/list/things = M.GetAllContents(3) // Depth of two should reach into bags but just in case lets make it three.
|
|
var/obj/item/cataloguer/other_cataloguer = locate() in things // If someone has two or more scanners this only adds points to one.
|
|
if(other_cataloguer)
|
|
to_chat(M, span_notice("Gained [points_gained] points from \the [user]'s scan of \the [target]."))
|
|
other_cataloguer.adjust_points(points_gained)
|
|
to_chat(user, span_notice("Shared discovery with [contributers.len] other contributer\s."))
|
|
|
|
|
|
|
|
|
|
/obj/item/cataloguer/click_alt(mob/user)
|
|
pulse_scan(user)
|
|
|
|
// Gives everything capable of being scanned an outline for a brief moment.
|
|
// Helps to avoid having to click a hundred things in a room for things that have an entry.
|
|
/obj/item/cataloguer/proc/pulse_scan(mob/user)
|
|
if(busy)
|
|
to_chat(user, span_warning("\The [src] is busy doing something else."))
|
|
return
|
|
|
|
busy = TRUE
|
|
update_icon()
|
|
playsound(src, 'sound/machines/beep.ogg', 50)
|
|
|
|
// First, get everything able to be scanned.
|
|
var/list/scannable_atoms = list()
|
|
for(var/atom/A as anything in view(world.view, user))
|
|
if(A.can_catalogue()) // Not passing the user is intentional, so they don't get spammed.
|
|
scannable_atoms += A
|
|
|
|
// Highlight things able to be scanned.
|
|
var/filter = filter(type = "outline", size = 1, color = "#00FF00")
|
|
for(var/atom/A as anything in scannable_atoms)
|
|
A.filters += filter
|
|
to_chat(user, span_notice("\The [src] is highlighting scannable objects in green, if any exist."))
|
|
|
|
sleep(2 SECONDS)
|
|
|
|
// Remove the highlights.
|
|
for(var/atom/A as anything in scannable_atoms)
|
|
if(QDELETED(A))
|
|
continue
|
|
A.filters -= filter
|
|
|
|
busy = FALSE
|
|
update_icon()
|
|
if(scannable_atoms.len)
|
|
playsound(src, 'sound/machines/ping.ogg', 50)
|
|
else
|
|
playsound(src, 'sound/machines/buzz-two.ogg', 50)
|
|
to_chat(user, span_notice("\The [src] found [scannable_atoms.len] object\s that can be scanned."))
|
|
|
|
|
|
// Negative points are bad.
|
|
/obj/item/cataloguer/proc/adjust_points(amount)
|
|
points_stored = max(0, points_stored += amount)
|
|
|
|
/obj/item/cataloguer/attack_self(mob/living/user)
|
|
interact(user)
|
|
|
|
/obj/item/cataloguer/interact(mob/user)
|
|
var/list/dat = list()
|
|
var/title = "Cataloguer Data Display"
|
|
|
|
// Important buttons go on top since the scrollbar will default to the top of the window.
|
|
dat += "Contains <b>[points_stored]</b> Exploration Points."
|
|
dat += "<a href='byond://?src=\ref[src];pulse_scan=1'>\[Highlight Scannables\]</a><a href='byond://?src=\ref[src];refresh=1'>\[Refresh\]</a><a href='byond://?src=\ref[src];close=1'>\[Close\]</a>"
|
|
|
|
// If displayed_data exists, we show that, otherwise we show a list of all data in the mysterious global list.
|
|
if(displayed_data)
|
|
title = uppertext(displayed_data.name)
|
|
|
|
dat += "<a href='byond://?src=\ref[src];show_data=null'>\[Back to List\]</a>"
|
|
if(debug && !displayed_data.visible)
|
|
dat += "<a href='byond://?src=\ref[src];debug_unlock=\ref[displayed_data]'>\[(DEBUG) Force Discovery\]</a>"
|
|
dat += "<hr>"
|
|
|
|
dat += span_italics("[displayed_data.desc]")
|
|
if(LAZYLEN(displayed_data.cataloguers))
|
|
dat += "Cataloguers : <b>[english_list(displayed_data.cataloguers)]</b>."
|
|
else
|
|
dat += "Catalogued by nobody."
|
|
dat += "Worth <b>[displayed_data.value]</b> exploration points."
|
|
|
|
else
|
|
dat += "<hr>"
|
|
for(var/datum/category_group/group as anything in GLOB.catalogue_data.categories)
|
|
var/list/group_dat = list()
|
|
var/show_group = FALSE
|
|
|
|
group_dat += span_bold("[group.name]")
|
|
for(var/datum/category_item/catalogue/item as anything in group.items)
|
|
if(item.visible || debug)
|
|
group_dat += "<a href='byond://?src=\ref[src];show_data=\ref[item]'>[item.name]</a>"
|
|
show_group = TRUE
|
|
|
|
if(show_group || debug) // Avoid showing 'empty' groups on regular cataloguers.
|
|
dat += group_dat
|
|
|
|
var/datum/browser/popup = new(user, "cataloguer_display_\ref[src]", title, 500, 600, src)
|
|
popup.set_content(dat.Join("<br>"))
|
|
popup.open()
|
|
add_fingerprint(user)
|
|
|
|
/obj/item/cataloguer/Topic(href, href_list)
|
|
if(..())
|
|
usr << browse(null, "window=cataloguer_display")
|
|
return 0
|
|
if(href_list["close"] )
|
|
usr << browse(null, "window=cataloguer_display")
|
|
return 0
|
|
|
|
if(href_list["show_data"])
|
|
displayed_data = locate(href_list["show_data"])
|
|
|
|
if(href_list["pulse_scan"])
|
|
pulse_scan(usr)
|
|
return // Don't refresh the window for this or it will open it back if its closed during the highlighting.
|
|
|
|
if(href_list["debug_unlock"] && debug)
|
|
var/datum/category_item/catalogue/item = locate(href_list["debug_unlock"])
|
|
item.discover(usr, list("Debugger"))
|
|
|
|
interact(usr) // So it refreshes the window.
|
|
return 1
|
|
|
|
/obj/item/cataloguer/attackby(obj/item/W, mob/user)
|
|
if(istype(W, /obj/item/card/id) && !busy)
|
|
busy = TRUE
|
|
var/obj/item/card/id/ID = W
|
|
if(points_stored)
|
|
ID.survey_points += points_stored
|
|
points_stored = 0
|
|
to_chat(user, span_notice("You swipe the id over \the [src]."))
|
|
else
|
|
to_chat(user, span_notice("\The [src] has no points available."))
|
|
busy = FALSE
|
|
return ..()
|
|
|
|
/obj/item/cataloguer/compact
|
|
name = "compact cataloguer"
|
|
desc = "A compact hand-held device, used for compiling information about an object by scanning it. \
|
|
Alt+click to highlight scannable objects around you."
|
|
icon = 'icons/obj/device.dmi'
|
|
icon_state = "compact"
|
|
actions_types = list(/datum/action/item_action/toggle_cataloguer)
|
|
var/deployed = TRUE
|
|
scan_range = 1
|
|
toolspeed = 1.2
|
|
|
|
/obj/item/cataloguer/compact/pathfinder
|
|
name = "pathfinder's cataloguer"
|
|
desc = "A compact hand-held device, used for compiling information about an object by scanning it. \
|
|
Alt+click to highlight scannable objects around you."
|
|
icon = 'icons/obj/device.dmi'
|
|
icon_state = "pathcat"
|
|
scan_range = 3
|
|
toolspeed = 1
|
|
|
|
/obj/item/cataloguer/compact/update_icon()
|
|
if(busy)
|
|
icon_state = "[initial(icon_state)]_s"
|
|
else
|
|
icon_state = initial(icon_state)
|
|
|
|
/obj/item/cataloguer/compact/ui_action_click(mob/user, actiontype)
|
|
toggle()
|
|
|
|
/obj/item/cataloguer/compact/verb/toggle()
|
|
set name = "Toggle Cataloguer"
|
|
set category = "Object"
|
|
|
|
if(busy)
|
|
to_chat(usr, span_warning("\The [src] is currently scanning something."))
|
|
return
|
|
deployed = !(deployed)
|
|
if(deployed)
|
|
w_class = ITEMSIZE_NORMAL
|
|
icon_state = "[initial(icon_state)]"
|
|
to_chat(usr, span_notice("You flick open \the [src]."))
|
|
else
|
|
w_class = ITEMSIZE_SMALL
|
|
icon_state = "[initial(icon_state)]_closed"
|
|
to_chat(usr, span_notice("You close \the [src]."))
|
|
|
|
if (ismob(usr))
|
|
var/mob/M = usr
|
|
M.update_mob_action_buttons()
|
|
|
|
/obj/item/cataloguer/compact/afterattack(atom/target, mob/user, proximity_flag)
|
|
if(!deployed)
|
|
to_chat(user, span_warning("\The [src] is closed."))
|
|
return
|
|
return ..()
|
|
|
|
/obj/item/cataloguer/compact/pulse_scan(mob/user)
|
|
if(!deployed)
|
|
to_chat(user, span_warning("\The [src] is closed."))
|
|
return
|
|
return ..()
|