Adds "Aphrodisiacal Bite" Quirk, moving "Venomous Bite" 's ERP options. (#4269)

## About The Pull Request
Adds a new 0 Points quirk that injects people with ERP poisons, moving
them from Venomous Bite to it.
<img width="478" height="223" alt="image"
src="https://github.com/user-attachments/assets/51a685bc-4674-43ec-b95d-53407f0c0411"
/>
<img width="469" height="241" alt="image"
src="https://github.com/user-attachments/assets/f155d7a0-2ceb-4d1c-a13d-2c07da9c64a5"
/>
<img width="217" height="230" alt="image"
src="https://github.com/user-attachments/assets/7f769484-eda3-4a66-8560-5bb52dc5a8bd"
/>
Links the Venom Milker over to this quirk.
Links Aphro-Biting to the Aphrodisiac preference, and logs its usage.
<img width="679" height="68" alt="image"
src="https://github.com/user-attachments/assets/29b819be-398a-4bd8-8d75-3aa0c3eb9931"
/>

Frankly, I have no attachment over the sprite or name/descriptions, feel
free to change to something more proper.

First PR here! Tell me if I've majorly fucked something up.
## Why It's Good For The Game
I've seen people mention it was weird a Mechanical Quirk had ERP
options, especially for such a steep point cost, I agree. Separating
them fixes it, giving each quirk its own niche and usage.
## Proof Of Testing
See above, compiles.
<details>
<summary>Screenshots/Videos</summary>

</details>

## Changelog
🆑 sunnyaries
add: added a new quirk "Aphrodisiacal Bite", and moved ERP chems from
"Venomous Bite" to it.
/🆑

