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)