Expedition Fluff Expansion - Tents & Sleeping Bags (#19362)

Adds tents, in two variants (A larger and smaller one). These utilise a
new large structure framework allowing a single item to spawn multiple
structures (The parts of the tent) in the correct places. The tents
count as inside, so can be used to shelter from weather. Tents have
roofs which become semi-transparent when entered. There are also
mappable effects which generate a tent at round start for mapping.

Adds sleeping bags, which can be attached to the outside of bags for
easy carrying. Ideally this would be an accessories interaction, but
backpacks aren't clothing and converting them to be such is outside of
the scope of this PR.

Adds foldable tables, for setting up on expeditions.
This commit is contained in:
Sparky
2024-06-19 19:51:25 +00:00
committed by GitHub
parent 0623b9db53
commit eff6ca9a7b
15 changed files with 667 additions and 18 deletions
+386
View File
@@ -0,0 +1,386 @@
/*
Tents
*/
/datum/large_structure/tent
stages = list("poles" = STAGE_DISASSEMBLED,
"canvas" = STAGE_DISASSEMBLED,
"guy lines" = STAGE_DISASSEMBLED,
"pegs" = STAGE_DISASSEMBLED)
component_structure = /obj/structure/component/tent_canvas
source_item_type = /obj/item/tent
/**
* The state name of an overlay in `icons/obj/item/tent_decals.dmi`
* Used for branded tents, such as the SCC base camp tent
*/
var/decal
/datum/large_structure/tent/build_structures()
. = ..()
var/list/roofs = list()
for(var/obj/structure/component/tent_canvas/C in grouped_structures)
if(dir & (NORTH | SOUTH))
if(C.x == x1 || C.x == x2)
C.icon_state = "canvas_dir"
var/mid = Mean(x1, x2)
if(C.x < mid)
C.dir = WEST
else
C.dir = EAST
else
if(C.y == y1 || C.y == y2)
C.icon_state = "canvas_dir"
var/mid = Mean(y1, y2)
if(C.y < mid)
C.dir = SOUTH
else
C.dir = NORTH
var/obj/structure/component/tent_canvas/roof/roof = new /obj/structure/component/tent_canvas/roof(C.loc)
roofs += roof
roof.color = color
roof.dir = C.dir
if(decal && C.x == x1 && C.y == y1)
roof.AddOverlays(overlay_image('icons/obj/item/tent_decals.dmi', decal, flags=RESET_COLOR))
roof.icon_state = "roof_[get_roof_type(C)]"
grouped_structures += roofs
/datum/large_structure/tent/structure_entered(turf/entry_point, atom/movable/entering)
. = ..()
if(!.)
return
var/mob/M = entering
if(!M.renderers)
return
var/atom/movable/renderer/roofs/roof_plane = M.renderers["[ROOF_PLANE]"]
if(roof_plane)
roof_plane.alpha = 76
/datum/large_structure/tent/mob_moved(mob/mover, turf/exit_point)
. = ..()
if(!.)
var/atom/movable/renderer/roofs/roof_plane = mover.renderers["[ROOF_PLANE]"]
if(roof_plane)
roof_plane.alpha = 255
/**
* Determines roof state over each canvas structure
* Returns `edge` for structures on the edge of the tent, so they don't cover the walls
* Returns `mid` for structures in the centre of the tent, for odd number widths
* Otherwise returns `norm` for other structures
*/
/datum/large_structure/tent/proc/get_roof_type(var/obj/structure/component/tent_canvas/canvas)
var/edge1
var/edge2
var/axis
if(dir & (NORTH | SOUTH))
edge1 = x1
edge2 = x2
axis = canvas.x
else
edge1 = y1
edge2 = y2
axis = canvas.y
if(axis == edge1 || axis == edge2)
return "edge"
else if(axis == Mean(edge1, edge2))
return "mid"
return "norm"
/obj/item/tent
name = "expedition tent"
desc = "A rolled up tent, ready to be assembled to make a base camp, shelter, or just a cozy place to chat."
desc_info = "Drag this to yourself to begin assembly. This will take some time, in 4 stages. Others can start working on the other stages by dragging it to themselves as well."
icon = 'icons/obj/item/camping.dmi'
icon_state = "tent"
item_state = "tent"
contained_sprite = TRUE
w_class = ITEMSIZE_LARGE
color = "#58a178"
var/width = 2
var/length = 3
var/decal
var/datum/large_structure/tent/my_tent
/obj/item/tent/Initialize()
. = ..()
w_class = min(ceil(width * length / 1.5), ITEMSIZE_IMMENSE) // 2x2 = ITEMSIZE_NORMAL
desc += "\nThis one is [width] x [length] in size."
/obj/item/tent/Destroy()
if(my_tent)
my_tent.source_item = null
if(!my_tent.grouped_structures)
QDEL_NULL(my_tent)
return ..()
/obj/item/tent/MouseDrop(over_object, src_location, over_location)
. = ..()
if(use_check(usr) || !Adjacent(usr))
return
var/turf/T = get_turf(src)
if(istype(T))
deploy_tent(T, usr)
/obj/item/tent/proc/deploy_tent(var/turf/target, var/mob/user)
if(my_tent)
if(my_tent.origin == get_turf(src)) //Not moved
my_tent.assemble(1 SECOND, user)
return
else
QDEL_NULL(my_tent)
var/deploy_dir = get_compass_dir(user,target)
if(target == get_turf(user))
deploy_dir = user.dir
my_tent = new /datum/large_structure/tent(src)
setup_my_tent(deploy_dir, target)
my_tent.assemble(1 SECOND, user)
/obj/item/tent/proc/setup_my_tent(var/deploy_dir, var/turf/target)
my_tent.name = name
my_tent.color = color
my_tent.decal = decal
my_tent.dir = deploy_dir
my_tent.z1 = target.z
my_tent.z2 = target.z
my_tent.source_item_type = type
my_tent.source_item = src
my_tent.origin = get_turf(src)
if(deploy_dir & NORTH)
my_tent.x1 = target.x - floor((width-1)/2)
my_tent.x2 = target.x + ceil((width-1)/2)
my_tent.y1 = target.y
my_tent.y2 = target.y + (length-1)
else if(deploy_dir & SOUTH)
my_tent.x1 = target.x - ceil((width-1)/2)
my_tent.x2 = target.x + floor((width-1)/2)
my_tent.y1 = target.y - (length-1)
my_tent.y2 = target.y
else if(deploy_dir & EAST)
my_tent.x1 = target.x
my_tent.x2 = target.x + (length-1)
my_tent.y1 = target.y - ceil((width-1)/2)
my_tent.y2 = target.y + floor((width-1)/2)
else
my_tent.x1 = target.x - (length-1)
my_tent.x2 = target.x
my_tent.y1 = target.y - floor((width-1)/2)
my_tent.y2 = target.y + ceil((width-1)/2)
/obj/item/tent/big
name = "base camp tent"
color = "#2e3763"
width = 3
length = 4
/obj/item/tent/big/scc
name = "scc base camp tent"
decal = "scc"
/obj/item/tent/mining
name = "miners' tent"
color = "#8b7242"
width = 3
length = 3
/obj/structure/component/tent_canvas
name = "tent canvas"
icon = 'icons/obj/item/camping.dmi'
icon_state = "canvas"
item_state = "canvas"
anchored = TRUE
density = TRUE
atom_flags = ATOM_FLAG_CHECKS_BORDER
atmos_canpass = CANPASS_ALWAYS //Tents are not air tight
layer = ABOVE_HUMAN_LAYER
/obj/structure/component/tent_canvas/CanPass(atom/movable/mover, turf/target, height, air_group)
. = ..()
if(icon_state == "canvas")
return TRUE //Non-directional, always allow passage
if(get_dir(loc, target) & dir)
return !density
return TRUE
/obj/structure/component/tent_canvas/CheckExit(atom/movable/O, turf/target)
. = ..()
if(icon_state == "canvas")
return TRUE //Non-directional, always allow passage
if(get_dir(O.loc, target) & dir)
return !density
return TRUE
/obj/structure/component/tent_canvas/MouseDrop(over_object, src_location, over_location)
..()
if(use_check(usr) || !Adjacent(usr))
return
part_of.disassemble(2 SECONDS, usr, src)
/obj/structure/component/tent_canvas/roof
plane = ROOF_PLANE
//Pre-fabricated tents for mapping
/obj/effect/tent
name = "Pre-frabricated expedition tent"
icon = 'icons/effects/map_effects.dmi'
icon_state = "portal_side_a"
var/builds = /obj/item/tent
/obj/effect/tent/Initialize(mapload, ...)
. = ..()
var/obj/item/tent/tent_item = new builds(src)
var/datum/large_structure/tent/tent = new /datum/large_structure/tent(src)
tent_item.my_tent = tent
tent_item.setup_my_tent(dir, get_turf(src))
tent.get_target_turfs(null, TRUE)
tent.build_structures()
qdel(tent_item)
qdel(src)
/*
Sleeping bags
*/
/obj/item/sleeping_bag
name = "sleeping bag"
desc = "A rolled up sleeping bag, ready to be taken on a camping trip."
desc_extended = "This item can be attached to a backpack."
icon = 'icons/obj/item/camping.dmi'
icon_state = "sleepingbag"
item_state = "sleepingbag"
contained_sprite = TRUE
w_class = ITEMSIZE_LARGE
/obj/item/sleeping_bag/Initialize(mapload, ...)
. = ..()
if(!color)
color = pick(COLOR_NAVY_BLUE, COLOR_GREEN, COLOR_MAROON, COLOR_VIOLET, COLOR_OLIVE, COLOR_SEDONA)
/obj/item/sleeping_bag/afterattack(obj/target, mob/user, proximity)
. = ..()
if(!proximity)
return
var/turf/T = target
if(istype(T))
unroll(T, user)
/obj/item/sleeping_bag/attack_self(mob/user)
. = ..()
var/turf/T = get_turf(user)
if(istype(T))
unroll(T, user)
/obj/item/sleeping_bag/MouseDrop(over_object, src_location, over_location)
. = ..()
if(use_check(usr) || !Adjacent(usr))
return
var/turf/T = get_turf(src)
if(istype(T))
unroll(T, usr)
/**
* Creates sleeping bag structure on the target turf, deleting this item in the process
*/
/obj/item/sleeping_bag/proc/unroll(var/turf/target, var/mob/user)
user.visible_message(SPAN_NOTICE("\The [user] unrolls \the [src]."))
var/obj/structure/bed/sleeping_bag/S = new /obj/structure/bed/sleeping_bag(target, MATERIAL_CLOTH)
S.color = color
qdel(src)
/obj/item/sleeping_bag/mining
color = COLOR_DARK_BROWN
/obj/structure/bed/sleeping_bag
name = "sleeping bag"
desc = "A bag for sleeping in. Great for trying to pretend you're somewhere more comfortable than you really are."
icon = 'icons/obj/item/camping.dmi'
icon_state = "sleepingbag_floor"
base_icon = "sleepingbag_floor"
density = FALSE
anchored = FALSE
buckling_sound = 'sound/items/drop/cloth.ogg'
held_item = /obj/item/sleeping_bag
can_dismantle = FALSE
can_pad = FALSE
/obj/structure/bed/sleeping_bag/update_icon()
return
/obj/structure/bed/sleeping_bag/buckle(mob/living/M)
. = ..()
var/image/I = overlay_image(icon, "[base_icon]_top", color)
M.AddOverlays(I)
/obj/structure/bed/sleeping_bag/unbuckle()
if(buckled)
buckled.update_icon()
. = ..()
/obj/structure/bed/sleeping_bag/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params)
. = ..()
if(use_check(usr) || !Adjacent(usr))
return
if(!ishuman(usr) && (!isrobot(usr) || isDrone(usr))) //Humans and borgs can roll, but not drones
return
if(buckled)
to_chat(usr, SPAN_WARNING("You can't roll up \the [src] while someone is sleeping inside."))
return
var/obj/item/sleeping_bag/S = new held_item(get_turf(src))
S.color = color
usr.visible_message(SPAN_NOTICE("\The [usr] rolls up \the [src]."))
qdel(src)
/*
Folding Tables
*/
/obj/item/material/folding_table
name = "folding table"
desc = "A temporary surface, for when you need a table, but only for a little while."
icon = 'icons/obj/item/camping.dmi'
icon_state = "table_folded"
item_state = "table_folded"
contained_sprite = TRUE
default_material = MATERIAL_ALUMINIUM
/obj/item/material/folding_table/attack_self(mob/user)
if(use_check(user) || !Adjacent(user))
return
deploy(get_turf(user), user.dir, user)
/obj/item/material/folding_table/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
if(use_check(user) || !Adjacent(user))
return
if(isturf(target))
deploy(target, user.dir, user)
/obj/item/material/folding_table/proc/deploy(var/turf/T, var/direction, var/mob/user)
new /obj/structure/table/rack/folding_table(T)
user.visible_message(SPAN_NOTICE("\The [user] sets up \the [src]."))
qdel(src)
/obj/structure/table/rack/folding_table
name = "folding table"
desc = "A temporary surface, for when you need a table, but only for a little while."
icon_state = "camping_table"
table_mat = MATERIAL_ALUMINIUM
/obj/structure/table/rack/folding_table/dismantle(obj/item/wrench/W, mob/user)
return FALSE
/obj/structure/table/rack/folding_table/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params)
. = ..()
if(use_check(usr) || !Adjacent(usr))
return
if(!ishuman(usr) && (!isrobot(usr) || isDrone(usr))) //Humans and borgs can collapse, but not drones
return
new /obj/item/material/folding_table(get_turf(src))
usr.visible_message(SPAN_NOTICE("\The [usr] collapses \the [src]."))
qdel(src)
/obj/item/material/stool/chair/folding/camping
default_material = MATERIAL_ALUMINIUM
@@ -24,6 +24,14 @@
allow_quick_empty = TRUE
empty_delay = 0.5 SECOND
var/straps = FALSE // Only really used for the verb.
/**
* References a sleeping bag attached to this bag. Should convert this to use accessories later, but that means making backpacks clothing.
*/
var/obj/item/sleeping_bag/attached_bag
/**
* Suffix used for overlays with an attached sleeping bag, because satchels are at people's sides while other bags are on people's backs.
*/
var/attached_icon = "backpack"
/obj/item/storage/backpack/Initialize()
. = ..()
@@ -31,6 +39,10 @@
alpha_mask = "normal"
verbs += /obj/item/storage/backpack/proc/adjust_backpack_straps
/obj/item/storage/backpack/Destroy()
QDEL_NULL(attached_bag)
. = ..()
/obj/item/storage/backpack/proc/adjust_backpack_straps()
set name = "Adjust Bag Straps"
set desc = "Adjust your bag straps."
@@ -79,6 +91,44 @@
return 0
return 1
/obj/item/storage/backpack/attackby(obj/item/attacking_item, mob/user, params)
if(istype(attacking_item, /obj/item/sleeping_bag) && !attached_bag && ishuman(user))
var/mob/living/carbon/human/H = user
attached_bag = attacking_item
update_icon()
if(H.back == src)
H.update_inv_back() // Add overlay to backpack if on back
H.drop_from_inventory(attached_bag)
attached_bag.loc = null
return
return ..()
/obj/item/storage/backpack/update_icon()
. = ..()
ClearOverlays()
if(attached_bag)
var/image/I = overlay_image(attached_bag.icon, "[attached_bag.icon_state]_[attached_icon]", attached_bag.color)
AddOverlays(I)
/obj/item/storage/backpack/get_mob_overlay(mob/living/carbon/human/H, mob_icon, mob_state, slot, main_call)
var/image/I = ..()
if(slot == slot_back_str && attached_bag)
var/image/over = overlay_image(attached_bag.icon, "[attached_bag.icon_state]_[attached_icon]_ba", attached_bag.color)
I.AddOverlays(over)
return I
/obj/item/storage/backpack/AltClick(mob/usr)
if(attached_bag && ishuman(usr))
var/mob/living/carbon/human/H = usr
H.put_in_hands(attached_bag)
attached_bag = null
update_icon()
if(H.back == src)
H.update_inv_back()
return
return ..()
/*
* Backpack Types
*/
@@ -333,6 +383,7 @@
icon_state = "satchel"
item_state = "satchel"
straps = TRUE
attached_icon = "satchel"
/obj/item/storage/backpack/satchel/leather/withwallet/New()
..()
@@ -927,6 +978,7 @@
contained_sprite = FALSE
sprite_sheets = list(BODYTYPE_VAURCA = 'icons/mob/species/vaurca/back.dmi', BODYTYPE_VAURCA_BULWARK = 'icons/mob/species/bulwark/back.dmi')
var/hooded = FALSE
var/cape_backing_state = "cape_backing"
/obj/item/storage/backpack/cloak/verb/toggle_cloak_hood()
set name = "Toggle Cloak Hood"
@@ -945,7 +997,7 @@
/obj/item/storage/backpack/cloak/get_mob_overlay(var/mob/living/carbon/human/human, var/mob_icon, var/mob_state, var/slot)
var/image/I = ..()
var/image/cape_backing = image(mob_icon, null, "[initial(icon_state)]_backing", MOB_SHADOW_LAYER)
var/image/cape_backing = image(mob_icon, null, "[icon_state]_backing", MOB_SHADOW_LAYER)
I.AddOverlays(cape_backing)
return I
@@ -102,6 +102,7 @@
new /obj/item/clothing/head/hardhat/orange(src)
new /obj/item/device/radio(src)
new /obj/item/device/flashlight/lantern(src)
new /obj/item/sleeping_bag/mining(src)
// Merchant
/obj/structure/closet/secure_closet/merchant
+167
View File
@@ -0,0 +1,167 @@
/*
* # Large Structure
*
* This datum controls a large, multi-tile structure, capable of being assembled or disassembled as a unit (i.e. tents).
*/
/datum/large_structure
/**
* Name of this structure
*/
var/name = "structure"
/**
* Turfs this structure exists on
*/
var/list/turf/target_turfs = list()
/**
* Structures that are part of this structure
*/
var/list/obj/structure/component/grouped_structures = list()
/**
* Mobs assembling or disassembling this structure
*/
var/list/mob/interacting = list()
/**
* A list of the stages required to assemble or disassemble the large structure. Format: "string" = STAGE
*/
var/list/stages = list()
/**
* The smaller structure which makes up this large one
*/
var/component_structure
/**
* The type item used to set up this structure in the first place, made on disassembly
*/
var/source_item_type
/**
* The item used to set up this structure, deleted after assembly
*/
var/obj/item/source_item
/**
* The turf this structure started from. Used to check if the source_item building this has moved.
*/
var/turf/origin
var/color
var/dir
var/x1
var/x2
var/y1
var/y2
var/z1
var/z2
/**
* Returns the first stage with value `type`
*/
/datum/large_structure/proc/get_next_stage(var/type)
for(var/stage in stages)
if(stages[stage] == type)
return stage
/datum/large_structure/proc/assemble(var/time_per_structure, var/mob/user)
if(user in interacting)
to_chat(user, SPAN_INFO("You are already working on assembling \the [src]."))
return FALSE
var/stage_to_do = get_next_stage(STAGE_DISASSEMBLED)
if(!stage_to_do)
to_chat(user, SPAN_INFO("There is no more work to be done assembling \the [src]."))
return FALSE
if(!LAZYLEN(target_turfs))
get_target_turfs(user)
interacting += user
user.visible_message(SPAN_NOTICE("\The [user] begins assembling \the [src]'s [stage_to_do]."))
stages[stage_to_do] = STAGE_PROGRESS
if(!do_after(user, time_per_structure * LAZYLEN(target_turfs)))
stages[stage_to_do] = STAGE_DISASSEMBLED
return
user.visible_message(SPAN_NOTICE("\The [user] finishes assembling the [stage_to_do]."))
stages[stage_to_do] = STAGE_ASSEMBLED
interacting -= user
if(get_next_stage(STAGE_DISASSEMBLED)) //Still work to do
assemble(time_per_structure, user)
return FALSE
if(get_next_stage(STAGE_PROGRESS)) //Still work being done
return FALSE
build_structures()
QDEL_NULL(source_item)
return TRUE
/datum/large_structure/proc/get_target_turfs(var/mob/user, var/force = FALSE)
target_turfs = block(x1,y1,z1,x2,y2,z2)
for(var/turf/T in target_turfs)
if(!(istype(T) || force))
to_chat(user, SPAN_ALERT("You cannot set up \the [src] here. Try and find a big enough solid surface."))
return FALSE
/datum/large_structure/proc/build_structures()
for(var/turf/T in target_turfs)
var/obj/structure/component/C = new component_structure(T)
C.part_of = src
C.color = color
grouped_structures += C
RegisterSignal(T, COMSIG_ATOM_ENTERED, PROC_REF(structure_entered), override = TRUE)
/datum/large_structure/proc/disassemble(var/time_per_structure, var/mob/user)
if(user in interacting)
to_chat(user, SPAN_INFO("You are already working on disassembling \the [src]."))
return FALSE
var/stage_to_do = get_next_stage(STAGE_ASSEMBLED)
if(!stage_to_do)
to_chat(user, SPAN_INFO("There is no more work to be done disassembling \the [src]."))
return FALSE
interacting += user
user.visible_message(SPAN_NOTICE("\The [user] begins disassembling \the [src]'s [stage_to_do]."))
stages[stage_to_do] = STAGE_PROGRESS
if(!do_after(user, time_per_structure * LAZYLEN(grouped_structures)))
stages[stage_to_do] = STAGE_ASSEMBLED
return FALSE
user.visible_message(SPAN_NOTICE("\The [user] finishes disassembling the [stage_to_do]."))
stages[stage_to_do] = STAGE_DISASSEMBLED
interacting -= user
if(get_next_stage(STAGE_ASSEMBLED)) //Still work to do
return disassemble(time_per_structure, user)
if(get_next_stage(STAGE_PROGRESS)) //Still work being done
return
QDEL_LIST(grouped_structures)
var/obj/item/I = new source_item_type(get_turf(user))
I.color = color
qdel(src)
return TRUE
/datum/large_structure/proc/structure_entered(var/turf/entry_point, var/atom/movable/entering)
if(!isliving(entering))
return FALSE
RegisterSignal(entering, COMSIG_MOVABLE_MOVED, PROC_REF(mob_moved), override = TRUE)
return TRUE
/datum/large_structure/proc/mob_moved(var/mob/mover)
if(!(get_turf(mover) in target_turfs))
UnregisterSignal(mover, COMSIG_MOVABLE_MOVED)
return FALSE
return TRUE
/obj/structure/component
var/datum/large_structure/part_of
/obj/structure/component/Destroy()
if(part_of)
part_of.grouped_structures -= src
return ..()