Ports lore consoles for mapping related things (#21986)

## About PR

Ports https://github.com/ParadiseSS13/Paradise/pull/29896, all credit
goes to warriorstar-orion for this very solid feature!

Additionally, the consoles can be edited by storytellers or any ghost
that has admin permissions

## Images

<img width="928" height="700" alt="Screenshot_30"
src="https://github.com/user-attachments/assets/f3005afb-a049-461e-ad15-901bc9e8537f"
/>
<img width="912" height="699" alt="Screenshot_31"
src="https://github.com/user-attachments/assets/952a9b39-f557-4c87-9e50-41ea0d221436"
/>


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

| Path | Original Author | License |
| --- | --- | --- |
| `loreconsole.dm`, `LoreConsole.tsx`, `LoreConsole.scss`,
`amberpos.scss` | warriorstar-orion (Paradise Station) | AGPL 3 |
| `loreconsole` icon_state in
icons/obj/modular_computers/modular_terminal.dmi | warriorstar-orion
(Paradise Station) | CC 3.0 BY-SA |
This commit is contained in:
Kano
2026-03-17 09:31:34 +00:00
committed by GitHub
parent 108d2ca63a
commit b56522f6e5
13 changed files with 309 additions and 63 deletions
+1
View File
@@ -1042,6 +1042,7 @@
#include "code\game\machinery\computer\computer.dm"
#include "code\game\machinery\computer\guestpass.dm"
#include "code\game\machinery\computer\law.dm"
#include "code\game\machinery\computer\loreconsole.dm"
#include "code\game\machinery\computer\Operating.dm"
#include "code\game\machinery\computer\pod.dm"
#include "code\game\machinery\computer\robot.dm"
+1 -1
View File
@@ -89,7 +89,7 @@
if(dir == SOUTH)
layer = ABOVE_HUMAN_LAYER
ClearOverlays()
if(stat & NOPOWER)
if(!interact_offline && (stat & NOPOWER)) // we only stop here if we care about power
set_light(0)
return
else
@@ -0,0 +1,97 @@
/datum/lore_console_entry
var/title
var/body
/datum/lore_console_entry/New(title_ = "", body_)
title = title_
body = body_
/**
* A useful object to convey the lore of your away_site maps, opposed to papers
* How to use it?
* * Create `new/datum/lore_console_entry(title, body)` instances in the `entries` list
* * This list can contain multiple datum entries, each entry represents a page
*/
ABSTRACT_TYPE(/obj/machinery/computer/terminal/loreconsole)
name = "information terminal"
desc = "A terminal with a blank screen, waiting to receive an input."
icon_screen = "loreconsole"
icon_keyboard = "power_key"
light_power_on = 2
var/list/entries = list()
/obj/machinery/computer/terminal/loreconsole/attack_hand(mob/user)
ui_interact(user)
/obj/machinery/computer/terminal/loreconsole/ui_state(mob/user)
return GLOB.default_state
/obj/machinery/computer/terminal/loreconsole/ui_interact(mob/user, datum/tgui/ui = null)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "LoreConsole", name)
ui.open()
/obj/machinery/computer/terminal/loreconsole/ui_static_data(mob/user)
var/list/data = list()
data["entries"] = list()
for(var/datum/lore_console_entry/entry as anything in entries)
data["entries"] += list(list("title" = entry.title, "body" = entry.body))
return data
// Allows storytellers and ghosts with admin perms edit entries.
/obj/machinery/computer/terminal/loreconsole/attack_ghost(mob/user)
if(isstoryteller(user) || check_rights(R_ADMIN|R_FUN, TRUE, user))
var/choice = tgui_input_list(user, "Would you like to add, edit or remove entries?", "Manage Entries", list("Add", "Edit", "Remove"))
if(choice)
var/list/entry_titles
if(length(entries))
for(var/datum/lore_console_entry/E in entries)
LAZYADDASSOC(entry_titles, E.title, E) // title is key, entry datum is its value
switch(choice)
if("Add")
var/new_title = tgui_input_text(user, "Enter the title of the entry", "Entry Title")
var/new_body = tgui_input_text(user, "Enter the body of the entry", "Entry Body", multiline = TRUE)
if(!new_title || !new_body) // no info, no entries
return
entries += new/datum/lore_console_entry(new_title, new_body)
to_chat(user, SPAN_NOTICE("A new entry has been added."))
return
if("Edit")
if(!length(entries))
to_chat(user, SPAN_WARNING("There are no entries to edit!"))
return
var/edit_choice = tgui_input_list(user, "Choose an entry to edit.", "Manage Entries", entry_titles)
if(!edit_choice)
return
var/datum/lore_console_entry/edit_datum = entry_titles[edit_choice]
edit_datum.title = tgui_input_text(user, "Enter the title of the entry", "Entry Title", default = edit_datum.title)
edit_datum.body = tgui_input_text(user, "Enter the body of the entry", "Entry Body", default = edit_datum.body, multiline = TRUE)
to_chat(user, SPAN_NOTICE("An entry has been edited."))
return
if("Remove")
if(!length(entries))
to_chat(user, SPAN_WARNING("There are no entries to remove!"))
return
var/remove_choice = tgui_input_list(user, "Choose an entry to remove.", "Manage Entries", entry_titles)
if(!remove_choice)
return
var/datum/lore_console_entry/remove_datum = entry_titles[remove_choice]
entries -= remove_datum
to_chat(user, SPAN_NOTICE("An entry has been removed."))
return
..()
ABSTRACT_TYPE(/obj/machinery/computer/terminal/loreconsole/always_powered)
interact_offline = TRUE
+10 -5
View File
@@ -141,15 +141,20 @@ Class Procs:
var/uid
var/panel_open = 0
var/global/gl_uid = 1
var/interact_offline = 0 // Can the machine be interacted with while de-powered.
var/printing = 0 // Is this machine currently printing anything?
var/list/processing_parts // Component parts queued for processing by the machine. Expected type: `/obj/item/stock_parts` Unused currently
/// Can the machine be interacted with while de-powered?
var/interact_offline = FALSE
/// Is this machine currently printing anything?
var/printing = FALSE
/// Component parts queued for processing by the machine. Expected type: `/obj/item/stock_parts` Unused currently.
var/list/processing_parts
/// Bitflag. What is being processed. One of `MACHINERY_PROCESS_*`.
var/processing_flags
var/clicksound //played sound on usage
var/clickvol = 40 //volume
/// Played sound on usage.
var/clicksound
/// Volume.
var/clickvol = 40
/// Signaller attached to the machine
var/obj/item/assembly/signaler/signaler
/// Overmap sector the machine is linked to
@@ -0,0 +1,6 @@
author: Kano
delete-after: True
changes:
- rscadd: "Ported lore consoles from Paradise Station."
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

