diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm
index 4e9d7fd6ad..edc57f9a4b 100644
--- a/code/__defines/_planes+layers.dm
+++ b/code/__defines/_planes+layers.dm
@@ -153,6 +153,9 @@ What is the naming convention for planes or layers?
#define CRIT_LAYER 18.3
//Client UI HUD stuff
+#define PLANE_HOLOMAP_FRAME 92
+#define PLANE_HOLOMAP 93 // Holomap rendered here
+#define PLANE_HOLOMAP_ICONS 94 // Holomap markers and bobbins
#define PLANE_PLAYER_HUD 95 //The character's UI is on this plane
#define LAYER_HUD_UNDER 1 //Under the HUD items
#define LAYER_HUD_BASE 2 //The HUD items themselves
diff --git a/code/__defines/holomap.dm b/code/__defines/holomap.dm
index 0b93e03ae0..b7dc0305ad 100644
--- a/code/__defines/holomap.dm
+++ b/code/__defines/holomap.dm
@@ -8,6 +8,45 @@
#define HOLOMAP_ICON_SIZE 480 // Pixel width & height of the holomap icon. Used for auto-centering etc.
#define ui_holomap "CENTER-7, CENTER-7" // Screen location of the holomap "hud"
+//Holomap filters
+#define HOLOMAP_FILTER_DEATHSQUAD 1
+#define HOLOMAP_FILTER_ERT 2
+#define HOLOMAP_FILTER_NUKEOPS 4
+#define HOLOMAP_FILTER_ELITESYNDICATE 8
+#define HOLOMAP_FILTER_VOX 16
+#define HOLOMAP_FILTER_STATIONMAP 32
+#define HOLOMAP_FILTER_STATIONMAP_STRATEGIC 64//features markers over the captain's office, the armory, the SMES
+#define HOLOMAP_FILTER_CULT 128//bloodstone locators
+
+#define HOLOMAP_EXTRA_STATIONMAP "stationmapformatted"
+#define HOLOMAP_EXTRA_STATIONMAP_STRATEGIC "stationmapstrategic"
+#define HOLOMAP_EXTRA_STATIONMAPAREAS "stationareas"
+#define HOLOMAP_EXTRA_STATIONMAPSMALL_NORTH "stationmapsmallnorth"
+#define HOLOMAP_EXTRA_STATIONMAPSMALL_SOUTH "stationmapsmallsouth"
+#define HOLOMAP_EXTRA_STATIONMAPSMALL_EAST "stationmapsmalleast"
+#define HOLOMAP_EXTRA_STATIONMAPSMALL_WEST "stationmapsmallwest"
+#define HOLOMAP_EXTRA_CULTMAP "cultmap"
+
+#define HOLOMAP_MARKER_SMES "smes"
+#define HOLOMAP_MARKER_DISK "diskspawn"
+#define HOLOMAP_MARKER_SKIPJACK "skipjack"
+#define HOLOMAP_MARKER_SYNDISHUTTLE "syndishuttle"
+#define HOLOMAP_MARKER_BLOODSTONE "bloodstone"
+#define HOLOMAP_MARKER_BLOODSTONE_BROKEN "bloodstone-broken"
+#define HOLOMAP_MARKER_BLOODSTONE_ANCHOR "bloodstone-narsie"
+#define HOLOMAP_MARKER_CULT_ALTAR "altar"
+#define HOLOMAP_MARKER_CULT_FORGE "forge"
+#define HOLOMAP_MARKER_CULT_SPIRE "spire"
+#define HOLOMAP_MARKER_CULT_ENTRANCE "path_entrance"
+#define HOLOMAP_MARKER_CULT_EXIT "path_exit"
+#define HOLOMAP_MARKER_CULT_RUNE "rune"
+
+#define HOLOMAP_DRAW_NORMAL 0
+#define HOLOMAP_DRAW_FULL 1
+#define HOLOMAP_DRAW_EMPTY 2
+#define HOLOMAP_DRAW_PATH 3
+#define HOLOMAP_DRAW_HALLWAY 4
+
// Holomap colors
#define HOLOMAP_OBSTACLE "#FFFFFFDD" // Color of walls and barriers
#define HOLOMAP_PATH "#66666699" // Color of floors
diff --git a/code/__defines/map.dm b/code/__defines/map.dm
index 0638ee77ef..a3a27dab9e 100644
--- a/code/__defines/map.dm
+++ b/code/__defines/map.dm
@@ -8,6 +8,7 @@
#define MAP_LEVEL_CONSOLES 0x040 // Z-levels available to various consoles, such as the crew monitor (when that gets coded in). Defaults to station_levels if unset.
#define MAP_LEVEL_XENOARCH_EXEMPT 0x080 // Z-levels exempt from xenoarch digsite generation.
#define MAP_LEVEL_PERSIST 0x100 // Z-levels where SSpersistence should persist between rounds
+#define MAP_LEVEL_MAPPABLE 0x200 // Z-levels where mapping units will work fully
// Misc map defines.
#define SUBMAP_MAP_EDGE_PAD 15 // Automatically created submaps are forbidden from being this close to the main map's edge.
\ No newline at end of file
diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index f293a54444..358db89db4 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -35,6 +35,11 @@ GLOBAL_LIST_EMPTY(respawn_timers)
var/global/list/poster_designs = list()
var/global/list/NT_poster_designs = list()
+// Holomaps
+var/global/list/holomap_markers = list()
+var/global/list/mapping_units = list()
+var/global/list/mapping_beacons = list()
+
//Preferences stuff
//Hairstyles
var/global/list/hair_styles_list = list() //stores /datum/sprite_accessory/hair indexed by name
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 4bd324d7cf..56d79f5836 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -87,12 +87,6 @@ var/list/global_huds = list(
science = setup_overlay("science_hud")
material = setup_overlay("material_hud")
- // The holomap screen object is actually totally invisible.
- // Station maps work by setting it as an images location before sending to client, not
- // actually changing the icon or icon state of the screen object itself!
- // Why do they work this way? I don't know really, that is how /vg designed them, but since they DO
- // work this way, we can take advantage of their immutability by making them part of
- // the global_hud (something we have and /vg doesn't) instead of an instance per mob.
holomap = new /obj/screen()
holomap.name = "holomap"
holomap.icon = null
@@ -188,7 +182,7 @@ var/list/global_huds = list(
var/icon/ui_style
var/ui_color
var/ui_alpha
-
+
var/list/minihuds = list()
datum/hud/New(mob/owner)
@@ -198,6 +192,7 @@ datum/hud/New(mob/owner)
/datum/hud/Destroy()
. = ..()
+ qdel_null(minihuds)
grab_intent = null
hurt_intent = null
disarm_intent = null
@@ -216,7 +211,6 @@ datum/hud/New(mob/owner)
hotkeybuttons = null
// item_action_list = null // ?
mymob = null
- qdel_null(minihuds)
/datum/hud/proc/hidden_inventory_update()
if(!mymob) return
@@ -307,7 +301,7 @@ datum/hud/New(mob/owner)
/datum/hud/proc/instantiate()
if(!ismob(mymob))
return 0
-
+
mymob.create_mob_hud(src)
persistant_inventory_update()
diff --git a/code/_onclick/hud/minihud.dm b/code/_onclick/hud/minihud.dm
new file mode 100644
index 0000000000..1b8cd69caf
--- /dev/null
+++ b/code/_onclick/hud/minihud.dm
@@ -0,0 +1,36 @@
+/datum/mini_hud
+ var/datum/hud/main_hud
+ var/list/screenobjs
+ var/needs_processing = FALSE
+
+/datum/mini_hud/New(var/datum/hud/other)
+ apply_to_hud(other)
+ if(needs_processing)
+ START_PROCESSING(SSprocessing, src)
+
+/datum/mini_hud/Destroy()
+ unapply_to_hud()
+ if(needs_processing)
+ STOP_PROCESSING(SSprocessing, src)
+ QDEL_LIST_NULL(screenobjs)
+ return ..()
+
+// Apply to a real /datum/hud
+/datum/mini_hud/proc/apply_to_hud(var/datum/hud/other)
+ if(main_hud)
+ unapply_to_hud(main_hud)
+ main_hud = other
+ main_hud.apply_minihud(src)
+
+// Remove from a real /datum/hud
+/datum/mini_hud/proc/unapply_to_hud()
+ main_hud?.remove_minihud(src)
+ main_hud = null
+
+// Update the hud
+/datum/mini_hud/process()
+ return PROCESS_KILL // You shouldn't be here!
+
+// Return a list of screen objects we use
+/datum/mini_hud/proc/get_screen_objs(var/mob/M)
+ return screenobjs.Copy()
diff --git a/code/_onclick/hud/minihud_mapper.dm b/code/_onclick/hud/minihud_mapper.dm
new file mode 100644
index 0000000000..1b9246d729
--- /dev/null
+++ b/code/_onclick/hud/minihud_mapper.dm
@@ -0,0 +1,13 @@
+// Specific types
+/datum/mini_hud/mapper
+ var/obj/item/device/mapping_unit/owner
+
+/datum/mini_hud/mapper/New(var/datum/hud/other, owner)
+ src.owner = owner
+ screenobjs = list(new /obj/screen/movable/mapper_holder(null, owner))
+ ..()
+
+/datum/mini_hud/mapper/Destroy()
+ owner?.hud_item = null
+ owner?.hud_datum = null
+ return ..()
\ No newline at end of file
diff --git a/code/_onclick/hud/rigmech.dm b/code/_onclick/hud/minihud_rigmech.dm
similarity index 86%
rename from code/_onclick/hud/rigmech.dm
rename to code/_onclick/hud/minihud_rigmech.dm
index 91632e1604..e54a733f80 100644
--- a/code/_onclick/hud/rigmech.dm
+++ b/code/_onclick/hud/minihud_rigmech.dm
@@ -1,40 +1,3 @@
-/datum/mini_hud
- var/datum/hud/main_hud
- var/list/screenobjs = list()
- var/list/types_to_instantiate
- var/needs_processing = FALSE
-
-/datum/mini_hud/New(var/datum/hud/other)
- apply_to_hud(other)
- if(needs_processing)
- START_PROCESSING(SSprocessing, src)
-
-/datum/mini_hud/Destroy()
- main_hud?.remove_minihud(src)
- main_hud = null
- if(needs_processing)
- STOP_PROCESSING(SSprocessing, src)
- return ..()
-
-// Apply to a real /datum/hud
-/datum/mini_hud/proc/apply_to_hud(var/datum/hud/other)
- if(main_hud)
- unapply_to_hud(main_hud)
- main_hud = other
- main_hud.apply_minihud(src)
-
-// Remove from a real /datum/hud
-/datum/mini_hud/proc/unapply_to_hud(var/datum/hud/other)
- main_hud.remove_minihud(src)
-
-// Update the hud
-/datum/mini_hud/process()
- return PROCESS_KILL // You shouldn't be here!
-
-// Return a list of screen objects we use
-/datum/mini_hud/proc/get_screen_objs(var/mob/M)
- return screenobjs
-
// Specific types
/datum/mini_hud/rig
var/obj/item/weapon/rig/owner_rig
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 6a42572c2f..9d6da07f0b 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -655,3 +655,218 @@
holder.screen -= src
holder = null
return ..()
+
+
+/**
+ * This object holds all the on-screen elements of the mapping unit.
+ * It has a decorative frame and onscreen buttons. The map itself is drawn
+ * using a white mask and multiplying the mask against it to crop it to the
+ * size of the screen. This is not ideal, as filter() is faster, and has
+ * alpha masks, but the alpha masks it has can't be animated, so the 'ping'
+ * mode of this device isn't possible using that technique.
+ *
+ * The markers use that technique, though, so at least there's that.
+ */
+/obj/screen/movable/mapper_holder
+ name = "gps unit"
+ icon = null
+ icon_state = ""
+ screen_loc = "CENTER,CENTER"
+ alpha = 255
+ appearance_flags = KEEP_TOGETHER
+ mouse_opacity = 1
+ plane = PLANE_HOLOMAP
+
+ var/running = FALSE
+
+ var/obj/screen/mapper/mask_full/mask_full
+ var/obj/screen/mapper/mask_ping/mask_ping
+ var/obj/screen/mapper/bg/bg
+
+ var/obj/screen/mapper/frame/frame
+ var/obj/screen/mapper/powbutton/powbutton
+ var/obj/screen/mapper/mapbutton/mapbutton
+
+ var/obj/item/device/mapping_unit/owner
+ var/obj/screen/mapper/extras_holder/extras_holder
+
+/obj/screen/movable/mapper_holder/Initialize(mapload, newowner)
+ owner = newowner
+
+ mask_full = new(src) // Full white square mask
+ mask_ping = new(src) // Animated 'pinging' mask
+ bg = new(src) // Background color, holds map in vis_contents, uses mult against masks
+
+ frame = new(src) // Decorative frame
+ powbutton = new(src) // Clickable button
+ mapbutton = new(src) // Clickable button
+
+ frame.icon_state = initial(frame.icon_state)+owner.hud_frame_hint
+
+ /**
+ * The vis_contents layout is: this(frame,extras_holder,mask(bg(map)))
+ * bg is set to BLEND_MULTIPLY against the mask to crop it.
+ */
+
+ mask_full.vis_contents.Add(bg)
+ mask_ping.vis_contents.Add(bg)
+ frame.vis_contents.Add(powbutton,mapbutton)
+ vis_contents.Add(frame)
+
+
+/obj/screen/movable/mapper_holder/Destroy()
+ qdel_null(mask_full)
+ qdel_null(mask_ping)
+ qdel_null(bg)
+
+ qdel_null(frame)
+ qdel_null(powbutton)
+ qdel_null(mapbutton)
+
+ extras_holder = null
+ owner = null
+ return ..()
+
+/obj/screen/movable/mapper_holder/proc/update(var/obj/screen/mapper/map, var/obj/screen/mapper/extras_holder/extras, ping = FALSE)
+ if(!running)
+ running = TRUE
+ if(ping)
+ vis_contents.Add(mask_ping)
+ else
+ vis_contents.Add(mask_full)
+
+ bg.vis_contents.Cut()
+ bg.vis_contents.Add(map)
+
+ if(extras && !extras_holder)
+ extras_holder = extras
+ vis_contents += extras_holder
+ if(!extras && extras_holder)
+ vis_contents -= extras_holder
+ extras_holder = null
+
+/obj/screen/movable/mapper_holder/proc/powerClick()
+ if(running)
+ off()
+ else
+ on()
+
+/obj/screen/movable/mapper_holder/proc/mapClick()
+ if(owner)
+ if(running)
+ off()
+ owner.pinging = !owner.pinging
+ on()
+
+/obj/screen/movable/mapper_holder/proc/off(var/inform = TRUE)
+ frame.cut_overlay("powlight")
+ bg.vis_contents.Cut()
+ vis_contents.Remove(mask_ping, mask_full, extras_holder)
+ extras_holder = null
+ running = FALSE
+ if(inform)
+ owner.stop_updates()
+
+/obj/screen/movable/mapper_holder/proc/on(var/inform = TRUE)
+ frame.add_overlay("powlight")
+ if(inform)
+ owner.start_updates()
+
+// Prototype
+/obj/screen/mapper
+ plane = PLANE_HOLOMAP
+ mouse_opacity = 0
+ var/obj/screen/movable/mapper_holder/parent
+
+/obj/screen/mapper/New()
+ ..()
+ parent = loc
+
+/obj/screen/mapper/Destroy()
+ parent = null
+ return ..()
+
+// Holds the actual map image
+/obj/screen/mapper/map
+ var/offset_x = 32
+ var/offset_y = 32
+
+// I really wish I could use filters for this instead of this multiplication-masking technique
+// but alpha filters can't be animated, which means I can't use them for the 'sonar ping' mode.
+// If filters start supporting animated icons in the future (for the alpha mask filter),
+// you should definitely replace these with that technique instead.
+/obj/screen/mapper/mask_full
+ icon = 'icons/effects/64x64.dmi'
+ icon_state = "mapper_mask"
+
+/obj/screen/mapper/mask_ping
+ icon = 'icons/effects/64x64.dmi'
+ icon_state = "mapper_ping"
+
+/obj/screen/mapper/bg
+ icon = 'icons/effects/64x64.dmi'
+ icon_state = "mapper_bg"
+
+ blend_mode = BLEND_MULTIPLY
+ appearance_flags = KEEP_TOGETHER
+
+// Frame/deco components
+/obj/screen/mapper/frame
+ icon = 'icons/effects/gpshud.dmi'
+ icon_state = "frame"
+ plane = PLANE_HOLOMAP_FRAME
+ pixel_x = -18
+ pixel_y = -29
+ mouse_opacity = 1
+ vis_flags = VIS_INHERIT_ID
+
+/obj/screen/mapper/powbutton
+ icon = 'icons/effects/gpshud.dmi'
+ icon_state = "powbutton"
+ plane = PLANE_HOLOMAP_FRAME
+ mouse_opacity = 1
+
+/obj/screen/mapper/powbutton/Click()
+ if(!usr.checkClickCooldown())
+ return TRUE
+ if(usr.stat || usr.paralysis || usr.stunned || usr.weakened)
+ return TRUE
+ if(istype(usr.loc,/obj/mecha)) // stops inventory actions in a mech
+ return TRUE
+ parent.powerClick()
+ flick("powClick",src)
+ usr << get_sfx("button")
+ return TRUE
+
+/obj/screen/mapper/mapbutton
+ icon = 'icons/effects/gpshud.dmi'
+ icon_state = "mapbutton"
+ plane = PLANE_HOLOMAP_FRAME
+ mouse_opacity = 1
+
+/obj/screen/mapper/mapbutton/Click()
+ if(!usr.checkClickCooldown())
+ return TRUE
+ if(usr.stat || usr.paralysis || usr.stunned || usr.weakened)
+ return TRUE
+ if(istype(usr.loc,/obj/mecha)) // stops inventory actions in a mech
+ return TRUE
+ parent.mapClick()
+ flick("mapClick",src)
+ usr << get_sfx("button")
+ return TRUE
+
+// Markers are 16x16, people have apparently settled on centering them on the 8,8 pixel
+/obj/screen/mapper/marker
+ icon = 'icons/holomap_markers.dmi'
+ plane = PLANE_HOLOMAP_ICONS
+
+ var/offset_x = -8
+ var/offset_y = -8
+
+// Holds markers in its vis_contents. It uses an alpha filter to crop them to the HUD screen size
+/obj/screen/mapper/extras_holder
+ icon = null
+ icon_state = null
+ plane = PLANE_HOLOMAP_ICONS
+ appearance_flags = KEEP_TOGETHER
diff --git a/code/modules/holomap/generate_holomap.dm b/code/modules/holomap/generate_holomap.dm
index e4917e0ba0..9978416424 100644
--- a/code/modules/holomap/generate_holomap.dm
+++ b/code/modules/holomap/generate_holomap.dm
@@ -48,27 +48,23 @@
// Generates the "base" holomap for one z-level, showing only the physical structure of walls and paths.
/datum/controller/subsystem/holomaps/proc/generateHoloMinimap(var/zLevel = 1)
- // Save these values now to avoid a bazillion array lookups
- var/offset_x = HOLOMAP_PIXEL_OFFSET_X(zLevel)
- var/offset_y = HOLOMAP_PIXEL_OFFSET_Y(zLevel)
-
// Sanity checks - Better to generate a helpful error message now than have DrawBox() runtime
var/icon/canvas = icon(HOLOMAP_ICON, "blank")
- if(world.maxx + offset_x > canvas.Width())
- crash_with("Minimap for z=[zLevel] : world.maxx ([world.maxx]) + holomap_offset_x ([offset_x]) must be <= [canvas.Width()]")
- if(world.maxy + offset_y > canvas.Height())
- crash_with("Minimap for z=[zLevel] : world.maxy ([world.maxy]) + holomap_offset_y ([offset_y]) must be <= [canvas.Height()]")
+ if(world.maxx > canvas.Width())
+ crash_with("Minimap for z=[zLevel] : world.maxx ([world.maxx]) must be <= [canvas.Width()]")
+ if(world.maxy > canvas.Height())
+ crash_with("Minimap for z=[zLevel] : world.maxy ([world.maxy]) must be <= [canvas.Height()]")
for(var/x = 1 to world.maxx)
for(var/y = 1 to world.maxy)
var/turf/tile = locate(x, y, zLevel)
if(tile && tile.loc:holomapAlwaysDraw())
if(IS_ROCK(tile))
- canvas.DrawBox(HOLOMAP_ROCK, x + offset_x, y + offset_y)
+ canvas.DrawBox(HOLOMAP_ROCK, x, y)
if(IS_OBSTACLE(tile))
- canvas.DrawBox(HOLOMAP_OBSTACLE, x + offset_x, y + offset_y)
+ canvas.DrawBox(HOLOMAP_OBSTACLE, x, y)
else if(IS_PATH(tile))
- canvas.DrawBox(HOLOMAP_PATH, x + offset_x, y + offset_y)
+ canvas.DrawBox(HOLOMAP_PATH, x, y)
// Check sleeping after each row to avoid *completely* destroying the server
CHECK_TICK
return canvas
@@ -78,16 +74,12 @@
// Leshana: I'm guessing this map will get overlayed on top of the base map at runtime? We'll see.
// Wait, seems we actually blend the area map on top of it right now! Huh.
/datum/controller/subsystem/holomaps/proc/generateStationMinimap(var/zLevel)
- // Save these values now to avoid a bazillion array lookups
- var/offset_x = HOLOMAP_PIXEL_OFFSET_X(zLevel)
- var/offset_y = HOLOMAP_PIXEL_OFFSET_Y(zLevel)
-
// Sanity checks - Better to generate a helpful error message now than have DrawBox() runtime
var/icon/canvas = icon(HOLOMAP_ICON, "blank")
- if(world.maxx + offset_x > canvas.Width())
- crash_with("Minimap for z=[zLevel] : world.maxx ([world.maxx]) + holomap_offset_x ([offset_x]) must be <= [canvas.Width()]")
- if(world.maxy + offset_y > canvas.Height())
- crash_with("Minimap for z=[zLevel] : world.maxy ([world.maxy]) + holomap_offset_y ([offset_y]) must be <= [canvas.Height()]")
+ if(world.maxx > canvas.Width())
+ crash_with("Minimap for z=[zLevel] : world.maxx ([world.maxx]) must be <= [canvas.Width()]")
+ if(world.maxy > canvas.Height())
+ crash_with("Minimap for z=[zLevel] : world.maxy ([world.maxy]) must be <= [canvas.Height()]")
for(var/x = 1 to world.maxx)
for(var/y = 1 to world.maxy)
@@ -95,7 +87,7 @@
if(tile && tile.loc)
var/area/areaToPaint = tile.loc
if(areaToPaint.holomap_color)
- canvas.DrawBox(areaToPaint.holomap_color, x + offset_x, y + offset_y)
+ canvas.DrawBox(areaToPaint.holomap_color, x, y)
// Save this nice area-colored canvas in case we want to layer it or something I guess
extraMiniMaps["[HOLOMAP_EXTRA_STATIONMAPAREAS]_[zLevel]"] = canvas
@@ -129,13 +121,17 @@
var/icon/small_map = icon(HOLOMAP_ICON, "blank")
// For each zlevel in turn, overlay them on top of each other
for(var/zLevel in zlevels)
+ var/offset_x = HOLOMAP_PIXEL_OFFSET_X(zLevel) || 1
+ var/offset_y = HOLOMAP_PIXEL_OFFSET_Y(zLevel) || 1
+
var/icon/z_terrain = icon(holoMiniMaps[zLevel])
- z_terrain.Blend(HOLOMAP_HOLOFIER, ICON_MULTIPLY)
- big_map.Blend(z_terrain, ICON_OVERLAY)
- small_map.Blend(z_terrain, ICON_OVERLAY)
+ z_terrain.Blend(HOLOMAP_HOLOFIER, ICON_MULTIPLY, offset_x, offset_y)
+ big_map.Blend(z_terrain, ICON_OVERLAY, offset_x, offset_y)
+ small_map.Blend(z_terrain, ICON_OVERLAY, offset_x, offset_y)
+
var/icon/z_areas = extraMiniMaps["[HOLOMAP_EXTRA_STATIONMAPAREAS]_[zLevel]"]
- big_map.Blend(z_areas, ICON_OVERLAY)
- small_map.Blend(z_areas, ICON_OVERLAY)
+ big_map.Blend(z_areas, ICON_OVERLAY, offset_x, offset_y)
+ small_map.Blend(z_areas, ICON_OVERLAY, offset_x, offset_y)
// Then scale and rotate to make the actual small map we will use
small_map.Scale(WORLD_ICON_SIZE, WORLD_ICON_SIZE)
diff --git a/code/modules/holomap/mapper.dm b/code/modules/holomap/mapper.dm
new file mode 100644
index 0000000000..a068c752eb
--- /dev/null
+++ b/code/modules/holomap/mapper.dm
@@ -0,0 +1,444 @@
+/obj/item/device/mapping_unit
+ name = "mapping unit"
+ desc = "A portable mapping unit, capable of locating other similar units on a map. Also has a short-range sonar mapping system."
+ icon_state = "mapping_unit"
+ item_state = null
+ w_class = ITEMSIZE_SMALL
+
+ //Holomap stuff
+ var/marker_prefix = "basic"
+ var/base_prefix = "basic"
+ var/map_color = null
+ var/mapper_filter = HOLOMAP_FILTER_STATIONMAP
+
+ var/list/prefix_update_head
+ var/list/prefix_update_rig
+
+ // These are local because they are different for every holochip.
+ // The maps and icons are all pixel_x and pixel_y'd so we're in the center.
+ var/list/map_image_cache = list()
+ var/list/icon_image_cache = list()
+
+ var/pinging = FALSE
+ var/updating = FALSE
+ var/global/icon/mask_icon
+ var/obj/screen/mapper/extras_holder/extras_holder
+ var/hud_frame_hint
+
+ var/datum/mini_hud/mapper/hud_datum
+ var/obj/screen/movable/mapper_holder/hud_item
+
+ var/obj/item/weapon/cell/cell
+ var/cell_type = /obj/item/weapon/cell/device
+ var/power_usage = 0.5 // Usage per map scan (doubled for ping mode)
+ var/uses_power = 1 // If it uses power at all
+
+ var/list/debug_mappers_list
+ var/list/debug_beacons_list
+
+/obj/item/device/mapping_unit/deathsquad
+ name = "deathsquad mapping unit"
+ icon_state = "mapping_unit_ds"
+ marker_prefix = "ds"
+ mapper_filter = HOLOMAP_FILTER_DEATHSQUAD
+ //map_color = "#0B74B4"
+ hud_frame_hint = "_ds"
+
+/obj/item/device/mapping_unit/operative
+ name = "nuclear operative mapping unit"
+ icon_state = "mapping_unit_op"
+ marker_prefix = "op"
+ mapper_filter = HOLOMAP_FILTER_NUKEOPS
+ //map_color = "#13B40B"
+ hud_frame_hint = "_op"
+
+/obj/item/device/mapping_unit/ert
+ name = "emergency response team mapping unit"
+ icon_state = "mapping_unit_ert"
+ marker_prefix = "ert"
+ mapper_filter = HOLOMAP_FILTER_ERT
+ //map_color = "#5FFF28"
+ hud_frame_hint = "_ert"
+
+ prefix_update_head = list(
+ "/obj/item/clothing/head/helmet/ert/command" = "ertc",
+ "/obj/item/clothing/head/helmet/ert/security" = "erts",
+ "/obj/item/clothing/head/helmet/ert/engineer" = "erte",
+ "/obj/item/clothing/head/helmet/ert/medical" = "ertm",
+ )
+
+ prefix_update_rig = list(
+ "/obj/item/weapon/rig/ert" = "ertc",
+ "/obj/item/weapon/rig/ert/security" = "erts",
+ "/obj/item/weapon/rig/ert/engineer" = "erte",
+ "/obj/item/weapon/rig/ert/medical" = "ertm"
+ )
+
+/obj/item/device/mapping_unit/Initialize()
+ . = ..()
+ base_prefix = marker_prefix
+
+ if(!mask_icon)
+ mask_icon = icon('icons/effects/64x64.dmi', "mapper_mask")
+
+ extras_holder = new()
+
+ var/obj/screen/mapper/marker/mark = new()
+ mark.icon = 'icons/effects/64x64.dmi'
+ mark.icon_state = "mapper_none"
+ mark.layer = 10
+ icon_image_cache["bad"] = mark
+
+ var/obj/screen/mapper/map/tmp = new()
+ var/icon/canvas = icon(HOLOMAP_ICON, "blank")
+ canvas.Crop(1,1,world.maxx,world.maxy)
+ canvas.DrawBox("#A7BE97",1,1,world.maxx,world.maxy)
+ tmp.icon = icon
+ map_image_cache["bad"] = tmp
+
+ if(uses_power && cell_type)
+ cell = new cell_type(src)
+
+ debug_mappers_list = mapping_units
+ debug_beacons_list = mapping_beacons
+
+/obj/item/device/mapping_unit/Destroy()
+ mapping_units -= src
+
+ last_run()
+
+ map_image_cache.Cut()
+ icon_image_cache.Cut()
+ qdel_null(extras_holder)
+
+ return ..()
+
+/obj/item/device/mapping_unit/dropped(mob/dropper)
+ if(loc != dropper) // Not just a juggle
+ hide_device()
+
+/obj/item/device/mapping_unit/attack_self(mob/user)
+ if(user.stat != CONSCIOUS)
+ return
+
+ if(!ishuman(user))
+ to_chat(user, "Only humanoids can use this device.")
+ return
+
+ var/mob/living/carbon/human/H = user
+
+ if(!ishuman(loc) || user != loc)
+ to_chat(H, "This device needs to be on your person.")
+
+ if(hud_datum?.main_hud)
+ hide_device()
+ to_chat(H, "You put \the [src] away.")
+ else
+ show_device(H)
+ to_chat(H, "You hold \the [src] where you can see it.")
+
+/obj/item/device/mapping_unit/attack_hand(mob/user)
+ if(cell && user.get_inactive_hand() == src) // click with empty off hand
+ to_chat(user,"You eject \the [cell] from \the [src].")
+ user.put_in_hands(cell)
+ cell = null
+ if(updating)
+ stop_updates()
+ else
+ return ..()
+
+/obj/item/device/mapping_unit/attackby(obj/W, mob/user)
+ if(istype(W,cell_type) && !cell)
+ cell = W
+ cell.update_icon() //Why doesn't a cell do this already? :|
+ user.unEquip(cell)
+ cell.forceMove(src)
+ to_chat(user,"You insert \the [cell] into \the [src].")
+
+
+/obj/item/device/mapping_unit/proc/first_run(mob/user)
+ hud_datum = new(user.hud_used, src)
+ hud_item = hud_datum.screenobjs[1]
+
+/obj/item/device/mapping_unit/proc/show_device(mob/user)
+ if(!hud_datum)
+ first_run(user)
+ else
+ hud_datum.apply_to_hud(user.hud_used)
+
+/obj/item/device/mapping_unit/proc/start_updates()
+ mapping_units += src
+ updating = TRUE
+ START_PROCESSING(SSobj, src)
+ process()
+
+
+
+/obj/item/device/mapping_unit/proc/stop_updates()
+ mapping_units -= src
+ STOP_PROCESSING(SSobj, src)
+ updating = FALSE
+ if(hud_item)
+ hud_item.off(FALSE)
+
+/obj/item/device/mapping_unit/proc/hide_device()
+ hud_datum.unapply_to_hud()
+
+/obj/item/device/mapping_unit/proc/last_run()
+ stop_updates()
+ if(!QDELETED(hud_datum))
+ qdel(hud_datum)
+ hud_datum = null
+ hud_item = null
+
+
+/obj/item/device/mapping_unit/process()
+ if(!updating || (uses_power && !cell))
+ stop_updates()
+ return
+
+ if(uses_power)
+ var/power_to_use = pinging ? power_usage*2 : power_usage
+ if(cell.use(power_to_use) != power_to_use) // we weren't able to use our full power_usage amount!
+ visible_message("\The [src] flickers before going dull.")
+ stop_updates()
+ return
+
+ if(!hud_item || !hud_datum)
+ log_error("Mapping device tried to update with missing hud_item or hud_datum")
+ stop_updates()
+ last_run()
+ return
+
+ update_holomap()
+
+#define HOLOMAP_ERROR 0
+#define HOLOMAP_YOU 1
+#define HOLOMAP_OTHER 2
+#define HOLOMAP_DEAD 3
+
+/obj/item/device/mapping_unit/proc/update_holomap()
+ var/turf/T = get_turf(src)
+ if(!T)//nullspace begone!
+ stop_updates()
+ return
+
+ var/T_x = T.x // Used many times, just grab it to avoid derefs
+ var/T_y = T.y
+ var/T_z = T.z
+
+ var/obj/screen/mapper/map/bgmap
+ var/list/extras = list()
+
+ var/map_cache_key = "[T_z]"
+ var/badmap = FALSE
+ if(!pinging && using_map && !(T_z in using_map.mappable_levels))
+ map_cache_key = "bad"
+ badmap = TRUE
+
+ // Cache miss
+ if(!(map_cache_key in map_image_cache))
+ var/mutable_appearance/map_app = new()
+ map_app.appearance_flags = PIXEL_SCALE
+ map_app.plane = PLANE_HOLOMAP
+ map_app.layer = HUD_LAYER
+ map_app.color = map_color
+
+ if(!SSholomaps.holoMiniMaps[T_z])
+ var/obj/screen/mapper/map/baddo = map_image_cache["bad"]
+ map_app.icon = icon(baddo.icon)
+ badmap = TRUE
+ // SSholomaps did map it and we're allowed to see it
+ else
+ map_app.icon = icon(SSholomaps.holoMiniMaps[T.z])
+
+ // Apply markers
+ for(var/marker in holomap_markers)
+ var/datum/holomap_marker/holomarker = holomap_markers[marker]
+ if(holomarker.z == T_z && holomarker.filter & mapper_filter)
+ var/image/markerImage = image(holomarker.icon,holomarker.id)
+ markerImage.plane = FLOAT_PLANE
+ markerImage.layer = FLOAT_LAYER
+ markerImage.appearance_flags = RESET_COLOR|PIXEL_SCALE
+ markerImage.pixel_x = holomarker.x+holomarker.offset_x
+ markerImage.pixel_y = holomarker.y+holomarker.offset_y
+ map_app.overlays += markerImage
+
+ var/obj/screen/mapper/map/tmp = new()
+ tmp.appearance = map_app
+ map_image_cache[map_cache_key] = tmp
+
+ bgmap = map_image_cache[map_cache_key]
+
+ // The holomap moves around, the user is always in the center. This slides the holomap.
+ var/offset_x = bgmap.offset_x
+ var/offset_y = bgmap.offset_y
+ extras_holder.pixel_x = bgmap.pixel_x = -1*T_x + offset_x
+ extras_holder.pixel_y = bgmap.pixel_y = -1*T_y + offset_y
+
+ // Populate other mapper icons
+ for(var/hc in mapping_units)
+ var/obj/item/device/mapping_unit/HC = hc
+ if(HC.mapper_filter != mapper_filter)
+ continue
+ var/mob_indicator = HOLOMAP_ERROR
+ var/turf/TU = get_turf(HC)
+ // Mapper not on a turf or elsewhere
+ if(!TU || (TU.z != T_z))
+ continue
+
+ // We're the marker
+ if(HC == src)
+ mob_indicator = HOLOMAP_YOU
+
+ // The marker is held by a borg
+ else if(isrobot(HC.loc))
+ var/mob/living/silicon/robot/R = HC.loc
+ if(R.stat == DEAD)
+ mob_indicator = HOLOMAP_DEAD
+ else
+ mob_indicator = HOLOMAP_OTHER
+
+ // The marker is worn by a human
+ else if(ishuman(loc))
+ var/mob/living/carbon/human/H = loc
+ if(H.stat == DEAD)
+ mob_indicator = HOLOMAP_DEAD
+ else
+ mob_indicator = HOLOMAP_OTHER
+
+ // It's not attached to anything useful
+ else
+ mob_indicator = HOLOMAP_DEAD
+
+ // Ask it to update it's icon based on helmet (or whatever)
+ HC.update_marker()
+
+ // Generate the icon and apply it to the list of images to show the client
+ if(mob_indicator != HOLOMAP_ERROR)
+
+ // This is so specific because the icons are pixel_x and pixel_y only relative to OUR view of where THEY are relative to us
+ var/marker_cache_key = "\ref[HC]_[HC.marker_prefix]_[mob_indicator]"
+
+ if(!(marker_cache_key in icon_image_cache))
+ var/obj/screen/mapper/marker/mark = new()
+ mark.icon_state = "[HC.marker_prefix][mob_indicator]"
+ icon_image_cache[marker_cache_key] = mark
+ switch(mob_indicator)
+ if(HOLOMAP_YOU)
+ mark.layer = 3 // Above the other markers
+ if(HOLOMAP_DEAD)
+ mark.layer = 2
+ else
+ mark.layer = 1
+
+ var/obj/screen/mapper/marker/mark = icon_image_cache[marker_cache_key]
+ handle_marker(mark,TU.x,TU.y)
+ extras += mark
+
+ // Marker beacon items
+ for(var/hb in mapping_beacons)
+ var/obj/item/device/holomap_beacon/HB = hb
+ if(HB.mapper_filter != mapper_filter)
+ continue
+
+ var/turf/TB = get_turf(HB)
+ // Marker beacon not on a turf or elsewhere
+ if(!TB || (TB.z != T_z))
+ continue
+
+ var/marker_cache_key = "\ref[HB]_marker"
+ if(!(marker_cache_key in icon_image_cache))
+ var/obj/screen/mapper/marker/mark = new()
+ mark.icon_state = "beacon"
+ mark.layer = 1
+ icon_image_cache[marker_cache_key] = mark
+
+ var/obj/screen/mapper/marker/mark = icon_image_cache[marker_cache_key]
+ handle_marker(mark,TB.x,TB.y)
+ extras += mark
+
+ if(badmap)
+ var/obj/O = icon_image_cache["bad"]
+ O.pixel_x = T_x - offset_x
+ O.pixel_y = T_y - offset_y
+ extras += O
+
+ extras_holder.filters = filter(type = "alpha", icon = mask_icon, x = T_x-(offset_x*0.5), y = T_y-(offset_y*0.5))
+ extras_holder.vis_contents = extras
+
+ hud_item.update(bgmap, extras_holder, badmap ? FALSE : pinging)
+
+/obj/item/device/mapping_unit/proc/update_marker()
+ marker_prefix = base_prefix
+ if (prefix_update_head)
+ if(ishuman(loc))
+ var/mob/living/carbon/human/H = loc
+ var/obj/item/helmet = H.get_equipped_item(slot_head)
+ if(helmet && ("[helmet.type]" in prefix_update_head))
+ marker_prefix = prefix_update_head["[helmet.type]"]
+ return
+ if (prefix_update_rig)
+ if(ishuman(loc))
+ var/mob/living/carbon/human/H = loc
+ var/obj/item/weapon/rig = H.get_rig()
+ if(rig && ("[rig.type]" in prefix_update_rig))
+ marker_prefix = prefix_update_rig["[rig.type]"]
+ return
+
+/obj/item/device/mapping_unit/proc/handle_marker(var/atom/movable/marker,var/TU_x,var/TU_y)
+ marker.pixel_x = TU_x - 8 //16x16 icons, so to center.
+ marker.pixel_y = TU_y - 8
+ animate(marker, alpha = 0, time = 5, easing = SINE_EASING)
+ animate(alpha = 255, time = 2, easing = SINE_EASING)
+
+/obj/item/device/holomap_beacon
+ name = "holomap beacon"
+ desc = "When active, the beacon will show itself on mapping units of the same type."
+ icon_state = "holochip"
+ w_class = ITEMSIZE_TINY
+ var/mapper_filter = HOLOMAP_FILTER_STATIONMAP
+ var/in_list = FALSE
+
+/obj/item/device/holomap_beacon/Initialize()
+ . = ..()
+ if(in_list) // mapped in turned on
+ in_list = TRUE
+ mapping_beacons += src
+ icon_state = initial(icon_state) + in_list ? "_on" : ""
+
+/obj/item/device/holomap_beacon/attack_self(mob/user)
+ if(!in_list)
+ in_list = TRUE
+ mapping_beacons += src
+ else
+ in_list = FALSE
+ mapping_beacons -= src
+ icon_state = "[initial(icon_state)][in_list ? "_on" : ""]"
+ to_chat(user,SPAN_NOTICE("The [src] is now [in_list ? "broadcasting" : "disabled"]."))
+
+/obj/item/device/holomap_beacon/Destroy()
+ if(in_list)
+ mapping_beacons -= src
+ return ..()
+
+/obj/item/device/holomap_beacon/deathsquad
+ name = "deathsquad holomap beacon"
+ icon_state = "holochip_ds"
+ mapper_filter = HOLOMAP_FILTER_DEATHSQUAD
+
+/obj/item/device/holomap_beacon/operative
+ name = "operative holomap beacon"
+ icon_state = "holochip_op"
+ mapper_filter = HOLOMAP_FILTER_NUKEOPS
+
+/obj/item/device/holomap_beacon/ert
+ name = "ert holomap beacon"
+ icon_state = "holochip_ert"
+ mapper_filter = HOLOMAP_FILTER_ERT
+
+
+#undef HOLOMAP_ERROR
+#undef HOLOMAP_YOU
+#undef HOLOMAP_OTHER
+#undef HOLOMAP_DEAD
diff --git a/code/modules/holomap/station_holomap.dm b/code/modules/holomap/station_holomap.dm
index 93cf76017d..dc15dabb46 100644
--- a/code/modules/holomap/station_holomap.dm
+++ b/code/modules/holomap/station_holomap.dm
@@ -240,9 +240,29 @@
origin_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 2)
req_components = list()
-// TODO
-// //Portable holomaps, currently AI/Borg/MoMMI only
+/datum/holomap_marker
+ var/x
+ var/y
+ var/z
+ var/offset_x = -8
+ var/offset_y = -8
+ var/filter
+ var/id // used for icon_state of the marker on maps
+ var/icon = 'icons/holomap_markers.dmi'
+ var/color //used by path rune markers
-// TODO
-// OHHHH YEAH - STRATEGIC HOLOMAP! NICE!
-// It will need to wait until later tho.
+/obj/effect/landmark/holomarker
+ delete_me = TRUE
+
+ var/filter = HOLOMAP_FILTER_STATIONMAP
+ var/id = "generic"
+
+/obj/effect/landmark/holomarker/Initialize()
+ . = ..()
+ var/datum/holomap_marker/holomarker = new()
+ holomarker.id = id
+ holomarker.filter = filter
+ holomarker.x = src.x
+ holomarker.y = src.y
+ holomarker.z = src.z
+ holomap_markers["[id]_\ref[src]"] = holomarker
diff --git a/icons/effects/64x64.dmi b/icons/effects/64x64.dmi
index e4ae33567f..093da559ef 100644
Binary files a/icons/effects/64x64.dmi and b/icons/effects/64x64.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index f578e1f9ac..8c74dc49b5 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/gpshud.dmi b/icons/effects/gpshud.dmi
new file mode 100644
index 0000000000..dc8544ce26
Binary files /dev/null and b/icons/effects/gpshud.dmi differ
diff --git a/icons/holomap_markers.dmi b/icons/holomap_markers.dmi
index 3ea32c120d..9523510710 100644
Binary files a/icons/holomap_markers.dmi and b/icons/holomap_markers.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index dd15d13fb7..3ca9cde72b 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/maps/~map_system/maps.dm b/maps/~map_system/maps.dm
index 0863992efe..e5a66d33ae 100644
--- a/maps/~map_system/maps.dm
+++ b/maps/~map_system/maps.dm
@@ -36,6 +36,7 @@ var/list/all_maps = list()
var/static/list/persist_levels = list() // Z-levels where SSpersistence should persist between rounds. Defaults to station_levels if unset.
var/static/list/secret_levels = list() // Z-levels that (non-admin) ghosts can't get to
var/static/list/empty_levels = list() // Empty Z-levels that may be used for various things
+ var/static/list/mappable_levels = list()// List of levels where mapping or other similar devices might work fully
// End Static Lists
// Z-levels available to various consoles, such as the crew monitor. Defaults to station_levels if unset.
@@ -122,6 +123,8 @@ var/list/all_maps = list()
map_levels = station_levels.Copy()
if(!persist_levels?.len)
persist_levels = station_levels.Copy()
+ if(!mappable_levels?.len)
+ mappable_levels = station_levels.Copy()
if(!allowed_jobs || !allowed_jobs.len)
allowed_jobs = subtypesof(/datum/job)
if(default_skybox) //Type was specified
@@ -294,11 +297,13 @@ var/list/all_maps = list()
map.base_turf_by_z["[z]"] = base_turf
if(transit_chance)
map.accessible_z_levels["[z]"] = transit_chance
+ if(flags & MAP_LEVEL_MAPPABLE)
+ map.mappable_levels |= z
// Holomaps
// Auto-center the map if needed (Guess based on maxx/maxy)
if (holomap_offset_x < 0)
holomap_offset_x = ((HOLOMAP_ICON_SIZE - world.maxx) / 2)
- if (holomap_offset_x < 0)
+ if (holomap_offset_y < 0)
holomap_offset_y = ((HOLOMAP_ICON_SIZE - world.maxy) / 2)
// Assign them to the map lists
LIST_NUMERIC_SET(map.holomap_offset_x, z, holomap_offset_x)
diff --git a/polaris.dme b/polaris.dme
index af509d7e96..ec2c67895e 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -146,11 +146,13 @@
#include "code\_onclick\hud\hud.dm"
#include "code\_onclick\hud\human.dm"
#include "code\_onclick\hud\map_popups.dm"
+#include "code\_onclick\hud\minihud.dm"
+#include "code\_onclick\hud\minihud_mapper.dm"
+#include "code\_onclick\hud\minihud_rigmech.dm"
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\picture_in_picture.dm"
#include "code\_onclick\hud\radial.dm"
#include "code\_onclick\hud\radial_persistent.dm"
-#include "code\_onclick\hud\rigmech.dm"
#include "code\_onclick\hud\robot.dm"
#include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\skybox.dm"
@@ -1939,6 +1941,7 @@
#include "code\modules\holomap\generate_holomap.dm"
#include "code\modules\holomap\holomap_area.dm"
#include "code\modules\holomap\holomap_datum.dm"
+#include "code\modules\holomap\mapper.dm"
#include "code\modules\holomap\station_holomap.dm"
#include "code\modules\hydroponics\_hydro_setup.dm"
#include "code\modules\hydroponics\grown.dm"