Files
Aurora.3/code/game/objects/structures/whiteboard.dm
T
KanoandGitHub 3043bf536d Adds whiteboards and gives a new UI to barsigns and its subtypes (#22133)
## About PR

Sprites made by @KingOfThePing! 
Adds whiteboards to following areas:

- CE and RD office (portable)
- Expedition EVA storage (portable)
- Intrepid flight deck (portable)
- Xenoarcheology presentation room
- Engineering breakroom
- Kitchen (as an alternative to holo-menu)

Whiteboards are also available in cargo console in the recreation tab
As an addition whiteboards can be mapped in with preset overlays and
written contents
This PR also changes sign selection list menu for barsigns and its
subtypes with a tgui image menu

## Images

<img width="699" height="534" alt="image"
src="https://github.com/user-attachments/assets/7b3de94c-76e4-491d-802c-ae9a31501805"
/>

<details><summary>video sample</summary>
<p>


https://github.com/user-attachments/assets/24de419c-e155-4623-9d08-0ff6c0b7d228

</p>
</details> 

### Asset Licenses
The following assets that **have not** been created by myself are
included in this PR:

| Path | Original Author | License |
| --- | --- | --- |
| icons/obj/whiteboard.dmi & icons/obj/whiteboard_portable.dmi |
KingOfThePing | CC BY-SA 3.0 |
| sound/effects/whiteboard_scribble_1.ogg,
sound/effects/whiteboard_scribble_2.ogg,
sound/effects/whiteboard_scribble_3.ogg | freesound_community
(https://pixabay.com/sound-effects/film-special-effects-marker-whiteboard-squeak03-83059/)
| Pixabay Licence |
2026-04-06 19:03:01 +00:00

294 lines
8.9 KiB
Plaintext

/// List of data passed over to tgui, obtained from whiteboard singletons. List contains lists of: "display_name", "icon", "icon_name", "singleton_path"
GLOBAL_LIST_EMPTY(whiteboard_overlay_cache)
// ---- large whiteboard, 64x32
/obj/structure/whiteboard
name = "whiteboard"
desc = "A whiteboard in classic design. Often associated with its sibling glassboard, perfect material to draw incomprehensible equations."
icon = 'icons/obj/whiteboard.dmi'
icon_state = "whiteboard"
layer = OBJ_LAYER
/// Singleton path to whiteboard overlays.
var/overlay_choices_singleton = /singleton/whiteboard
/// Written things on the whiteboard.
var/written_contents
/// Is someone currently drawing things on the whiteboard?
var/busy = FALSE
/// Used to prevent pixel-shift mechanic spam.
var/currently_pixel_shifting = FALSE
/// For mapping purposes. Define this as one of the overlay icon_state's name (e.g. overlay_1).
var/preset_overlay
/obj/structure/whiteboard/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "You can write or edit things on [src] with a <b>pen</b>."
. += "You can <b>ALT Click</b> to pixel-shift [src] to the wall. Only works for north direction when wheel brakes are locked!"
. += "You can engage/release wheel brakes with the help of a <b>wrench</b>."
/obj/structure/whiteboard/examine(mob/user, distance, is_adjacent, infix, suffix, show_extended)
. = ..()
if(written_contents)
to_chat(user, EXAMINE_BLOCK("It reads: \"[written_contents]\"."))
/obj/structure/whiteboard/Initialize()
. = ..()
if(!length(GLOB.whiteboard_overlay_cache))
var/list/singleton_instances = GET_SINGLETON_SUBTYPE_MAP(overlay_choices_singleton)
for(var/i in singleton_instances)
var/singleton/whiteboard/S = GET_SINGLETON(i)
var/overlay_image = image(icon = icon, icon_state = S.icon_state)
GLOB.whiteboard_overlay_cache.Add(list(list(
"display_name" = S.name,
"icon" = icon2base64(getFlatIcon(overlay_image)), // expensive but we only do it once
"icon_name" = S.icon_state,
"singleton_path" = "[S.type]"
)))
if(preset_overlay)
AddOverlays(preset_overlay)
/obj/structure/whiteboard/Move()
. = ..()
if(has_gravity())
playsound(src, 'sound/effects/roll.ogg', 50, 1)
/obj/structure/whiteboard/AltClick(mob/user)
if(!anchored)
to_chat(user, SPAN_WARNING("You need the wheels locked first in order to secure [src] to a wall!"))
return
if(currently_pixel_shifting)
return
currently_pixel_shifting = TRUE
if(!do_after(user, 0.5 SECOND))
currently_pixel_shifting = FALSE
return
currently_pixel_shifting = FALSE
animate(src, pixel_y = pixel_y == 0 ? 12 : 0, time = 0.2 SECOND)
balloon_alert(user, "shifted")
/obj/structure/whiteboard/attackby(obj/item/attacking_item, mob/user)
if(busy)
to_chat(user, SPAN_WARNING("Someone is already drawing things on this board!"))
return
var/obj/item/pen/P = attacking_item
if(istype(P))
to_chat(user, SPAN_NOTICE("You start contemplating what to draw."))
handle_drawing(user)
else if(attacking_item.tool_behaviour == TOOL_WRENCH)
animate(src, pixel_y = 0, time = 0.2 SECOND) // if we were pixel-shifted, return to our original state
anchored = !anchored
attacking_item.play_tool_sound(get_turf(src), 75)
balloon_alert(user, "[anchored ? "secure" : "unsecure"]")
user.visible_message("[user] [anchored ? "locks" : "unlocks"] [src]'s wheels.", \
"You [anchored ? "locked" : "unlocked"] [src]'s wheels.", \
"You hear a click.")
else
to_chat(user, SPAN_WARNING("This is the wrong tool to manifest your inland empire, maybe a pen would be more fitting?"))
/obj/structure/whiteboard/proc/handle_drawing(mob/user)
busy = TRUE
// Text input
var/new_written_contents = tgui_input_text(user, "Write your text here", "Whiteboard Contents", default = written_contents, multiline = TRUE)
if(new_written_contents == written_contents) // if untouched, cancel it
to_chat(user, SPAN_WARNING("You withdraw your pen in thought, [src] remains untouched."))
busy = FALSE
return
if(!new_written_contents) //if empty, erase the board
to_chat(user, SPAN_NOTICE("You erase the contents on [src]."))
ClearOverlays()
desc = initial(desc)
written_contents = null
busy = FALSE
return
written_contents = new_written_contents
ui_interact(user)
/obj/structure/whiteboard/proc/finalize_drawing(mob/user, choice)
playsound(get_turf(src), SFX_WHITEBOARD_SCRIBBLE, 60, FALSE)
if(do_after(user, 2 SECONDS))
update_icon(choice)
else
ClearOverlays()
written_contents = null
/obj/structure/whiteboard/update_icon(choice)
ClearOverlays()
AddOverlays(choice)
// ---- UI code
/obj/structure/whiteboard/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "OverlayChoice", capitalize_first_letters(name))
ui.open()
/obj/structure/whiteboard/ui_static_data(mob/user)
var/list/data = list()
data["contents"] = GLOB.whiteboard_overlay_cache
return data
/obj/structure/whiteboard/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
var/mob/user = ui.user
add_fingerprint(user)
switch(action)
if("select_overlay")
var/choice = params["choice"]
INVOKE_ASYNC(src, PROC_REF(finalize_drawing), user, choice)
busy = FALSE
SStgui.close_uis(src)
. = TRUE
/obj/structure/whiteboard/ui_close(mob/user, datum/tgui/ui)
. = ..()
if(busy) // if the ui was closed while we were still busy, it means no overlay were chosen
busy = FALSE
written_contents = null
ClearOverlays()
to_chat(user, SPAN_WARNING("You withdraw your pen in thought, [src] remains untouched."))
// ---- Portable whiteboard, 32x32
/obj/item/portable_whiteboard
name = "portable whiteboard"
desc = "A compact whiteboard folded into a portable frame, ready to be deployed on any unexpected seminar!"
icon = 'icons/obj/whiteboard_portable.dmi'
icon_state = "whiteboard_case"
item_state = "whiteboard_case"
contained_sprite = TRUE
force = 18
throw_speed = 1
throw_range = 4
w_class = WEIGHT_CLASS_BULKY
attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked")
drop_sound = 'sound/items/drop/toolbox.ogg'
pickup_sound = 'sound/items/pickup/toolbox.ogg'
/// Used to keep track of folding process to prevent spams.
var/currently_unfolding = FALSE
/obj/item/portable_whiteboard/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "You can <b>ALT Click</b> to deploy [src]. It must be on the floor first!"
/obj/item/portable_whiteboard/AltClick(mob/user)
if(loc == user)
to_chat(user, SPAN_WARNING("[src] needs to be standing on the floor in order to be unfolded!"))
return
if(currently_unfolding)
return
currently_unfolding = TRUE
if(!do_after(user, 3 SECONDS))
currently_unfolding = FALSE
return
new /obj/structure/whiteboard/portable(get_turf(src))
playsound(get_turf(src), 'sound/items/storage/briefcase.ogg', 50)
qdel(src)
/obj/structure/whiteboard/portable
name = "portable whiteboard"
desc = "A compact whiteboard folded into a portable frame, ready to be deployed on any unexpected seminar!"
icon = 'icons/obj/whiteboard_portable.dmi'
anchored = FALSE
/// Used to keep track of folding process to prevent spams.
var/currently_folding = FALSE
/obj/structure/whiteboard/portable/AltClick(mob/user)
if(currently_folding)
return
currently_folding = TRUE
if(!do_after(user, 3 SECONDS))
currently_folding = FALSE
return
new /obj/item/portable_whiteboard(get_turf(src))
playsound(get_turf(src), 'sound/items/storage/briefcase.ogg', 50)
qdel(src)
/obj/structure/whiteboard/portable/update_icon(choice)
ClearOverlays()
var/image/overlay_image = image(icon = 'icons/obj/whiteboard.dmi', icon_state = choice)
overlay_image.transform *= 0.5
overlay_image.pixel_x = -16
overlay_image.pixel_y = 2
AddOverlays(overlay_image)
// ---- Whiteboard overlay singletons
/singleton/whiteboard
var/name = "placeholder"
var/icon_state = ""
/singleton/whiteboard/bar_chart
name = "Bar chart"
icon_state = "overlay_1"
/singleton/whiteboard/pie_chart
name = "Pie chart"
icon_state = "overlay_2"
/singleton/whiteboard/spacetime_diagram
name = "Spacetime diagram"
icon_state = "overlay_3"
/singleton/whiteboard/geometric_chart
name = "Geometric chart"
icon_state = "overlay_4"
/singleton/whiteboard/scribbles
name = "Scribbles"
icon_state = "overlay_5"
/singleton/whiteboard/gun_diagram
name = "Gun diagram"
icon_state = "overlay_6"
/singleton/whiteboard/circle_chart
name = "Circle chart"
icon_state = "overlay_7"
/singleton/whiteboard/display_chart
name = "Display chart"
icon_state = "overlay_8"
/singleton/whiteboard/todays_menu
name = "Today's menu"
icon_state = "overlay_9"
/singleton/whiteboard/calculations
name = "Calculations"
icon_state = "overlay_10"
/singleton/whiteboard/erased_board
name = "Erased board"
icon_state = "overlay_11"
/singleton/whiteboard/wavelenghth
name = "Wavelength"
icon_state = "overlay_12"
/singleton/whiteboard/grafitti
name = "Grafitti"
icon_state = "overlay_13"
/singleton/whiteboard/archeological
name = "Archeological"
icon_state = "overlay_14"