diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm new file mode 100644 index 0000000000..129236125c --- /dev/null +++ b/code/__defines/_planes+layers.dm @@ -0,0 +1,54 @@ +/*This file is a list of all preclaimed planes & layers + +All planes & layers should be given a value here instead of using a magic/arbitrary number. + +After fiddling with planes and layers for some time, I figured I may as well provide some documentation: + +What are planes? + Think of Planes as a sort of layer for a layer - if plane X is a larger number than plane Y, the highest number for a layer in X will be below the lowest + number for a layer in Y. + Planes also have the added bonus of having planesmasters. + +What are Planesmasters? + Planesmasters, when in the sight of a player, will have its appearance properties (for example, colour matrices, alpha, transform, etc) + applied to all the other objects in the plane. This is all client sided. + Usually you would want to add the planesmaster as an invisible image in the client's screen. + +What can I do with Planesmasters? + You can: Make certain players not see an entire plane, + Make an entire plane have a certain colour matrices, + Make an entire plane transform in a certain way, + Make players see a plane which is hidden to normal players - I intend to implement this with the antag HUDs for example. + Planesmasters can be used as a neater way to deal with client images or potentially to do some neat things + +How do planes work? + A plane can be any integer from -100 to 100. (If you want more, bug lummox.) + All planes above 0, the 'base plane', are visible even when your character cannot 'see' them, for example, the HUD. + All planes below 0, the 'base plane', are only visible when a character can see them. + +How do I add a plane? + Think of where you want the plane to appear, look through the pre-existing planes and find where it is above and where it is below + Slot it in in that place, and change the pre-existing planes, making sure no plane shares a number. + Add a description with a comment as to what the plane does. + +How do I make something a planesmaster? + Add the PLANE_MASTER appearance flag to the appearance_flags variable. + +What is the naming convention for planes or layers? + Make sure to use the name of your object before the _LAYER or _PLANE, eg: [NAME_OF_YOUR_OBJECT HERE]_LAYER or [NAME_OF_YOUR_OBJECT HERE]_PLANE + Also, as it's a define, it is standard practice to use capital letters for the variable so people know this. + +*/ + +#define DEFAULT_PLANE 0 // BYOND's default value for plane, the "base plane" + +#define SPACE_PLANE -32 // Reserved for use in space/parallax + +#define PARALLAX_PLANE -30 // Reserved for use in space/parallax + +// OPENSPACE_PLANE reserves all planes between OPENSPACE_PLANE_START and OPENSPACE_PLANE_END inclusive +#define OPENSPACE_PLANE_START -23 +#define OPENSPACE_PLANE_END -8 +#define OPENSPACE_PLANE -25 // /turf/simulated/open will use OPENSPACE_PLANE + z (Valid z's being 2 thru 17) + +#define OVER_OPENSPACE_PLANE -7 diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index dea3c82461..652e11f6a3 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -302,6 +302,21 @@ proc/get_radio_key_from_channel(var/channel) var/image/speech_bubble = image('icons/mob/talk.dmi',src,"[speech_type][speech_bubble_test]") spawn(30) qdel(speech_bubble) + // VOREStation Edit - Attempt Multi-Z Talking + var/mob/above = src.shadow + while(!QDELETED(above)) + var/turf/ST = get_turf(above) + if(ST) + var/list/results = get_mobs_and_objs_in_view_fast(ST, world.view) + var/image/z_speech_bubble = image('icons/mob/talk.dmi', above, "h[speech_bubble_test]") + spawn(30) qdel(z_speech_bubble) + for(var/item in results["mobs"]) + if(item != above && !(item in listening)) + listening[item] = z_speech_bubble + listening_obj |= results["objs"] + above = above.shadow + // VOREStation Edit End + //Main 'say' and 'whisper' message delivery for(var/mob/M in listening) spawn(0) //Using spawns to queue all the messages for AFTER this proc is done, and stop runtimes @@ -310,12 +325,12 @@ proc/get_radio_key_from_channel(var/channel) var/dst = get_dist(get_turf(M),get_turf(src)) if(dst <= message_range || (M.stat == DEAD && !forbid_seeing_deadchat)) //Inside normal message range, or dead with ears (handled in the view proc) - M << speech_bubble + M << (listening[M] || speech_bubble) // VOREStation Edit - Send the image attached to shadow mob if available M.hear_say(message, verb, speaking, alt_name, italics, src, speech_sound, sound_vol) if(whispering) //Don't even bother with these unless whispering if(dst > message_range && dst <= w_scramble_range) //Inside whisper scramble range - M << speech_bubble + M << (listening[M] || speech_bubble) // VOREStation Edit - Send the image attached to shadow mob if available M.hear_say(stars(message), verb, speaking, alt_name, italics, src, speech_sound, sound_vol*0.2) if(dst > w_scramble_range && dst <= world.view) //Inside whisper 'visible' range M.show_message("[src.name] [w_not_heard].", 2) diff --git a/code/modules/multiz/open_space_controller.dm b/code/modules/multiz/open_space_controller.dm new file mode 100644 index 0000000000..fc6c7e5484 --- /dev/null +++ b/code/modules/multiz/open_space_controller.dm @@ -0,0 +1,111 @@ +// +// Controller handling icon updates of open space turfs +// + +/var/global/open_space_initialised = FALSE +/var/global/datum/controller/process/open_space/OS_controller = null +/var/global/image/over_OS_darkness = image('icons/turf/open_space.dmi', "black_open") + +/datum/controller/process/open_space + var/list/turfs_to_process = list() // List of turfs queued for update. + var/list/turfs_to_process_old = null // List of turfs currently being updated. + +/datum/controller/process/open_space/setup() + . = ..() + name = "openspace" + schedule_interval = world.tick_lag // every second + start_delay = 30 SECONDS + OS_controller = src + over_OS_darkness.plane = OVER_OPENSPACE_PLANE + over_OS_darkness.layer = MOB_LAYER + initialize_open_space() + + // Pre-process open space once once before the round starts. Wait 20 seconds so other stuff has time to finish. + spawn(200) + doWork(1) + +/datum/controller/process/open_space/copyStateFrom(var/datum/controller/process/open_space/other) + . = ..() + OS_controller = src + +/datum/controller/process/open_space/doWork() + // We use a different list so any additions to the update lists during a delay from scheck() + // don't cause things to be cut from the list without being updated. + turfs_to_process_old = turfs_to_process + turfs_to_process = list() + + for(last_object in turfs_to_process_old) + var/turf/T = last_object + if(!QDELETED(T)) + update_turf(T) + SCHECK + +/datum/controller/process/open_space/proc/update_turf(var/turf/T) + for(var/atom/movable/A in T) + A.fall() + T.update_icon() + +/datum/controller/process/open_space/proc/add_turf(var/turf/T, var/recursive = 0) + ASSERT(isturf(T)) + turfs_to_process += T + if(recursive > 0) + var/turf/above = GetAbove(T) + if(above && isopenspace(above)) + add_turf(above, recursive) + +// Do the initial updates of open space turfs when the game starts. This will lag! +/datum/controller/process/open_space/proc/initialize_open_space() + // Do initial setup from bottom to top. + for(var/zlevel = 1 to world.maxz) + for(var/turf/simulated/open/T in block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel))) + add_turf(T) + open_space_initialised = TRUE + +/turf/simulated/open/initialize() + . = ..() + if(open_space_initialised) + // log_debug("[src] ([x],[y],[z]) queued for update for initialize()") + OS_controller.add_turf(src) + +/turf/Entered(atom/movable/AM) + . = ..() + if(open_space_initialised && !AM.invisibility && isobj(AM)) + var/turf/T = GetAbove(src) + if(isopenspace(T)) + // log_debug("[T] ([T.x],[T.y],[T.z]) queued for update for [src].Entered([AM])") + OS_controller.add_turf(T, 1) + +/turf/Exited(atom/movable/AM) + . = ..() + if(open_space_initialised && !AM.invisibility && isobj(AM)) + var/turf/T = GetAbove(src) + if(isopenspace(T)) + // log_debug("[T] ([T.x],[T.y],[T.z]) queued for update for [src].Exited([AM])") + OS_controller.add_turf(T, 1) + +/obj/update_icon() + . = ..() + if(open_space_initialised && !invisibility && isturf(loc)) + var/turf/T = GetAbove(src) + if(isopenspace(T)) + // log_debug("[T] ([T.x],[T.y],[T.z]) queued for update for [src].update_icon()") + OS_controller.add_turf(T, 1) + +// Ouch... this is painful. But is there any other way? +/* - No for now +/obj/New() + . = ..() + if(open_space_initialised && !invisibility) + var/turf/T = GetAbove(src) + if(isopenspace(T)) + // log_debug("[T] ([T.x],[T.y],[T.z]) queued for update for [src]New()") + OS_controller.add_turf(T, 1) +*/ + +// Just as New() we probably should hook Destroy() If we can think of something more efficient, lets hear it. +/obj/Destroy() + if(open_space_initialised && !invisibility && isturf(loc)) + var/turf/T = GetAbove(src) + if(isopenspace(T)) + OS_controller.add_turf(T, 1) + . = ..() // Important that this be at the bottom, or we will have been moved to nullspace. diff --git a/code/modules/multiz/turf.dm b/code/modules/multiz/turf.dm index b6845d076f..161adfb80d 100644 --- a/code/modules/multiz/turf.dm +++ b/code/modules/multiz/turf.dm @@ -13,13 +13,19 @@ /turf/space/CanZPass(atom, direction) return 1 +// +// Open Space - "empty" turf that lets stuff fall thru it to the layer below +// + /turf/simulated/open name = "open space" icon = 'icons/turf/space.dmi' icon_state = "" - layer = 0 + desc = "\..." density = 0 + plane = OPENSPACE_PLANE_START pathweight = 100000 //Seriously, don't try and path over this one numbnuts + dynamic_lighting = 0 // Someday lets do proper lighting z-transfer. Until then we are leaving this off so it looks nicer. var/turf/below @@ -42,42 +48,75 @@ AM.fall() /turf/simulated/open/proc/update() + plane = OPENSPACE_PLANE + src.z below = GetBelow(src) turf_changed_event.register(below, src, /turf/simulated/open/update_icon) - var/turf/simulated/T = get_step(src,NORTH) - if(T) - turf_changed_event.register(T, src, /turf/simulated/open/update_icon) levelupdate() for(var/atom/movable/A in src) A.fall() - update_icon() + OS_controller.add_turf(src, 1) // override to make sure nothing is hidden /turf/simulated/open/levelupdate() for(var/obj/O in src) O.hide(0) +/turf/simulated/open/examine(mob/user, distance, infix, suffix) + if(..(user, 2)) + var/depth = 1 + for(var/T = GetBelow(src); isopenspace(T); T = GetBelow(T)) + depth += 1 + to_chat(user, "It is about [depth] levels deep.") + +/** +* Update icon and overlays of open space to be that of the turf below, plus any visible objects on that turf. +*/ /turf/simulated/open/update_icon() + overlays = list() // Edit - Overlays are being crashy when modified. + update_icon_edge()// Add - Get grass into open spaces and whatnot. + var/turf/below = GetBelow(src) if(below) - underlays = list(image(icon = below.icon, icon_state = below.icon_state)) - underlays += below.overlays.Copy() + var/below_is_open = isopenspace(below) - var/list/noverlays = list() - if(!istype(below,/turf/space)) - noverlays += image(icon =icon, icon_state = "empty", layer = 2.2) + if(below_is_open) + underlays = below.underlays + else + var/image/bottom_turf = image(icon = below.icon, icon_state = below.icon_state, dir=below.dir, layer=below.layer) + bottom_turf.plane = src.plane + bottom_turf.color = below.color + underlays = list(bottom_turf) + // VOREStation Edit - Hack workaround to byond crash bug - Include the magic overlay holder object. + overlays += below.overlays + // if(below.overlay_holder) + // overlays += (below.overlays + below.overlay_holder.overlays) + // else + // overlays += below.overlays + // VOREStation Edit End - var/turf/simulated/T = get_step(src,NORTH) - if(istype(T) && !istype(T,/turf/simulated/open)) - noverlays += image(icon ='icons/turf/cliff.dmi', icon_state = "metal", layer = 2.2) + // get objects (not mobs, they are handled by /obj/zshadow) + var/list/o_img = list() + for(var/obj/O in below) + if(O.invisibility) continue // Ignore objects that have any form of invisibility + if(O.loc != below) continue // Ignore multi-turf objects not directly below + var/image/temp2 = image(O, dir = O.dir, layer = O.layer) + temp2.plane = src.plane + temp2.color = O.color + temp2.overlays += O.overlays + // TODO Is pixelx/y needed? + o_img += temp2 + var/overlays_pre = overlays.len + overlays += o_img + var/overlays_post = overlays.len + if(overlays_post != (overlays_pre + o_img.len)) //Here we go! + world.log << "Corrupted openspace turf at [x],[y],[z] being replaced. Pre: [overlays_pre], Post: [overlays_post]" + new /turf/simulated/open(src) + return //Let's get out of here. - var/obj/structure/stairs/S = locate() in below - if(S && S.loc == below) - var/image/I = image(icon = S.icon, icon_state = "below", dir = S.dir, layer = 2.2) - I.pixel_x = S.pixel_x - I.pixel_y = S.pixel_y - noverlays += I + if(!below_is_open) + overlays += over_OS_darkness - overlays = noverlays + return 0 + return PROCESS_KILL // Straight copy from space. /turf/simulated/open/attackby(obj/item/C as obj, mob/user as mob) @@ -115,4 +154,8 @@ //Most things use is_plating to test if there is a cover tile on top (like regular floors) /turf/simulated/open/is_plating() - return 1 \ No newline at end of file + return TRUE + +/turf/simulated/open/is_space() + var/turf/below = GetBelow(src) + return !below || below.is_space() \ No newline at end of file diff --git a/code/modules/multiz/zshadow.dm b/code/modules/multiz/zshadow.dm new file mode 100644 index 0000000000..42dee28f2b --- /dev/null +++ b/code/modules/multiz/zshadow.dm @@ -0,0 +1,133 @@ +/mob // TODO: rewrite as obj. + var/mob/zshadow/shadow + +/mob/zshadow + plane = OVER_OPENSPACE_PLANE + name = "shadow" + desc = "Z-level shadow" + status_flags = GODMODE + anchored = 1 + unacidable = 1 + density = 0 + opacity = 0 // Don't trigger lighting recalcs gah! TODO - consider multi-z lighting. + auto_init = FALSE // We do not need to be initialize()d + var/mob/owner = null // What we are a shadow of. + +/mob/zshadow/can_fall() + return FALSE + +/mob/zshadow/New(var/mob/L) + if(!istype(L)) + qdel(src) + return + ..() // I'm cautious about this, but its the right thing to do. + owner = L + sync_icon(L) + +/mob/Destroy() + if(shadow) + qdel(shadow) + shadow = null + . = ..() + +/mob/zshadow/examine(mob/user, distance, infix, suffix) + return owner.examine(user, distance, infix, suffix) + +// Relay some stuff they hear +/mob/zshadow/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol) + if(speaker && speaker.z != src.z) + return // Only relay speech on our acutal z, otherwise we might relay sounds that were themselves relayed up! + if(isliving(owner)) + verb += " from above" + return owner.hear_say(message, verb, language, alt_name, italics, speaker, speech_sound, sound_vol) + +/mob/zshadow/proc/sync_icon(var/mob/M) + name = M.name + icon = M.icon + icon_state = M.icon_state + //color = M.color + color = "#848484" + overlays = M.overlays + transform = M.transform + dir = M.dir + if(shadow) + shadow.sync_icon(src) + +/mob/living/Move() + . = ..() + check_shadow() + +/mob/living/forceMove() + . = ..() + check_shadow() + +/mob/living/on_mob_jump() + // We're about to be admin-jumped. + // Unfortuantely loc isn't set until after this proc is called. So we must spawn() so check_shadow executes with the new loc. + . = ..() + if(shadow) + spawn(0) + check_shadow() + +/mob/living/proc/check_shadow() + var/mob/M = src + if(isturf(M.loc)) + var/turf/simulated/open/OS = GetAbove(src) + while(OS && istype(OS)) + if(!M.shadow) + M.shadow = new /mob/zshadow(M) + M.shadow.forceMove(OS) + M = M.shadow + OS = GetAbove(M) + // The topmost level does not need a shadow! + if(M.shadow) + qdel(M.shadow) + M.shadow = null + +// +// Handle cases where the owner mob might have changed its icon or overlays. +// + +/mob/living/update_icons() + . = ..() + if(shadow) + shadow.sync_icon(src) + +// WARNING - the true carbon/human/update_icons does not call ..(), therefore we must sideways override this. +// But be careful, we don't want to screw with that proc. So lets be cautious about what we do here. +/mob/living/carbon/human/update_icons() + . = ..() + if(shadow) + shadow.sync_icon(src) + +/mob/set_dir(new_dir) + . = ..() + if(shadow) + shadow.set_dir(new_dir) + +// Transfer messages about what we are doing to upstairs +/mob/visible_message(var/message, var/self_message, var/blind_message) + . = ..() + if(shadow) + shadow.visible_message(message, self_message, blind_message) + +// We should show the typing indicator so people above us can tell we're about to talk. +/mob/set_typing_indicator(var/state) + var/old_typing = src.typing + . = ..() + if(shadow && old_typing != src.typing) + shadow.set_typing_indicator(state) // Okay the real proc changed something! That means we should handle things too + +/mob/zshadow/set_typing_indicator(var/state) + if(!typing_indicator) + typing_indicator = new + typing_indicator.icon = 'icons/mob/talk.dmi' //VOREStation Edit - Looks better on the right with job icons. + typing_indicator.icon_state = "typing" + if(state && !typing) + overlays += typing_indicator + typing = 1 + else if(!state && typing) + overlays -= typing_indicator + typing = 0 + if(shadow) + shadow.set_typing_indicator(state) diff --git a/html/changelogs/Woodrat-MultiZ.yml b/html/changelogs/Woodrat-MultiZ.yml new file mode 100644 index 0000000000..0058ee3623 --- /dev/null +++ b/html/changelogs/Woodrat-MultiZ.yml @@ -0,0 +1,37 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Woodrat + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added 'see down' in open spaces from Vore." + - rscadd: "Added talking and visible messages upward through open space from Vore." \ No newline at end of file diff --git a/icons/mob/talk.dmi b/icons/mob/talk.dmi index acd94a74c3..ef15e9da6d 100644 Binary files a/icons/mob/talk.dmi and b/icons/mob/talk.dmi differ diff --git a/icons/turf/open_space.dmi b/icons/turf/open_space.dmi new file mode 100644 index 0000000000..8c5810fffc Binary files /dev/null and b/icons/turf/open_space.dmi differ diff --git a/polaris.dme b/polaris.dme index 150b5e4f1d..ae23c5360a 100644 --- a/polaris.dme +++ b/polaris.dme @@ -18,6 +18,7 @@ #include "code\stylesheet.dm" #include "code\world.dm" #include "code\__defines\_compile_options.dm" +#include "code\__defines\_planes+layers.dm" #include "code\__defines\admin.dm" #include "code\__defines\appearance.dm" #include "code\__defines\atmos.dm" @@ -1835,9 +1836,11 @@ #include "code\modules\multiz\_stubs.dm" #include "code\modules\multiz\basic.dm" #include "code\modules\multiz\movement.dm" +#include "code\modules\multiz\open_space_controller.dm" #include "code\modules\multiz\pipes.dm" #include "code\modules\multiz\structures.dm" #include "code\modules\multiz\turf.dm" +#include "code\modules\multiz\zshadow.dm" #include "code\modules\nano\nanoexternal.dm" #include "code\modules\nano\nanomanager.dm" #include "code\modules\nano\nanomapgen.dm"