@@ -797,10 +797,6 @@
/obj/item/material/knife{
pixel_x = -9
},
/obj/item/storage/box/produce{
pixel_y = 9;
pixel_x = 6
},
/obj/item/reagent_containers/food/condiment/enzyme{
pixel_y = 3
},
@@ -4229,13 +4225,16 @@
pixel_x = -9;
pixel_y = 8
},
/obj/item/paper/fluff/quarantined_outpost/maintenance_log{
pixel_x = 5
},
/obj/item/pen/black{
pixel_y = -2;
pixel_x = -8
},
/obj/item/clipboard{
pixel_x = 5
},
/obj/item/paper{
pixel_x = 5
},
/turf/simulated/floor/tiled/gunmetal/full,
/area/quarantined_outpost/server_relay/south_east)
"cYX" = (
@@ -8963,10 +8962,6 @@
"gcC" = (
/obj/effect/floor_decal/industrial/hatch/grey,
/obj/structure/table/rack/retail_shelf,
/obj/item/storage/box/freezer/organcooler{
pixel_y = 16;
pixel_x = 8
},
/obj/item/reagent_containers/glass/bottle/inaprovaline{
pixel_x = -8
},
@@ -16403,10 +16398,6 @@
"kVw" = (
/obj/structure/table/standard,
/obj/effect/floor_decal/industrial/outline/yellow,
/obj/item/storage/box/flares{
pixel_y = 13;
pixel_x = 2
},
/obj/random/dirt_75,
/turf/simulated/floor/tiled/dark/full/airless,
/area/quarantined_outpost/security)
@@ -20080,10 +20071,6 @@
dir = 6
},
/obj/structure/table/standard,
/obj/item/storage/box/sharps{
pixel_y = 11;
pixel_x = 6
},
/obj/item/reagent_containers/spray/cleaner{
desc = "Someone has crossed out the Space from Space Cleaner and written in Surgery. 'Do not remove under punishment of death!!!' is scrawled on the back.";
icon_state = "sterilesprayblue";
@@ -20503,15 +20490,6 @@
},
/turf/simulated/floor/plating,
/area/quarantined_outpost/elevator_hallway)
"nIX" = (
/obj/structure/railing/mapped{
dir = 8
},
/obj/structure/lattice/catwalk/indoor/grate/dark,
/obj/machinery/portable_atmospherics/canister/empty/boron,
/obj/effect/decal/rolling_fog,
/turf/simulated/floor/plating,
/area/quarantined_outpost/research)
"nJg" = (
/obj/effect/decal/cleanable/blood,
/turf/simulated/floor/tiled/white,
@@ -22498,9 +22476,6 @@
},
/obj/random/dirt_75,
/obj/effect/floor_decal/industrial/outline/custodial,
/obj/structure/janitorialcart{
dir = 4
},
/turf/simulated/floor/tiled,
/area/quarantined_outpost/dorm_hallway)
"oWe" = (
@@ -22612,8 +22587,6 @@
/obj/structure/closet/crate/freezer{
name = "Fridge"
},
/obj/item/storage/box/monkeycubes,
/obj/item/storage/box/freezer/organcooler,
/turf/simulated/floor/tiled/freezer{
name = "cooled tiles";
temperature = 253.15
@@ -23885,7 +23858,6 @@
/obj/machinery/light/small/broken{
dir = 8
},
/obj/machinery/portable_atmospherics/canister/empty/boron,
/obj/effect/floor_decal/corner/yellow{
dir = 8
},
@@ -24261,10 +24233,6 @@
/area/quarantined_outpost/cargo_bay)
"qfQ" = (
/obj/structure/table/standard,
/obj/item/storage/box/sharps{
pixel_y = 12;
pixel_x = -7
},
/obj/item/reagent_containers/spray/cleaner{
desc = "Someone has crossed out the Space from Space Cleaner and written in Surgery. 'Do not remove under punishment of death!!!' is scrawled on the back.";
icon_state = "sterilesprayblue";
@@ -28418,8 +28386,6 @@
},
/obj/effect/floor_decal/industrial/outline/yellow,
/obj/structure/closet/secure_closet/guncabinet,
/obj/item/storage/box/shotgunshells,
/obj/item/storage/box/shotgunshells,
/obj/item/ammo_magazine/c762/sol,
/obj/item/ammo_magazine/c762/sol,
/obj/item/ammo_magazine/c762/sol,
@@ -28427,7 +28393,6 @@
/obj/item/ammo_magazine/c762/sol,
/obj/item/ammo_magazine/c762/sol,
/obj/item/ammo_magazine/c762/sol,
/obj/item/storage/box/shotgunshells,
/turf/simulated/floor/tiled/dark/full/airless,
/area/quarantined_outpost/security)
"sOv" = (
@@ -34550,7 +34515,6 @@
dir = 4
},
/obj/effect/floor_decal/industrial/outline/yellow,
/obj/structure/engineeringcart/half_filled,
/obj/machinery/light/small/maybe_broken/decayed{
dir = 1
},
@@ -35336,9 +35300,9 @@
/area/quarantined_outpost/entrance_maintenance)
"xwn" = (
/obj/effect/floor_decal/industrial/outline/yellow,
/obj/machinery/computer/terminal/inactive,
/obj/effect/decal/cleanable/cobweb2,
/obj/random/dirt_75,
/obj/machinery/computer/terminal/loreconsole/always_powered/quarantined_outpost/maintenance_log,
/turf/simulated/floor/tiled/gunmetal/full,
/area/quarantined_outpost/server_relay/south_east)
"xwE" = (
@@ -75670,7 +75634,7 @@ dPa
ksz
kgg
ppZ
nIX
cLu
cLu
cLu
aMQ
@@ -1112,9 +1112,21 @@ GLOBAL_LIST_EMPTY(trackables_pool)
/obj/structure/filler/ex_act()
return
/*######################################
PAPERS
######################################*/
/*####################################
PAPERS & LORE CONSOLES
####################################*/
/obj/machinery/computer/terminal/loreconsole/always_powered/quarantined_outpost/maintenance_log
entries = list(
new/datum/lore_console_entry(
"\[11 FEB 2274 - CYCLE 451 - ATKINSON\]", {"
<hr>
Regular check-up, all green.
<br><br>
We're pushing more and more processing into the labs, even though the gear wasn't built for this kind of workload. Miracles don't happen by itself,
we <i>need</i> proper requipment and being in the middle of nowhere is no excuse. Open a ticket, get us some real hardware. Maybe then we can stop having to
manually pulse this box every time the red lights start blinking.
"}))
/obj/item/paper/fluff/quarantined_outpost/engineers_note
name = "crumpled instructions"
@@ -1140,16 +1152,6 @@ GLOBAL_LIST_EMPTY(trackables_pool)
...bring the equipment to... dispose of... if you see any, make sure they stay down... burn...
"}
/obj/item/paper/fluff/quarantined_outpost/maintenance_log
name = "maintenance log"
info = {"
...
<br><br>
- \[11 FEB 2274 - CYCLE 451 - ATKINSON\] Regular check-up, all green. Lately we allocate more and more processing space for the labs, even though these equipments
weren't meant to pull off such an intense workload. We may be in the middle of damn nowhere, but hoping for miracles won't get you too far either. Put a ticket,
get us some proper hardware and then maybe you will see less blinking red lights in the box that we have to pulse to reboot every now and then.
"}
/obj/item/paper/fluff/quarantined_outpost/venusians_note
name = "hastily written note"
info = {"
+1
View File
@@ -33,6 +33,7 @@ import './styles/themes/hammertail.scss';
import './styles/themes/hephaestus.scss';
import './styles/themes/sol.scss';
import './styles/themes/vaurca.scss';
import './styles/themes/amberpos.scss';
import { StoreProvider, configureStore } from './store';
@@ -0,0 +1,71 @@
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Section, Stack } from '../components';
import { Window } from '../layouts';
type LoreEntry = {
title?: string;
body: string;
};
type LoreConsoleData = {
entries: LoreEntry[];
};
export const LoreConsole = (props, context) => {
const { data } = useBackend<LoreConsoleData>(context);
const { entries } = data;
const [currentPage, setCurrentPage] = useLocalState(
context,
'currentPage',
0,
);
const entry = entries[currentPage];
return (
<Window width={700} height={700} theme="amberpos">
<Stack fill vertical>
{entries.length > 1 && (
<Stack.Item>
<Section
fill
title={`Page ${currentPage + 1}/${entries.length}`}
textAlign="center"
>
<Stack>
<Stack.Item grow basis={0}>
<Button
fluid
content="Previous"
fontSize="150%"
icon="arrow-left"
lineHeight={2}
disabled={currentPage <= 0}
onClick={() => setCurrentPage(currentPage - 1)}
/>
</Stack.Item>
<Stack.Item grow basis={0}>
<Button
fluid
content="Next"
fontSize="150%"
icon="arrow-right"
lineHeight={2}
disabled={currentPage >= entries.length - 1}
onClick={() => setCurrentPage(currentPage + 1)}
/>
</Stack.Item>
</Stack>
</Section>
</Stack.Item>
)}
<Stack.Item>
<h1>{entry.title && entry.title}</h1>
<Box
className="LoreConsole__entryText"
dangerouslySetInnerHTML={{ __html: entry.body }}
/>
</Stack.Item>
</Stack>
</Window>
);
};
@@ -0,0 +1,17 @@
@use '../base.scss';
$background-color: rgba(0, 0, 0, 0.33) !default;
.LoreConsole__titleText {
font-size: 300%;
font-weight: bold;
margin: 0;
padding: 4px;
}
.LoreConsole__entryText {
color: chocolate;
font-size: 150%;
font-family: 'Courier New', Courier, monospace;
padding: 32px;
}
+1
View File
@@ -54,6 +54,7 @@
@include meta.load-css('./interfaces/PersonalCrafting.scss');
@include meta.load-css('./interfaces/RequestManager.scss');
@include meta.load-css('./interfaces/Radio.scss');
@include meta.load-css('./interfaces/LoreConsole.scss');
// Layouts
@include meta.load-css('./layouts/Layout.scss');
@@ -0,0 +1,81 @@
.theme-amberpos:root {
// Base
--color-base: hsl(24, 100%, 5%);
--color-primary: hsl(from var(--color-base) h s calc(l - 30));
--color-label: #d86c00;
--secondary-saturation: 10;
--secondary-lightness-adjustment: -56;
--base-gradient-spread: 0;
--border-radius-unit: 0;
--base-font-stack:
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas,
'DejaVu Sans Mono', monospace;
// Components
--button-color: var(--color-label);
--button-background-default: var(--color-base);
--button-background-selected: hsl(0, 90%, 35%);
--button-background-caution: hsl(30, 85%, 40%);
--button-background-danger: hsl(31, 100%, 30%);
--progress-bar-background: hsl(0, 0%, 0%, 0.5);
--input-background: transparent;
// Background
body,
:root {
background-color: var(--color-base);
}
.Window,
.Window__dimmer {
background-color: var(--color-base);
background-image: none;
}
.Button {
font-family: var(--base-font-stack);
border: var(--border-thickness-small) outset var(--color-base);
outline: var(--border-thickness-tiny) solid hsl(60, 6%, 8%);
&-disabled {
color: hsl(60, 6%, 77%);
font-family: var(--base-font-stack);
&:hover {
color: hsl(0, 0%, 100%);
}
}
&--selected {
color: var(--color-white);
border-style: inset;
}
}
.Section {
font-family: var(--base-font-stack);
&__titleText {
color: var(--color-label);
}
}
div,
h1,
h2,
h3,
h4 {
font-family: var(--base-font-stack);
color: var(--color-label);
}
h1 {
font-size: 250%;
padding: 32px;
padding-bottom: 0;
}
h2 {
font-size: 120%;
}
}