diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 0ca515486b..e7a6bbba96 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -634,20 +634,60 @@ as a single icon. Useful for when you want to manipulate an icon via the above a The _flatIcons list is a cache for generated icon files. */ -proc - getFlatIcon(atom/A, dir) // 1 = use cache, 2 = override cache, 0 = ignore cache +proc // Creates a single icon from a given /atom or /image. Only the first argument is required. + 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', "icon_state"="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() - - // Add the atom's icon itself - if(A.icon) - // Make a copy without pixel_x/y settings - var/image/copy = image(icon=A.icon,icon_state=A.icon_state,layer=A.layer,dir=A.dir) + 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 - // dir defaults to A's dir - if(!dir) dir = A.dir - // 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 @@ -662,7 +702,7 @@ proc if(!current) continue currentLayer = current:layer if(currentLayer<0) // Special case for FLY_LAYER - if(currentLayer <= -1000) return 0 + if(currentLayer <= -1000) return flat if(pSet == 0) // Underlay currentLayer = A.layer+currentLayer/1000 else // Overlay @@ -688,8 +728,6 @@ proc else // All done break - // We start with a blank canvas, otherwise some icon procs crash silently - var/icon/flat = icon('icons/effects/effects.dmi', "icon_state"="nothing") // Final flattened icon var/icon/add // Icon of overlay being added // Current dimensions of flattened icon @@ -699,24 +737,33 @@ proc for(var/I in layers) - if(I:icon) - if(I:icon_state) - // Has icon and state set - add = icon(I:icon, I:icon_state) - else - if(A.icon_state in icon_states(I:icon)) - // Inherits icon_state from atom - add = icon(I:icon, A.icon_state) - else - // Uses default state ("") - add = icon(I:icon) - else if(I:icon_state) - // Inherits icon from atom - add = icon(A.icon, I:icon_state) - else - // Unknown + 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) + // This checks for a silent failure mode of the icon routine. If the requested dir + // doesn't exist in this icon state it returns a 32x32 icon with 0 alpha. + if (I:dir != SOUTH && add.Width() == 32 && add.Height() == 32) + // Check every pixel for blank (computationally expensive, but the process is limited + // by the amount of film on the station, only happens when we hit something that's + // turned, and bails at the very first pixel it sees. + var/blankpixel; + for(var/y;y<=32;y++) + for(var/x;x<32;x++) + blankpixel = isnull(add.GetPixel(x,y)) + if(!blankpixel) + break + if(!blankpixel) + break + // If we ALWAYS returned a null (which happens when GetPixel encounters something with alpha 0) + if (blankpixel) + // Pull the default direction. + add = icon(I:icon, I:icon_state) + 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()) @@ -730,9 +777,14 @@ proc flatY1=addY1;flatY2=addY2 // Blend the overlay into the flattened icon - flat.Blend(add,ICON_OVERLAY,I:pixel_x+2-flatX1,I:pixel_y+2-flatY1) + flat.Blend(add, blendMode2iconMode(curblend), I:pixel_x + 2 - flatX1, I:pixel_y + 2 - flatY1) - return flat + 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) getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A. @@ -783,4 +835,23 @@ proc/adjust_brightness(var/color, var/value) RGB[1] = Clamp(RGB[1]+value,0,255) RGB[2] = Clamp(RGB[2]+value,0,255) RGB[3] = Clamp(RGB[3]+value,0,255) - return rgb(RGB[1],RGB[2],RGB[3]) \ No newline at end of file + return rgb(RGB[1],RGB[2],RGB[3]) + +proc/sort_atoms_by_layer(var/list/atoms) + // Comb sort icons based on levels + var/list/result = atoms.Copy() + var/gap = result.len + var/swapped = 1 + while (gap > 1 || swapped) + swapped = 0 + if(gap > 1) + gap = round(gap / 1.3) // 1.3 is the emperic comb sort coefficient + if(gap < 1) + gap = 1 + for(var/i = 1; gap + i <= result.len; i++) + var/atom/l = result[i] //Fucking hate + var/atom/r = result[gap+i] //how lists work here + if(l.layer > r.layer) //no "result[i].layer" for me + result.Swap(i, gap + i) + swapped = 1 + return result diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index 885de82c44..8c188d0d18 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -298,6 +298,13 @@ proc/tg_list2text(list/list, glue=",") /proc/angle2text(var/degree) return dir2text(angle2dir(degree)) +//Converts a blend_mode constant to one acceptable to icon.Blend() +/proc/blendMode2iconMode(blend_mode) + switch(blend_mode) + if(BLEND_MULTIPLY) return ICON_MULTIPLY + if(BLEND_ADD) return ICON_ADD + if(BLEND_SUBTRACT) return ICON_SUBTRACT + else return ICON_OVERLAY //Converts a rights bitfield into a string /proc/rights2text(rights,seperator="") diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index 98da4a58de..a1a626313b 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -119,7 +119,7 @@ L.fields["b_dna"] = H.dna.unique_enzymes L.fields["enzymes"] = H.dna.SE // Used in respawning L.fields["identity"] = H.dna.UI // " - L.fields["image"] = getFlatIcon(H,0) //This is god-awful + L.fields["image"] = getFlatIcon(H) //This is god-awful locked += L return diff --git a/code/game/supplyshuttle.dm b/code/game/supplyshuttle.dm index 4fb6683bd7..1ef29b5065 100644 --- a/code/game/supplyshuttle.dm +++ b/code/game/supplyshuttle.dm @@ -54,7 +54,10 @@ var/list/mechtoys = list( if (istype(A, /obj/structure/stool/bed) && B.buckled_mob)//if it's a bed/chair and someone is buckled, it will not pass return 0 - else if(istype(A, /mob/living)) // You Shall Not Pass! + if(istype(A, /obj/vehicle)) //no vehicles + return 0 + + if(istype(A, /mob/living)) // You Shall Not Pass! var/mob/living/M = A if(!M.lying && !istype(M, /mob/living/carbon/monkey) && !istype(M, /mob/living/carbon/slime) && !istype(M, /mob/living/simple_animal/mouse) && !istype(M, /mob/living/silicon/robot/drone)) //If your not laying down, or a small creature, no pass. return 0 diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 1fa7e6349d..5a06f2cfbe 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -251,7 +251,7 @@ proc/get_damage_icon_part(damage_state, body_part) icon_key = "[icon_key]0" else if(part.status & ORGAN_ROBOT) icon_key = "[icon_key]2" - else if(part.status & ORGAN_DEAD) //Do we even have necrosis in our current code? ~Z + else if(part.status & ORGAN_DEAD) icon_key = "[icon_key]3" else icon_key = "[icon_key]1" @@ -274,6 +274,10 @@ proc/get_damage_icon_part(damage_state, body_part) //No icon stored, so we need to start with a basic one. var/datum/organ/external/chest = get_organ("chest") base_icon = chest.get_icon(g) + + if(chest.status & ORGAN_DEAD) + base_icon.ColorTone(necrosis_color_mod) + base_icon.SetIntensity(0.7) for(var/datum/organ/external/part in organs) diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index f0d39337b6..cbc119eaf5 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -53,10 +53,10 @@ /obj/item/weapon/photo/proc/show(mob/user as mob) user << browse_rsc(img, "tmp_photo.png") user << browse("[name]" \ - + "" \ - + "
Written on the back:
[scribble]" : ]"\ - + "", "window=book;size=200x[scribble ? 400 : 200]") + + "" \ + + "" \ + + "[scribble ? "
Written on the back:
[scribble]" : ""]"\ + + "", "window=book;size=192x[scribble ? 400 : 192]") onclose(user, "[name]") return @@ -152,41 +152,51 @@ ..() -/obj/item/device/camera/proc/get_icon(turf/the_turf as turf) +/obj/item/device/camera/proc/get_icon(list/turfs, turf/center) + //Bigger icon base to capture those icons that were shifted to the next tile //i.e. pretty much all wall-mounted machinery var/icon/res = icon('icons/effects/96x96.dmi', "") - - var/icon/turficon = build_composite_icon(the_turf) - res.Blend(turficon, ICON_OVERLAY, 33, 33) + // Initialize the photograph to black. + res.Blend("#000", ICON_OVERLAY) var/atoms[] = list() - for(var/atom/A in the_turf) - if(A.invisibility) continue - atoms.Add(A) + for(var/turf/the_turf in turfs) + // Add outselves 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) - //Sorting icons based on levels - var/gap = atoms.len - var/swapped = 1 - while (gap > 1 || swapped) - swapped = 0 - if(gap > 1) - gap = round(gap / 1.247330950103979) - if(gap < 1) - gap = 1 - for(var/i = 1; gap + i <= atoms.len; i++) - var/atom/l = atoms[i] //Fucking hate - var/atom/r = atoms[gap+i] //how lists work here - if(l.layer > r.layer) //no "atoms[i].layer" for me - atoms.Swap(i, gap + i) - swapped = 1 + // Sort the atoms into their layers + var/list/sorted = sort_atoms_by_layer(atoms) - for(var/i; i <= atoms.len; i++) - var/atom/A = atoms[i] + for(var/i; i <= sorted.len; i++) + var/atom/A = sorted[i] if(A) - var/icon/img = getFlatIcon(A, A.dir)//build_composite_icon(A) + var/icon/img = getFlatIcon(A)//build_composite_icon(A) + + // If what we got back is actually a picture, draw it. if(istype(img, /icon)) - res.Blend(new/icon(img, "", A.dir), ICON_OVERLAY, 33 + A.pixel_x, 33 + A.pixel_y) + // Check if we're looking at a mob that's lying down + if(istype(A, /mob/living) && A:lying) + // If they are, apply that effect to their picture. + img.BecomeLying() + // Calculate where we are relative to the center of the photo + var/xoff = (A.x - center.x) * 32 + var/yoff = (A.y - center.y) * 32 + if (istype(A,/atom/movable)) + xoff+=A:step_x + yoff+=A:step_y + res.Blend(img, blendMode2iconMode(A.blend_mode), 33 + A.pixel_x + xoff, 33 + A.pixel_y + yoff) + + // Lastly, render any contained effects on top. + for(var/turf/the_turf in turfs) + // Calculate where we are relative to the center of the photo + var/xoff = (the_turf.x - center.x) * 32 + var/yoff = (the_turf.y - center.y) * 32 + res.Blend(getFlatIcon(the_turf.loc), blendMode2iconMode(the_turf.blend_mode),33 + xoff,33 + yoff) return res @@ -217,8 +227,8 @@ var/y_c = target.y + 1 var/z_c = target.z - var/icon/temp = icon('icons/effects/96x96.dmi',"") - var/icon/black = icon('icons/turf/space.dmi', "black") + + var/list/turfs = list() var/mobs = "" for(var/i = 1; i <= 3; i++) for(var/j = 1; j <= 3; j++) @@ -228,9 +238,7 @@ if(user.client) //To make shooting through security cameras possible viewer = user.client.eye if(dummy in viewers(world.view, viewer)) - temp.Blend(get_icon(T), ICON_OVERLAY, 32 * (j-1-1), 32 - 32 * (i-1)) - else - temp.Blend(black, ICON_OVERLAY, 32 * (j-1), 64 - 32 * (i-1)) + turfs.Add(T) mobs += get_mobs(T) dummy.loc = null dummy = null //Alas, nameless creature //garbage collect it instead @@ -238,12 +246,14 @@ y_c-- x_c = x_c - 3 + var/icon/photoimage = get_icon(turfs, target) + var/obj/item/weapon/photo/P = new/obj/item/weapon/photo() P.loc = user.loc if(!user.get_inactive_hand()) user.put_in_inactive_hand(P) - var/icon/small_img = icon(temp) - var/icon/tiny_img = icon(temp) + var/icon/small_img = icon(photoimage) + var/icon/tiny_img = icon(photoimage) var/icon/ic = icon('icons/obj/items.dmi',"photo") var/icon/pc = icon('icons/obj/bureaucracy.dmi', "photo") small_img.Scale(8, 8) @@ -252,7 +262,7 @@ pc.Blend(tiny_img,ICON_OVERLAY, 12, 19) P.icon = ic P.tiny = pc - P.img = temp + P.img = photoimage P.desc = mobs P.pixel_x = rand(-10, 10) P.pixel_y = rand(-10, 10) diff --git a/code/modules/vehicles/cargo_train.dm b/code/modules/vehicles/cargo_train.dm index b6f91feec7..11d0c53fb8 100644 --- a/code/modules/vehicles/cargo_train.dm +++ b/code/modules/vehicles/cargo_train.dm @@ -43,6 +43,7 @@ ..() cell = new /obj/item/weapon/cell/high verbs -= /atom/movable/verb/pull + verbs -= /obj/vehicle/train/cargo/engine/verb/stop_engine key = new() var/image/I = new(icon = 'icons/obj/vehicles.dmi', icon_state = "cargo_engine_overlay", layer = src.layer + 0.2) //over mobs overlays += I @@ -70,8 +71,8 @@ if(istype(W, /obj/item/weapon/key/cargo_train)) if(!key) user.drop_item() + W.forceMove(src) key = W - W.loc = src verbs += /obj/vehicle/train/cargo/engine/verb/remove_key return ..() @@ -97,7 +98,7 @@ var/obj/machinery/door/D = Obstacle var/mob/living/carbon/human/H = load if(istype(D) && istype(H)) - D.Bumped(H) //a little hacky, but hey, it works, and repects access rights + D.Bumped(H) //a little hacky, but hey, it works, and respects access rights ..() @@ -194,6 +195,8 @@ turn_on() if (on) usr << "You start [src]'s engine." + verbs += /obj/vehicle/train/cargo/engine/verb/stop_engine + verbs -= /obj/vehicle/train/cargo/engine/verb/start_engine else if(cell.charge < power_use) usr << "[src] is out of power." @@ -215,6 +218,8 @@ turn_off() if (!on) usr << "You stop [src]'s engine." + verbs -= /obj/vehicle/train/cargo/engine/verb/stop_engine + verbs += /obj/vehicle/train/cargo/engine/verb/start_engine /obj/vehicle/train/cargo/engine/verb/remove_key() set name = "Remove key" @@ -243,7 +248,7 @@ /obj/vehicle/train/cargo/trolley/load(var/atom/movable/C) if(ismob(C) && !passenger_allowed) return 0 - if(!istype(C,/obj/machinery) && !istype(C,/obj/structure/closet) && !istype(C,/obj/structure/largecrate) && !istype(C,/obj/structure/reagent_dispensers) && !istype(C,/obj/structure/ore_box) && !ismob(C)) + if(!istype(C,/obj/machinery) && !istype(C,/obj/structure/closet) && !istype(C,/obj/structure/largecrate) && !istype(C,/obj/structure/reagent_dispensers) && !istype(C,/obj/structure/ore_box) && !istype(C, /mob/living/carbon/human)) return 0 ..() @@ -255,7 +260,7 @@ return 1 /obj/vehicle/train/cargo/engine/load(var/atom/movable/C) - if(!ismob(C)) + if(!istype(C, /mob/living/carbon/human)) return 0 return ..() diff --git a/code/modules/vehicles/train.dm b/code/modules/vehicles/train.dm index deaed1130c..d29933b5ed 100644 --- a/code/modules/vehicles/train.dm +++ b/code/modules/vehicles/train.dm @@ -68,8 +68,7 @@ if(user != load) if(user in src) //for handling players stuck in src - this shouldn't happen - but just in case it does - user.loc = T - contents -= user + user.forceMove(T) return 1 return 0 @@ -93,8 +92,7 @@ return 0 if(user != load && (user in src)) - user.loc = loc //for handling players stuck in src - contents -= user + user.forceMove(loc) //for handling players stuck in src else if(load) unload(user) //unload if loaded else if(!load && !user.buckled) diff --git a/code/modules/vehicles/vehicle.dm b/code/modules/vehicles/vehicle.dm index 5e5affac99..caec0c896a 100644 --- a/code/modules/vehicles/vehicle.dm +++ b/code/modules/vehicles/vehicle.dm @@ -50,7 +50,7 @@ anchored = init_anc if(load) - load.loc = loc + load.forceMove(loc)// = loc load.dir = dir return 1 @@ -196,7 +196,7 @@ new /obj/item/weapon/cable_coil/cut(Tsec) if(cell) - cell.loc = Tsec + cell.forceMove(Tsec) cell.update_icon() cell = null @@ -234,8 +234,8 @@ return H.drop_from_inventory(C) + C.forceMove(src) cell = C - C.loc = null //this wont be GC'd since it's referrenced above powercheck() usr << "You install [C] in [src]." @@ -244,7 +244,8 @@ return usr << "You remove [cell] from [src]." - cell.loc = get_turf(H) + cell.forceMove(get_turf(H)) + H.put_in_hands(cell) cell = null powercheck() @@ -271,7 +272,7 @@ if(istype(crate)) crate.close() - C.loc = loc + C.forceMove(loc) C.dir = dir C.anchored = 1 @@ -310,7 +311,7 @@ var/list/options = new() for(var/test_dir in alldirs) var/new_dir = get_step_to(src, get_step(src, test_dir)) - if(new_dir) + if(new_dir && load.Adjacent(new_dir)) options += new_dir if(options.len) dest = pick(options) @@ -320,8 +321,7 @@ if(!isturf(dest)) //if there still is nowhere to unload, cancel out since the vehicle is probably in nullspace return 0 - - load.loc = dest + load.forceMove(dest) load.dir = get_dir(loc, dest) load.anchored = initial(load.anchored) load.pixel_x = initial(load.pixel_x)