Skip to content

Commit

Permalink
Add Caves of Qud-inspired procedural coating and liquid names
Browse files Browse the repository at this point in the history
  • Loading branch information
out-of-phaze committed Feb 1, 2025
1 parent 92db1b8 commit bc61312
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 6 deletions.
5 changes: 5 additions & 0 deletions code/__defines/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@
#define CONFIG_SERVER_JOIN_WHITELIST 3
#define CONFIG_SERVER_CONNECT_WHITELIST 4

// Coating name color config enums
#define CONFIG_COATING_COLOR_NONE 1
#define CONFIG_COATING_COLOR_MIXTURE 2
#define CONFIG_COATING_COLOR_COMPONENTS 3

// Location for server whitelist file to load from.
#define CONFIG_SERVER_WHITELIST_FILE "config/server_whitelist.txt"

Expand Down
15 changes: 13 additions & 2 deletions code/datums/config/config_types/config_game_world.dm
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
/decl/config/toggle/humans_need_surnames,
/decl/config/toggle/roundstart_level_generation,
/decl/config/toggle/lights_start_on,
/decl/config/toggle/on/cisnormativity
/decl/config/toggle/on/cisnormativity,
/decl/config/enum/colored_coating_names
)

/decl/config/num/exterior_ambient_light
Expand Down Expand Up @@ -134,4 +135,14 @@

/decl/config/toggle/on/cisnormativity
uid = "cisnormativity"
desc = "If true, when bodytype is changed in character creation, selected pronouns are also changed."
desc = "If true, when bodytype is changed in character creation, selected pronouns are also changed."

/decl/config/enum/colored_coating_names
uid = "colored_coating_names"
desc = "Determines the coloring of various strings representing coatings on objects (blood, oil, mud, etc)."
default_value = CONFIG_COATING_COLOR_MIXTURE
enum_map = list(
"none" = CONFIG_COATING_COLOR_NONE,
"mixture" = CONFIG_COATING_COLOR_MIXTURE,
"components" = CONFIG_COATING_COLOR_COMPONENTS
)
12 changes: 12 additions & 0 deletions code/game/objects/items/__item.dm
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,13 @@
if(drying_wetness > 0 && drying_wetness != initial(drying_wetness))
desc_comp += "\The [src] is [get_dryness_text()]."

if(coating?.total_volume)
desc_comp += "It is covered in [coating.get_coated_name()]." // It is covered in dilute oily slimy bloody mud.

if(check_rights(R_DEBUG, 0, user))
to_chat(user, "\The [src] has a temperature of [temperature]K.")


return ..(user, distance, "", jointext(desc_comp, "<br/>"))

/obj/item/check_mousedrop_adjacency(var/atom/over, var/mob/user)
Expand Down Expand Up @@ -1308,3 +1312,11 @@ modules/mob/living/human/life.dm if you die, you will be zoomed out.

/obj/item/proc/get_provided_intents(mob/wielder)
return null

/obj/item/get_examine_prefix()
if(coating?.total_volume)
var/coating_string = coating.get_coated_adjectives() // component coloring is handled in here
if(get_config_value(/decl/config/enum/colored_coating_names) == CONFIG_COATING_COLOR_MIXTURE)
coating_string = FONT_COLORED(coating.get_color(), coating_string)
return coating_string
return ..()
2 changes: 1 addition & 1 deletion code/game/objects/items/weapons/towels.dm
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
liquid_adjective = "soaked through"
if(1)
liquid_adjective = "entirely saturated"
to_chat(user, "It is [liquid_adjective] with liquid.")
to_chat(user, "It is [liquid_adjective] with [reagents.get_coated_name()].")

/obj/item/towel/set_material(new_material)
. = ..()
Expand Down
2 changes: 1 addition & 1 deletion code/game/turfs/floors/floor_icon.dm
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
color = get_color()

cut_overlays()
update_height_appearance() // Also refreshes out base layer.
update_height_appearance() // Also refreshes our base layer.
update_floor_icon()

for(var/image/I in decals)
Expand Down
29 changes: 29 additions & 0 deletions code/modules/materials/_materials.dm
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ INITIALIZE_IMMEDIATE(/obj/effect/gas_overlay)
/// If an item has a null paint_verb, it automatically sets it based on material.
var/paint_verb = "painted"

/// What word is used to describe an item covered in/stained by this by default?
/// Can be overridden by get_coated_adjective().
var/coated_adjective = "stained"

