From 11891d6eb70ce6b00718bc1e25683e572baeb21f Mon Sep 17 00:00:00 2001 From: Drathek <76988376+Drulikar@users.noreply.github.com> Date: Thu, 12 Oct 2023 02:34:15 -0700 Subject: [PATCH] Photo Code Refactoring (#4636) # About the pull request This PR is a partial revival of #4584 in addition to just a general refactor of the code it was affecting. Now after every blend or getFlatIcon call there is a CHECK_TICK. Various loops use the `as anything` usage as possible to help with type checking. Additionally, the descriptions for mobs is a little more fluid, and now there is actually a description for a photo with no mobs in it. # Explain why it's good for the game This should hopefully mitigate issues where photos cause the server to stutter, but hard to know for sure without testing on live. Also the description was annoyingly rigid. # Testing Photographs and Procedure
Screenshots & Videos ![image](https://github.com/cmss13-devs/cmss13/assets/76988376/4d3d5dc4-bd1e-4a63-a188-b0a21076ee99) (There was an additional space in this photo's description that was since removed)
# Changelog :cl: Drathek refactor: Refactored camera code to be less blocking, use typechecks less often, and provide somewhat more fluid descriptions to photos. /:cl: --------- Co-authored-by: harryob --- code/modules/paperwork/photography.dm | 176 ++++++++++++++++---------- 1 file changed, 107 insertions(+), 69 deletions(-) diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index e004715f326a..dbd490792e8f 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -175,81 +175,110 @@ res.Scale(size*32, size*32) // Initialize the photograph to black. res.Blend("#000", ICON_OVERLAY) + CHECK_TICK - var/atoms[] = list() - for(var/turf/the_turf in turfs) - // Add outselves to the list of stuff to draw + var/pixel_size = world.icon_size + var/radius = (size - 1) * 0.5 + var/center_offset = radius * pixel_size + 1 + var/x_min = center.x - radius + var/x_max = center.x + radius + var/y_min = center.y - radius + var/y_max = center.y + radius + + var/list/atoms = list() + for(var/turf/the_turf as anything in turfs) + // Add ourselves to the list of stuff to draw atoms.Add(the_turf); + // As well as anything that isn't invisible. - for(var/atom/A in the_turf) - if(A.invisibility) continue - atoms.Add(A) + for(var/atom/cur_atom as anything in the_turf) + if(!cur_atom || cur_atom.invisibility) + continue + atoms.Add(cur_atom) // Sort the atoms into their layers var/list/sorted = sort_atoms_by_layer(atoms) - var/center_offset = (size-1)/2 * 32 + 1 - for(var/i; i <= sorted.len; i++) - var/atom/A = sorted[i] - if(A) - var/icon/IM = getFlatIcon(A)//build_composite_icon(A) - - // If what we got back is actually a picture, draw it. - if(istype(IM, /icon)) - // Check if we're looking at a mob that's lying down - if(istype(A, /mob/living)) - var/mob/living/L = A - if(!istype(L, /mob/living/carbon/xenomorph)) //xenos don't use icon rotatin for lying. - if(L.lying) - // If they are, apply that effect to their picture. - IM.BecomeLying() - // Calculate where we are relative to the center of the photo - var/xoff = (A.x - center.x) * 32 + center_offset - var/yoff = (A.y - center.y) * 32 + center_offset - if (istype(A,/atom/movable)) - xoff+=A:step_x - yoff+=A:step_y - res.Blend(IM, blendMode2iconMode(A.blend_mode), A.pixel_x + xoff, A.pixel_y + yoff) + for(var/atom/cur_atom as anything in sorted) + if(QDELETED(cur_atom)) + continue + + if(cur_atom.x < x_min || cur_atom.x > x_max || cur_atom.y < y_min || cur_atom.y > y_max) + // they managed to move out of frame with all this CHECK_TICK... + continue + + var/icon/cur_icon = getFlatIcon(cur_atom)//build_composite_icon(cur_atom) + + // If what we got back is actually a picture, draw it. + if(istype(cur_icon, /icon)) + // Check if we're looking at a mob that's lying down + if(istype(cur_atom, /mob/living)) + var/mob/living/cur_mob = cur_atom + if(!isxeno(cur_mob) && cur_mob.lying) //xenos don't use icon rotatin for lying. + cur_icon.BecomeLying() + + // Calculate where we are relative to the center of the photo + var/xoff = (cur_atom.x - center.x) * pixel_size + center_offset + var/yoff = (cur_atom.y - center.y) * pixel_size + center_offset + if(istype(cur_atom, /atom/movable)) + xoff += cur_atom:step_x + yoff += cur_atom:step_y + res.Blend(cur_icon, blendMode2iconMode(cur_atom.blend_mode), cur_atom.pixel_x + xoff, cur_atom.pixel_y + yoff) + + CHECK_TICK // Lastly, render any contained effects on top. for(var/turf/the_turf as anything in turfs) // Calculate where we are relative to the center of the photo - var/xoff = (the_turf.x - center.x) * 32 + center_offset - var/yoff = (the_turf.y - center.y) * 32 + center_offset - var/image/IM = getFlatIcon(the_turf.loc) - if(IM) - res.Blend(IM, blendMode2iconMode(the_turf.blend_mode),xoff,yoff) + var/xoff = (the_turf.x - center.x) * pixel_size + center_offset + var/yoff = (the_turf.y - center.y) * pixel_size + center_offset + var/image/cur_icon = getFlatIcon(the_turf.loc) + CHECK_TICK + + if(cur_icon) + res.Blend(cur_icon, blendMode2iconMode(the_turf.blend_mode), xoff, yoff) + CHECK_TICK return res +/obj/item/device/camera/proc/get_mob_descriptions(turf/the_turf, existing_descripion) + var/mob_detail = existing_descripion + for(var/mob/living/carbon/cur_carbon in the_turf) + if(cur_carbon.invisibility) + continue -/obj/item/device/camera/proc/get_mobs(turf/the_turf as turf) - var/mob_detail - for(var/mob/living/carbon/A in the_turf) - if(A.invisibility) continue var/holding = null - if(A.l_hand || A.r_hand) - if(A.l_hand) holding = "They are holding \a [A.l_hand]" - if(A.r_hand) + if(cur_carbon.l_hand || cur_carbon.r_hand) + if(cur_carbon.l_hand) + holding = "They are holding \a [cur_carbon.l_hand]" + if(cur_carbon.r_hand) if(holding) - holding += " and \a [A.r_hand]" + holding += " and \a [cur_carbon.r_hand]" else - holding = "They are holding \a [A.r_hand]" + holding = "They are holding \a [cur_carbon.r_hand]" + + var/hurt = "" + if(cur_carbon.health < 75) + hurt = prob(25) ? " - they look hurt" : " - [cur_carbon] looks hurt" if(!mob_detail) - mob_detail = "You can see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]. " + mob_detail = "You can see [cur_carbon] in the photo[hurt].[holding ? " [holding]" : "."]." else - mob_detail += "You can also see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]." + mob_detail += " You [prob(50) ? "can" : "also"] see [cur_carbon] in the photo[hurt].[holding ? " [holding]" : "."]." return mob_detail /obj/item/device/camera/afterattack(atom/target as mob|obj|turf|area, mob/user as mob, flag) - if(!on || !pictures_left || ismob(target.loc) || isstorage(target.loc)) return - if(user.contains(target) || istype(target, /atom/movable/screen)) return - captureimage(target, user, flag) + if(!on || !pictures_left || ismob(target.loc) || isstorage(target.loc)) + return + if(user.contains(target) || istype(target, /atom/movable/screen)) + return playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 15, 1) pictures_left-- desc = "A polaroid camera. It has [pictures_left] photos left." to_chat(user, SPAN_NOTICE("[pictures_left] photos left.")) + + captureimage(target, user, flag) + icon_state = icon_off on = 0 spawn(64) @@ -257,37 +286,46 @@ on = 1 /obj/item/device/camera/proc/captureimage(atom/target, mob/user, flag) - var/mobs = "" + var/mob_descriptions = "" var/radius = (size-1)*0.5 var/list/turf/turfs = RANGE_TURFS(radius, target) & view(world_view_size + radius, user.client) - for(var/turf/T as anything in turfs) - mobs += get_mobs(T) - var/datum/picture/P = createpicture(target, user, turfs, mobs, flag) - printpicture(user, P) + for(var/turf/the_turf as anything in turfs) + mob_descriptions = get_mob_descriptions(the_turf, mob_descriptions) + var/datum/picture/the_picture = createpicture(target, user, turfs, mob_descriptions, flag) -/obj/item/device/camera/proc/createpicture(atom/target, mob/user, list/turfs, mobs, flag) + if(QDELETED(user)) + return + + printpicture(user, the_picture) + +/obj/item/device/camera/proc/createpicture(atom/target, mob/user, list/turfs, description, flag) var/icon/photoimage = get_icon(turfs, target) + if(!description) + description = "A very scenic photo" + var/icon/small_img = icon(photoimage) var/icon/tiny_img = icon(photoimage) - var/icon/ic = icon('icons/obj/items/items.dmi',"photo") - var/icon/pc = icon('icons/obj/items/paper.dmi', "photo") + var/icon/item_icon = icon('icons/obj/items/items.dmi',"photo") + var/icon/paper_icon = icon('icons/obj/items/paper.dmi', "photo") small_img.Scale(8, 8) tiny_img.Scale(4, 4) - ic.Blend(small_img,ICON_OVERLAY, 10, 13) - pc.Blend(tiny_img,ICON_OVERLAY, 12, 19) - - var/datum/picture/P = new() - P.fields["author"] = user - P.fields["icon"] = ic - P.fields["tiny"] = pc - P.fields["img"] = photoimage - P.fields["desc"] = mobs - P.fields["pixel_x"] = rand(-10, 10) - P.fields["pixel_y"] = rand(-10, 10) - P.fields["size"] = size - - return P + item_icon.Blend(small_img, ICON_OVERLAY, 10, 13) + CHECK_TICK + paper_icon.Blend(tiny_img, ICON_OVERLAY, 12, 19) + CHECK_TICK + + var/datum/picture/the_picture = new() + the_picture.fields["author"] = user + the_picture.fields["icon"] = item_icon + the_picture.fields["tiny"] = paper_icon + the_picture.fields["img"] = photoimage + the_picture.fields["desc"] = description + the_picture.fields["pixel_x"] = rand(-10, 10) + the_picture.fields["pixel_y"] = rand(-10, 10) + the_picture.fields["size"] = size + + return the_picture /obj/item/device/camera/proc/printpicture(mob/user, datum/picture/P) var/obj/item/photo/Photo = new/obj/item/photo()