ID:1778235
 
Keywords: dir, sprite
BYOND Version:507
Operating System:Windows 7 Home Premium 64-bit
Web Browser:Firefox 35.0
Applies to:Dream Seeker
Status: Open

Issue hasn't been assigned a status value.
Descriptive Problem Summary:
When attempting to copy/duplicate an icon (in my particular case, code functioning as a polaroid camera), if an atom has a dir other than SOUTH, and it's icon_state does not have a sprite for that dir, the atom comes back as being invisible (or black, in the case of turfs).


Expected Results:
I would expect the return to be the icon_state for SOUTH if no other sprite exists within an icon_state for the dir being evaluated for.

Actual Results:
It returns what I would assume to be the graphical form of null.

Does the problem occur:
Every time? Or how often? Every time dir is not SOUTH and the icon_state does not support that dir
In other games? I would assume so
In other user accounts? Yes
On other computers? Yes

When does the problem NOT occur?
When dir can be reliably accounted for beforehand in the code running the icon duplication.

Did the problem NOT occur in any earlier versions? If so, what was the last version that worked? Not to my knowledge.

Workarounds:
Writing code to attempt to evaluate the dir of the atom as SOUTH when you know the icon_state won't support other dirs.

Bumping this because this is still an issue.. I presume it wasn't addressed because of lack of information so I have included some screenshots of it in action as well as the code that is being run:

http://s24.photobucket.com/user/AndroidSFV/media/ 41d6690f-fa9a-4063-bd3d-430bb89cf250.jpg.html?state=copy&sp= false

http://s24.photobucket.com/user/AndroidSFV/media/ Bug2_1.jpg.html?state=copy&sp=false

The above screens show two objects (only one of which has dir of south, the object only has an icon for south) and three mobs (only one of which has icons for the 4 primary directions, the crabs only have an icon for south). The camera object takes a photo but only the object with the south dir and the mob with icons for multiple dirs is shown, in addition to background stuff which can be considered merely control, if at all.

/obj/item/device/camera/proc/camera_get_icon(list/turfs, turf/center)
var/atoms[] = list()
for(var/turf/T in turfs)
atoms.Add(T)
for(var/atom/movable/A in T)
if(A.invisibility) continue
atoms.Add(A)

var/list/sorted = list()
var/j
for(var/i = 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)

var/icon/res = icon('icons/effects/96x96.dmi', "")

for(var/atom/A in sorted)
var/icon/img = getFlatIcon(A)
if(istype(A, /mob/living) && A:lying)
img.Turn(A:lying)

var/offX = 32 * (A.x - center.x) + A.pixel_x + 33
var/offY = 32 * (A.y - center.y) + A.pixel_y + 33
if(istype(A, /atom/movable))
offX += A:step_x
offY += A: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)
res.Blend(getFlatIcon(T.loc), blendMode2iconMode(T.blend_mode), 32 * (T.x - center.x) + 33, 32 * (T.y - center.y) + 33)

return res


