diff --git a/baystation12.dme b/baystation12.dme index 79032543b3b..5649460c99e 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -193,6 +193,7 @@ #include "code\controllers\evacuation\evacuation_predicate.dm" #include "code\controllers\evacuation\evacuation_shuttle.dm" #include "code\controllers\evacuation\~evac.dm" +#include "code\controllers\subsystems\addiction.dm" #include "code\controllers\subsystems\ai.dm" #include "code\controllers\subsystems\aifast.dm" #include "code\controllers\subsystems\air.dm" @@ -2871,6 +2872,8 @@ #include "code\modules\reagents\Chemistry-Sublimator.dm" #include "code\modules\reagents\reagent_containers.dm" #include "code\modules\reagents\reagent_dispenser.dm" +#include "code\modules\reagents\addiction\_addiction.dm" +#include "code\modules\reagents\addiction\alcohol.dm" #include "code\modules\reagents\Chemistry-Reagents\Chemistry-Reagents-Core.dm" #include "code\modules\reagents\Chemistry-Reagents\Chemistry-Reagents-Dispenser.dm" #include "code\modules\reagents\Chemistry-Reagents\Chemistry-Reagents-Drinks.dm" diff --git a/code/__defines/chemistry.dm b/code/__defines/chemistry.dm index 744d84557cc..eac741900e9 100644 --- a/code/__defines/chemistry.dm +++ b/code/__defines/chemistry.dm @@ -58,3 +58,14 @@ #define HANDLE_REACTIONS(_reagents) SSchemistry.active_holders[_reagents] = TRUE #define UNQUEUE_REACTIONS(_reagents) SSchemistry.active_holders -= _reagents + +/// Minimum requirement for addiction buzz to be met +#define MIN_ADDICTION_REAGENT_AMOUNT 2 +#define MAX_ADDICTION_POINTS 1000 + +/// Addiction start/ends +#define WITHDRAWAL_STAGE1_START_CYCLE 1 +#define WITHDRAWAL_STAGE1_END_CYCLE 60 +#define WITHDRAWAL_STAGE2_START_CYCLE 61 +#define WITHDRAWAL_STAGE2_END_CYCLE 120 +#define WITHDRAWAL_STAGE3_START_CYCLE 121 diff --git a/code/__defines/lists.dm b/code/__defines/lists.dm index b67c4d5347b..4425facb963 100644 --- a/code/__defines/lists.dm +++ b/code/__defines/lists.dm @@ -13,6 +13,9 @@ #define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } // Adds I to L, initalizing L if necessary #define LAZYADD(L, I) if(!L) { L = list(); } L += I; +#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += V; +/// This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects) +#define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V); // Insert I into L at position X, initalizing L if necessary #define LAZYINSERT(L, I, X) if(!L) { L = list(); } L.Insert(X, I); // Adds I to L, initalizing L if necessary, if I is not already in L diff --git a/code/_helpers/medical_scans.dm b/code/_helpers/medical_scans.dm index 1d7812b7c39..01a71807473 100644 --- a/code/_helpers/medical_scans.dm +++ b/code/_helpers/medical_scans.dm @@ -58,6 +58,8 @@ reagent["scannable"] = R.scannable scan["reagents"] += list(reagent) + scan["addictions"] = H.active_addictions + scan["external_organs"] = list() for(var/obj/item/organ/external/E in H.organs) @@ -266,6 +268,9 @@ if(other_reagent) dat += "Warning: Unknown substance detected in subject's blood." + for(var/datum/addiction/addiction_type as anything in scan["addictions"]) + dat += "Subject is addicted to [initial(addiction_type.name)]" + //summary for the medically disinclined. /* You see a lot of numbers and abbreviations here, but you have no clue what any of this means. diff --git a/code/controllers/subsystems/addiction.dm b/code/controllers/subsystems/addiction.dm new file mode 100644 index 00000000000..7ec5a8975a3 --- /dev/null +++ b/code/controllers/subsystems/addiction.dm @@ -0,0 +1,19 @@ +/*! +This subsystem mostly exists to populate and manage the withdrawal singletons. +*/ + +SUBSYSTEM_DEF(addiction) + name = "Addiction" + flags = SS_NO_FIRE + /// Dictionary of addiction.type || addiction ref + var/list/all_addictions = list() + +/datum/controller/subsystem/addiction/Initialize(timeofday) + InitializeAddictions() + return ..() + +///Ran on initialize, populates the addiction dictionary +/datum/controller/subsystem/addiction/proc/InitializeAddictions() + for(var/type in subtypesof(/datum/addiction)) + var/datum/addiction/ref = new type + all_addictions[type] = ref diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index bf394f7bbd5..bb4ba38bb46 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -29,6 +29,8 @@ R.clear_reagents() set_nutrition(400) set_hydration(400) + for(var/addiction_type in subtypesof(/datum/addiction)) + RemoveAddictionPoints(addiction_type, MAX_ADDICTION_POINTS) //Remove the addiction! ..() /mob/living/carbon/Move(NewLoc, direct) @@ -514,3 +516,15 @@ to_chat(src, "You are no longer running on internals.") if(internals) internals.icon_state = "internal[!!internal]" + +/// Adds addiction points to the specified addiction +/mob/living/carbon/proc/AddAddictionPoints(type, amount) + LAZYSET(addiction_points, type, min(LAZYACCESS(addiction_points, type) + amount, MAX_ADDICTION_POINTS)) + var/datum/addiction/affected_addiction = SSaddiction.all_addictions[type] + return affected_addiction.OnGainAddictionPoints(src) + +/// Adds addiction points to the specified addiction +/mob/living/carbon/proc/RemoveAddictionPoints(type, amount) + LAZYSET(addiction_points, type, max(LAZYACCESS(addiction_points, type) - amount, 0)) + var/datum/addiction/affected_addiction = SSaddiction.all_addictions[type] + return affected_addiction.OnLoseAddictionPoints(src) diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 5ca21077ca1..f777bace373 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -39,3 +39,8 @@ var/stasis_value var/player_triggered_sleeping = 0 + + /// Assoc list of addiction values, key is the type of withdrawal (as singleton type), and the value is the amount of addiction points (as number) + var/list/addiction_points + /// Assoc list of key active addictions and value amount of cycles that it has been active. + var/list/active_addictions diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index ae6498dcc31..1e85fe51dd9 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -111,4 +111,4 @@ var/list/descriptors - var/last_smelt = 0 \ No newline at end of file + var/last_smelt = 0 diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index f28f885409d..08cc0abfee5 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -26,6 +26,11 @@ handle_viruses() + // Handle addictions + for(var/key in addiction_points) + var/datum/addiction/addiction = SSaddiction.all_addictions[key] + addiction.ProcessAddiction(src) + . = 1 if(!client && (!mind || ghosted) && species) diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm index de4710ab665..4d9c601e8cf 100644 --- a/code/modules/reagents/Chemistry-Reagents.dm +++ b/code/modules/reagents/Chemistry-Reagents.dm @@ -62,6 +62,9 @@ var/should_admin_log = FALSE + /// Assoc list with key type of addiction this reagent feeds, and value amount of addiction points added per unit of reagent metabolzied (which means * REM every life()) + var/list/addiction_types = null + /datum/reagent/New(var/datum/reagents/holder) if(!istype(holder)) CRASH("Invalid reagents holder: [log_info_line(holder)]") @@ -98,6 +101,9 @@ if(volume > overdose_threshold) overdose(M, alien) + for(var/addiction in addiction_types) + M.AddAddictionPoints(addiction, addiction_types[addiction] * REM) + //determine the metabolism rate var/removed = metabolism if(ingest_met && (location == CHEM_INGEST)) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm index c6c76a01582..3358376efd0 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm @@ -121,6 +121,10 @@ glass_desc = "A well-known alcohol with a variety of applications." value = DISPENSER_REAGENT_VALUE +/datum/reagent/ethanol/New() + addiction_types = list(/datum/addiction/alcohol = 0.05 * strength) + return ..() + /datum/reagent/ethanol/touch_mob(var/mob/living/L, var/amount) if(istype(L)) L.adjust_fire_stacks(amount / 15) diff --git a/code/modules/reagents/addiction/_addiction.dm b/code/modules/reagents/addiction/_addiction.dm new file mode 100644 index 00000000000..c3694a249c9 --- /dev/null +++ b/code/modules/reagents/addiction/_addiction.dm @@ -0,0 +1,118 @@ +/// Base class for addiction, handles when you become addicted and what the effects of that are. By default you become addicted when you hit a certain threshold, and stop being addicted once you go below another one. +/datum/addiction + /// Name of this addiction + var/name = "Unknown Addiction" + /// Higher threshold, when you start being addicted + var/addiction_gain_threshold = 600 + /// Lower threshold, when you stop being addicted + var/addiction_loss_threshold = 400 + /// Messages for each stage of addictions. + var/list/withdrawal_stage_messages = list() + /// Rates at which you lose addiction (in units/second) if you are not on the drug at that time per stage + var/addiction_loss_per_stage = list(0.5, 0.5, 1, 1.5) + +/// Called when you gain addiction points somehow. Takes a carbon mob as argument and sees if you gained the addiction +/datum/addiction/proc/OnGainAddictionPoints(mob/living/carbon/victim) + var/current_addiction_point_amount = victim.addiction_points[type] + if(current_addiction_point_amount < addiction_gain_threshold) //Not enough to become addicted + return + if(LAZYACCESS(victim.active_addictions, type)) //Already addicted + return + BecomeAddicted(victim) + +///Called when you become addicted +/datum/addiction/proc/BecomeAddicted(mob/living/carbon/victim) + LAZYSET(victim.active_addictions, type, 1) // Start at first cycle. + log_game("[key_name(victim)] has become addicted to [name].") + +/// Called when you lose addiction poitns somehow. Takes a mind as argument and sees if you lost the addiction +/datum/addiction/proc/OnLoseAddictionPoints(mob/living/carbon/victim) + var/current_addiction_point_amount = victim.addiction_points[type] + if(!LAZYACCESS(victim.active_addictions, type)) // Not addicted + return FALSE + if(current_addiction_point_amount > addiction_loss_threshold) // Not enough to stop being addicted + return FALSE + LoseAddiction(victim) + return TRUE + +/datum/addiction/proc/LoseAddiction(mob/living/carbon/victim) + to_chat(victim, SPAN_NOTICE("You feel like you've gotten over your need for drugs.")) + LAZYREMOVE(victim.active_addictions, type) + + +/datum/addiction/proc/ProcessAddiction(mob/living/carbon/victim, delta_time = 2) + var/current_addiction_cycle = LAZYACCESS(victim.active_addictions, type) // If this is null, we're not addicted + var/on_drug_of_this_addiction = FALSE + for(var/datum/reagent/possible_drug as anything in victim.reagents.reagent_list) // Go through the drugs in our system + for(var/addiction in possible_drug.addiction_types) // And check all of their addiction types + if(addiction == type && possible_drug.volume >= MIN_ADDICTION_REAGENT_AMOUNT) // If one of them matches, and we have enough of it in our system, we're not losing addiction + if(current_addiction_cycle) + LAZYSET(victim.active_addictions, type, 1) // Keeps withdrawal at first cycle. + on_drug_of_this_addiction = TRUE + return + + var/withdrawal_stage + switch(current_addiction_cycle) + if(WITHDRAWAL_STAGE1_START_CYCLE to WITHDRAWAL_STAGE1_END_CYCLE) + withdrawal_stage = 1 + if(WITHDRAWAL_STAGE2_START_CYCLE to WITHDRAWAL_STAGE2_END_CYCLE) + withdrawal_stage = 2 + if(WITHDRAWAL_STAGE3_START_CYCLE to INFINITY) + withdrawal_stage = 3 + else + withdrawal_stage = 0 + + if(!on_drug_of_this_addiction) + if(victim.RemoveAddictionPoints(type, addiction_loss_per_stage[withdrawal_stage + 1] * delta_time)) // If true was returned, we lost the addiction! + return + + if(!current_addiction_cycle) // Dont do the effects if were not on drugs + return FALSE + + switch(current_addiction_cycle) + if(WITHDRAWAL_STAGE1_START_CYCLE) + WithdrawalEntersStage1(victim) + if(WITHDRAWAL_STAGE2_START_CYCLE) + WithdrawalEntersStage2(victim) + if(WITHDRAWAL_STAGE3_START_CYCLE) + WithdrawalEntersStage3(victim) + + ///One cycle is 2 seconds + switch(withdrawal_stage) + if(1) + WithdrawalStage1Process(victim, delta_time) + if(2) + WithdrawalStage3Process(victim, delta_time) + if(3) + WithdrawalStage3Process(victim, delta_time) + + LAZYADDASSOC(victim.active_addictions, type, 1 * delta_time) //Next cycle! + +/// Called when addiction enters stage 1 +/datum/addiction/proc/WithdrawalEntersStage1(mob/living/carbon/victim) + return + +/// Called when addiction enters stage 2 +/datum/addiction/proc/WithdrawalEntersStage2(mob/living/carbon/victim) + return + +/// Called when addiction enters stage 3 +/datum/addiction/proc/WithdrawalEntersStage3(mob/living/carbon/victim) + return + + +/// Called when addiction is in stage 1 every process +/datum/addiction/proc/WithdrawalStage1Process(mob/living/carbon/victim, delta_time) + if(prob(5)) + to_chat(victim, SPAN_DANGER("[withdrawal_stage_messages[1]]")) + +/// Called when addiction is in stage 2 every process +/datum/addiction/proc/WithdrawalStage2Process(mob/living/carbon/victim, delta_time) + if(prob(10)) + to_chat(victim, SPAN_DANGER("[withdrawal_stage_messages[2]]")) + + +/// Called when addiction is in stage 3 every process +/datum/addiction/proc/WithdrawalStage3Process(mob/living/carbon/victim, delta_time) + if(prob(15)) + to_chat(victim, SPAN_DANGER("[withdrawal_stage_messages[3]]")) diff --git a/code/modules/reagents/addiction/alcohol.dm b/code/modules/reagents/addiction/alcohol.dm new file mode 100644 index 00000000000..d0c2939d2be --- /dev/null +++ b/code/modules/reagents/addiction/alcohol.dm @@ -0,0 +1,18 @@ +///Alcohol +/datum/addiction/alcohol + name = "alcohol" + withdrawal_stage_messages = list("I could use a drink...", "Maybe the bar is still open?..", "God I need a drink!") + +/datum/addiction/alcohol/WithdrawalStage1Process(mob/living/carbon/victim, delta_time) + . = ..() + victim.jitteriness = clamp(victim.jitteriness + 5 * delta_time, victim.jitteriness, 250) + +/datum/addiction/alcohol/WithdrawalStage2Process(mob/living/carbon/victim, delta_time) + . = ..() + victim.jitteriness = clamp(victim.jitteriness + 10 * delta_time, victim.jitteriness, 250) + victim.adjust_hallucination(3, 3) + +/datum/addiction/alcohol/WithdrawalStage3Process(mob/living/carbon/victim, delta_time) + . = ..() + victim.jitteriness = clamp(victim.jitteriness + 15 * delta_time, victim.jitteriness, 250) + victim.adjust_hallucination(5, 5)