---------

Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
This commit is contained in:
sunny
2025-08-09 22:13:03 -04:00
committed by GitHub
co-authored by Roxy
parent 430a99c3a4
commit 897ea73c11
8 changed files with 239 additions and 55 deletions
@@ -14,7 +14,7 @@
/obj/item/reagent_containers/venom_milker/Initialize(mapload)
. = ..()
var/filter_immune_string = /datum/preference/choiced/venomous_bite_venom::filter_immune_string
var/filter_immune_string = /datum/preference/choiced/aphrodisiacal_bite_venom::filter_immune_string
if (length(filter_immune_string))
desc += span_notice("\nThe following reagents cannot be filtered by the neutralizer: [filter_immune_string]")
@@ -52,7 +52,7 @@
* * silent = FALSE: If TRUE, will not give user any feedback.
*/
/obj/item/reagent_containers/venom_milker/proc/can_milk(mob/living/target, mob/living/user, silent = FALSE)
var/datum/action/cooldown/mob_cooldown/venomous_bite/bite = locate() in target.actions
var/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/bite = locate() in target.actions
if (isnull(bite))
if (!silent)
user?.balloon_alert(user, "no fangs!")
@@ -87,7 +87,7 @@
/obj/item/reagent_containers/venom_milker/proc/siphon(mob/living/target, mob/living/user)
if (!can_milk(target, user))
return FALSE
var/datum/action/cooldown/mob_cooldown/venomous_bite/bite = locate(/datum/action/cooldown/mob_cooldown/venomous_bite) in target.actions
var/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/bite = locate() in target.actions
if (isnull(bite))
return FALSE
@@ -106,7 +106,7 @@
return TRUE
/**
* If a reagent milked from someone with the venom quirk is NOT in /datum/preference/choiced/venomous_bite_venom::milkable_venoms, it will be transformed into this
* If a reagent milked from someone with the venom quirk is NOT in /datum/preference/choiced/aphrodisiacal_bite_venom::milkable_venoms, it will be transformed into this
* generic chem that has no effects.
*/
/datum/reagent/generic_milked_venom
@@ -0,0 +1,106 @@
/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite
name = "Inject Aphrodisiac"
desc = "Sink your fangs into another and inject them with your aphrodisiac."
button_icon = 'modular_zubbers/icons/mob/actions/quirks/aphrodisiacal_bite.dmi'
button_icon_state = "aphrodisiac"
ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_pickturf.dmi'
check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED // can use it if cuffed, enjoy
shared_cooldown = NONE
/// The reagent we will inject.
var/datum/reagent/reagent_typepath
/// How much of [reagent_typepath] we will inject.
var/to_inject = 0
/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/New(Target, original, datum/reagent/our_reagent, quantity_override = null)
. = ..()
set_reagent(our_reagent, quantity_override)
/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/proc/set_reagent(datum/reagent/new_reagent, quantity_override = null, cooldown_override = null)
reagent_typepath = new_reagent
var/list/reagent_spec = /datum/preference/choiced/aphrodisiacal_bite_venom::aphrodisiacal_bite_choice_specs[new_reagent]
to_inject = quantity_override || reagent_spec[1]
cooldown_time = cooldown_override || reagent_spec[2]
/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/set_click_ability(mob/on_who)
. = ..()
if (!.)
return
owner.visible_message(span_warning("[owner] bares [owner.p_their()] fangs..."), span_notice("You bare your fangs..."))
/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/Activate(atom/target_atom)
if (!isliving(target_atom))
return FALSE
if (astype(owner, /mob/living/carbon)?.is_mouth_covered())
owner.balloon_alert(owner, "mouth covered!")
return FALSE
if (!owner.Adjacent(target_atom))
owner.balloon_alert(owner, "too far!")
return FALSE
if (target_atom == owner)
owner.balloon_alert(owner, "can't bite yourself!")
return FALSE
if(!iscarbon(target_atom))
owner.balloon_alert(owner, "not carbon!")
return FALSE
if(!astype(target_atom, /mob/living/carbon).client?.prefs.read_preference(/datum/preference/toggle/erp/aphro))
owner.balloon_alert(owner, "not interested!")
log_game("[key_name(owner)] tried to bite [key_name(astype(target_atom, /mob/living/carbon))] but [key_name(astype(target_atom, /mob/living/carbon))] had aphrodisiacs disabled")
return FALSE
log_combat(owner, target_atom, "started to bite", null, "with venom: [reagent_typepath::name]")
owner.visible_message(span_warning("[owner] starts to bite [target_atom]!"), span_warning("You start to bite [target_atom]!"), ignored_mobs = target_atom)
to_chat(target_atom, span_userdanger("[owner] starts to bite you!"))
owner.balloon_alert_to_viewers("biting...")
if (!do_after(owner, 0.5 SECONDS, target_atom, IGNORE_HELD_ITEM))
return FALSE
. = ..()
if (try_bite(target_atom))
inject(target_atom)
return TRUE
/// Does NOT inject reagents; represents the initial bite.
/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/proc/try_bite(mob/living/target)
PRIVATE_PROC(TRUE)
var/target_zone = check_zone(owner.zone_selected)
var/text = "[owner] sinks [owner.p_their()] teeth into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"
var/self_message = "You sink your teeth into [target]'s [target.parse_zone_with_bodypart(target_zone)]!"
var/victim_message = "[owner] sinks [owner.p_their()] teeth into your [target.parse_zone_with_bodypart(target_zone)]!"
owner.visible_message(span_warning(text), span_warning(self_message), ignored_mobs = list(target))
to_chat(target, span_userdanger(victim_message))
owner.do_attack_animation(target, ATTACK_EFFECT_BITE)
playsound(owner, 'sound/items/weapons/bite.ogg', 60, TRUE)
log_combat(owner, target, "successfully bit", null, "with venom: [reagent_typepath::name]")
return TRUE
/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/proc/inject(mob/living/target)
if (!target.try_inject(owner, check_zone(owner.zone_selected)))
return FALSE
add_reagents(target.reagents)
return TRUE
/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/proc/add_reagents(datum/reagents/target, harvesting = FALSE)
var/temp = astype(owner, /mob/living/carbon/human)?.coretemperature
var/datum/reagent/local_typepath = reagent_typepath
if (harvesting)
var/list/spec = /datum/preference/choiced/aphrodisiacal_bite_venom::aphrodisiacal_bite_choice_specs[local_typepath]
if (!spec[3])
local_typepath = /datum/reagent/generic_milked_venom
target.add_reagent(local_typepath, to_inject, reagtemp = temp)
return TRUE
@@ -0,0 +1,111 @@
/datum/quirk/aphrodisiacal_bite
name = "Aphrodisiacal Bite"
desc = "You have a aphrodisiacal gland, and can bite people to inject them with an aphrodisiac of your choosing."
icon = FA_ICON_ICICLES
value = 0
gain_text = span_notice("You feel a aphrodisiacal gland in the back of your throat.")
lose_text = span_warning("Your aphrodisiacal gland vanishes.")
medical_record_text = "Patient possesses an aphrodisiacal gland."
/datum/quirk/aphrodisiacal_bite/add(client/client_source)
var/datum/reagent/reagent = text2path(client_source?.prefs?.read_preference(/datum/preference/choiced/aphrodisiacal_bite_venom))
var/mob/living/carbon/human/human_holder = quirk_holder
var/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/action = new /datum/action/cooldown/mob_cooldown/aphrodisiacal_bite(human_holder, our_reagent = reagent)
action.Grant(human_holder)
/datum/quirk/aphrodisiacal_bite/remove()
if(QDELETED(quirk_holder))
return ..()
var/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite/action = locate(/datum/action/cooldown/mob_cooldown/aphrodisiacal_bite) in quirk_holder.actions
action.Remove()
return ..()
/datum/quirk_constant_data/aphrodisiacal_bite
associated_typepath = /datum/quirk/aphrodisiacal_bite
customization_options = list(/datum/preference/choiced/aphrodisiacal_bite_venom)
/datum/preference/choiced/aphrodisiacal_bite_venom
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
savefile_key = "aphrodisiacal_bite_venom"
savefile_identifier = PREFERENCE_CHARACTER
/// Format: (reagent typepath -> list(amount to inject per bite, cooldown, can be milked (venom_milker.dm)))
var/static/list/aphrodisiacal_bite_choice_specs = list(
/datum/reagent/drug/aphrodisiac/crocin = list(5, 2 SECONDS, TRUE),
/datum/reagent/drug/aphrodisiac/crocin/hexacrocin = list(5, 5 SECONDS, TRUE),
/datum/reagent/drug/aphrodisiac/succubus_milk = list(5, 2 SECONDS, TRUE),
/datum/reagent/drug/aphrodisiac/incubus_draft = list(5, 2 SECONDS, TRUE),
)
var/static/list/milkable_venoms = generate_milkable_venom_list()
var/static/filter_immune_string = generate_filter_immune_string()
/**
* Generates a static list of /datum/reagent that will not be transformed into [/datum/reagent/generic_milked_venom] upon being milked by a venom milker.
*
* Ran once, then never again.
*/
/proc/generate_milkable_venom_list()
RETURN_TYPE(/list)
var/list/milkable = list()
for (var/datum/reagent/typepath as anything in /datum/preference/choiced/aphrodisiacal_bite_venom::aphrodisiacal_bite_choice_specs)
var/list/spec = /datum/preference/choiced/aphrodisiacal_bite_venom::aphrodisiacal_bite_choice_specs[typepath]
if (spec[3])
milkable += typepath
return milkable
/**
* Generates a static string appended to the venom milker's description, detailing which reagents available to the venom quirk will not be transformed into [/datum/reagent/generic_milked_venom].
*
* Ran once, then never again.
*/
/proc/generate_filter_immune_string()
var/filter_immune_string = ""
for (var/datum/reagent/typepath as anything in /datum/preference/choiced/aphrodisiacal_bite_venom::milkable_venoms)
if (length(filter_immune_string))
filter_immune_string += ", "
filter_immune_string += typepath::name
return filter_immune_string
/datum/preference/choiced/aphrodisiacal_bite_venom/init_possible_values()
var/list/choices = list()
for (var/entry in aphrodisiacal_bite_choice_specs)
choices += "[entry]"
return choices
/datum/preference/choiced/aphrodisiacal_bite_venom/create_default_value()
return "/datum/reagent/drug/aphrodisiac/crocin"
/datum/preference/choiced/aphrodisiacal_bite_venom/compile_constant_data()
var/list/data = ..()
var/list/titles = list()
for (var/datum/reagent/entry as anything in aphrodisiacal_bite_choice_specs)
var/list/specs = aphrodisiacal_bite_choice_specs[entry]
var/inject = specs[1]
var/cooldown = specs[2]
var/name = entry::name
titles["[entry]"] = "[name] ([inject]u, [cooldown / 10] second cooldown)"
data[CHOICED_PREFERENCE_DISPLAY_NAMES] = titles
return data
/datum/preference/choiced/aphrodisiacal_bite_venom/is_accessible(datum/preferences/preferences)
. = ..()
if (!.)
return FALSE
return "Aphrodisiacal Bite" in preferences.all_quirks
/datum/preference/choiced/aphrodisiacal_bite_venom/apply_to_human(mob/living/carbon/human/target, value)
return
@@ -116,13 +116,9 @@
add_reagents(target.reagents)
return TRUE
/datum/action/cooldown/mob_cooldown/venomous_bite/proc/add_reagents(datum/reagents/target, harvesting = FALSE)
/datum/action/cooldown/mob_cooldown/venomous_bite/proc/add_reagents(datum/reagents/target)
var/temp = astype(owner, /mob/living/carbon/human)?.coretemperature
var/datum/reagent/local_typepath = reagent_typepath
if (harvesting)
var/list/spec = /datum/preference/choiced/venomous_bite_venom::venomous_bite_choice_specs[local_typepath]
if (!spec[3])
local_typepath = /datum/reagent/generic_milked_venom
target.add_reagent(local_typepath, to_inject, reagtemp = temp)
return TRUE
@@ -30,53 +30,15 @@
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
savefile_key = "venomous_bite_venom"
savefile_identifier = PREFERENCE_CHARACTER
/// Format: (reagent typepath -> list(amount to inject per bite, cooldown, can be milked (venom_milker.dm)))
/// Format: (reagent typepath -> list(amount to inject per bite, cooldown))
var/static/list/venomous_bite_choice_specs = list(
/datum/reagent/toxin = list(5, 80 SECONDS, FALSE),
/datum/reagent/toxin/venom = list(5, 180 SECONDS, FALSE),
/datum/reagent/toxin/carpotoxin = list(5, 60 SECONDS, FALSE), // less powerful than toxin
/datum/reagent/toxin = list(5, 80 SECONDS),
/datum/reagent/toxin/venom = list(5, 180 SECONDS),
/datum/reagent/toxin/carpotoxin = list(5, 60 SECONDS), // less powerful than toxin
// drugs
/datum/reagent/drug/space_drugs = list(5, 60 SECONDS, TRUE),
/datum/reagent/toxin/mindbreaker = list(5, 60 SECONDS, FALSE),
// erp stuff
/datum/reagent/drug/aphrodisiac/crocin = list(5, 2 SECONDS, TRUE),
/datum/reagent/drug/aphrodisiac/crocin/hexacrocin = list(5, 5 SECONDS, TRUE),
/datum/reagent/drug/aphrodisiac/succubus_milk = list(5, 2 SECONDS, TRUE),
/datum/reagent/drug/aphrodisiac/incubus_draft = list(5, 2 SECONDS, TRUE),
/datum/reagent/drug/space_drugs = list(5, 60 SECONDS),
/datum/reagent/toxin/mindbreaker = list(5, 60 SECONDS),
)
var/static/list/milkable_venoms = generate_milkable_venom_list()
var/static/filter_immune_string = generate_filter_immune_string()
/**
* Generates a static list of /datum/reagent that will not be transformed into [/datum/reagent/generic_milked_venom] upon being milked by a venom milker.
*
* Ran once, then never again.
*/
/proc/generate_milkable_venom_list()
RETURN_TYPE(/list)
var/list/milkable = list()
for (var/datum/reagent/typepath as anything in /datum/preference/choiced/venomous_bite_venom::venomous_bite_choice_specs)
var/list/spec = /datum/preference/choiced/venomous_bite_venom::venomous_bite_choice_specs[typepath]
if (spec[3])
milkable += typepath
return milkable
/**
* Generates a static string appended to the venom milker's description, detailing which reagents available to the venom quirk will not be transformed into [/datum/reagent/generic_milked_venom].
*
* Ran once, then never again.
*/
/proc/generate_filter_immune_string()
var/filter_immune_string = ""
for (var/datum/reagent/typepath as anything in /datum/preference/choiced/venomous_bite_venom::milkable_venoms)
if (length(filter_immune_string))
filter_immune_string += ", "
filter_immune_string += typepath::name
return filter_immune_string
/datum/preference/choiced/venomous_bite_venom/init_possible_values()
var/list/choices = list()
Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