/obj/item/device/camera/proc/camera_get_mobs(turf/the_turf)
var/mob_detail
for(var/mob/living/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(holding)
holding += " and \a [A.r_hand]"
else
holding = "They are holding \a [A.r_hand]"

if(!mob_detail)
mob_detail = "You can see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]. "
else
mob_detail += "You can also see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]."
return mob_detail


/obj/item/device/camera/proc/captureimage(atom/target, mob/user, flag) //Proc for both regular and AI-based camera to take the image
var/mobs = ""
var/isAi = istype(user, /mob/living/silicon/ai)
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 && !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/device/camera/proc/printpicture(mob/user, icon/temp, mobs, flag) //Normal camera proc for creating photos
var/obj/item/weapon/photo/P = new/obj/item/weapon/photo(get_turf(src))
if(Adjacent(user)) //needed because of TK
user.put_in_hands(P)
var/icon/small_img = icon(temp)
var/icon/ic = icon('icons/obj/items.dmi',"photo")
small_img.Scale(8, 8)
ic.Blend(small_img,ICON_OVERLAY, 10, 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

/*
Get flat icon by DarkCampainger. As it says on the tin, will return an icon with all the overlays
as a single icon. Useful for when you want to manipulate an icon via the above as overlays are not normally included.
The _flatIcons list is a cache for generated icon files.
*/


// Creates a single icon from a given /atom or /image. Only the first argument is required.
/proc/getFlatIcon(image/A, defdir=A.dir, deficon=A.icon, defstate=A.icon_state, defblend=A.blend_mode)
// We start with a blank canvas, otherwise some icon procs crash silently
var/icon/flat = icon('icons/effects/effects.dmi', "nothing") // Final flattened icon
if(!A)
return flat
if(A.alpha <= 0)
return flat
var/noIcon = FALSE

var/curicon
if(A.icon)
curicon = A.icon
else
curicon = deficon

if(!curicon)
noIcon = TRUE // Do not render this object.

var/curstate
if(A.icon_state)
curstate = A.icon_state
else
curstate = defstate

if(!noIcon && !(curstate in icon_states(curicon)))
if("" in icon_states(curicon))
curstate = ""
else
noIcon = TRUE // Do not render this object.

var/curdir
if(A.dir != 2)
curdir = A.dir
else
curdir = defdir

var/curblend
if(A.blend_mode == BLEND_DEFAULT)
curblend = defblend
else
curblend = A.blend_mode

// Layers will be a sorted list of icons/overlays, based on the order in which they are displayed
var/list/layers = list()
var/image/copy
// Add the atom's icon itself, without pixel_x/y offsets.
if(!noIcon)
copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=curdir)
copy.color = A.color
copy.alpha = A.alpha
copy.blend_mode = curblend
layers[copy] = A.layer

// Loop through the underlays, then overlays, sorting them into the layers list
var/list/process = A.underlays // Current list being processed
var/pSet=0 // Which list is being processed: 0 = underlays, 1 = overlays
var/curIndex=1 // index of 'current' in list being processed
var/current // Current overlay being sorted
var/currentLayer // Calculated layer that overlay appears on (special case for FLOAT_LAYER)
var/compare // The overlay 'add' is being compared against
var/cmpIndex // The index in the layers list of 'compare'
while(TRUE)
if(curIndex<=process.len)
current = process[curIndex]
if(!current)
curIndex++ //Try the next layer
continue
currentLayer = current:layer
if(currentLayer<0) // Special case for FLY_LAYER
if(currentLayer <= -1000) return flat
if(pSet == 0) // Underlay
currentLayer = A.layer+currentLayer/1000
else // Overlay
currentLayer = A.layer+(1000+currentLayer)/1000

// Sort add into layers list
for(cmpIndex=1,cmpIndex<=layers.len,cmpIndex++)
compare = layers[cmpIndex]
if(currentLayer < layers[compare]) // Associated value is the calculated layer
layers.Insert(cmpIndex,current)
layers[current] = currentLayer
break
if(cmpIndex>layers.len) // Reached end of list without inserting
layers[current]=currentLayer // Place at end

curIndex++

if(curIndex>process.len)
if(pSet == 0) // Switch to overlays
curIndex = 1
pSet = 1
process = A.overlays
else // All done
break

var/icon/add // Icon of overlay being added

// Current dimensions of flattened icon
var/{flatX1=1;flatX2=flat.Width();flatY1=1;flatY2=flat.Height()}
// Dimensions of overlay being added
var/{addX1;addX2;addY1;addY2}

for(var/I in layers)

if(I:alpha == 0)
continue

if(I == copy) // 'I' is an /image based on the object being flattened.
curblend = BLEND_OVERLAY
add = icon(I:icon, I:icon_state, I:dir)
else // 'I' is an appearance object.
add = getFlatIcon(new/image(I), curdir, curicon, curstate, curblend)

// Find the new dimensions of the flat icon to fit the added overlay
addX1 = min(flatX1, I:pixel_x+1)
addX2 = max(flatX2, I:pixel_x+add.Width())
addY1 = min(flatY1, I:pixel_y+1)
addY2 = max(flatY2, I:pixel_y+add.Height())

if(addX1!=flatX1 || addX2!=flatX2 || addY1!=flatY1 || addY2!=flatY2)
// Resize the flattened icon so the new icon fits
flat.Crop(addX1-flatX1+1, addY1-flatY1+1, addX2-flatX1+1, addY2-flatY1+1)
flatX1=addX1;flatX2=addX2
flatY1=addY1;flatY2=addY2

// Blend the overlay into the flattened icon
flat.Blend(add, blendMode2iconMode(curblend), I:pixel_x + 2 - flatX1, I:pixel_y + 2 - flatY1)

if(A.color)
flat.Blend(A.color, ICON_MULTIPLY)
if(A.alpha < 255)
flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY)

return icon(flat, "", SOUTH)


As far as I can tell, the code is good. It seems it is just returning a null value for the image when there is no icon for the dir the object has. If more info is needed, please say so.
I guess there are two schools of thought on this: One is that if you did do a dir-based "cut" of an icon, you wouldn't necessarily want states included that didn't include the dir. The other is that a fallback to a default dir might be preferable.

I'm not sure how this should be handled when the dir in question is diagonal and the icon state has four dirs.
My thinking is that byond should have a built in fallback to the default icon_state if a sprite for the dir being requested does not exist, and put the onus on the projects to account for necessary sprites (I'm not sure if you would want to have it create a log as well). At least in TG's SS13, I cannot think of any instance where something has icon states for only the four dirs and could then get a diagonal dir through any means, aside from variable editing.
I suppose falling back on a blank icon for each such state probably does make the most sense, and is unlikely (though not necessarily guaranteed) to mess up any existing projects.
I edited my previous post, I meant default icon_state and dir for a sprite** if the requested dir's icon_state doenst exist.

E: Basically, I don't think byond should be returning a blank as it currently does doing what we are doing. It should return the icon's south default if it cannot return anything else, and if a project needs it to return that something else, it's on them to create the sprite for it.
I know this is bumping an old post but I've been trying to work around this issue.

It's frustrating that it works that way because it doesn't correspond with how DS interprets this same thing. If I have an object on a map that has a singular icon_state and I set it to have any direction, it will still show up on the map with the same icon_state.

But now if I use darkcampaigner's very useful getflaticon() on that object, as in my specific use case, which takes the object's dir as the icon's dir: it will give an invisible icon unless that dir is SOUTH.

And all that forces us to manually manage which icons have directional icon states and which don't. Or to use getpixel() extensively and rather hackishly to determine if we've been given an invisible icon.