mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-19 08:26:48 +01:00
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may not be viewable. --> <!-- You can view Contributing.MD for a detailed description of the pull request process. --> ## About The Pull Request <!-- Describe The Pull Request. Please be sure every change is documented or this can delay review and even discourage maintainers from merging your PR! --> ## Why It's Good For The Game <!-- Argue for the merits of your changes and how they benefit the game, especially if they are controversial and/or far reaching. If you can't actually explain WHY what you are doing will improve the game, then it probably isn't good for the game in the first place. --> ## Changelog <!-- If your PR modifies aspects of the game that can be concretely observed by players or admins you should add a changelog. If your change does NOT meet this description, remove this section. Please note that maintainers freely reserve the right to remove and add tags should they deem it appropriate. You can attempt to finagle the system all you want, but it's best to shoot for clear communication right off the bat. --> 🆑 fix: fixed /obj/hitsound_throwhit() runtiming when src has marterial_primary set to "DISABLED" /🆑 <!-- Both 🆑's are required for the changelog to work! You can put your name to the right of the first 🆑 if you want to overwrite your GitHub username as author ingame. --> <!-- You can use multiple of the same prefix (they're only used for the icon ingame) and delete the unneeded ones. Despite some of the tags, changelogs should generally represent how a player might be affected by the changes rather than a summary of the PR's contents. -->
380 lines
12 KiB
Plaintext
380 lines
12 KiB
Plaintext
//* This file is explicitly licensed under the MIT license. *//
|
|
//* Copyright (c) 2023 Citadel Station developers. *//
|
|
|
|
//? TL;DR
|
|
//?
|
|
//? Three ways of using materials
|
|
//?
|
|
//? 1. You only need one material
|
|
//? - set material_parts to MATERIAL_DEFAULT_NONE or a material typepath / id
|
|
//? - set material_costs to a number to indicate how much cm3 of the material is in there
|
|
//? - for items that can be made of multiple materials (most items), set material_constraints to a set of MATERIAL_CONSTRAINT flags that indicate the required constraint_flags on the material. combined flags are stricter
|
|
//? - on update, update_material_single will be called; hook modifications to this
|
|
//? - on init, the system will call update_material_single for you.
|
|
//
|
|
//?
|
|
//? 2. You need multiple materials, and your item is rare enough there isn't more than a few hundred of it
|
|
//? - set material_parts to a k-v list.
|
|
//? note that the key needs to be player-readable
|
|
//?
|
|
//? example: material_parts = list("structure" = /datum/prototype/material/steel, "reinforcement" = /datum/prototype/material/wood)
|
|
//?
|
|
//? - set material_costs to an ordered list of costs
|
|
//?
|
|
//? example: list(2000, 1000) = 1 sheet of steel and 0.5 sheets of wood as per above
|
|
//?
|
|
//? - set material_constraints to an ordered list of constraints corresponding to the parts
|
|
//?
|
|
//? example: list(MATERIAL_CONSTRAINT_RIGID, MATERIAL_CONSTRAINT_UNCONSTRAINED)
|
|
//?
|
|
//? - update_material_multi will be called with list of keys to instances as values
|
|
//? so you can implement your behaviors there
|
|
//?
|
|
//? 3. You need multiple materials, and your object is spammed on the map and you need it to be more efficient
|
|
//? - Define material vars yourself. a hard-coded material variable is more than 10x as efficient as setting it
|
|
//? in material_parts list.
|
|
//? - Override the procs in the abstraction API to point to and use those variables.
|
|
//? - See [code/game/objects/structures/girder.dm] for an example.
|
|
//? - The system will still call update_material_multi for you as long as you call the
|
|
//? parts API instead of the abstraction API directly. It will, however, not call it with list/parts.
|
|
//? - **Do not ever call the abstraction API directly.**
|
|
|
|
//* Base API
|
|
//! Override these procs to implement behavior.
|
|
//! Do not skip parent calls, as these are not meant to be overridden
|
|
|
|
/obj/get_materials(respect_multiplier)
|
|
. = isnull(materials_base)? list() : materials_base.Copy()
|
|
if(islist(material_parts))
|
|
for(var/i in 1 to length(material_parts))
|
|
var/datum/prototype/material/mat = material_parts[material_parts[i]]
|
|
.[mat.id] += material_costs[i]
|
|
else if(material_parts == MATERIAL_DEFAULT_DISABLED)
|
|
else if(material_parts == MATERIAL_DEFAULT_ABSTRACTED)
|
|
var/list/got = material_get_parts()
|
|
for(var/i in 1 to length(got))
|
|
var/key = got[i]
|
|
var/datum/prototype/material/mat = got[key]
|
|
.[mat.id] += material_costs[i]
|
|
else
|
|
var/datum/prototype/material/mat = material_parts
|
|
.[mat.id] += material_costs
|
|
if(respect_multiplier && material_multiplier != 1)
|
|
for(var/key in .)
|
|
.[key] *= material_multiplier
|
|
|
|
/**
|
|
* sets our base materials
|
|
*
|
|
* * note that this takes material ids, not instances, unlike set_material_part(s).
|
|
*
|
|
* @params
|
|
* * materials - material ids associated to costs
|
|
*/
|
|
/obj/proc/set_materials_base(list/materials)
|
|
obj_flags |= OBJ_MATERIALS_MODIFIED
|
|
materials_base = materials.Copy()
|
|
|
|
/**
|
|
* get base material amounts
|
|
*/
|
|
/obj/proc/get_base_material_amounts(respect_multiplier)
|
|
. = isnull(materials_base)? list() : materials_base.Copy()
|
|
if(respect_multiplier && material_multiplier != 1)
|
|
for(var/key in .)
|
|
.[key] *= material_multiplier
|
|
|
|
//* Parts API
|
|
//! These cannot be overridden, and instead call the abstraction API.
|
|
//! This restriction is for code organization reasons.
|
|
|
|
/**
|
|
* initialize materials
|
|
*/
|
|
/obj/proc/init_material_parts()
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
obj_flags |= OBJ_MATERIAL_INITIALIZED
|
|
if(islist(material_parts))
|
|
var/list/parts = list()
|
|
for(var/key in material_parts)
|
|
var/datum/prototype/material/result = RSmaterials.fetch_or_defer(key)
|
|
switch(result)
|
|
if(REPOSITORY_FETCH_DEFER)
|
|
// todo: handle this
|
|
result = null
|
|
parts[key] = result
|
|
update_material_multi(parts)
|
|
else if(material_parts == MATERIAL_DEFAULT_DISABLED)
|
|
else if(material_parts == MATERIAL_DEFAULT_ABSTRACTED)
|
|
material_init_parts()
|
|
// skip specifying parts because abstracted
|
|
update_material_multi()
|
|
else
|
|
var/datum/prototype/material/result = RSmaterials.fetch_or_defer(material_parts)
|
|
switch(result)
|
|
if(REPOSITORY_FETCH_DEFER)
|
|
// todo: handle this
|
|
result = null
|
|
update_material_single((material_parts = result))
|
|
|
|
/**
|
|
* forces a material update
|
|
*/
|
|
/obj/proc/update_material_parts()
|
|
if(islist(material_parts))
|
|
update_material_multi(material_parts)
|
|
else if(material_parts == MATERIAL_DEFAULT_DISABLED)
|
|
else if(material_parts == MATERIAL_DEFAULT_ABSTRACTED)
|
|
update_material_multi()
|
|
else
|
|
update_material_single(material_parts)
|
|
|
|
/**
|
|
* @return key-value list of material part keys to ids
|
|
*/
|
|
/obj/proc/get_material_part_ids()
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
. = material_get_parts()
|
|
for(var/key in .)
|
|
var/datum/prototype/material/mat = .[key]
|
|
if(isnull(mat))
|
|
continue
|
|
.[key] = mat.id
|
|
|
|
/**
|
|
* @return material id of part key. null if part doesn't exist.
|
|
*/
|
|
/obj/proc/get_material_part_id(part)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
var/datum/prototype/material/mat = material_get_part(part)
|
|
return mat?.id
|
|
|
|
/**
|
|
* @return key-value list of material part keys to instances
|
|
*/
|
|
/obj/proc/get_material_parts()
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
return material_get_parts()
|
|
|
|
/**
|
|
* @return material instance
|
|
*/
|
|
/obj/proc/get_material_part(part)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
RETURN_TYPE(/datum/prototype/material)
|
|
return material_get_part(part)
|
|
|
|
/**
|
|
* sets a single material part
|
|
*
|
|
* @params
|
|
* * part - part key. **undefined behavior if it does not exist.**
|
|
* * material - material. ids and paths are not allowed for performance reasons.
|
|
*/
|
|
/obj/proc/set_material_part(part, datum/prototype/material/material)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
obj_flags |= OBJ_MATERIAL_PARTS_MODIFIED
|
|
material_set_part(part, material)
|
|
if(obj_flags & OBJ_MATERIAL_INITIALIZED)
|
|
update_material_parts()
|
|
|
|
/**
|
|
* sets our material parts to a list by key / value. values should be material datums.
|
|
* ids and typepaths are not allowed in part_instances for performance reasons.
|
|
*/
|
|
/obj/proc/set_material_parts(list/part_instances)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
obj_flags |= OBJ_MATERIAL_PARTS_MODIFIED
|
|
for(var/key in part_instances)
|
|
material_set_part(key, part_instances[key])
|
|
if(obj_flags & OBJ_MATERIAL_INITIALIZED)
|
|
update_material_parts()
|
|
|
|
/**
|
|
* do we use material parts system?
|
|
*/
|
|
/obj/proc/uses_material_parts()
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
return material_parts != MATERIAL_DEFAULT_DISABLED
|
|
|
|
/**
|
|
* Get primary material, or first material. Can return null.
|
|
* Determined by [material_primary]
|
|
*
|
|
* This should be the most 'useful' or plentiful material and is used in and only used in general heuristic checks.
|
|
*/
|
|
/obj/proc/get_primary_material()
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
RETURN_TYPE(/datum/prototype/material)
|
|
return isnull(material_primary)? null : material_get_part(material_primary)
|
|
|
|
/**
|
|
* Get primary material ID, or first material ID. Can return null.
|
|
* Determined by [material_primary]
|
|
*
|
|
* This should be the most 'useful' or plentiful material and is used in and only used in general heuristic checks.
|
|
*/
|
|
/obj/proc/get_primary_material_id()
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
return isnull(material_primary)? null : material_get_part(material_primary)?.id
|
|
|
|
/**
|
|
* sets our primary material to something
|
|
*
|
|
* if we have more than one material part (as determined by material_parts),
|
|
* this sets the first one.
|
|
* if we don't, this sets our only material.
|
|
*
|
|
* ids and typepaths are not allowed in part_instances for performance reasons.
|
|
*/
|
|
/obj/proc/set_primary_material(datum/prototype/material/material)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
if(isnull(material_primary))
|
|
return
|
|
obj_flags |= OBJ_MATERIAL_PARTS_MODIFIED
|
|
material_set_part(material_primary, material)
|
|
if(obj_flags & OBJ_MATERIAL_INITIALIZED)
|
|
update_material_parts()
|
|
|
|
/**
|
|
* get material amounts of parts
|
|
*/
|
|
/obj/proc/get_material_part_amounts(respect_multiplier)
|
|
if(material_parts == MATERIAL_DEFAULT_DISABLED)
|
|
return list()
|
|
else if(material_parts == MATERIAL_DEFAULT_ABSTRACTED)
|
|
. = list()
|
|
var/list/parts = material_get_parts()
|
|
if(isnull(material_costs))
|
|
return
|
|
for(var/i in 1 to length(parts))
|
|
.[parts[i]] = material_costs[i]
|
|
else if(islist(material_parts))
|
|
. = list()
|
|
if(isnull(material_costs))
|
|
return
|
|
for(var/i in 1 to length(material_parts))
|
|
var/key = material_parts[i]
|
|
.[key] = material_costs[i]
|
|
else
|
|
. = list(MATERIAL_PART_DEFAULT = material_costs || 0)
|
|
if(respect_multiplier && material_multiplier != 1)
|
|
for(var/key in .)
|
|
.[key] *= material_multiplier
|
|
|
|
//* Abstraction API
|
|
//! Override these procs to implement more efficient material systems.
|
|
//! If your subtype has more than a few hundred instances on the map,
|
|
//! it's a good sign you should do so.
|
|
//!
|
|
//! Don't forget to set material_parts to MATERIAL_DEFAULT_ABSTRACTED.
|
|
|
|
/**
|
|
* @return key-value list of material part keys to instances
|
|
*/
|
|
/obj/proc/material_get_parts()
|
|
PROTECTED_PROC(TRUE) // Do not ever call directly.
|
|
switch(material_parts)
|
|
if(MATERIAL_DEFAULT_ABSTRACTED)
|
|
CRASH("abstracted hit base")
|
|
if(MATERIAL_DEFAULT_DISABLED)
|
|
return list()
|
|
if(islist(material_parts))
|
|
return material_parts.Copy()
|
|
return list(MATERIAL_PART_DEFAULT = material_parts)
|
|
|
|
/**
|
|
* @return material instance
|
|
*/
|
|
/obj/proc/material_get_part(part)
|
|
RETURN_TYPE(/datum/prototype/material)
|
|
PROTECTED_PROC(TRUE) // Do not ever call directly.
|
|
if(islist(material_parts))
|
|
return material_parts[part]
|
|
return istext(material_parts) ? null : material_parts
|
|
|
|
/**
|
|
* sets a single material part
|
|
*
|
|
* undefined behavior if part does not exist in material parts when material parts is a list..
|
|
*
|
|
* @params
|
|
* * part - part key
|
|
* * material - material. ids and paths are not allowed for performance reasons.
|
|
*/
|
|
/obj/proc/material_set_part(part, datum/prototype/material/material)
|
|
PROTECTED_PROC(TRUE) // Do not ever call directly.
|
|
var/datum/prototype/material/old
|
|
if(islist(material_parts))
|
|
old = material_parts[part]
|
|
material_parts[part] = material
|
|
else if(part == MATERIAL_PART_DEFAULT)
|
|
old = material_parts
|
|
material_parts = material
|
|
else if(part == material_primary) //if we're not MATERIAL_PART_DEFAULT but our primary isn't MATERIAL_PART_DEFAULT, we need to check for that.
|
|
old = material_parts
|
|
material_parts = material
|
|
if(material != old)
|
|
if(part == material_primary)
|
|
unregister_material(old, TRUE)
|
|
register_material(material, TRUE)
|
|
else
|
|
unregister_material(old, FALSE)
|
|
register_material(material, FALSE)
|
|
|
|
/**
|
|
* Called to initialize material parts.
|
|
*/
|
|
/obj/proc/material_init_parts()
|
|
PROTECTED_PROC(TRUE) // Do not ever call directly.
|
|
CRASH("unimplemented abstracted material_init_parts even when abstraction is enabled")
|
|
|
|
//* User API
|
|
//! Override these to implement the actual behaviors your object uses with materials.
|
|
|
|
/**
|
|
* update material parts
|
|
*
|
|
* only called if material_parts is in list format, or materials is using the abstraction system
|
|
*
|
|
* @params
|
|
* * parts - list of key-value key to material id. if material_parts is abstracted, parts is null
|
|
*/
|
|
/obj/proc/update_material_multi(list/parts)
|
|
return
|
|
|
|
/**
|
|
* update material default part
|
|
*
|
|
* only called if material_parts is in singleton format
|
|
*/
|
|
/obj/proc/update_material_single(datum/prototype/material/material)
|
|
return
|
|
|
|
//* Lathe Autodetect
|
|
//! Do not override these. These are automatic based on the APIs.
|
|
|
|
/**
|
|
* autodetect proc used by lathes
|
|
* called only right after init
|
|
* should never be called after materials are mutated in any way, including by init.
|
|
*
|
|
* returned list is a copy
|
|
*
|
|
* @return key-value associative list of part name to cost
|
|
*/
|
|
/obj/proc/detect_material_part_costs()
|
|
return get_material_part_amounts()
|
|
|
|
/**
|
|
* autodetect proc used by lathes
|
|
* called only right after init
|
|
* should never be called after materials are mutated in any way, including by init.
|
|
*
|
|
* returned list is a coy
|
|
*
|
|
* @return key-value associative list of our *own* baseline materials, without material parts.
|
|
*/
|
|
/obj/proc/detect_material_base_costs()
|
|
return isnull(materials_base)? list() : materials_base.Copy()
|