/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/iron =SMALL_MATERIAL_AMOUNT*0.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT*1.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, 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 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) 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("
"), 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