From 2a6e4fce5946bda8dd9cd7760354c2d9f24583ae Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Fri, 20 Jul 2018 15:22:00 -0700 Subject: [PATCH 1/3] Photography Update --- code/__DEFINES/misc.dm | 9 +- code/__HELPERS/icons.dm | 9 - code/__HELPERS/priority_announce.dm | 4 +- code/__HELPERS/qdel.dm | 10 + code/__HELPERS/unsorted.dm | 13 - code/_globalvars/logging.dm | 10 + code/_onclick/hud/ai.dm | 8 +- .../configuration/entries/general.dm | 4 + code/controllers/subsystem/pathfinder.dm | 2 +- code/controllers/subsystem/persistence.dm | 65 +- code/datums/datacore.dm | 17 +- code/datums/datum.dm | 64 ++ code/datums/traits/negative.dm | 2 +- code/game/gamemodes/objective_items.dm | 8 +- code/game/machinery/computer/medical.dm | 4 +- code/game/machinery/computer/security.dm | 21 +- code/game/machinery/newscaster.dm | 28 +- .../machinery/telecomms/computers/message.dm | 2 +- .../telecomms/machines/message_server.dm | 8 +- code/game/objects/items/devices/PDA/PDA.dm | 16 +- .../crates_lockers/closets/secure/cargo.dm | 1 + .../closets/secure/engineering.dm | 1 + .../crates_lockers/closets/secure/medical.dm | 1 + .../closets/secure/scientist.dm | 1 + .../crates_lockers/closets/secure/security.dm | 3 + code/game/world.dm | 16 +- code/modules/admin/verbs/mapping.dm | 2 +- .../abductor/machinery/experiment.dm | 5 +- .../space_management/space_reservation.dm | 4 + code/modules/mob/living/carbon/examine.dm | 1 - code/modules/mob/living/silicon/robot/laws.dm | 3 +- .../modules/mob/living/silicon/robot/robot.dm | 8 + code/modules/mob/living/silicon/silicon.dm | 4 +- code/modules/paperwork/photocopier.dm | 59 +- code/modules/paperwork/photography.dm | 661 ------------------ code/modules/photography/_pictures.dm | 163 +++++ code/modules/photography/camera/camera.dm | 221 ++++++ .../camera/camera_image_capturing.dm | 88 +++ code/modules/photography/camera/film.dm | 14 + code/modules/photography/camera/other.dm | 14 + .../photography/camera/picture_frame.dm | 145 ++++ .../photography/camera/silicon_camera.dm | 98 +++ code/modules/photography/photo_album.dm | 43 ++ code/modules/photography/photo_item.dm | 86 +++ code/modules/photography/photos/album.dm | 55 ++ code/modules/photography/photos/frame.dm | 157 +++++ code/modules/photography/photos/photo.dm | 91 +++ config/config.txt | 6 + tgstation.dme | 11 +- 49 files changed, 1455 insertions(+), 811 deletions(-) create mode 100644 code/__HELPERS/qdel.dm delete mode 100644 code/modules/paperwork/photography.dm create mode 100644 code/modules/photography/_pictures.dm create mode 100644 code/modules/photography/camera/camera.dm create mode 100644 code/modules/photography/camera/camera_image_capturing.dm create mode 100644 code/modules/photography/camera/film.dm create mode 100644 code/modules/photography/camera/other.dm create mode 100644 code/modules/photography/camera/picture_frame.dm create mode 100644 code/modules/photography/camera/silicon_camera.dm create mode 100644 code/modules/photography/photo_album.dm create mode 100644 code/modules/photography/photo_item.dm create mode 100644 code/modules/photography/photos/album.dm create mode 100644 code/modules/photography/photos/frame.dm create mode 100644 code/modules/photography/photos/photo.dm diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index f1b62745ed..408b14f6d7 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -23,6 +23,8 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s //define THIS_PROC_TYPE_WEIRD_STR "[THIS_PROC_TYPE_WEIRD]" //Included for completeness //define THIS_PROC_TYPE_WEIRD_STR_WITH_ARGS "[THIS_PROC_TYPE_WEIRD]([args.Join(",")])" //Ditto +#define NOT_IMPLEMENTED "NOT_IMPLEMENTED" + #define MIDNIGHT_ROLLOVER 864000 //number of deciseconds in a day #define JANUARY 1 @@ -454,6 +456,9 @@ GLOBAL_LIST_INIT(pda_styles, list(MONO, VT, ORBITRON, SHARE)) //Filters #define AMBIENT_OCCLUSION filter(type="drop_shadow", x=0, y=-2, size=4, border=4, color="#04080FAA") - #define STANDARD_GRAVITY 1 //Anything above this is high gravity, anything below no grav -#define GRAVITY_DAMAGE_TRESHOLD 3 //Starting with this value gravity will start to damage mobs \ No newline at end of file +#define GRAVITY_DAMAGE_TRESHOLD 3 //Starting with this value gravity will start to damage mobs + +#define CAMERA_NO_GHOSTS 0 +#define CAMERA_SEE_GHOSTS_BASIC 1 +#define CAMERA_SEE_GHOSTS_ORBIT 2 diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 9a8a249159..aed2e9c749 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -931,15 +931,6 @@ world flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect. return flat_icon -//For photo camera. -/proc/build_composite_icon(atom/A) - var/icon/composite = icon(A.icon, A.icon_state, A.dir, 1) - for(var/O in A.overlays) - var/image/I = O - composite.Blend(icon(I.icon, I.icon_state, I.dir, 1), ICON_OVERLAY) - return composite - - //What the mob looks like as animated static //By vg's ComicIronic /proc/getStaticIcon(icon/A, safety = TRUE) diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm index bbaf7b275d..8819eb4d04 100644 --- a/code/__HELPERS/priority_announce.dm +++ b/code/__HELPERS/priority_announce.dm @@ -19,7 +19,7 @@ announcement += "

[sender_override]

" if (title && length(title) > 0) announcement += "

[html_encode(title)]

