Files
Aurora.3/code/datums/room_template.dm
Kano 58570e035a Adds base planning blueprints (#21626)
## About PR

Adds some building/planning mechanics typically seen in
base-building/survival games such as Grounded. Hopefully this will ease
the away base construction process. Happy base buildings!

Currently, this allows placing blueprint objects that players can click
on with the required materials to build whatever the blueprint
represents. You can place templates, edit them or build your own
glorious base from ground.

Sprites for `blueprints` and `blueprints_unfolded` inside
`base_planning_blueprints.dmi` are ported from SS20 Russian Paradise
Station.

## Images
https://youtu.be/FCrZn8BkISU?si=Fst0ksAgi47D_wN7

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

| Path | Original Author | License |
| --- | --- | --- |
| icons/obj/item/base_planning_blueprints.dmi | vovakons (SS20 Russian
Paradise Station) | AGPLv3 |
2025-12-19 23:26:56 +00:00

219 lines
7.4 KiB
Plaintext

/**
* A basic template loader system, allows placing objects, items, turfs in a specified area.
* Currently this function doesn't support the following:
*
* * Uploading templates in-game.
* * Even number area dimensions.
*/
/datum/room_template
var/name = "base template"
/// Top-down view of the room template, which can be edited like the example below:
/**
* Define some vars that contains object-turf paths in New() like this:
* var/W = /turf/simulated/wall
* var/F = /turf/simulated/floor/plating
* var/G = /obj/effect/map_effect/window_spawner/full/reinforced
* var/A = /obj/machinery/door/airlock
* var/list/AF = list(A, F)
*
* then:
* layouts = list(
* list(
* list(W, G, W, W, W),
* list(W, F, F, F, W),
* list(W, W, W, AF, W)
* ) = "average college student dorm",
* )
* This is a layout of a 5x3 (5 width, 3 height) room. Vars can contain whatever path you want and you can fit multiple layouts in here.
* You can also enter values to the keys in this list, it'll be displayed in template selection UI. Make sure each value is unique.
* This system also supports placing multiple atoms per turf, needs to be defined inside a list on the turf represented in layout, see the example above.
* Keep the area dimension as odd numbers, reason explained below.
*/
var/list/layouts
/// Lists for keeping record of template related placements, incase the user wants to revert it.
var/list/placed_template_objects = list()
var/list/replaced_turfs = list()
var/list/deleted_blueprints = list()
/datum/room_template/proc/template_selection(turf/center)
var/list/options = list()
var/list/chosen_layout = list()
for(var/key in layouts)
options += layouts[key]
var/choice = tgui_input_list(usr, "Choose a template to place.", "Template Selection", options)
if(!choice)
return
for(var/key in layouts)
if(layouts[key] == choice) // this is the reason every value needs to be unique
chosen_layout = key
break
if(chosen_layout)
build_the_template(center, chosen_layout)
/datum/room_template/proc/build_the_template(turf/center, list/chosen_layout)
var/height = length(chosen_layout)
var/width = length(chosen_layout[1])
var/starting_x = center.x - round(width / 2) // this system assumes it's always at the center, hence even numbers aren't supported
var/starting_y = center.y + round(height / 2)
var/starting_z = center.z
if(width % 2 == 0 || height % 2 == 0)
CRASH("Room template [name] has a layout with even dimensions as width or height. Only odd numbers are supported.")
for(var/row in 1 to height)
for(var/column in 1 to width)
// The math and reasoning behind this is, due to how we arrange reading the layout list
// (consider the top-down view, and now visualise the list getting flattened), we start placing the template from top-left corner to bottom right.
// We simply pick a turf that is specified by width*height area, and see where this exact turf corrresponds to in the layout given to us.
var/turf/T = locate(starting_x + column - 1, starting_y - row + 1, starting_z) // we're locating while assuming we're in the center
if(!T || T.density)
continue
var/tile_data = chosen_layout[row][column]
if(!tile_data)
continue
for(var/obj/structure/blueprint/colliding_blueprint in T)
deleted_blueprints += list(list("turf" = T, "type" = colliding_blueprint.type))
qdel(colliding_blueprint)
if(islist(tile_data))
for(var/i in tile_data)
place_tile_data(T, i)
else
place_tile_data(T, tile_data)
// Finalizing template placement
if(usr?.client)
if(tgui_alert(usr, "Would you like to keep the placed template?", "Template Confirmation", list("Confirm", "Cancel")) != "Confirm")
QDEL_LAZYLIST(placed_template_objects) // revert placed objects
for(var/turf/T in replaced_turfs) // restore turfs
T.ChangeTurf(replaced_turfs[T])
for(var/tile in deleted_blueprints) // restore blueprints
var/blueprint_to_restore = tile["type"]
new blueprint_to_restore(tile["turf"])
to_chat(usr, SPAN_NOTICE("Template has been reverted."))
replaced_turfs = null
deleted_blueprints = null
/datum/room_template/proc/place_tile_data(turf/T, tile_data)
if(ispath(tile_data, /turf)) // Turf
replaced_turfs[T] = T.type
T.ChangeTurf(tile_data)
return
placed_template_objects += new tile_data(T) // Object or anything else
// ---- Subtypes
// Base Planner Templates
/datum/room_template/base_planner_template
name = "Base Planner Template"
/datum/room_template/base_planner_template/New()
var/G = /obj/structure/blueprint/girder
var/W = /obj/structure/blueprint/window_frame
var/A = /obj/structure/blueprint/airlock_frame
var/F = /obj/structure/blueprint/turf_plating
var/list/X = list(A, F)
var/N
layouts = list(
list(
list(G, G ,G, G, G),
list(G, F, F, F, G),
list(G, F, F, F, G),
list(G, F, F, F, G),
list(G, G, G, G, G),
) = "5x5 Room",
list(
list(G, G ,G, G, G, G, G),
list(G, F, F, F, F, F, G),
list(G, F, F, F, F, F, G),
list(G, F, F, F, F, F, G),
list(G, F, F, F, F, F, G),
list(G, F, F, F, F, F, G),
list(G, G, G, G, G, G, G),
) = "7x7 Room",
list(
list(N, G, G, X, W, X, G, G, N),
list(G, G, F, F, F, F, F, G, G),
list(G, F, F, F, F, F, F, F, G),
list(W, F, F, F, F, F, F, F, W),
list(W, F, F, F, F, F, F, F, W),
list(W, F, F, F, F, F, F, F, W),
list(G, F, F, F, F, F, F, F, G),
list(G, G, F, F, F, F, F, G, G),
list(N, G, G, X, W, X, G, G, N)
) = "9x9 Room with Windows and Airlocks",
list(
list(N, G ,G, G, X, W, X, G, G, G, N),
list(G, G ,F, F, F, F, F, F, F, G, G),
list(G, F ,F, F, F, F, F, F, F, F, G),
list(W, F ,F, F, F, F, F, F, F, F, W),
list(W, F ,F, F, F, F, F, F, F, F, W),
list(X, F ,F, F, F, F, F, F, F, F, X),
list(W, F ,F, F, F, F, F, F, F, F, W),
list(W, F ,F, F, F, F, F, F, F, F, W),
list(G, F ,F, F, F, F, F, F, F, F, G),
list(G, G ,F, F, F, F, F, F, F, G, G),
list(N, G ,G, G, X, W, X, G, G, G, N),
) = "11x11 Room with Windows and Airlocks",
list(
list(N, G ,W, A, A, G, G, G, G, G, N),
list(G, G ,F, F, F, G, F, F, F, G, G),
list(G, F ,F, F, F, G, F, F, F, F, W),
list(G, F ,F, F, F, X, F, F, F, F, X),
list(W, F ,F, F, F, W, F, F, F, F, G),
list(W, F ,F, F, F, G, G, F, F, F, G),
list(W, F ,F, F, F, F, G, G, W, G, G),
list(G, F ,F, F, F, F, W, F, F, F, G),
list(G, F ,F, F, F, F, X, F, F, F, G),
list(G, G ,F, F, F, F, G, F, F, G, G),
list(N, G ,W, X, X, G, G, G, G, G, N),
) = "11x11 Base Template - A",
list(
list(N, G ,G, G, X, W, X, G, G, G, N),
list(G, G ,F, G, F, F, F, G, F, G, G),
list(W, F ,F, W, F, F, F, W, F, F, W),
list(G, F ,F, X, F, F, F, X, F, F, G),
list(G, F ,F, G, F, F, F, G, F, F, G),
list(G, G ,G, G, F, F, F, G, G, G, G),
list(G, F ,F, G, F, F, F, G, F, F, G),
list(G, F ,F, X, F, F, F, X, F, F, G),
list(W, F ,F, W, F, F, F, W, F, F, W),
list(G, G ,F, G, F, F, F, G, F, G, G),
list(N, G ,G, G, X, W, X, G, G, G, N),
) = "11x11 Base Template - B",
list(
list(N, G ,X, X, W, G, G, W, W, G, N),
list(G, G ,F, F, F, F, F, F, F, G, G),
list(G, F ,F, F, F, F, F, F, F, F, G),
list(W, F ,F, F, F, F, F, F, F, F, G),
list(G, F ,F, F, F, F, G, W, X, G, G),
list(G, G ,X, X, G, G, G, F, F, F, G),
list(G, F ,F, F, F, G, F, F, F, F, G),
list(W, F ,F, F, F, X, F, F, F, F, X),
list(G, F ,F, F, F, X, F, F, F, F, X),
list(G, G ,F, F, F, G, F, F, F, G, G),
list(N, G ,G, G, G, G, G, X, X, G, N),
) = "11x11 Base Template - C"
)