/// Chance of a natural wall made of this material dropping a gemstone, if the gemstone_types list is populated.
var/gemstone_chance = 5
/// Assoc weighted list of gemstone material types to weighting.
Expand Down Expand Up @@ -1220,3 +1224,28 @@ INITIALIZE_IMMEDIATE(/obj/effect/gas_overlay)
/// If any value below 0 is returned, it doesn't start processing.
/decl/material/proc/get_time_to_dry_stain(obj/effect/decal/cleanable/blood/stain)
return initial(stain.time_to_dry)

// TODO: Maybe make this use a strengths system like taste?
/// Returns a string to describe an item coated with this reagent (and others).
/// Receives the coating reagent holder as an argument, so coating.my_atom is accessible
/// and it can also conditionally use a different string for primary/non-primary materials, or
/// if another liquid is present, e.g. 'wet bloody muddy shoes'.
/decl/material/proc/get_coated_adjective(datum/reagents/coating)
var/used_color = get_reagent_color(coating)
if(get_config_value(/decl/config/enum/colored_coating_names) == CONFIG_COATING_COLOR_COMPONENTS)
return FONT_COLORED(used_color, coated_adjective)
return coated_adjective

/// Gets the name used to describe a coating with this material as its primary reagent.
/// This is mostly for handling special cases like mud.
/decl/material/proc/get_primary_coating_name(datum/reagents/coating)
// this should probably respect current phase/solution/etc better, but coating sure doesn't
return get_reagent_name(coating, phase_at_temperature())

/// Builds a string to describe a coating made up of this reagent (and others).
/// This reagent will never be the primary reagent, however; that's handled in get_primary_coating_name.
/// Receives the coating as an argument like get_coated_adjective, but also receives the accumulator list
/// for more complex behaviors like adding to the start. It can't reliably handle things like removing
/// another entry because ordering is not guaranteed, so beware if you need something like that.
/decl/material/proc/build_coated_name(datum/reagents/coating, list/accumulator)
accumulator |= get_coated_adjective(coating)
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
value = 0.1
slipperiness = 80
exoplanet_rarity_gas = MAT_RARITY_EXOTIC
coated_adjective = "oily"

// Prevent oil stains from drying.
/decl/material/liquid/lube/get_time_to_dry_stain(obj/effect/decal/cleanable/blood/stain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@

/decl/material/liquid/tar
name = "tar"
solid_name = "asphalt"
uid = "liquid_tar"
coated_adjective = "tarry"
lore_text = "A dark, viscous liquid."
taste_description = "petroleum"
color = "#140b30"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@
)
temperature_burn_milestone_material = /decl/material/liquid/water
can_boil_to_gas = TRUE
coated_adjective = "wet"

/decl/material/liquid/water/build_coated_name(datum/reagents/coating, list/accumulator)
if(length(coating.reagent_volumes) > 1)
accumulator.Insert(1, "dilute") // dilute always comes first! also this is intentionally not colored in component color mode
return // don't insert 'wet'
..()

// make salty water named saltwater
/decl/material/liquid/water/get_reagent_name(datum/reagents/holder, phase = MAT_PHASE_LIQUID)
if(phase == MAT_PHASE_LIQUID && holder?.get_primary_reagent_decl() == src)
if(REAGENT_VOLUME(holder, /decl/material/solid/sodiumchloride))
return "saltwater"
return ..() // just use the default handling

// make pure water named fresh water
/decl/material/liquid/water/get_reagent_name(datum/reagents/holder, phase = MAT_PHASE_LIQUID)
. = ..()
// length == 1 implies primary reagent, so checking both is redundant
if(phase == MAT_PHASE_LIQUID && length(holder?.reagent_volumes) == 1)
return "fresh [.]"
return

/decl/material/liquid/water/affect_blood(var/mob/living/M, var/removed, var/datum/reagents/holder)
..()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
solid_name = "snow"
gas_name = "steam"
adjective_name = "snow"
coated_adjective = "snowy"
color = COLOR_WHITE
codex_name = null
uid = "solid_snow"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,17 @@
/decl/flooring/mud,
/decl/flooring/dirt
)
solution_name = "mud"
coated_adjective = "muddy"

// todo: make mud either its own material or a mix of dirt and water
// or let dirt be in the liquid volumes list for mud?
// would a ball of mud just be a ball of dirt coated with water?
// or would water be part of its matter?
// well anyway.
// for now, at least, we assume dirt coatings are always mud.
/decl/material/solid/soil/get_primary_coating_name(datum/reagents/coating)
return solution_name