" - + if(!sender_override) if(title == "") GLOB.news_network.SubmitArticle(text, "Central Command Update", "Station Announcements", null) @@ -46,7 +46,7 @@ var/datum/comm_message/M = new M.title = title M.content = text - + SScommunications.send_message(M) /proc/minor_announce(message, title = "Attention:", alert) diff --git a/code/__HELPERS/qdel.dm b/code/__HELPERS/qdel.dm new file mode 100644 index 0000000000..9bd0d4c95f --- /dev/null +++ b/code/__HELPERS/qdel.dm @@ -0,0 +1,10 @@ +#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE) +#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME) +#define QDEL_NULL(item) qdel(item); item = null +#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); } +#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE) +#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); } +#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); } + +/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly. + QDEL_LIST(L) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 273e1cf1b7..f7aac6123e 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1293,19 +1293,6 @@ GLOBAL_REAL_VAR(list/stack_trace_storage) #define RANDOM_COLOUR (rgb(rand(0,255),rand(0,255),rand(0,255))) -#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE) -#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME) -#define QDEL_NULL(item) qdel(item); item = null -#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); } -#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE) -#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); } -#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); } - -/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly. - QDEL_LIST(L) - - - /proc/random_nukecode() var/val = rand(0, 99999) var/str = "[val]" diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 2951ddf165..8963b2d4b8 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -44,3 +44,13 @@ GLOBAL_LIST_EMPTY(adminlog) GLOBAL_PROTECT(adminlog) GLOBAL_LIST_EMPTY(active_turfs_startlist) + +/////Picture logging +GLOBAL_VAR(picture_log_directory) +GLOBAL_PROTECT(picture_log_directory) + +GLOBAL_VAR_INIT(picture_logging_id, 1) +GLOBAL_PROTECT(picture_logging_id) +GLOBAL_VAR(picture_logging_prefix) +GLOBAL_PROTECT(picture_logging_prefix) +///// diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index 2bb514d008..5e2879a5d3 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -135,10 +135,10 @@ return if(isAI(usr)) var/mob/living/silicon/ai/AI = usr - AI.aicamera.toggle_camera_mode() + AI.aicamera.toggle_camera_mode(usr) else if(iscyborg(usr)) var/mob/living/silicon/robot/R = usr - R.aicamera.toggle_camera_mode() + R.aicamera.toggle_camera_mode(usr) /obj/screen/ai/image_view name = "View Images" @@ -149,10 +149,10 @@ return if(isAI(usr)) var/mob/living/silicon/ai/AI = usr - AI.aicamera.viewpictures() + AI.aicamera.viewpictures(usr) else if(iscyborg(usr)) var/mob/living/silicon/robot/R = usr - R.aicamera.viewpictures() + R.aicamera.viewpictures(usr) /obj/screen/ai/sensors name = "Sensor Augmentation" diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 1d41e9a765..f05a42e72c 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -410,3 +410,7 @@ /datum/config_entry/string/default_view config_entry_value = "15x15" + +/datum/config_entry/flag/log_pictures + +/datum/config_entry/flag/picture_logging_camera diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm index 871eba49ad..fee93d14b2 100644 --- a/code/controllers/subsystem/pathfinder.dm +++ b/code/controllers/subsystem/pathfinder.dm @@ -9,7 +9,7 @@ SUBSYSTEM_DEF(pathfinder) /datum/controller/subsystem/pathfinder/Initialize() space_type_cache = typecacheof(/turf/open/space) mobs = new(10) - circuits = new(3) + circuits = new(3) return ..() /datum/flowcache diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm index 14db2f1607..b844a1fc1a 100644 --- a/code/controllers/subsystem/persistence.dm +++ b/code/controllers/subsystem/persistence.dm @@ -15,6 +15,9 @@ SUBSYSTEM_DEF(persistence) var/list/spawned_objects = list() var/list/antag_rep = list() var/list/antag_rep_change = list() + var/list/picture_logging_information = list() + var/list/obj/structure/sign/picture_frame/photo_frames + var/list/obj/item/storage/photo_album/photo_albums /datum/controller/subsystem/persistence/Initialize() LoadSatchels() @@ -22,6 +25,7 @@ SUBSYSTEM_DEF(persistence) LoadChiselMessages() LoadTrophies() LoadRecentModes() + LoadPhotoPersistence() if(CONFIG_GET(flag/use_antag_rep)) LoadAntagReputation() ..() @@ -194,15 +198,74 @@ SUBSYSTEM_DEF(persistence) T.placer_key = chosen_trophy["placer_key"] T.update_icon() - /datum/controller/subsystem/persistence/proc/CollectData() CollectChiselMessages() CollectSecretSatchels() CollectTrophies() CollectRoundtype() + SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. if(CONFIG_GET(flag/use_antag_rep)) CollectAntagReputation() +/datum/controller/subsystem/persistence/proc/LoadPhotoPersistence() + var/album_path = file("data/photo_albums.json") + var/frame_path = file("data/photo_frames.json") + if(fexists(album_path)) + var/list/json = json_decode(file2text(album_path)) + if(json.len) + for(var/i in photo_albums) + var/obj/item/storage/photo_album/A = i + if(!A.persistence_id) + continue + if(json[A.persistence_id]) + A.populate_from_id_list(json[A.persistence_id]) + + if(fexists(frame_path)) + var/list/json = json_decode(file2text(frame_path)) + if(json.len) + for(var/i in photo_frames) + var/obj/structure/sign/picture_frame/PF = i + if(!PF.persistence_id) + continue + if(json[PF.persistence_id]) + PF.load_from_id(json[PF.persistence_id]) + +/datum/controller/subsystem/persistence/proc/SavePhotoPersistence() + var/album_path = file("data/photo_albums.json") + var/frame_path = file("data/photo_frames.json") + + var/list/frame_json = list() + var/list/album_json = list() + + if(fexists(album_path)) + album_json = json_decode(file2text(album_path)) + fdel(album_path) + + for(var/i in photo_albums) + var/obj/item/storage/photo_album/A = i + if(!istype(A) || !A.persistence_id) + continue + var/list/L = A.get_picture_id_list() + album_json[A.persistence_id] = L + + album_json = json_encode(album_json) + + WRITE_FILE(album_path, album_json) + + if(fexists(frame_path)) + frame_json = json_decode(file2text(frame_path)) + fdel(frame_path) + + for(var/i in photo_frames) + var/obj/structure/sign/picture_frame/F = i + if(!istype(F) || !F.persistence_id) + continue + frame_json[F.persistence_id] = F.get_photo_id() + + frame_json = json_encode(frame_json) + + WRITE_FILE(frame_path, frame_json) + /datum/controller/subsystem/persistence/proc/CollectSecretSatchels() satchel_blacklist = typecacheof(list(/obj/item/stack/tile/plasteel, /obj/item/crowbar)) var/list/satchels_to_add = list() diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index c88a86c092..60bb24c8c2 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -214,13 +214,16 @@ if(!C) C = H.client var/image = get_id_photo(H, C, show_directions) - var/obj/item/photo/photo_front = new() - var/obj/item/photo/photo_side = new() - for(var/D in show_directions) - if(D == SOUTH) - photo_front.photocreate(null, icon(image, dir = D)) - if(D == WEST || D == EAST) - photo_side.photocreate(null, icon(image, dir = D)) + var/datum/picture/pf = new + var/datum/picture/ps = new + pf.picture_name = "[H]" + ps.picture_name = "[H]" + pf.picture_desc = "This is [H]." + ps.picture_desc = "This is [H]." + pf.picture_image = icon(image, dir = SOUTH) + ps.picture_image = icon(image, dir = WEST) + var/obj/item/photo/photo_front = new(null, pf) + var/obj/item/photo/photo_side = new(null, ps) //These records should ~really~ be merged or something //General Record diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 9e14704123..aba03c784c 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -83,3 +83,67 @@ /datum/proc/to_chat_check_changed_vars(target = world) to_chat(target, txt_changed_vars()) #endif + +//Return a LIST for serialize_datum to encode! Not the actual json! +/datum/proc/serialize_list(list/options) + return NOT_IMPLEMENTED + +//Accepts a LIST from deserialize_datum. Should return src or another datum. +/datum/proc/deserialize_list(json, list/options) + return NOT_IMPLEMENTED + +//Serializes into JSON. Does not encode type. +/datum/proc/serialize_json(list/options) + . = serialize_list(options) + if((. == NOT_IMPLEMENTED) || !islist(.)) + . = null + else + . = json_encode(.) + +//Deserializes from JSON. Does not parse type. +/datum/proc/deserialize_json(list/input, list/options) + var/list/jsonlist = json_decode(input) + . = deserialize_list(jsonlist) + if(!istype(., /datum)) + . = null + +/proc/json_serialize_datum(datum/D, list/options) + if(!istype(D)) + return + var/list/jsonlist = D.serialize_list(options) + if(islist(jsonlist)) + jsonlist["DATUM_TYPE"] = D.type + return json_encode(jsonlist) + +/proc/json_deserialize_datum(list/jsonlist, list/options, target_type, strict_target_type = FALSE) + if(!islist(jsonlist)) + if(!istext(jsonlist)) + CRASH("Invalid JSON") + return + jsonlist = json_decode(jsonlist) + if(!islist(jsonlist)) + CRASH("Invalid JSON") + return + if(!jsonlist["DATUM_TYPE"]) + return + if(!ispath(jsonlist["DATUM_TYPE"])) + if(!istext(jsonlist["DATUM_TYPE"])) + return + jsonlist["DATUM_TYPE"] = text2path(jsonlist["DATUM_TYPE"]) + if(!ispath(jsonlist["DATUM_TYPE"])) + return + if(target_type) + if(!ispath(target_type)) + return + if(strict_target_type) + if(target_type != jsonlist["DATUM_TYPE"]) + return + else if(!ispath(jsonlist["DATUM_TYPE"], target_type)) + return + var/typeofdatum = jsonlist["DATUM_TYPE"] //BYOND won't directly read if this is just put in the line below, and will instead runtime because it thinks you're trying to make a new list? + var/datum/D = new typeofdatum + var/datum/returned = D.deserialize_list(jsonlist, options) + if(!istype(returned, /datum)) + qdel(D) + else + return returned \ No newline at end of file diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm index 7bbcbe6171..2f2ee53e3e 100644 --- a/code/datums/traits/negative.dm +++ b/code/datums/traits/negative.dm @@ -12,7 +12,7 @@ var/mob/living/carbon/human/H = quirk_holder if(NOBLOOD in H.dna.species.species_traits) //can't lose blood if your species doesn't have any return - else + else quirk_holder.blood_volume -= 0.275 diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index d42c546990..6603bb3604 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -148,12 +148,12 @@ /datum/objective_item/steal/blueprints/check_special_completion(obj/item/I) if(istype(I, /obj/item/areaeditor/blueprints)) - return 1 + return TRUE if(istype(I, /obj/item/photo)) var/obj/item/photo/P = I - if(P.blueprints) //if the blueprints are in frame - return 1 - return 0 + if(P.picture.has_blueprints) //if the blueprints are in frame + return TRUE + return FALSE /datum/objective_item/steal/slime name = "an unused sample of slime extract." diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 0ee394d889..2f1c0020b9 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -108,10 +108,10 @@ if(active1 in GLOB.data_core.general) if(istype(active1.fields["photo_front"], /obj/item/photo)) var/obj/item/photo/P1 = active1.fields["photo_front"] - user << browse_rsc(P1.img, "photo_front") + user << browse_rsc(P1.picture.picture_image, "photo_front") if(istype(active1.fields["photo_side"], /obj/item/photo)) var/obj/item/photo/P2 = active1.fields["photo_side"] - user << browse_rsc(P2.img, "photo_side") + user << browse_rsc(P2.picture.picture_image, "photo_side") dat += "Name:[active1.fields["name"]]" dat += "" dat += "" diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm index 8a2a06bf1f..666966ee2e 100644 --- a/code/game/machinery/computer/security.dm +++ b/code/game/machinery/computer/security.dm @@ -182,10 +182,10 @@ if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)) if(istype(active1.fields["photo_front"], /obj/item/photo)) var/obj/item/photo/P1 = active1.fields["photo_front"] - user << browse_rsc(P1.img, "photo_front") + user << browse_rsc(P1.picture.picture_image, "photo_front") if(istype(active1.fields["photo_side"], /obj/item/photo)) var/obj/item/photo/P2 = active1.fields["photo_side"] - user << browse_rsc(P2.img, "photo_side") + user << browse_rsc(P2.picture.picture_image, "photo_side") dat += {"
@@ -446,7 +446,7 @@ What a mess.*/ sleep(30) if((istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)))//make sure the record still exists. var/obj/item/photo/photo = active1.fields["photo_front"] - new /obj/item/poster/wanted(src.loc, photo.img, wanted_name, info) + new /obj/item/poster/wanted(loc, photo.picture.picture_image, wanted_name, info) printing = 0 //RECORD DELETE @@ -611,7 +611,7 @@ What a mess.*/ if(photo) qdel(active1.fields["photo_front"]) //Lets center it to a 32x32. - var/icon/I = photo.img + var/icon/I = photo.picture.picture_image var/w = I.Width() var/h = I.Height() var/dw = w - 32 @@ -622,7 +622,7 @@ What a mess.*/ if(active1.fields["photo_front"]) if(istype(active1.fields["photo_front"], /obj/item/photo)) var/obj/item/photo/P = active1.fields["photo_front"] - print_photo(P.img, active1.fields["name"]) + print_photo(P.picture.picture_image, active1.fields["name"]) if("show_photo_side") if(active1.fields["photo_side"]) if(istype(active1.fields["photo_side"], /obj/item/photo)) @@ -633,7 +633,7 @@ What a mess.*/ if(photo) qdel(active1.fields["photo_side"]) //Lets center it to a 32x32. - var/icon/I = photo.img + var/icon/I = photo.picture.picture_image var/w = I.Width() var/h = I.Height() var/dw = w - 32 @@ -644,7 +644,7 @@ What a mess.*/ if(active1.fields["photo_side"]) if(istype(active1.fields["photo_side"], /obj/item/photo)) var/obj/item/photo/P = active1.fields["photo_side"] - print_photo(P.img, active1.fields["name"]) + print_photo(P.picture.picture_image, active1.fields["name"]) if("mi_crim_add") if(istype(active1, /datum/data/record)) var/t1 = stripped_input(usr, "Please input minor crime names:", "Secure. records", "", null) @@ -759,10 +759,9 @@ What a mess.*/ var/obj/item/photo/P = null if(issilicon(user)) var/mob/living/silicon/tempAI = user - var/datum/picture/selection = tempAI.GetPhoto() + var/datum/picture/selection = tempAI.GetPhoto(user) if(selection) - P = new() - P.photocreate(selection.fields["icon"], selection.fields["img"], selection.fields["desc"]) + P = new(null, selection) else if(istype(user.get_active_held_item(), /obj/item/photo)) P = user.get_active_held_item() return P @@ -778,7 +777,7 @@ What a mess.*/ small_img.Scale(8, 8) ic.Blend(small_img,ICON_OVERLAY, 13, 13) P.icon = ic - P.img = temp + P.picture.picture_image = temp P.desc = "The photo on file for [name]." P.pixel_x = rand(-10, 10) P.pixel_y = rand(-10, 10) diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm index aa847a4624..bb4868d054 100644 --- a/code/game/machinery/newscaster.dm +++ b/code/game/machinery/newscaster.dm @@ -126,9 +126,9 @@ GLOBAL_LIST_EMPTY(allCasters) newMsg.is_admin_message = adminMessage newMsg.locked = !allow_comments if(photo) - newMsg.img = photo.img + newMsg.img = photo.picture.picture_image newMsg.caption = photo.scribble - newMsg.photo_file = save_photo(photo.img) + newMsg.photo_file = save_photo(photo.picture.picture_image) for(var/datum/newscaster/feed_channel/FC in network_channels) if(FC.channel_name == channel_name) FC.messages += newMsg @@ -145,8 +145,8 @@ GLOBAL_LIST_EMPTY(allCasters) wanted_issue.scannedUser = scanned_user wanted_issue.isAdminMsg = adminMsg if(photo) - wanted_issue.img = photo.img - wanted_issue.photo_file = save_photo(photo.img) + wanted_issue.img = photo.picture.picture_image + wanted_issue.photo_file = save_photo(photo.picture.picture_image) if(newMessage) for(var/obj/machinery/newscaster/N in GLOB.allCasters) N.newsAlert() @@ -794,10 +794,7 @@ GLOBAL_LIST_EMPTY(allCasters) if(photo && !user.transferItemToLoc(photo, src)) photo = null if(issilicon(user)) - var/list/nametemp = list() - var/find - var/datum/picture/selection - var/obj/item/camera/siliconcam/targetcam = null + var/obj/item/camera/siliconcam/targetcam if(isAI(user)) var/mob/living/silicon/ai/R = user targetcam = R.aicamera @@ -809,19 +806,12 @@ GLOBAL_LIST_EMPTY(allCasters) targetcam = R.aicamera else to_chat(user, "You cannot interface with silicon photo uploading!") - if(targetcam.aipictures.len == 0) + if(!targetcam.stored.len) to_chat(usr, "No images saved") return - for(var/datum/picture/t in targetcam.aipictures) - nametemp += t.fields["name"] - find = input("Select image (numbered in order taken)") in nametemp - var/obj/item/photo/P = new/obj/item/photo() - for(var/datum/picture/q in targetcam.aipictures) - if(q.fields["name"] == find) - selection = q - break - P.photocreate(selection.fields["icon"], selection.fields["img"], selection.fields["desc"]) - P.sillynewscastervar = 1 + var/datum/picture/selection = targetcam.selectpicture(user) + var/obj/item/photo/P = new(null, selection) + P.sillynewscastervar = TRUE photo = P qdel(P) diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm index 74881cb064..5996bcea6b 100644 --- a/code/game/machinery/telecomms/computers/message.dm +++ b/code/game/machinery/telecomms/computers/message.dm @@ -141,7 +141,7 @@ break // Del - Sender - Recepient - Message // X - Al Green - Your Mom - WHAT UP!? - dat += "" + dat += "" dat += "
Name: [active1.fields["name"]] 
ID: [active1.fields["id"]] 
X
[pda.sender][pda.recipient][pda.message][pda.photo ? " (Photo)":""]
X
[pda.sender][pda.recipient][pda.message][pda.picture ? " (Photo)":""]
" //Hacking screen. if(2) diff --git a/code/game/machinery/telecomms/machines/message_server.dm b/code/game/machinery/telecomms/machines/message_server.dm index 45661add0d..3b2dbed96f 100644 --- a/code/game/machinery/telecomms/machines/message_server.dm +++ b/code/game/machinery/telecomms/machines/message_server.dm @@ -124,7 +124,7 @@ var/sender = "Unspecified" var/recipient = "Unspecified" var/message = "Blank" // transferred message - var/icon/photo // attached photo + var/datum/picture/picture // attached photo /datum/data_pda_msg/New(param_rec, param_sender, param_message, param_photo) if(param_rec) @@ -134,17 +134,17 @@ if(param_message) message = param_message if(param_photo) - photo = param_photo + picture = param_photo /datum/data_pda_msg/Topic(href,href_list) ..() if(href_list["photo"]) var/mob/M = usr - M << browse_rsc(photo, "pda_photo.png") + M << browse_rsc(picture.picture_image, "pda_photo.png") M << browse("PDA Photo" \ + "" \ + "" \ - + "", "window=pdaphoto;size=192x192") + + "", "window=pdaphoto;size=[picture.psize_x]x[picture.psize_y]") onclose(M, "pdaphoto") /datum/data_rc_msg diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 389934b778..5b3f657180 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -72,7 +72,7 @@ GLOBAL_LIST_EMPTY(PDAs) var/obj/item/paicard/pai = null // A slot for a personal AI device - var/icon/photo //Scanned photo + var/datum/picture/picture //Scanned photo var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. @@ -636,8 +636,8 @@ GLOBAL_LIST_EMPTY(PDAs) "message" = message, "targets" = string_targets )) - if (photo) - signal.data["photo"] = photo + if (picture) + signal.data["photo"] = picture signal.send_to_receivers() // If it didn't reach, note that fact @@ -657,7 +657,7 @@ GLOBAL_LIST_EMPTY(PDAs) log_talk(user, "[key_name(user)] (PDA: [initial(name)]) sent \"[message]\" to [target_text]", LOGPDA) to_chat(user, "Message sent to [target_text]: \"[message]\"") // Reset the photo - photo = null + picture = null last_text = world.time if (everyone) last_everyone = world.time @@ -811,7 +811,7 @@ GLOBAL_LIST_EMPTY(PDAs) update_icon() else if(istype(C, /obj/item/photo)) var/obj/item/photo/P = C - photo = P.img + picture = P.picture to_chat(user, "You scan \the [C].") else return ..() @@ -925,11 +925,11 @@ GLOBAL_LIST_EMPTY(PDAs) var/selected = plist[c] - if(aicamera.aipictures.len>0) + if(aicamera.stored.len) var/add_photo = input(user,"Do you want to attach a photo?","Photo","No") as null|anything in list("Yes","No") if(add_photo=="Yes") - var/datum/picture/Pic = aicamera.selectpicture(aicamera) - aiPDA.photo = Pic.fields["img"] + var/datum/picture/Pic = aicamera.selectpicture(user) + aiPDA.picture = Pic if(incapacitated()) return diff --git a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm index 7a79b7d2de..29096c6dd8 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm @@ -19,3 +19,4 @@ new /obj/item/export_scanner(src) new /obj/item/door_remote/quartermaster(src) new /obj/item/circuitboard/machine/techfab/department/cargo(src) + new /obj/item/storage/photo_album/QM(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm index 97a157e0be..45216a6d95 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -29,6 +29,7 @@ new /obj/item/inducer(src) new /obj/item/circuitboard/machine/techfab/department/engineering(src) new /obj/item/extinguisher/advanced(src) + new /obj/item/storage/photo_album/CE(src) /obj/structure/closet/secure_closet/engineering_electrical name = "electrical supplies locker" diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm index e2f0fb5f88..93da17b9b1 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -76,6 +76,7 @@ new /obj/item/pet_carrier(src) new /obj/item/wallframe/defib_mount(src) new /obj/item/circuitboard/machine/techfab/department/medical(src) + new /obj/item/storage/photo_album/CMO(src) /obj/structure/closet/secure_closet/animal name = "animal control" diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm index 6e5c425b2b..eb10b97bc3 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm @@ -25,3 +25,4 @@ new /obj/item/laser_pointer(src) new /obj/item/door_remote/research_director(src) new /obj/item/circuitboard/machine/techfab/department/science(src) + new /obj/item/storage/photo_album/RD(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index 52032e9bb3..47226c6a61 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -33,6 +33,7 @@ new /obj/item/gun/energy/e_gun(src) new /obj/item/door_remote/captain(src) new /obj/item/card/id/captains_spare(src) + new /obj/item/storage/photo_album/Captain(src) /obj/structure/closet/secure_closet/hop name = "\proper head of personnel's locker" @@ -59,6 +60,7 @@ new /obj/item/pet_carrier(src) new /obj/item/door_remote/civillian(src) new /obj/item/circuitboard/machine/techfab/department/service(src) + new /obj/item/storage/photo_album/HoP(src) /obj/structure/closet/secure_closet/hos name = "\proper head of security's locker" @@ -91,6 +93,7 @@ new /obj/item/flashlight/seclite(src) new /obj/item/pinpointer/nuke(src) new /obj/item/circuitboard/machine/techfab/department/security(src) + new /obj/item/storage/photo_album/HoS(src) /obj/structure/closet/secure_closet/warden name = "\proper warden's locker" diff --git a/code/game/world.dm b/code/game/world.dm index 7d7b17b4ac..2f3f9b4c7b 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -79,13 +79,24 @@ GLOBAL_PROTECT(security_mode) /world/proc/SetupLogs() var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] if(!override_dir) - GLOB.log_directory = "data/logs/[time2text(world.realtime, "YYYY/MM/DD")]/round-" + var/realtime = world.realtime + var/texttime = time2text(realtime, "YYYY/MM/DD") + GLOB.log_directory = "data/logs/[texttime]/round-" + GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" + GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" if(GLOB.round_id) GLOB.log_directory += "[GLOB.round_id]" + GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" + GLOB.picture_log_directory += "[GLOB.round_id]" else - GLOB.log_directory += "[replacetext(time_stamp(), ":", ".")]" + var/timestamp = replacetext(time_stamp(), ":", ".") + GLOB.log_directory += "[timestamp]" + GLOB.picture_log_directory += "[timestamp]" + GLOB.picture_logging_prefix += "T_[timestamp]_" else GLOB.log_directory = "data/logs/[override_dir]" + GLOB.picture_logging_prefix = "O_[override_dir]_" + GLOB.picture_log_directory = "data/picture_logs/[override_dir]" GLOB.world_game_log = "[GLOB.log_directory]/game.log" GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" @@ -97,6 +108,7 @@ GLOBAL_PROTECT(security_mode) GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" + GLOB.picture_log_directory = GLOB.log_directory + "/pictures" #ifdef UNIT_TESTS GLOB.test_log = file("[GLOB.log_directory]/tests.log") diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 800a94c4ba..8c8b599862 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -47,7 +47,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list( /client/proc/stop_line_profiling, /client/proc/show_line_profiling, /client/proc/create_mapping_job_icons, - /client/proc/debug_z_levels, + /client/proc/debug_z_levels )) /obj/effect/debugging/mapfix_marker diff --git a/code/modules/antagonists/abductor/machinery/experiment.dm b/code/modules/antagonists/abductor/machinery/experiment.dm index 5614fc9105..10581e7bbb 100644 --- a/code/modules/antagonists/abductor/machinery/experiment.dm +++ b/code/modules/antagonists/abductor/machinery/experiment.dm @@ -99,8 +99,9 @@ dat += "

