mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
## About The Pull Request In similar fashion to what was done with crafting recipes last year, this year it's time for techweb designs and printed items to be audited. This is mostly just about consistency, we've a lot of items that can be printed by protolathes, autolathes, circuit printers and techfab etc. However they almost all (except most stacks, mainly) have custom materials that do not match in one way or another with the materials used by the design, which is what this PR is for. "But items printed from lathes etc. already get the mats used to make them." Yes, they do, however that isn't the case for items of the same type that were spawned in some other way (cargo shuttle, space/maints loot, mapped, admins), this create a subtle discrepancy. It isn't a huge deal (in spite of the size of this PR, ton of designs), but given that I have done something similar with crafting recipes before, I may as well give it a second arc of some sort and bring things to completion. And fix a few possible oversights. TL;DR consistency and stuff ## Why It's Good For The Game Consistency, unit test checks to make it harder not to be consistent in the future. Still has a few TODOs like: - [x] Fixed newly printed, fully charged RCDs costing less than the RCD cartridges required to fully charge one. EDIT: I had to tweak the newly added RDD as well because it suffered from the same fundamental issue. - [x] Fixed plates being made of iron and yet shattering like ceramic ones. A new subtype for metallic ones has been made. ## Changelog 🆑 refactor: Refactored a few things with techweb designs (the ones for autolathes, protolathes, circuit printers, mechfabs etc.) to make sure that the materials of items that can be made from these designs more closely match the materials used to make them. fix: Lizard fries no longer need a plate to be made, like all other treats that used to require plates in a distant past. balance: Tweaked the materials cost of RCD, RDD and RCD cartridges. image: Oven trays now have a more metallic hue. balance: Plates printed printed from lathes won't shatter like ceramic ones, in virtue of them being made out of iron instead. /🆑
465 lines
15 KiB
Plaintext
465 lines
15 KiB
Plaintext
/obj/item/camera
|
|
name = "camera"
|
|
icon = 'icons/obj/art/camera.dmi'
|
|
desc = "A polaroid camera."
|
|
icon_state = "camera"
|
|
base_icon_state = "camera"
|
|
inhand_icon_state = "camera"
|
|
worn_icon_state = "camera"
|
|
lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
|
|
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
|
|
light_system = NONE
|
|
light_range = 6
|
|
light_color = COLOR_WHITE
|
|
light_power = FLASH_LIGHT_POWER
|
|
light_on = FALSE
|
|
w_class = WEIGHT_CLASS_SMALL
|
|
obj_flags = CONDUCTS_ELECTRICITY
|
|
slot_flags = ITEM_SLOT_NECK
|
|
custom_materials = list(/datum/material/glass = SMALL_MATERIAL_AMOUNT, /datum/material/iron = SMALL_MATERIAL_AMOUNT * 0.5)
|
|
custom_price = PAYCHECK_CREW * 2
|
|
|
|
/// Cooldown before we can take another picture.
|
|
var/cooldown = 6.4 SECONDS
|
|
/// Whether we are currently ready to take a picture.
|
|
var/on = TRUE
|
|
/// Whether we are still processing an image.
|
|
var/blending = FALSE
|
|
/// The maximum amount of pictures we can take before needing new film.
|
|
var/pictures_max = 10
|
|
/// The amount of pictures we can still take before needing new film.
|
|
var/pictures_left = 10
|
|
/// Currently inserted holorecord disk.
|
|
var/obj/item/disk/holodisk/disk
|
|
///Boolean on whether or not the camera will print in monochrome.
|
|
var/print_monochrome = FALSE
|
|
/// Whether we flash upon taking a picture.
|
|
var/flash_enabled = TRUE
|
|
/// Whether we silence our picture taking and zoom adjusting sounds.
|
|
var/silent = FALSE
|
|
/// To what degree ghosts are visible in our pictures.
|
|
var/see_ghosts = CAMERA_NO_GHOSTS
|
|
/// Whether the camera should print pictures immediately when a picture is taken.
|
|
var/print_picture_on_snap = TRUE
|
|
/// Whether we allow setting picture label/desc/scribble when a picture is taken.
|
|
var/can_customise = TRUE
|
|
/// Picture name we default to when none is set manually.
|
|
var/default_picture_name
|
|
///Width of the picture
|
|
var/picture_size_x = 2
|
|
///height of the picture
|
|
var/picture_size_y = 2
|
|
///Internal holder to apply camera light on
|
|
VAR_PRIVATE/atom/movable/light_holder
|
|
|
|
/// Special type of component so it does not intefer with the modular computer default lighting system if any
|
|
/datum/component/overlay_lighting/camera
|
|
dupe_mode = COMPONENT_DUPE_SOURCES
|
|
|
|
/obj/item/camera/Initialize(mapload)
|
|
. = ..()
|
|
|
|
//we do this so if this camera is used as an internal component, the flash will still be visible
|
|
if(flash_enabled)
|
|
var/atom/movable/parent = loc
|
|
light_holder = src
|
|
while(!(isnull(parent) || ismob(parent) || isturf(parent)))
|
|
light_holder = parent
|
|
parent = light_holder.loc
|
|
light_holder.AddComponentFrom(REF(src), /datum/component/overlay_lighting/camera, light_range, light_power, light_color, FALSE, TRUE, FALSE, TRUE)
|
|
|
|
AddComponent(/datum/component/shell, list(new /obj/item/circuit_component/camera, new /obj/item/circuit_component/remotecam/polaroid), SHELL_CAPACITY_SMALL)
|
|
register_context()
|
|
|
|
/obj/item/camera/Destroy(force)
|
|
if(light_holder)
|
|
light_holder.RemoveComponentSource(REF(src), /datum/component/overlay_lighting/camera)
|
|
light_holder = null
|
|
return ..()
|
|
|
|
/obj/item/camera/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
|
|
context[SCREENTIP_CONTEXT_ALT_LMB] = "Adjust Zoom"
|
|
context[SCREENTIP_CONTEXT_CTRL_LMB] = "Change Printer Mode"
|
|
|
|
if(istype(held_item, /obj/item/camera_film))
|
|
context[SCREENTIP_CONTEXT_LMB] = "Insert Film"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if(istype(held_item, /obj/item/disk/holodisk))
|
|
context[SCREENTIP_CONTEXT_LMB] = disk ? "Swap Disks" : "Insert Disk"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if((isnull(held_item) || (held_item == src)) && disk)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Eject Disk"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
/obj/item/camera/examine(mob/user)
|
|
. = ..()
|
|
. += span_notice("It has [pictures_left] photos left.")
|
|
. += span_notice("Alt-click to change its focusing, allowing you to set how big of an area it will capture.")
|
|
. += span_notice("Ctrl-click to change the printer between color and monochrome.")
|
|
. += span_notice("The present dimensions of the picture are [EXAMINE_HINT("[APERTURE_TO_METERS(picture_size_x)]x[APERTURE_TO_METERS(picture_size_y)]")]")
|
|
|
|
if(isnull(disk))
|
|
. += span_notice("It has a slot for a holorecord disk.")
|
|
else
|
|
. += span_notice("It has \an [disk.name] inserted.")
|
|
|
|
/obj/item/camera/Exited(atom/movable/gone, direction)
|
|
. = ..()
|
|
if(gone == disk)
|
|
disk = null
|
|
|
|
/**
|
|
* Adjusts the zoom of this camera
|
|
* Arguments
|
|
*
|
|
* * desired_x - the x zoom value to use
|
|
* * desired_y - the y zoom value to use
|
|
* * mob/user - the optional user who is taking the photo. Passing the mob will ask for input and ignore the above params
|
|
*/
|
|
/obj/item/camera/proc/adjust_zoom(desired_x = picture_size_x, desired_y = picture_size_y, mob/user)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
if(user)
|
|
if(loc != user)
|
|
to_chat(user, span_warning("You must be holding the camera to continue!"))
|
|
return FALSE
|
|
desired_x = tgui_input_number(user, "Set camera half width Aperture", "Zoom", picture_size_x, CAMERA_PICTURE_SIZE_HARD_LIMIT, 1)
|
|
if(!desired_x || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH|ALLOW_PAI) || loc != user)
|
|
return FALSE
|
|
desired_y = tgui_input_number(user, "Set camera half height Aperture", "Zoom", picture_size_y, CAMERA_PICTURE_SIZE_HARD_LIMIT, 1)
|
|
if(!desired_y || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH|ALLOW_PAI) || loc != user)
|
|
return FALSE
|
|
|
|
picture_size_x = clamp(desired_x, 1, CAMERA_PICTURE_SIZE_HARD_LIMIT)
|
|
picture_size_y = clamp(desired_y, 1, CAMERA_PICTURE_SIZE_HARD_LIMIT)
|
|
|
|
if(user)
|
|
to_chat(user, span_notice("The dimensions of the picture will be [EXAMINE_HINT("[APERTURE_TO_METERS(picture_size_x)]x[APERTURE_TO_METERS(picture_size_y)]")]"))
|
|
|
|
return TRUE
|
|
|
|
/// Resets flash to be used again
|
|
/obj/item/camera/proc/cooldown()
|
|
PRIVATE_PROC(TRUE)
|
|
|
|
UNTIL(!blending)
|
|
icon_state = base_icon_state
|
|
on = TRUE
|
|
|
|
/// Turns the light/flash off
|
|
/obj/item/camera/proc/flash_end()
|
|
PRIVATE_PROC(TRUE)
|
|
|
|
light_holder.set_light_on(FALSE)
|
|
|
|
/**
|
|
* Turns the flash quickly on and off when picture is taken
|
|
* Arguments
|
|
*
|
|
* * atom/target - the target we are trying to take a photo of
|
|
* * mob/user - the optional user who is taking the photo
|
|
*/
|
|
/obj/item/camera/proc/on_flash(atom/target, mob/user)
|
|
PROTECTED_PROC(TRUE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
on = FALSE
|
|
addtimer(CALLBACK(src, PROC_REF(cooldown)), cooldown)
|
|
icon_state = "[base_icon_state]_off"
|
|
if(flash_enabled)
|
|
light_holder.set_light_on(TRUE)
|
|
addtimer(CALLBACK(src, PROC_REF(flash_end)), FLASH_LIGHT_DURATION, TIMER_OVERRIDE|TIMER_UNIQUE)
|
|
|
|
/**
|
|
* Steal souls from all mobs captured in the image
|
|
* Arguments
|
|
*
|
|
* * list/victims - list of all mobs captured in the image
|
|
*/
|
|
/obj/item/camera/proc/steal_souls(list/mob/victims)
|
|
PROTECTED_PROC(TRUE)
|
|
|
|
return
|
|
|
|
/**
|
|
* Attempts to take an image of the target and all its surrounding tiles
|
|
* Returns TRUE if it successfully starts taking a picture.
|
|
* Arguments
|
|
*
|
|
* * atom/target - the target we are trying to take a photo of
|
|
* * mob/user - the optional user who is taking the photo
|
|
*/
|
|
/obj/item/camera/proc/attempt_picture(atom/target, mob/user)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
if(!on)
|
|
if(user)
|
|
user.balloon_alert(user, "flash still charging!")
|
|
return FALSE
|
|
|
|
if(blending)
|
|
if(user)
|
|
user.balloon_alert(user, "image still blending!")
|
|
return FALSE
|
|
|
|
blending = TRUE
|
|
INVOKE_ASYNC(src, PROC_REF(capture_image), target, user)
|
|
return TRUE
|
|
|
|
/**
|
|
* Renders an image of the target and all its surrounding tiles
|
|
* Arguments passed from attempt_picture()
|
|
*/
|
|
/obj/item/camera/proc/capture_image(atom/target, mob/user)
|
|
PRIVATE_PROC(TRUE)
|
|
//Checking if we can target
|
|
var/turf/target_turf = get_turf(target)
|
|
var/view_size = user?.client?.view || world.view
|
|
if(isnull(target_turf))
|
|
blending = FALSE
|
|
return
|
|
if(isAI(user))
|
|
if(!SScameras.is_visible_by_cameras(target_turf))
|
|
blending = FALSE
|
|
return
|
|
else if(!(target_turf in view(view_size, user)))
|
|
blending = FALSE
|
|
return
|
|
|
|
//These vars will be reused later on
|
|
var/size_x = picture_size_x - 1
|
|
var/size_y = picture_size_y - 1
|
|
var/list/viewlist = getviewsize(view_size)
|
|
var/view_range = max(viewlist[1], viewlist[2]) + max(size_x, size_y)
|
|
var/viewer = get_turf(user?.client?.eye || user)
|
|
var/list/seen = get_hear_turfs(view_range, viewer)
|
|
if(!(target_turf in seen))
|
|
blending = FALSE
|
|
return
|
|
|
|
//taking the actual picture
|
|
on_flash(target, user)
|
|
var/list/mobs_spotted = list()
|
|
var/list/dead_spotted = list()
|
|
var/list/turfs = list()
|
|
var/list/mobs = list()
|
|
var/blueprints = FALSE
|
|
var/width = APERTURE_TO_METERS(picture_size_x)
|
|
var/height = APERTURE_TO_METERS(picture_size_y)
|
|
///list of human names taken on picture
|
|
var/list/names = list()
|
|
var/cameranet_user = isAI(user) || istype(viewer, /mob/eye/camera)
|
|
var/datum/turf_reservation/clone_area = SSmapping.request_turf_block_reservation(width, height, 1)
|
|
for(var/turf/seen_placeholder as anything in CORNER_BLOCK_OFFSET(target_turf, width, height, -size_x, -size_y))
|
|
if(isnull(seen_placeholder))
|
|
continue
|
|
if(cameranet_user && !SScameras.is_visible_by_cameras(seen_placeholder))
|
|
continue
|
|
if(!cameranet_user && !(seen_placeholder in seen))
|
|
continue
|
|
|
|
//Multi-z photography
|
|
var/turf/target_placeholder = seen_placeholder
|
|
while(!isnull(target_placeholder))
|
|
turfs += target_placeholder
|
|
for(var/mob/mob_there in target_placeholder)
|
|
mobs += mob_there
|
|
if(locate(/obj/item/blueprints) in target_placeholder)
|
|
blueprints = TRUE
|
|
|
|
if(isopenspaceturf(target_placeholder) || istype(target_placeholder, /turf/open/floor/glass))
|
|
target_placeholder = GET_TURF_BELOW(target_placeholder)
|
|
else
|
|
break
|
|
|
|
// do this before picture is taken so we can reveal revenants for the photo
|
|
steal_souls(mobs)
|
|
|
|
var/list/desc = list("This is a photo of an area of [width] meters by [height] meters.")
|
|
for(var/mob/mob as anything in mobs)
|
|
mobs_spotted += mob
|
|
if(mob.stat == DEAD)
|
|
dead_spotted += mob
|
|
var/info = mob.get_photo_description(src)
|
|
if(!isnull(info))
|
|
desc += info
|
|
|
|
var/icon/get_icon = camera_get_icon(turfs, target_turf, clone_area)
|
|
get_icon.Blend("#000", ICON_UNDERLAY)
|
|
qdel(clone_area)
|
|
for(var/mob/living/carbon/human/person in mobs)
|
|
if(person.obscured_slots & HIDEFACE)
|
|
continue
|
|
names += "[person.name]"
|
|
|
|
var/datum/picture/picture = new(
|
|
"picture",
|
|
desc.Join("<br>"),
|
|
mobs_spotted,
|
|
dead_spotted,
|
|
names,
|
|
get_icon,
|
|
null,
|
|
width * ICON_SIZE_X,
|
|
height * ICON_SIZE_Y,
|
|
blueprints,
|
|
can_see_ghosts = see_ghosts,
|
|
)
|
|
after_picture(user, picture)
|
|
SEND_SIGNAL(src, COMSIG_CAMERA_IMAGE_CAPTURED, target, user, picture)
|
|
blending = FALSE
|
|
|
|
/**
|
|
* Action to take after the picture is taken
|
|
*
|
|
* Arguments
|
|
*
|
|
* * mob/user - the user who took the picture
|
|
* * datum/picture/picture - the picture taken
|
|
*/
|
|
/obj/item/camera/proc/after_picture(mob/user, datum/picture/picture)
|
|
PROTECTED_PROC(TRUE)
|
|
|
|
if(silent)
|
|
user.playsound_local(get_turf(src), SFX_POLAROID, 35, TRUE)
|
|
else
|
|
playsound(loc, SFX_POLAROID, 75, TRUE, -3)
|
|
|
|
if(print_picture_on_snap)
|
|
printpicture(user, picture)
|
|
|
|
/**
|
|
* Print the picture tkane on film
|
|
*
|
|
* Arguments
|
|
*
|
|
* * mob/user - the user who took the picture
|
|
* * datum/picture/picture - the picture taken
|
|
*/
|
|
/obj/item/camera/proc/printpicture(mob/user, datum/picture/picture)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
var/obj/item/photo/new_photo
|
|
if(user)
|
|
if(!pictures_left)
|
|
to_chat(user, span_warning("No film left."))
|
|
return
|
|
|
|
new_photo = new(src, picture)
|
|
|
|
to_chat(user, span_notice("[pictures_left] photos left."))
|
|
|
|
var/name_customized = FALSE
|
|
if(can_customise)
|
|
var/customise = tgui_alert(user, "Do you want to customize the photo?", "Customization", list("Yes", "No"))
|
|
if(customise == "Yes")
|
|
var/name1 = tgui_input_text(user, "Set a name for this photo, or leave blank.", "Name", max_length = 32)
|
|
var/desc1 = tgui_input_text(user, "Set a description to add to photo, or leave blank.", "Description", max_length = 128)
|
|
var/caption = tgui_input_text(user, "Set a caption for this photo, or leave blank.", "Caption", max_length = 256)
|
|
if(name1)
|
|
picture.picture_name = name1
|
|
name_customized = TRUE
|
|
if(desc1)
|
|
picture.picture_desc = "[desc1] - [picture.picture_desc]"
|
|
if(caption)
|
|
picture.caption = caption
|
|
if(!name_customized && default_picture_name)
|
|
picture.picture_name = default_picture_name
|
|
|
|
else if(isliving(loc))
|
|
var/mob/living/holder = loc
|
|
|
|
if(!pictures_left)
|
|
to_chat(holder, span_warning("No film left."))
|
|
return
|
|
|
|
new_photo = new(get_turf(src), picture)
|
|
|
|
to_chat(holder, span_notice("[pictures_left] photos left."))
|
|
|
|
new_photo.set_picture(picture, TRUE, TRUE)
|
|
if(CONFIG_GET(flag/picture_logging_camera))
|
|
picture.log_to_file()
|
|
|
|
pictures_left--
|
|
|
|
user.put_in_hands(new_photo)
|
|
|
|
/obj/item/camera/attack_self(mob/user)
|
|
if(isnull(disk))
|
|
return
|
|
playsound(src, 'sound/machines/card_slide.ogg', 50)
|
|
user.put_in_hands(disk)
|
|
disk = null
|
|
|
|
/obj/item/camera/attack(mob/living/carbon/human/M, mob/user)
|
|
return
|
|
|
|
/obj/item/camera/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
if(istype(tool, /obj/item/camera_film))
|
|
if(pictures_left)
|
|
balloon_alert(user, "isn't empty!")
|
|
return ITEM_INTERACT_BLOCKING
|
|
if(!user.temporarilyRemoveItemFromInventory(tool))
|
|
return ITEM_INTERACT_BLOCKING
|
|
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
|
|
qdel(tool)
|
|
pictures_left = pictures_max
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
if(istype(tool, /obj/item/disk/holodisk))
|
|
if(!user.transferItemToLoc(tool, src))
|
|
balloon_alert(user, "stuck in hand!")
|
|
return TRUE
|
|
if(disk)
|
|
user.put_in_hands(disk)
|
|
balloon_alert(user, "disks swapped!")
|
|
else
|
|
balloon_alert(user, "disk inserted!")
|
|
playsound(src, 'sound/machines/card_slide.ogg', 50)
|
|
disk = tool
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/item/camera/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
|
if(HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
|
|
return NONE
|
|
return ranged_interact_with_atom(interacting_with, user, modifiers)
|
|
|
|
/obj/item/camera/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
|
if(disk)
|
|
if(!ismob(interacting_with))
|
|
to_chat(user, span_warning("Invalid holodisk target."))
|
|
return ITEM_INTERACT_BLOCKING
|
|
if(disk.record)
|
|
QDEL_NULL(disk.record)
|
|
|
|
disk.record = new
|
|
var/mob/recorded_mob = interacting_with
|
|
disk.record.caller_name = recorded_mob.name
|
|
disk.record.set_caller_image(recorded_mob)
|
|
|
|
attempt_picture(interacting_with, user)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/item/camera/click_alt(mob/user)
|
|
if(!adjust_zoom(user = user))
|
|
return CLICK_ACTION_BLOCKING
|
|
if(silent) // Don't out your silent cameras
|
|
user.playsound_local(get_turf(src), 'sound/machines/click.ogg', 50, TRUE)
|
|
else
|
|
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
|
|
return CLICK_ACTION_SUCCESS
|
|
|
|
/obj/item/camera/item_ctrl_click(mob/user)
|
|
print_monochrome = !print_monochrome
|
|
user.balloon_alert(user, "printing [print_monochrome ? "monochrome" : "in color"]")
|
|
if(silent) // Don't out your silent cameras
|
|
user.playsound_local(get_turf(src), 'sound/machines/click.ogg', 50, TRUE)
|
|
else
|
|
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
|
|
return CLICK_ACTION_SUCCESS
|