Files
Aurora.3/code/modules/customitems/item_spawning.dm
Geeves 851c34337f Custom Item Limb Framework + Gracia's Aut'akh Arm (#20004)
* You can now view your custom items from within the loadout.

Also implements the framework necessary for custom items to be limbs,
and to automatically replace the limb it's meant to replace.

Implements the Aut'akh arm mentioned in this request:
https://forums.aurorastation.org/topic/21092-gracias-autakh-arm
2024-10-13 21:16:01 +00:00

335 lines
13 KiB
Plaintext

//This is the custom items system.
//There are two main modes of operation: database-based and file-based
//The operating mode is decided by the config.sql_enabled parameter
//
/// File
// In File mode the system loads all the custom items from the custom_items.json into the custom_items list at roundstart
// There is also a fallback mode for the DEPRECATED custom_items.txt. This fallback mode will be removed at some point.
// You SHOULD migrate to json file (manually) or the db using the automatic migration feature described in the db section
// When the equip_custom_items() proc is called for a specified mob, the custom_items list is iteraed over to determine which
// custom items belong to that mob of the player. Afterwards the items are spawned and applied to the mob (if the role matches)
//
/// Database
// In the database mode the custom items are NOT preloaded but fetched on demand based on the character id.
// If the database is empty and one of the configuration files exists the configuration file is loaded into the custom_items list.
// Afterwards a attempt is made to migrate the custom items into the database.
// Once there are entries in the ss13_characters_custom_items table no more attempts to migrate the data are made.
// To make it easier to find the round when that migration occured, the feedback variables
// custom_item_migration_success and custom_item_migration_error are set.
//
/// Removed Features
// The kits have been removed without replacement
// The icon manipulation features have been removed without replacement
// The required access has been removed without replacement
// The name/desc field have been removed and replaced with the item_data system
// This allows to modify any (string/int) variable of a existing item via the custom_item system.
// The key in the item_data list is the name of the variable, and the value is the value of the variable.
// i.e. `item_data = list("name"="asdf")` would set the name of the item to asdf when its spawned in
/var/list/custom_items = list()
//Loads the custom items from the json file if the db backend is disabled
/hook/pregame_start/proc/load_custom_items()
var/load_from_file = 0
if (GLOB.config.sql_enabled)
log_module_customitems("Loading from SQL")
//If we have sql enabled we check if the db is empty. If so we migrate the json file to the db
//If the db is not empty we dont load the json file
if(!establish_db_connection(GLOB.dbcon))
log_module_customitems("Unable to establish database connection. - Aborting")
return 1
var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT COUNT(*) FROM ss13_characters_custom_items")
query.Execute()
if (!query.NextRow())
log_module_customitems("Unable to fetch custom item count from database. - Aborting")
return 1
var/item_count = text2num(query.item[1])
if(item_count > 0)
return 1
//If there are no items in the db, migrate them
load_from_file = 2
log_module_customitems("No items found in the database - attempting migration")
else
//If we dont have a db, we need to load the json file
load_from_file = 1
if(load_from_file)
log_module_customitems("Loading from File")
//Check if the json file exists
if(fexists("config/custom_items.json"))
log_module_customitems("Loading from json")
var/list/loaded_items = list()
var/item_id = 0
try
loaded_items = json_decode(return_file_text("config/custom_items.json"))
catch(var/exception/e)
log_module_customitems("Failed to load custom_items.json: [e]")
for(var/item in loaded_items)
//TODO: Check for existance of the vars first
item_id += 1
var/datum/custom_item/ci = new()
ci.id = item_id
ci.usr_ckey = item["ckey"]
ci.usr_charname = item["character_name"]
ci.item_path = text2path(item["item_path"])
if(item["item_data"])
ci.item_data = item["item_data"]
if(item["item_name"])
ci.item_data["name"] = item["item_name"]
if(item["item_desc"])
ci.item_data["desc"] = item["item_desc"]
ci.additional_data = item["additional_data"]
ci.req_titles = item["req_titles"]
custom_items.Add(ci)
log_module_customitems("Loaded [length(custom_items)] custom items")
else if(fexists("config/custom_items.txt")) //TODO: Retire that at some point down the line
log_module_customitems("Loading from txt")
log_and_message_admins("The deprecated custom_items.txt file is used. Migrate to SQL or JSON.")
//If we dont have the json file, we might have the old file so lets try that
var/datum/custom_item/current_data
for(var/line in text2list(file2text("config/custom_items.txt"), "\n"))
line = trim(line)
if(line == "" || !line || findtext(line, "#", 1, 2))
continue
if(findtext(line, "{", 1, 2) || findtext(line, "}", 1, 2)) // New block!
if(current_data && current_data.usr_ckey && current_data.usr_charname)
custom_items.Add(current_data)
current_data = null
var/split = findtext(line,":")
if(!split)
continue
var/field = trim(copytext(line,1,split))
var/field_data = trim(copytext(line,(split+1)))
if(!field || !field_data)
continue
if(!current_data)
current_data = new()
switch(field)
if("ckey")
current_data.usr_ckey = ckey(field_data)
if("character_name")
current_data.usr_charname = lowertext(field_data)
if("item_path")
current_data.item_path = text2path(field_data)
if("item_name")
current_data.item_data["name"] = field_data
if("item_icon")
continue
if("inherit_inhands")
continue
if("item_desc")
current_data.item_data["desc"] = field_data
if("req_access")
continue
if("req_titles")
current_data.req_titles = text2list(field_data,", ")
if("kit_name")
continue
if("kit_desc")
continue
if("kit_icon")
continue
if("additional_data")
current_data.additional_data = field_data
if(load_from_file == 2 && length(custom_items)) //insert the item into the db
log_module_customitems("Migrating custom_items to database")
var/success_count = 0
var/error_count = 0
for(var/item in custom_items)
var/datum/custom_item/ci = item
log_module_customitems("Migrating Item for: [ci.usr_ckey] - [ci.usr_charname]")
if(!ci.item_path || ci.item_path == "")
log_module_customitems("Invalid Item path")
error_count += 1
continue
//Fetch the character id of the character
var/DBQuery/char_query = GLOB.dbcon.NewQuery("SELECT id FROM ss13_characters WHERE ckey = :ckey: AND name = :name: AND deleted_at IS NULL ORDER BY id DESC")
char_query.Execute(list("ckey"=ckey(ci.usr_ckey),"name"=ci.usr_charname))
if (!char_query.NextRow())
log_module_customitems("Unable to find matching character for: ckey: [ci.usr_ckey] name: [ci.usr_charname]")
error_count += 1
continue
var/char_id = text2num(char_query.item[1])
try
var/DBQuery/item_insert_query = GLOB.dbcon.NewQuery("INSERT INTO ss13_characters_custom_items (`char_id`, `item_path`, `item_data`, `req_titles`, `additional_data`) VALUES (:char_id:, :item_path:, :item_data:, :req_titles:, :additional_data:)")
item_insert_query.Execute(list("char_id"=char_id,"item_path"="[ci.item_path]","item_data"=json_encode(ci.item_data),"req_titles"=json_encode(ci.req_titles),"additional_data"=ci.additional_data))
catch(var/exception/e)
log_module_customitems("Failed to save item to db: [e]")
error_count += 1
success_count += 1
feedback_set("custom_item_migration_success",success_count)
feedback_set("custom_item_migration_error",error_count)
return 1
/datum/custom_item
var/id
//the character_id is used with the db setup
var/character_id
//the char_name/ckey is used with the disk based setup (and auto-generated from the char id for the db based setup)
var/usr_ckey
var/usr_charname
var/item_path
var/list/item_data = list()
var/list/req_titles = list()
var/additional_data
/datum/custom_item/proc/spawn_item(var/newloc) //TODO: pass mob its spawned for as parameter
var/obj/item/citem = new item_path(newloc)
apply_to_item(citem)
return citem
/datum/custom_item/proc/apply_to_item(var/obj/item/item)
if(!item)
return
//Customize the item with the item_data
for(var/var_name in item_data)
try
item.vars[var_name] = item_data[var_name]
catch(var/exception/e)
log_module_customitems("Bad variable name [var_name] in custom item with id [id]: [e]")
// for snowflake implants
if(istype(item, /obj/item/implanter/fluff))
var/obj/item/implanter/fluff/L = item
L.allowed_ckey = usr_ckey
L.implant_type = text2path(additional_data)
L.create_implant()
return item
GLOBAL_LIST_EMPTY(character_id_to_custom_items_mapping)
/// Gets the relevant custom items for the given target_mob, character_id, and player_ckey, and equips the target_mob with those custom items
/proc/equip_custom_items(var/mob/living/carbon/human/target_mob, var/character_id, var/player_ckey)
if(!character_id)
character_id = target_mob.character_id
if(!player_ckey)
player_ckey = target_mob.ckey
var/glob_character_id_key = "[player_ckey]-[character_id]"
//Fetch the custom items for the mob
if(GLOB.config.sql_enabled)
if(!establish_db_connection(GLOB.dbcon))
log_module_customitems("Unable to establish database connection while loading item. - Aborting")
return
if(!GLOB.character_id_to_custom_items_mapping[glob_character_id_key])
var/list/custom_items_list = list()
var/DBQuery/char_item_query = GLOB.dbcon.NewQuery("SELECT ss13_characters_custom_items.id, ss13_characters_custom_items.char_id, ss13_characters.ckey as usr_ckey, ss13_characters.name as usr_charname, item_path, item_data, req_titles, additional_data FROM ss13_characters_custom_items LEFT JOIN ss13_characters ON ss13_characters.id = ss13_characters_custom_items.char_id WHERE char_id = :char_id:")
char_item_query.Execute(list("char_id"=character_id))
while(char_item_query.NextRow())
CHECK_TICK
var/datum/custom_item/ci = new()
ci.id = text2num(char_item_query.item[1])
ci.character_id = text2num(char_item_query.item[2])
ci.usr_ckey = char_item_query.item[3]
ci.usr_charname = char_item_query.item[4]
ci.item_path = text2path(char_item_query.item[5])
ci.item_data = json_decode(char_item_query.item[6]) //TODO: try/catch
ci.req_titles = json_decode(char_item_query.item[7]) //TODO: try/catch
ci.additional_data = char_item_query.item[8]
custom_items_list += ci
GLOB.character_id_to_custom_items_mapping[glob_character_id_key] = custom_items_list
else
if(!GLOB.character_id_to_custom_items_mapping[glob_character_id_key])
var/list/custom_items_list = list()
for(var/item in custom_items)
CHECK_TICK
var/datum/custom_item/ci = item
if(lowertext(ci.usr_ckey) != lowertext(player_ckey))
continue
if(lowertext(ci.usr_charname) != lowertext(target_mob.real_name))
continue
custom_items_list += ci
GLOB.character_id_to_custom_items_mapping[glob_character_id_key] = custom_items_list
for(var/datum/custom_item/ci as anything in GLOB.character_id_to_custom_items_mapping[glob_character_id_key])
equip_custom_item_to_mob(ci, target_mob)
/proc/equip_custom_item_to_mob(var/datum/custom_item/citem, var/mob/living/carbon/human/target_mob)
// Check for required job title.
if(length(citem.req_titles))
var/has_title
var/current_title = target_mob.mind.role_alt_title ? target_mob.mind.role_alt_title : target_mob.mind.assigned_role
for(var/title in citem.req_titles)
if(title == current_title)
has_title = 1
break
if(!has_title)
to_chat(target_mob, "A custom item could not be equipped as you have joined with the wrong role.")
return FALSE
if(ispath(citem.item_path, /obj/item/organ/internal/augment/fluff))
var/obj/item/organ/internal/augment/fluff/aug = citem.spawn_item(target_mob)
var/obj/item/organ/external/affected = target_mob.get_organ(aug.parent_organ)
aug.replaced(target_mob, affected)
target_mob.update_body()
target_mob.updatehealth()
target_mob.UpdateDamageIcon()
return
if(ispath(citem.item_path, /obj/item/organ/external))
var/obj/item/organ/external/dummy_limb = citem.item_path
var/obj/item/organ/external/parent_organ = target_mob.get_organ(initial(dummy_limb.parent_organ))
if(parent_organ)
var/obj/item/organ/external/existing_limb = target_mob.get_organ(initial(dummy_limb.limb_name))
if(existing_limb)
existing_limb.droplimb(TRUE)
qdel(existing_limb)
var/obj/item/organ/external/custom_limb = citem.spawn_item(target_mob)
custom_limb.replaced(target_mob, parent_organ)
target_mob.update_body()
target_mob.updatehealth()
target_mob.UpdateDamageIcon()
return
// ID cards and MCs are applied directly to the existing object rather than spawned fresh.
var/obj/item/existing_item
if(citem.item_path == /obj/item/card/id)
existing_item = locate(/obj/item/card/id) in target_mob.get_contents() //TODO: Improve this ?
else if(citem.item_path == /obj/item/modular_computer)
existing_item = locate(/obj/item/modular_computer) in target_mob.contents
// Spawn and equip the item.
if(existing_item)
citem.apply_to_item(existing_item)
return TRUE
else
var/obj/item/newitem = citem.spawn_item()
if(target_mob.equip_to_appropriate_slot(newitem))
return TRUE
if(target_mob.equip_to_storage(newitem))
return TRUE
newitem.forceMove(get_turf(target_mob.loc))
to_chat(target_mob, "A custom item has been placed on the floor as there was no space for it on your mob.")
return TRUE