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()