diff --git a/code/__DEFINES/camera.dm b/code/__DEFINES/camera.dm
index 645037f68b4..383e3ead609 100644
--- a/code/__DEFINES/camera.dm
+++ b/code/__DEFINES/camera.dm
@@ -1 +1,5 @@
-#define CAMERA_PICTURE_SIZE_HARD_LIMIT 21
+///Converts camera half dimension aperture into full meters
+///We first do size = value - 1 which is the picture size & then 2 * size + 1 to give us meters
+#define APERTURE_TO_METERS(value)(2 * value - 1)
+///Max size of an photograph in square dimensions
+#define CAMERA_PICTURE_SIZE_HARD_LIMIT 4
diff --git a/code/__DEFINES/wiremod.dm b/code/__DEFINES/wiremod.dm
index 8922a522c17..460ec5f7f23 100644
--- a/code/__DEFINES/wiremod.dm
+++ b/code/__DEFINES/wiremod.dm
@@ -1,4 +1,3 @@
-/// Helper define that can only be used in /obj/item/circuit_component/input_received()
#define COMPONENT_TRIGGERED_BY(trigger, port) (trigger.value && trigger == port)
/// Define to be placed at any proc that is triggered by a port.
diff --git a/code/modules/mob/living/basic/pets/orbie/orbie_abilities.dm b/code/modules/mob/living/basic/pets/orbie/orbie_abilities.dm
index fb9994a9321..c016f649291 100644
--- a/code/modules/mob/living/basic/pets/orbie/orbie_abilities.dm
+++ b/code/modules/mob/living/basic/pets/orbie/orbie_abilities.dm
@@ -19,28 +19,29 @@
overlay_icon_state = "bg_default_border"
cooldown_time = 30 SECONDS
///camera we use to take photos
- var/obj/item/camera/ability_camera
+ var/obj/item/camera/internal_camera
/datum/action/cooldown/mob_cooldown/capture_photo/Grant(mob/grant_to)
. = ..()
if(isnull(owner))
return
- ability_camera = new(owner)
- ability_camera.print_picture_on_snap = FALSE
- RegisterSignal(ability_camera, COMSIG_PREQDELETED, PROC_REF(on_camera_delete))
+ internal_camera = new(owner)
+ internal_camera.print_picture_on_snap = FALSE
+ internal_camera.cooldown = 1 SECONDS
+ RegisterSignal(internal_camera, COMSIG_PREQDELETED, PROC_REF(on_camera_delete))
/datum/action/cooldown/mob_cooldown/capture_photo/Activate(atom/target)
- if(isnull(ability_camera))
+ if(isnull(internal_camera))
return FALSE
- ability_camera.captureimage(target, owner)
+ internal_camera.attempt_picture(target, owner)
StartCooldown()
return TRUE
/datum/action/cooldown/mob_cooldown/capture_photo/proc/on_camera_delete(datum/source)
SIGNAL_HANDLER
- UnregisterSignal(ability_camera, COMSIG_PREQDELETED)
- ability_camera = null
+ UnregisterSignal(internal_camera, COMSIG_PREQDELETED)
+ internal_camera = null
/datum/action/cooldown/mob_cooldown/capture_photo/Destroy()
- QDEL_NULL(ability_camera)
+ QDEL_NULL(internal_camera)
return ..()
diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/file_browser.dm
index d33c34a348b..d25a9894307 100644
--- a/code/modules/modular_computers/file_system/programs/file_browser.dm
+++ b/code/modules/modular_computers/file_system/programs/file_browser.dm
@@ -5,25 +5,26 @@ GLOBAL_LIST_INIT(print_types, init_print_types())
for(var/obj/item/canvas/canvas_type as anything in typesof(/obj/item/canvas))
var/width = canvas_type::width
var/height = canvas_type::height
- LAZYADDASSOC(print_types, "[width]x[height]", list("[canvas_type]" = list(
+ LAZYADDASSOC(print_types, "[width]x[height]", list(
"displayText" = "Canvas ([width]x[height])",
"typepath" = canvas_type,
"width" = width,
"height" = height,
"check_callback" = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(check_can_print_canvas)),
"prepare_callback" = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(prepare_canvas_from_file)),
- )))
- for(var/size in 1 to /obj/item/camera::picture_size_x_max)
- var/width = ICON_SIZE_X*(size*2-1)
- var/height = ICON_SIZE_Y*(size*2-1)
- LAZYADDASSOC(print_types, "[width]x[height]", list("[/obj/item/photo]" = list(
- "displayText" = "Photo Paper ([size*2-1]m focal length)",
+ ))
+ for(var/size in 1 to CAMERA_PICTURE_SIZE_HARD_LIMIT)
+ var/meters = APERTURE_TO_METERS(size)
+ var/width = ICON_SIZE_X * meters
+ var/height = ICON_SIZE_Y * meters
+ LAZYADDASSOC(print_types, "[width]x[height]", list(
+ "displayText" = "Photo Paper ([meters]m focal length)",
"typepath" = /obj/item/photo,
"width" = width,
"height" = height,
"check_callback" = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(check_can_print_photo)),
"prepare_callback" = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(prepare_photo_from_file)),
- )))
+ ))
return print_types
/proc/check_can_print_canvas(_typepath, _image_file, obj/item/modular_computer/computer, mob/user)
@@ -177,11 +178,8 @@ GLOBAL_LIST_INIT(print_types, init_print_types())
try_print(picture, params["width"], params["height"], params["offsetX"], params["offsetY"], params["typepath"], usr)
/datum/computer_file/program/filemanager/proc/try_print(datum/computer_file/image/picture, width, height, offset_x, offset_y, typepath, mob/user)
- var/list/print_types_for_dimensions = GLOB.print_types["[width]x[height]"]
- if(!length(print_types_for_dimensions))
- return
- var/list/print_type = print_types_for_dimensions[typepath]
- if(!print_type)
+ var/list/print_type = GLOB.print_types["[width]x[height]"]
+ if(!length(print_type))
return
var/image_width = picture.stored_icon.Width()
var/image_height = picture.stored_icon.Height()
@@ -203,10 +201,16 @@ GLOBAL_LIST_INIT(print_types, init_print_types())
/datum/computer_file/program/filemanager/ui_static_data(mob/user)
var/list/print_types = list()
- for(var/dimensions in GLOB.print_types)
- var/list/types_for_dimensions = GLOB.print_types[dimensions]
- for(var/print_typepath in types_for_dimensions)
- print_types += list(types_for_dimensions[print_typepath])
+ for(var/dimensions, types_for_dimensions in GLOB.print_types)
+ var/list/typepath = types_for_dimensions["typepath"]
+ if(ispath(typepath, /obj/item/canvas) && !(computer.hardware_flag & PROGRAM_CONSOLE))
+ continue
+ print_types += list(list(
+ displayText = types_for_dimensions["displayText"],
+ typepath = "[typepath]",
+ width = types_for_dimensions["width"],
+ height = types_for_dimensions["height"]
+ ))
return list("printTypes" = print_types)
/datum/computer_file/program/filemanager/ui_data(mob/user)
diff --git a/code/modules/modular_computers/file_system/programs/maintenance/camera.dm b/code/modules/modular_computers/file_system/programs/maintenance/camera.dm
index 4ff763e1d5f..a2fd42d4434 100644
--- a/code/modules/modular_computers/file_system/programs/maintenance/camera.dm
+++ b/code/modules/modular_computers/file_system/programs/maintenance/camera.dm
@@ -14,8 +14,8 @@
var/obj/item/camera/app/internal_camera
/// Latest picture taken by the app.
var/datum/picture/internal_picture
- /// A mutable_appearance of the latest picture, for getting an appeance reference for the UI.
- var/mutable_appearance/picture_appearance
+ /// base64 of the current taken picture
+ var/internal_picture_icon
/// How many pictures were taken already, used for the camera's TGUI photo display
var/picture_number = 1
/// Can we edit the metadata of the latest picture?
@@ -29,24 +29,20 @@
// Special type of camera for this exact usecase to prevent harddels
/obj/item/camera/app
- name = "internal camera"
- desc = "Specialized internal camera protected from the hellish depths of SSWardrobe. \
- Yell at coders if you somehow manage to see this"
+ print_picture_on_snap = FALSE
+ cooldown = 1 SECONDS
+ light_range = 3
/datum/computer_file/program/maintenance/camera/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing, mob/user)
. = ..()
internal_camera = new(computer)
- internal_camera.print_picture_on_snap = FALSE
- picture_appearance = new()
RegisterSignal(internal_camera, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_image_captured))
/datum/computer_file/program/maintenance/camera/Destroy()
QDEL_NULL(internal_camera)
- internal_picture = null
- QDEL_NULL(picture_appearance)
+ QDEL_NULL(internal_picture)
return ..()
-
/datum/computer_file/program/maintenance/camera/tap(atom/tapped_atom, mob/living/user, list/modifiers)
. = ..()
take_picture(user, get_turf(tapped_atom))
@@ -57,7 +53,7 @@
/datum/computer_file/program/maintenance/camera/kill_program(mob/user)
. = ..()
UnregisterSignal(computer, COMSIG_RANGED_ITEM_INTERACTING_WITH_ATOM_SECONDARY)
- internal_picture = null
+ QDEL_NULL(internal_picture)
/datum/computer_file/program/maintenance/camera/background_program(mob/user)
. = ..()
@@ -68,47 +64,21 @@
take_picture(user, get_turf(target))
/datum/computer_file/program/maintenance/camera/proc/take_picture(mob/user, turf/target)
- if(internal_camera.blending)
- user.balloon_alert(user, "still blending!")
- return
-
- var/spooky_camera = locate(/datum/computer_file/program/maintenance/spectre_meter) in computer.stored_files
- internal_camera.see_ghosts = spooky_camera ? CAMERA_SEE_GHOSTS_BASIC : CAMERA_NO_GHOSTS
- INVOKE_ASYNC(internal_camera, TYPE_PROC_REF(/obj/item/camera, captureimage), target, user, internal_camera.picture_size_x - 1, internal_camera.picture_size_y - 1)
+ internal_camera.see_ghosts = (locate(/datum/computer_file/program/maintenance/spectre_meter) in computer.stored_files) ? CAMERA_SEE_GHOSTS_BASIC : CAMERA_NO_GHOSTS
+ internal_camera.attempt_picture(target, user)
/datum/computer_file/program/maintenance/camera/proc/on_image_captured(cam, target, user, datum/picture/picture)
SIGNAL_HANDLER
+ QDEL_NULL(internal_picture)
internal_picture = picture
- picture_appearance.icon = internal_picture.picture_image
+ internal_picture_icon = icon2base64(internal_picture.picture_image)
current_picture_name = null
current_picture_desc = null
current_picture_caption = null
can_edit_metadata = TRUE
picture_number++
-/datum/computer_file/program/maintenance/camera/proc/save_picture(mob/user)
- var/datum/computer_file/image/photo_file = new(
- internal_picture.picture_image,
- display_name = internal_picture.picture_name || "photo[picture_number]",
- source_photo_or_painting = internal_picture
- )
- if(computer.store_file(photo_file, user))
- return FALSE
- commit_metadata()
- return TRUE
-
-/datum/computer_file/program/maintenance/camera/proc/print_picture(mob/user)
- if(computer.stored_paper < PHOTO_PAPER_COST)
- return
- commit_metadata()
- var/obj/item/photo/new_photo = new(computer.physical.drop_location())
- new_photo.set_picture(internal_picture, TRUE, TRUE)
- user?.put_in_hands(new_photo)
- playsound(computer.physical, 'sound/machines/printer.ogg', 100, TRUE)
- computer.stored_paper--
- computer.visible_message(span_notice("\The [computer] prints out a paper."))
-
/datum/computer_file/program/maintenance/camera/proc/commit_metadata()
if(can_edit_metadata)
internal_picture.picture_name = current_picture_name
@@ -117,105 +87,90 @@
can_edit_metadata = FALSE
/datum/computer_file/program/maintenance/camera/ui_static_data(mob/user)
- return list("maxNameLength" = 32, "maxDescLength" = 128, "maxCaptionLength" = 256, "printCost" = 1)
+ return list(
+ "maxNameLength" = 32,
+ "maxDescLength" = 128,
+ "maxCaptionLength" = 256,
+ "printCost" = 1,
+ "minSize" = 2,
+ "maxSize" = CAMERA_PICTURE_SIZE_HARD_LIMIT
+ )
/datum/computer_file/program/maintenance/camera/ui_data(mob/user)
var/list/data = list()
if(!isnull(internal_picture))
- data["photo"] = REF(picture_appearance.appearance)
+ data["photo"] = internal_picture_icon
data["canEditMetadata"] = can_edit_metadata
data["name"] = current_picture_name
data["desc"] = current_picture_desc
data["caption"] = current_picture_caption
data["storedPaper"] = computer.stored_paper
- data["size"] = internal_camera.picture_size_x
- data["minSize"] = internal_camera.picture_size_x_min
- data["maxSize"] = min(internal_camera.picture_size_x_max, CAMERA_PICTURE_SIZE_HARD_LIMIT)
+
+ data["width"] = internal_camera.picture_size_x
+ data["height"] = internal_camera.picture_size_y
+ data["size"] = "[APERTURE_TO_METERS(internal_camera.picture_size_x)]x[APERTURE_TO_METERS(internal_camera.picture_size_y)]"
return data
/datum/computer_file/program/maintenance/camera/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
+ if(.)
+ return
+
switch(action)
- if("adjustSize")
- var/new_size = round(params["value"], 1)
- if(!ISINRANGE(new_size, internal_camera.picture_size_x_min, min(CAMERA_PICTURE_SIZE_HARD_LIMIT, internal_camera.picture_size_x_max)))
+ if("adjustWidth")
+ var/new_size = text2num(params["value"])
+ if(!new_size)
return
- internal_camera.picture_size_x = new_size
- internal_camera.picture_size_y = new_size
+ return internal_camera.adjust_zoom(desired_x = new_size)
+
+ if("adjustHeight")
+ var/new_size = text2num(params["value"])
+ if(!new_size)
+ return
+ return internal_camera.adjust_zoom(desired_y = new_size)
+
if("setName")
if(!(internal_picture && can_edit_metadata))
- return
+ return FALSE
current_picture_name = trim(params["value"], PREVENT_CHARACTER_TRIM_LOSS(32))
+ return TRUE
+
if("setDesc")
if(!(internal_picture && can_edit_metadata))
return
current_picture_desc = trim(params["value"], PREVENT_CHARACTER_TRIM_LOSS(128))
+ return TRUE
+
if("setCaption")
if(!(internal_picture && can_edit_metadata))
return
current_picture_caption = trim(params["value"], PREVENT_CHARACTER_TRIM_LOSS(256))
+ return TRUE
+
if("savePhoto")
if(!internal_picture)
return
- save_picture(ui.user)
+ var/datum/computer_file/image/photo_file = new(
+ internal_picture.picture_image,
+ display_name = internal_picture.picture_name || "photo[picture_number]",
+ source_photo_or_painting = internal_picture
+ )
+ if(computer.store_file(photo_file, ui.user))
+ return FALSE
+ commit_metadata()
+ return TRUE
if("printPhoto")
if(!internal_picture)
- return
- print_picture(ui.user)
- return TRUE
-
-/obj/item/circuit_component/mod_program/camera
- associated_program = /datum/computer_file/program/maintenance/camera
- circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
-
- ///A target to take a picture of.
- var/datum/port/input/picture_target
- ///The size of the photo to take.
- var/datum/port/input/picture_size
- ///The photographed target
- var/datum/port/output/photographed
- /**
- * Pinged when the image has been captured.
- * I'm not using the default trigger output here because the process is asynced,
- * even though I'm mostly sure it only sleeps if there's a set user.
- */
- var/datum/port/output/photo_taken
-
-/obj/item/circuit_component/mod_program/camera/populate_ports()
- . = ..()
- picture_target = add_input_port("Picture Target", PORT_TYPE_ATOM)
- picture_size = add_input_port("Picture Size", PORT_TYPE_NUMBER)
- photographed = add_output_port("Photographed Entity", PORT_TYPE_ATOM)
- photo_taken = add_output_port("Photo Taken", PORT_TYPE_SIGNAL)
-
-/obj/item/circuit_component/mod_program/camera/register_shell(atom/movable/shell)
- . = ..()
- var/datum/computer_file/program/maintenance/camera/cam = associated_program
- RegisterSignal(cam.internal_camera, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_image_captured))
-
-/obj/item/circuit_component/mod_program/camera/unregister_shell()
- var/datum/computer_file/program/maintenance/camera/cam = associated_program
- UnregisterSignal(cam.internal_camera, COMSIG_CAMERA_IMAGE_CAPTURED)
- return ..()
-
-/obj/item/circuit_component/mod_program/camera/input_received(datum/port/input/port)
- if(!COMPONENT_TRIGGERED_BY(port, trigger_input))
- return
- var/atom/target = picture_target.value
- if(!target)
- var/turf/our_turf = get_location()
- target = locate(our_turf.x, our_turf.y, our_turf.z)
- if(!target)
- return
- var/datum/computer_file/program/maintenance/camera/cam = associated_program
- if(!cam.internal_camera.can_target(target))
- return
- var/pic_size = clamp(round(picture_size.value), 1, cam.internal_camera.picture_size_x_max)-1
- INVOKE_ASYNC(cam.internal_camera, TYPE_PROC_REF(/obj/item/camera, captureimage), target, null, pic_size, pic_size)
-
-/obj/item/circuit_component/mod_program/camera/proc/on_image_captured(obj/item/camera/source, atom/target, mob/user)
- SIGNAL_HANDLER
- photographed.set_output(target)
- photo_taken.set_output(COMPONENT_SIGNAL)
+ return FALSE
+ if(computer.stored_paper < PHOTO_PAPER_COST)
+ return FALSE
+ commit_metadata()
+ var/obj/item/photo/new_photo = new(computer.physical.drop_location())
+ new_photo.set_picture(internal_picture, TRUE, TRUE)
+ ui.user.put_in_hands(new_photo)
+ playsound(computer.physical, 'sound/machines/printer.ogg', 100, TRUE)
+ computer.stored_paper--
+ computer.visible_message(span_notice("\The [computer] prints out a paper."))
+ return TRUE
diff --git a/code/modules/modular_computers/file_system/programs/virtual_pet.dm b/code/modules/modular_computers/file_system/programs/virtual_pet.dm
index f36a4e407e6..6715322104c 100644
--- a/code/modules/modular_computers/file_system/programs/virtual_pet.dm
+++ b/code/modules/modular_computers/file_system/programs/virtual_pet.dm
@@ -330,7 +330,7 @@ GLOBAL_LIST_EMPTY(virtual_pets_list)
var/datum/action/cooldown/mob_cooldown/capture_photo/photo_ability = new(pet)
photo_ability.Grant(pet)
pet.ai_controller?.set_blackboard_key(BB_PHOTO_ABILITY, photo_ability)
- RegisterSignal(photo_ability.ability_camera, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_photo_captured))
+ RegisterSignal(photo_ability.internal_camera, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_photo_captured))
/datum/computer_file/program/virtual_pet/proc/announce_global_updates(message)
if(isnull(message))
diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm
index 5cf55f99bfe..00815854fbe 100644
--- a/code/modules/photography/camera/camera.dm
+++ b/code/modules/photography/camera/camera.dm
@@ -3,11 +3,12 @@
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 = OVERLAY_LIGHT_DIRECTIONAL //Used as a flash here.
+ light_system = NONE
light_range = 6
light_color = COLOR_WHITE
light_power = FLASH_LIGHT_POWER
@@ -24,52 +25,55 @@
var/on = TRUE
/// Whether we are still processing an image.
var/blending = FALSE
- /// Our icon_state when ready to take a picture.
- var/state_on = "camera"
- /// Our icon_state when not ready to take a picture.
- var/state_off = "camera_off"
-
/// 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
-
/// 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 //for the spoop of it
+ 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
- var/picture_size_x_min = 1
- var/picture_size_y_min = 1
- var/picture_size_x_max = 4
- var/picture_size_y_max = 4
+ ///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/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.")
-
- 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/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"
@@ -88,187 +92,161 @@
return CONTEXTUAL_SCREENTIP_SET
-/obj/item/camera/proc/adjust_zoom(mob/user)
- if(loc != user)
- to_chat(user, span_warning("You must be holding the camera to continue!"))
- return FALSE
- var/desired_x = tgui_input_number(user, "How wide do you want the camera to shoot?", "Zoom", picture_size_x, picture_size_x_max, picture_size_x_min)
- if(!desired_x || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH|ALLOW_PAI) || loc != user)
- return FALSE
- var/desired_y = tgui_input_number(user, "How high do you want the camera to shoot", "Zoom", picture_size_y, picture_size_y_max, picture_size_y_min)
- if(!desired_y || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH|ALLOW_PAI) || loc != user)
- return FALSE
- picture_size_x = min(clamp(desired_x, picture_size_x_min, picture_size_x_max), CAMERA_PICTURE_SIZE_HARD_LIMIT)
- picture_size_y = min(clamp(desired_y, picture_size_y_min, picture_size_y_max), CAMERA_PICTURE_SIZE_HARD_LIMIT)
- return TRUE
+/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("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
-/obj/item/camera/click_alt(mob/user)
- if(!adjust_zoom(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
+/**
+ * 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)
-/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))
- return camera_film_act(user, tool)
- if(istype(tool, /obj/item/disk/holodisk))
- return holodisk_act(user, tool)
-
-/obj/item/camera/proc/camera_film_act(mob/living/user, obj/item/camera_film/new_film)
- if(pictures_left)
- balloon_alert(user, "isn't empty!")
- return ITEM_INTERACT_BLOCKING
- if(!user.temporarilyRemoveItemFromInventory(new_film))
- return ITEM_INTERACT_BLOCKING
- playsound(src, 'sound/machines/click.ogg', 50, TRUE)
- qdel(new_film)
- pictures_left = pictures_max
- return ITEM_INTERACT_SUCCESS
-
-/obj/item/camera/proc/holodisk_act(mob/living/user, obj/item/disk/holodisk/new_disk)
- if(!user.transferItemToLoc(new_disk, 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 = new_disk
- return ITEM_INTERACT_SUCCESS
-
-/// Attempt to take an image, optionally given a user.
-/obj/item/camera/proc/attempt_picture(atom/target, atom/user)
- if(!can_target(target, user))
- return FALSE
- if(!photo_taken(target, user))
- return FALSE
- return TRUE
-
-/// Check whether we can take a picture of the target, optionally given a user.
-/obj/item/camera/proc/can_target(atom/target, atom/user)
- if(!on || blending || !pictures_left)
- return FALSE
- var/turf/target_turf = get_turf(target)
- if(isnull(target_turf))
- return FALSE
- if(isAI(user))
- return can_ai_target(target_turf)
- if(ismob(user))
- return can_mob_target(target_turf, user)
-
- if(isliving(loc))
- if(!(target_turf in view(world.view, loc)))
+ 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, 2)
+ 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, 2)
+ if(!desired_y || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH|ALLOW_PAI) || loc != user)
return FALSE
- return TRUE
- // User is an atom or null
- if(!(target_turf in view(world.view, user || src)))
- return FALSE
+ picture_size_x = clamp(desired_x, 2, CAMERA_PICTURE_SIZE_HARD_LIMIT)
+ picture_size_y = clamp(desired_y, 2, 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)
-/// Check whether an AI could take a picture of the target turf.
-/obj/item/camera/proc/can_ai_target(turf/target_turf)
- return SScameras.is_visible_by_cameras(target_turf)
+ UNTIL(!blending)
+ icon_state = base_icon_state
+ on = TRUE
-/// Check whether a mob could take a picture of the target turf.
-/obj/item/camera/proc/can_mob_target(turf/target_turf, mob/user)
- return (target_turf in get_hear_turfs(user.client?.view || world.view, user.client?.eye || user))
+/// Turns the light/flash off
+/obj/item/camera/proc/flash_end()
+ PRIVATE_PROC(TRUE)
-/obj/item/camera/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
- // Always skip on storage and tables
- if(HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
- return NONE
+ light_holder.set_light_on(FALSE)
- 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)
-
- return attempt_picture(interacting_with, user) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING
-
-/obj/item/camera/proc/photo_taken(atom/target, mob/user)
+/**
+ * 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 = state_off
-
- INVOKE_ASYNC(src, PROC_REF(captureimage), target, user, picture_size_x - 1, picture_size_y - 1)
- return TRUE
-
-/obj/item/camera/proc/cooldown()
- UNTIL(!blending)
- icon_state = state_on
- on = TRUE
-
-/obj/item/camera/proc/show_picture(mob/user, datum/picture/selection)
- var/obj/item/photo/P = new(src, selection)
- P.show(user)
- to_chat(user, P.desc)
- qdel(P)
-
-/obj/item/camera/proc/captureimage(atom/target, mob/user, size_x = 1, size_y = 1)
+ icon_state = "[base_icon_state]_off"
if(flash_enabled)
- set_light_on(TRUE)
+ light_holder.set_light_on(TRUE)
addtimer(CALLBACK(src, PROC_REF(flash_end)), FLASH_LIGHT_DURATION, TIMER_OVERRIDE|TIMER_UNIQUE)
- blending = TRUE
+
+/**
+ * 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
+ * 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
+
+ if(blending)
+ if(user)
+ user.balloon_alert(user, "image still blending!")
+ return
+
+ INVOKE_ASYNC(src, PROC_REF(capture_image), target, user)
+
+/**
+ * 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)
- if(!isturf(target_turf))
- blending = FALSE
- return FALSE
- size_x = clamp(size_x, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT)
- size_y = clamp(size_y, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT)
- var/list/desc = list("This is a photo of an area of [size_x+1] meters by [size_y+1] meters.")
- var/list/mobs_spotted = list()
- var/list/dead_spotted = list()
+ if(isnull(target_turf))
+ return
+ if(isAI(user) && !SScameras.is_visible_by_cameras(target_turf))
+ return
+ if(isliving(loc) && !(target_turf in view(world.view, loc)))
+ return
+ if(!(target_turf in view(world.view, user || src)))
+ 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(user?.client?.view || world.view)
var/view_range = max(viewlist[1], viewlist[2]) + max(size_x, size_y)
var/viewer = get_turf(user?.client?.eye || user || target) // not sure why target is a fallback
var/list/seen = get_hear_turfs(view_range, viewer)
+ if(!(target_turf in seen))
+ return
+
+ //taking the actual picture
+ on_flash(target, user)
+ blending = TRUE
+ var/list/mobs_spotted = list()
+ var/list/dead_spotted = list()
var/list/turfs = list()
var/list/mobs = list()
var/blueprints = FALSE
- var/clone_area = SSmapping.request_turf_block_reservation(size_x * 2 + 1, size_y * 2 + 1, 1)
+ 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/width = size_x * 2 + 1
- var/height = size_y * 2 + 1
+ 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))
@@ -291,6 +269,7 @@
// 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)
@@ -299,50 +278,64 @@
if(!isnull(info))
desc += info
- var/psize_x = (size_x * 2 + 1) * ICON_SIZE_X
- var/psize_y = (size_y * 2 + 1) * ICON_SIZE_Y
- var/icon/get_icon = camera_get_icon(turfs, target_turf, psize_x, psize_y, clone_area, size_x, size_y, (size_x * 2 + 1), (size_y * 2 + 1))
- qdel(clone_area)
+ 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("
"), mobs_spotted, dead_spotted, names, get_icon, null, psize_x, psize_y, blueprints, can_see_ghosts = see_ghosts)
+ var/datum/picture/picture = new("picture", desc.Join("
"), mobs_spotted, dead_spotted, names, get_icon, null, width * ICON_SIZE_X, height * ICON_SIZE_X, blueprints, can_see_ghosts = see_ghosts)
after_picture(user, picture)
SEND_SIGNAL(src, COMSIG_CAMERA_IMAGE_CAPTURED, target, user, picture)
blending = FALSE
- return picture
-
-/obj/item/camera/proc/flash_end()
- set_light_on(FALSE)
-
-/obj/item/camera/proc/steal_souls(list/victims)
- return
+/**
+ * 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)
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)
-/obj/item/camera/proc/printpicture(mob/user, datum/picture/picture) //Normal camera proc for creating photos
- pictures_left--
- var/obj/item/photo/new_photo = new(get_turf(src), picture)
+ var/obj/item/photo/new_photo
if(user)
- if(in_range(new_photo, user) && user.put_in_hands(new_photo)) //needed because of TK
- to_chat(user, span_notice("[pictures_left] photos left."))
+ 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 = user.is_holding(new_photo) && tgui_alert(user, "Do you want to customize the photo?", "Customization", list("Yes", "No"))
+ var/customise = tgui_alert(user, "Do you want to customize the photo?", "Customization", list("Yes", "No"))
if(customise == "Yes")
- var/name1 = user.is_holding(new_photo) && tgui_input_text(user, "Set a name for this photo, or leave blank.", "Name", max_length = 32)
- var/desc1 = user.is_holding(new_photo) && tgui_input_text(user, "Set a description to add to photo, or leave blank.", "Description", max_length = 128)
- var/caption = user.is_holding(new_photo) && tgui_input_text(user, "Set a caption for this photo, or leave blank.", "Caption", max_length = 256)
+ 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
@@ -355,69 +348,84 @@
else if(isliving(loc))
var/mob/living/holder = loc
- if(holder.put_in_hands(new_photo))
- to_chat(holder, span_notice("[pictures_left] photos left."))
+
+ 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()
-/obj/item/circuit_component/camera
- display_name = "Camera"
- desc = "A polaroid camera that takes pictures when triggered. The picture coordinate ports are relative to the position of the camera."
- circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
+ pictures_left--
- /// The atom that was photographed from either user click or trigger input.
- var/datum/port/output/photographed_atom
- /// The item that was added/removed.
- var/datum/port/output/picture_taken
- /// If set, the trigger input will target this atom.
- var/datum/port/input/picture_target
- /// If the above is unset, these coordinates will be used.
- var/datum/port/input/picture_coord_x
- var/datum/port/input/picture_coord_y
- /// Adjusts the picture_size_x variable of the camera.
- var/datum/port/input/adjust_size_x
- /// Idem but for picture_size_y.
- var/datum/port/input/adjust_size_y
+ user.put_in_hands(new_photo)
- /// The camera this circut is attached to.
- var/obj/item/camera/camera
+/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/circuit_component/camera/populate_ports()
- picture_taken = add_output_port("Picture Taken", PORT_TYPE_SIGNAL)
- photographed_atom = add_output_port("Photographed Entity", PORT_TYPE_ATOM)
+/obj/item/camera/attack(mob/living/carbon/human/M, mob/user)
+ return
- picture_target = add_input_port("Picture Target", PORT_TYPE_ATOM)
- picture_coord_x = add_input_port("Picture Coordinate X", PORT_TYPE_NUMBER)
- picture_coord_y = add_input_port("Picture Coordinate Y", PORT_TYPE_NUMBER)
- adjust_size_x = add_input_port("Picture Size X", PORT_TYPE_NUMBER, trigger = PROC_REF(sanitize_picture_size))
- adjust_size_y = add_input_port("Picture Size Y", PORT_TYPE_NUMBER, trigger = PROC_REF(sanitize_picture_size))
+/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
-/obj/item/circuit_component/camera/register_shell(atom/movable/shell)
- . = ..()
- camera = shell
- RegisterSignal(shell, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_image_captured))
+ 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/circuit_component/camera/unregister_shell(atom/movable/shell)
- UnregisterSignal(shell, COMSIG_CAMERA_IMAGE_CAPTURED)
- camera = null
- return ..()
+/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/circuit_component/camera/proc/sanitize_picture_size()
- camera.picture_size_x = clamp(adjust_size_x.value, camera.picture_size_x_min, camera.picture_size_x_max)
- camera.picture_size_y = clamp(adjust_size_y.value, camera.picture_size_y_min, camera.picture_size_y_max)
+/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)
-/obj/item/circuit_component/camera/proc/on_image_captured(obj/item/camera/source, atom/target, mob/user)
- SIGNAL_HANDLER
- photographed_atom.set_output(target)
- picture_taken.set_output(COMPONENT_SIGNAL)
+ disk.record = new
+ var/mob/recorded_mob = interacting_with
+ disk.record.caller_name = recorded_mob.name
+ disk.record.set_caller_image(recorded_mob)
-/obj/item/circuit_component/camera/input_received(datum/port/input/port)
- var/atom/target = picture_target.value
- if(!target)
- var/turf/our_turf = get_location()
- target = locate(our_turf.x + picture_coord_x.value, our_turf.y + picture_coord_y.value, our_turf.z)
- if(!target)
- return
- INVOKE_ASYNC(camera, TYPE_PROC_REF(/obj/item/camera, attempt_picture), target)
+ 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
diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm
index 2b0387d6728..c33a351c00b 100644
--- a/code/modules/photography/camera/camera_image_capturing.dm
+++ b/code/modules/photography/camera/camera_image_capturing.dm
@@ -14,7 +14,9 @@
#define PHYSICAL_POSITION(atom) ((atom.y * ICON_SIZE_Y) + (atom.pixel_y))
-/obj/item/camera/proc/camera_get_icon(list/turfs, turf/center, psize_x = 96, psize_y = 96, datum/turf_reservation/clone_area, size_x, size_y, total_x, total_y)
+/obj/item/camera/proc/camera_get_icon(list/turfs, turf/center, datum/turf_reservation/clone_area)
+ PRIVATE_PROC(TRUE)
+
var/list/atoms = list()
var/list/lighting = list()
var/skip_normal = FALSE
@@ -24,35 +26,34 @@
backdrop.blend_mode = BLEND_OVERLAY
backdrop.color = "#292319"
- if(istype(clone_area) && total_x == clone_area.width && total_y == clone_area.height && size_x >= 0 && size_y > 0)
- var/turf/bottom_left = clone_area.bottom_left_turfs[1]
- var/cloned_center_x = round(bottom_left.x + ((total_x - 1) / 2))
- var/cloned_center_y = round(bottom_left.y + ((total_y - 1) / 2))
- for(var/t in turfs)
- var/turf/T = t
- var/offset_x = T.x - center.x
- var/offset_y = T.y - center.y
- var/turf/newT = locate(cloned_center_x + offset_x, cloned_center_y + offset_y, bottom_left.z)
- if(!(newT in clone_area.reserved_turfs)) //sanity check so we don't overwrite other areas somehow
- continue
- atoms += new /obj/effect/appearance_clone(newT, T)
- if(T.loc.icon_state)
- atoms += new /obj/effect/appearance_clone(newT, T.loc)
- if(T.lighting_object)
- var/obj/effect/appearance_clone/lighting_overlay = new(newT)
- lighting_overlay.appearance = T.lighting_object.current_underlay
- lighting_overlay.underlays += backdrop
- lighting_overlay.blend_mode = BLEND_MULTIPLY
- lighting += lighting_overlay
- for(var/atom/found_atom as anything in T.contents)
- if(HAS_TRAIT(found_atom, TRAIT_INVISIBLE_TO_CAMERA))
- if(see_ghosts)
- atoms += new /obj/effect/appearance_clone(newT, found_atom)
- else if(!found_atom.invisibility || (see_ghosts && isobserver(found_atom)))
+ var/turf/bottom_left = clone_area.bottom_left_turfs[1]
+ var/cloned_center_x = round(bottom_left.x + ((clone_area.width - 1) / 2))
+ var/cloned_center_y = round(bottom_left.y + ((clone_area.height - 1) / 2))
+ for(var/t in turfs)
+ var/turf/T = t
+ var/offset_x = T.x - center.x
+ var/offset_y = T.y - center.y
+ var/turf/newT = locate(cloned_center_x + offset_x, cloned_center_y + offset_y, bottom_left.z)
+ if(!(newT in clone_area.reserved_turfs)) //sanity check so we don't overwrite other areas somehow
+ continue
+ atoms += new /obj/effect/appearance_clone(newT, T)
+ if(T.loc.icon_state)
+ atoms += new /obj/effect/appearance_clone(newT, T.loc)
+ if(T.lighting_object)
+ var/obj/effect/appearance_clone/lighting_overlay = new(newT)
+ lighting_overlay.appearance = T.lighting_object.current_underlay
+ lighting_overlay.underlays += backdrop
+ lighting_overlay.blend_mode = BLEND_MULTIPLY
+ lighting += lighting_overlay
+ for(var/atom/found_atom as anything in T.contents)
+ if(HAS_TRAIT(found_atom, TRAIT_INVISIBLE_TO_CAMERA))
+ if(see_ghosts)
atoms += new /obj/effect/appearance_clone(newT, found_atom)
- skip_normal = TRUE
- wipe_atoms = TRUE
- center = locate(cloned_center_x, cloned_center_y, bottom_left.z)
+ else if(!found_atom.invisibility || (see_ghosts && isobserver(found_atom)))
+ atoms += new /obj/effect/appearance_clone(newT, found_atom)
+ skip_normal = TRUE
+ wipe_atoms = TRUE
+ center = locate(cloned_center_x, cloned_center_y, bottom_left.z)
if(!skip_normal)
for(var/i in turfs)
@@ -71,8 +72,11 @@
atoms += A
CHECK_TICK
+ var/psize_x = clone_area.width * ICON_SIZE_X
+ var/psize_y = clone_area.height * ICON_SIZE_Y
var/icon/res = icon('icons/blanks/96x96.dmi', "nothing")
res.Scale(psize_x, psize_y)
+
atoms += lighting
var/list/sorted = list()
diff --git a/code/modules/photography/camera/silicon_camera.dm b/code/modules/photography/camera/silicon_camera.dm
index a40207c559a..b85d71b487f 100644
--- a/code/modules/photography/camera/silicon_camera.dm
+++ b/code/modules/photography/camera/silicon_camera.dm
@@ -2,6 +2,7 @@
/obj/item/camera/siliconcam
name = "silicon photo camera"
resistance_flags = INDESTRUCTIBLE
+ cooldown = 2 SECONDS
/// List of all pictures taken by this camera.
var/list/datum/picture/stored = list()
@@ -15,7 +16,7 @@
if(!can_take_picture(clicker))
return
clicker.face_atom(clicked_on)
- INVOKE_ASYNC(src, PROC_REF(captureimage), clicked_on, clicker, picture_size_x - 1, picture_size_y - 1)
+ attempt_picture(clicked_on, clicker)
toggle_camera_mode(clicker, sound = FALSE)
/// Toggles the camera mode on or off.
@@ -56,7 +57,10 @@
/obj/item/camera/siliconcam/proc/viewpictures(mob/user)
var/datum/picture/selection = selectpicture(user)
if(istype(selection))
- show_picture(user, selection)
+ var/obj/item/photo/P = new(src, selection)
+ P.show(user)
+ to_chat(user, P.desc)
+ qdel(P)
/obj/item/camera/siliconcam/ai_camera
name = "AI photo camera"
diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm
index a75df1542a6..a0343a05385 100644
--- a/code/modules/research/xenobiology/crossbreeding/_misc.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm
@@ -45,11 +45,8 @@ Slimecrossing Items
ret[part.body_zone] = saved_part
return ret
-/obj/item/camera/rewind/photo_taken(atom/target, mob/user)
+/obj/item/camera/rewind/on_flash(atom/target, mob/user)
. = ..()
- if(!.)
- return
-
if(user == target)
to_chat(user, span_notice("You take a selfie!"))
else
@@ -66,10 +63,8 @@ Slimecrossing Items
pictures_left = 1
pictures_max = 1
-/obj/item/camera/timefreeze/photo_taken(atom/target, mob/user)
+/obj/item/camera/timefreeze/on_flash(atom/target, mob/user)
. = ..()
- if(!.)
- return
new /obj/effect/timestop(get_turf(target), 2, 50, list(user))
//Hypercharged slime cell - Charged Yellow
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
index 289bfc097bc..3814df5ef38 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -267,6 +267,8 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
/obj/structure/holosign/robot_seat,
//Singleton
/mob/dview,
+ //Template type
+ /obj/item/bodypart,
//This is meant to fail extremely loud every single time it occurs in any environment in any context, and it falsely alarms when this unit test iterates it. Let's not spawn it in.
/obj/merge_conflict_marker,
//Not meant to spawn without the machine wand
diff --git a/code/modules/wiremod/components/action/camera.dm b/code/modules/wiremod/components/action/camera.dm
new file mode 100644
index 00000000000..7a3f9460501
--- /dev/null
+++ b/code/modules/wiremod/components/action/camera.dm
@@ -0,0 +1,110 @@
+/obj/item/circuit_component/camera
+ display_name = "Camera"
+ desc = "A polaroid camera that takes pictures when triggered. The picture coordinate ports are relative to the position of the camera."
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
+
+ /// The atom that was photographed from either user click or trigger input.
+ var/datum/port/output/photographed_atom
+ /// The item that was added/removed.
+ var/datum/port/output/picture_taken
+ /// If set, the trigger input will target this atom.
+ var/datum/port/input/picture_target
+ /// If the above is unset, these coordinates will be used.
+ var/datum/port/input/picture_coord_x
+ var/datum/port/input/picture_coord_y
+ /// Adjusts the picture_size_x variable of the camera.
+ var/datum/port/input/adjust_size_x
+ /// Idem but for picture_size_y.
+ var/datum/port/input/adjust_size_y
+
+ /// The camera this circut is attached to.
+ var/obj/item/camera/camera
+
+/obj/item/circuit_component/camera/populate_ports()
+ picture_taken = add_output_port("Picture Taken", PORT_TYPE_SIGNAL)
+ photographed_atom = add_output_port("Photographed Entity", PORT_TYPE_ATOM)
+
+ picture_target = add_input_port("Picture Target", PORT_TYPE_ATOM)
+ picture_coord_x = add_input_port("Picture Coordinate X", PORT_TYPE_NUMBER)
+ picture_coord_y = add_input_port("Picture Coordinate Y", PORT_TYPE_NUMBER)
+ adjust_size_x = add_input_port("Picture Size X", PORT_TYPE_NUMBER, trigger = PROC_REF(sanitize_picture_size))
+ adjust_size_y = add_input_port("Picture Size Y", PORT_TYPE_NUMBER, trigger = PROC_REF(sanitize_picture_size))
+
+/obj/item/circuit_component/camera/register_shell(atom/movable/shell)
+ . = ..()
+ camera = shell
+ RegisterSignal(shell, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_image_captured))
+
+/obj/item/circuit_component/camera/unregister_shell(atom/movable/shell)
+ UnregisterSignal(shell, COMSIG_CAMERA_IMAGE_CAPTURED)
+ camera = null
+ return ..()
+
+///Adjuts the zoom of the camera
+/obj/item/circuit_component/camera/proc/sanitize_picture_size()
+ camera.adjust_zoom(adjust_size_x.value, adjust_size_y.value)
+
+/obj/item/circuit_component/camera/proc/on_image_captured(obj/item/camera/source, atom/target, mob/user)
+ SIGNAL_HANDLER
+ photographed_atom.set_output(target)
+ picture_taken.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/camera/input_received(datum/port/input/port)
+ var/atom/target = picture_target.value
+ if(!target)
+ var/turf/our_turf = get_location()
+ target = locate(our_turf.x + picture_coord_x.value, our_turf.y + picture_coord_y.value, our_turf.z)
+ if(!target)
+ return
+ camera.attempt_picture(target)
+
+/obj/item/circuit_component/mod_program/camera
+ associated_program = /datum/computer_file/program/maintenance/camera
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
+
+ ///A target to take a picture of.
+ var/datum/port/input/picture_target
+ ///The size of the photo to take.
+ var/datum/port/input/picture_size
+ ///The photographed target
+ var/datum/port/output/photographed
+ /**
+ * Pinged when the image has been captured.
+ * I'm not using the default trigger output here because the process is asynced,
+ * even though I'm mostly sure it only sleeps if there's a set user.
+ */
+ var/datum/port/output/photo_taken
+
+/obj/item/circuit_component/mod_program/camera/populate_ports()
+ . = ..()
+ picture_target = add_input_port("Picture Target", PORT_TYPE_ATOM)
+ picture_size = add_input_port("Picture Size", PORT_TYPE_NUMBER)
+ photographed = add_output_port("Photographed Entity", PORT_TYPE_ATOM)
+ photo_taken = add_output_port("Photo Taken", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/mod_program/camera/register_shell(atom/movable/shell)
+ . = ..()
+ var/datum/computer_file/program/maintenance/camera/cam = associated_program
+ RegisterSignal(cam.internal_camera, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_image_captured))
+
+/obj/item/circuit_component/mod_program/camera/unregister_shell()
+ var/datum/computer_file/program/maintenance/camera/cam = associated_program
+ UnregisterSignal(cam.internal_camera, COMSIG_CAMERA_IMAGE_CAPTURED)
+ return ..()
+
+/obj/item/circuit_component/mod_program/camera/input_received(datum/port/input/port)
+ if(!COMPONENT_TRIGGERED_BY(port, trigger_input))
+ return
+ var/atom/target = picture_target.value
+ if(!target)
+ var/turf/our_turf = get_location()
+ target = locate(our_turf.x, our_turf.y, our_turf.z)
+ if(!target)
+ return
+ var/datum/computer_file/program/maintenance/camera/cam = associated_program
+ cam.internal_camera.attempt_picture(target)
+
+/obj/item/circuit_component/mod_program/camera/proc/on_image_captured(obj/item/camera/source, atom/target, mob/user)
+ SIGNAL_HANDLER
+ photographed.set_output(target)
+ photo_taken.set_output(COMPONENT_SIGNAL)
diff --git a/code/modules/modular_computers/file_system/program_circuit.dm b/code/modules/wiremod/core/program_circuit.dm
similarity index 100%
rename from code/modules/modular_computers/file_system/program_circuit.dm
rename to code/modules/wiremod/core/program_circuit.dm
diff --git a/tgstation.dme b/tgstation.dme
index 247820c849f..05586a9e481 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -5753,7 +5753,6 @@
#include "code\modules\modular_computers\file_system\data.dm"
#include "code\modules\modular_computers\file_system\image_file.dm"
#include "code\modules\modular_computers\file_system\program.dm"
-#include "code\modules\modular_computers\file_system\program_circuit.dm"
#include "code\modules\modular_computers\file_system\programs\airestorer.dm"
#include "code\modules\modular_computers\file_system\programs\alarm.dm"
#include "code\modules\modular_computers\file_system\programs\arcade.dm"
@@ -6723,6 +6722,7 @@
#include "code\modules\wiremod\components\abstract\list_variable.dm"
#include "code\modules\wiremod\components\abstract\module.dm"
#include "code\modules\wiremod\components\abstract\variable.dm"
+#include "code\modules\wiremod\components\action\camera.dm"
#include "code\modules\wiremod\components\action\equpiment_action.dm"
#include "code\modules\wiremod\components\action\laserpointer.dm"
#include "code\modules\wiremod\components\action\light.dm"
@@ -6832,6 +6832,7 @@
#include "code\modules\wiremod\core\integrated_circuit.dm"
#include "code\modules\wiremod\core\marker.dm"
#include "code\modules\wiremod\core\port.dm"
+#include "code\modules\wiremod\core\program_circuit.dm"
#include "code\modules\wiremod\core\usb_cable.dm"
#include "code\modules\wiremod\core\variable.dm"
#include "code\modules\wiremod\datatypes\any.dm"
diff --git a/tgui/packages/tgui/interfaces/NtosCamera.tsx b/tgui/packages/tgui/interfaces/NtosCamera.tsx
index 100bbf8c0d8..c55c084a3ee 100644
--- a/tgui/packages/tgui/interfaces/NtosCamera.tsx
+++ b/tgui/packages/tgui/interfaces/NtosCamera.tsx
@@ -13,7 +13,9 @@ import { useBackend } from '../backend';
import { NtosWindow } from '../layouts';
type NtosCameraCommonData = {
- size: number;
+ width: number;
+ height: number;
+ size: string;
minSize: number;
maxSize: number;
maxNameLength: number;
@@ -48,6 +50,8 @@ export const NtosCamera = (props) => {
export const NtosCameraContent = (props) => {
const { act, data } = useBackend();
const {
+ width,
+ height,
size,
minSize,
maxSize,
@@ -74,7 +78,7 @@ export const NtosCameraContent = (props) => {
maxWidth="100%"
height="auto"
fixErrors
- src={photo}
+ src={`data:image/jpeg;base64,${photo}`}
/>
@@ -86,7 +90,11 @@ export const NtosCameraContent = (props) => {
disabled={!canEditMetadata}
value={name}
maxLength={maxNameLength}
- onChange={(value) => act('setName', { value })}
+ onChange={(value) =>
+ act('setName', {
+ value: value,
+ })
+ }
/>
@@ -98,7 +106,11 @@ export const NtosCameraContent = (props) => {
disabled={!canEditMetadata}
value={desc}
maxLength={maxDescLength}
- onChange={(value) => act('setDesc', { value })}
+ onChange={(value) =>
+ act('setDesc', {
+ value: value,
+ })
+ }
/>
@@ -111,7 +123,11 @@ export const NtosCameraContent = (props) => {
disabled={!canEditMetadata}
value={caption}
maxLength={maxCaptionLength}
- onChange={(value) => act('setCaption', { value })}
+ onChange={(value) =>
+ act('setCaption', {
+ value: value,
+ })
+ }
/>
@@ -138,17 +154,39 @@ export const NtosCameraContent = (props) => {
)}
+ {size}
- Photo Size:
+ Half Width Aperture:
act('adjustSize', { value })}
+ onChange={(e, value) =>
+ act('adjustWidth', {
+ value: value,
+ })
+ }
+ />
+
+
+
+ Half Height Aperture:
+
+
+ act('adjustHeight', {
+ value: value,
+ })
+ }
/>
diff --git a/tgui/packages/tgui/interfaces/NtosFileManager.tsx b/tgui/packages/tgui/interfaces/NtosFileManager.tsx
index 2e4af805686..8bb426d9491 100644
--- a/tgui/packages/tgui/interfaces/NtosFileManager.tsx
+++ b/tgui/packages/tgui/interfaces/NtosFileManager.tsx
@@ -90,7 +90,7 @@ const PrintDialog = (props: PrintDialogProps) => {
left="5%"
>
-
+
{printTypes.map((type, i) => {