diff --git a/code/__DEFINES/~~~splurt_defines/interactions.dm b/code/__DEFINES/~~~splurt_defines/interactions.dm
new file mode 100644
index 0000000000000..8381fd905df9a
--- /dev/null
+++ b/code/__DEFINES/~~~splurt_defines/interactions.dm
@@ -0,0 +1,53 @@
+/// Extra interaction checks.
+
+// A person needs to have mouth available to do this interaction
+#define INTERACTION_REQUIRE_SELF_MOUTH "self_mouth"
+#define INTERACTION_REQUIRE_TARGET_MOUTH "target_mouth"
+
+// A person needs to have their top clothes removed to do this interaction
+#define INTERACTION_REQUIRE_SELF_TOPLESS "self_topless"
+#define INTERACTION_REQUIRE_TARGET_TOPLESS "target_topless"
+
+// A person needs to have their bottom clothes removed to do this interaction
+#define INTERACTION_REQUIRE_SELF_BOTTOMLESS "self_bottomless"
+#define INTERACTION_REQUIRE_TARGET_BOTTOMLESS "target_bottomless"
+
+// A person needs to have their feet exposed to do this interaction
+#define INTERACTION_REQUIRE_SELF_FEET "self_feet"
+#define INTERACTION_REQUIRE_TARGET_FEET "target_feet"
+
+/// Climax definitions
+
+//Climaxing genitals
+#define CLIMAX_VAGINA "vagina"
+#define CLIMAX_PENIS "penis"
+#define CLIMAX_BOTH "both"
+
+//Climaxing positions
+#define CLIMAX_POSITION_USER "climax_user"
+#define CLIMAX_POSITION_TARGET "climax_target"
+
+//Climaxing targets (use these if you're not using organ slots)
+#define CLIMAX_TARGET_MOUTH "mouth"
+#define CLIMAX_TARGET_SHEATH "sheath"
+
+// Extreme types
+#define INTERACTION_EXTREME (1<<0)
+#define INTERACTION_HARMFUL (1<<1) // If you use this on an interaction, add INTERACTION_EXTREME to it as well
+#define INTERACTION_UNHOLY (1<<2)
+
+// Interaction categories
+#define INTERACTION_CAT_LEWD "lewd"
+#define INTERACTION_CAT_EXTREME "extreme"
+#define INTERACTION_CAT_HARMFUL "harmful"
+#define INTERACTION_CAT_UNHOLY "unholy"
+
+// Additional details sent to the interaction menu
+#define INTERACTION_FILLS_CONTAINERS list( \
+ "info" = "You can fill a container if you have it in your active hand or are pulling it", \
+ "icon" = "flask", \
+ "color" = "transparent" \
+ )
+
+// Interaction flags (used for logic but normally not sent to the interaction menu)
+#define INTERACTION_OVERRIDE_FLUID_TRANSFER (1<<0)
diff --git a/code/__DEFINES/~~~splurt_defines/subsystems.dm b/code/__DEFINES/~~~splurt_defines/subsystems.dm
new file mode 100644
index 0000000000000..c581df65ca656
--- /dev/null
+++ b/code/__DEFINES/~~~splurt_defines/subsystems.dm
@@ -0,0 +1,6 @@
+/*
+ * # INIT_ORDER_INTERACTIONS
+ * Used by the Interactions subsystems, used to set it's own position in the queue.
+ * This puts this last on priority, very far from other subsystems,
+*/
+#define INIT_ORDER_INTERACTIONS -150
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 5af3f7b842275..4c43381d46e22 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -105,7 +105,7 @@
load_whitelist()
// SKYRAT EDIT ADDITION START
- populate_interaction_instances()
+ //populate_interaction_instances() //SPLURT EDIT REMOVAL - Interactions
remove_erp_things()
// SKYRAT EDIT ADDITION END
diff --git a/modular_skyrat/master_files/code/modules/mob/living/examine_tgui.dm b/modular_skyrat/master_files/code/modules/mob/living/examine_tgui.dm
index ec0dd5b9b4edc..5d7a578287510 100644
--- a/modular_skyrat/master_files/code/modules/mob/living/examine_tgui.dm
+++ b/modular_skyrat/master_files/code/modules/mob/living/examine_tgui.dm
@@ -66,10 +66,20 @@
var/e_prefs_v = preferences.read_preference(/datum/preference/choiced/erp_status_v)
var/e_prefs_nc = preferences.read_preference(/datum/preference/choiced/erp_status_nc)
var/e_prefs_mechanical = preferences.read_preference(/datum/preference/choiced/erp_status_mechanics)
+ // SPLURT EDIT ADDITION - Interaction preferences
+ var/e_prefs_extm = preferences.read_preference(/datum/preference/choiced/erp_status_extm)
+ var/e_prefs_extmharm = preferences.read_preference(/datum/preference/choiced/erp_status_extmharm)
+ var/e_prefs_unholy = preferences.read_preference(/datum/preference/choiced/erp_status_unholy)
+ // SPLURT EDIT END
ooc_notes += "ERP: [e_prefs]\n"
ooc_notes += "Hypnosis: [e_prefs_hypno]\n"
ooc_notes += "Vore: [e_prefs_v]\n"
ooc_notes += "Non-Con: [e_prefs_nc]\n"
+ // SPLURT EDIT ADDITION - Interaction preferences
+ ooc_notes += "Extreme ERP verbs: [e_prefs_extm]\n"
+ ooc_notes += "Harmful ERP verbs: [e_prefs_extmharm]\n"
+ ooc_notes += "Unholy ERP verbs: [e_prefs_unholy]\n"
+ // SPLURT EDIT END
ooc_notes += "ERP Mechanics: [e_prefs_mechanical]\n"
ooc_notes += "\n"
diff --git a/modular_skyrat/modules/interaction_menu/code/interaction_component.dm b/modular_skyrat/modules/interaction_menu/code/interaction_component.dm
index b6122decd070e..42f54025a2d88 100644
--- a/modular_skyrat/modules/interaction_menu/code/interaction_component.dm
+++ b/modular_skyrat/modules/interaction_menu/code/interaction_component.dm
@@ -21,13 +21,31 @@
/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]
+ //SPLURT EDIT - Interactions subsystem
+ if(!SSinteractions)
+ return // Can't continue, no subsystem
+ for(var/iterating_interaction_id in SSinteractions.interactions)
+ var/datum/interaction/interaction = SSinteractions.interactions[iterating_interaction_id]
+ //SPLURT EDIT END
if(interaction.lewd)
if(!self.client?.prefs?.read_preference(/datum/preference/toggle/erp))
continue
+ // SPLURT EDIT ADDITION - Interaction preferences
+ if(interaction.unsafe_types & INTERACTION_EXTREME)
+ if(self.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_extm) == "No")
+ continue
+ if(interaction.unsafe_types & INTERACTION_HARMFUL)
+ if(self.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_extmharm) == "No")
+ continue
+ if(interaction.unsafe_types & INTERACTION_UNHOLY)
+ if(self.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_unholy) == "No")
+ continue
+ // SPLURT EDIT END
+ /*
+ SPLURT EDIT REMOVAL - Interactions
if(interaction.sexuality != "" && interaction.sexuality != self.client?.prefs?.read_preference(/datum/preference/choiced/erp_sexuality))
continue
+ */
interactions.Add(interaction)
/datum/component/interactable/RegisterWithParent()
@@ -143,11 +161,11 @@
if(params["interaction"])
var/interaction_id = params["interaction"]
- if(GLOB.interaction_instances[interaction_id])
+ if(SSinteractions.interactions[interaction_id])
var/mob/living/carbon/human/user = locate(params["userref"])
- if(!can_interact(GLOB.interaction_instances[interaction_id], user))
+ if(!can_interact(SSinteractions.interactions[interaction_id], user))
return FALSE
- GLOB.interaction_instances[interaction_id].act(user, locate(params["selfref"]))
+ SSinteractions.interactions[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
diff --git a/modular_skyrat/modules/interaction_menu/code/interaction_datum.dm b/modular_skyrat/modules/interaction_menu/code/interaction_datum.dm
index 7826e8d285047..bc5744f6094e7 100644
--- a/modular_skyrat/modules/interaction_menu/code/interaction_datum.dm
+++ b/modular_skyrat/modules/interaction_menu/code/interaction_datum.dm
@@ -1,5 +1,7 @@
+/* SPLURT EDIT REMOVAL - Interactions subsystem
GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
+*/
/datum/interaction
/// The name to be displayed in the interaction menu for this interaction
@@ -48,9 +50,12 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
var/list/interaction_requires = list()
/// What color should the interaction button be?
var/color = "blue"
+ /* SPLURT EDIT REMOVAL - Interactions
/// What sexuality preference do we display for.
var/sexuality = ""
+ */
+/* SPLURT EDIT REMOVAL - Interactions - Refractored in modular
/datum/interaction/proc/allow_act(mob/living/carbon/human/user, mob/living/carbon/human/target)
if(target == user && usage == INTERACTION_OTHER)
return FALSE
@@ -82,6 +87,7 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
else
CRASH("Unimplemented interaction requirement '[requirement]'")
return TRUE
+*/ //SPLURT EDIT END
/datum/interaction/proc/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
if(!allow_act(user, target))
@@ -115,16 +121,24 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
message_admins("Deprecated sound handling for '[name]'. Correct format is a list with one entry. This message will only show once.")
sound_possible = list(sound_possible)
sound_cache = pick(sound_possible)
+ //SPLURT EDIT - Interactions - For some reason the original code didn't send any sound
+ /*
for(var/mob/mob in view(sound_range, user))
SEND_SOUND(sound_cache, mob)
-
+ */
+ conditional_pref_sound(user, sound_cache, 80, TRUE, falloff_distance = sound_range, pref_to_check = /datum/preference/toggle/erp/sounds)
+ //SPLURT EDIT END
if(lewd)
- user.adjust_pleasure(user_pleasure)
+ user.adjust_pleasure(user_pleasure * (target.dna.features["sexual_potency"] || 1), target, src, CLIMAX_POSITION_USER) //SPLURT EDIT - Interactions
user.adjust_arousal(user_arousal)
- user.adjust_pain(user_pain)
- target.adjust_pleasure(target_pleasure)
- target.adjust_arousal(target_arousal)
- target.adjust_pain(target_pain)
+ user.adjust_pain(user_pain, target, src, CLIMAX_POSITION_USER) //SPLURT EDIT - Interactions
+ if(usage == INTERACTION_OTHER) //SPLURT EDIT - Interactions
+ target.adjust_pleasure(target_pleasure * (user.dna.features["sexual_potency"] || 1), user, src, CLIMAX_POSITION_TARGET) //SPLURT EDIT - Interactions
+ target.adjust_arousal(target_arousal)
+ target.adjust_pain(target_pain, user, src, CLIMAX_POSITION_TARGET) //SPLURT EDIT - Interactions
+
+ post_interaction(user, target) //SPLURT EDIT - Interactions
+ return TRUE
/datum/interaction/proc/load_from_json(path)
var/fpath = path
@@ -157,7 +171,7 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
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)
- sexuality = sanitize_text(json["sexuality"])
+ // sexuality = sanitize_text(json["sexuality"]) //SPLURT EDIT REMOVAL - Interactions
return TRUE
/datum/interaction/proc/json_save(path)
@@ -187,7 +201,7 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
"target_pleasure" = target_pleasure,
"target_pain" = target_pain,
"lewd" = lewd,
- "sexuality" = sexuality,
+ // "sexuality" = sexuality, //SPLURT EDIT REMOVAL - Interactions
)
var/file = file(fpath)
WRITE_FILE(file, json_encode(json))
@@ -201,7 +215,11 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
/proc/populate_interaction_instances()
for(var/spath in subtypesof(/datum/interaction))
var/datum/interaction/interaction = new spath()
- GLOB.interaction_instances[interaction.name] = interaction
+ //SPLURT EDIT - Interactions
+ if(interaction.name == /datum/interaction::name || interaction.description == /datum/interaction::description)
+ continue
+ //SPLURT EDIT END
+ SSinteractions.interactions[interaction.name] = interaction //SPLURT EDIT - Interactions subsystem - Original: GLOB.interaction_instances[interaction.name] = interaction
populate_interaction_jsons(INTERACTION_JSON_FOLDER)
/proc/populate_interaction_jsons(directory)
@@ -214,7 +232,7 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
continue
var/datum/interaction/interaction = new()
if(interaction.load_from_json(directory + file))
- GLOB.interaction_instances[interaction.name] = interaction
+ SSinteractions.interactions[interaction.name] = interaction //SPLURT EDIT - Interactions subsystem - Original: GLOB.interaction_instances[interaction.name] = interaction
else message_admins("Error loading interaction from file: '[directory + file]'. Inform coders.")
/proc/populate_interaction_jsons_master(path)
@@ -225,7 +243,7 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
var/list/json = json_load(file)
for(var/iname in json)
- if(GLOB.interaction_instances[iname])
+ if(SSinteractions.interactions[iname]) //SPLURT EDIT - Interactions subsystem - Original: if(GLOB.interaction_instances[iname])
message_admins("Interaction Master '[path]' contained a duplicate interaction! '[iname]'")
continue
@@ -257,9 +275,9 @@ GLOBAL_LIST_EMPTY_TYPED(interaction_instances, /datum/interaction)
interaction.target_pleasure = sanitize_integer(ijson["target_pleasure"], 0, 100, 0)
interaction.target_pain = sanitize_integer(ijson["target_pain"], 0, 100, 0)
interaction.lewd = sanitize_integer(ijson["lewd"], 0, 1, 0)
- interaction.sexuality = sanitize_text(ijson["sexuality"])
+ // interaction.sexuality = sanitize_text(ijson["sexuality"]) //SPLURT EDIT REMOVAL - Interactions
- GLOB.interaction_instances[iname] = interaction
+ SSinteractions.interactions[iname] = interaction //SPLURT EDIT - Interactions subsystem - Original: GLOB.interaction_instances[iname] = interaction
ADMIN_VERB(reload_interactions, R_DEBUG, "Reload Interactions", "Force reload interactions.", ADMIN_CATEGORY_DEBUG)
- populate_interaction_instances()
+ SSinteractions.prepare_interactions() //SPLURT EDIT - Interactions subsystem - Original: populate_interaction_instances()
diff --git a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/climax.dm b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/climax.dm
index e980f1c9c0f65..c652a792e5f43 100644
--- a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/climax.dm
+++ b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/climax.dm
@@ -1,6 +1,10 @@
+//SPLURT EDIT REMOVAL BEGIN - Interactions - Moved climax defines to global defines
+/*
#define CLIMAX_VAGINA "Vagina"
#define CLIMAX_PENIS "Penis"
#define CLIMAX_BOTH "Both"
+*/
+//SPLURT EDIT REMOVAL END
#define CLIMAX_ON_FLOOR "On the floor"
#define CLIMAX_IN_OR_ON "Climax in or on someone"
@@ -9,7 +13,7 @@
/// Used to prevent nightmare scenarios.
var/refractory_period
-/mob/living/carbon/human/proc/climax(manual = TRUE)
+/mob/living/carbon/human/proc/climax(manual = TRUE, mob/living/carbon/human/partner, datum/interaction/climax_interaction, interaction_position)
if (CONFIG_GET(flag/disable_erp_preferences))
return
@@ -39,7 +43,10 @@
else if(has_penis())
genitals.Add(CLIMAX_PENIS)
climax_choice = tgui_alert(src, "You are climaxing, choose which genitalia to climax with.", "Genitalia Preference!", genitals)
-
+ //SPLURT EDIT ADDITION BEGIN - Interactions
+ else if(climax_interaction?.cum_genital[interaction_position])
+ climax_choice = climax_interaction.cum_genital[interaction_position]
+ //SPLURT EDIT ADDITION END
switch(gender)
if(MALE)
conditional_pref_sound(get_turf(src), pick('modular_skyrat/modules/modular_items/lewd_items/sounds/final_m1.ogg',
@@ -81,7 +88,7 @@
if(interactable_inrange_humans.len)
buttons += CLIMAX_IN_OR_ON
- var/penis_climax_choice = tgui_alert(src, "Choose where to shoot your load.", "Load preference!", buttons)
+ var/penis_climax_choice = climax_interaction && !manual ? CLIMAX_IN_OR_ON : tgui_alert(src, "Choose where to shoot your load.", "Load preference!", buttons) //SPLURT EDIT CHANGE - Interactions
var/create_cum_decal = FALSE
@@ -91,13 +98,13 @@
span_userlove("You shoot string after string of hot cum, hitting the floor!"))
else
- var/target_choice = tgui_input_list(src, "Choose a person to cum in or on.", "Choose target!", interactable_inrange_humans)
+ var/target_choice = climax_interaction && !manual ? partner?.name : tgui_input_list(src, "Choose a person to cum in or on.", "Choose target!", interactable_inrange_humans) //SPLURT EDIT CHANGE - Interactions
if(!target_choice)
create_cum_decal = TRUE
visible_message(span_userlove("[src] shoots [self_their] sticky load onto the floor!"), \
span_userlove("You shoot string after string of hot cum, hitting the floor!"))
else
- var/mob/living/carbon/human/target_human = interactable_inrange_humans[target_choice]
+ var/mob/living/carbon/human/target_human = climax_interaction && !manual && partner ? partner : interactable_inrange_humans[target_choice] //SPLURT EDIT CHANGE - Interactions
var/target_human_them = target_human.p_them()
var/list/target_buttons = list()
@@ -107,16 +114,29 @@
if(target_human.has_vagina(REQUIRE_GENITAL_EXPOSED))
target_buttons += ORGAN_SLOT_VAGINA
if(target_human.has_anus(REQUIRE_GENITAL_EXPOSED))
- target_buttons += "asshole"
+ target_buttons += ORGAN_SLOT_ANUS //SPLURT EDIT CHANGE - Interactions - Changed asshole to anus for consistency
if(target_human.has_penis(REQUIRE_GENITAL_EXPOSED))
+ target_buttons += ORGAN_SLOT_PENIS
var/obj/item/organ/external/genital/penis/other_penis = target_human.get_organ_slot(ORGAN_SLOT_PENIS)
if(other_penis.sheath != "None")
target_buttons += "sheath"
target_buttons += "On [target_human_them]"
- var/climax_into_choice = tgui_input_list(src, "Where on or in [target_human] do you wish to cum?", "Final frontier!", target_buttons)
+ //SPLURT EDIT CHANGE BEGIN - Interactions
+ var/climax_into_choice
+ var/interaction_inside = partner?.get_organ_slot(climax_interaction?.cum_target[interaction_position]) || target_buttons.Find(climax_interaction?.cum_target[interaction_position])
+
+ if(climax_interaction && !manual && interaction_inside)
+ climax_into_choice = climax_interaction.cum_target[interaction_position]
+ else if(manual)
+ climax_into_choice = tgui_input_list(src, "Where on or in [target_human] do you wish to cum?", "Final frontier!", target_buttons)
+ else
+ climax_into_choice = "On [target_human_them]"
- if(!climax_into_choice)
+ if(climax_interaction && !manual && climax_interaction.show_climax(src, target_human, interaction_position))
+ create_cum_decal = !interaction_inside
+ else if(!climax_into_choice)
+ //SPLURT EDIT CHANGE END
create_cum_decal = TRUE
visible_message(span_userlove("[src] shoots their sticky load onto the floor!"), \
span_userlove("You shoot string after string of hot cum, hitting the floor!"))
@@ -130,9 +150,33 @@
to_chat(target_human, span_userlove("Your [climax_into_choice] fills with warm cum as [src] shoots [self_their] load into it."))
var/obj/item/organ/external/genital/testicles/testicles = get_organ_slot(ORGAN_SLOT_TESTICLES)
- testicles.transfer_internal_fluid(null, testicles.internal_fluid_count * 0.6) // yep. we are sending semen to nullspace
- if(create_cum_decal)
- add_cum_splatter_floor(get_turf(src))
+ //SPLURT EDIT CHANGE BEGIN - Interactions
+ if(!(climax_interaction?.interaction_modifier_flags & INTERACTION_OVERRIDE_FLUID_TRANSFER))
+ if(create_cum_decal)
+ if(HAS_TRAIT(src, TRAIT_MESSY))
+ // Transfer reagents to the turf using liquids system
+ var/datum/reagents/R = new(testicles.internal_fluid_maximum)
+ testicles.transfer_internal_fluid(R, testicles.internal_fluid_count * 0.6)
+ if(partner && partner != src)
+ // Get turf between src and partner for directional splatter
+ var/turf/T = get_turf(partner)
+ T.add_liquid_from_reagents(R, FALSE, 1, get_turf(src), partner)
+ else
+ var/turf/T = get_turf(src)
+ T.add_liquid_from_reagents(R, FALSE, 1)
+ qdel(R)
+ else
+ testicles.transfer_internal_fluid(null, testicles.internal_fluid_count * 0.6)
+ add_cum_splatter_floor(get_turf(src))
+ else if(partner)
+ // Transfer reagents directly to partner
+ var/datum/reagents/R = new(testicles.internal_fluid_maximum)
+ testicles.transfer_internal_fluid(R, testicles.internal_fluid_count * 0.6)
+ R.trans_to(partner, R.total_volume)
+ qdel(R)
+ else
+ testicles.transfer_internal_fluid(null, testicles.internal_fluid_count * 0.6)
+ //SPLURT EDIT CHANGE END
try_lewd_autoemote("moan")
if(climax_choice == CLIMAX_PENIS)
@@ -144,22 +188,122 @@
if(climax_choice == CLIMAX_VAGINA || climax_choice == CLIMAX_BOTH)
var/obj/item/organ/external/genital/vagina/vagina = get_organ_slot(ORGAN_SLOT_VAGINA)
- if(is_bottomless() || vagina.visibility_preference == GENITAL_ALWAYS_SHOW)
- visible_message(span_userlove("[src] twitches and moans as [p_they()] climax from their vagina!"), span_userlove("You twitch and moan as you climax from your vagina!"))
- add_cum_splatter_floor(get_turf(src), female = TRUE)
- else
+ //SPLURT EDIT CHANGE BEGIN - Interactions
+ if(!is_bottomless() && vagina.visibility_preference != GENITAL_ALWAYS_SHOW)
visible_message(span_userlove("[src] cums in [self_their] underwear from [self_their] vagina!"), \
- span_userlove("You cum in your underwear from your vagina! Eww."))
+ span_userlove("You cum in your underwear from your vagina! Eww."))
self_orgasm = TRUE
+ else
+ var/list/interactable_inrange_humans = list()
+
+ for(var/mob/living/carbon/human/iterating_human in (view(1, src) - src))
+ interactable_inrange_humans[iterating_human.name] = iterating_human
+
+ var/list/buttons = list(CLIMAX_ON_FLOOR)
+ if(interactable_inrange_humans.len)
+ buttons += CLIMAX_IN_OR_ON
+
+ var/vagina_climax_choice = climax_interaction && !manual ? CLIMAX_IN_OR_ON : tgui_alert(src, "Choose where to squirt.", "Squirt preference!", buttons)
+
+ var/create_cum_decal = FALSE
+
+ if(!vagina_climax_choice || vagina_climax_choice == CLIMAX_ON_FLOOR)
+ create_cum_decal = TRUE
+ visible_message(span_userlove("[src] twitches and moans as [p_they()] squirt on the floor!"), \
+ span_userlove("You twitch and moan as you squirt on the floor!"))
+
+ else
+ var/target_choice = climax_interaction && !manual ? partner.name : tgui_input_list(src, "Choose who to squirt on.", "Choose target!", interactable_inrange_humans)
+ if(!target_choice)
+ create_cum_decal = TRUE
+ visible_message(span_userlove("[src] twitches and moans as [p_they()] squirt on the floor!"), \
+ span_userlove("You twitch and moan as you squirt on the floor!"))
+ else
+ var/mob/living/carbon/human/target_human = climax_interaction && !manual ? partner : interactable_inrange_humans[target_choice]
+ var/target_human_them = target_human.p_them()
+
+ var/list/target_buttons = list()
+
+ if(!target_human.wear_mask)
+ target_buttons += "mouth"
+ if(target_human.has_vagina(REQUIRE_GENITAL_EXPOSED))
+ target_buttons += ORGAN_SLOT_VAGINA
+ if(target_human.has_anus(REQUIRE_GENITAL_EXPOSED))
+ target_buttons += ORGAN_SLOT_ANUS
+ if(target_human.has_penis(REQUIRE_GENITAL_EXPOSED))
+ target_buttons += ORGAN_SLOT_PENIS
+ var/obj/item/organ/external/genital/penis/other_penis = target_human.get_organ_slot(ORGAN_SLOT_PENIS)
+ if(other_penis.sheath != "None")
+ target_buttons += "sheath"
+ target_buttons += "On [target_human_them]"
+
+ var/climax_into_choice
+ var/interaction_inside = partner?.get_organ_slot(climax_interaction?.cum_target[interaction_position]) || target_buttons.Find(climax_interaction?.cum_target[interaction_position])
+
+ if(climax_interaction && !manual && interaction_inside)
+ climax_into_choice = climax_interaction.cum_target[interaction_position]
+ else if(manual)
+ climax_into_choice = tgui_input_list(src, "Where on or in [target_human] do you wish to squirt?", "Final frontier!", target_buttons)
+ else
+ climax_into_choice = "On [target_human_them]"
+
+ if(climax_interaction && !manual && climax_interaction.show_climax(src, target_human, interaction_position))
+ create_cum_decal = !interaction_inside
+ else if(!climax_into_choice)
+ create_cum_decal = TRUE
+ visible_message(span_userlove("[src] squirts on the floor!"), \
+ span_userlove("You squirt on the floor!"))
+ else if(climax_into_choice == "On [target_human_them]")
+ create_cum_decal = TRUE
+ visible_message(span_userlove("[src] squirts all over [target_human]!"), \
+ span_userlove("You squirt all over [target_human]!"))
+ else
+ visible_message(span_userlove("[src] squirts into [target_human]'s [climax_into_choice]!"), \
+ span_userlove("You squirt into [target_human]'s [climax_into_choice]!"))
+ to_chat(target_human, span_userlove("Your [climax_into_choice] fills with [src]'s fluids."))
+
+ if(!(climax_interaction?.interaction_modifier_flags & INTERACTION_OVERRIDE_FLUID_TRANSFER))
+ if(create_cum_decal)
+ if(HAS_TRAIT(src, TRAIT_MESSY))
+ var/datum/reagents/R = new(vagina.internal_fluid_maximum)
+ vagina.transfer_internal_fluid(R, vagina.internal_fluid_count)
+ if(partner && partner != src)
+ var/turf/T = get_turf(partner)
+ T.add_liquid_from_reagents(R, FALSE, 1, get_turf(src), partner)
+ else
+ var/turf/T = get_turf(src)
+ T.add_liquid_from_reagents(R, FALSE, 1)
+ qdel(R)
+ else
+ vagina.transfer_internal_fluid(null, vagina.internal_fluid_count)
+ add_cum_splatter_floor(get_turf(src), female = TRUE)
+ else if(partner)
+ var/datum/reagents/R = new(vagina.internal_fluid_maximum)
+ vagina.transfer_internal_fluid(R, vagina.internal_fluid_count)
+ R.trans_to(partner, R.total_volume)
+ qdel(R)
+ else
+ vagina.transfer_internal_fluid(null, vagina.internal_fluid_count)
+ //SPLURT EDIT CHANGE END
apply_status_effect(/datum/status_effect/climax)
apply_status_effect(/datum/status_effect/climax_cooldown)
if(self_orgasm)
add_mood_event("orgasm", /datum/mood_event/climaxself)
+
+ // SPLURT EDIT ADDITION BEGIN - Interactions
+ if(climax_interaction && !manual)
+ climax_interaction.post_climax(src, partner, interaction_position)
+ //SPLURT EDIT ADDITION END
return TRUE
+//SPLURT EDIT REMOVAL BEGIN - Interactions - Moved climax defines to global defines
+/*
#undef CLIMAX_VAGINA
#undef CLIMAX_PENIS
#undef CLIMAX_BOTH
+*/
+//SPLURT EDIT REMOVAL END
+
#undef CLIMAX_ON_FLOOR
#undef CLIMAX_IN_OR_ON
diff --git a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/pain.dm b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/pain.dm
index e0d14bf01a21b..8cad6ca5087b7 100644
--- a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/pain.dm
+++ b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/pain.dm
@@ -5,7 +5,7 @@
/mob/living/proc/adjust_pain(change_amount = 0)
return
-/mob/living/carbon/human/adjust_pain(change_amount = 0)
+/mob/living/carbon/human/adjust_pain(change_amount = 0, mob/living/carbon/human/partner, datum/interaction/interaction, position)
if(stat >= DEAD || !client?.prefs?.read_preference(/datum/preference/toggle/erp))
return
@@ -23,6 +23,6 @@
else if(HAS_TRAIT(src, TRAIT_MASOCHISM))
if(change_amount > 0)
adjust_arousal(change_amount)
- adjust_pleasure(change_amount / 2)
+ adjust_pleasure(change_amount / 2, partner, interaction, position)
pain = clamp(pain + change_amount, AROUSAL_MINIMUM, AROUSAL_LIMIT)
diff --git a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/pleasure.dm b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/pleasure.dm
index bcda8503d219c..3d667dc29da0e 100644
--- a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/pleasure.dm
+++ b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_arousal/pleasure.dm
@@ -1,8 +1,8 @@
-/mob/living/carbon/human/proc/adjust_pleasure(pleas = 0)
+/mob/living/carbon/human/proc/adjust_pleasure(pleas = 0, mob/living/carbon/human/partner, datum/interaction/interaction, position)
if(stat >= DEAD || !client?.prefs?.read_preference(/datum/preference/toggle/erp))
return
- pleasure = clamp(pleasure + pleas, AROUSAL_MINIMUM, AROUSAL_LIMIT)
+ pleasure = clamp(pleasure + pleas, AROUSAL_MINIMUM, AROUSAL_LIMIT * (dna.features["lust_tolerance"] || 1)) // SPLURT EDIT - Lust tolerance
- if(pleasure >= AROUSAL_AUTO_CLIMAX_THRESHOLD) // lets cum
- climax(manual = FALSE)
+ if(pleasure >= AROUSAL_AUTO_CLIMAX_THRESHOLD * (dna.features["lust_tolerance"] || 1)) // lets cum // SPLURT EDIT - Lust tolerance
+ climax(manual = FALSE, partner = partner, climax_interaction = interaction, interaction_position = position)
diff --git a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/pentacamphor.dm b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/pentacamphor.dm
index 8816143570445..114c3c3f4f8ee 100644
--- a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/pentacamphor.dm
+++ b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_chemistry/reagents/pentacamphor.dm
@@ -25,7 +25,7 @@
to_chat(exposed_mob, span_notice("Your mind is free. Your thoughts are pure and innocent once more."))
REMOVE_TRAIT(exposed_mob, TRAIT_BIMBO, TRAIT_LEWDCHEM)
return
- if(!HAS_TRAIT(exposed_mob, TRAIT_NEVERBONER))
+ if(!HAS_TRAIT(exposed_mob, TRAIT_NEVERBONER) && !exposed_mob.client?.prefs?.read_preference(/datum/preference/toggle/erp/forced_neverboner)) // SPLURT EDIT - Added a preference to force neverboner
to_chat(exposed_mob, span_notice("You feel like you'll never feel aroused again..."))
ADD_TRAIT(exposed_mob, TRAIT_NEVERBONER, TRAIT_LEWDCHEM)
diff --git a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_helpers/human.dm b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_helpers/human.dm
index 86b0ca58ab70e..6388a81de2ccf 100644
--- a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_helpers/human.dm
+++ b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_helpers/human.dm
@@ -36,6 +36,7 @@
/mob/living/carbon/human/proc/is_head_uncovered()
return (head?.body_parts_covered & HEAD)
+/* SPLURT EDIT REMOVAL - Interactions - Refractored in modular
/// Returns true if the human has an accessible penis for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
/mob/living/carbon/human/proc/has_penis(required_state = REQUIRE_GENITAL_ANY)
var/obj/item/organ/external/genital/genital = get_organ_slot(ORGAN_SLOT_PENIS)
@@ -117,6 +118,7 @@
return genital.visibility_preference != GENITAL_ALWAYS_SHOW && !is_bottomless()
else
return TRUE
+*/ //SPLURT EDIT END
/// Returns true if the human has a accessible feet for the parameter, returning the number of feet the human has if they do. Accepts any of the `REQUIRE_GENITAL_` defines.
/mob/living/carbon/human/proc/has_arms(required_state = REQUIRE_GENITAL_ANY)
diff --git a/modular_zubbers/code/modules/client/verbs/character_directory.dm b/modular_zubbers/code/modules/client/verbs/character_directory.dm
index 9e90850dae17d..ffd2d32b1e8d7 100644
--- a/modular_zubbers/code/modules/client/verbs/character_directory.dm
+++ b/modular_zubbers/code/modules/client/verbs/character_directory.dm
@@ -192,6 +192,11 @@ GLOBAL_DATUM(character_directory, /datum/character_directory)
var/vore = "Ask"
var/hypno = "Ask"
var/noncon = "Ask"
+ // SPLURT EDIT START: INTERACTION PANEL
+ var/extreme = "Ask"
+ var/extremeharm = "Ask"
+ var/unholy = "Ask"
+ // SPLURT EDIT END: INTERACTION PANEL
var/character_ad = ""
var/exploitable = ""
var/ref = REF(mob)
@@ -235,6 +240,11 @@ GLOBAL_DATUM(character_directory, /datum/character_directory)
noncon = READ_PREFS(mob, choiced/erp_status_nc)
character_ad = READ_PREFS(mob, text/character_ad)
ooc_notes = READ_PREFS(mob, text/ooc_notes)
+ // SPLURT EDIT START: INTERACTION PANEL
+ extreme = READ_PREFS(mob, choiced/erp_status_extm)
+ extremeharm = READ_PREFS(mob, choiced/erp_status_extmharm)
+ unholy = READ_PREFS(mob, choiced/erp_status_unholy)
+ // SPLURT EDIT END: INTERACTION PANEL
//If the user is an antagonist or Observer, we want them to be able to see exploitables in the Directory.
if(user.mind?.has_antag_datum(/datum/antagonist) || isobserver(user))
if(exploitable == EXPLOITABLE_DEFAULT_TEXT)
@@ -254,6 +264,11 @@ GLOBAL_DATUM(character_directory, /datum/character_directory)
"vore" = vore,
"hypno" = hypno,
"noncon" = noncon,
+ // SPLURT EDIT START: INTERACTION PANEL
+ "extreme" = extreme,
+ "extremeharm" = extremeharm,
+ "unholy" = unholy,
+ // SPLURT EDIT END: INTERACTION PANEL
"exploitable" = exploitable,
"character_ad" = character_ad,
"flavor_text" = flavor_text,
diff --git a/modular_zzplurt/code/controllers/subsystem/interactions.dm b/modular_zzplurt/code/controllers/subsystem/interactions.dm
new file mode 100644
index 0000000000000..5904a1ae7fed5
--- /dev/null
+++ b/modular_zzplurt/code/controllers/subsystem/interactions.dm
@@ -0,0 +1,52 @@
+SUBSYSTEM_DEF(interactions)
+ name = "Interactions"
+ flags = SS_NO_FIRE
+ init_order = INIT_ORDER_INTERACTIONS
+ var/list/datum/interaction/interactions
+ VAR_PROTECTED/list/blacklisted_mobs = list(
+ /mob/dead,
+ /mob/dview,
+ /mob/camera,
+ /mob/living/basic/pet,
+ /mob/living/basic/cockroach,
+ /mob/living/basic/butterfly,
+ /mob/living/basic/chick,
+ /mob/living/basic/chicken,
+ /mob/living/basic/cow,
+ /mob/living/basic/crab,
+ /mob/living/basic/kiwi,
+ /mob/living/basic/parrot,
+ /mob/living/basic/sloth,
+ /mob/living/basic/goat
+ )
+ VAR_PROTECTED/initialized_blacklist
+
+/datum/controller/subsystem/interactions/Initialize()
+ prepare_interactions()
+ prepare_blacklisted_mobs()
+ . = ..()
+ var/extra_info = "↳ Loaded [LAZYLEN(interactions)] interactions!"
+ to_chat(world, span_boldannounce(extra_info))
+ log_config(extra_info)
+
+/datum/controller/subsystem/interactions/stat_entry(msg)
+ msg += "|🖐:[LAZYLEN(interactions)]|"
+ msg += "🚫👨:[LAZYLEN(blacklisted_mobs)]"
+ return ..()
+
+/datum/controller/subsystem/interactions/proc/prepare_interactions()
+ QDEL_LIST_ASSOC_VAL(interactions)
+ QDEL_NULL(interactions)
+ interactions = list()
+ populate_interaction_instances()
+
+/datum/controller/subsystem/interactions/proc/prepare_blacklisted_mobs()
+ blacklisted_mobs = typecacheof(blacklisted_mobs)
+ initialized_blacklist = TRUE
+
+/datum/controller/subsystem/interactions/proc/is_blacklisted(mob/living/creature)
+ if(!creature || !initialized_blacklist)
+ return TRUE
+ if(is_type_in_typecache(creature, blacklisted_mobs))
+ return TRUE
+ return FALSE
diff --git a/modular_zzplurt/code/datums/quirks/neutral_quirks/messy.dm b/modular_zzplurt/code/datums/quirks/neutral_quirks/messy.dm
index dcd68645b844b..c34c20da269d6 100644
--- a/modular_zzplurt/code/datums/quirks/neutral_quirks/messy.dm
+++ b/modular_zzplurt/code/datums/quirks/neutral_quirks/messy.dm
@@ -11,4 +11,3 @@
mail_goodies = list (
/obj/item/mop = 1 // Clean this mess up!
)
- hidden_quirk = TRUE
diff --git a/modular_zzplurt/code/modules/client/preferences/erp_preferences.dm b/modular_zzplurt/code/modules/client/preferences/erp_preferences.dm
new file mode 100644
index 0000000000000..cf20deef15c49
--- /dev/null
+++ b/modular_zzplurt/code/modules/client/preferences/erp_preferences.dm
@@ -0,0 +1,152 @@
+/datum/preference/choiced/erp_status_extm
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "erp_status_pref_extm"
+
+/datum/preference/choiced/erp_status_extm/init_possible_values()
+ return list("Yes - Switch", "Yes - Dom", "Yes - Sub", "Yes", "Ask (L)OOC", "Check OOC Notes", "No")
+
+/datum/preference/choiced/erp_status_extm/create_default_value()
+ return "No"
+
+/datum/preference/choiced/erp_status_extm/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ if(CONFIG_GET(flag/disable_erp_preferences))
+ return FALSE
+
+ return preferences.read_preference(/datum/preference/toggle/master_erp_preferences)
+
+/datum/preference/choiced/erp_status_extm/deserialize(input, datum/preferences/preferences)
+ if(CONFIG_GET(flag/disable_erp_preferences))
+ return "No"
+ if(!preferences.read_preference(/datum/preference/toggle/master_erp_preferences))
+ return "No"
+ . = ..()
+
+/datum/preference/choiced/erp_status_extm/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
+ return FALSE
+
+/datum/preference/choiced/erp_status_extmharm
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "erp_status_pref_extmharm"
+
+/datum/preference/choiced/erp_status_extmharm/init_possible_values()
+ return list("Yes - Switch", "Yes - Dom", "Yes - Sub", "Yes", "Ask (L)OOC", "Check OOC Notes", "No")
+
+/datum/preference/choiced/erp_status_extmharm/create_default_value()
+ return "No"
+
+/datum/preference/choiced/erp_status_extmharm/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ if(CONFIG_GET(flag/disable_erp_preferences))
+ return FALSE
+
+ return preferences.read_preference(/datum/preference/toggle/master_erp_preferences) && preferences.read_preference(/datum/preference/choiced/erp_status_extm) != "No"
+
+/datum/preference/choiced/erp_status_extmharm/deserialize(input, datum/preferences/preferences)
+ if(CONFIG_GET(flag/disable_erp_preferences))
+ return "No"
+ if(!preferences.read_preference(/datum/preference/toggle/master_erp_preferences) || !(preferences.read_preference(/datum/preference/choiced/erp_status_extm) != "No"))
+ return "No"
+ . = ..()
+
+/datum/preference/choiced/erp_status_extmharm/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
+ return FALSE
+
+/datum/preference/choiced/erp_status_unholy
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "erp_status_pref_unholy"
+
+/datum/preference/choiced/erp_status_unholy/init_possible_values()
+ return list("Yes - Switch", "Yes - Dom", "Yes - Sub", "Yes", "Ask (L)OOC", "Check OOC Notes", "No")
+
+/datum/preference/choiced/erp_status_unholy/create_default_value()
+ return "No"
+
+/datum/preference/choiced/erp_status_unholy/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ if(CONFIG_GET(flag/disable_erp_preferences))
+ return FALSE
+
+ return preferences.read_preference(/datum/preference/toggle/master_erp_preferences)
+
+/datum/preference/choiced/erp_status_unholy/deserialize(input, datum/preferences/preferences)
+ if(CONFIG_GET(flag/disable_erp_preferences))
+ return "No"
+ if(!preferences.read_preference(/datum/preference/toggle/master_erp_preferences))
+ return "No"
+ . = ..()
+
+/datum/preference/choiced/erp_status_unholy/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
+ return FALSE
+
+/datum/preference/numeric/erp_lust_tolerance
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+ savefile_key = "erp_lust_tolerance_pref"
+ savefile_identifier = PREFERENCE_CHARACTER
+
+ minimum = 0.5
+ maximum = 2.0
+ step = 0.01
+
+/datum/preference/numeric/erp_lust_tolerance/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["lust_tolerance"] = value
+
+/datum/preference/numeric/erp_lust_tolerance/create_informed_default_value(datum/preferences/preferences)
+ return 1.0
+
+/datum/preference/numeric/erp_lust_tolerance/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ if(CONFIG_GET(flag/disable_erp_preferences))
+ return FALSE
+
+ return preferences.read_preference(/datum/preference/toggle/master_erp_preferences)
+
+/datum/preference/numeric/erp_sexual_potency
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+ savefile_key = "erp_sexual_potency_pref"
+ savefile_identifier = PREFERENCE_CHARACTER
+
+ minimum = 0.5
+ maximum = 2.0
+ step = 0.01
+
+/datum/preference/numeric/erp_sexual_potency/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["sexual_potency"] = value
+
+/datum/preference/numeric/erp_sexual_potency/create_informed_default_value(datum/preferences/preferences)
+ return 1.0
+
+/datum/preference/numeric/erp_sexual_potency/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ if(CONFIG_GET(flag/disable_erp_preferences))
+ return FALSE
+
+ return preferences.read_preference(/datum/preference/toggle/master_erp_preferences)
+
+/datum/preference/toggle/erp/butt_enlargement
+ savefile_key = "butt_enlargement_pref"
+
+/datum/preference/toggle/erp/butt_shrinkage
+ savefile_key = "butt_shrinkage_pref"
+
+/datum/preference/toggle/erp/belly_enlargement
+ savefile_key = "belly_enlargement_pref"
+
+/datum/preference/toggle/erp/belly_shrinkage
+ savefile_key = "belly_shrinkage_pref"
+
+/datum/preference/toggle/erp/forced_neverboner
+ savefile_key = "forced_neverboner_pref"
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interaction_datum.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interaction_datum.dm
new file mode 100644
index 0000000000000..31f446d60a06f
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interaction_datum.dm
@@ -0,0 +1,260 @@
+#define isclownjob(A) (is_species(A, /datum/species/human) && A?.mind?.assigned_role?.title == "Clown")
+
+/datum/interaction
+ /// Which genital the interaction cums with (must be penis, vagina or both)
+ var/list/cum_genital = list(CLIMAX_POSITION_USER = null, CLIMAX_POSITION_TARGET = null)
+ /// Where on the partner the interaction cums in? (It must match a genital's ORGAN_SLOT, mouth or sheath)
+ var/list/cum_target = list(CLIMAX_POSITION_USER = null, CLIMAX_POSITION_TARGET = null)
+ /// override of the text to display when the interaction cums (use this if you're not using a cum_target)
+ var/list/cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(), CLIMAX_POSITION_TARGET = list())
+ /// override of the self message to display when the interaction cums (use this if you're not using a cum_target)
+ var/list/cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(), CLIMAX_POSITION_TARGET = list())
+ /// override of the text to display to the partner when the interaction cums (use this if you're not using a cum_target)
+ var/list/cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(), CLIMAX_POSITION_TARGET = list())
+ /// Is the interaction considered extreme/harmful/unholy?
+ var/unsafe_types = NONE
+ /// Additional details to display in the interaction menu and used in some interaction logic
+ var/list/additional_details = list()
+ /// Interaction modifier flags, edits how the interaction works from its original definition
+ var/interaction_modifier_flags = NONE
+
+ /// List of clown-specific messages for different genital types
+ var/static/list/clown_genital_messages = list(
+ ORGAN_SLOT_VAGINA = list(
+ "\The %OWNER%'s clussy honks[pick(" loudly", "")]!",
+ "\The %OWNER%'s pussy squeaks[pick(" loudly", "")]!"
+ ),
+ ORGAN_SLOT_PENIS = list(
+ "\The %OWNER%'s cock honks[pick(" loudly", "")]!",
+ "\The %OWNER%'s penis squeaks[pick(" loudly", "")]!"
+ ),
+ ORGAN_SLOT_ANUS = list(
+ "\The %OWNER%'s fun hole honks[pick(" loudly", "")]!",
+ "\The %OWNER%'s ass squeaks[pick(" loudly", "")]!"
+ ),
+ ORGAN_SLOT_BREASTS = list(
+ "\The %OWNER%'s honkers produce a loud squeak!",
+ "\The %OWNER%'s breasts honk[pick(" loudly", "")]!"
+ ),
+ ORGAN_SLOT_MOUTH = list(
+ "\The %OWNER%'s mouth honks[pick(" loudly", "")]!",
+ "\The %OWNER%'s throat squeaks[pick(" loudly", "")]!"
+ )
+ )
+
+/datum/interaction/New()
+ cum_message_text_overrides[CLIMAX_POSITION_USER] = sanitize_islist(cum_message_text_overrides[CLIMAX_POSITION_USER], list())
+ cum_self_text_overrides[CLIMAX_POSITION_USER] = sanitize_islist(cum_self_text_overrides[CLIMAX_POSITION_USER], list())
+ cum_partner_text_overrides[CLIMAX_POSITION_USER] = sanitize_islist(cum_partner_text_overrides[CLIMAX_POSITION_USER], list())
+ cum_message_text_overrides[CLIMAX_POSITION_TARGET] = sanitize_islist(cum_message_text_overrides[CLIMAX_POSITION_TARGET], list())
+ cum_self_text_overrides[CLIMAX_POSITION_TARGET] = sanitize_islist(cum_self_text_overrides[CLIMAX_POSITION_TARGET], list())
+ cum_partner_text_overrides[CLIMAX_POSITION_TARGET] = sanitize_islist(cum_partner_text_overrides[CLIMAX_POSITION_TARGET], list())
+ . = ..()
+
+/datum/interaction/load_from_json(path)
+ . = ..()
+ if(!.)
+ return FALSE
+
+ var/list/unsafe_flags = list(
+ "extreme" = INTERACTION_EXTREME,
+ "extremeharm" = INTERACTION_EXTREME | INTERACTION_HARMFUL,
+ "unholy" = INTERACTION_UNHOLY,
+ )
+
+ var/list/json = json_decode(file2text(path))
+ cum_genital[CLIMAX_POSITION_USER] = sanitize_text(json["cum_genital_user"])
+ cum_genital[CLIMAX_POSITION_TARGET] = sanitize_text(json["cum_genital_target"])
+ cum_target[CLIMAX_POSITION_USER] = sanitize_text(json["cum_target_user"])
+ cum_target[CLIMAX_POSITION_TARGET] = sanitize_text(json["cum_target_target"])
+ cum_message_text_overrides[CLIMAX_POSITION_USER] = sanitize_islist(json["cum_message_text_overrides_user"], list())
+ cum_message_text_overrides[CLIMAX_POSITION_TARGET] = sanitize_islist(json["cum_message_text_overrides_target"], list())
+ cum_self_text_overrides[CLIMAX_POSITION_USER] = sanitize_islist(json["cum_self_text_overrides_user"], list())
+ cum_self_text_overrides[CLIMAX_POSITION_TARGET] = sanitize_islist(json["cum_self_text_overrides_target"], list())
+ cum_partner_text_overrides[CLIMAX_POSITION_USER] = sanitize_islist(json["cum_partner_text_overrides_user"], list())
+ cum_partner_text_overrides[CLIMAX_POSITION_TARGET] = sanitize_islist(json["cum_partner_text_overrides_target"], list())
+
+ var/list/unsafe_list = sanitize_islist(json["unsafe_types"], list())
+ for(var/unsafe_type in unsafe_list)
+ unsafe_types |= unsafe_flags[unsafe_type]
+
+/datum/interaction/json_save(path)
+ . = ..()
+ if(!.)
+ return FALSE
+
+ var/file = file(path)
+ var/list/json = json_decode(file2text(path))
+
+ json["cum_genital_user"] = cum_genital[CLIMAX_POSITION_USER]
+ json["cum_genital_target"] = cum_genital[CLIMAX_POSITION_TARGET]
+ json["cum_target_user"] = cum_target[CLIMAX_POSITION_USER]
+ json["cum_target_target"] = cum_target[CLIMAX_POSITION_TARGET]
+ json["cum_message_text_overrides_user"] = cum_message_text_overrides[CLIMAX_POSITION_USER]
+ json["cum_message_text_overrides_target"] = cum_message_text_overrides[CLIMAX_POSITION_TARGET]
+ json["cum_self_text_overrides_user"] = cum_self_text_overrides[CLIMAX_POSITION_USER]
+ json["cum_self_text_overrides_target"] = cum_self_text_overrides[CLIMAX_POSITION_TARGET]
+ json["cum_partner_text_overrides_user"] = cum_partner_text_overrides[CLIMAX_POSITION_USER]
+ json["cum_partner_text_overrides_target"] = cum_partner_text_overrides[CLIMAX_POSITION_TARGET]
+
+ var/list/unsafe_flags = list()
+ if(unsafe_types & INTERACTION_EXTREME)
+ unsafe_flags += "extreme"
+ if(unsafe_types & INTERACTION_HARMFUL)
+ unsafe_flags += "extremeharm"
+ if(unsafe_types & INTERACTION_UNHOLY)
+ unsafe_flags += "unholy"
+ json["unsafe_types"] = unsafe_flags
+
+ WRITE_FILE(file, json_encode(json))
+ return TRUE
+
+
+/datum/interaction/proc/allow_act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ if(target == user && usage == INTERACTION_OTHER)
+ return FALSE
+
+ if(unsafe_types & INTERACTION_EXTREME)
+ if(!(user.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_extm) != "No") || !(target.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_extm) != "No"))
+ return FALSE
+ if(unsafe_types & INTERACTION_HARMFUL)
+ if(!(user.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_extmharm) != "No") || !(target.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_extmharm) != "No"))
+ return FALSE
+ if(unsafe_types & INTERACTION_UNHOLY)
+ if(!(user.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_unholy) != "No") || !(target.client?.prefs?.read_preference(/datum/preference/choiced/erp_status_unholy) != "No"))
+ return FALSE
+
+ if(user_required_parts.len)
+ for(var/slot in user_required_parts)
+ if(!user.has_genital(LAZYACCESS(user_required_parts, slot) || REQUIRE_GENITAL_EXPOSED, slot))
+ return FALSE
+
+ if(target_required_parts.len)
+ for(var/slot in target_required_parts)
+ if(!target.has_genital(LAZYACCESS(target_required_parts, slot) || REQUIRE_GENITAL_EXPOSED, slot))
+ return FALSE
+
+ for(var/requirement in interaction_requires)
+ switch(requirement)
+ if(INTERACTION_REQUIRE_SELF_HAND)
+ if(!user.get_active_hand())
+ return FALSE
+ if(INTERACTION_REQUIRE_TARGET_HAND)
+ if(!target.get_active_hand())
+ return FALSE
+ if(INTERACTION_REQUIRE_SELF_MOUTH)
+ if(!user.get_bodypart(BODY_ZONE_PRECISE_MOUTH) || user.is_mouth_covered())
+ return FALSE
+ if(INTERACTION_REQUIRE_TARGET_MOUTH)
+ if(!target.get_bodypart(BODY_ZONE_PRECISE_MOUTH) || target.is_mouth_covered())
+ return FALSE
+ if(INTERACTION_REQUIRE_SELF_TOPLESS)
+ if(!user.is_topless())
+ return FALSE
+ if(INTERACTION_REQUIRE_TARGET_TOPLESS)
+ if(!target.is_topless())
+ return FALSE
+ if(INTERACTION_REQUIRE_SELF_BOTTOMLESS)
+ if(!user.is_bottomless())
+ return FALSE
+ if(INTERACTION_REQUIRE_TARGET_BOTTOMLESS)
+ if(!target.is_bottomless())
+ return FALSE
+ if(INTERACTION_REQUIRE_SELF_FEET)
+ if(!(user.has_feet() >= (LAZYACCESS(user_required_parts, INTERACTION_REQUIRE_SELF_FEET) || 2))) //We prolly don't need to care if it's exposed or not
+ return FALSE
+ if(INTERACTION_REQUIRE_TARGET_FEET)
+ if(!(target.has_feet() >= (LAZYACCESS(target_required_parts, INTERACTION_REQUIRE_TARGET_FEET) || 2)))
+ return FALSE
+ else
+ CRASH("Unimplemented interaction requirement '[requirement]'")
+ return TRUE
+
+/// Called when the interaction is performed
+/datum/interaction/proc/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ handle_clown_interaction(user, target)
+ return
+
+/// Handles clown-specific interaction effects
+/datum/interaction/proc/handle_clown_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ if(!prob(50)) // 50% chance for honk effects
+ return
+
+ var/is_user_clown = isclownjob(user)
+ var/is_target_clown = target ? isclownjob(target) : FALSE
+ if(!is_user_clown && !is_target_clown)
+ return
+
+ // Handle user's genitals if they're a clown
+ if(is_user_clown)
+ for(var/genital_slot in user_required_parts)
+ if(clown_genital_messages[genital_slot])
+ var/message = pick(clown_genital_messages[genital_slot])
+ message = replacetext(message, "%OWNER%", user)
+ user.visible_message(span_lewd(message))
+ playsound(user, 'sound/items/bikehorn.ogg', 40, TRUE, -1)
+
+ // Handle target's genitals if they're a clown
+ if(is_target_clown && target)
+ for(var/genital_slot in target_required_parts)
+ if(clown_genital_messages[genital_slot])
+ var/message = pick(clown_genital_messages[genital_slot])
+ message = replacetext(message, "%OWNER%", target)
+ target.visible_message(span_lewd(message))
+ playsound(target, 'sound/items/bikehorn.ogg', 40, TRUE, -1)
+
+// Called when either the user or target is cumming from the interaction, makes the interaction text
+/datum/interaction/proc/show_climax(mob/living/carbon/human/cumming, mob/living/carbon/human/came_in, position)
+ var/override_check = length(cum_message_text_overrides[position]) && length(cum_self_text_overrides[position]) && (length(cum_partner_text_overrides[position]) || usage == INTERACTION_SELF)
+ if(!override_check)
+ return FALSE
+
+ var/cumming_their = cumming.p_their()
+ var/cumming_them = cumming.p_them()
+ var/came_in_them = came_in.p_them()
+ var/came_in_their = came_in.p_their()
+ var/genital_used = cum_genital[position]
+ var/hole_used = cum_target[position]
+
+ if(override_check)
+ var/message = pick(cum_message_text_overrides[position])
+ message = replacetext(message, "%CUMMING%", "[cumming]")
+ message = replacetext(message, "%CUMMING_THEIR%", "[cumming_their]")
+ message = replacetext(message, "%CUMMING_THEM%", "[cumming_them]")
+ message = replacetext(message, "%CAME_IN%", "[came_in]")
+ message = replacetext(message, "%CAME_IN_THEIR%", "[came_in_their]")
+ message = replacetext(message, "%CAME_IN_THEM%", "[came_in_them]")
+ message = replacetext(message, "%CUM_GENITAL%", "[genital_used]")
+ message = replacetext(message, "%CUM_TARGET%", "[hole_used]")
+
+ var/self_message = pick(cum_self_text_overrides[position])
+ self_message = replacetext(self_message, "%CUMMING%", "[cumming]")
+ self_message = replacetext(self_message, "%CUMMING_THEIR%", "[cumming_their]")
+ self_message = replacetext(self_message, "%CUMMING_THEM%", "[cumming_them]")
+ self_message = replacetext(self_message, "%CAME_IN%", "[came_in]")
+ self_message = replacetext(self_message, "%CAME_IN_THEIR%", "[came_in_their]")
+ self_message = replacetext(self_message, "%CAME_IN_THEM%", "[came_in_them]")
+ self_message = replacetext(self_message, "%CUM_GENITAL%", "[genital_used]")
+ self_message = replacetext(self_message, "%CUM_TARGET%", "[hole_used]")
+
+ cumming.visible_message(span_userlove(message), span_userlove(self_message))
+
+ if(usage == INTERACTION_OTHER)
+ var/partner_message = pick(cum_partner_text_overrides[position])
+ partner_message = replacetext(partner_message, "%CUMMING%", "[cumming]")
+ partner_message = replacetext(partner_message, "%CUMMING_THEIR%", "[cumming_their]")
+ partner_message = replacetext(partner_message, "%CUMMING_THEM%", "[cumming_them]")
+ partner_message = replacetext(partner_message, "%CAME_IN%", "[came_in]")
+ partner_message = replacetext(partner_message, "%CAME_IN_THEIR%", "[came_in_their]")
+ partner_message = replacetext(partner_message, "%CAME_IN_THEM%", "[came_in_them]")
+ partner_message = replacetext(partner_message, "%CUM_GENITAL%", "[genital_used]")
+ partner_message = replacetext(partner_message, "%CUM_TARGET%", "[hole_used]")
+
+ to_chat(came_in, span_userlove(partner_message))
+ return TRUE
+
+/// Called after either the user or target cums from the interaction
+/datum/interaction/proc/post_climax(mob/living/carbon/human/cumming, mob/living/carbon/human/came_in, position)
+ return
+
+#undef isclownjob
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/_lewd.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/_lewd.dm
new file mode 100644
index 0000000000000..68a5e6cc52786
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/_lewd.dm
@@ -0,0 +1,4 @@
+/datum/interaction/lewd
+ lewd = TRUE
+ color = "pink"
+ category = INTERACTION_CAT_LEWD
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/armpit.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/armpit.dm
new file mode 100644
index 0000000000000..ee33860a7acee
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/armpit.dm
@@ -0,0 +1,162 @@
+/datum/interaction/lewd/armpit_fuck
+ name = "Armpit Fuck"
+ description = "Fuck their armpit."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_TOPLESS)
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums all over %TARGET%'s armpit",
+ "shoots their load into %TARGET%'s pit",
+ "covers %TARGET%'s underarm in cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum all over %TARGET%'s armpit",
+ "You shoot your load into %TARGET%'s pit",
+ "You cover %TARGET%'s underarm in cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "%USER% cums all over your armpit",
+ "%USER% shoots their load into your pit",
+ "%USER% covers your underarm in cum"
+ ))
+ message = list(
+ "fucks %TARGET%'s armpit",
+ "slides their cock into %TARGET%'s underarm",
+ "thrusts into %TARGET%'s pit",
+ "pounds %TARGET%'s armpit"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s warm pit around your cock",
+ "The softness of %TARGET%'s armpit feels good against your shaft",
+ "%TARGET%'s underarm squeezes your cock nicely"
+ )
+ target_messages = list(
+ "You feel %USER%'s cock rubbing in your armpit",
+ "%USER%'s shaft slides against your underarm",
+ "The warmth of %USER%'s cock presses into your pit"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 3
+ target_pleasure = 0
+ user_arousal = 5
+ target_arousal = 2
+
+/datum/interaction/lewd/armpit_lick
+ name = "Lick Armpit"
+ description = "Lick their armpit."
+ interaction_requires = list(
+ INTERACTION_REQUIRE_SELF_MOUTH,
+ INTERACTION_REQUIRE_TARGET_TOPLESS
+ )
+ message = list(
+ "licks %TARGET%'s armpit",
+ "runs their tongue along %TARGET%'s underarm",
+ "tastes %TARGET%'s pit",
+ "plants their face in %TARGET%'s armpit"
+ )
+ user_messages = list(
+ "You taste %TARGET%'s armpit",
+ "The scent of %TARGET%'s pit fills your nose",
+ "You savor the taste of %TARGET%'s underarm"
+ )
+ target_messages = list(
+ "You feel %USER%'s tongue in your armpit",
+ "%USER%'s wet tongue slides across your pit",
+ "The warmth of %USER%'s mouth tingles your underarm"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ_fingering.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
+
+/datum/interaction/lewd/armpit_smother
+ name = "Armpit Smother"
+ description = "Press your armpit against their face."
+ interaction_requires = list(
+ INTERACTION_REQUIRE_TARGET_MOUTH,
+ INTERACTION_REQUIRE_SELF_TOPLESS
+ )
+ message = list(
+ "presses their armpit against %TARGET%'s face",
+ "smothers %TARGET%'s face with their pit",
+ "forces %TARGET%'s face into their underarm",
+ "pins %TARGET%'s head under their arm"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s face pressed into your pit",
+ "You hold %TARGET%'s head against your underarm",
+ "You keep %TARGET%'s face buried in your armpit"
+ )
+ target_messages = list(
+ "Your face is pressed into %USER%'s armpit",
+ "%USER%'s underarm smothers your face",
+ "Your nose fills with the scent of %USER%'s pit"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
+
+/datum/interaction/lewd/armpit_pitjob
+ name = "Give Pitjob"
+ description = "Jerk them off with your armpit."
+ interaction_requires = list(
+ INTERACTION_REQUIRE_SELF_TOPLESS
+ )
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "cums all over %USER%'s armpit",
+ "shoots their load into %USER%'s pit",
+ "covers %USER%'s underarm in cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "%TARGET% cums all over your armpit",
+ "%TARGET% shoots their load into your pit",
+ "%TARGET% covers your underarm in cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "You cum all over %USER%'s armpit",
+ "You shoot your load into %USER%'s pit",
+ "You cover %USER%'s underarm in cum"
+ ))
+ message = list(
+ "works %TARGET%'s cock with their armpit",
+ "squeezes %TARGET%'s shaft between their arm and chest",
+ "jerks %TARGET% off with their pit",
+ "pleasures %TARGET%'s cock with their underarm"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s cock throb in your armpit",
+ "The warmth of %TARGET%'s shaft fills your pit",
+ "You squeeze %TARGET%'s cock with your underarm"
+ )
+ target_messages = list(
+ "%USER%'s warm pit strokes your cock",
+ "Your shaft slides between %USER%'s arm and chest",
+ "The softness of %USER%'s armpit feels amazing"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 3
+ user_arousal = 2
+ target_arousal = 5
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/belly.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/belly.dm
new file mode 100644
index 0000000000000..8ba48f644d0b9
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/belly.dm
@@ -0,0 +1,117 @@
+/datum/interaction/lewd/bellyfuck
+ name = "Bellyfuck"
+ description = "Fuck their belly."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_TOPLESS)
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums all over %TARGET%'s belly",
+ "shoots their load onto %TARGET%'s stomach",
+ "covers %TARGET%'s navel in cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum all over %TARGET%'s belly",
+ "You shoot your load onto %TARGET%'s stomach",
+ "You cover %TARGET%'s navel in cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "%USER% cums all over your belly",
+ "%USER% shoots their load onto your stomach",
+ "%USER% covers your navel in cum"
+ ))
+ message = list(
+ "rubs their cock against %TARGET%'s belly",
+ "fucks %TARGET%'s navel",
+ "grinds their cock on %TARGET%'s stomach",
+ "thrusts against %TARGET%'s belly"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s warm skin against your cock",
+ "The softness of %TARGET%'s belly feels good against your shaft",
+ "%TARGET%'s belly feels amazing against your cock"
+ )
+ target_messages = list(
+ "You feel %USER%'s cock rubbing against your belly",
+ "%USER%'s shaft slides across your stomach",
+ "The warmth of %USER%'s cock presses against your navel"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 3
+ target_pleasure = 0
+ user_arousal = 5
+ target_arousal = 2
+
+/datum/interaction/lewd/nuzzle_belly
+ name = "Nuzzle Belly"
+ description = "Nuzzle their belly."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_TOPLESS)
+ message = list(
+ "nuzzles %TARGET%'s belly",
+ "rubs their face against %TARGET%'s stomach",
+ "presses their cheek to %TARGET%'s navel",
+ "snuggles against %TARGET%'s tummy"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s warm skin against your face",
+ "The softness of %TARGET%'s belly feels nice against your cheek",
+ "%TARGET%'s stomach is warm and inviting"
+ )
+ target_messages = list(
+ "You feel %USER%'s face nuzzling your belly",
+ "%USER%'s cheek rubs softly against your stomach",
+ "The warmth of %USER%'s face presses against your navel"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 2
+ target_arousal = 3
+
+/datum/interaction/lewd/deflate_belly
+ name = "Deflate Belly"
+ description = "Deflate belly."
+ user_required_parts = list(ORGAN_SLOT_BELLY = REQUIRE_GENITAL_ANY)
+ usage = INTERACTION_SELF
+ message = list(
+ "deflates their belly",
+ "lets out air from their belly",
+ "makes their belly smaller"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ user_arousal = 0
+
+/datum/interaction/lewd/deflate_belly/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ var/obj/item/organ/external/genital/belly/gut = user.get_organ_slot(ORGAN_SLOT_BELLY)
+ if(gut)
+ gut.set_size(gut.genital_size - 1)
+
+/datum/interaction/lewd/inflate_belly
+ name = "Inflate Belly"
+ description = "Inflate belly."
+ user_required_parts = list(ORGAN_SLOT_BELLY = REQUIRE_GENITAL_ANY)
+ usage = INTERACTION_SELF
+ message = list(
+ "inflates their belly",
+ "makes their belly bigger",
+ "expands their belly"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ user_arousal = 0
+
+/datum/interaction/lewd/inflate_belly/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ var/obj/item/organ/external/genital/belly/gut = user.get_organ_slot(ORGAN_SLOT_BELLY)
+ if(gut)
+ gut.set_size(gut.genital_size + 1)
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/breasts.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/breasts.dm
new file mode 100644
index 0000000000000..da54fc38ed2ee
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/breasts.dm
@@ -0,0 +1,266 @@
+/datum/interaction/lewd/breastfeed
+ name = "Breastfeed"
+ description = "Breastfeed them."
+ user_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_EXPOSED)
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ message = list(
+ "pushes their breasts against %TARGET%'s mouth, squirting their warm %MILK% into their mouth.",
+ "fills %TARGET%'s mouth with warm, sweet %MILK% as they squeeze their boobs, panting.",
+ "lets a large stream of their own abundant %MILK% coat the back of %TARGET%'s throat."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/oral1.ogg',
+ 'modular_zzplurt/sound/interactions/oral2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 2
+ user_arousal = 3
+ target_pleasure = 0
+ target_arousal = 2
+
+/datum/interaction/lewd/breastfeed/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/obj/item/organ/external/genital/breasts/breasts = user.get_organ_slot(ORGAN_SLOT_BREASTS)
+ if(!breasts?.internal_fluid_datum)
+ return
+
+ var/datum/reagent/milk = find_reagent_object_from_type(breasts.internal_fluid_datum)
+ var/list/original_messages = message.Copy()
+ var/chosen_message = pick(message)
+ chosen_message = replacetext(chosen_message, "%MILK%", lowertext(milk.name))
+ message = list(chosen_message)
+ . = ..()
+ message = original_messages
+
+/datum/interaction/lewd/breastfeed/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ var/obj/item/organ/external/genital/breasts/breasts = user.get_organ_slot(ORGAN_SLOT_BREASTS)
+ if(breasts?.internal_fluid_datum)
+ // Calculate milk amount based on how full the breasts are (0.5 to 2 multiplier)
+ var/milk_multiplier = 0.5
+ if(breasts.internal_fluid_maximum > 0)
+ milk_multiplier = 0.5 + (1.5 * (breasts.internal_fluid_count / breasts.internal_fluid_maximum))
+
+ var/transfer_amount = rand(1, 3 * milk_multiplier)
+ var/datum/reagents/R = new(breasts.internal_fluid_maximum)
+ breasts.transfer_internal_fluid(R, transfer_amount)
+ R.trans_to(target, R.total_volume)
+ qdel(R)
+
+/datum/interaction/lewd/titgrope
+ name = "Grope Breasts"
+ description = "Grope their breasts."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ target_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_ANY)
+ additional_details = list(INTERACTION_FILLS_CONTAINERS)
+ message = list(
+ "gently gropes %TARGET%'s breast.",
+ "softly squeezes %TARGET%'s breasts.",
+ "grips %TARGET%'s breasts.",
+ "runs a few fingers over %TARGET%'s breast.",
+ "delicately teases %TARGET%'s nipple.",
+ "traces a touch across %TARGET%'s breast."
+ )
+ sound_possible = list('modular_zzplurt/sound/interactions/squelch1.ogg')
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ user_arousal = 2
+ target_pleasure = 3
+ target_arousal = 5
+
+/datum/interaction/lewd/titgrope/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/obj/item/reagent_containers/liquid_container
+ var/list/original_messages = message.Copy()
+
+ // Check for container
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ message = list("milks %TARGET%'s breasts into \the [liquid_container].")
+ . = ..()
+ message = original_messages
+ return
+
+ // Handle different intents
+ switch(resolve_intent_name(user.combat_mode))
+ if("harm")
+ message = list(
+ "aggressively gropes %TARGET%'s breast.",
+ "grabs %TARGET%'s breasts.",
+ "tightly squeezes %TARGET%'s breasts.",
+ "slaps at %TARGET%'s breasts.",
+ "gropes %TARGET%'s breasts roughly."
+ )
+ if("disarm")
+ message = list(
+ "playfully bats at %TARGET%'s breasts.",
+ "teasingly gropes %TARGET%'s breasts.",
+ "playfully squeezes %TARGET%'s breasts.",
+ "mischievously fondles %TARGET%'s breasts.",
+ "impishly teases %TARGET%'s nipples."
+ )
+ if("grab")
+ message = list(
+ "firmly grips %TARGET%'s breasts.",
+ "possessively gropes %TARGET%'s breasts.",
+ "eagerly kneads %TARGET%'s breasts.",
+ "roughly fondles %TARGET%'s breasts.",
+ "greedily squeezes %TARGET%'s breasts."
+ )
+ . = ..()
+ message = original_messages
+
+/datum/interaction/lewd/titgrope/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(interaction_modifier_flags & INTERACTION_OVERRIDE_FLUID_TRANSFER)
+ var/obj/item/reagent_containers/liquid_container
+
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ var/obj/item/organ/external/genital/breasts/breasts = target.get_organ_slot(ORGAN_SLOT_BREASTS)
+ if(breasts?.internal_fluid_datum)
+ // Calculate milk amount based on how full the breasts are (0.5 to 2 multiplier)
+ var/milk_multiplier = 0.5
+ if(breasts.internal_fluid_maximum > 0)
+ milk_multiplier = 0.5 + (1.5 * (breasts.internal_fluid_count / breasts.internal_fluid_maximum))
+
+ var/transfer_amount = rand(1, 3 * milk_multiplier)
+ var/datum/reagents/R = new(breasts.internal_fluid_maximum)
+ breasts.transfer_internal_fluid(R, transfer_amount)
+ R.trans_to(liquid_container, R.total_volume)
+ qdel(R)
+
+ // Handle arousal effects based on intent
+ var/intent = resolve_intent_name(user.combat_mode)
+ if(intent != "harm" && prob(5 + target.arousal))
+ var/list/arousal_messages
+ switch(intent)
+ if("help")
+ arousal_messages = list(
+ "%TARGET% shivers in arousal.",
+ "%TARGET% moans quietly.",
+ "%TARGET% breathes out a soft moan.",
+ "%TARGET% gasps.",
+ "%TARGET% shudders softly.",
+ "%TARGET% trembles as hands run across bare skin."
+ )
+ if("disarm")
+ arousal_messages = list(
+ "%TARGET% playfully squirms.",
+ "%TARGET% lets out a teasing giggle.",
+ "%TARGET% bites their lip.",
+ "%TARGET% wiggles teasingly.",
+ "%TARGET% gives a flirtatious gasp."
+ )
+ if("grab")
+ arousal_messages = list(
+ "%TARGET% moans eagerly.",
+ "%TARGET% presses into the touch.",
+ "%TARGET% lets out a wanting groan.",
+ "%TARGET% quivers with excitement.",
+ "%TARGET% shivers with anticipation."
+ )
+
+ if(arousal_messages)
+ var/target_message = list(pick(arousal_messages))
+ target.visible_message(span_lewd(replacetext(target_message, "%TARGET%", target)))
+
+/datum/interaction/lewd/breastsmother
+ name = "Breast Smother"
+ description = "Smother them with your breasts."
+ interaction_requires = list(
+ INTERACTION_REQUIRE_TARGET_MOUTH
+ )
+ user_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_EXPOSED)
+ message = list(
+ "presses their breasts against %TARGET%'s face",
+ "smothers %TARGET%'s face with their tits",
+ "forces %TARGET%'s face between their breasts",
+ "pins %TARGET%'s head between their boobs"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s face pressed between your breasts",
+ "You hold %TARGET%'s head against your chest",
+ "You keep %TARGET%'s face buried in your cleavage"
+ )
+ target_messages = list(
+ "Your face is pressed between %USER%'s breasts",
+ "%USER%'s tits smother your face",
+ "Your vision is filled with %USER%'s cleavage"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
+
+/datum/interaction/lewd/breastsmother/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(prob((user.dna.features["sexual_potency"] * 5) + 10))
+ target.adjustOxyLoss(2)
+ target.adjust_arousal(5)
+ user.adjust_arousal(8)
+
+/datum/interaction/lewd/do_boobjob
+ name = "Give Boobjob"
+ description = "Give them a boobjob."
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ user_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "cums all over %USER%'s breasts",
+ "shoots their load onto %USER%'s tits",
+ "covers %USER%'s chest in cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "%TARGET% cums all over your breasts",
+ "%TARGET% shoots their load onto your tits",
+ "%TARGET% covers your chest in cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "You cum all over %USER%'s breasts",
+ "You shoot your load onto %USER%'s tits",
+ "You cover %USER%'s chest in cum"
+ ))
+ message = list(
+ "wraps their breasts around %TARGET%'s cock",
+ "works %TARGET%'s shaft between their tits",
+ "pleasures %TARGET% with their breasts",
+ "squeezes their breasts around %TARGET%'s cock"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s cock throbbing between your breasts",
+ "The warmth of %TARGET%'s shaft feels nice between your tits",
+ "You squeeze your breasts around %TARGET%'s cock"
+ )
+ target_messages = list(
+ "%USER%'s soft breasts squeeze your cock",
+ "Your shaft slides between %USER%'s tits",
+ "The softness of %USER%'s breasts feels amazing"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 4
+ user_arousal = 4
+ target_arousal = 6
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/facefuck.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/facefuck.dm
new file mode 100644
index 0000000000000..59307abf62fd4
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/facefuck.dm
@@ -0,0 +1,82 @@
+/datum/interaction/lewd/facefuck_vagina
+ name = "Facefuck (Vagina)"
+ description = "Grind your pussy against their face."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_VAGINA)
+ cum_target = list(CLIMAX_POSITION_USER = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "grinds their pussy into %TARGET%'s face.",
+ "grips the back of %TARGET%'s head, forcing them onto their pussy.",
+ "rolls their pussy against %TARGET%'s tongue.",
+ "slides %TARGET%'s mouth between their legs.",
+ "looks %TARGET% in the eyes as their pussy presses into a waiting tongue.",
+ "sways their hips, pushing their sex into %TARGET%'s face."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/oral1.ogg',
+ 'modular_zzplurt/sound/interactions/oral2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 5
+ user_arousal = 8
+ target_pleasure = 0
+ target_arousal = 3
+
+/datum/interaction/lewd/facefuck_penis
+ name = "Facefuck (Penis)"
+ description = "Fuck their mouth with your cock."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "roughly fucks %TARGET%'s mouth.",
+ "forces their cock down %TARGET%'s throat.",
+ "pushes in against %TARGET%'s tongue until a tight gagging sound comes.",
+ "grips %TARGET%'s hair and draws them to the base of their cock.",
+ "looks %TARGET% in the eyes as their cock presses into a waiting tongue.",
+ "rolls their hips hard, sinking into %TARGET%'s mouth."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/oral1.ogg',
+ 'modular_zzplurt/sound/interactions/oral2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 5
+ target_pleasure = 0
+ user_arousal = 8
+ target_arousal = 3
+
+/datum/interaction/lewd/throatfuck
+ name = "Throatfuck"
+ description = "Fuck their throat. (Warning: Causes oxygen damage)"
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "brutally shoves their cock into %TARGET%'s throat to make them gag.",
+ "chokes %TARGET% on their cock, going in balls deep.",
+ "slams in and out of %TARGET%'s mouth, their balls slapping off their face."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/oral1.ogg',
+ 'modular_zzplurt/sound/interactions/oral2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 7
+ target_pleasure = 0
+ user_arousal = 10
+ target_arousal = 2
+ target_pain = 5
+
+/datum/interaction/lewd/throatfuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ var/stat_before = target.stat
+ target.adjustOxyLoss(3)
+ if(target.stat == UNCONSCIOUS && stat_before != UNCONSCIOUS)
+ message = list("%TARGET% passes out on %USER%'s cock.")
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/feet.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/feet.dm
new file mode 100644
index 0000000000000..eb6186bba72ab
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/feet.dm
@@ -0,0 +1,228 @@
+/datum/interaction/lewd/grindface
+ name = "Grind Face"
+ description = "Feet grind their face."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_FEET, INTERACTION_REQUIRE_TARGET_MOUTH)
+ message = list(
+ "grinds their %FEET% into %TARGET%'s face.",
+ "presses their %FEET% down hard on %TARGET%'s face.",
+ "rubs off the dirt from their %FEET% onto %TARGET%'s face.",
+ "plants their %FEET% ontop of %TARGET%'s face.",
+ "rests their %FEET% on %TARGET%'s face and presses down hard.",
+ "harshly places their %FEET% atop %TARGET%'s face."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/foot_dry1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_dry2.ogg',
+ 'modular_zzplurt/sound/interactions/foot_dry3.ogg',
+ 'modular_zzplurt/sound/interactions/foot_dry4.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
+
+/datum/interaction/lewd/grindface/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/list/original_messages = message.Copy()
+ // Get shoes or barefoot text
+ var/obj/item/clothing/shoes/worn_shoes = user.get_item_by_slot(ITEM_SLOT_FEET)
+ var/feet_text = worn_shoes?.name || pick("bare feet", "soles")
+
+ var/chosen_message = pick(message)
+ chosen_message = replacetext(chosen_message, "%FEET%", feet_text)
+ message = list(chosen_message)
+ . = ..()
+ message = original_messages
+
+/datum/interaction/lewd/grindmouth
+ name = "Grind Mouth"
+ description = "Feet grind their mouth."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_FEET, INTERACTION_REQUIRE_TARGET_MOUTH)
+ message = list(
+ "roughly shoves their %FEET% deeper into %TARGET%'s mouth.",
+ "harshly forces another inch of their %FEET% into %TARGET%'s mouth.",
+ "presses their weight down, their %FEET% prying deeper into %TARGET%'s mouth.",
+ "forces their %FEET% deep into %TARGET%'s mouth.",
+ "presses the tip of their %FEET% against %TARGET%'s lips and shoves inwards.",
+ "readies themselves and in one swift motion, shoves their %FEET% into %TARGET%'s mouth."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/foot_wet1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet2.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
+
+/datum/interaction/lewd/grindmouth/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/list/original_messages = message.Copy()
+ var/obj/item/clothing/shoes/worn_shoes = user.get_item_by_slot(ITEM_SLOT_FEET)
+ var/feet_text = worn_shoes?.name || pick("bare feet", "toes", "soles")
+
+ var/chosen_message = pick(message)
+ chosen_message = replacetext(chosen_message, "%FEET%", feet_text)
+ message = list(chosen_message)
+ . = ..()
+ message = original_messages
+
+/datum/interaction/lewd/footjob
+ name = "Footjob"
+ description = "Jerk them off with your foot."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_FEET)
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_TARGET = null)
+ message = list(
+ "jerks %TARGET% off with their %FEET%.",
+ "rubs their %FEET% on %TARGET%'s shaft.",
+ "works their %FEET% up and down on %TARGET%'s cock."
+ )
+ cum_message_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "cums all over %USER%'s %FEET%.",
+ "covers %USER%'s %FEET% in cum.",
+ "shoots their load onto %USER%'s %FEET%."
+ )
+ )
+ cum_self_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "you cum all over %USER%'s %FEET%.",
+ "you cover %USER%'s %FEET% in cum.",
+ "you shoot your load onto %USER%'s %FEET%."
+ )
+ )
+ cum_partner_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "%TARGET% cums all over your %FEET%.",
+ "%TARGET% covers your %FEET% in cum.",
+ "%TARGET% shoots their load onto your %FEET%."
+ )
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/foot_dry1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_dry3.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ target_pleasure = 4
+ user_pleasure = 0
+ user_arousal = 3
+ target_arousal = 6
+
+/datum/interaction/lewd/footjob/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/list/original_messages = message.Copy()
+ var/obj/item/clothing/shoes/worn_shoes = user.get_item_by_slot(ITEM_SLOT_FEET)
+ var/feet_text = worn_shoes?.name || pick("foot", "sole")
+
+ var/chosen_message = pick(message)
+ chosen_message = replacetext(chosen_message, "%FEET%", feet_text)
+ message = list(chosen_message)
+ . = ..()
+ message = original_messages
+
+/datum/interaction/lewd/footjob/show_climax(mob/living/carbon/human/cumming, mob/living/carbon/human/came_in, position)
+ var/obj/item/clothing/shoes/worn_shoes = cumming.get_item_by_slot(ITEM_SLOT_FEET)
+ var/feet_text = worn_shoes?.name || pick("foot", "sole")
+
+ // Store original lists, with null checks
+ var/list/original_message_overrides = cum_message_text_overrides[position]
+ var/list/original_self_overrides = cum_self_text_overrides[position]
+ var/list/original_partner_overrides = cum_partner_text_overrides[position]
+ original_message_overrides = original_message_overrides?.Copy()
+ original_self_overrides = original_self_overrides?.Copy()
+ original_partner_overrides = original_partner_overrides?.Copy()
+
+ // Pick and modify one message from each list
+ var/message_override = replacetext(pick(cum_message_text_overrides[position]), "%FEET%", feet_text)
+ var/self_override = replacetext(pick(cum_self_text_overrides[position]), "%FEET%", feet_text)
+ var/partner_override = replacetext(pick(cum_partner_text_overrides[position]), "%FEET%", feet_text)
+
+ // Set single message lists
+ cum_message_text_overrides[position] = list(message_override)
+ cum_self_text_overrides[position] = list(self_override)
+ cum_partner_text_overrides[position] = list(partner_override)
+
+ . = ..()
+
+ // Restore original lists
+ cum_message_text_overrides[position] = original_message_overrides
+ cum_self_text_overrides[position] = original_self_overrides
+ cum_partner_text_overrides[position] = original_partner_overrides
+
+/datum/interaction/lewd/footjob/double
+ name = "Double Footjob"
+ description = "Jerk them off with both of your feet."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_FEET)
+ message = list(
+ "jerks %TARGET% off with their %FEET%.",
+ "rubs their %FEET% on %TARGET%'s shaft.",
+ "works their %FEET% up and down on %TARGET%'s cock."
+ )
+ user_pleasure = 0
+ target_pleasure = 5
+ user_arousal = 4
+ target_arousal = 7
+
+/datum/interaction/lewd/footjob/double/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/list/original_messages = message.Copy()
+ var/obj/item/clothing/shoes/worn_shoes = user.get_item_by_slot(ITEM_SLOT_FEET)
+ var/feet_text = worn_shoes?.name || "feet"
+
+ var/chosen_message = pick(message)
+ chosen_message = replacetext(chosen_message, "%FEET%", feet_text)
+ message = list(chosen_message)
+ . = ..()
+ message = original_messages
+
+/datum/interaction/lewd/footjob/vagina
+ name = "Vaginal Footjob"
+ description = "Rub their vagina with your foot."
+ target_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_VAGINA)
+ cum_target = list(CLIMAX_POSITION_TARGET = null)
+ message = list(
+ "rubs %TARGET%'s clit with their %FEET%.",
+ "rubs their %FEET% on %TARGET%'s coochie.",
+ "rubs their %FEET% on %TARGET%'s pussy.",
+ "rubs their foot up and down on %TARGET%'s pussy."
+ )
+ cum_message_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "squirts all over %USER%'s %FEET%.",
+ "orgasms on %USER%'s %FEET%.",
+ "coats %USER%'s %FEET% with their juices."
+ )
+ )
+ cum_self_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "you squirt all over %USER%'s %FEET%.",
+ "you orgasm on %USER%'s %FEET%.",
+ "you coat %USER%'s %FEET% with your juices."
+ )
+ )
+ cum_partner_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "%TARGET% squirts all over your %FEET%.",
+ "%TARGET% orgasms on your %FEET%.",
+ "%TARGET% coats your %FEET% with their juices."
+ )
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/foot_dry1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_dry3.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ target_pleasure = 0
+ user_arousal = 6
+ target_arousal = 2
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/finger.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/finger.dm
new file mode 100644
index 0000000000000..0866197407a4e
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/finger.dm
@@ -0,0 +1,88 @@
+/datum/interaction/lewd/finger
+ name = "Finger Pussy"
+ description = "Finger their pussy."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ target_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_VAGINA)
+ cum_target = list(CLIMAX_POSITION_TARGET = null)
+ additional_details = list(INTERACTION_FILLS_CONTAINERS)
+ message = list(
+ "fingers %TARGET%",
+ "fingers %TARGET%'s pussy",
+ "fingers %TARGET% hard"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ_fingering.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 3
+ user_arousal = 3
+ target_arousal = 5
+ target_pain = 0
+
+/datum/interaction/lewd/finger/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/list/original_messages = message.Copy()
+ var/obj/item/reagent_containers/liquid_container
+
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ interaction_modifier_flags |= INTERACTION_OVERRIDE_FLUID_TRANSFER
+ message = list(
+ "fingers %TARGET% over \the [liquid_container]",
+ "fingers %TARGET%'s pussy above \the [liquid_container]",
+ "fingers %TARGET% hard while holding \the [liquid_container]"
+ )
+ . = ..()
+ message = original_messages
+ interaction_modifier_flags &= ~INTERACTION_OVERRIDE_FLUID_TRANSFER
+
+/datum/interaction/lewd/finger/post_climax(mob/living/carbon/human/cumming, mob/living/carbon/human/came_in, position)
+ if(interaction_modifier_flags & INTERACTION_OVERRIDE_FLUID_TRANSFER)
+ var/obj/item/reagent_containers/liquid_container
+
+ var/obj/item/cached_item = came_in.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = came_in.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ var/obj/item/organ/external/genital/vagina/vagina = cumming.get_organ_slot(ORGAN_SLOT_VAGINA)
+ if(vagina)
+ vagina.transfer_internal_fluid(liquid_container.reagents, vagina.internal_fluid_count)
+
+ . = ..()
+
+/datum/interaction/lewd/fingerass
+ name = "Finger Ass"
+ description = "Finger their ass."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ target_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_BOTH)
+ message = list(
+ "fingers %TARGET%'s ass",
+ "fingers %TARGET%'s asshole",
+ "fingers %TARGET% hard"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ_fingering.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 3
+ user_arousal = 3
+ target_arousal = 5
+ target_pain = 2
+
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/frotting.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/frotting.dm
new file mode 100644
index 0000000000000..40389df1dc2ae
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/frotting.dm
@@ -0,0 +1,80 @@
+/datum/interaction/lewd/frotting
+ name = "Frotting"
+ description = "Rub your penis against theirs."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS, CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = null, CLIMAX_POSITION_TARGET = null)
+ cum_message_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "cums all over %TARGET%'s cock.",
+ "shoots their load onto %TARGET%'s shaft.",
+ "covers %TARGET%'s penis with their cum."
+ ),
+ CLIMAX_POSITION_TARGET = list(
+ "cums all over %USER%'s cock.",
+ "shoots their load onto %USER%'s shaft.",
+ "covers %USER%'s penis with their cum."
+ )
+ )
+ cum_self_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "you cum all over %TARGET%'s cock.",
+ "you shoot your load onto %TARGET%'s shaft.",
+ "you cover %TARGET%'s penis with your cum."
+ ),
+ CLIMAX_POSITION_TARGET = list(
+ "you cum all over %USER%'s cock.",
+ "you shoot your load onto %USER%'s shaft.",
+ "you cover %USER%'s penis with your cum."
+ )
+ )
+ cum_partner_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "%USER% cums all over your cock.",
+ "%USER% shoots their load onto your shaft.",
+ "%USER% covers your penis with their cum."
+ ),
+ CLIMAX_POSITION_TARGET = list(
+ "%TARGET% cums all over your cock.",
+ "%TARGET% shoots their load onto your shaft.",
+ "%TARGET% covers your penis with their cum."
+ )
+ )
+ message = list(
+ "rubs their cock against %TARGET%'s.",
+ "grinds their shaft against %TARGET%'s penis.",
+ "presses their dick against %TARGET%'s member.",
+ "frotts against %TARGET%'s cock."
+ )
+ sound_use = TRUE
+ sound_range = 1
+ user_pleasure = 6
+ target_pleasure = 6
+ user_arousal = 10
+ target_arousal = 10
+
+/datum/interaction/lewd/tribadism
+ name = "Tribadism"
+ description = "Grind your pussy against theirs."
+ user_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_VAGINA, CLIMAX_POSITION_TARGET = CLIMAX_VAGINA)
+ cum_target = list(CLIMAX_POSITION_USER = ORGAN_SLOT_VAGINA, CLIMAX_POSITION_TARGET = ORGAN_SLOT_VAGINA)
+ message = list(
+ "grinds their pussy against %TARGET%'s cunt.",
+ "rubs their cunt against %TARGET%'s pussy.",
+ "thrusts against %TARGET%'s pussy.",
+ "humps %TARGET%, their pussies grinding against each other."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/squelch1.ogg',
+ 'modular_zzplurt/sound/interactions/squelch2.ogg',
+ 'modular_zzplurt/sound/interactions/squelch3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 6
+ target_pleasure = 6
+ user_arousal = 10
+ target_arousal = 10
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/fuck.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/fuck.dm
new file mode 100644
index 0000000000000..22dd398464be6
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/fuck.dm
@@ -0,0 +1,239 @@
+/datum/interaction/lewd/fuck
+ name = "Fuck"
+ description = "Fuck their pussy."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS, CLIMAX_POSITION_TARGET = CLIMAX_VAGINA)
+ cum_target = list(CLIMAX_POSITION_USER = ORGAN_SLOT_VAGINA, CLIMAX_POSITION_TARGET = ORGAN_SLOT_PENIS)
+ message = list(
+ "pounds %TARGET%'s pussy.",
+ "shoves their cock deep into %TARGET%'s pussy.",
+ "thrusts in and out of %TARGET%'s cunt.",
+ "goes balls deep into %TARGET%'s pussy over and over again."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ1.ogg',
+ 'modular_zzplurt/sound/interactions/champ2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 8
+ target_pleasure = 8
+ user_arousal = 12
+ target_arousal = 12
+
+/datum/interaction/lewd/fuck/anal
+ name = "Anal Fuck"
+ description = "Fuck their ass."
+ target_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS, CLIMAX_POSITION_TARGET = CLIMAX_BOTH)
+ cum_target = list(CLIMAX_POSITION_USER = ORGAN_SLOT_ANUS, CLIMAX_POSITION_TARGET = ORGAN_SLOT_PENIS)
+ message = list(
+ "thrusts in and out of %TARGET%'s ass.",
+ "pounds %TARGET%'s ass.",
+ "slams their hips up against %TARGET%'s ass hard.",
+ "goes balls deep into %TARGET%'s ass over and over again."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_use = TRUE
+ user_pleasure = 8
+ target_pleasure = 4
+ user_arousal = 12
+ target_arousal = 8
+ target_pain = 3
+
+/datum/interaction/lewd/breastfuck
+ name = "Breast Fuck"
+ description = "Fuck their breasts."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = ORGAN_SLOT_BREASTS)
+ message = list(
+ "fucks %TARGET%'s breasts.",
+ "grinds their cock between %TARGET%'s boobs.",
+ "thrusts into %TARGET%'s tits.",
+ "grabs %TARGET%'s breasts together and presses their cock between them."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ target_pleasure = 0
+ user_arousal = 8
+ target_arousal = 3
+
+/datum/interaction/lewd/footfuck
+ name = "Foot Fuck"
+ description = "Rub your cock on their foot."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_FEET)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "cums all over %TARGET%'s foot.",
+ "shoots their load on %TARGET%'s sole.",
+ "covers %TARGET%'s toes in cum."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ cum_self_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "you cum all over %TARGET%'s foot.",
+ "you shoot your load on %TARGET%'s sole.",
+ "you cover %TARGET%'s toes in cum."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ cum_partner_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "%USER% cums all over your foot.",
+ "%USER% shoots their load on your sole.",
+ "%USER% covers your toes in cum."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ message = list(
+ "fucks %TARGET%'s foot.",
+ "rubs their cock on %TARGET%'s foot.",
+ "grinds their cock on %TARGET%'s foot."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/foot_dry1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_dry3.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 3
+ target_pleasure = 0
+ user_arousal = 6
+ target_arousal = 2
+
+/datum/interaction/lewd/footfuck/double
+ name = "Double Foot Fuck"
+ description = "Rub your cock between their feet."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_FEET)
+ cum_message_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "cums all over %TARGET%'s feet.",
+ "shoots their load on %TARGET%'s soles.",
+ "covers %TARGET%'s toes in cum."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ cum_self_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "you cum all over %TARGET%'s feet.",
+ "you shoot your load on %TARGET%'s soles.",
+ "you cover %TARGET%'s toes in cum."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ cum_partner_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "%USER% cums all over your feet.",
+ "%USER% shoots their load on your soles.",
+ "%USER% covers your toes in cum."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ message = list(
+ "fucks %TARGET%'s feet.",
+ "rubs their cock between %TARGET%'s feet.",
+ "thrusts their cock between %TARGET%'s feet.",
+ "grinds their cock between %TARGET%'s feet."
+ )
+ user_arousal = 15
+ target_arousal = 5
+
+/datum/interaction/lewd/footfuck/vag
+ name = "Vaginal Foot Grind"
+ description = "Rub your vagina on their foot."
+ user_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_VAGINA)
+ cum_message_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "squirts all over %TARGET%'s foot.",
+ "orgasms on %TARGET%'s sole.",
+ "coats %TARGET%'s toes with their juices."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ cum_self_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "you squirt all over %TARGET%'s foot.",
+ "you orgasm on %TARGET%'s sole.",
+ "you coat %TARGET%'s toes with your juices."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ cum_partner_text_overrides = list(
+ CLIMAX_POSITION_USER = list(
+ "%USER% squirts all over your foot.",
+ "%USER% orgasms on your sole.",
+ "%USER% coats your toes with their juices."
+ ),
+ CLIMAX_POSITION_TARGET = list()
+ )
+ message = list(
+ "grinds their pussy against %TARGET%'s foot.",
+ "rubs their clit on %TARGET%'s foot.",
+ "ruts on %TARGET%'s foot."
+ )
+ sound_use = TRUE
+ user_pleasure = 15
+ target_pleasure = 0
+ user_arousal = 20
+ target_arousal = 5
+
+/datum/interaction/lewd/cockfuck
+ name = "Cockfuck"
+ description = "Fuck their cock."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(
+ CLIMAX_POSITION_USER = CLIMAX_PENIS,
+ CLIMAX_POSITION_TARGET = CLIMAX_PENIS
+ )
+ cum_target = list(
+ CLIMAX_POSITION_USER = ORGAN_SLOT_PENIS,
+ CLIMAX_POSITION_TARGET = ORGAN_SLOT_PENIS
+ )
+ message = list(
+ "pushes their cock into %TARGET%'s urethra",
+ "penetrates %TARGET%'s cock with their own",
+ "thrusts deep into %TARGET%'s cockhole",
+ "fucks %TARGET%'s cock from the inside"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s cock squeezing around yours",
+ "The warmth of %TARGET%'s urethra envelops your shaft",
+ "%TARGET%'s cock tightens around yours as you thrust deeper"
+ )
+ target_messages = list(
+ "You feel %USER%'s cock stretching your urethra",
+ "%USER%'s shaft pushes deep inside your cock",
+ "The warmth of %USER%'s cock fills your shaft from within"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 6
+ target_pleasure = 6
+ user_arousal = 8
+ target_arousal = 8
+ target_pain = 4
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/grope.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/grope.dm
new file mode 100644
index 0000000000000..d8cda5250d6a2
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/grope.dm
@@ -0,0 +1,26 @@
+/datum/interaction/lewd/grope_ass
+ name = "Grope Ass"
+ description = "Grope their ass."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ message = list(
+ "gropes %TARGET%'s ass",
+ "squeezes %TARGET%'s butt",
+ "fondles %TARGET%'s rear",
+ "grabs %TARGET%'s ass cheeks"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s soft ass in your hand",
+ "The firmness of %TARGET%'s butt feels nice in your palm",
+ "You squeeze %TARGET%'s plump rear"
+ )
+ target_messages = list(
+ "You feel %USER%'s hand groping your ass",
+ "%USER%'s fingers squeeze your butt cheeks",
+ "The warmth of %USER%'s palm presses against your rear"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/handjob.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/handjob.dm
new file mode 100644
index 0000000000000..8975f374a09de
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/handjob.dm
@@ -0,0 +1,129 @@
+/datum/interaction/lewd/handjob
+ name = "Handjob"
+ description = "Jerk them off."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_TARGET = null)
+ additional_details = list(INTERACTION_FILLS_CONTAINERS)
+ message = list(
+ "jerks %TARGET% off",
+ "works %TARGET%'s shaft",
+ "wanks %TARGET%'s cock hard"
+ )
+ cum_message_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "cums all over %USER%'s hand.",
+ "shoots their load onto %USER%'s palm.",
+ "covers %USER%'s fingers in cum."
+ )
+ )
+ cum_self_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "you cum all over %USER%'s hand.",
+ "you shoot your load onto %USER%'s palm.",
+ "you cover %USER%'s fingers in cum."
+ )
+ )
+ cum_partner_text_overrides = list(
+ CLIMAX_POSITION_TARGET = list(
+ "%TARGET% cums all over your hand.",
+ "%TARGET% shoots their load onto your palm.",
+ "%TARGET% covers your fingers in cum."
+ )
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ user_arousal = 3
+ target_pleasure = 4
+ target_arousal = 6
+
+/datum/interaction/lewd/handjob/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/obj/item/reagent_containers/liquid_container
+
+ // Check active hand first
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ // Check if pulling a container
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ // Add container text to message if needed
+ if(liquid_container)
+ var/list/original_messages = message.Copy()
+ var/chosen_message = pick(message)
+ message = list("[chosen_message] over \the [liquid_container]")
+ interaction_modifier_flags |= INTERACTION_OVERRIDE_FLUID_TRANSFER
+ . = ..()
+ interaction_modifier_flags &= ~INTERACTION_OVERRIDE_FLUID_TRANSFER
+ message = original_messages
+ else
+ . = ..()
+
+/datum/interaction/lewd/handjob/show_climax(mob/living/carbon/human/cumming, mob/living/carbon/human/came_in, position)
+ if(interaction_modifier_flags & INTERACTION_OVERRIDE_FLUID_TRANSFER)
+ var/obj/item/reagent_containers/liquid_container
+
+ // Check active hand first
+ var/obj/item/cached_item = came_in.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ // Check if pulling a container
+ cached_item = came_in.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ // Store original lists, with null checks
+ var/list/original_message_overrides = cum_message_text_overrides[position]
+ var/list/original_self_overrides = cum_self_text_overrides[position]
+ var/list/original_partner_overrides = cum_partner_text_overrides[position]
+ original_message_overrides = original_message_overrides?.Copy()
+ original_self_overrides = original_self_overrides?.Copy()
+ original_partner_overrides = original_partner_overrides?.Copy()
+
+ // Set container-specific messages
+ cum_message_text_overrides[position] = list("cums into \the [liquid_container].")
+ cum_self_text_overrides[position] = list("you cum into \the [liquid_container].")
+ cum_partner_text_overrides[position] = list("%TARGET% cums into \the [liquid_container].")
+
+ . = ..()
+
+ // Restore original messages
+ cum_message_text_overrides[position] = original_message_overrides
+ cum_self_text_overrides[position] = original_self_overrides
+ cum_partner_text_overrides[position] = original_partner_overrides
+ return
+
+ . = ..()
+
+/datum/interaction/lewd/handjob/post_climax(mob/living/carbon/human/cumming, mob/living/carbon/human/came_in, position)
+ if(interaction_modifier_flags & INTERACTION_OVERRIDE_FLUID_TRANSFER)
+ var/obj/item/reagent_containers/liquid_container
+
+ // Check active hand first
+ var/obj/item/cached_item = came_in.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ // Check if pulling a container
+ cached_item = came_in.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ var/obj/item/organ/external/genital/testicles/testicles = cumming.get_organ_slot(ORGAN_SLOT_TESTICLES)
+ if(testicles)
+ testicles.transfer_internal_fluid(liquid_container.reagents, testicles.internal_fluid_count)
+
+ . = ..()
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/_extreme.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/_extreme.dm
new file mode 100644
index 0000000000000..a01855b024220
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/_extreme.dm
@@ -0,0 +1,8 @@
+/datum/interaction/lewd/extreme
+ unsafe_types = INTERACTION_EXTREME
+ color = "red"
+ category = INTERACTION_CAT_EXTREME
+
+/datum/interaction/lewd/extreme/harmful
+ unsafe_types = INTERACTION_EXTREME | INTERACTION_HARMFUL
+ category = INTERACTION_CAT_HARMFUL
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/earfuck.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/earfuck.dm
new file mode 100644
index 0000000000000..6b1d6630b3b1c
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/earfuck.dm
@@ -0,0 +1,90 @@
+/datum/interaction/lewd/extreme/harmful/earfuck
+ name = "Earfuck"
+ description = "Fuck their ear."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums deep into %TARGET%'s ear",
+ "shoots their load into %TARGET%'s ear canal",
+ "fills %TARGET%'s ear with their cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum deep into %TARGET%'s ear",
+ "You shoot your load into %TARGET%'s ear canal",
+ "You fill %TARGET%'s ear with your cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "%USER% cums deep into your ear",
+ "%USER% shoots their load into your ear canal",
+ "%USER% fills your ear with their cum"
+ ))
+ message = list(
+ "pounds into %TARGET%'s ear.",
+ "shoves their cock deep into %TARGET%'s skull",
+ "thrusts in and out of %TARGET%'s ear.",
+ "goes balls deep into %TARGET%'s cranium over and over again."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ1.ogg',
+ 'modular_zzplurt/sound/interactions/champ2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 5
+ target_pleasure = 0
+ user_arousal = 8
+ target_arousal = 0
+ target_pain = 15
+
+/datum/interaction/lewd/extreme/harmful/earfuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(prob(15))
+ target.bleed(2)
+ if(prob(25))
+ target.adjustOrganLoss(ORGAN_SLOT_EARS, rand(3,7))
+ target.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(3,7))
+
+/datum/interaction/lewd/extreme/harmful/earsocketfuck
+ name = "Earsocketfuck"
+ description = "Fuck their earsocket."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums deep into %TARGET%'s empty ear socket",
+ "shoots their load into %TARGET%'s skull",
+ "fills %TARGET%'s ear socket with their cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum deep into %TARGET%'s empty ear socket",
+ "You shoot your load into %TARGET%'s skull",
+ "You fill %TARGET%'s ear socket with your cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "%USER% cums deep into your empty ear socket",
+ "%USER% shoots their load into your skull",
+ "%USER% fills your ear socket with their cum"
+ ))
+ message = list(
+ "pounds into %TARGET%'s earsocket.",
+ "shoves their cock deep into %TARGET%'s skull",
+ "thrusts in and out of %TARGET%'s earsocket.",
+ "goes balls deep into %TARGET%'s cranium over and over again."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ1.ogg',
+ 'modular_zzplurt/sound/interactions/champ2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 5
+ target_pleasure = 0
+ user_arousal = 8
+ target_arousal = 0
+ target_pain = 15
+
+/datum/interaction/lewd/extreme/harmful/earsocketfuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(prob(15))
+ target.bleed(2)
+ if(prob(25))
+ target.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(3,7))
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/eyefuck.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/eyefuck.dm
new file mode 100644
index 0000000000000..0e3a0d2dad8f7
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/harmful/eyefuck.dm
@@ -0,0 +1,90 @@
+/datum/interaction/lewd/extreme/harmful/eyefuck
+ name = "Eyefuck"
+ description = "Fuck their eye."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums deep into %TARGET%'s eye",
+ "shoots their load into %TARGET%'s eye socket",
+ "fills %TARGET%'s eye with their cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum deep into %TARGET%'s eye",
+ "You shoot your load into %TARGET%'s eye socket",
+ "You fill %TARGET%'s eye with your cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "%USER% cums deep into your eye",
+ "%USER% shoots their load into your eye socket",
+ "%USER% fills your eye with their cum"
+ ))
+ message = list(
+ "pounds into %TARGET%'s eye.",
+ "shoves their cock deep into %TARGET%'s skull",
+ "thrusts in and out of %TARGET%'s eye.",
+ "goes balls deep into %TARGET%'s cranium over and over again."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ1.ogg',
+ 'modular_zzplurt/sound/interactions/champ2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 5
+ target_pleasure = 0
+ user_arousal = 8
+ target_arousal = 0
+ target_pain = 15
+
+/datum/interaction/lewd/extreme/harmful/eyefuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(prob(15))
+ target.bleed(2)
+ if(prob(25))
+ target.adjustOrganLoss(ORGAN_SLOT_EYES, rand(3,7))
+ target.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(3,7))
+
+/datum/interaction/lewd/extreme/harmful/eyesocketfuck
+ name = "Eyesocketfuck"
+ description = "Fuck their eyesocket."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums deep into %TARGET%'s empty eye socket",
+ "shoots their load into %TARGET%'s skull",
+ "fills %TARGET%'s eye socket with their cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum deep into %TARGET%'s empty eye socket",
+ "You shoot your load into %TARGET%'s skull",
+ "You fill %TARGET%'s eye socket with your cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "%USER% cums deep into your empty eye socket",
+ "%USER% shoots their load into your skull",
+ "%USER% fills your eye socket with their cum"
+ ))
+ message = list(
+ "pounds into %TARGET%'s eyesocket.",
+ "shoves their cock deep into %TARGET%'s skull",
+ "thrusts in and out of %TARGET%'s eyesocket.",
+ "goes balls deep into %TARGET%'s cranium over and over again."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ1.ogg',
+ 'modular_zzplurt/sound/interactions/champ2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 5
+ target_pleasure = 0
+ user_arousal = 8
+ target_arousal = 0
+ target_pain = 15
+
+/datum/interaction/lewd/extreme/harmful/eyesocketfuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(prob(15))
+ target.bleed(2)
+ if(prob(25))
+ target.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(3,7))
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/kiss.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/kiss.dm
new file mode 100644
index 0000000000000..58277fef34ab8
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/kiss.dm
@@ -0,0 +1,46 @@
+/datum/interaction/lewd/kiss
+ name = "Kiss"
+ description = "Kiss them deeply."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH, INTERACTION_REQUIRE_TARGET_MOUTH)
+ message = list(
+ "gives an intense, lingering kiss to %TARGET%.",
+ "kisses %TARGET% deeply.",
+ "slides their tongue into %TARGET%'s mouth.",
+ "presses their lips against %TARGET%'s.",
+ "gives %TARGET% a passionate kiss."
+ )
+ user_messages = list(
+ "You feel %TARGET%'s warm lips against yours.",
+ "Your tongue dances with %TARGET%'s.",
+ "The taste of %TARGET%'s mouth lingers on your lips."
+ )
+ target_messages = list(
+ "%USER%'s tongue explores your mouth.",
+ "You feel %USER%'s lips press against yours.",
+ "The warmth of %USER%'s kiss sends shivers down your spine."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/kiss1.ogg',
+ 'modular_zzplurt/sound/interactions/kiss2.ogg',
+ 'modular_zzplurt/sound/interactions/kiss3.ogg',
+ 'modular_zzplurt/sound/interactions/kiss4.ogg',
+ 'modular_zzplurt/sound/interactions/kiss5.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 2
+ target_arousal = 2
+
+/datum/interaction/lewd/kiss/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+
+ // Check if user has TRAIT_KISS_SLUT and increase their lust
+ if(HAS_TRAIT(user, TRAIT_KISS_SLUT))
+ user.adjust_pleasure(10, target, interaction = src, position = CLIMAX_POSITION_USER)
+ user.adjust_arousal(10)
+ // Check if target has TRAIT_KISS_SLUT and increase their lust
+ if(HAS_TRAIT(target, TRAIT_KISS_SLUT))
+ target.adjust_pleasure(10, user, interaction = src, position = CLIMAX_POSITION_TARGET)
+ target.adjust_arousal(10)
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/lick.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/lick.dm
new file mode 100644
index 0000000000000..eb92dc12eda69
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/lick.dm
@@ -0,0 +1,128 @@
+/datum/interaction/lewd/rimjob
+ name = "Rim"
+ description = "Lick their ass."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ target_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_BOTH)
+ message = list(
+ "licks %TARGET%'s asshole.",
+ "rims %TARGET% deeply.",
+ "buries their tongue in %TARGET%'s ass.",
+ "presses their tongue against %TARGET%'s pucker.",
+ "gives %TARGET%'s ass a passionate licking."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ_fingering.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 4
+ user_arousal = 3
+ target_arousal = 6
+
+/datum/interaction/lewd/lickfeet
+ name = "Lick Feet"
+ description = "Lick their feet."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH, INTERACTION_REQUIRE_TARGET_FEET)
+ message = list(
+ "licks %TARGET%'s bare feet.",
+ "runs their tongue along %TARGET%'s soles.",
+ "laps at %TARGET%'s toes.",
+ "tastes %TARGET%'s bare feet."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ_fingering.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
+
+/datum/interaction/lewd/lickfeet/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/list/original_messages = message.Copy()
+ var/obj/item/clothing/shoes/shoes = target.get_item_by_slot(ITEM_SLOT_FEET)
+
+ if(shoes)
+ message = list(
+ "licks %TARGET%'s [shoes.name].",
+ "runs their tongue over %TARGET%'s [shoes.name].",
+ "drags their tongue across %TARGET%'s [shoes.name].",
+ "tastes %TARGET%'s [shoes.name]."
+ )
+ . = ..()
+ message = original_messages
+
+/datum/interaction/lewd/lick_sweat
+ name = "Lick Sweat"
+ description = "Lick their sweat."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ message = list(
+ "licks the sweat off %TARGET%'s skin",
+ "tastes %TARGET%'s salty sweat",
+ "runs their tongue along %TARGET%'s sweaty body",
+ "savors the taste of %TARGET%'s perspiration"
+ )
+ user_messages = list(
+ "You taste %TARGET%'s salty sweat",
+ "The tang of %TARGET%'s sweat fills your mouth",
+ "You savor the salty taste of %TARGET%'s skin"
+ )
+ target_messages = list(
+ "You feel %USER%'s tongue licking your sweat",
+ "%USER%'s wet tongue slides across your sweaty skin",
+ "The warmth of %USER%'s mouth tingles against your damp skin"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ_fingering.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
+
+/datum/interaction/lewd/lick_nuts
+ name = "Lick Balls"
+ description = "Lick their balls."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ target_required_parts = list(ORGAN_SLOT_TESTICLES = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ message = list(
+ "licks %TARGET%'s balls",
+ "sucks on %TARGET%'s testicles",
+ "tongues %TARGET%'s ballsack",
+ "worships %TARGET%'s balls with their tongue"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s balls against your tongue",
+ "The taste of %TARGET%'s sack fills your mouth",
+ "You lavish attention on %TARGET%'s balls"
+ )
+ target_messages = list(
+ "%USER%'s tongue works over your balls",
+ "You feel %USER%'s hot mouth on your sack",
+ "The warmth of %USER%'s tongue makes your balls tingle"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bj1.ogg',
+ 'modular_zzplurt/sound/interactions/bj2.ogg',
+ 'modular_zzplurt/sound/interactions/bj3.ogg',
+ 'modular_zzplurt/sound/interactions/bj4.ogg',
+ 'modular_zzplurt/sound/interactions/bj5.ogg',
+ 'modular_zzplurt/sound/interactions/bj6.ogg',
+ 'modular_zzplurt/sound/interactions/bj7.ogg',
+ 'modular_zzplurt/sound/interactions/bj8.ogg',
+ 'modular_zzplurt/sound/interactions/bj9.ogg',
+ 'modular_zzplurt/sound/interactions/bj10.ogg',
+ 'modular_zzplurt/sound/interactions/bj11.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 3
+ user_arousal = 3
+ target_arousal = 5
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/misc.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/misc.dm
new file mode 100644
index 0000000000000..717a6c356bd27
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/misc.dm
@@ -0,0 +1,19 @@
+/datum/interaction/lewd/clothesplosion
+ name = "Clothesplosion"
+ description = "Explode out of your clothes."
+ usage = INTERACTION_SELF
+ message = list(
+ "bursts out of their clothes!",
+ "explodes out of their outfit!",
+ "dramatically tears free of their garments!"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ user_arousal = 0
+
+/datum/interaction/lewd/clothesplosion/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(!istype(user))
+ return
+ user.clothing_burst(FALSE)
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/mount.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/mount.dm
new file mode 100644
index 0000000000000..c1a6f28602dc0
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/mount.dm
@@ -0,0 +1,73 @@
+/datum/interaction/lewd/mount_vagina
+ name = "Mount (Vagina)"
+ description = "Mount them with your pussy."
+ user_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_VAGINA, CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = ORGAN_SLOT_PENIS, CLIMAX_POSITION_TARGET = ORGAN_SLOT_VAGINA)
+ message = list(
+ "rides %TARGET%'s cock.",
+ "forces %TARGET%'s cock into their pussy.",
+ "slides their pussy onto %TARGET%'s cock.",
+ "impales themself on %TARGET%'s cock."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 7
+ target_pleasure = 7
+ user_arousal = 10
+ target_arousal = 10
+
+/datum/interaction/lewd/mount_anus
+ name = "Mount (Anus)"
+ description = "Mount them with your ass."
+ user_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_BOTH, CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = null, CLIMAX_POSITION_TARGET = ORGAN_SLOT_ANUS)
+ message = list(
+ "rides %TARGET%'s cock with their ass.",
+ "forces %TARGET%'s cock into their ass.",
+ "slides their ass onto %TARGET%'s cock.",
+ "impales their ass on %TARGET%'s cock."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ target_pleasure = 7
+ user_arousal = 8
+ target_arousal = 10
+ user_pain = 3
+
+/datum/interaction/lewd/mount_face
+ name = "Mount Face"
+ description = "Sit on their face."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ message = list(
+ "grinds their ass into %TARGET%'s face.",
+ "shoves their ass into %TARGET%'s face.",
+ "plants their ass right on %TARGET%'s face.",
+ "grabs the back of %TARGET%'s head and forces it into their asscheeks."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/squelch1.ogg',
+ 'modular_zzplurt/sound/interactions/squelch2.ogg',
+ 'modular_zzplurt/sound/interactions/squelch3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 3
+ target_pleasure = 0
+ user_arousal = 5
+ target_arousal = 3
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nipples.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nipples.dm
new file mode 100644
index 0000000000000..c84ea062f150e
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nipples.dm
@@ -0,0 +1,52 @@
+/datum/interaction/lewd/nipplefuck
+ name = "Nipplefuck"
+ description = "Fuck their nipple."
+ interaction_requires = list(
+ INTERACTION_REQUIRE_TARGET_TOPLESS
+ )
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums all over %TARGET%'s nipple",
+ "shoots their load into %TARGET%'s breast",
+ "fills %TARGET%'s nipple with cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum all over %TARGET%'s nipple",
+ "You shoot your load into %TARGET%'s breast",
+ "You fill %TARGET%'s nipple with cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "%USER% cums all over your nipple",
+ "%USER% shoots their load into your breast",
+ "%USER% fills your nipple with cum"
+ ))
+ message = list(
+ "fucks %TARGET%'s nipple",
+ "slides their cock into %TARGET%'s breast",
+ "pounds %TARGET%'s nipple",
+ "thrusts deep into %TARGET%'s nipple"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s nipple squeezing your cock",
+ "The warmth of %TARGET%'s breast envelops your shaft",
+ "%TARGET%'s nipple feels amazing around your cock"
+ )
+ target_messages = list(
+ "You feel %USER%'s cock stretching your nipple",
+ "%USER%'s shaft pushes deep into your breast",
+ "The warmth of %USER%'s cock fills your nipple"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ target_pleasure = 2
+ user_arousal = 6
+ target_arousal = 4
+ target_pain = 2
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nipsuck.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nipsuck.dm
new file mode 100644
index 0000000000000..688d0c8eba783
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nipsuck.dm
@@ -0,0 +1,100 @@
+/datum/interaction/lewd/nipsuck
+ name = "Suck Nipples"
+ description = "Suck their nipples."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ target_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_EXPOSED)
+ message = list(
+ "gently sucks on %TARGET%'s nipple.",
+ "gently nibs %TARGET%'s nipple.",
+ "licks %TARGET%'s nipple."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/oral1.ogg',
+ 'modular_zzplurt/sound/interactions/oral2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ user_arousal = 2
+ target_pleasure = 3
+ target_arousal = 5
+
+/datum/interaction/lewd/nipsuck/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/list/original_messages = message.Copy()
+
+ // Handle different intents
+ switch(resolve_intent_name(user.combat_mode))
+ if("harm")
+ message = list(
+ "bites %TARGET%'s nipple.",
+ "aggressively sucks %TARGET%'s nipple."
+ )
+ target_pleasure = 4 // Aggressive sucking has higher rewards
+ target_arousal = 5
+ if("disarm")
+ message = list(
+ "playfully nibbles %TARGET%'s nipple.",
+ "teasingly sucks %TARGET%'s nipple.",
+ "gently bites %TARGET%'s nipple."
+ )
+ if("grab")
+ message = list(
+ "sucks %TARGET%'s nipple intently.",
+ "feasts on %TARGET%'s nipple.",
+ "glomps %TARGET%'s nipple."
+ )
+ target_pleasure = 4 // Intent sucking has higher rewards
+ target_arousal = 5
+ . = ..()
+ message = original_messages
+
+/datum/interaction/lewd/nipsuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ var/obj/item/organ/external/genital/breasts/breasts = target.get_organ_slot(ORGAN_SLOT_BREASTS)
+ if(breasts?.internal_fluid_datum)
+ // Calculate milk amount based on how full the breasts are (0.5 to 2 multiplier)
+ var/milk_multiplier = 0.5
+ if(breasts.internal_fluid_maximum > 0)
+ milk_multiplier = 0.5 + (1.5 * (breasts.internal_fluid_count / breasts.internal_fluid_maximum))
+
+ var/transfer_amount = rand(1, 2 * milk_multiplier)
+ var/intent = resolve_intent_name(user.combat_mode)
+ if(intent == "harm" || intent == "grab")
+ transfer_amount = rand(1, 3 * milk_multiplier) // More aggressive sucking gets more milk
+
+ var/datum/reagents/R = new(breasts.internal_fluid_maximum)
+ breasts.transfer_internal_fluid(R, transfer_amount)
+ R.trans_to(user, R.total_volume)
+ qdel(R)
+
+ if(!user.combat_mode && prob(5 + target.arousal))
+ var/list/arousal_messages
+ switch(resolve_intent_name(user.combat_mode))
+ if("help")
+ arousal_messages = list(
+ "%TARGET% shivers in arousal.",
+ "%TARGET% moans quietly.",
+ "%TARGET% breathes out a soft moan.",
+ "%TARGET% gasps.",
+ "%TARGET% shudders softly.",
+ "%TARGET% trembles as their chest gets molested."
+ )
+ if("disarm")
+ arousal_messages = list(
+ "%TARGET% playfully squirms.",
+ "%TARGET% wiggles teasingly.",
+ "%TARGET% lets out a playful moan.",
+ "%TARGET% bites their lip.",
+ "%TARGET% squirms from the teasing."
+ )
+ if("grab")
+ arousal_messages = list(
+ "%TARGET% moans eagerly.",
+ "%TARGET% presses their chest forward.",
+ "%TARGET% lets out a wanting groan.",
+ "%TARGET% quivers with excitement.",
+ "%TARGET% shivers with anticipation."
+ )
+
+ if(arousal_messages)
+ message = list(pick(arousal_messages))
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nuts.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nuts.dm
new file mode 100644
index 0000000000000..801e23ce255ff
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/nuts.dm
@@ -0,0 +1,54 @@
+/datum/interaction/lewd/nuts
+ name = "Nuts to Face"
+ description = "Put your balls in their face."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_TESTICLES = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "grabs the back of %TARGET%'s head and pulls it into their crotch.",
+ "jams their nutsack right into %TARGET%'s face.",
+ "roughly grinds their fat nutsack into %TARGET%'s mouth.",
+ "pulls out their saliva-covered nuts from %TARGET%'s violated mouth and then wipes off the slime onto their face.",
+ "wedges a digit into the side of %TARGET%'s jaw and pries it open before using their other hand to shove their whole nutsack inside!",
+ "stands with their groin inches away from %TARGET%'s face, then thrusting their hips forward and smothering %TARGET%'s whole face with their heavy ballsack."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/oral1.ogg',
+ 'modular_zzplurt/sound/interactions/oral2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 3
+ target_pleasure = 0
+ user_arousal = 5
+ target_arousal = 2
+
+/datum/interaction/lewd/nut_smack
+ name = "Smack Nuts"
+ description = "Smack their nuts."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ target_required_parts = list(ORGAN_SLOT_TESTICLES = REQUIRE_GENITAL_EXPOSED)
+ message = list(
+ "smacks %TARGET%'s nuts!",
+ "slaps %TARGET%'s balls!",
+ "gives %TARGET%'s testicles a slap!",
+ "whacks %TARGET% right in the nuts!"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/slap.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = -10
+ target_pain = 15
+ user_arousal = 2
+ target_arousal = 0
+
+/datum/interaction/lewd/nut_smack/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/original_pleasure = target_pleasure
+ if(HAS_TRAIT(target, TRAIT_MASOCHISM))
+ target_pleasure = abs(original_pleasure) * 1.5 // Masochists get 50% more pleasure from the pain
+ . = ..()
+ target_pleasure = original_pleasure
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/oral.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/oral.dm
new file mode 100644
index 0000000000000..85f633f6a6b5e
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/oral.dm
@@ -0,0 +1,96 @@
+/datum/interaction/lewd/oral_vagina
+ name = "Perform Oral"
+ description = "Go down on them."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ target_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_VAGINA)
+ cum_target = list(CLIMAX_POSITION_TARGET = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "buries their face in %TARGET%'s pussy.",
+ "nuzzles %TARGET%'s wet sex.",
+ "finds their face caught between %TARGET%'s thighs.",
+ "kneels down between %TARGET%'s legs.",
+ "grips %TARGET%'s legs, pushing them apart.",
+ "sinks their face in between %TARGET%'s thighs."
+ )
+ user_messages = list(
+ "You feel %TARGET%'s warm wetness against your face.",
+ "The scent of %TARGET%'s arousal fills your senses.",
+ "You press your tongue deeper into %TARGET%'s folds."
+ )
+ target_messages = list(
+ "%USER%'s tongue explores your pussy.",
+ "You feel %USER%'s hot breath against your sex.",
+ "The warmth of %USER%'s mouth sends shivers up your spine."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bj1.ogg',
+ 'modular_zzplurt/sound/interactions/bj2.ogg',
+ 'modular_zzplurt/sound/interactions/bj3.ogg',
+ 'modular_zzplurt/sound/interactions/bj4.ogg',
+ 'modular_zzplurt/sound/interactions/bj5.ogg',
+ 'modular_zzplurt/sound/interactions/bj6.ogg',
+ 'modular_zzplurt/sound/interactions/bj7.ogg',
+ 'modular_zzplurt/sound/interactions/bj8.ogg',
+ 'modular_zzplurt/sound/interactions/bj9.ogg',
+ 'modular_zzplurt/sound/interactions/bj10.ogg',
+ 'modular_zzplurt/sound/interactions/bj11.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 5
+ user_arousal = 3
+ target_arousal = 7
+
+/datum/interaction/lewd/oral_penis
+ name = "Suck Cock"
+ description = "Suck them off."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_TARGET = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "takes %TARGET%'s cock into their mouth.",
+ "wraps their lips around %TARGET%'s cock.",
+ "finds their face between %TARGET%'s thighs.",
+ "kneels down between %TARGET%'s legs.",
+ "grips %TARGET%'s legs, kissing at the tip of their cock.",
+ "goes down on %TARGET%."
+ )
+ user_messages = list(
+ "You feel %TARGET%'s cock throb in your mouth.",
+ "The taste of %TARGET%'s precum lingers on your tongue.",
+ "You take %TARGET% deeper into your throat."
+ )
+ target_messages = list(
+ "%USER%'s tongue swirls around your cock.",
+ "You feel %USER%'s hot mouth envelope you.",
+ "The warmth of %USER%'s throat makes you twitch."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bj1.ogg',
+ 'modular_zzplurt/sound/interactions/bj2.ogg',
+ 'modular_zzplurt/sound/interactions/bj3.ogg',
+ 'modular_zzplurt/sound/interactions/bj4.ogg',
+ 'modular_zzplurt/sound/interactions/bj5.ogg',
+ 'modular_zzplurt/sound/interactions/bj6.ogg',
+ 'modular_zzplurt/sound/interactions/bj7.ogg',
+ 'modular_zzplurt/sound/interactions/bj8.ogg',
+ 'modular_zzplurt/sound/interactions/bj9.ogg',
+ 'modular_zzplurt/sound/interactions/bj10.ogg',
+ 'modular_zzplurt/sound/interactions/bj11.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 5
+ user_arousal = 3
+ target_arousal = 7
+
+/datum/interaction/lewd/oral_penis/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(prob((target.dna.features["sexual_potency"] * 10) + 15))
+ user.adjustOxyLoss(3)
+ target.adjust_arousal(10)
+ target.adjust_pleasure(10, user, interaction = src, position = CLIMAX_POSITION_TARGET)
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/breasts.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/breasts.dm
new file mode 100644
index 0000000000000..24500f85f3221
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/breasts.dm
@@ -0,0 +1,128 @@
+/datum/interaction/lewd/titgrope_self
+ name = "Grope Breasts (self)"
+ description = "Grope your own breasts."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ user_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_ANY)
+ usage = INTERACTION_SELF
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_BOTH)
+ additional_details = list(INTERACTION_FILLS_CONTAINERS)
+ message = list(
+ "gently gropes their breast",
+ "softly squeezes their breasts",
+ "grips their breasts",
+ "runs a few fingers over their breast",
+ "delicately teases their nipple",
+ "traces a touch across their breast"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/squelch1.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 2
+ user_arousal = 3
+
+/datum/interaction/lewd/titgrope_self/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ if(prob(5 + user.arousal))
+ user.visible_message(span_lewd("\The [user] [pick(
+ "shivers in arousal.",
+ "moans quietly.",
+ "breathes out a soft moan.",
+ "gasps.",
+ "shudders softly.",
+ "trembles as their hands run across bare skin.")]"))
+
+ var/obj/item/reagent_containers/liquid_container
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ var/obj/item/organ/external/genital/breasts/breasts = user.get_organ_slot(ORGAN_SLOT_BREASTS)
+ if(breasts?.internal_fluid_datum)
+ // Calculate milk amount based on how full the breasts are (0.5 to 2 multiplier)
+ var/milk_multiplier = 0.5
+ if(breasts.internal_fluid_maximum > 0)
+ milk_multiplier = 0.5 + (1.5 * (breasts.internal_fluid_count / breasts.internal_fluid_maximum))
+
+ var/transfer_amount = rand(1, 3 * milk_multiplier)
+ var/datum/reagents/R = new(breasts.internal_fluid_maximum)
+ breasts.transfer_internal_fluid(R, transfer_amount)
+ R.trans_to(liquid_container, R.total_volume)
+ qdel(R)
+
+/datum/interaction/lewd/self_nipsuck
+ name = "Suck Nipples (self)"
+ description = "Suck your own nipples."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_EXPOSED)
+ usage = INTERACTION_SELF
+ message = list(
+ "brings their own milk tanks to their mouth and sucks deeply into them",
+ "takes a big sip of their own fresh milk",
+ "fills their own mouth with a big gulp of their warm milk"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/oral1.ogg',
+ 'modular_zzplurt/sound/interactions/oral2.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 3
+ user_arousal = 5
+
+/datum/interaction/lewd/self_nipsuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ var/obj/item/organ/external/genital/breasts/breasts = user.get_organ_slot(ORGAN_SLOT_BREASTS)
+ if(breasts?.internal_fluid_datum)
+ // Calculate milk amount based on how full the breasts are (0.5 to 2 multiplier)
+ var/milk_multiplier = 0.5
+ if(breasts.internal_fluid_maximum > 0)
+ milk_multiplier = 0.5 + (1.5 * (breasts.internal_fluid_count / breasts.internal_fluid_maximum))
+
+ var/transfer_amount = rand(1, 3 * milk_multiplier)
+ var/datum/reagents/R = new(breasts.internal_fluid_maximum)
+ breasts.transfer_internal_fluid(R, transfer_amount)
+ R.trans_to(user, R.total_volume)
+ qdel(R)
+
+/datum/interaction/lewd/breastfuck_self
+ name = "Breastfuck (self)"
+ description = "Fuck your own breasts."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ user_required_parts = list(
+ ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED,
+ ORGAN_SLOT_BREASTS = REQUIRE_GENITAL_EXPOSED
+ )
+ usage = INTERACTION_SELF
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums all over their own breasts",
+ "shoots their load onto their tits",
+ "covers their breasts in cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum all over your own breasts",
+ "You shoot your load onto your tits",
+ "You cover your breasts in cum"
+ ))
+ message = list(
+ "fucks their own breasts",
+ "slides their cock between their breasts",
+ "thrusts between their tits",
+ "pleasures themself with their breasts"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ user_arousal = 6
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/finger.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/finger.dm
new file mode 100644
index 0000000000000..f8b5ff5777151
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/finger.dm
@@ -0,0 +1,92 @@
+/datum/interaction/lewd/finger_self_vagina
+ name = "Finger Pussy (self)"
+ description = "Finger your own pussy."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ user_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ usage = INTERACTION_SELF
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_VAGINA)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums hard on their fingers",
+ "shudders as they cum on their hand",
+ "fingers themself to climax"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum hard on your fingers",
+ "You shudder as you cum on your hand",
+ "You finger yourself to climax"
+ ))
+ additional_details = list(INTERACTION_FILLS_CONTAINERS)
+ message = list(
+ "fingers their pussy deep",
+ "fingers their pussy",
+ "plays with their pussy",
+ "fingers their own pussy hard"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ_fingering.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ user_arousal = 6
+
+/datum/interaction/lewd/finger_self_vagina/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/obj/item/reagent_containers/liquid_container
+
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ var/list/original_messages = message.Copy()
+ var/chosen_message = pick(message)
+ message = list("[chosen_message] over \the [liquid_container]")
+ interaction_modifier_flags |= INTERACTION_OVERRIDE_FLUID_TRANSFER
+ . = ..()
+ interaction_modifier_flags &= ~INTERACTION_OVERRIDE_FLUID_TRANSFER
+ message = original_messages
+ else
+ . = ..()
+
+/datum/interaction/lewd/finger_self_vagina/post_climax(mob/living/carbon/human/user, mob/living/carbon/human/target, position)
+ var/obj/item/reagent_containers/liquid_container
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ var/obj/item/organ/external/genital/vagina/vagina = user.get_organ_slot(ORGAN_SLOT_VAGINA)
+ if(vagina?.internal_fluid_datum)
+ var/datum/reagents/R = new(vagina.internal_fluid_maximum)
+ vagina.transfer_internal_fluid(R, vagina.internal_fluid_count)
+ R.trans_to(liquid_container, R.total_volume)
+ qdel(R)
+ . = ..()
+
+/datum/interaction/lewd/finger_self_anus
+ name = "Finger Ass (self)"
+ description = "Finger your own ass."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ user_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ usage = INTERACTION_SELF
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_BOTH)
+ message = list(
+ "fingers themself",
+ "fingers their asshole",
+ "fingers themself hard"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/champ_fingering.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 3
+ user_arousal = 5
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/jack.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/jack.dm
new file mode 100644
index 0000000000000..fa251a5f69d58
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/jack.dm
@@ -0,0 +1,74 @@
+/datum/interaction/lewd/jack_self
+ name = "Jack Off (self)"
+ description = "Jerk yourself off."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ usage = INTERACTION_SELF
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums hard on their hand",
+ "shoots their load onto their fingers",
+ "ejaculates onto their palm"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum hard on your hand",
+ "You shoot your load onto your fingers",
+ "You ejaculate onto your palm"
+ ))
+ additional_details = list(INTERACTION_FILLS_CONTAINERS)
+ message = list(
+ "jerks themself off",
+ "works their shaft",
+ "strokes their cock",
+ "wanks their cock hard"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ user_arousal = 6
+
+/datum/interaction/lewd/jack_self/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/obj/item/reagent_containers/liquid_container
+
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ var/list/original_messages = message.Copy()
+ var/chosen_message = pick(message)
+ message = list("[chosen_message] over \the [liquid_container]")
+ interaction_modifier_flags |= INTERACTION_OVERRIDE_FLUID_TRANSFER
+ . = ..()
+ interaction_modifier_flags &= ~INTERACTION_OVERRIDE_FLUID_TRANSFER
+ message = original_messages
+ else
+ . = ..()
+
+/datum/interaction/lewd/jack_self/post_climax(mob/living/carbon/human/user, mob/living/carbon/human/target, position)
+ var/obj/item/reagent_containers/liquid_container
+ var/obj/item/cached_item = user.get_active_held_item()
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+ else
+ cached_item = user.pulling
+ if(istype(cached_item, /obj/item/reagent_containers))
+ liquid_container = cached_item
+
+ if(liquid_container)
+ var/obj/item/organ/external/genital/penis/penis = user.get_organ_slot(ORGAN_SLOT_PENIS)
+ if(penis?.internal_fluid_datum)
+ var/datum/reagents/R = new(penis.internal_fluid_maximum)
+ penis.transfer_internal_fluid(R, penis.internal_fluid_count)
+ R.trans_to(liquid_container, R.total_volume)
+ qdel(R)
+ . = ..()
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/oral.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/oral.dm
new file mode 100644
index 0000000000000..2334b8b588212
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/self/oral.dm
@@ -0,0 +1,63 @@
+/datum/interaction/lewd/oral_vagina_self
+ name = "Lick pussy (Self)"
+ description = "Lick your own pussy."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ usage = INTERACTION_SELF
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_VAGINA)
+ cum_target = list(CLIMAX_POSITION_USER = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "licks their own pussy.",
+ "pleasures themself with their tongue.",
+ "performs oral on themself.",
+ "licks their own clit eagerly."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bj1.ogg',
+ 'modular_zzplurt/sound/interactions/bj2.ogg',
+ 'modular_zzplurt/sound/interactions/bj3.ogg',
+ 'modular_zzplurt/sound/interactions/bj4.ogg',
+ 'modular_zzplurt/sound/interactions/bj5.ogg',
+ 'modular_zzplurt/sound/interactions/bj6.ogg',
+ 'modular_zzplurt/sound/interactions/bj7.ogg',
+ 'modular_zzplurt/sound/interactions/bj8.ogg',
+ 'modular_zzplurt/sound/interactions/bj9.ogg',
+ 'modular_zzplurt/sound/interactions/bj10.ogg',
+ 'modular_zzplurt/sound/interactions/bj11.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 5
+ user_arousal = 7
+
+/datum/interaction/lewd/oral_self
+ name = "Selfsuck"
+ description = "Suck yourself off."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ usage = INTERACTION_SELF
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "wraps their lips around their own cock.",
+ "pleasures themself with their mouth.",
+ "sucks their own cock eagerly.",
+ "gives themself head."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bj1.ogg',
+ 'modular_zzplurt/sound/interactions/bj2.ogg',
+ 'modular_zzplurt/sound/interactions/bj3.ogg',
+ 'modular_zzplurt/sound/interactions/bj4.ogg',
+ 'modular_zzplurt/sound/interactions/bj5.ogg',
+ 'modular_zzplurt/sound/interactions/bj6.ogg',
+ 'modular_zzplurt/sound/interactions/bj7.ogg',
+ 'modular_zzplurt/sound/interactions/bj8.ogg',
+ 'modular_zzplurt/sound/interactions/bj9.ogg',
+ 'modular_zzplurt/sound/interactions/bj10.ogg',
+ 'modular_zzplurt/sound/interactions/bj11.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 5
+ user_arousal = 7
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/slap.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/slap.dm
new file mode 100644
index 0000000000000..5b77abe03558e
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/slap.dm
@@ -0,0 +1,28 @@
+/datum/interaction/lewd/slap
+ name = "Slap Ass"
+ description = "Slap their ass."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_HAND)
+ target_required_parts = list(ORGAN_SLOT_BUTT = REQUIRE_GENITAL_EXPOSED)
+ message = list(
+ "slaps %TARGET% right on the ass!",
+ "spanks %TARGET%'s ass!",
+ "gives %TARGET%'s behind a good smack!",
+ "lands a stinging slap on %TARGET%'s butt!"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/slap.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ target_pain = 10
+ user_arousal = 2
+ target_arousal = 0
+
+/datum/interaction/lewd/slap/act(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ var/original_pleasure = target_pleasure
+ if(HAS_TRAIT(target, TRAIT_MASOCHISM))
+ target_pleasure = 2
+ . = ..()
+ target_pleasure = original_pleasure
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/thighs.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/thighs.dm
new file mode 100644
index 0000000000000..4027e902bb843
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/thighs.dm
@@ -0,0 +1,147 @@
+/datum/interaction/lewd/thighs_penis
+ name = "Thigh Smother (Penis)"
+ description = "Smother them with your penis."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_target = list(CLIMAX_POSITION_USER = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "presses their weight down onto %TARGET%'s face, blocking their vision completely.",
+ "forces their cock into %TARGET%'s face as they're stuck locked between their thighs.",
+ "slips their cock into %TARGET%'s helpless mouth, keeping their shaft pressed hard into their face."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bj10.ogg',
+ 'modular_zzplurt/sound/interactions/bj3.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_dry3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ target_pleasure = 0
+ user_arousal = 6
+ target_arousal = 2
+
+/datum/interaction/lewd/thighs_vagina
+ name = "Thigh Smother (Vagina)"
+ description = "Smother them with your pussy."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_VAGINA = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_VAGINA)
+ cum_target = list(CLIMAX_POSITION_USER = CLIMAX_TARGET_MOUTH)
+ message = list(
+ "presses their weight down onto %TARGET%'s face, blocking their vision completely.",
+ "rides %TARGET%'s face, grinding their wet pussy all over it.",
+ "grinds their pussy into %TARGET%'s face."
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bj10.ogg',
+ 'modular_zzplurt/sound/interactions/bj3.ogg',
+ 'modular_zzplurt/sound/interactions/foot_wet1.ogg',
+ 'modular_zzplurt/sound/interactions/foot_dry3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ target_pleasure = 0
+ user_arousal = 6
+ target_arousal = 2
+
+/datum/interaction/lewd/thighfuck
+ name = "Thighfuck"
+ description = "Fuck their thighs."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_BOTTOMLESS)
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_USER = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "cums all over %TARGET%'s thighs",
+ "shoots their load onto %TARGET%'s legs",
+ "covers %TARGET%'s thighs in cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "You cum all over %TARGET%'s thighs",
+ "You shoot your load onto %TARGET%'s legs",
+ "You cover %TARGET%'s thighs in cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_USER = list(
+ "%USER% cums all over your thighs",
+ "%USER% shoots their load onto your legs",
+ "%USER% covers your thighs in cum"
+ ))
+ message = list(
+ "fucks %TARGET%'s thighs",
+ "slides their cock between %TARGET%'s legs",
+ "thrusts between %TARGET%'s thighs",
+ "pounds against %TARGET%'s legs"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s thighs squeezing your cock",
+ "The warmth between %TARGET%'s legs feels amazing",
+ "%TARGET%'s soft thighs feel great around your shaft"
+ )
+ target_messages = list(
+ "You feel %USER%'s cock sliding between your thighs",
+ "%USER%'s shaft rubs between your legs",
+ "The warmth of %USER%'s cock presses against your thighs"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 4
+ target_pleasure = 0
+ user_arousal = 6
+ target_arousal = 4
+
+/datum/interaction/lewd/thighjob
+ name = "Give Thighjob"
+ description = "Pleasure them with your thighs."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_BOTTOMLESS)
+ target_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(CLIMAX_POSITION_TARGET = CLIMAX_PENIS)
+ cum_message_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "cums all over %USER%'s thighs",
+ "shoots their load onto %USER%'s legs",
+ "covers %USER%'s thighs in cum"
+ ))
+ cum_self_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "%TARGET% cums all over your thighs",
+ "%TARGET% shoots their load onto your legs",
+ "%TARGET% covers your thighs in cum"
+ ))
+ cum_partner_text_overrides = list(CLIMAX_POSITION_TARGET = list(
+ "You cum all over %USER%'s thighs",
+ "You shoot your load onto %USER%'s legs",
+ "You cover %USER%'s thighs in cum"
+ ))
+ message = list(
+ "squeezes %TARGET%'s cock between their thighs",
+ "works %TARGET%'s shaft with their legs",
+ "pleasures %TARGET% with their thighs",
+ "rubs %TARGET%'s cock between their legs"
+ )
+ user_messages = list(
+ "You feel %TARGET%'s cock throbbing between your thighs",
+ "The warmth of %TARGET%'s shaft feels nice between your legs",
+ "You squeeze %TARGET%'s cock with your thighs"
+ )
+ target_messages = list(
+ "%USER%'s warm thighs squeeze your cock",
+ "Your shaft slides between %USER%'s legs",
+ "The softness of %USER%'s thighs feels amazing"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 4
+ user_arousal = 4
+ target_arousal = 6
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/_unholy.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/_unholy.dm
new file mode 100644
index 0000000000000..57edf6214a4c7
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/_unholy.dm
@@ -0,0 +1,4 @@
+/datum/interaction/lewd/unholy
+ unsafe_types = INTERACTION_UNHOLY
+ color = "brown"
+ category = INTERACTION_CAT_UNHOLY
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/fart.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/fart.dm
new file mode 100644
index 0000000000000..733d447c72b8f
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/fart.dm
@@ -0,0 +1,151 @@
+/datum/interaction/lewd/unholy/facefart
+ name = "Face Fart"
+ description = "Fart on their face."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_ANY)
+ message = list(
+ "presses their ass against %TARGET%'s face and lets out a fart",
+ "releases gas right into %TARGET%'s face",
+ "lets loose a nasty fart on %TARGET%'s face",
+ "forces %TARGET% to smell their flatulence"
+ )
+ user_messages = list(
+ "You feel relief as you release gas in %TARGET%'s face",
+ "You let out a fart right on %TARGET%'s face",
+ "You make %TARGET% smell your gas"
+ )
+ target_messages = list(
+ "%USER% releases their gas right in your face",
+ "You're forced to smell %USER%'s fart",
+ "%USER%'s flatulence fills your nostrils"
+ )
+ sound_possible = list()
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 2
+ target_arousal = 2
+
+/datum/interaction/lewd/unholy/facefart/New()
+ sound_possible = GLOB.brap_noises // GLOB.brap_noises: expected a constant expression
+ . = ..()
+
+/datum/interaction/lewd/unholy/crotchfart
+ name = "Crotch Fart"
+ description = "Fart on their crotch."
+ user_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_ANY)
+ message = list(
+ "presses their ass against %TARGET%'s crotch and lets out a fart",
+ "releases gas all over %TARGET%'s groin",
+ "lets loose a nasty fart on %TARGET%'s genitals",
+ "forces %TARGET% to feel their flatulence"
+ )
+ user_messages = list(
+ "You feel relief as you release gas on %TARGET%'s crotch",
+ "You let out a fart right on %TARGET%'s groin",
+ "You make %TARGET% feel your gas"
+ )
+ target_messages = list(
+ "%USER% releases their gas right on your crotch",
+ "You feel %USER%'s fart on your groin",
+ "%USER%'s flatulence washes over your genitals"
+ )
+ sound_possible = list()
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 2
+ user_arousal = 3
+ target_arousal = 4
+
+/datum/interaction/lewd/unholy/crotchfart/New()
+ sound_possible = GLOB.brap_noises // GLOB.brap_noises: expected a constant expression
+ . = ..()
+
+/datum/interaction/lewd/unholy/fartfuck
+ name = "Fart Fuck"
+ description = "Fuck their ass + fart."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(
+ CLIMAX_POSITION_USER = CLIMAX_PENIS,
+ CLIMAX_POSITION_TARGET = CLIMAX_BOTH
+ )
+ cum_target = list(
+ CLIMAX_POSITION_USER = ORGAN_SLOT_ANUS
+ )
+ message = list(
+ "pounds %TARGET%'s ass while letting out farts",
+ "releases gas while fucking %TARGET%'s hole",
+ "farts with each thrust into %TARGET%'s ass",
+ "fills %TARGET% with their flatulence while fucking them"
+ )
+ user_messages = list(
+ "You feel relief as you release gas while fucking %TARGET%",
+ "You let out farts as you pound %TARGET%'s ass",
+ "You make %TARGET% feel your gas inside them"
+ )
+ target_messages = list(
+ "%USER% releases their gas inside your ass",
+ "You feel %USER%'s farts filling you up",
+ "%USER%'s flatulence mingles with their thrusts"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_possible = list()
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 6
+ target_pleasure = 4
+ user_arousal = 8
+ target_arousal = 6
+
+/datum/interaction/lewd/unholy/fartfuck/New()
+ sound_possible = GLOB.brap_noises // GLOB.brap_noises: expected a constant expression
+ . = ..()
+
+/datum/interaction/lewd/unholy/fartfuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ conditional_pref_sound(user, pick('modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'), 80, TRUE, falloff_distance = sound_range, pref_to_check = /datum/preference/toggle/erp/sounds)
+
+/datum/interaction/lewd/unholy/suck_fart
+ name = "Suck Fart"
+ description = "Suck the farts out of their asshole."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ target_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(
+ CLIMAX_POSITION_TARGET = CLIMAX_BOTH
+ )
+ message = list(
+ "presses their face between %TARGET%'s asscheeks and inhales their farts",
+ "sucks the gas right out of %TARGET%'s ass",
+ "breathes in %TARGET%'s flatulence deeply",
+ "puts their mouth on %TARGET%'s hole to taste their gas"
+ )
+ user_messages = list(
+ "You inhale %TARGET%'s farts directly from the source",
+ "You suck the gas from %TARGET%'s asshole",
+ "You taste %TARGET%'s flatulence on your tongue"
+ )
+ target_messages = list(
+ "%USER% sucks your farts right out of your ass",
+ "You feel %USER%'s mouth pulling gas from your hole",
+ "%USER% inhales your flatulence eagerly"
+ )
+ sound_possible = list()
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 3
+ user_arousal = 4
+ target_arousal = 5
+
+/datum/interaction/lewd/unholy/suck_fart/New()
+ sound_possible = GLOB.brap_noises // GLOB.brap_noises: expected a constant expression
+ . = ..()
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/piss.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/piss.dm
new file mode 100644
index 0000000000000..f536e6a2d0444
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/piss.dm
@@ -0,0 +1,56 @@
+/datum/interaction/lewd/unholy/piss_over
+ name = "Piss Over"
+ description = "Piss all over them."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_BOTTOMLESS)
+ message = list(
+ "relieves themselves all over %TARGET%",
+ "marks their territory on %TARGET%",
+ "releases their bladder onto %TARGET%",
+ "pisses all over %TARGET%"
+ )
+ user_messages = list(
+ "You feel relief as you release onto %TARGET%",
+ "You empty your bladder on %TARGET%",
+ "You mark %TARGET% with your urine"
+ )
+ target_messages = list(
+ "%USER% pisses all over you",
+ "You feel %USER%'s warm urine splash on you",
+ "%USER% marks you as their territory"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 2
+ target_arousal = 2
+
+/datum/interaction/lewd/unholy/piss_mouth
+ name = "Piss Mouth"
+ description = "Piss inside their mouth."
+ interaction_requires = list(
+ INTERACTION_REQUIRE_SELF_BOTTOMLESS,
+ INTERACTION_REQUIRE_TARGET_MOUTH
+ )
+ message = list(
+ "relieves themselves into %TARGET%'s mouth",
+ "fills %TARGET%'s mouth with piss",
+ "releases their bladder down %TARGET%'s throat",
+ "uses %TARGET%'s mouth as their urinal"
+ )
+ user_messages = list(
+ "You feel relief as you release into %TARGET%'s mouth",
+ "You empty your bladder down %TARGET%'s throat",
+ "You make %TARGET% drink your piss"
+ )
+ target_messages = list(
+ "%USER% pisses right into your mouth",
+ "You're forced to swallow %USER%'s urine",
+ "%USER% uses your mouth as their urinal"
+ )
+ sound_range = 1
+ sound_use = FALSE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 3
+ target_arousal = 3
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/scat.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/scat.dm
new file mode 100644
index 0000000000000..2efb7ef956d58
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/interactions/lewd/unholy/scat.dm
@@ -0,0 +1,150 @@
+/datum/interaction/lewd/unholy/faceshit
+ name = "Face Shit"
+ description = "Shit on their face."
+ interaction_requires = list(INTERACTION_REQUIRE_TARGET_MOUTH)
+ user_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_ANY)
+ message = list(
+ "squats over %TARGET%'s face and releases their bowels",
+ "defecates right onto %TARGET%'s face",
+ "lets loose their filth on %TARGET%'s face",
+ "forces %TARGET% to experience their mess"
+ )
+ user_messages = list(
+ "You feel relief as you release onto %TARGET%'s face",
+ "You empty your bowels on %TARGET%'s face",
+ "You make %TARGET% deal with your mess"
+ )
+ target_messages = list(
+ "%USER% releases their filth right on your face",
+ "You're forced to experience %USER%'s mess",
+ "%USER%'s waste covers your face"
+ )
+ sound_possible = list()
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 2
+ target_arousal = 2
+
+/datum/interaction/lewd/unholy/faceshit/New()
+ sound_possible = GLOB.brap_noises // GLOB.brap_noises: expected a constant expression
+ . = ..()
+
+/datum/interaction/lewd/unholy/crotchshit
+ name = "Crotch Shit"
+ description = "Shit on their crotch."
+ user_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_ANY)
+ message = list(
+ "squats over %TARGET%'s crotch and releases their bowels",
+ "defecates all over %TARGET%'s groin",
+ "lets loose their filth on %TARGET%'s genitals",
+ "forces %TARGET% to feel their mess"
+ )
+ user_messages = list(
+ "You feel relief as you release onto %TARGET%'s crotch",
+ "You empty your bowels on %TARGET%'s groin",
+ "You make %TARGET% deal with your mess"
+ )
+ target_messages = list(
+ "%USER% releases their filth right on your crotch",
+ "You feel %USER%'s mess on your groin",
+ "%USER%'s waste covers your genitals"
+ )
+ sound_possible = list()
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 0
+ user_arousal = 2
+ target_arousal = 2
+
+/datum/interaction/lewd/unholy/crotchshit/New()
+ sound_possible = GLOB.brap_noises // GLOB.brap_noises: expected a constant expression
+ . = ..()
+
+/datum/interaction/lewd/unholy/shitfuck
+ name = "Shit Fuck"
+ description = "Fuck their ass + shit."
+ user_required_parts = list(ORGAN_SLOT_PENIS = REQUIRE_GENITAL_EXPOSED)
+ target_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(
+ CLIMAX_POSITION_USER = CLIMAX_PENIS,
+ CLIMAX_POSITION_TARGET = CLIMAX_BOTH
+ )
+ cum_target = list(
+ CLIMAX_POSITION_USER = ORGAN_SLOT_ANUS
+ )
+ message = list(
+ "pounds %TARGET%'s ass while they release their bowels",
+ "fucks %TARGET%'s hole as they defecate",
+ "thrusts into %TARGET%'s ass as they make a mess",
+ "fills %TARGET% with their cock while they empty themselves"
+ )
+ user_messages = list(
+ "You feel %TARGET% releasing as you fuck them",
+ "You pound %TARGET%'s ass as they make a mess",
+ "You make %TARGET% feel full while they empty themselves"
+ )
+ target_messages = list(
+ "You release your bowels as %USER% fucks you",
+ "You feel %USER%'s cock while you make a mess",
+ "You empty yourself as %USER% fills you up"
+ )
+ sound_possible = list(
+ 'modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'
+ )
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 6
+ target_pleasure = 4
+ user_arousal = 8
+ target_arousal = 6
+
+/datum/interaction/lewd/unholy/shitfuck/New()
+ sound_possible = GLOB.brap_noises // GLOB.brap_noises: expected a constant expression
+ . = ..()
+
+/datum/interaction/lewd/unholy/shitfuck/post_interaction(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ . = ..()
+ conditional_pref_sound(user, pick('modular_zzplurt/sound/interactions/bang1.ogg',
+ 'modular_zzplurt/sound/interactions/bang2.ogg',
+ 'modular_zzplurt/sound/interactions/bang3.ogg'), 80, TRUE, falloff_distance = sound_range, pref_to_check = /datum/preference/toggle/erp/sounds)
+
+/datum/interaction/lewd/unholy/suck_shit
+ name = "Suck Shit"
+ description = "Suck the shit out of their asshole."
+ interaction_requires = list(INTERACTION_REQUIRE_SELF_MOUTH)
+ target_required_parts = list(ORGAN_SLOT_ANUS = REQUIRE_GENITAL_EXPOSED)
+ cum_genital = list(
+ CLIMAX_POSITION_TARGET = CLIMAX_BOTH
+ )
+ message = list(
+ "presses their face between %TARGET%'s asscheeks and sucks out their filth",
+ "eagerly consumes %TARGET%'s mess directly from the source",
+ "feeds on %TARGET%'s waste eagerly",
+ "puts their mouth on %TARGET%'s hole to taste their mess"
+ )
+ user_messages = list(
+ "You consume %TARGET%'s waste directly from the source",
+ "You suck the filth from %TARGET%'s asshole",
+ "You taste %TARGET%'s mess on your tongue"
+ )
+ target_messages = list(
+ "%USER% sucks your waste right out of your ass",
+ "You feel %USER%'s mouth pulling your mess from your hole",
+ "%USER% eagerly consumes your waste"
+ )
+ sound_possible = list()
+ sound_range = 1
+ sound_use = TRUE
+ user_pleasure = 0
+ target_pleasure = 3
+ user_arousal = 4
+ target_arousal = 5
+
+/datum/interaction/lewd/unholy/suck_shit/New()
+ sound_possible = GLOB.brap_noises // GLOB.brap_noises: expected a constant expression
+ . = ..()
diff --git a/modular_zzplurt/code/modules/lewd/interaction_menu/lewd_helpers/human.dm b/modular_zzplurt/code/modules/lewd/interaction_menu/lewd_helpers/human.dm
new file mode 100644
index 0000000000000..984f8d644cacb
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd/interaction_menu/lewd_helpers/human.dm
@@ -0,0 +1,46 @@
+
+/// Helper proc that checks if a human has a genital of a specific type and exposure state
+/mob/living/carbon/human/proc/has_genital(required_state = REQUIRE_GENITAL_ANY, genital_slot)
+ var/obj/item/organ/external/genital/genital = get_organ_slot(genital_slot)
+ if(!genital)
+ return FALSE
+
+ switch(required_state)
+ if(REQUIRE_GENITAL_ANY)
+ return TRUE
+ if(REQUIRE_GENITAL_EXPOSED)
+ return genital.is_exposed()
+ if(REQUIRE_GENITAL_UNEXPOSED)
+ return !genital.is_exposed()
+ else
+ return TRUE
+
+/// Returns true if the human has an accessible penis for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
+/mob/living/carbon/human/proc/has_penis(required_state = REQUIRE_GENITAL_ANY)
+ return has_genital(required_state, ORGAN_SLOT_PENIS)
+
+/// Returns true if the human has accessible balls for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
+/mob/living/carbon/human/proc/has_balls(required_state = REQUIRE_GENITAL_ANY)
+ return has_genital(required_state, ORGAN_SLOT_TESTICLES)
+
+/// Returns true if the human has an accessible vagina for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
+/mob/living/carbon/human/proc/has_vagina(required_state = REQUIRE_GENITAL_ANY)
+ return has_genital(required_state, ORGAN_SLOT_VAGINA)
+
+/// Returns true if the human has accessible breasts for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
+/mob/living/carbon/human/proc/has_breasts(required_state = REQUIRE_GENITAL_ANY)
+ return has_genital(required_state, ORGAN_SLOT_BREASTS)
+
+/// Returns true if the human has an accessible anus for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
+/mob/living/carbon/human/proc/has_anus(required_state = REQUIRE_GENITAL_ANY)
+ if(issilicon(src))
+ return TRUE
+ return has_genital(required_state, ORGAN_SLOT_ANUS)
+
+/// Returns true if the human has an accessible butt for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
+/mob/living/carbon/human/proc/has_butt(required_state = REQUIRE_GENITAL_ANY)
+ return has_genital(required_state, ORGAN_SLOT_BUTT)
+
+/// Returns true if the human has an accessible belly for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
+/mob/living/carbon/human/proc/has_belly(required_state = REQUIRE_GENITAL_ANY)
+ return has_genital(required_state, ORGAN_SLOT_BELLY)
diff --git a/modular_zzplurt/code/modules/lewd_chemistry/reagents/_aphrodisiac.dm b/modular_zzplurt/code/modules/lewd_chemistry/reagents/_aphrodisiac.dm
new file mode 100644
index 0000000000000..36db711263d01
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd_chemistry/reagents/_aphrodisiac.dm
@@ -0,0 +1,195 @@
+/datum/reagent/drug/aphrodisiac
+ /// Largest size the chem can make a mob's butt
+ var/butt_max_size = 8
+ /// How much the butt is increased in size each time it's run
+ var/butt_size_increase_step = 1
+ /// Smallest size the chem can make a mob's butt
+ var/butt_minimum_size = 1
+ /// How much to reduce the size of the butt each time it's run
+ var/butt_size_reduction_step = 1
+
+ /// Largest size the chem can make a mob's belly
+ var/belly_max_size = 7
+ /// How much the belly is increased in size each time it's run
+ var/belly_size_increase_step = 1
+ /// Smallest size the chem can make a mob's belly
+ var/belly_minimum_size = 1
+ /// How much to reduce the size of the belly each time it's run
+ var/belly_size_reduction_step = 1
+
+ // Blud didn't even undefine the indexes in nonmodular code 💀💀💀
+ #define GENITAL_BELLY 6
+ #define GENITAL_BUTT 7
+
+/**
+* Handle belly growth
+*
+* exposed_mob - the mob being affected by the reagent
+* suppress_chat - whether or not to display a message in chat
+* mob_belly - the belly to cause to grow
+*/
+/datum/reagent/drug/aphrodisiac/proc/grow_belly(mob/living/carbon/human/exposed_mob, suppress_chat = FALSE, obj/item/organ/external/genital/belly/mob_belly = exposed_mob?.get_organ_slot(ORGAN_SLOT_BELLY))
+ if(!mob_belly)
+ return
+
+ if(!exposed_mob.client?.prefs?.read_preference(/datum/preference/toggle/erp/belly_enlargement))
+ return
+
+ enlargement_amount += enlarger_increase_step
+
+ if(enlargement_amount >= enlargement_threshold)
+ if(mob_belly?.genital_size >= belly_max_size)
+ return
+
+ mob_belly.genital_size = min(mob_belly.genital_size + belly_size_increase_step, belly_max_size)
+ update_appearance(exposed_mob, mob_belly)
+ enlargement_amount = 0
+
+ growth_to_chat(exposed_mob, mob_belly, suppress_chat)
+
+ // Damage from being too big for clothes
+ if((mob_belly?.genital_size >= (belly_max_size - 2)) && (exposed_mob.w_uniform || exposed_mob.wear_suit))
+ if(prob(damage_chance))
+ to_chat(exposed_mob, span_danger("Your belly strains against your clothes!"))
+ exposed_mob.adjustOxyLoss(5)
+ exposed_mob.apply_damage(1, BRUTE, exposed_mob.get_bodypart(BODY_ZONE_CHEST))
+
+/**
+* Handle butt growth
+*
+* exposed_mob - the mob being affected by the reagent
+* suppress_chat - whether or not to display a message in chat
+* mob_butt - the butt to cause to grow
+*/
+/datum/reagent/drug/aphrodisiac/proc/grow_butt(mob/living/carbon/human/exposed_mob, suppress_chat = FALSE, obj/item/organ/external/genital/butt/mob_butt = exposed_mob?.get_organ_slot(ORGAN_SLOT_BUTT))
+ if(!mob_butt)
+ return
+
+ if(!exposed_mob.client?.prefs?.read_preference(/datum/preference/toggle/erp/butt_enlargement))
+ return
+
+ enlargement_amount += enlarger_increase_step
+
+ if(enlargement_amount >= enlargement_threshold)
+ if(mob_butt?.genital_size >= butt_max_size)
+ return
+
+ mob_butt.genital_size = min(mob_butt.genital_size + butt_size_increase_step, butt_max_size)
+ update_appearance(exposed_mob, mob_butt)
+ enlargement_amount = 0
+
+ growth_to_chat(exposed_mob, mob_butt, suppress_chat)
+
+ // Damage from being too big for clothes
+ if((mob_butt?.genital_size >= (butt_max_size - 2)) && (exposed_mob.w_uniform || exposed_mob.wear_suit))
+ if(prob(damage_chance))
+ to_chat(exposed_mob, span_danger("Your bottom strains against your clothes!"))
+ exposed_mob.apply_damage(1, BRUTE, exposed_mob.get_bodypart(BODY_ZONE_PRECISE_GROIN))
+
+/**
+* Handle belly creation
+*
+* exposed_mob - the mob being affected by the reagent
+* suppress_chat - whether or not to display a message in chat
+* mob_belly - the mob's belly
+*/
+/datum/reagent/drug/aphrodisiac/proc/create_belly(mob/living/carbon/human/exposed_mob, suppress_chat = FALSE, obj/item/organ/external/genital/belly/mob_belly = exposed_mob?.get_organ_slot(ORGAN_SLOT_BELLY))
+ if(mob_belly)
+ return
+
+ if(!exposed_mob.client?.prefs?.read_preference(/datum/preference/toggle/erp/new_genitalia_growth))
+ return
+
+ var/obj/item/organ/external/genital/belly/new_belly = new
+ new_belly.build_from_dna(exposed_mob.dna, ORGAN_SLOT_BELLY)
+ new_belly.Insert(exposed_mob, 0, FALSE)
+ new_belly.genital_size = belly_minimum_size
+ update_appearance(exposed_mob, new_belly)
+
+ if(!suppress_chat)
+ to_chat(exposed_mob, span_purple("Your midsection feels warm as it begins to expand outward."))
+
+/**
+* Handle butt creation
+*
+* exposed_mob - the mob being affected by the reagent
+* suppress_chat - whether or not to display a message in chat
+* mob_butt - the mob's butt
+*/
+/datum/reagent/drug/aphrodisiac/proc/create_butt(mob/living/carbon/human/exposed_mob, suppress_chat = FALSE, obj/item/organ/external/genital/butt/mob_butt = exposed_mob?.get_organ_slot(ORGAN_SLOT_BUTT))
+ if(mob_butt)
+ return
+
+ if(!exposed_mob.client?.prefs?.read_preference(/datum/preference/toggle/erp/new_genitalia_growth))
+ return
+
+ var/obj/item/organ/external/genital/butt/new_butt = new
+ new_butt.build_from_dna(exposed_mob.dna, ORGAN_SLOT_BUTT)
+ new_butt.Insert(exposed_mob, 0, FALSE)
+ new_butt.genital_size = butt_minimum_size
+ update_appearance(exposed_mob, new_butt)
+
+ if(!suppress_chat)
+ to_chat(exposed_mob, span_purple("Your bottom feels warm as it begins to expand outward."))
+
+/**
+* Handle belly shrinkage
+*
+* exposed_mob - the mob being affected by the reagent
+* suppress_chat - whether or not to display a message in chat
+* mob_belly - the belly to shrink
+* message - the message to send to chat
+*/
+/datum/reagent/drug/aphrodisiac/proc/shrink_belly(mob/living/carbon/human/exposed_mob, suppress_chat = FALSE, obj/item/organ/external/genital/belly/mob_belly = exposed_mob?.get_organ_slot(ORGAN_SLOT_BELLY))
+ if(!mob_belly)
+ return
+
+ if(!exposed_mob.client?.prefs?.read_preference(/datum/preference/toggle/erp/belly_shrinkage))
+ return
+
+ if(mob_belly.genital_size > belly_minimum_size)
+ mob_belly.genital_size = max(mob_belly.genital_size - belly_size_reduction_step, belly_minimum_size)
+ update_appearance(exposed_mob, mob_belly)
+
+ else if(mob_belly.genital_size == belly_minimum_size)
+ var/message = "Your belly has completely flattened out."
+ remove_genital(exposed_mob, mob_belly, suppress_chat, message)
+
+/**
+* Handle butt shrinkage
+*
+* exposed_mob - the mob being affected by the reagent
+* suppress_chat - whether or not to display a message in chat
+* mob_butt - the butt to shrink
+* message - the message to send to chat
+*/
+/datum/reagent/drug/aphrodisiac/proc/shrink_butt(mob/living/carbon/human/exposed_mob, suppress_chat = FALSE, obj/item/organ/external/genital/butt/mob_butt = exposed_mob?.get_organ_slot(ORGAN_SLOT_BUTT))
+ if(!mob_butt)
+ return
+
+ if(!exposed_mob.client?.prefs?.read_preference(/datum/preference/toggle/erp/butt_shrinkage))
+ return
+
+ if(mob_butt.genital_size > butt_minimum_size)
+ mob_butt.genital_size = max(mob_butt.genital_size - butt_size_reduction_step, butt_minimum_size)
+ update_appearance(exposed_mob, mob_butt)
+
+ else if(mob_butt.genital_size == butt_minimum_size)
+ var/message = "Your bottom has completely flattened out."
+ remove_genital(exposed_mob, mob_butt, suppress_chat, message)
+
+/**
+* Handle shrinkage of genitals
+*
+* exposed_mob - the mob being affected by the reagent
+* suppress_chat - whether or not to display a message in chat
+* genitals_to_shrink - the genitals to shrink
+*/
+/datum/reagent/drug/aphrodisiac/shrink_genitals(mob/living/carbon/human/exposed_mob, suppress_chat, list/genitals_to_shrink)
+ . = ..()
+ for(var/mob_genital in genitals_to_shrink)
+ switch(mob_genital)
+ if(GENITAL_BELLY)
+ shrink_belly(exposed_mob, suppress_chat)
+ if(GENITAL_BUTT)
+ shrink_butt(exposed_mob, suppress_chat)
diff --git a/modular_zzplurt/code/modules/lewd_chemistry/reagents/belly_enlarger.dm b/modular_zzplurt/code/modules/lewd_chemistry/reagents/belly_enlarger.dm
new file mode 100644
index 0000000000000..571241d817dd4
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd_chemistry/reagents/belly_enlarger.dm
@@ -0,0 +1,78 @@
+/*
+* BELLY ENLARGEMENT
+* Normal function increases the player's belly size
+* If the player's belly is near or at the maximum size and they're wearing clothing, they press against the player's clothes and causes brute damage
+*/
+
+/datum/reagent/drug/aphrodisiac/belly_enlarger
+ name = "belladine nectar"
+ description = "A volatile collodial mixture that encourages abdominal expansion via a potent gastric mix."
+ color = "#01ff63"
+ taste_description = "blueberry gum"
+ overdose_threshold = 17
+ metabolization_rate = 0.25
+ life_pref_datum = /datum/preference/toggle/erp/belly_enlargement
+ overdose_pref_datum = /datum/preference/toggle/erp
+
+ /// Words for the belly when huge
+ var/static/list/words_for_bigger = list(
+ "huge",
+ "massive",
+ "distended",
+ "bloated",
+ "swollen",
+ "rather large",
+ "jiggly",
+ "hefty"
+ )
+
+ /// Wording chosen to expand the belly, shown only to the mob
+ var/static/list/action_text_list = list(
+ "expands outward to ",
+ "swells out to ",
+ "begins to bloat, growing to ",
+ "suddenly distends to ",
+ "inflates to "
+ )
+
+ /// Wording chosen to be seen by other mobs
+ var/static/list/public_action_text_list = list(
+ "expands outward.",
+ "seems to grow larger.",
+ "appears more distended than before.",
+ "suddenly swells up."
+ )
+
+/datum/reagent/drug/aphrodisiac/belly_enlarger/life_effects(mob/living/carbon/human/exposed_mob)
+ if(!ishuman(exposed_mob))
+ return
+
+ var/obj/item/organ/external/genital/belly/mob_belly = exposed_mob.get_organ_slot(ORGAN_SLOT_BELLY)
+
+ // Create belly if they don't have one
+ if(!mob_belly)
+ create_belly(exposed_mob)
+ return
+
+ // Grow existing belly
+ grow_belly(exposed_mob)
+
+/datum/reagent/drug/aphrodisiac/belly_enlarger/growth_to_chat(mob/living/carbon/human/exposed_mob, obj/item/organ/external/genital/belly/mob_belly)
+ if(!mob_belly)
+ return
+
+ if(mob_belly.genital_size >= (belly_max_size - 2))
+ exposed_mob.visible_message(span_notice("[exposed_mob]'s [pick(words_for_bigger)] belly [pick(public_action_text_list)]"))
+ to_chat(exposed_mob, span_purple("Your [pick(words_for_bigger)] belly [pick(action_text_list)]size [mob_belly.genital_size]."))
+ else
+ exposed_mob.visible_message(span_notice("[exposed_mob]'s belly [pick(public_action_text_list)]"))
+ to_chat(exposed_mob, span_purple("Your belly [pick(action_text_list)]size [mob_belly.genital_size]."))
+
+/datum/chemical_reaction/belly_enlarger
+ results = list(/datum/reagent/drug/aphrodisiac/belly_enlarger = 8)
+ required_reagents = list(/datum/reagent/medicine/salglu_solution = 1,
+ /datum/reagent/consumable/nutriment = 3,
+ /datum/reagent/silicon = 2,
+ /datum/reagent/drug/aphrodisiac/crocin = 2)
+ mix_message = "the reaction gives off a sweet aroma."
+ erp_reaction = TRUE
diff --git a/modular_zzplurt/code/modules/lewd_chemistry/reagents/butt_enlarger.dm b/modular_zzplurt/code/modules/lewd_chemistry/reagents/butt_enlarger.dm
new file mode 100644
index 0000000000000..30d8fbbbedcbc
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd_chemistry/reagents/butt_enlarger.dm
@@ -0,0 +1,77 @@
+/*
+* BUTT ENLARGEMENT
+* Normal function increases the player's butt size
+* If the player's butt is near or at the maximum size and they're wearing clothing, they press against the player's clothes and causes brute damage
+*/
+
+/datum/reagent/drug/aphrodisiac/butt_enlarger
+ name = "denbu tincture"
+ description = "A mixture of natural vitamins and valentines plant extract, causing butt enlargement in humanoids."
+ color = "#e8ff1b"
+ taste_description = "butter with a sweet aftertaste"
+ overdose_threshold = 17
+ metabolization_rate = 0.25
+ life_pref_datum = /datum/preference/toggle/erp/butt_enlargement
+ overdose_pref_datum = /datum/preference/toggle/erp
+
+ /// Words for the butt when huge
+ var/static/list/words_for_bigger = list(
+ "huge",
+ "massive",
+ "jiggly",
+ "hefty",
+ "plump",
+ "thick",
+ "bubbling"
+ )
+
+ /// Wording chosen to expand the butt, shown only to the mob
+ var/static/list/action_text_list = list(
+ "expands outward to ",
+ "grows out to ",
+ "begins to plump up, growing to ",
+ "suddenly expands to ",
+ "swells out to "
+ )
+
+ /// Wording chosen to be seen by other mobs
+ var/static/list/public_action_text_list = list(
+ "expands outward.",
+ "seems to grow larger.",
+ "appears more plump than before.",
+ "suddenly swells up."
+ )
+
+/datum/reagent/drug/aphrodisiac/butt_enlarger/life_effects(mob/living/carbon/human/exposed_mob)
+ if(!ishuman(exposed_mob))
+ return
+
+ var/obj/item/organ/external/genital/butt/mob_butt = exposed_mob.get_organ_slot(ORGAN_SLOT_BUTT)
+
+ // Create butt if they don't have one
+ if(!mob_butt)
+ create_butt(exposed_mob)
+ return
+
+ // Grow existing butt
+ grow_butt(exposed_mob)
+
+/datum/reagent/drug/aphrodisiac/butt_enlarger/growth_to_chat(mob/living/carbon/human/exposed_mob, obj/item/organ/external/genital/butt/mob_butt)
+ if(!mob_butt)
+ return
+
+ if(mob_butt.genital_size >= (butt_max_size - 2))
+ exposed_mob.visible_message(span_notice("[exposed_mob]'s [pick(words_for_bigger)] bottom [pick(public_action_text_list)]"))
+ to_chat(exposed_mob, span_purple("Your [pick(words_for_bigger)] bottom [pick(action_text_list)]size [mob_butt.genital_size]."))
+ else
+ exposed_mob.visible_message(span_notice("[exposed_mob]'s bottom [pick(public_action_text_list)]"))
+ to_chat(exposed_mob, span_purple("Your bottom [pick(action_text_list)]size [mob_butt.genital_size]."))
+
+/datum/chemical_reaction/butt_enlarger
+ results = list(/datum/reagent/drug/aphrodisiac/butt_enlarger = 8)
+ required_reagents = list(/datum/reagent/medicine/salglu_solution = 1,
+ /datum/reagent/consumable/sugar = 2,
+ /datum/reagent/silicon = 3,
+ /datum/reagent/drug/aphrodisiac/crocin = 2)
+ mix_message = "the reaction gives off a sweet buttery aroma."
+ erp_reaction = TRUE
diff --git a/modular_zzplurt/code/modules/lewd_chemistry/reagents/camphor.dm b/modular_zzplurt/code/modules/lewd_chemistry/reagents/camphor.dm
new file mode 100644
index 0000000000000..7f6ce8bcbdba7
--- /dev/null
+++ b/modular_zzplurt/code/modules/lewd_chemistry/reagents/camphor.dm
@@ -0,0 +1,32 @@
+/datum/reagent/drug/aphrodisiac/camphor/overdose_effects(mob/living/carbon/human/exposed_mob)
+ . = ..() // Call parent to handle base genital resets
+ var/modified_genitals = FALSE
+
+ if(exposed_mob.get_organ_slot(ORGAN_SLOT_BELLY))
+ var/obj/item/organ/external/genital/belly/mob_belly = exposed_mob.get_organ_slot(ORGAN_SLOT_BELLY)
+ var/original_belly_size = exposed_mob.client?.prefs.read_preference(/datum/preference/numeric/belly_size)
+ if(original_belly_size)
+ if(mob_belly?.genital_size > original_belly_size)
+ mob_belly.genital_size -= belly_size_reduction_step
+ mob_belly.update_sprite_suffix()
+ modified_genitals = TRUE
+ if(mob_belly?.genital_size < original_belly_size)
+ mob_belly.genital_size += belly_size_increase_step
+ mob_belly.update_sprite_suffix()
+ modified_genitals = TRUE
+
+ if(exposed_mob.get_organ_slot(ORGAN_SLOT_BUTT))
+ var/obj/item/organ/external/genital/butt/mob_butt = exposed_mob.get_organ_slot(ORGAN_SLOT_BUTT)
+ var/original_butt_size = exposed_mob.client?.prefs.read_preference(/datum/preference/numeric/butt_size)
+ if(original_butt_size)
+ if(mob_butt?.genital_size > original_butt_size)
+ mob_butt.genital_size -= butt_size_reduction_step
+ mob_butt.update_sprite_suffix()
+ modified_genitals = TRUE
+ if(mob_butt?.genital_size < original_butt_size)
+ mob_butt.genital_size += butt_size_increase_step
+ mob_butt.update_sprite_suffix()
+ modified_genitals = TRUE
+
+ if(modified_genitals)
+ exposed_mob.update_body()
diff --git a/modular_zzplurt/code/modules/reagents/chemistry/reagents.dm b/modular_zzplurt/code/modules/reagents/chemistry/reagents.dm
deleted file mode 100644
index 05a65ce5c1060..0000000000000
--- a/modular_zzplurt/code/modules/reagents/chemistry/reagents.dm
+++ /dev/null
@@ -1,95 +0,0 @@
-//Own stuff
-
-//Belly inflator yes
-/datum/reagent/belly_inflator
- name = "Belladine nectar"
- description = "It will give you the lewdest tummy ache you've ever had"
- color = "#01ff63"
- taste_description = "blueberry gum"
- overdose_threshold = 17
-
-/datum/reagent/belly_inflator/on_mob_life(mob/living/carbon/M)
- . = ..()
- if(!ishuman(M))
- return ..()
-
- var/mob/living/carbon/human/H = M
- var/obj/item/organ/external/genital/belly/B = H.get_organ_slot(ORGAN_SLOT_BELLY)
- if(!B)
- B = new
- B.build_from_dna(H.dna, ORGAN_SLOT_BELLY)
- B.Insert(M)
- to_chat(M, span_warning("You feel your midsection get warmer... bubbling softly as it seems to start distending"))
- M.reagents.remove_reagent(type, 5)
-
- //If they have, increase size.
- if(B.genital_size < 16) //just in case
- B.set_size(B.genital_size + 0.1)
- ..()
-
-/datum/reagent/GEsmaller_hypo
- name = "Super Antacid"
- color = "#fca3d4"
- taste_description = "Milky strawberries"
- description = "A medicine used to treat a patient's heavily bloated stomach"
- metabolization_rate = 0.5
-
-/datum/reagent/GEsmaller_hypo/on_mob_life(mob/living/carbon/M)
- var/obj/item/organ/external/genital/belly/B = M.get_organ_slot(ORGAN_SLOT_BELLY)
- if(!B)
- return ..()
- var/optimal_size = M.dna.features["belly_size"]
- if(!optimal_size)//Fast fix for those who don't want it.
- B.set_size(B.genital_size-0.2)
- else if(B.genital_size > optimal_size)
- B.set_size(B.genital_size-0.1)
- else if(B.genital_size < optimal_size)
- B.set_size(B.genital_size + 0.1)
- return ..()
-
-
-///Ass enhancer
-/datum/reagent/butt_enlarger
- name = "Denbu Tincture" //on Hyper it was 'Denbu Draft' but this makes it more consistent with the rectifying chemical down below.
- description = "A mixture of natural vitamins and valentines plant extract, causing butt enlargement in humanoids."
- color = "#e8ff1b"
- taste_description = "butter with a sweet aftertaste" //pass me the butter, OM NOM
- overdose_threshold = 17
-
-/datum/reagent/butt_enlarger/on_mob_life(mob/living/carbon/M) //Increases butt size
- if(!ishuman(M))
- return ..()
- var/mob/living/carbon/human/H = M
- var/obj/item/organ/external/genital/butt/B =M.get_organ_slot(ORGAN_SLOT_BUTT)
- if(!B) //If they don't have a butt. Give them one!
- B = new
- B.build_from_dna(H.dna, ORGAN_SLOT_BUTT)
- B.Insert(M)
- to_chat(M, "Your ass cheeks bulge outwards and feel more plush.")
- M.reagents.remove_reagent(type, 5)
-
- //If they have, increase size.
- if(B.genital_size < BUTT_MAX_SIZE) //just in case
- B.set_size(B.genital_size + 0.05)
- ..()
-
-/datum/reagent/AEsmaller_hypo //"BEsmaller" already exists so using "AE" instead, A is for ass.
- name = "Rectify tincture"
- color = "#e8ff1b"
- taste_description = "butter"
- description = "A medicine used to treat organomegaly in a patient's ass."
- metabolization_rate = 0.5
-
-/datum/reagent/AEsmaller_hypo/on_mob_life(mob/living/carbon/M)
- var/obj/item/organ/external/genital/butt/B = M.get_organ_slot(ORGAN_SLOT_BUTT)
- if(!B)
- return ..()
-
- var/optimal_size = M.dna.features["butt_size"]
- if(!optimal_size)//Fast fix for those who don't want it.
- B.set_size(B.genital_size -0.2)
- else if(B.genital_size > optimal_size)
- B.set_size(B.genital_size - 0.1)
- else if(B.genital_size < optimal_size)
- B.set_size(B.genital_size + 0.1)
- return ..()
diff --git a/modular_zzplurt/sound/interactions/bang1.ogg b/modular_zzplurt/sound/interactions/bang1.ogg
new file mode 100644
index 0000000000000..d0b8abafa7d60
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bang1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bang2.ogg b/modular_zzplurt/sound/interactions/bang2.ogg
new file mode 100644
index 0000000000000..ff07de9666502
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bang2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bang3.ogg b/modular_zzplurt/sound/interactions/bang3.ogg
new file mode 100644
index 0000000000000..62cbe7cd633a4
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bang3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bang4.ogg b/modular_zzplurt/sound/interactions/bang4.ogg
new file mode 100644
index 0000000000000..4d9d9da8051ce
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bang4.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bang5.ogg b/modular_zzplurt/sound/interactions/bang5.ogg
new file mode 100644
index 0000000000000..184af6787cb11
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bang5.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bang6.ogg b/modular_zzplurt/sound/interactions/bang6.ogg
new file mode 100644
index 0000000000000..11c7c056e5b27
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bang6.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj1.ogg b/modular_zzplurt/sound/interactions/bj1.ogg
new file mode 100644
index 0000000000000..004d668f05266
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj10.ogg b/modular_zzplurt/sound/interactions/bj10.ogg
new file mode 100644
index 0000000000000..5d0d56e010f63
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj10.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj11.ogg b/modular_zzplurt/sound/interactions/bj11.ogg
new file mode 100644
index 0000000000000..5335057b45580
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj11.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj2.ogg b/modular_zzplurt/sound/interactions/bj2.ogg
new file mode 100644
index 0000000000000..54c74dd5b463c
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj3.ogg b/modular_zzplurt/sound/interactions/bj3.ogg
new file mode 100644
index 0000000000000..f12481f0b7e01
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj4.ogg b/modular_zzplurt/sound/interactions/bj4.ogg
new file mode 100644
index 0000000000000..35b036d3a5646
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj4.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj5.ogg b/modular_zzplurt/sound/interactions/bj5.ogg
new file mode 100644
index 0000000000000..1c33bee84efc1
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj5.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj6.ogg b/modular_zzplurt/sound/interactions/bj6.ogg
new file mode 100644
index 0000000000000..564e82bad4cfd
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj6.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj7.ogg b/modular_zzplurt/sound/interactions/bj7.ogg
new file mode 100644
index 0000000000000..a65d9cdc845ae
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj7.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj8.ogg b/modular_zzplurt/sound/interactions/bj8.ogg
new file mode 100644
index 0000000000000..e88550afcecdd
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj8.ogg differ
diff --git a/modular_zzplurt/sound/interactions/bj9.ogg b/modular_zzplurt/sound/interactions/bj9.ogg
new file mode 100644
index 0000000000000..2f4feb33ea8a9
Binary files /dev/null and b/modular_zzplurt/sound/interactions/bj9.ogg differ
diff --git a/modular_zzplurt/sound/interactions/champ1.ogg b/modular_zzplurt/sound/interactions/champ1.ogg
new file mode 100644
index 0000000000000..07f2118f2a45a
Binary files /dev/null and b/modular_zzplurt/sound/interactions/champ1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/champ2.ogg b/modular_zzplurt/sound/interactions/champ2.ogg
new file mode 100644
index 0000000000000..63a1e8f1b85e4
Binary files /dev/null and b/modular_zzplurt/sound/interactions/champ2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/champ_fingering.ogg b/modular_zzplurt/sound/interactions/champ_fingering.ogg
new file mode 100644
index 0000000000000..7e540c970378d
Binary files /dev/null and b/modular_zzplurt/sound/interactions/champ_fingering.ogg differ
diff --git a/modular_zzplurt/sound/interactions/clawcum1.ogg b/modular_zzplurt/sound/interactions/clawcum1.ogg
new file mode 100644
index 0000000000000..4313ad4ee1a08
Binary files /dev/null and b/modular_zzplurt/sound/interactions/clawcum1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/clawcum2.ogg b/modular_zzplurt/sound/interactions/clawcum2.ogg
new file mode 100644
index 0000000000000..8573ca2093165
Binary files /dev/null and b/modular_zzplurt/sound/interactions/clawcum2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/final_f1.ogg b/modular_zzplurt/sound/interactions/final_f1.ogg
new file mode 100644
index 0000000000000..3453ec4f9355c
Binary files /dev/null and b/modular_zzplurt/sound/interactions/final_f1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/final_f2.ogg b/modular_zzplurt/sound/interactions/final_f2.ogg
new file mode 100644
index 0000000000000..a0ba60e0a6ab7
Binary files /dev/null and b/modular_zzplurt/sound/interactions/final_f2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/final_f3.ogg b/modular_zzplurt/sound/interactions/final_f3.ogg
new file mode 100644
index 0000000000000..dd9070bd5b407
Binary files /dev/null and b/modular_zzplurt/sound/interactions/final_f3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/final_m1.ogg b/modular_zzplurt/sound/interactions/final_m1.ogg
new file mode 100644
index 0000000000000..6ef14a3d2d468
Binary files /dev/null and b/modular_zzplurt/sound/interactions/final_m1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/final_m2.ogg b/modular_zzplurt/sound/interactions/final_m2.ogg
new file mode 100644
index 0000000000000..128d5f933b821
Binary files /dev/null and b/modular_zzplurt/sound/interactions/final_m2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/final_m3.ogg b/modular_zzplurt/sound/interactions/final_m3.ogg
new file mode 100644
index 0000000000000..b57d0f4382882
Binary files /dev/null and b/modular_zzplurt/sound/interactions/final_m3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/final_m4.ogg b/modular_zzplurt/sound/interactions/final_m4.ogg
new file mode 100644
index 0000000000000..128d5f933b821
Binary files /dev/null and b/modular_zzplurt/sound/interactions/final_m4.ogg differ
diff --git a/modular_zzplurt/sound/interactions/final_m5.ogg b/modular_zzplurt/sound/interactions/final_m5.ogg
new file mode 100644
index 0000000000000..b57d0f4382882
Binary files /dev/null and b/modular_zzplurt/sound/interactions/final_m5.ogg differ
diff --git a/modular_zzplurt/sound/interactions/foot_dry1.ogg b/modular_zzplurt/sound/interactions/foot_dry1.ogg
new file mode 100644
index 0000000000000..1ac7bc66936ff
Binary files /dev/null and b/modular_zzplurt/sound/interactions/foot_dry1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/foot_dry2.ogg b/modular_zzplurt/sound/interactions/foot_dry2.ogg
new file mode 100644
index 0000000000000..957e57591b740
Binary files /dev/null and b/modular_zzplurt/sound/interactions/foot_dry2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/foot_dry3.ogg b/modular_zzplurt/sound/interactions/foot_dry3.ogg
new file mode 100644
index 0000000000000..5b38d5ab85710
Binary files /dev/null and b/modular_zzplurt/sound/interactions/foot_dry3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/foot_dry4.ogg b/modular_zzplurt/sound/interactions/foot_dry4.ogg
new file mode 100644
index 0000000000000..7f71feedffa77
Binary files /dev/null and b/modular_zzplurt/sound/interactions/foot_dry4.ogg differ
diff --git a/modular_zzplurt/sound/interactions/foot_wet1.ogg b/modular_zzplurt/sound/interactions/foot_wet1.ogg
new file mode 100644
index 0000000000000..b38a1932262c1
Binary files /dev/null and b/modular_zzplurt/sound/interactions/foot_wet1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/foot_wet2.ogg b/modular_zzplurt/sound/interactions/foot_wet2.ogg
new file mode 100644
index 0000000000000..857906e8dbcfd
Binary files /dev/null and b/modular_zzplurt/sound/interactions/foot_wet2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/foot_wet3.ogg b/modular_zzplurt/sound/interactions/foot_wet3.ogg
new file mode 100644
index 0000000000000..9a3b8c755fc88
Binary files /dev/null and b/modular_zzplurt/sound/interactions/foot_wet3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/hug.ogg b/modular_zzplurt/sound/interactions/hug.ogg
new file mode 100644
index 0000000000000..68c2948bdc03f
Binary files /dev/null and b/modular_zzplurt/sound/interactions/hug.ogg differ
diff --git a/modular_zzplurt/sound/interactions/kiss1.ogg b/modular_zzplurt/sound/interactions/kiss1.ogg
new file mode 100644
index 0000000000000..f4bd734338373
Binary files /dev/null and b/modular_zzplurt/sound/interactions/kiss1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/kiss2.ogg b/modular_zzplurt/sound/interactions/kiss2.ogg
new file mode 100644
index 0000000000000..18f12d6f04a9e
Binary files /dev/null and b/modular_zzplurt/sound/interactions/kiss2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/kiss3.ogg b/modular_zzplurt/sound/interactions/kiss3.ogg
new file mode 100644
index 0000000000000..0f89617f002b6
Binary files /dev/null and b/modular_zzplurt/sound/interactions/kiss3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/kiss4.ogg b/modular_zzplurt/sound/interactions/kiss4.ogg
new file mode 100644
index 0000000000000..a7c47dd8af7b4
Binary files /dev/null and b/modular_zzplurt/sound/interactions/kiss4.ogg differ
diff --git a/modular_zzplurt/sound/interactions/kiss5.ogg b/modular_zzplurt/sound/interactions/kiss5.ogg
new file mode 100644
index 0000000000000..31a48c4d62278
Binary files /dev/null and b/modular_zzplurt/sound/interactions/kiss5.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_f1.ogg b/modular_zzplurt/sound/interactions/moan_f1.ogg
new file mode 100644
index 0000000000000..5f1ceff6fed64
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_f1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_f2.ogg b/modular_zzplurt/sound/interactions/moan_f2.ogg
new file mode 100644
index 0000000000000..22160295fc695
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_f2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_f3.ogg b/modular_zzplurt/sound/interactions/moan_f3.ogg
new file mode 100644
index 0000000000000..9654b020e9230
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_f3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_f4.ogg b/modular_zzplurt/sound/interactions/moan_f4.ogg
new file mode 100644
index 0000000000000..6fcb314058f3a
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_f4.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_f5.ogg b/modular_zzplurt/sound/interactions/moan_f5.ogg
new file mode 100644
index 0000000000000..7b38319610823
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_f5.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_f6.ogg b/modular_zzplurt/sound/interactions/moan_f6.ogg
new file mode 100644
index 0000000000000..c3b368e3a8b9d
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_f6.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_f7.ogg b/modular_zzplurt/sound/interactions/moan_f7.ogg
new file mode 100644
index 0000000000000..09d3bb3fafbad
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_f7.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m0.ogg b/modular_zzplurt/sound/interactions/moan_m0.ogg
new file mode 100644
index 0000000000000..4f1ede0079c34
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m0.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m1.ogg b/modular_zzplurt/sound/interactions/moan_m1.ogg
new file mode 100644
index 0000000000000..6ef14a3d2d468
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m12.ogg b/modular_zzplurt/sound/interactions/moan_m12.ogg
new file mode 100644
index 0000000000000..cd9a9d1e3d7ee
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m12.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m2.ogg b/modular_zzplurt/sound/interactions/moan_m2.ogg
new file mode 100644
index 0000000000000..128d5f933b821
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m3.ogg b/modular_zzplurt/sound/interactions/moan_m3.ogg
new file mode 100644
index 0000000000000..b57d0f4382882
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m4.ogg b/modular_zzplurt/sound/interactions/moan_m4.ogg
new file mode 100644
index 0000000000000..e8683ea52611d
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m4.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m5.ogg b/modular_zzplurt/sound/interactions/moan_m5.ogg
new file mode 100644
index 0000000000000..2308558b66997
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m5.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m6.ogg b/modular_zzplurt/sound/interactions/moan_m6.ogg
new file mode 100644
index 0000000000000..0d60d7a74ccac
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m6.ogg differ
diff --git a/modular_zzplurt/sound/interactions/moan_m7.ogg b/modular_zzplurt/sound/interactions/moan_m7.ogg
new file mode 100644
index 0000000000000..4e744775702d1
Binary files /dev/null and b/modular_zzplurt/sound/interactions/moan_m7.ogg differ
diff --git a/modular_zzplurt/sound/interactions/oral1.ogg b/modular_zzplurt/sound/interactions/oral1.ogg
new file mode 100644
index 0000000000000..332ccf3c7302d
Binary files /dev/null and b/modular_zzplurt/sound/interactions/oral1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/oral2.ogg b/modular_zzplurt/sound/interactions/oral2.ogg
new file mode 100644
index 0000000000000..fff69d370f247
Binary files /dev/null and b/modular_zzplurt/sound/interactions/oral2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/purr1.ogg b/modular_zzplurt/sound/interactions/purr1.ogg
new file mode 100644
index 0000000000000..e1cc6bd20495c
Binary files /dev/null and b/modular_zzplurt/sound/interactions/purr1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/purr2.ogg b/modular_zzplurt/sound/interactions/purr2.ogg
new file mode 100644
index 0000000000000..caa9ca2dcbfd3
Binary files /dev/null and b/modular_zzplurt/sound/interactions/purr2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/purr3.ogg b/modular_zzplurt/sound/interactions/purr3.ogg
new file mode 100644
index 0000000000000..f53100aec24f9
Binary files /dev/null and b/modular_zzplurt/sound/interactions/purr3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/slap.ogg b/modular_zzplurt/sound/interactions/slap.ogg
new file mode 100644
index 0000000000000..d8804bc6e91a8
Binary files /dev/null and b/modular_zzplurt/sound/interactions/slap.ogg differ
diff --git a/modular_zzplurt/sound/interactions/squelch1.ogg b/modular_zzplurt/sound/interactions/squelch1.ogg
new file mode 100644
index 0000000000000..faf8d651bd1f4
Binary files /dev/null and b/modular_zzplurt/sound/interactions/squelch1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/squelch2.ogg b/modular_zzplurt/sound/interactions/squelch2.ogg
new file mode 100644
index 0000000000000..d4148c198101e
Binary files /dev/null and b/modular_zzplurt/sound/interactions/squelch2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/squelch3.ogg b/modular_zzplurt/sound/interactions/squelch3.ogg
new file mode 100644
index 0000000000000..a9b4a7b6e2770
Binary files /dev/null and b/modular_zzplurt/sound/interactions/squelch3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/swallow.ogg b/modular_zzplurt/sound/interactions/swallow.ogg
new file mode 100644
index 0000000000000..d58b2628d55b4
Binary files /dev/null and b/modular_zzplurt/sound/interactions/swallow.ogg differ
diff --git a/modular_zzplurt/sound/interactions/under_moan_f1.ogg b/modular_zzplurt/sound/interactions/under_moan_f1.ogg
new file mode 100644
index 0000000000000..a3fd6b56d5df8
Binary files /dev/null and b/modular_zzplurt/sound/interactions/under_moan_f1.ogg differ
diff --git a/modular_zzplurt/sound/interactions/under_moan_f2.ogg b/modular_zzplurt/sound/interactions/under_moan_f2.ogg
new file mode 100644
index 0000000000000..5232ccd4b9cf2
Binary files /dev/null and b/modular_zzplurt/sound/interactions/under_moan_f2.ogg differ
diff --git a/modular_zzplurt/sound/interactions/under_moan_f3.ogg b/modular_zzplurt/sound/interactions/under_moan_f3.ogg
new file mode 100644
index 0000000000000..d3b651ab8197a
Binary files /dev/null and b/modular_zzplurt/sound/interactions/under_moan_f3.ogg differ
diff --git a/modular_zzplurt/sound/interactions/under_moan_f4.ogg b/modular_zzplurt/sound/interactions/under_moan_f4.ogg
new file mode 100644
index 0000000000000..1c697450d08ba
Binary files /dev/null and b/modular_zzplurt/sound/interactions/under_moan_f4.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index cfa5a7c1d8581..e535f96c100b9 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -520,6 +520,7 @@
#include "code\__DEFINES\~~~splurt_defines\DNA.dm"
#include "code\__DEFINES\~~~splurt_defines\hud.dm"
#include "code\__DEFINES\~~~splurt_defines\intents.dm"
+#include "code\__DEFINES\~~~splurt_defines\interactions.dm"
#include "code\__DEFINES\~~~splurt_defines\inventory.dm"
#include "code\__DEFINES\~~~splurt_defines\keybinding.dm"
#include "code\__DEFINES\~~~splurt_defines\mobs.dm"
@@ -527,6 +528,7 @@
#include "code\__DEFINES\~~~splurt_defines\spans.dm"
#include "code\__DEFINES\~~~splurt_defines\species_clothing_paths.dm"
#include "code\__DEFINES\~~~splurt_defines\strippable.dm"
+#include "code\__DEFINES\~~~splurt_defines\subsystems.dm"
#include "code\__DEFINES\~~~splurt_defines\thirst.dm"
#include "code\__DEFINES\~~~splurt_defines\underwear.dm"
#include "code\__DEFINES\~~~splurt_defines\dcs\signals.dm"
@@ -9470,6 +9472,7 @@
#include "modular_zzplurt\code\controllers\configuration\entries\discord.dm"
#include "modular_zzplurt\code\controllers\configuration\entries\fetish_content.dm"
#include "modular_zzplurt\code\controllers\subsystem\discord.dm"
+#include "modular_zzplurt\code\controllers\subsystem\interactions.dm"
#include "modular_zzplurt\code\datums\dna.dm"
#include "modular_zzplurt\code\datums\outfit.dm"
#include "modular_zzplurt\code\datums\sprite_accessories.dm"
@@ -9552,6 +9555,7 @@
#include "modular_zzplurt\code\modules\client\client_procs.dm"
#include "modular_zzplurt\code\modules\client\preferences\body_size.dm"
#include "modular_zzplurt\code\modules\client\preferences\clothing.dm"
+#include "modular_zzplurt\code\modules\client\preferences\erp_preferences.dm"
#include "modular_zzplurt\code\modules\client\preferences\genitals.dm"
#include "modular_zzplurt\code\modules\client\preferences\mutant_parts.dm"
#include "modular_zzplurt\code\modules\client\preferences\player_panel.dm"
@@ -9575,6 +9579,44 @@
#include "modular_zzplurt\code\modules\hud\screen_objects.dm"
#include "modular_zzplurt\code\modules\language\_language_holder.dm"
#include "modular_zzplurt\code\modules\language\buzzwords.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interaction_datum.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\_lewd.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\armpit.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\belly.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\breasts.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\facefuck.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\feet.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\finger.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\frotting.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\fuck.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\grope.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\handjob.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\kiss.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\lick.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\misc.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\mount.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\nipples.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\nipsuck.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\nuts.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\oral.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\slap.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\thighs.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\harmful\_extreme.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\harmful\earfuck.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\harmful\eyefuck.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\self\breasts.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\self\finger.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\self\jack.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\self\oral.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\unholy\_unholy.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\unholy\fart.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\unholy\piss.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\interactions\lewd\unholy\scat.dm"
+#include "modular_zzplurt\code\modules\lewd\interaction_menu\lewd_helpers\human.dm"
+#include "modular_zzplurt\code\modules\lewd_chemistry\reagents\_aphrodisiac.dm"
+#include "modular_zzplurt\code\modules\lewd_chemistry\reagents\belly_enlarger.dm"
+#include "modular_zzplurt\code\modules\lewd_chemistry\reagents\butt_enlarger.dm"
+#include "modular_zzplurt\code\modules\lewd_chemistry\reagents\camphor.dm"
#include "modular_zzplurt\code\modules\mob\mob.dm"
#include "modular_zzplurt\code\modules\mob\mob_defines.dm"
#include "modular_zzplurt\code\modules\mob\mob_update_icons.dm"
@@ -9621,7 +9663,6 @@
#include "modular_zzplurt\code\modules\modular_items\lewd_items\code\lewd_chemistry\reagents\cum.dm"
#include "modular_zzplurt\code\modules\movespeed\modifiers\components.dm"
#include "modular_zzplurt\code\modules\movespeed\modifiers\innate.dm"
-#include "modular_zzplurt\code\modules\reagents\chemistry\reagents.dm"
#include "modular_zzplurt\code\modules\reagents\chemistry\machinery\chem_dispenser.dm"
#include "modular_zzplurt\code\modules\reagents\chemistry\reagents\food_reagents.dm"
#include "modular_zzplurt\code\modules\reagents\chemistry\reagents\other_reagents.dm"
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/splurt/genitals.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/splurt/genitals.tsx
index 563f94534fee7..059f3e1b74305 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/splurt/genitals.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/splurt/genitals.tsx
@@ -1,6 +1,7 @@
import {
CheckboxInput,
Feature,
+ FeatureChoiced,
FeatureChoicedServerData,
FeatureNumberInput,
FeatureNumeric,
@@ -107,3 +108,32 @@ export const belly_emissive: Feature = {
description: 'Emissive parts glow in the dark.',
component: FeatureTriBoolInput,
};
+
+export const erp_status_pref_extm: FeatureChoiced = {
+ name: 'Extreme ERP verbs',
+ component: FeatureDropdownInput,
+};
+
+export const erp_status_pref_extmharm: FeatureChoiced = {
+ name: 'Harmful ERP verbs',
+ component: FeatureDropdownInput,
+};
+
+export const erp_status_pref_unholy: FeatureChoiced = {
+ name: 'Unholy ERP verbs',
+ component: FeatureDropdownInput,
+};
+
+export const erp_lust_tolerance_pref: FeatureNumeric = {
+ name: 'Lust tolerance multiplier',
+ description:
+ 'Set your lust tolerance multiplier. \n(0.5 = half tolerance, 2 = double tolerance)',
+ component: FeatureNumberInput,
+};
+
+export const erp_sexual_potency_pref: FeatureNumeric = {
+ name: 'Sexual potency multiplier',
+ description:
+ 'Set your sexual potency multiplier. \n(0.5 = half potency, 2 = double potency)',
+ component: FeatureNumberInput,
+};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/splurt/erp_preferences.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/splurt/erp_preferences.tsx
new file mode 100644
index 0000000000000..f551924fb794b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/splurt/erp_preferences.tsx
@@ -0,0 +1,41 @@
+import { CheckboxInput, FeatureToggle } from '../../base';
+
+export const butt_enlargement_pref: FeatureToggle = {
+ name: 'Butt enlargement',
+ category: 'ERP',
+ description:
+ 'Determines if you wish to receive the effects of butt enlargement chemicals.',
+ component: CheckboxInput,
+};
+
+export const butt_shrinkage_pref: FeatureToggle = {
+ name: 'Butt shrinkage',
+ category: 'ERP',
+ description:
+ 'Determines if you wish to receive the effects of butt shrinkage chemicals.',
+ component: CheckboxInput,
+};
+
+export const belly_enlargement_pref: FeatureToggle = {
+ name: 'Belly enlargement',
+ category: 'ERP',
+ description:
+ 'Determines if you wish to receive the effects of belly enlargement chemicals.',
+ component: CheckboxInput,
+};
+
+export const belly_shrinkage_pref: FeatureToggle = {
+ name: 'Belly shrinkage',
+ category: 'ERP',
+ description:
+ 'Determines if you wish to receive the effects of belly shrinkage chemicals.',
+ component: CheckboxInput,
+};
+
+export const forced_neverboner_pref: FeatureToggle = {
+ name: 'Forced neverboner',
+ category: 'ERP',
+ description:
+ 'Determines if you wish to receive the effects of forced neverboner chemicals.',
+ component: CheckboxInput,
+};
diff --git a/tgui/packages/tgui/interfaces/ZubbersCharacterDirectory.jsx b/tgui/packages/tgui/interfaces/ZubbersCharacterDirectory.jsx
index 80f61aa885d3d..53b1246bbf077 100644
--- a/tgui/packages/tgui/interfaces/ZubbersCharacterDirectory.jsx
+++ b/tgui/packages/tgui/interfaces/ZubbersCharacterDirectory.jsx
@@ -236,6 +236,30 @@ const CharacterDirectoryList = (props) => {
>
Noncon
+
+ Extreme
+
+
+ Extreme Harm
+
+
+ Unholy
+
Advert