Experiment

" if(occupant) var/obj/item/photo/P = new - P.photocreate(null, icon(dissection_icon(occupant), dir = SOUTH)) - user << browse_rsc(P.img, "dissection_img") + P.picture = new + P.picture.picture_image = icon(dissection_icon(occupant), dir = SOUTH) + user << browse_rsc(P.picture.picture_image, "dissection_img") dat += "
" dat += "" //Avert your eyes dat += "" diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm index 83147d5df7..f1b3b1ccdc 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -3,6 +3,8 @@ //Yes, I'm sorry. /datum/turf_reservation var/list/reserved_turfs = list() + var/width = 0 + var/height = 0 var/bottom_left_coords[3] var/top_right_coords[3] var/wipe_reservation_on_release = TRUE @@ -59,6 +61,8 @@ SSmapping.unused_turfs["[T.z]"] -= T SSmapping.used_turfs[T] = src T.ChangeTurf(turf_type, turf_type) + src.width = width + src.height = height return TRUE /datum/turf_reservation/New() diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 7d6d5afca2..36d26f0faa 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -104,7 +104,6 @@ msg += "[t_He] look[p_s()] very happy.\n" if(MOOD_LEVEL_HAPPY4 to INFINITY) msg += "[t_He] look[p_s()] ecstatic.\n" - msg += "*---------*" to_chat(user, msg) diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm index c03f68a648..f6143e9039 100644 --- a/code/modules/mob/living/silicon/robot/laws.dm +++ b/code/modules/mob/living/silicon/robot/laws.dm @@ -78,4 +78,5 @@ temp = master.supplied[index] if (length(temp) > 0) laws.supplied[index] = temp - return + + picturesync() diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 3a1abd1aa3..473fcb24df 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1205,4 +1205,12 @@ lawsync() lawupdate = 1 return TRUE + picturesync() return FALSE + +/mob/living/silicon/robot/proc/picturesync() + if(connected_ai && connected_ai.aicamera && aicamera) + for(var/i in aicamera.stored) + connected_ai.aicamera.stored[i] = TRUE + for(var/i in connected_ai.aicamera.stored) + aicamera.stored[i] = TRUE diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 3fadd0d6c8..0e69db8f51 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -385,9 +385,9 @@ add_sensors() to_chat(src, "Sensor overlay activated.") -/mob/living/silicon/proc/GetPhoto() +/mob/living/silicon/proc/GetPhoto(mob/user) if (aicamera) - return aicamera.selectpicture(aicamera) + return aicamera.selectpicture(user) /mob/living/silicon/update_transform() var/matrix/ntransform = matrix(transform) //aka transform.Copy() diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index e8a4c26d10..2a880b670c 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -94,30 +94,7 @@ else if(photocopy) for(var/i = 0, i < copies, i++) if(toner >= 5 && !busy && photocopy) //Was set to = 0, but if there was say 3 toner left and this ran, you would get -2 which would be weird for ink - var/obj/item/photo/p = new /obj/item/photo (loc) - var/icon/I = icon(photocopy.icon, photocopy.icon_state) - var/icon/img = icon(photocopy.img) - if(greytoggle == "Greyscale") - if(toner > 10) //plenty of toner, go straight greyscale - I.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) //I'm not sure how expensive this is, but given the many limitations of photocopying, it shouldn't be an issue. - img.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) - else //not much toner left, lighten the photo - I.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(100,100,100)) - img.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(100,100,100)) - toner -= 5 //photos use a lot of ink! - else if(greytoggle == "Color") - if(toner >= 10) - toner -= 10 //Color photos use even more ink! - else - continue - p.icon = I - p.img = img - p.name = photocopy.name - p.desc = photocopy.desc - p.scribble = photocopy.scribble - p.pixel_x = rand(-10, 10) - p.pixel_y = rand(-10, 10) - p.blueprints = photocopy.blueprints //a copy of a picture is still good enough for the syndicate + new /obj/item/photo (loc, photocopy.picture.Copy(greytoggle == "Greyscale"? TRUE : FALSE)) busy = TRUE sleep(15) busy = FALSE @@ -155,15 +132,10 @@ else break var/obj/item/photo/p = new /obj/item/photo (loc) - p.desc = "You see [ass]'s ass on the photo." p.pixel_x = rand(-10, 10) p.pixel_y = rand(-10, 10) - p.img = temp_img - var/icon/small_img = icon(temp_img) //Icon() is needed or else temp_img will be rescaled too >.> - var/icon/ic = icon('icons/obj/items_and_weapons.dmi',"photo") - small_img.Scale(8, 8) - ic.Blend(small_img,ICON_OVERLAY, 13, 13) - p.icon = ic + p.picture = new(null, "You see [ass]'s ass on the photo.", temp_img) + p.update_icon() toner -= 5 busy = TRUE sleep(15) @@ -196,29 +168,14 @@ if(!isAI(usr)) return if(toner >= 5 && !busy) - var/list/nametemp = list() - var/find - var/datum/picture/selection var/mob/living/silicon/ai/tempAI = usr - if(tempAI.aicamera.aipictures.len == 0) + if(tempAI.aicamera.stored.len == 0) to_chat(usr, "No images saved") return - for(var/datum/picture/t in tempAI.aicamera.aipictures) - nametemp += t.fields["name"] - find = input("Select image (numbered in order taken)") in nametemp - var/obj/item/photo/p = new /obj/item/photo (loc) - for(var/datum/picture/q in tempAI.aicamera.aipictures) - if(q.fields["name"] == find) - selection = q - break - var/icon/I = selection.fields["icon"] - var/icon/img = selection.fields["img"] - p.icon = I - p.img = img - p.desc = selection.fields["desc"] - p.blueprints = selection.fields["blueprints"] - p.pixel_x = rand(-10, 10) - p.pixel_y = rand(-10, 10) + var/datum/picture/selection = tempAI.aicamera.selectpicture(usr) + var/obj/item/photo = new(loc, selection) + photo.pixel_x = rand(-10, 10) + photo.pixel_y = rand(-10, 10) toner -= 5 //AI prints color pictures only, thus they can do it more efficiently busy = TRUE sleep(15) diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm deleted file mode 100644 index 0fa3a36d19..0000000000 --- a/code/modules/paperwork/photography.dm +++ /dev/null @@ -1,661 +0,0 @@ -/* Photography! - * Contains: - * Camera - * Camera Film - * Photos - * Photo Albums - * Picture Frames - * AI Photography - */ - -/* - * Film - */ -/obj/item/camera_film - name = "film cartridge" - icon = 'icons/obj/items_and_weapons.dmi' - desc = "A camera film cartridge. Insert it into a camera to reload it." - icon_state = "film" - item_state = "electropack" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - materials = list(MAT_METAL = 10, MAT_GLASS = 10) - -/* - * Photo - */ -/obj/item/photo - name = "photo" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "photo" - item_state = "paper" - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - max_integrity = 50 - grind_results = list("iodine" = 4) - var/icon/img //Big photo image - var/scribble //Scribble on the back. - var/blueprints = 0 //Does it include the blueprints? - var/sillynewscastervar //Photo objects with this set to 1 will not be ejected by a newscaster. Only gets set to 1 if a silicon puts one of their images into a newscaster - -/obj/item/photo/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is taking one last look at \the [src]! It looks like [user.p_theyre()] giving in to death!")//when you wanna look at photo of waifu one last time before you die... - if (user.gender == MALE) - playsound(user, 'sound/voice/human/manlaugh1.ogg', 50, 1)//EVERY TIME I DO IT MAKES ME LAUGH - else if (user.gender == FEMALE) - playsound(user, 'sound/voice/human/womanlaugh.ogg', 50, 1) - return OXYLOSS - -/obj/item/photo/attack_self(mob/user) - user.examinate(src) - - -/obj/item/photo/attackby(obj/item/P, mob/user, params) - if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on [src]!") - return - var/txt = sanitize(input(user, "What would you like to write on the back?", "Photo Writing", null) as text) - txt = copytext(txt, 1, 128) - if(user.canUseTopic(src, BE_CLOSE)) - scribble = txt - ..() - - -/obj/item/photo/examine(mob/user) - ..() - - if(in_range(src, user)) - show(user) - else - to_chat(user, "You need to get closer to get a good look at this photo!") - - -/obj/item/photo/proc/show(mob/user) - user << browse_rsc(img, "tmp_photo.png") - user << browse("[name]" \ - + "" \ - + "" \ - + "[scribble ? "
Written on the back:
[scribble]" : ""]"\ - + "", "window=book;size=192x[scribble ? 400 : 192]") - onclose(user, "[name]") - - -/obj/item/photo/verb/rename() - set name = "Rename photo" - set category = "Object" - set src in usr - - var/n_name = copytext(sanitize(input(usr, "What would you like to label the photo?", "Photo Labelling", null) as text), 1, MAX_NAME_LEN) - //loc.loc check is for making possible renaming photos in clipboards - if((loc == usr || loc.loc && loc.loc == usr) && usr.stat == CONSCIOUS && usr.canmove && !usr.restrained()) - name = "photo[(n_name ? text("- '[n_name]'") : null)]" - add_fingerprint(usr) - -/obj/item/photo/proc/photocreate(inicon, inimg, indesc, inblueprints) - icon = inicon - img = inimg - desc = indesc - blueprints = inblueprints - -/* - * Photo album - */ -/obj/item/storage/photo_album - name = "photo album" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "album" - item_state = "briefcase" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - resistance_flags = FLAMMABLE - -/obj/item/storage/photo_album/Initialize() - . = ..() - GET_COMPONENT(STR, /datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/photo)) - -/* - * Camera - */ -/obj/item/camera - name = "camera" - icon = 'icons/obj/items_and_weapons.dmi' - desc = "A polaroid camera." - icon_state = "camera" - item_state = "electropack" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - materials = list(MAT_METAL = 50, MAT_GLASS = 150) - var/pictures_max = 10 - var/pictures_left = 10 - var/on = TRUE - var/blueprints = 0 //are blueprints visible in the current photo being created? - var/list/aipictures = list() //Allows for storage of pictures taken by AI, in a similar manner the datacore stores info. Keeping this here allows us to share some procs w/ regualar camera - var/see_ghosts = 0 //for the spoop of it - var/obj/item/disk/holodisk/disk - - -/obj/item/camera/CheckParts(list/parts_list) - ..() - var/obj/item/camera/C = locate(/obj/item/camera) in contents - if(C) - pictures_max = C.pictures_max - pictures_left = C.pictures_left - visible_message("[C] has been imbued with godlike power!") - qdel(C) - - -/obj/item/camera/spooky - name = "camera obscura" - desc = "A polaroid camera, some say it can see ghosts!" - see_ghosts = 1 - -/obj/item/camera/detective - name = "Detective's camera" - desc = "A polaroid camera with extra capacity for crime investigations." - pictures_max = 30 - pictures_left = 30 - - -/obj/item/camera/siliconcam //camera AI can take pictures with - name = "silicon photo camera" - var/in_camera_mode = 0 - -/obj/item/camera/siliconcam/ai_camera //camera AI can take pictures with - name = "AI photo camera" - -/obj/item/camera/siliconcam/robot_camera //camera cyborgs can take pictures with.. needs it's own because of verb CATEGORY >.> - name = "Cyborg photo camera" - -/obj/item/camera/siliconcam/robot_camera/verb/borgprinting() - set category ="Robot Commands" - set name = "Print Image" - set src in usr - - if(usr.stat == DEAD) - return //won't work if dead - borgprint() - -/obj/item/camera/attack(mob/living/carbon/human/M, mob/user) - return - - -/obj/item/camera/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/camera_film)) - if(pictures_left) - to_chat(user, "[src] still has some film in it!") - return - if(!user.temporarilyRemoveItemFromInventory(I)) - return - to_chat(user, "You insert [I] into [src].") - qdel(I) - pictures_left = pictures_max - return - if(istype(I, /obj/item/disk/holodisk)) - if (!disk) - if(!user.transferItemToLoc(I, src)) - to_chat(user, "[I] is stuck to your hand!") - return TRUE - to_chat(user, "You slide [I] into the back of [src].") - disk = I - else - to_chat(user, "There's already a disk inside [src].") - return TRUE //no afterattack - ..() - -/obj/item/camera/attack_self(mob/user) - if(!disk) - return - to_chat(user, "You eject [disk] out the back of [src].") - user.put_in_hands(disk) - disk = null - -/obj/item/camera/examine(mob/user) - ..() - to_chat(user, "It has [pictures_left] photo\s left.") - - -/obj/item/camera/proc/camera_get_icon(list/turfs, turf/center) - var/list/atoms = list() - for(var/turf/T in turfs) - atoms.Add(T) - for(var/atom/movable/A in T) - if(A.invisibility) - if(see_ghosts && isobserver(A)) - var/mob/dead/observer/O = A - if(O.orbiting) //so you dont see ghosts following people like antags, etc. - continue - else - continue - atoms.Add(A) - - var/list/sorted = sortTim(atoms,/proc/cmp_atom_layer_asc) - - var/icon/res = icon('icons/effects/96x96.dmi', "") - - for(var/atom/A in sorted) - var/icon/img = getFlatIcon(A, no_anim = TRUE) - if(isliving(A)) - var/mob/living/L = A - if(L.lying) - img.Turn(L.lying) - - var/offX = world.icon_size * (A.x - center.x) + A.pixel_x + 33 - var/offY = world.icon_size * (A.y - center.y) + A.pixel_y + 33 - if(ismovableatom(A)) - var/atom/movable/AM = A - offX += AM.step_x - offY += AM.step_y - - res.Blend(img, blendMode2iconMode(A.blend_mode), offX, offY) - - if(istype(A, /obj/item/areaeditor/blueprints)) - blueprints = 1 - - for(var/turf/T in turfs) - var/area/A = T.loc - if(A.icon_state)//There's actually something to blend in. - res.Blend(getFlatIcon(A,no_anim = TRUE), blendMode2iconMode(A.blend_mode), world.icon_size * (T.x - center.x) + 33, world.icon_size * (T.y - center.y) + 33) - - return res - - -/obj/item/camera/proc/camera_get_mobs(turf/the_turf) - var/mob_detail - for(var/mob/M in the_turf) - if(M.invisibility) - if(see_ghosts && isobserver(M)) - var/mob/dead/observer/O = M - if(O.orbiting) - continue - if(!mob_detail) - mob_detail = "You can see a g-g-g-g-ghooooost! " - else - mob_detail += "You can also see a g-g-g-g-ghooooost!" - else - continue - - var/list/holding = list() - - if(isliving(M)) - var/mob/living/L = M - - for(var/obj/item/I in L.held_items) - if(!holding) - holding += "[L.p_theyre(TRUE)] holding \a [I]" - else - holding += " and \a [I]" - holding = holding.Join() - - if(!mob_detail) - mob_detail = "You can see [L] on the photo[L.health < (L.maxHealth * 0.75) ? " - [L] looks hurt":""].[holding ? " [holding]":"."]. " - else - mob_detail += "You can also see [L] on the photo[L.health < (L.maxHealth * 0.75) ? " - [L] looks hurt":""].[holding ? " [holding]":"."]." - - - return mob_detail - - -/obj/item/camera/proc/captureimage(atom/target, mob/user, flag) //Proc for both regular and AI-based camera to take the image - var/mobs = "" - var/isAi = isAI(user) - var/list/seen - if(!isAi) //crappy check, but without it AI photos would be subject to line of sight from the AI Eye object. Made the best of it by moving the sec camera check inside - if(user.client) //To make shooting through security cameras possible - seen = get_hear(world.view, user.client.eye) //To make shooting through security cameras possible - else - seen = get_hear(world.view, user) - else - seen = get_hear(world.view, target) - - var/list/turfs = list() - for(var/turf/T in range(1, target)) - if(T in seen) - if(isAi && !GLOB.cameranet.checkTurfVis(T)) - continue - else - turfs += T - mobs += camera_get_mobs(T) - - var/icon/temp = icon('icons/effects/96x96.dmi',"") - temp.Blend("#000", ICON_OVERLAY) - temp.Blend(camera_get_icon(turfs, target), ICON_OVERLAY) - - if(!issilicon(user)) - printpicture(user, temp, mobs, flag) - else - aipicture(user, temp, mobs, isAi, blueprints) - - - - -/obj/item/camera/proc/printpicture(mob/user, icon/temp, mobs, flag) //Normal camera proc for creating photos - var/obj/item/photo/P = new/obj/item/photo(get_turf(src)) - if(in_range(src, user)) //needed because of TK - user.put_in_hands(P) - var/icon/small_img = icon(temp) - var/icon/ic = icon('icons/obj/items_and_weapons.dmi',"photo") - small_img.Scale(8, 8) - ic.Blend(small_img,ICON_OVERLAY, 13, 13) - P.icon = ic - P.img = temp - P.desc = mobs - P.pixel_x = rand(-10, 10) - P.pixel_y = rand(-10, 10) - - if(blueprints) - P.blueprints = 1 - blueprints = 0 - - -/obj/item/camera/proc/aipicture(mob/user, icon/temp, mobs, isAi) //instead of printing a picture like a regular camera would, we do this instead for the AI - - var/icon/small_img = icon(temp) - var/icon/ic = icon('icons/obj/items_and_weapons.dmi',"photo") - small_img.Scale(8, 8) - ic.Blend(small_img,ICON_OVERLAY, 13, 13) - var/icon = ic - var/img = temp - var/desc = mobs - var/pixel_x = rand(-10, 10) - var/pixel_y = rand(-10, 10) - - var/injectblueprints = 1 - if(blueprints) - injectblueprints = 1 - blueprints = 0 - - if(isAi) - injectaialbum(icon, img, desc, pixel_x, pixel_y, injectblueprints) - else - injectmasteralbum(icon, img, desc, pixel_x, pixel_y, injectblueprints) - - - -/datum/picture - var/name = "image" - var/list/fields = list() - - -/obj/item/camera/proc/injectaialbum(icon, img, desc, pixel_x, pixel_y, blueprintsinject) //stores image information to a list similar to that of the datacore - var/numberer = 1 - for(var/datum/picture in src.aipictures) - numberer++ - var/datum/picture/P = new() - P.fields["name"] = "Image [numberer] (taken by [src.loc.name])" - P.fields["icon"] = icon - P.fields["img"] = img - P.fields["desc"] = desc - P.fields["pixel_x"] = pixel_x - P.fields["pixel_y"] = pixel_y - P.fields["blueprints"] = blueprintsinject - - aipictures += P - to_chat(usr, "Image recorded") //feedback to the AI player that the picture was taken - -/obj/item/camera/proc/injectmasteralbum(icon, img, desc, pixel_x, pixel_y, blueprintsinject) //stores image information to a list similar to that of the datacore - var/numberer = 1 - var/mob/living/silicon/robot/C = src.loc - if(C.connected_ai) - for(var/datum/picture in C.connected_ai.aicamera.aipictures) - numberer++ - var/datum/picture/P = new() - P.fields["name"] = "Image [numberer] (taken by [src.loc.name])" - P.fields["icon"] = icon - P.fields["img"] = img - P.fields["desc"] = desc - P.fields["pixel_x"] = pixel_x - P.fields["pixel_y"] = pixel_y - P.fields["blueprints"] = blueprintsinject - - C.connected_ai.aicamera.aipictures += P - to_chat(usr, "Image recorded and saved to remote database") //feedback to the Cyborg player that the picture was taken - else - injectaialbum(icon, img, desc, pixel_x, pixel_y, blueprintsinject) - -/obj/item/camera/siliconcam/proc/selectpicture(obj/item/camera/siliconcam/targetloc) - var/list/nametemp = list() - var/find - if(targetloc.aipictures.len == 0) - to_chat(usr, "No images saved") - return - for(var/datum/picture/t in targetloc.aipictures) - nametemp += t.fields["name"] - find = input("Select image (numbered in order taken)") in nametemp - for(var/datum/picture/q in targetloc.aipictures) - if(q.fields["name"] == find) - return q - -/obj/item/camera/siliconcam/proc/viewpichelper(obj/item/camera/siliconcam/targetloc) - var/obj/item/photo/P = new/obj/item/photo() - var/datum/picture/selection = selectpicture(targetloc) - if(selection) - P.photocreate(selection.fields["icon"], selection.fields["img"], selection.fields["desc"]) - P.pixel_x = selection.fields["pixel_x"] - P.pixel_y = selection.fields["pixel_y"] - - P.show(usr) - to_chat(usr, P.desc) - qdel(P) //so 10 thousand picture items are not left in memory should an AI take them and then view them all - -/obj/item/camera/siliconcam/proc/viewpictures(user) - if(iscyborg(user)) // Cyborg - var/mob/living/silicon/robot/C = src.loc - var/obj/item/camera/siliconcam/Cinfo - if(C.connected_ai) - Cinfo = C.connected_ai.aicamera - viewpichelper(Cinfo) - else - Cinfo = C.aicamera - viewpichelper(Cinfo) - else // AI - var/Ainfo = src - viewpichelper(Ainfo) - -/obj/item/camera/afterattack(atom/target, mob/user, flag) - if(!on || !pictures_left || !isturf(target.loc)) - return - if (disk) - if(ismob(target)) - if (disk.record) - QDEL_NULL(disk.record) - - disk.record = new - var/mob/M = target - disk.record.caller_name = M.name - disk.record.set_caller_image(M) - else - return - else - captureimage(target, user, flag) - pictures_left-- - to_chat(user, "[pictures_left] photos left.") - - playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3) - - icon_state = "camera_off" - on = FALSE - addtimer(CALLBACK(src, .proc/cooldown), 64) - -/obj/item/camera/proc/cooldown() - set waitfor = FALSE - icon_state = "camera" - on = TRUE - -/obj/item/camera/siliconcam/proc/toggle_camera_mode() - if(in_camera_mode) - camera_mode_off() - else - camera_mode_on() - -/obj/item/camera/siliconcam/proc/camera_mode_off() - src.in_camera_mode = 0 - to_chat(usr, "Camera Mode deactivated") - -/obj/item/camera/siliconcam/proc/camera_mode_on() - src.in_camera_mode = 1 - to_chat(usr, "Camera Mode activated") - -/obj/item/camera/siliconcam/robot_camera/proc/borgprint() - var/list/nametemp = list() - var/find - var/datum/picture/selection - var/mob/living/silicon/robot/C = src.loc - var/obj/item/camera/siliconcam/targetcam = null - if(C.toner < 20) - to_chat(usr, "Insufficent toner to print image.") - return - if(C.connected_ai) - targetcam = C.connected_ai.aicamera - else - targetcam = C.aicamera - if(targetcam.aipictures.len == 0) - to_chat(usr, "No images saved") - return - for(var/datum/picture/t in targetcam.aipictures) - nametemp += t.fields["name"] - find = input("Select image (numbered in order taken)") in nametemp - for(var/datum/picture/q in targetcam.aipictures) - if(q.fields["name"] == find) - selection = q - break - var/obj/item/photo/p = new /obj/item/photo(C.loc) - p.photocreate(selection.fields["icon"], selection.fields["img"], selection.fields["desc"], selection.fields["blueprints"]) - p.pixel_x = rand(-10, 10) - p.pixel_y = rand(-10, 10) - C.toner -= 20 //Cyborgs are very ineffeicient at printing an image - visible_message("[C.name] spits out a photograph from a narrow slot on its chassis.") - to_chat(usr, "You print a photograph.") - -// Picture frames - -/obj/item/wallframe/picture - name = "picture frame" - desc = "The perfect showcase for your favorite deathtrap memories." - icon = 'icons/obj/decals.dmi' - materials = list() - flags_1 = 0 - icon_state = "frame-empty" - result_path = /obj/structure/sign/picture_frame - var/obj/item/photo/displayed - -/obj/item/wallframe/picture/attackby(obj/item/I, mob/user) - if(istype(I, /obj/item/photo)) - if(!displayed) - if(!user.transferItemToLoc(I, src)) - return - displayed = I - update_icon() - else - to_chat(user, "\The [src] already contains a photo.") - ..() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/wallframe/picture/attack_hand(mob/user) - if(user.get_inactive_held_item() != src) - ..() - return - if(contents.len) - var/obj/item/I = pick(contents) - user.put_in_hands(I) - to_chat(user, "You carefully remove the photo from \the [src].") - displayed = null - update_icon() - return ..() - -/obj/item/wallframe/picture/attack_self(mob/user) - user.examinate(src) - -/obj/item/wallframe/picture/examine(mob/user) - if(user.is_holding(src) && displayed) - displayed.show(user) - else - ..() - -/obj/item/wallframe/picture/update_icon() - cut_overlays() - if(displayed) - add_overlay(getFlatIcon(displayed)) - -/obj/item/wallframe/picture/after_attach(obj/O) - ..() - var/obj/structure/sign/picture_frame/PF = O - PF.copy_overlays(src) - if(displayed) - PF.framed = displayed - if(contents.len) - var/obj/item/I = pick(contents) - I.forceMove(PF) - - -/obj/structure/sign/picture_frame - name = "picture frame" - desc = "Every time you look it makes you laugh." - icon = 'icons/obj/decals.dmi' - icon_state = "frame-empty" - var/obj/item/photo/framed - -/obj/structure/sign/picture_frame/New(loc, dir, building) - ..() - if(dir) - setDir(dir) - if(building) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30) - pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 - -/obj/structure/sign/picture_frame/examine(mob/user) - if(in_range(src, user) && framed) - framed.show(user) - else - ..() - -/obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/screwdriver) || istype(I, /obj/item/wrench)) - to_chat(user, "You start unsecuring [name]...") - if(I.use_tool(src, user, 30, volume=50)) - playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) - to_chat(user, "You unsecure [name].") - deconstruct() - return - - else if(istype(I, /obj/item/photo)) - if(!framed) - var/obj/item/photo/P = I - if(!user.transferItemToLoc(P, src)) - return - framed = P - update_icon() - else - to_chat(user, "\The [src] already contains a photo.") - - ..() - -/obj/structure/sign/picture_frame/attack_hand(mob/user) - . = ..() - if(.) - return - if(framed) - framed.show(user) - -/obj/structure/sign/picture_frame/update_icon() - cut_overlays() - if(framed) - add_overlay(getFlatIcon(framed)) - -/obj/structure/sign/picture_frame/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - var/obj/item/wallframe/picture/F = new /obj/item/wallframe/picture(loc) - if(framed) - F.displayed = framed - framed = null - if(contents.len) - var/obj/item/I = pick(contents) - I.forceMove(F) - F.update_icon() - qdel(src) diff --git a/code/modules/photography/_pictures.dm b/code/modules/photography/_pictures.dm new file mode 100644 index 0000000000..24892cf541 --- /dev/null +++ b/code/modules/photography/_pictures.dm @@ -0,0 +1,163 @@ +/datum/picture + var/picture_name = "picture" + var/picture_desc = "This is a picture." + var/caption + var/icon/picture_image + var/icon/picture_icon + var/psize_x = 0 + var/psize_y = 0 + var/has_blueprints = FALSE + var/logpath //If the picture has been logged this is the path. + var/id //this var is NOT protected because the worst you can do with this that you couldn't do otherwise is overwrite photos, and photos aren't going to be used as attack logs/investigations anytime soon. + +/datum/picture/New(name = "picture", desc = "This is a picture. A bugged one. Report it to coderbus!", image, icon, size_x = 96, size_y = 96, bp = FALSE, caption_, autogenerate_icon = FALSE) + picture_name = name + picture_desc = desc + picture_image = image + picture_icon = icon + psize_x = size_x + psize_y = size_y + has_blueprints = bp + caption = caption_ + if(autogenerate_icon && !picture_icon && picture_image) + regenerate_small_icon() + +/datum/picture/proc/get_small_icon() + if(!picture_icon) + regenerate_small_icon() + return picture_icon + +/datum/picture/proc/regenerate_small_icon() + if(!picture_image) + return + var/icon/small_img = icon(picture_image) + var/icon/ic = icon('icons/obj/items_and_weapons.dmi', "photo") + small_img.Scale(8, 8) + ic.Blend(small_img,ICON_OVERLAY, 13, 13) + picture_icon = ic + +/datum/picture/serialize_list(list/options) + . = list() + .["id"] = id + .["desc"] = picture_desc + .["name"] = picture_name + .["caption"] = caption + .["pixel_size_x"] = psize_x + .["pixel_size_y"] = psize_y + .["blueprints"] = has_blueprints + .["logpath"] = logpath + +/datum/picture/deserialize_list(list/input, list/options) + if(!input["logpath"] || !fexists(input["logpath"]) || !input["id"] || !input["pixel_size_x"] || !input["pixel_size_y"]) + return + picture_image = icon(file(input["logpath"])) + logpath = input["logpath"] + id = input["id"] + psize_x = input["pixel_size_x"] + psize_y = input["pixel_size_y"] + if(input["blueprints"]) + has_blueprints = input["blueprints"] + if(input["caption"]) + caption = input["caption"] + if(input["desc"]) + picture_desc = input["desc"] + if(input["name"]) + picture_name = input["name"] + return src + +/proc/load_photo_from_disk(id, location) + var/datum/picture/P = load_picture_from_disk(id) + if(istype(P)) + var/obj/item/photo/p = new(location, P) + return p + +/proc/load_picture_from_disk(id) + var/pathstring = log_path_from_picture_ID(id) + if(!pathstring) + return + var/path = file(pathstring) + if(!fexists(path)) + return + var/dir_index = findlasttext(pathstring, "/") + var/dir = copytext(pathstring, 1, dir_index) + var/json_path = file("[dir]/metadata.json") + if(!fexists(json_path)) + return + var/list/json = json_decode(file2text(json_path)) + if(!json[id]) + return + var/datum/picture/P = new + P.deserialize_json(json[id]) + return P + +/proc/log_path_from_picture_ID(id) + if(!istext(id)) + return + . = "data/picture_logs/" + var/list/data = splittext(id, "_") + if(data.len < 3) + return null + var/mode = data[1] + switch(mode) + if("L") + if(data.len < 5) + return null + var/timestamp = data[2] + var/year = copytext(timestamp, 1, 5) + var/month = copytext(timestamp, 5, 7) + var/day = copytext(timestamp, 7, 9) + var/round = data[4] + . += "[year]/[month]/[day]/round-[round]" + if("O") + var/list/path = data.Copy(2, data.len) + . += path.Join("") + else + return null + var/n = data[data.len] + . += "/[n].png" + +//BE VERY CAREFUL WITH THIS PROC, TO AVOID DUPLICATION. +/datum/picture/proc/log_to_file() + if(!picture_image) + return + if(!CONFIG_GET(flag/log_pictures)) + return + if(logpath) + return //we're already logged + var/number = GLOB.picture_logging_id++ + var/finalpath = "[GLOB.picture_log_directory]/[number].png" + fcopy(icon(picture_image, dir = SOUTH, frame = 1), finalpath) + logpath = finalpath + id = "[GLOB.picture_logging_prefix][number]" + var/jsonpath = "[GLOB.picture_log_directory]/metadata.json" + jsonpath = file(jsonpath) + var/list/json + if(fexists(jsonpath)) + json = json_decode(file2text(jsonpath)) + fdel(jsonpath) + else + json = list() + json[id] = serialize_json() + WRITE_FILE(jsonpath, json_encode(json)) + +/datum/picture/proc/Copy(greyscale = FALSE, cropx = 0, cropy = 0) + var/datum/picture/P = new + P.picture_name = picture_name + P.picture_desc = picture_desc + if(picture_image) + P.picture_image = icon(picture_image) //Copy, not reference. + if(picture_icon) + P.picture_icon = icon(picture_icon) + P.psize_x = psize_x - cropx * 2 + P.psize_y = psize_y - cropy * 2 + P.has_blueprints = has_blueprints + if(greyscale) + if(picture_image) + P.picture_image.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) + if(picture_icon) + P.picture_icon.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) + if(cropx || cropy) + if(picture_image) + P.picture_image.Crop(cropx, cropy, psize_x - cropx, psize_y - cropy) + P.regenerate_small_icon() + return P diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm new file mode 100644 index 0000000000..db4f6fd863 --- /dev/null +++ b/code/modules/photography/camera/camera.dm @@ -0,0 +1,221 @@ + +#define CAMERA_PICTURE_SIZE_HARD_LIMIT 21 + +/obj/item/camera + name = "camera" + icon = 'icons/obj/items_and_weapons.dmi' + desc = "A polaroid camera." + icon_state = "camera" + item_state = "electropack" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + materials = list(MAT_METAL = 50, MAT_GLASS = 150) + var/state_on = "camera" + var/state_off = "camera_off" + var/pictures_max = 10 + var/pictures_left = 10 + var/on = TRUE + var/cooldown = 64 + var/blending = FALSE //lets not take pictures while the previous is still processing! + var/see_ghosts = CAMERA_NO_GHOSTS //for the spoop of it + var/obj/item/disk/holodisk/disk + var/sound/custom_sound + var/silent = FALSE + var/picture_size_x = 1 + var/picture_size_y = 1 + var/picture_size_x_min = 0 + var/picture_size_y_min = 0 + var/picture_size_x_max = 3 + var/picture_size_y_max = 3 + +/obj/item/camera/CheckParts(list/parts_list) + ..() + var/obj/item/camera/C = locate(/obj/item/camera) in contents + if(C) + pictures_max = C.pictures_max + pictures_left = C.pictures_left + visible_message("[C] has been imbued with godlike power!") + qdel(C) + +/obj/item/camera/attack_self(mob/user) + if(!disk) + return + to_chat(user, "You eject [disk] out the back of [src].") + user.put_in_hands(disk) + disk = null + +/obj/item/camera/AltClick(mob/user) + var/desired_x = input(user, "How high do you want the camera to shoot, between [picture_size_x_min] and [picture_size_x_max]?", "Zoom", picture_size_x) as num + var/desired_y = input(user, "How wide do you want the camera to shoot, between [picture_size_y_min] and [picture_size_y_max]?", "Zoom", picture_size_y) as num + desired_x = min(CLAMP(desired_x, picture_size_x_min, picture_size_x_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) + desired_y = min(CLAMP(desired_y, picture_size_y_min, picture_size_y_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) + if(user.canUseTopic(src)) + picture_size_x = desired_x + picture_size_y = desired_y + +/obj/item/camera/attack(mob/living/carbon/human/M, mob/user) + return + +/obj/item/camera/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/camera_film)) + if(pictures_left) + to_chat(user, "[src] still has some film in it!") + return + if(!user.temporarilyRemoveItemFromInventory(I)) + return + to_chat(user, "You insert [I] into [src].") + qdel(I) + pictures_left = pictures_max + return + if(istype(I, /obj/item/disk/holodisk)) + if (!disk) + if(!user.transferItemToLoc(I, src)) + to_chat(user, "[I] is stuck to your hand!") + return TRUE + to_chat(user, "You slide [I] into the back of [src].") + disk = I + else + to_chat(user, "There's already a disk inside [src].") + return TRUE //no afterattack + ..() + +/obj/item/camera/examine(mob/user) + ..() + to_chat(user, "It has [pictures_left] photos left.") + +/obj/item/camera/proc/can_target(atom/target, mob/user, prox_flag) + if(!on || blending || !pictures_left || (!isturf(target) && !isturf(target.loc)) || !((isAI(user) && GLOB.cameranet.checkTurfVis(get_turf(target))) || ((user.client && (get_turf(target) in get_hear(user.client.view, user)) || (get_turf(target) in get_hear(world.view, user)))))) + return FALSE + return TRUE + +/obj/item/camera/afterattack(atom/target, mob/user, flag) + if (disk) + if(ismob(target)) + if (disk.record) + QDEL_NULL(disk.record) + + disk.record = new + var/mob/M = target + disk.record.caller_name = M.name + disk.record.set_caller_image(M) + else + to_chat(user, "Invalid holodisk target.") + return + + if(!can_target(target, user, flag)) + return + + on = FALSE + addtimer(CALLBACK(src, .proc/cooldown), cooldown) + icon_state = state_off + + INVOKE_ASYNC(src, .proc/captureimage, target, user, flag, picture_size_x, picture_size_y) + + +/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, flag, size_x = 1, size_y = 1) + blending = TRUE + 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() + var/ai_user = isAI(user) + var/list/seen + var/list/viewlist = (user && user.client)? getviewsize(user.client.view) : getviewsize(world.view) + var/viewr = max(viewlist[1], viewlist[2]) + max(size_x, size_y) + var/viewc = user.client? user.client.eye : target + seen = get_hear(viewr, viewc) + var/list/turfs = list() + var/list/mobs = list() + var/blueprints = FALSE + var/clone_area = SSmapping.RequestBlockReservation(size_x * 2 + 1, size_y * 2 + 1) + for(var/turf/T in block(locate(target_turf.x - size_x, target_turf.y - size_y, target_turf.z), locate(target_turf.x + size_x, target_turf.y + size_y, target_turf.z))) + if((ai_user && GLOB.cameranet.checkTurfVis(T)) || T in seen) + turfs += T + for(var/mob/M in T) + mobs += M + if(locate(/obj/item/areaeditor/blueprints) in T) + blueprints = TRUE + CHECK_TICK + for(var/i in mobs) + desc += camera_get_mobdesc(i) + + var/psize_x = (size_x * 2 + 1) * world.icon_size + var/psize_y = (size_y * 2 + 1) * world.icon_size + var/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/temp = icon('icons/effects/96x96.dmi',"") + temp.Blend("#000", ICON_OVERLAY) + temp.Scale(psize_x, psize_y) + temp.Blend(get_icon, ICON_OVERLAY) + + var/datum/picture/P = new("picture", desc.Join(""), temp, null, psize_x, psize_y, blueprints) + after_picture(user, P, flag) + blending = FALSE + +/obj/item/camera/proc/camera_get_mobdesc(mob/M) + var/list/mob_details = list() + if(M.invisibility) + if(see_ghosts && isobserver(M)) + mob_details += "You can also see a g-g-g-g-ghooooost! " + else + return mob_details + var/list/holding = list() + if(isliving(M)) + var/mob/living/L = M + var/len = length(L.held_items) + if(len) + for(var/obj/item/I in L.held_items) + if(!holding.len) + holding += "They are holding \a [I]" + else if(L.held_items.Find(I) == len) + holding += ", and \a [I]." + else + holding += ", \a [I]" + holding += "." + holding = holding.Join("") + mob_details += "You can also see [L] on the photo[L.health < (L.maxHealth * 0.75) ? ", looking a bit hurt":""][holding ? ". [holding]":"."]." + return mob_details.Join("") + +/obj/item/camera/proc/after_picture(mob/user, datum/picture/picture, proximity_flag) + printpicture(user, picture) + +/obj/item/camera/proc/printpicture(mob/user, datum/picture/picture) //Normal camera proc for creating photos + var/obj/item/photo/p = new(get_turf(src), picture) + if(in_range(src, user)) //needed because of TK + user.put_in_hands(p) + pictures_left-- + to_chat(user, "[pictures_left] photos left.") + var/customize = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No") + if(customize == "Yes") + var/name1 = input(user, "Set a name for this photo, or leave blank. 32 characters max.", "Name") as text|null + var/desc1 = input(user, "Set a description to add to photo, or leave blank. 128 characters max.", "Caption") as text|null + var/caption = input(user, "Set a caption for this photo, or leave blank. 256 characters max.", "Caption") as text|null + if(name1) + name1 = copytext(name1, 1, 33) + picture.picture_name = name1 + if(desc1) + desc1 = copytext(desc1, 1, 129) + picture.picture_desc = "[desc1] - [picture.picture_desc]" + if(caption) + caption = copytext(caption, 1, 257) + picture.caption = caption + p.set_picture(picture) + if(CONFIG_GET(flag/picture_logging_camera)) + picture.log_to_file() diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm new file mode 100644 index 0000000000..7b9fe94f44 --- /dev/null +++ b/code/modules/photography/camera/camera_image_capturing.dm @@ -0,0 +1,88 @@ +/obj/effect/appearance_clone + +/obj/effect/appearance_clone/New(loc, atom/A) //Intentionally not Initialize(). + if(istype(A)) + appearance = A.appearance + dir = A.dir + if(ismovableatom(A)) + var/atom/movable/AM = A + step_x = AM.step_x + step_y = AM.step_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) + var/list/atoms = list() + var/skip_normal = FALSE + var/wipe_atoms = FALSE + + if(istype(clone_area) && total_x == clone_area.width && total_y == clone_area.height && size_x >= 0 && size_y > 0) + var/cloned_center_x = round(clone_area.bottom_left_coords[1] + ((total_x - 1) / 2)) + var/cloned_center_y = round(clone_area.bottom_left_coords[2] + ((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, clone_area.bottom_left_coords[3]) + 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) + for(var/i in T.contents) + var/atom/A = i + if(!A.invisibility || (see_ghosts && isobserver(A))) + atoms += new /obj/effect/appearance_clone(newT, A) + skip_normal = TRUE + wipe_atoms = TRUE + center = locate(cloned_center_x, cloned_center_y, clone_area.bottom_left_coords[3]) + + if(!skip_normal) + for(var/i in turfs) + var/turf/T = i + atoms += T + for(var/atom/movable/A in T) + if(A.invisibility) + if(!(see_ghosts && isobserver(A))) + continue + atoms += A + CHECK_TICK + + var/icon/res = icon('icons/effects/96x96.dmi', "") + res.Scale(psize_x, psize_y) + + var/list/sorted = list() + var/j + for(var/i in 1 to atoms.len) + var/atom/c = atoms[i] + for(j = sorted.len, j > 0, --j) + var/atom/c2 = sorted[j] + if(c2.layer <= c.layer) + break + sorted.Insert(j+1, c) + CHECK_TICK + + var/xcomp = FLOOR(psize_x / 2, 1) - 15 + var/ycomp = FLOOR(psize_y / 2, 1) - 15 + + + for(var/atom/A in sorted) + var/xo = (A.x - center.x) * world.icon_size + A.pixel_x + xcomp + var/yo = (A.y - center.y) * world.icon_size + A.pixel_y + ycomp + if(ismovableatom(A)) + var/atom/movable/AM = A + xo += AM.step_x + yo += AM.step_y + var/icon/img = getFlatIcon(A) + res.Blend(img, blendMode2iconMode(A.blend_mode), xo, yo) + CHECK_TICK + + if(!silent) + if(istype(custom_sound)) //This is where the camera actually finishes its exposure. + playsound(loc, custom_sound, 75, 1, -3) + else + playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3) + + if(wipe_atoms) + QDEL_LIST(atoms) + + return res diff --git a/code/modules/photography/camera/film.dm b/code/modules/photography/camera/film.dm new file mode 100644 index 0000000000..5d69824bad --- /dev/null +++ b/code/modules/photography/camera/film.dm @@ -0,0 +1,14 @@ +/* + * Film + */ +/obj/item/camera_film + name = "film cartridge" + icon = 'icons/obj/items_and_weapons.dmi' + desc = "A camera film cartridge. Insert it into a camera to reload it." + icon_state = "film" + item_state = "electropack" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + materials = list(MAT_METAL = 10, MAT_GLASS = 10) diff --git a/code/modules/photography/camera/other.dm b/code/modules/photography/camera/other.dm new file mode 100644 index 0000000000..ce2572db36 --- /dev/null +++ b/code/modules/photography/camera/other.dm @@ -0,0 +1,14 @@ +/obj/item/camera/spooky + name = "camera obscura" + desc = "A polaroid camera, some say it can see ghosts!" + see_ghosts = CAMERA_SEE_GHOSTS_BASIC + +/obj/item/camera/spooky/badmin + desc = "A polaroid camera, some say it can see ghosts! It seems to have an extra magnifier on the end." + see_ghosts = CAMERA_SEE_GHOSTS_ORBIT + +/obj/item/camera/detective + name = "Detective's camera" + desc = "A polaroid camera with extra capacity for crime investigations." + pictures_max = 30 + pictures_left = 30 diff --git a/code/modules/photography/camera/picture_frame.dm b/code/modules/photography/camera/picture_frame.dm new file mode 100644 index 0000000000..c31d888bd8 --- /dev/null +++ b/code/modules/photography/camera/picture_frame.dm @@ -0,0 +1,145 @@ +// Picture frames + +/obj/item/wallframe/picture + name = "picture frame" + desc = "The perfect showcase for your favorite deathtrap memories." + icon = 'icons/obj/decals.dmi' + materials = list() + flags_1 = 0 + icon_state = "frame-empty" + result_path = /obj/structure/sign/picture_frame + var/obj/item/photo/displayed + +/obj/item/wallframe/picture/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/photo)) + if(!displayed) + if(!user.transferItemToLoc(I, src)) + return + displayed = I + update_icon() + else + to_chat(user, "\The [src] already contains a photo.") + ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/wallframe/picture/attack_hand(mob/user) + if(user.get_inactive_held_item() != src) + ..() + return + if(contents.len) + var/obj/item/I = pick(contents) + user.put_in_hands(I) + to_chat(user, "You carefully remove the photo from \the [src].") + displayed = null + update_icon() + return ..() + +/obj/item/wallframe/picture/attack_self(mob/user) + user.examinate(src) + +/obj/item/wallframe/picture/examine(mob/user) + if(user.is_holding(src) && displayed) + displayed.show(user) + else + ..() + +/obj/item/wallframe/picture/update_icon() + cut_overlays() + if(displayed) + add_overlay(getFlatIcon(displayed)) + +/obj/item/wallframe/picture/after_attach(obj/O) + ..() + var/obj/structure/sign/picture_frame/PF = O + PF.copy_overlays(src) + if(displayed) + PF.framed = displayed + if(contents.len) + var/obj/item/I = pick(contents) + I.forceMove(PF) + +/obj/structure/sign/picture_frame + name = "picture frame" + desc = "Every time you look it makes you laugh." + icon = 'icons/obj/decals.dmi' + icon_state = "frame-empty" + var/obj/item/photo/framed + var/persistence_id + +/obj/structure/sign/picture_frame/Initialize(mapload, dir, building) + . = ..() + LAZYADD(SSpersistence.photo_frames, src) + if(dir) + setDir(dir) + if(building) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30) + pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 + +/obj/structure/sign/picture_frame/Destroy() + LAZYREMOVE(SSpersistence.photo_frames, src) + return ..() + +/obj/structure/sign/picture_frame/proc/get_photo_id() + if(istype(framed) && istype(framed.picture)) + return framed.picture.id + +/obj/structure/sign/picture_frame/proc/load_from_id(id) + var/obj/item/photo/P = load_photo_from_disk(id) + if(istype(P)) + if(istype(framed)) + framed.forceMove(drop_location()) + else + qdel(framed) + framed = P + update_icon() + +/obj/structure/sign/picture_frame/examine(mob/user) + if(in_range(src, user) && framed) + framed.show(user) + else + ..() + +/obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/screwdriver) || istype(I, /obj/item/wrench)) + to_chat(user, "You start unsecuring [name]...") + if(I.use_tool(src, user, 30, volume=50)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You unsecure [name].") + deconstruct() + return + + else if(istype(I, /obj/item/photo)) + if(!framed) + var/obj/item/photo/P = I + if(!user.transferItemToLoc(P, src)) + return + framed = P + update_icon() + else + to_chat(user, "\The [src] already contains a photo.") + + ..() + +/obj/structure/sign/picture_frame/attack_hand(mob/user) + . = ..() + if(.) + return + if(framed) + framed.show(user) + +/obj/structure/sign/picture_frame/update_icon() + cut_overlays() + if(framed) + add_overlay(getFlatIcon(framed)) + +/obj/structure/sign/picture_frame/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + var/obj/item/wallframe/picture/F = new /obj/item/wallframe/picture(loc) + if(framed) + F.displayed = framed + framed = null + if(contents.len) + var/obj/item/I = pick(contents) + I.forceMove(F) + F.update_icon() + qdel(src) diff --git a/code/modules/photography/camera/silicon_camera.dm b/code/modules/photography/camera/silicon_camera.dm new file mode 100644 index 0000000000..ad8542a5c1 --- /dev/null +++ b/code/modules/photography/camera/silicon_camera.dm @@ -0,0 +1,98 @@ + +/obj/item/camera/siliconcam + name = "silicon photo camera" + var/in_camera_mode = FALSE + var/list/datum/picture/stored = list() + +/obj/item/camera/siliconcam/ai_camera + name = "AI photo camera" + +/obj/item/camera/siliconcam/proc/toggle_camera_mode(mob/user) + if(in_camera_mode) + camera_mode_off(user) + else + camera_mode_on(user) + +/obj/item/camera/siliconcam/proc/camera_mode_off(mob/user) + in_camera_mode = FALSE + to_chat(user, "Camera Mode deactivated") + +/obj/item/camera/siliconcam/proc/camera_mode_on(mob/user) + in_camera_mode = TRUE + to_chat(user, "Camera Mode activated") + +/obj/item/camera/siliconcam/proc/selectpicture(mob/user) + var/list/nametemp = list() + var/find + if(!stored.len) + to_chat(usr, "No images saved") + return + var/list/temp = list() + for(var/i in stored) + var/datum/picture/p = i + nametemp += p.picture_name + temp[p.picture_name] = p + find = input(user, "Select image") in nametemp|null + if(!find) + return + return temp[find] + +/obj/item/camera/siliconcam/proc/viewpictures(mob/user) + var/datum/picture/selection = selectpicture(user) + if(istype(selection)) + show_picture(user, selection) + +/obj/item/camera/siliconcam/ai_camera/after_picture(mob/user, datum/picture/picture, proximity_flag) + var/number = stored.len + picture.picture_name = "Image [number] (taken by [loc.name])" + stored[picture] = TRUE + to_chat(usr, "Image recorded") + +/obj/item/camera/siliconcam/robot_camera + name = "Cyborg photo camera" + var/printcost = 2 + +/obj/item/camera/siliconcam/robot_camera/after_picture(mob/user, datum/picture/picture, proximity_flag) + var/mob/living/silicon/robot/C = loc + if(istype(C) && istype(C.connected_ai)) + var/number = C.connected_ai.aicamera.stored.len + picture.picture_name = "Image [number] (taken by [loc.name])" + C.connected_ai.aicamera.stored[picture] = TRUE + to_chat(usr, "Image recorded and saved to remote database") + else + var/number = stored.len + picture.picture_name = "Image [number] (taken by [loc.name])" + stored[picture] = TRUE + to_chat(usr, "Image recorded and saved to local storage. Upload will happen automatically if unit is lawsynced.") + +/obj/item/camera/siliconcam/robot_camera/selectpicture(mob/user) + var/mob/living/silicon/robot/R = loc + if(istype(R) && R.connected_ai) + R.picturesync() + return R.connected_ai.aicamera.selectpicture(user) + else + return ..() + +/obj/item/camera/siliconcam/robot_camera/verb/borgprinting() + set category ="Robot Commands" + set name = "Print Image" + set src in usr + if(usr.stat == DEAD) + return + borgprint(usr) + +/obj/item/camera/siliconcam/robot_camera/proc/borgprint(mob/user) + var/mob/living/silicon/robot/C = loc + if(!istype(C) || C.toner < 20) + to_chat(user, "Insufficent toner to print image.") + return + var/datum/picture/selection = selectpicture(user) + if(!istype(selection)) + to_chat(user, "Invalid Image.") + return + var/obj/item/photo/p = new /obj/item/photo(C.loc, selection) + p.pixel_x = rand(-10, 10) + p.pixel_y = rand(-10, 10) + C.toner -= printcost //All fun allowed. + visible_message("[C.name] spits out a photograph from a narrow slot on its chassis.") + to_chat(usr, "You print a photograph.") diff --git a/code/modules/photography/photo_album.dm b/code/modules/photography/photo_album.dm new file mode 100644 index 0000000000..cf4fd3d80b --- /dev/null +++ b/code/modules/photography/photo_album.dm @@ -0,0 +1,43 @@ +/* + * Photo album + */ +/obj/item/storage/photo_album + name = "photo album" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "album" + item_state = "briefcase" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + resistance_flags = FLAMMABLE + var/persistence_id + +/obj/item/storage/photo_album/Initialize() + . = ..() + GET_COMPONENT(STR, /datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/photo)) + LAZYADD(SSpersistence.photo_albums, src) + +/obj/item/storage/photo_album/Destroy() + LAZYREMOVE(SSpersistence.photo_albums, src) + return ..() + +/obj/item/storage/photo_album/proc/get_picture_id_list() + var/list/L = list() + for(var/i in contents) + if(istype(i, /obj/item/photo)) + L += i + if(!L.len) + return + . = list() + for(var/i in L) + var/obj/item/photo/P = i + if(!istype(P.picture)) + continue + . |= P.picture.id + +/obj/item/storage/photo_album/proc/populate_from_id_list(list/ids) + for(var/i in ids) + var/obj/item/photo/P = load_photo_from_disk(i) + if(istype(P)) + if(!SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, P, null, TRUE, TRUE)) + qdel(P) diff --git a/code/modules/photography/photo_item.dm b/code/modules/photography/photo_item.dm new file mode 100644 index 0000000000..d03fa08fbd --- /dev/null +++ b/code/modules/photography/photo_item.dm @@ -0,0 +1,86 @@ +/* + * Photo + */ +/obj/item/photo + name = "photo" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "photo" + item_state = "paper" + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + max_integrity = 50 + grind_results = list("iodine" = 4) + var/datum/picture/picture + var/scribble //Scribble on the back. + var/sillynewscastervar //Photo objects with this set to 1 will not be ejected by a newscaster. Only gets set to 1 if a silicon puts one of their images into a newscaster + +/obj/item/photo/Initialize(mapload, datum/picture/P, datum_name = TRUE, datum_desc = TRUE) + set_picture(P, datum_name, datum_desc) + return ..() + +/obj/item/photo/proc/set_picture(datum/picture/P, setname, setdesc) + if(!istype(P)) + return + picture = P + update_icon() + if(P.caption) + scribble = P.caption + if(setname && P.picture_name) + name = P.picture_name + if(setdesc && P.picture_desc) + desc = P.picture_desc + +/obj/item/photo/update_icon() + var/icon/I = picture.get_small_icon() + if(I) + icon = I + +/obj/item/photo/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is taking one last look at \the [src]! It looks like [user.p_theyre()] giving in to death!")//when you wanna look at photo of waifu one last time before you die... + if (user.gender == MALE) + playsound(user, 'sound/voice/human/manlaugh1.ogg', 50, 1)//EVERY TIME I DO IT MAKES ME LAUGH + else if (user.gender == FEMALE) + playsound(user, 'sound/voice/human/womanlaugh.ogg', 50, 1) + return OXYLOSS + +/obj/item/photo/attack_self(mob/user) + user.examinate(src) + +/obj/item/photo/attackby(obj/item/P, mob/user, params) + if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on [src]!") + return + var/txt = sanitize(input(user, "What would you like to write on the back?", "Photo Writing", null) as text) + txt = copytext(txt, 1, 128) + if(user.canUseTopic(src, BE_CLOSE)) + scribble = txt + ..() + +/obj/item/photo/examine(mob/user) + ..() + + if(in_range(src, user)) + show(user) + else + to_chat(user, "You need to get closer to get a good look at this photo!") + +/obj/item/photo/proc/show(mob/user) + user << browse_rsc(picture.picture_image, "tmp_photo.png") + user << browse("[name]" \ + + "" \ + + "" \ + + "[scribble ? "
Written on the back:
[scribble]" : ""]"\ + + "", "window=photo_showing;size=[picture.psize_x + scribble? 128:0]x[picture.psize_y + scribble? 128:0]") + onclose(user, "[name]") + +/obj/item/photo/verb/rename() + set name = "Rename photo" + set category = "Object" + set src in usr + + var/n_name = copytext(sanitize(input(usr, "What would you like to label the photo?", "Photo Labelling", null) as text), 1, MAX_NAME_LEN) + //loc.loc check is for making possible renaming photos in clipboards + if((loc == usr || loc.loc && loc.loc == usr) && usr.stat == CONSCIOUS && usr.canmove && !usr.restrained()) + name = "photo[(n_name ? text("- '[n_name]'") : null)]" + add_fingerprint(usr) diff --git a/code/modules/photography/photos/album.dm b/code/modules/photography/photos/album.dm new file mode 100644 index 0000000000..002fba674b --- /dev/null +++ b/code/modules/photography/photos/album.dm @@ -0,0 +1,55 @@ +/* + * Photo album + */ +/obj/item/storage/photo_album + name = "photo album" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "album" + item_state = "briefcase" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + resistance_flags = FLAMMABLE + var/persistence_id + +/obj/item/storage/photo_album/Initialize() + . = ..() + GET_COMPONENT(STR, /datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/photo)) + LAZYADD(SSpersistence.photo_albums, src) + +/obj/item/storage/photo_album/Destroy() + LAZYREMOVE(SSpersistence.photo_albums, src) + return ..() + +/obj/item/storage/photo_album/proc/get_picture_id_list() + var/list/L = list() + for(var/i in contents) + if(istype(i, /obj/item/photo)) + L += i + if(!L.len) + return + . = list() + for(var/i in L) + var/obj/item/photo/P = i + if(!istype(P.picture)) + continue + . |= P.picture.id + +/obj/item/storage/photo_album/proc/populate_from_id_list(list/ids) + for(var/i in ids) + var/obj/item/photo/P = load_photo_from_disk(i) + if(istype(P)) + if(!SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, P, null, TRUE, TRUE)) + qdel(P) + +#define ALBUM_DEFINE(id) /obj/item/storage/photo_album/##id/persistence_id = #id + +ALBUM_DEFINE(HoS) +ALBUM_DEFINE(RD) +ALBUM_DEFINE(HoP) +ALBUM_DEFINE(Captain) +ALBUM_DEFINE(CMO) +ALBUM_DEFINE(QM) +ALBUM_DEFINE(CE) + +#undef ALBUM_DEFINE diff --git a/code/modules/photography/photos/frame.dm b/code/modules/photography/photos/frame.dm new file mode 100644 index 0000000000..c3b9530e07 --- /dev/null +++ b/code/modules/photography/photos/frame.dm @@ -0,0 +1,157 @@ +// Picture frames + +/obj/item/wallframe/picture + name = "picture frame" + desc = "The perfect showcase for your favorite deathtrap memories." + icon = 'icons/obj/decals.dmi' + materials = list() + flags_1 = 0 + icon_state = "frame-empty" + result_path = /obj/structure/sign/picture_frame + var/obj/item/photo/displayed + +/obj/item/wallframe/picture/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/photo)) + if(!displayed) + if(!user.transferItemToLoc(I, src)) + return + displayed = I + update_icon() + else + to_chat(user, "\The [src] already contains a photo.") + ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/wallframe/picture/attack_hand(mob/user) + if(user.get_inactive_held_item() != src) + ..() + return + if(contents.len) + var/obj/item/I = pick(contents) + user.put_in_hands(I) + to_chat(user, "You carefully remove the photo from \the [src].") + displayed = null + update_icon() + return ..() + +/obj/item/wallframe/picture/attack_self(mob/user) + user.examinate(src) + +/obj/item/wallframe/picture/examine(mob/user) + if(user.is_holding(src) && displayed) + displayed.show(user) + else + ..() + +/obj/item/wallframe/picture/update_icon() + cut_overlays() + if(displayed) + add_overlay(getFlatIcon(displayed)) + +/obj/item/wallframe/picture/after_attach(obj/O) + ..() + var/obj/structure/sign/picture_frame/PF = O + PF.copy_overlays(src) + if(displayed) + PF.framed = displayed + if(contents.len) + var/obj/item/I = pick(contents) + I.forceMove(PF) + +/obj/structure/sign/picture_frame + name = "picture frame" + desc = "Every time you look it makes you laugh." + icon = 'icons/obj/decals.dmi' + icon_state = "frame-empty" + var/obj/item/photo/framed + var/persistence_id + var/can_decon = TRUE + +#define FRAME_DEFINE(id) /obj/structure/sign/picture_frame/##id/persistence_id = #id + +//Put default persistent frame defines here! + +#undef FRAME_DEFINE + +/obj/structure/sign/picture_frame/Initialize(mapload, dir, building) + . = ..() + LAZYADD(SSpersistence.photo_frames, src) + if(dir) + setDir(dir) + if(building) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30) + pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 + +/obj/structure/sign/picture_frame/Destroy() + LAZYREMOVE(SSpersistence.photo_frames, src) + return ..() + +/obj/structure/sign/picture_frame/proc/get_photo_id() + if(istype(framed) && istype(framed.picture)) + return framed.picture.id + +/obj/structure/sign/picture_frame/proc/load_from_id(id) + var/obj/item/photo/P = load_photo_from_disk(id) + if(istype(P)) + if(istype(framed)) + framed.forceMove(drop_location()) + else + qdel(framed) + framed = P + update_icon() + +/obj/structure/sign/picture_frame/examine(mob/user) + if(in_range(src, user) && framed) + framed.show(user) + else + ..() + +/obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) + if(can_decon && (istype(I, /obj/item/screwdriver) || istype(I, /obj/item/wrench))) + to_chat(user, "You start unsecuring [name]...") + if(I.use_tool(src, user, 30, volume=50)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You unsecure [name].") + deconstruct() + + else if(istype(I, /obj/item/wirecutters) && framed) + framed.forceMove(drop_location()) + framed = null + user.visible_message("[user] cuts away [framed] from [src]!") + return + + else if(istype(I, /obj/item/photo)) + if(!framed) + var/obj/item/photo/P = I + if(!user.transferItemToLoc(P, src)) + return + framed = P + update_icon() + else + to_chat(user, "\The [src] already contains a photo.") + + ..() + +/obj/structure/sign/picture_frame/attack_hand(mob/user) + . = ..() + if(.) + return + if(framed) + framed.show(user) + +/obj/structure/sign/picture_frame/update_icon() + cut_overlays() + if(framed) + add_overlay(getFlatIcon(framed)) + +/obj/structure/sign/picture_frame/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + var/obj/item/wallframe/picture/F = new /obj/item/wallframe/picture(loc) + if(framed) + F.displayed = framed + framed = null + if(contents.len) + var/obj/item/I = pick(contents) + I.forceMove(F) + F.update_icon() + qdel(src) diff --git a/code/modules/photography/photos/photo.dm b/code/modules/photography/photos/photo.dm new file mode 100644 index 0000000000..b7a37febab --- /dev/null +++ b/code/modules/photography/photos/photo.dm @@ -0,0 +1,91 @@ +/* + * Photo + */ +/obj/item/photo + name = "photo" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "photo" + item_state = "paper" + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + max_integrity = 50 + grind_results = list("iodine" = 4) + var/datum/picture/picture + var/scribble //Scribble on the back. + var/sillynewscastervar //Photo objects with this set to 1 will not be ejected by a newscaster. Only gets set to 1 if a silicon puts one of their images into a newscaster + +/obj/item/photo/Initialize(mapload, datum/picture/P, datum_name = TRUE, datum_desc = TRUE) + set_picture(P, datum_name, datum_desc) + return ..() + +/obj/item/photo/proc/set_picture(datum/picture/P, setname, setdesc) + if(!istype(P)) + return + picture = P + update_icon() + if(P.caption) + scribble = P.caption + if(setname && P.picture_name) + name = P.picture_name + if(setdesc && P.picture_desc) + desc = P.picture_desc + +/obj/item/photo/update_icon() + if(!istype(picture) || !picture.picture_image) + return + var/icon/I = picture.get_small_icon() + if(I) + icon = I + +/obj/item/photo/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is taking one last look at \the [src]! It looks like [user.p_theyre()] giving in to death!")//when you wanna look at photo of waifu one last time before you die... + if (user.gender == MALE) + playsound(user, 'sound/voice/human/manlaugh1.ogg', 50, 1)//EVERY TIME I DO IT MAKES ME LAUGH + else if (user.gender == FEMALE) + playsound(user, 'sound/voice/human/womanlaugh.ogg', 50, 1) + return OXYLOSS + +/obj/item/photo/attack_self(mob/user) + user.examinate(src) + +/obj/item/photo/attackby(obj/item/P, mob/user, params) + if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on [src]!") + return + var/txt = sanitize(input(user, "What would you like to write on the back?", "Photo Writing", null) as text) + txt = copytext(txt, 1, 128) + if(user.canUseTopic(src, BE_CLOSE)) + scribble = txt + ..() + +/obj/item/photo/examine(mob/user) + ..() + + if(in_range(src, user)) + show(user) + else + to_chat(user, "You need to get closer to get a good look at this photo!") + +/obj/item/photo/proc/show(mob/user) + if(!istype(picture) || !picture.picture_image) + to_chat(user, "[src] seems to be blank...") + return + user << browse_rsc(picture.picture_image, "tmp_photo.png") + user << browse("[name]" \ + + "" \ + + "" \ + + "[scribble ? "
Written on the back:
[scribble]" : ""]"\ + + "", "window=photo_showing;size=480x608") + onclose(user, "[name]") + +/obj/item/photo/verb/rename() + set name = "Rename photo" + set category = "Object" + set src in usr + + var/n_name = copytext(sanitize(input(usr, "What would you like to label the photo?", "Photo Labelling", null) as text), 1, MAX_NAME_LEN) + //loc.loc check is for making possible renaming photos in clipboards + if((loc == usr || loc.loc && loc.loc == usr) && usr.stat == CONSCIOUS && usr.canmove && !usr.restrained()) + name = "photo[(n_name ? text("- '[n_name]'") : null)]" + add_fingerprint(usr) diff --git a/config/config.txt b/config/config.txt index fba903477c..411e8f979f 100644 --- a/config/config.txt +++ b/config/config.txt @@ -133,6 +133,12 @@ LOG_MANIFEST ## As an example of how this could be """useful""" look towards Poly (https://twitter.com/Poly_the_Parrot) # LOG_TWITTER +## Enable logging pictures +LOG_PICTURES + +##Log camera pictures - Must have picture logging enabled +PICTURE_LOGGING_CAMERA + ## period of time in seconds for players to be considered inactive # INACTIVITY_PERIOD 300 diff --git a/tgstation.dme b/tgstation.dme index 69c9fbb3de..3fea03bd48 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -116,6 +116,7 @@ #include "code\__HELPERS\names.dm" #include "code\__HELPERS\priority_announce.dm" #include "code\__HELPERS\pronouns.dm" +#include "code\__HELPERS\qdel.dm" #include "code\__HELPERS\radiation.dm" #include "code\__HELPERS\radio.dm" #include "code\__HELPERS\roundend.dm" @@ -2189,8 +2190,16 @@ #include "code\modules\paperwork\paperplane.dm" #include "code\modules\paperwork\pen.dm" #include "code\modules\paperwork\photocopier.dm" -#include "code\modules\paperwork\photography.dm" #include "code\modules\paperwork\stamps.dm" +#include "code\modules\photography\_pictures.dm" +#include "code\modules\photography\camera\camera.dm" +#include "code\modules\photography\camera\camera_image_capturing.dm" +#include "code\modules\photography\camera\film.dm" +#include "code\modules\photography\camera\other.dm" +#include "code\modules\photography\camera\silicon_camera.dm" +#include "code\modules\photography\photos\album.dm" +#include "code\modules\photography\photos\frame.dm" +#include "code\modules\photography\photos\photo.dm" #include "code\modules\power\apc.dm" #include "code\modules\power\cable.dm" #include "code\modules\power\cell.dm" From 22069cd841303bf290b0b6975f565241de66be7e Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Sat, 21 Jul 2018 13:23:02 -0700 Subject: [PATCH 2/3] Update album.dm --- code/modules/photography/photos/album.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/modules/photography/photos/album.dm b/code/modules/photography/photos/album.dm index 002fba674b..b4481eb008 100644 --- a/code/modules/photography/photos/album.dm +++ b/code/modules/photography/photos/album.dm @@ -15,6 +15,8 @@ . = ..() GET_COMPONENT(STR, /datum/component/storage) STR.can_hold = typecacheof(list(/obj/item/photo)) + STR.max_combined_w_class = 42 + STR.max_items = 21 LAZYADD(SSpersistence.photo_albums, src) /obj/item/storage/photo_album/Destroy() From 70bdea81ec4c110532176bd751a9d95039741989 Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Sun, 22 Jul 2018 16:46:52 -0700 Subject: [PATCH 3/3] Update world.dm --- code/game/world.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/game/world.dm b/code/game/world.dm index 2f3f9b4c7b..b5d0781dab 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -108,7 +108,6 @@ GLOBAL_PROTECT(security_mode) GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" - GLOB.picture_log_directory = GLOB.log_directory + "/pictures" #ifdef UNIT_TESTS GLOB.test_log = file("[GLOB.log_directory]/tests.log")