+4 -2
View File
@@ -8943,6 +8943,8 @@
#include "modular_zubbers\code\datums\quirks\neutral_quirks\seamless_clothes.dm"
#include "modular_zubbers\code\datums\quirks\neutral_quirks\unblinking.dm"
#include "modular_zubbers\code\datums\quirks\neutral_quirks\waddle.dm"
#include "modular_zubbers\code\datums\quirks\neutral_quirks\aphrodisiacal_bite\aphrodisiacal_bite.dm"
#include "modular_zubbers\code\datums\quirks\neutral_quirks\aphrodisiacal_bite\aphrodisiacal_bite_quirk.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\apathetic_override.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\bilingual_override.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\chip_connector_override.dm"
@@ -8960,8 +8962,8 @@
#include "modular_zubbers\code\datums\quirks\positive_quirks\spacer_override.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\strong_stomach_override.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\tagger_override.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\venemous_bite\venomous_bite.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\venemous_bite\venomous_bite_quirk.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\venomous_bite\venomous_bite.dm"
#include "modular_zubbers\code\datums\quirks\positive_quirks\venomous_bite\venomous_bite_quirk.dm"
#include "modular_zubbers\code\datums\shuttle\arena.dm"
#include "modular_zubbers\code\datums\shuttles\emergency.dm"
#include "modular_zubbers\code\datums\station_traits\disabled_traits.dm"
@@ -0,0 +1,7 @@
import type { FeatureChoiced } from '../../base';
import { FeatureDropdownInput } from '../../dropdowns';
export const aphrodisiacal_bite_venom: FeatureChoiced = {
name: 'Aphrodisiac Type',
component: FeatureDropdownInput,
};