/decl/material/solid/hematite
name = "hematite"
Expand Down
50 changes: 48 additions & 2 deletions code/modules/reagents/Chemistry-Holder.dm
Original file line number Diff line number Diff line change
Expand Up @@ -925,14 +925,60 @@ var/global/datum/reagents/sink/infinite_reagent_sink = new

return trans_to_holder(target.reagents, amount, multiplier, copy, defer_update = defer_update, transferred_phases = transferred_phases)

/* Atom reagent creation - use it all the time */

/datum/reagents/proc/get_skimmable_reagents()
for(var/mat in reagent_volumes)
var/decl/material/reagent = GET_DECL(mat)
if(reagent.skimmable)
LAZYADD(., mat)

/// Used to return strings like "dilute blood" for use in phrases like "It's covered in dilute oily slimy bloody mud!"
/// This is explicitly inspired by Caves of Qud, by the way.
/datum/reagents/proc/get_coated_name()
/// arbitrary number, number of words that will be included in the name. will always have primary in addition
var/const/MAX_COATING_NAMES = 4
if(!total_volume)
return null
// sort reagents from low to high, so largest reagent comes last (feels most impactful? idk, it's arbitrary)
var/list/volumes_temp = sortTim(reagent_volumes.Copy(), /proc/cmp_numeric_asc, associative = TRUE)
var/list/accumulator = list()
var/decl/material/primary_reagent_decl = get_primary_reagent_decl() // this also sets src.primary_reagent to the typepath
for(var/reagent_type in volumes_temp)
if(reagent_type == primary_reagent)
continue // added later
var/decl/material/reagent = GET_DECL(reagent_type)
reagent.build_coated_name(src, accumulator)
if(length(accumulator) >= MAX_COATING_NAMES)
break
var/primary_name = primary_reagent_decl.get_primary_coating_name(src)
if(get_config_value(/decl/config/enum/colored_coating_names) == CONFIG_COATING_COLOR_COMPONENTS)
var/primary_color = primary_reagent_decl.get_reagent_color(src)
primary_name = FONT_COLORED(primary_color, primary_name)
accumulator += primary_name // add primary to the end
. = jointext(accumulator, " ") // dilute oily slimy bloody mud
if(get_config_value(/decl/config/enum/colored_coating_names) == CONFIG_COATING_COLOR_MIXTURE)
. = FONT_COLORED(get_color(), .)

/// Used to return strings like "inky bloody muddy snowy oily wet" for use in phrases like "You are wearing some inky bloody muddy snowy oily wet leather boots."
/// This is explicitly inspired by Caves of Qud, by the way.
/datum/reagents/proc/get_coated_adjectives()
var/const/MAX_COATING_ADJECTIVES = 5 // arbitrary number, limit how many reagents will show up in the name on examine
if(!total_volume)
return null
// sort reagents from low to high, so primary reagent comes last (feels most impactful? idk, it's arbitrary)
// todo: maybe at some point we could make staining have some kind of priority to sort by?
var/list/volumes_temp = sortTim(reagent_volumes.Copy(), /proc/cmp_numeric_asc, associative = TRUE)
var/list/accumulator = list()
for(var/reagent_type in volumes_temp)
var/decl/material/reagent = GET_DECL(reagent_type)
var/coated_name = reagent.get_coated_adjective(src)
accumulator |= coated_name
if(length(accumulator) >= MAX_COATING_ADJECTIVES)
break
. = jointext(accumulator, " ") // bloody muddy inky slimy wet
if(get_config_value(/decl/config/enum/colored_coating_names) == CONFIG_COATING_COLOR_MIXTURE)
. = FONT_COLORED(get_color(), .)

/* Atom reagent creation - use it all the time */
/atom/proc/create_reagents(var/max_vol)
if(reagents)
log_debug("Attempted to create a new reagents holder when already referencing one: [log_info_line(src)]")
Expand Down
1 change: 1 addition & 0 deletions code/modules/reagents/chems/chems_blood.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
taste_mult = 1.3
glass_name = "tomato juice"
glass_desc = "Are you sure this is tomato juice?"
coated_adjective = "bloody"
value = 2.5
opacity = TRUE
min_fluid_opacity = FLUID_MAX_ALPHA
Expand Down
1 change: 1 addition & 0 deletions code/modules/reagents/chems/chems_oil.dm
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
affect_blood_on_ingest = 0
affect_blood_on_inhale = 0
slipperiness = 8
coated_adjective = "oily"

/decl/material/liquid/oil/plant
name = "plant oil"
Expand Down

0 comments on commit bc61312

Please sign in to comment.