mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
Interactions tidyup (#8850)
* Updated interactions * Update interaction_menu.dm * Colors * Update interaction_menu.dm * Update interaction_menu.dm * Update interaction_menu.dm * Better code for all. * Update interaction_component.dm * Removes support for TARGET SPEAK and USER SPEAK due to instability. * Update interactions.dm * Update interaction_component.dm * Enables you to interact from a distance again.
This commit is contained in:
16
code/__DEFINES/~skyrat_defines/interactions.dm
Normal file
16
code/__DEFINES/~skyrat_defines/interactions.dm
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#define INTERACTION_JSON_FOLDER "config/skyrat/interactions/"
|
||||||
|
|
||||||
|
// Special values
|
||||||
|
#define INTERACTION_MAX_CHAR 255
|
||||||
|
#define INTERACTION_COOLDOWN 1 SECONDS
|
||||||
|
|
||||||
|
// If an interaction has this category it will not be shown to players
|
||||||
|
#define INTERACTION_CAT_HIDE "hide"
|
||||||
|
|
||||||
|
// If you add a new requirement you also need to implement its checking. See /datum/interaction/proc/allow_act
|
||||||
|
#define INTERACTION_REQUIRE_SELF_HAND "self_hand"
|
||||||
|
#define INTERACTION_REQUIRE_TARGET_HAND "target_hand"
|
||||||
|
|
||||||
|
// Interaction Types: Do we do it to ourself or someone else
|
||||||
|
#define INTERACTION_SELF "self"
|
||||||
|
#define INTERACTION_OTHER "other"
|
||||||
@@ -438,7 +438,7 @@
|
|||||||
/mob/proc/ShiftMiddleClickOn(atom/A)
|
/mob/proc/ShiftMiddleClickOn(atom/A)
|
||||||
src.pointed(A)
|
src.pointed(A)
|
||||||
return
|
return
|
||||||
|
//SKYRAT EDIT NOTE: CtrlShiftClick for HUMANS has been overriden in click.dm in the interaction module.
|
||||||
/atom/proc/CtrlShiftClick(mob/user)
|
/atom/proc/CtrlShiftClick(mob/user)
|
||||||
if(!can_interact(user))
|
if(!can_interact(user))
|
||||||
return FALSE
|
return FALSE
|
||||||
|
|||||||
@@ -71,6 +71,8 @@
|
|||||||
LoadPolicy()
|
LoadPolicy()
|
||||||
LoadChatFilter()
|
LoadChatFilter()
|
||||||
|
|
||||||
|
populate_interaction_instances() //SKYRAT EDIT ADDITION
|
||||||
|
|
||||||
loaded = TRUE
|
loaded = TRUE
|
||||||
|
|
||||||
if (Master)
|
if (Master)
|
||||||
|
|||||||
@@ -10,8 +10,5 @@
|
|||||||
"sound_use": 0,
|
"sound_use": 0,
|
||||||
"sound_possible": [
|
"sound_possible": [
|
||||||
],
|
],
|
||||||
"sound_range": 7,
|
"sound_range": 7
|
||||||
"interaction_requires": [
|
|
||||||
"self_speak"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
"sound_range": 7,
|
"sound_range": 7,
|
||||||
"interaction_requires": [
|
"interaction_requires": [
|
||||||
"self_hand",
|
"self_hand",
|
||||||
"self_speak",
|
"target_hand"
|
||||||
"target_hand",
|
|
||||||
"target_speak"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Pat",
|
"name": "Pat",
|
||||||
"description": "Pat their shoulder; perhaps if to comfort them.",
|
"description": "Pat their shoulder; perhaps if to comfort them.",
|
||||||
"usage": "other",
|
"usage": "other",
|
||||||
"category": "Miscellaneous",
|
"category": "Miscellaneous",
|
||||||
"distance_allowed": 0,
|
"distance_allowed": 0,
|
||||||
"message": [
|
"message": [
|
||||||
|
|||||||
3
modular_skyrat/modules/interaction_menu/code/click.dm
Normal file
3
modular_skyrat/modules/interaction_menu/code/click.dm
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/mob/living/carbon/human/CtrlShiftClick(mob/user) //We have to remove the can_interact check from humans.
|
||||||
|
SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT, user)
|
||||||
|
return
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
/datum/component/interactable
|
||||||
|
/// A hard reference to the parent
|
||||||
|
var/mob/living/carbon/human/self = null
|
||||||
|
/// A list of interactions that the user can engage in.
|
||||||
|
var/list/datum/interaction/interactions
|
||||||
|
var/interact_last = 0
|
||||||
|
var/interact_next = 0
|
||||||
|
|
||||||
|
/datum/component/interactable/Initialize(...)
|
||||||
|
if(QDELETED(parent))
|
||||||
|
qdel(src)
|
||||||
|
return
|
||||||
|
|
||||||
|
if(!ishuman(parent))
|
||||||
|
return COMPONENT_INCOMPATIBLE
|
||||||
|
|
||||||
|
self = parent
|
||||||
|
|
||||||
|
build_interactions_list()
|
||||||
|
|
||||||
|
/datum/component/interactable/proc/build_interactions_list()
|
||||||
|
interactions = list()
|
||||||
|
for(var/iterating_interaction_id in GLOB.interaction_instances)
|
||||||
|
var/datum/interaction/interaction = GLOB.interaction_instances[iterating_interaction_id]
|
||||||
|
if(interaction.lewd && !self.client?.prefs?.read_preference(/datum/preference/toggle/erp))
|
||||||
|
continue
|
||||||
|
interactions.Add(interaction)
|
||||||
|
|
||||||
|
/datum/component/interactable/RegisterWithParent()
|
||||||
|
RegisterSignal(parent, COMSIG_CLICK_CTRL_SHIFT, .proc/open_interaction_menu)
|
||||||
|
|
||||||
|
/datum/component/interactable/UnregisterFromParent()
|
||||||
|
UnregisterSignal(parent, COMSIG_CLICK_CTRL_SHIFT)
|
||||||
|
|
||||||
|
/datum/component/interactable/Destroy(force, silent)
|
||||||
|
self = null
|
||||||
|
interactions = null
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
/datum/component/interactable/proc/open_interaction_menu(datum/source, mob/user)
|
||||||
|
if(!ishuman(user))
|
||||||
|
return
|
||||||
|
build_interactions_list()
|
||||||
|
ui_interact(user)
|
||||||
|
|
||||||
|
/datum/component/interactable/proc/can_interact(datum/interaction/interaction, mob/living/carbon/human/target)
|
||||||
|
if(!interaction.allow_act(target, self))
|
||||||
|
return FALSE
|
||||||
|
if(interaction.lewd && !target.client?.prefs?.read_preference(/datum/preference/toggle/erp))
|
||||||
|
return FALSE
|
||||||
|
if(!interaction.distance_allowed && !target.Adjacent(self))
|
||||||
|
return FALSE
|
||||||
|
if(interaction.category == INTERACTION_CAT_HIDE)
|
||||||
|
return FALSE
|
||||||
|
if(self == target && interaction.usage == INTERACTION_OTHER)
|
||||||
|
return FALSE
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/// UI Control
|
||||||
|
|
||||||
|
/datum/component/interactable/ui_interact(mob/user, datum/tgui/ui)
|
||||||
|
ui = SStgui.try_update_ui(user, src, ui)
|
||||||
|
if(!ui)
|
||||||
|
ui = new(user, src, "InteractionMenu")
|
||||||
|
ui.open()
|
||||||
|
|
||||||
|
/datum/component/interactable/ui_status(mob/user, datum/ui_state/state)
|
||||||
|
return UI_INTERACTIVE // This UI is always interactive as we handle distance flags via can_interact
|
||||||
|
|
||||||
|
/datum/component/interactable/ui_data(mob/user)
|
||||||
|
var/list/data = list()
|
||||||
|
var/list/descriptions = list()
|
||||||
|
var/list/categories = list()
|
||||||
|
var/list/colors = list()
|
||||||
|
for(var/datum/interaction/interaction in interactions)
|
||||||
|
if(!can_interact(interaction, user))
|
||||||
|
continue
|
||||||
|
if(!categories[interaction.category])
|
||||||
|
categories[interaction.category] = list(interaction.name)
|
||||||
|
else
|
||||||
|
categories[interaction.category] += interaction.name
|
||||||
|
descriptions[interaction.name] = interaction.description
|
||||||
|
colors[interaction.name] = interaction.color
|
||||||
|
data["categories"] = list()
|
||||||
|
data["descriptions"] = descriptions
|
||||||
|
data["colors"] = colors
|
||||||
|
for(var/category in categories)
|
||||||
|
data["categories"] += category
|
||||||
|
data["ref_user"] = REF(user)
|
||||||
|
data["ref_self"] = REF(self)
|
||||||
|
data["self"] = self.name
|
||||||
|
data["block_interact"] = interact_next >= world.time
|
||||||
|
data["interactions"] = categories
|
||||||
|
return data
|
||||||
|
|
||||||
|
/datum/component/interactable/ui_act(action, list/params)
|
||||||
|
. = ..()
|
||||||
|
if(.)
|
||||||
|
return
|
||||||
|
if(params["interaction"])
|
||||||
|
var/interaction_id = params["interaction"]
|
||||||
|
if(GLOB.interaction_instances[interaction_id])
|
||||||
|
var/mob/living/carbon/human/user = locate(params["userref"])
|
||||||
|
if(!can_interact(GLOB.interaction_instances[interaction_id], user))
|
||||||
|
return FALSE
|
||||||
|
GLOB.interaction_instances[interaction_id].act(user, locate(params["selfref"]))
|
||||||
|
var/datum/component/interactable/interaction_component = user.GetComponent(/datum/component/interactable)
|
||||||
|
interaction_component.interact_last = world.time
|
||||||
|
interact_next = interaction_component.interact_last + INTERACTION_COOLDOWN
|
||||||
|
interaction_component.interact_next = interact_next
|
||||||
|
return TRUE
|
||||||
|
message_admins("Unhandled interaction '[params["interaction"]]'. Inform coders.")
|
||||||
@@ -1,162 +1,6 @@
|
|||||||
#define INTERACTION_JSON_FOLDER "config/skyrat/interactions/"
|
|
||||||
|
|
||||||
GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
|
GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
|
||||||
|
|
||||||
// Special values
|
|
||||||
#define INTERACTION_MAX_CHAR 255
|
|
||||||
#define INTERACTION_COOLDOWN 0.5 SECONDS
|
|
||||||
|
|
||||||
// If an interaction has this category it will not be shown to players
|
|
||||||
#define INTERACTION_CAT_HIDE "hide"
|
|
||||||
|
|
||||||
// I want these to be bitflags; but that would make json loading painful beyond compare
|
|
||||||
// If you add a new requirement you also need to implement its checking. See /datum/interaction/proc/allow_act
|
|
||||||
#define INTERACTION_REQUIRE_SELF_HAND "self_hand"
|
|
||||||
#define INTERACTION_REQUIRE_SELF_SPEAK "self_speak"
|
|
||||||
#define INTERACTION_REQUIRE_TARGET_HAND "target_hand"
|
|
||||||
#define INTERACTION_REQUIRE_TARGET_SPEAK "target_speak"
|
|
||||||
|
|
||||||
// Interaction Types: Do we do it to ourself or someone else
|
|
||||||
#define INTERACTION_SELF "self"
|
|
||||||
#define INTERACTION_OTHER "other"
|
|
||||||
|
|
||||||
/mob/living/carbon/human/CtrlShiftClickOn(atom/atom_on)
|
|
||||||
. = ..()
|
|
||||||
if(.) // already handled, ignore
|
|
||||||
return .
|
|
||||||
if(ishuman(atom_on))
|
|
||||||
var/mob/living/carbon/human/clicked_living = atom_on
|
|
||||||
var/datum/component/interactable/clicked_int = clicked_living.GetComponent(/datum/component/interactable)
|
|
||||||
if(!clicked_int)
|
|
||||||
message_admins("Interactable CtrlShiftClickOn failure. Inform Coders.")
|
|
||||||
clicked_int.ui_interact(src)
|
|
||||||
|
|
||||||
/proc/populate_interaction_instances()
|
|
||||||
if(GLOB.interaction_instances.len)
|
|
||||||
return
|
|
||||||
for(var/spath in subtypesof(/datum/interaction))
|
|
||||||
var/datum/interaction/interaction = new spath()
|
|
||||||
GLOB.interaction_instances[interaction.name] = interaction
|
|
||||||
populate_interaction_jsons(INTERACTION_JSON_FOLDER)
|
|
||||||
|
|
||||||
/proc/populate_interaction_jsons(directory)
|
|
||||||
for(var/file in flist(directory))
|
|
||||||
if(flist(directory + file) && !findlasttext(directory + file, ".json"))
|
|
||||||
populate_interaction_instances(directory + file)
|
|
||||||
continue
|
|
||||||
if(findlasttext(directory + file, ".master.json")) // This is a master json which has special handling
|
|
||||||
populate_interaction_jsons_master(directory + file)
|
|
||||||
continue
|
|
||||||
var/datum/interaction/int = new()
|
|
||||||
if(int.load_from_json(directory + file))
|
|
||||||
GLOB.interaction_instances[int.name] = int
|
|
||||||
else message_admins("Error loading interaction from file: '[directory + file]'. Inform coders.")
|
|
||||||
|
|
||||||
/proc/populate_interaction_jsons_master(path)
|
|
||||||
if(!fexists(path))
|
|
||||||
message_admins("We are attempting to load an interaction master without the file existing! '[path]'")
|
|
||||||
return
|
|
||||||
var/file = file(path)
|
|
||||||
var/list/json = json_load(file)
|
|
||||||
|
|
||||||
for(var/iname in json)
|
|
||||||
if(GLOB.interaction_instances[iname])
|
|
||||||
message_admins("Interaction Master '[path]' contained a duplicate interaction! '[iname]'")
|
|
||||||
continue
|
|
||||||
|
|
||||||
var/list/ijson = json[iname]
|
|
||||||
if(ijson["name"] != iname)
|
|
||||||
message_admins("Interaction Master '[path]' contained an invalid interaction! '[iname]'")
|
|
||||||
continue
|
|
||||||
|
|
||||||
var/datum/interaction/int = new()
|
|
||||||
|
|
||||||
int.distance_allowed = sanitize_integer(ijson["distance_allowed"], 0, 1, 0)
|
|
||||||
int.message = sanitize_islist(ijson["message"], list("json error"))
|
|
||||||
int.category = sanitize_text(ijson["category"])
|
|
||||||
int.usage = sanitize_text(ijson["usage"])
|
|
||||||
int.sound_use = sanitize_integer(ijson["sound_use"], 0, 1, 0)
|
|
||||||
int.sound_range = sanitize_integer(ijson["sound_range"], 1, 7, 1)
|
|
||||||
int.sound_possible = sanitize_islist(ijson["sound_possible"], list("json error"))
|
|
||||||
int.interaction_requires = sanitize_islist(ijson["interaction_requires"], list())
|
|
||||||
|
|
||||||
int.user_messages = sanitize_islist(ijson["user_messages"], list())
|
|
||||||
int.user_required_parts = sanitize_islist(ijson["user_required_parts"], list())
|
|
||||||
int.user_arousal = sanitize_integer(ijson["user_arousal"], 0, 100, 0)
|
|
||||||
int.user_pleasure = sanitize_integer(ijson["user_pleasure"], 0, 100, 0)
|
|
||||||
int.user_pain = sanitize_integer(ijson["user_pain"], 0, 100, 0)
|
|
||||||
int.target_messages = sanitize_islist(ijson["target_messages"], list())
|
|
||||||
int.target_required_parts = sanitize_islist(ijson["target_required_parts"], list())
|
|
||||||
int.target_arousal = sanitize_integer(ijson["target_arousal"], 0, 100, 0)
|
|
||||||
int.target_pleasure = sanitize_integer(ijson["target_pleasure"], 0, 100, 0)
|
|
||||||
int.target_pain = sanitize_integer(ijson["target_pain"], 0, 100, 0)
|
|
||||||
int.lewd = sanitize_integer(ijson["lewd"], 0, 1, 0)
|
|
||||||
|
|
||||||
GLOB.interaction_instances[iname] = int
|
|
||||||
|
|
||||||
/datum/interaction/proc/load_from_json(path)
|
|
||||||
var/fpath = path
|
|
||||||
if(!fexists(fpath))
|
|
||||||
message_admins("Attempted to load an interaction from json and the file does not exist")
|
|
||||||
qdel(src)
|
|
||||||
return FALSE
|
|
||||||
var/file = file(fpath)
|
|
||||||
var/list/json = json_load(file)
|
|
||||||
name = sanitize_text(json["name"])
|
|
||||||
description = sanitize_text(json["description"])
|
|
||||||
distance_allowed = sanitize_integer(json["distance_allowed"], 0, 1, 0)
|
|
||||||
message = sanitize_islist(json["message"], list("json error"))
|
|
||||||
category = sanitize_text(json["category"])
|
|
||||||
usage = sanitize_text(json["usage"])
|
|
||||||
sound_use = sanitize_integer(json["sound_use"], 0, 1, 0)
|
|
||||||
sound_range = sanitize_integer(json["sound_range"], 1, 7, 1)
|
|
||||||
sound_possible = sanitize_islist(json["sound_possible"], list("json error"))
|
|
||||||
interaction_requires = sanitize_islist(json["interaction_requires"], list())
|
|
||||||
|
|
||||||
user_messages = sanitize_islist(json["user_messages"], list())
|
|
||||||
user_required_parts = sanitize_islist(json["user_required_parts"], list())
|
|
||||||
user_arousal = sanitize_integer(json["user_arousal"], 0, 100, 0)
|
|
||||||
user_pleasure = sanitize_integer(json["user_pleasure"], 0, 100, 0)
|
|
||||||
user_pain = sanitize_integer(json["user_pain"], 0, 100, 0)
|
|
||||||
target_messages = sanitize_islist(json["target_messages"], list())
|
|
||||||
target_required_parts = sanitize_islist(json["target_required_parts"], list())
|
|
||||||
target_arousal = sanitize_integer(json["target_arousal"], 0, 100, 0)
|
|
||||||
target_pleasure = sanitize_integer(json["target_pleasure"], 0, 100, 0)
|
|
||||||
target_pain = sanitize_integer(json["target_pain"], 0, 100, 0)
|
|
||||||
lewd = sanitize_integer(json["lewd"], 0, 1, 0)
|
|
||||||
return TRUE
|
|
||||||
|
|
||||||
/datum/interaction/proc/json_save(path)
|
|
||||||
var/fpath = path
|
|
||||||
if(fexists(fpath))
|
|
||||||
fdel(fpath)
|
|
||||||
var/list/json = list(
|
|
||||||
"name" = name,
|
|
||||||
"description" = description,
|
|
||||||
"distance_allowed" = distance_allowed,
|
|
||||||
"message" = message,
|
|
||||||
"category" = category,
|
|
||||||
"usage" = usage,
|
|
||||||
"sound_use" = sound_use,
|
|
||||||
"sound_range" = sound_range,
|
|
||||||
"sound_possible" = sound_possible,
|
|
||||||
"interaction_requires" = interaction_requires,
|
|
||||||
"user_messages" = user_messages,
|
|
||||||
"user_required_parts" = user_required_parts,
|
|
||||||
"user_arousal" = user_arousal,
|
|
||||||
"user_pleasure" = user_pleasure,
|
|
||||||
"user_pain" = user_pain,
|
|
||||||
"target_messages" = target_messages,
|
|
||||||
"target_required_parts" = target_required_parts,
|
|
||||||
"target_arousal" = target_arousal,
|
|
||||||
"target_pleasure" = target_pleasure,
|
|
||||||
"target_pain" = target_pain,
|
|
||||||
"lewd" = lewd,
|
|
||||||
)
|
|
||||||
var/file = file(fpath)
|
|
||||||
WRITE_FILE(file, json_encode(json))
|
|
||||||
return TRUE
|
|
||||||
|
|
||||||
/datum/interaction
|
/datum/interaction
|
||||||
/// The name to be displayed in the interaction menu for this interaction
|
/// The name to be displayed in the interaction menu for this interaction
|
||||||
var/name = "broken interaction"
|
var/name = "broken interaction"
|
||||||
@@ -202,6 +46,8 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
|
|||||||
var/list/sound_possible = list()
|
var/list/sound_possible = list()
|
||||||
/// What requirements does this interaction have? See defines.
|
/// What requirements does this interaction have? See defines.
|
||||||
var/list/interaction_requires = list()
|
var/list/interaction_requires = list()
|
||||||
|
/// What color should the interaction button be?
|
||||||
|
var/color = "blue"
|
||||||
|
|
||||||
/datum/interaction/proc/allow_act(mob/living/carbon/human/user, mob/living/carbon/human/target)
|
/datum/interaction/proc/allow_act(mob/living/carbon/human/user, mob/living/carbon/human/target)
|
||||||
if(target == user && usage == INTERACTION_OTHER)
|
if(target == user && usage == INTERACTION_OTHER)
|
||||||
@@ -228,21 +74,16 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
|
|||||||
if(INTERACTION_REQUIRE_SELF_HAND)
|
if(INTERACTION_REQUIRE_SELF_HAND)
|
||||||
if(!user.get_active_hand())
|
if(!user.get_active_hand())
|
||||||
return FALSE
|
return FALSE
|
||||||
if(INTERACTION_REQUIRE_SELF_SPEAK)
|
|
||||||
if(!user.can_speak())
|
|
||||||
return FALSE
|
|
||||||
if(INTERACTION_REQUIRE_TARGET_HAND)
|
if(INTERACTION_REQUIRE_TARGET_HAND)
|
||||||
if(!target.get_active_hand())
|
if(!target.get_active_hand())
|
||||||
return FALSE
|
return FALSE
|
||||||
if(INTERACTION_REQUIRE_TARGET_SPEAK)
|
|
||||||
if(!target.can_speak())
|
|
||||||
return FALSE
|
|
||||||
else
|
else
|
||||||
message_admins("Unimplemented interaction requirement '[requirement]'.")
|
|
||||||
CRASH("Unimplemented interaction requirement '[requirement]'")
|
CRASH("Unimplemented interaction requirement '[requirement]'")
|
||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
/datum/interaction/proc/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
|
/datum/interaction/proc/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
|
||||||
|
if(!allow_act(user, target))
|
||||||
|
return
|
||||||
if(!message)
|
if(!message)
|
||||||
message_admins("Interaction had a null message list. '[name]'")
|
message_admins("Interaction had a null message list. '[name]'")
|
||||||
return
|
return
|
||||||
@@ -256,12 +97,14 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
|
|||||||
user.emote("subtler", null, msg, TRUE)
|
user.emote("subtler", null, msg, TRUE)
|
||||||
else
|
else
|
||||||
user.manual_emote(msg)
|
user.manual_emote(msg)
|
||||||
var/user_msg = pick(user_messages)
|
if(user_messages.len)
|
||||||
user_msg = replacetext(replacetext(user_msg, "%TARGET%", "[target]"), "%USER%", "[user]")
|
var/user_msg = pick(user_messages)
|
||||||
to_chat(user, user_msg)
|
user_msg = replacetext(replacetext(user_msg, "%TARGET%", "[target]"), "%USER%", "[user]")
|
||||||
var/target_msg = pick(target_messages)
|
to_chat(user, user_msg)
|
||||||
target_msg = replacetext(replacetext(target_msg, "%TARGET%", "[target]"), "%USER%", "[user]")
|
if(target_messages.len)
|
||||||
to_chat(target, target_msg)
|
var/target_msg = pick(target_messages)
|
||||||
|
target_msg = replacetext(replacetext(target_msg, "%TARGET%", "[target]"), "%USER%", "[user]")
|
||||||
|
to_chat(target, target_msg)
|
||||||
if(sound_use)
|
if(sound_use)
|
||||||
if(!sound_possible)
|
if(!sound_possible)
|
||||||
message_admins("Interaction has sound_use set to TRUE but does not set sound! '[name]'")
|
message_admins("Interaction has sound_use set to TRUE but does not set sound! '[name]'")
|
||||||
@@ -281,101 +124,134 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
|
|||||||
target.adjustArousal(target_arousal)
|
target.adjustArousal(target_arousal)
|
||||||
target.adjustPain(target_pain)
|
target.adjustPain(target_pain)
|
||||||
|
|
||||||
/datum/component/interactable
|
/datum/interaction/proc/load_from_json(path)
|
||||||
var/mob/living/carbon/human/self = null
|
var/fpath = path
|
||||||
var/interact_last = 0
|
if(!fexists(fpath))
|
||||||
var/interact_next = 0
|
message_admins("Attempted to load an interaction from json and the file does not exist")
|
||||||
|
qdel(src)
|
||||||
|
return FALSE
|
||||||
|
var/file = file(fpath)
|
||||||
|
var/list/json = json_load(file)
|
||||||
|
name = sanitize_text(json["name"])
|
||||||
|
description = sanitize_text(json["description"])
|
||||||
|
distance_allowed = sanitize_integer(json["distance_allowed"], 0, 1, 0)
|
||||||
|
message = sanitize_islist(json["message"], list("json error"))
|
||||||
|
category = sanitize_text(json["category"])
|
||||||
|
usage = sanitize_text(json["usage"])
|
||||||
|
sound_use = sanitize_integer(json["sound_use"], 0, 1, 0)
|
||||||
|
sound_range = sanitize_integer(json["sound_range"], 1, 7, 1)
|
||||||
|
sound_possible = sanitize_islist(json["sound_possible"], list("json error"))
|
||||||
|
interaction_requires = sanitize_islist(json["interaction_requires"], list())
|
||||||
|
color = sanitize_text(json["color"])
|
||||||
|
|
||||||
/datum/component/interactable/proc/can_interact(datum/interaction/interaction, mob/living/carbon/human/user)
|
user_messages = sanitize_islist(json["user_messages"], list())
|
||||||
if(!interaction.allow_act(user, self))
|
user_required_parts = sanitize_islist(json["user_required_parts"], list())
|
||||||
return FALSE
|
user_arousal = sanitize_integer(json["user_arousal"], 0, 100, 0)
|
||||||
if(!interaction.distance_allowed && !user.Adjacent(self))
|
user_pleasure = sanitize_integer(json["user_pleasure"], 0, 100, 0)
|
||||||
return FALSE
|
user_pain = sanitize_integer(json["user_pain"], 0, 100, 0)
|
||||||
if(interaction.category == INTERACTION_CAT_HIDE)
|
target_messages = sanitize_islist(json["target_messages"], list())
|
||||||
return FALSE
|
target_required_parts = sanitize_islist(json["target_required_parts"], list())
|
||||||
if(self == user && interaction.usage == INTERACTION_OTHER)
|
target_arousal = sanitize_integer(json["target_arousal"], 0, 100, 0)
|
||||||
return FALSE
|
target_pleasure = sanitize_integer(json["target_pleasure"], 0, 100, 0)
|
||||||
|
target_pain = sanitize_integer(json["target_pain"], 0, 100, 0)
|
||||||
|
lewd = sanitize_integer(json["lewd"], 0, 1, 0)
|
||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
/datum/component/interactable/proc/make_interact_list(datum/component/interactable/other)
|
/datum/interaction/proc/json_save(path)
|
||||||
populate_interaction_instances()
|
var/fpath = path
|
||||||
var/list/ints = list()
|
if(fexists(fpath))
|
||||||
for(var/interaction in GLOB.interaction_instances)
|
fdel(fpath)
|
||||||
if(other.can_interact(GLOB.interaction_instances[interaction], self))
|
var/list/json = list(
|
||||||
ints += GLOB.interaction_instances[interaction]
|
"name" = name,
|
||||||
return ints
|
"description" = description,
|
||||||
|
"distance_allowed" = distance_allowed,
|
||||||
/datum/component/interactable/proc/mil_mob(mob/user)
|
"message" = message,
|
||||||
var/datum/other = user.GetComponent(/datum/component/interactable)
|
"category" = category,
|
||||||
if(!other)
|
"usage" = usage,
|
||||||
CRASH("Unable to locate the interactable component for the given mob.")
|
"sound_use" = sound_use,
|
||||||
return make_interact_list(other)
|
"sound_range" = sound_range,
|
||||||
|
"sound_possible" = sound_possible,
|
||||||
/datum/component/interactable/ui_interact(mob/user, datum/tgui/ui)
|
"interaction_requires" = interaction_requires,
|
||||||
ui = SStgui.try_update_ui(user, src, ui)
|
"color" = color,
|
||||||
if(!ui)
|
"user_messages" = user_messages,
|
||||||
ui = new(user, src, "InteractionMenu")
|
"user_required_parts" = user_required_parts,
|
||||||
ui.set_autoupdate(TRUE)
|
"user_arousal" = user_arousal,
|
||||||
ui.open()
|
"user_pleasure" = user_pleasure,
|
||||||
|
"user_pain" = user_pain,
|
||||||
/datum/component/interactable/ui_status(mob/user, datum/ui_state/state)
|
"target_messages" = target_messages,
|
||||||
return UI_INTERACTIVE // This UI is always interactive as we handle distance flags via can_interact
|
"target_required_parts" = target_required_parts,
|
||||||
|
"target_arousal" = target_arousal,
|
||||||
|
"target_pleasure" = target_pleasure,
|
||||||
|
"target_pain" = target_pain,
|
||||||
|
"lewd" = lewd,
|
||||||
|
)
|
||||||
|
var/file = file(fpath)
|
||||||
|
WRITE_FILE(file, json_encode(json))
|
||||||
|
return TRUE
|
||||||
|
|
||||||
/mob/living/carbon/human/ComponentInitialize()
|
/mob/living/carbon/human/ComponentInitialize()
|
||||||
. = ..()
|
. = ..()
|
||||||
AddComponent(/datum/component/interactable)
|
AddComponent(/datum/component/interactable)
|
||||||
|
|
||||||
/datum/component/interactable/Initialize(...)
|
/// Global loading procs
|
||||||
. = ..()
|
/proc/populate_interaction_instances()
|
||||||
self = parent
|
for(var/spath in subtypesof(/datum/interaction))
|
||||||
|
var/datum/interaction/interaction = new spath()
|
||||||
|
GLOB.interaction_instances[interaction.name] = interaction
|
||||||
|
populate_interaction_jsons(INTERACTION_JSON_FOLDER)
|
||||||
|
|
||||||
/mob/living/carbon/human/verb/cmd_interact()
|
/proc/populate_interaction_jsons(directory)
|
||||||
set src in view()
|
for(var/file in flist(directory))
|
||||||
set category = "IC"
|
if(flist(directory + file) && !findlasttext(directory + file, ".json"))
|
||||||
set name = "Interact"
|
populate_interaction_instances(directory + file)
|
||||||
|
continue
|
||||||
|
if(findlasttext(directory + file, ".master.json")) // This is a master json which has special handling
|
||||||
|
populate_interaction_jsons_master(directory + file)
|
||||||
|
continue
|
||||||
|
var/datum/interaction/interaction = new()
|
||||||
|
if(interaction.load_from_json(directory + file))
|
||||||
|
GLOB.interaction_instances[interaction.name] = interaction
|
||||||
|
else message_admins("Error loading interaction from file: '[directory + file]'. Inform coders.")
|
||||||
|
|
||||||
if(isobserver(usr) || isdead(usr) || usr.stat == DEAD)
|
/proc/populate_interaction_jsons_master(path)
|
||||||
to_chat(usr, "You are dead.")
|
if(!fexists(path))
|
||||||
|
message_admins("We are attempting to load an interaction master without the file existing! '[path]'")
|
||||||
return
|
return
|
||||||
|
var/file = file(path)
|
||||||
|
var/list/json = json_load(file)
|
||||||
|
|
||||||
var/datum/component/interactable/int = GetComponent(/datum/component/interactable)
|
for(var/iname in json)
|
||||||
int.ui_interact(get_mob_by_ckey(usr.ckey))
|
if(GLOB.interaction_instances[iname])
|
||||||
|
message_admins("Interaction Master '[path]' contained a duplicate interaction! '[iname]'")
|
||||||
|
continue
|
||||||
|
|
||||||
/datum/component/interactable/ui_data(mob/user)
|
var/list/ijson = json[iname]
|
||||||
var/list/data = list()
|
if(ijson["name"] != iname)
|
||||||
var/list/datum/interaction/ints = mil_mob(user)
|
message_admins("Interaction Master '[path]' contained an invalid interaction! '[iname]'")
|
||||||
|
continue
|
||||||
|
|
||||||
var/list/descs = list()
|
var/datum/interaction/interaction = new()
|
||||||
var/list/cats = list()
|
|
||||||
for(var/datum/interaction/int in ints)
|
|
||||||
if(!cats[int.category])
|
|
||||||
cats[int.category] = list(int.name)
|
|
||||||
else cats[int.category] += int.name
|
|
||||||
descs[int.name] = int.description
|
|
||||||
data["categories"] = list()
|
|
||||||
data["descs"] = descs
|
|
||||||
for(var/cat in cats)
|
|
||||||
data["categories"] += cat
|
|
||||||
|
|
||||||
data["ref_user"] = REF(user)
|
interaction.distance_allowed = sanitize_integer(ijson["distance_allowed"], 0, 1, 0)
|
||||||
data["ref_self"] = REF(self)
|
interaction.message = sanitize_islist(ijson["message"], list("json error"))
|
||||||
data["self"] = self.name
|
interaction.category = sanitize_text(ijson["category"])
|
||||||
data["ints"] = cats
|
interaction.usage = sanitize_text(ijson["usage"])
|
||||||
data["block_interact"] = interact_next >= world.time
|
interaction.sound_use = sanitize_integer(ijson["sound_use"], 0, 1, 0)
|
||||||
return data
|
interaction.sound_range = sanitize_integer(ijson["sound_range"], 1, 7, 1)
|
||||||
|
interaction.sound_possible = sanitize_islist(ijson["sound_possible"], list("json error"))
|
||||||
|
interaction.interaction_requires = sanitize_islist(ijson["interaction_requires"], list())
|
||||||
|
interaction.color = sanitize_text(ijson["color"])
|
||||||
|
|
||||||
/datum/component/interactable/ui_act(action, list/params)
|
interaction.user_messages = sanitize_islist(ijson["user_messages"], list())
|
||||||
. = ..()
|
interaction.user_required_parts = sanitize_islist(ijson["user_required_parts"], list())
|
||||||
if(.)
|
interaction.user_arousal = sanitize_integer(ijson["user_arousal"], 0, 100, 0)
|
||||||
return
|
interaction.user_pleasure = sanitize_integer(ijson["user_pleasure"], 0, 100, 0)
|
||||||
populate_interaction_instances()
|
interaction.user_pain = sanitize_integer(ijson["user_pain"], 0, 100, 0)
|
||||||
for(var/interaction in GLOB.interaction_instances)
|
interaction.target_messages = sanitize_islist(ijson["target_messages"], list())
|
||||||
if(interaction == params["interaction"])
|
interaction.target_required_parts = sanitize_islist(ijson["target_required_parts"], list())
|
||||||
GLOB.interaction_instances[interaction].act(locate(params["userref"]), locate(params["selfref"]))
|
interaction.target_arousal = sanitize_integer(ijson["target_arousal"], 0, 100, 0)
|
||||||
var/mob/living/carbon/human/user = locate(params["userref"])
|
interaction.target_pleasure = sanitize_integer(ijson["target_pleasure"], 0, 100, 0)
|
||||||
var/datum/component/interactable/int = user.GetComponent(/datum/component/interactable)
|
interaction.target_pain = sanitize_integer(ijson["target_pain"], 0, 100, 0)
|
||||||
int.interact_last = world.time
|
interaction.lewd = sanitize_integer(ijson["lewd"], 0, 1, 0)
|
||||||
interact_next = int.interact_last + INTERACTION_COOLDOWN
|
|
||||||
int.interact_next = interact_next
|
GLOB.interaction_instances[iname] = interaction
|
||||||
return TRUE
|
|
||||||
message_admins("Unhandled interaction '[params["interaction"]]'. Inform coders.")
|
|
||||||
@@ -189,6 +189,7 @@
|
|||||||
#include "code\__DEFINES\~skyrat_defines\game_options.dm"
|
#include "code\__DEFINES\~skyrat_defines\game_options.dm"
|
||||||
#include "code\__DEFINES\~skyrat_defines\gun.dm"
|
#include "code\__DEFINES\~skyrat_defines\gun.dm"
|
||||||
#include "code\__DEFINES\~skyrat_defines\integrated_electronics.dm"
|
#include "code\__DEFINES\~skyrat_defines\integrated_electronics.dm"
|
||||||
|
#include "code\__DEFINES\~skyrat_defines\interactions.dm"
|
||||||
#include "code\__DEFINES\~skyrat_defines\inventory.dm"
|
#include "code\__DEFINES\~skyrat_defines\inventory.dm"
|
||||||
#include "code\__DEFINES\~skyrat_defines\jobs.dm"
|
#include "code\__DEFINES\~skyrat_defines\jobs.dm"
|
||||||
#include "code\__DEFINES\~skyrat_defines\keybindings.dm"
|
#include "code\__DEFINES\~skyrat_defines\keybindings.dm"
|
||||||
@@ -4606,7 +4607,9 @@
|
|||||||
#include "modular_skyrat\modules\indicators\code\ssd_indicator.dm"
|
#include "modular_skyrat\modules\indicators\code\ssd_indicator.dm"
|
||||||
#include "modular_skyrat\modules\indicators\code\typing_indicator.dm"
|
#include "modular_skyrat\modules\indicators\code\typing_indicator.dm"
|
||||||
#include "modular_skyrat\modules\inflatables\code\inflatable.dm"
|
#include "modular_skyrat\modules\inflatables\code\inflatable.dm"
|
||||||
#include "modular_skyrat\modules\interaction_menu\code\interaction_menu.dm"
|
#include "modular_skyrat\modules\interaction_menu\code\click.dm"
|
||||||
|
#include "modular_skyrat\modules\interaction_menu\code\interaction_component.dm"
|
||||||
|
#include "modular_skyrat\modules\interaction_menu\code\interaction_datum.dm"
|
||||||
#include "modular_skyrat\modules\jukebox\code\controllers\subsystem\jukeboxes.dm"
|
#include "modular_skyrat\modules\jukebox\code\controllers\subsystem\jukeboxes.dm"
|
||||||
#include "modular_skyrat\modules\jukebox\code\controllers\subsystem\game\machinery\dance_machine.dm"
|
#include "modular_skyrat\modules\jukebox\code\controllers\subsystem\game\machinery\dance_machine.dm"
|
||||||
#include "modular_skyrat\modules\jungle\code\game\objects\structures\flora.dm"
|
#include "modular_skyrat\modules\jungle\code\game\objects\structures\flora.dm"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Window } from '../layouts';
|
|||||||
|
|
||||||
export const InteractionMenu = (props, context) => {
|
export const InteractionMenu = (props, context) => {
|
||||||
const { act, data } = useBackend(context);
|
const { act, data } = useBackend(context);
|
||||||
const { categories, ints, descs } = data; // Interaction categories
|
const { categories, interactions, descriptions, colors } = data;
|
||||||
const { self, ref_self, ref_user } = data;
|
const { self, ref_self, ref_user } = data;
|
||||||
const { block_interact } = data;
|
const { block_interact } = data;
|
||||||
|
|
||||||
@@ -19,17 +19,17 @@ export const InteractionMenu = (props, context) => {
|
|||||||
<Section key="interactions">
|
<Section key="interactions">
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<Section key={category} title={category}>
|
<Section key={category} title={category}>
|
||||||
{ints[category].map((interaction) => (
|
{interactions[category].map((interaction) => (
|
||||||
<Section key={interaction}>
|
<Section key={interaction}>
|
||||||
<left>
|
<left>
|
||||||
<Button margin={0} padding={0}
|
<Button margin={0} padding={0}
|
||||||
disabled={block_interact}
|
disabled={block_interact}
|
||||||
color={block_interact ? "grey" : "blue"}
|
color={block_interact ? "grey" : colors[interaction]}
|
||||||
content={interaction}
|
content={interaction}
|
||||||
icon="exclamation-circle"
|
icon="exclamation-circle"
|
||||||
onClick={() => act('interact', { interaction: interaction, selfref: ref_self, userref: ref_user })}
|
onClick={() => act('interact', { interaction: interaction, selfref: ref_self, userref: ref_user })}
|
||||||
/><br />
|
/><br />
|
||||||
{descs[interaction]}
|
{descriptions[interaction]}
|
||||||
</left>
|
</left>
|
||||||
</Section>
|
</Section>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user