diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 65ec4589a5..a1cb45ba61 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -357,17 +357,25 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "
"
dat += ""
- var/client_file = user.client.Import()
+ var/savefile/client_file = user.client.Import()
var/savefile_name
- if(client_file)
- var/savefile/cache_savefile = new(user.client.Import())
- if(!cache_savefile["deleted"] || savefile_needs_update(cache_savefile) != -2)
- cache_savefile["real_name"] >> savefile_name
+ if(istype(client_file, /savefile))
+ if(!client_file["deleted"] || savefile_needs_update(client_file) != -2)
+ client_file["real_name"] >> savefile_name
dat += "Local storage: [savefile_name ? savefile_name : "Empty"]"
dat += "
"
dat += "Export current slot"
dat += "Import into current slot"
dat += "Delete locally saved character"
+ dat += "
"
+ dat += "[offer ? "Cancel offer" : "Offer slot"]"
+ dat += "Retrieve offered character"
+ if(offer)
+ dat += "
"
+ dat += "The redemption code is [offer.redemption_code]"
+ dat += "
"
+ dat += "The offer will automatically be cancelled if there is an error, or if someone takes it"
+
dat += ""
dat += "
"
@@ -3469,6 +3477,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(istype(S, /savefile))
if(istype(load_character(provided = S), /savefile))
tgui_alert_async(user, "Successfully loaded character slot.")
+ save_character(TRUE)
else
tgui_alert_async(user, "Failed loading character slot")
return
@@ -3480,6 +3489,63 @@ GLOBAL_LIST_EMPTY(preferences_datums)
user.client.clear_export()
tgui_alert_async(user, "Local save data erased.")
+ if("give_slot")
+ if(!QDELETED(offer))
+ var/datum/character_offer_instance/offer_datum = LAZYACCESS(GLOB.character_offers, offer.redemption_code)
+ if(!offer_datum)
+ return
+ qdel(offer_datum)
+ else
+ var/savefile/S = save_character(export = TRUE)
+ if(istype(S, /savefile))
+ var/datum/character_offer_instance/offer_datum = new(usr.ckey, S)
+ if(QDELETED(offer_datum))
+ tgui_alert_async(usr, "Could not set up offer, try again later")
+ return
+ offer_datum.RegisterSignal(usr, COMSIG_MOB_CLIENT_LOGOUT, TYPE_PROC_REF(/datum/character_offer_instance, on_quit))
+ offer = offer_datum
+ tgui_alert_async(usr, "The redemption code is [offer_datum.redemption_code], give it to the receiver")
+
+ if("retrieve_slot")
+ if(!LAZYLEN(GLOB.character_offers))
+ tgui_alert_async(usr, "There are no active offers")
+ return
+ var/retrieve_code = input(usr, "Input the 5 digit redemption code") as text|null
+ if(!retrieve_code)
+ return
+ if(!text2num(retrieve_code))
+ tgui_alert_async(usr, "Only numbers allowed")
+ return
+ if(length(retrieve_code) != 5)
+ tgui_alert_async(usr, "Exactly 5 digits, no less, no more, try again")
+ return
+ var/datum/character_offer_instance/offer_datum = LAZYACCESS(GLOB.character_offers, retrieve_code)
+ if(!offer_datum)
+ tgui_alert_async(usr, "This is an invalid code!")
+ return
+ var/savefile/savefile = offer_datum.character_savefile
+ var/mob/living/the_owner = get_mob_by_ckey(offer_datum.owner_ckey)
+ if(savefile_needs_update(savefile) == -2)
+ tgui_alert_async(usr, "Something's wrong, this savefile is corrupted.")
+ to_chat(the_owner, span_boldwarning("Something went wrong with the trade, it's been canceled."))
+ qdel(offer_datum)
+ return
+ var/character_name = savefile["real_name"]
+ if(tgui_alert(usr, "You are overwriting the currently selected slot with the character [character_name]", "Are you sure?", buttons = list("Yes, load this character deleting the currently selected slot", "No")) == "No")
+ return
+ if(QDELETED(offer_datum))
+ tgui_alert_async(usr, "This character is no longer available, such a shame!")
+ return
+ to_chat(the_owner, span_boldwarning("[usr.key] has retrieved your character, [character_name]!"))
+ if(!load_character(provided = savefile))
+ tgui_alert_async(usr, "Something went wrong loading the savefile, even though it has already been checked, please report this issue!")
+ to_chat(the_owner, span_boldwarning("Something went wrong at the final step of the trade, report this."))
+ qdel(offer_datum)
+ return
+ tgui_alert_async(usr, "Successfully received [character_name]!")
+ save_character(TRUE)
+ qdel(offer_datum)
+
if(href_list["preference"] == "gear")
if(href_list["clear_loadout"])
loadout_data["SAVE_[loadout_slot]"] = list()
diff --git a/modular_sand/code/modules/character_trading/character_trading.dm b/modular_sand/code/modules/character_trading/character_trading.dm
new file mode 100644
index 0000000000..62d36d3631
--- /dev/null
+++ b/modular_sand/code/modules/character_trading/character_trading.dm
@@ -0,0 +1,41 @@
+#define REDEMPTION_CODE_GENERATION_ATTEMPTS 5
+
+GLOBAL_LIST_INIT(character_offers, list())
+
+/datum/character_offer_instance
+ var/owner_ckey
+ var/savefile/character_savefile
+ var/redemption_code
+
+/datum/character_offer_instance/New(owner_ckey, character_savefile)
+ . = ..()
+ if(!owner_ckey || !character_savefile)
+ qdel(src)
+ return
+ src.owner_ckey = owner_ckey
+ src.character_savefile = character_savefile
+
+ // 5 digit number, no fucking way some idiot is guessing this
+ var/attempts = 0
+ while(!redemption_code || LAZYFIND(GLOB.character_offers, redemption_code))
+ if(attempts >= REDEMPTION_CODE_GENERATION_ATTEMPTS)
+ qdel(src)
+ return
+ redemption_code = "[random_nukecode()]"
+ attempts++
+
+ LAZYSET(GLOB.character_offers, redemption_code, src)
+
+/datum/character_offer_instance/Destroy(force, ...)
+ var/datum/preferences/to_remove = LAZYACCESS(GLOB.preferences_datums, owner_ckey)
+ to_remove.offer = null
+ LAZYREMOVE(GLOB.character_offers, redemption_code)
+ owner_ckey = null
+ character_savefile = null
+ redemption_code = null
+ return ..()
+
+/datum/character_offer_instance/proc/on_quit()
+ qdel(src)
+
+#undef REDEMPTION_CODE_GENERATION_ATTEMPTS
diff --git a/modular_sand/code/modules/client/preferences.dm b/modular_sand/code/modules/client/preferences.dm
index 3bc14f3e50..5613c034ba 100644
--- a/modular_sand/code/modules/client/preferences.dm
+++ b/modular_sand/code/modules/client/preferences.dm
@@ -11,6 +11,8 @@
/// Chance of moaning during an interaction
var/moaning_multiplier = 65
+ var/datum/character_offer_instance/offer
+
//SANDSTORM EDIT - extra language
/datum/preferences/proc/SetLanguage(mob/user)
var/list/dat = list()
diff --git a/tgstation.dme b/tgstation.dme
index eac4b2b968..821cf07e66 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -4188,6 +4188,7 @@
#include "modular_sand\code\modules\cargo\packs\misc.dm"
#include "modular_sand\code\modules\cargo\packs\supply_pack.dm"
#include "modular_sand\code\modules\cargo\packs\vending.dm"
+#include "modular_sand\code\modules\character_trading\character_trading.dm"
#include "modular_sand\code\modules\client\asset_cache.dm"
#include "modular_sand\code\modules\client\client_procs.dm"
#include "modular_sand\code\modules\client\preferences.dm"