diff --git a/code/__defines/_lists.dm b/code/__defines/_lists.dm
index 43c53308a2..f27069902a 100644
--- a/code/__defines/_lists.dm
+++ b/code/__defines/_lists.dm
@@ -1,11 +1,11 @@
// Helper macros to aid in optimizing lazy instantiation of lists.
// All of these are null-safe, you can use them without knowing if the list var is initialized yet
-//Picks from the list, with some safeties, and returns the "default" arg if it fails
-#define DEFAULTPICK(L, default) ((istype(L, /list) && L:len) ? pick(L) : default)
+///Picks from the list, with some safeties, and returns the "default" arg if it fails
+#define DEFAULTPICK(L, default) ((islist(L) && length(L)) ? pick(L) : default)
// Ensures L is initailized after this point
-#define LAZYINITLIST(L) if (!L) L = list()
+#define LAZYINITLIST(L) if (!L) { L = list(); }
// Sets a L back to null iff it is empty
#define UNSETEMPTY(L) if (L && !length(L)) L = null
@@ -21,7 +21,7 @@
// Adds I to L, initalizing L if necessary, if I is not already in L
#define LAZYDISTINCTADD(L, I) if(!L) { L = list(); } L |= I;
-#define LAZYFIND(L, V) L ? L.Find(V) : 0
+#define LAZYFIND(L, V) (L ? L.Find(V) : 0)
// Reads I from L safely - Works with both associative and traditional lists.
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
@@ -38,6 +38,9 @@
#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; }
#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
+// Sets a list to null
+#define LAZYNULL(L) L = null
+
// Null-safe L.Cut()
#define LAZYCLEARLIST(L) if(L) { L.Cut(); L = null; }
diff --git a/code/__defines/_reagents.dm b/code/__defines/_reagents.dm
index 7d0e14230d..0f7373c7d9 100644
--- a/code/__defines/_reagents.dm
+++ b/code/__defines/_reagents.dm
@@ -266,6 +266,8 @@
#define REAGENT_ID_MENTHOL "menthol"
#define REAGENT_EARTHSBLOOD "Earthsblood"
#define REAGENT_ID_EARTHSBLOOD "earthsblood"
+#define REAGENT_ASUSTENANCE "Artificial Sustenance"
+#define REAGENT_ID_ASUSTENANCE "a_sustenance"
// Virology
diff --git a/code/__defines/addictions.dm b/code/__defines/addictions.dm
new file mode 100644
index 0000000000..86df81aa03
--- /dev/null
+++ b/code/__defines/addictions.dm
@@ -0,0 +1,25 @@
+#define CE_WITHDRAWL "withdrawl" // Withdrawl symptoms
+
+#define ADDICT_NORMAL 1
+#define ADDICT_SLOW 2
+#define ADDICT_FAST 3
+#define ADDICT_POISON 4
+#define ADDICT_ALL 5
+
+GLOBAL_LIST_INIT(reagent_addictive_standard,list(REAGENT_ID_AMBROSIAEXTRACT,REAGENT_ID_TALUMQUEM,REAGENT_ID_METHYLPHENIDATE))
+GLOBAL_LIST_INIT(reagent_addictive_slow,list(REAGENT_ID_TRAMADOL,REAGENT_ID_OXYCODONE,REAGENT_ID_TRICORDRAZINE,REAGENT_ID_ASUSTENANCE,REAGENT_ID_ETHANOL,REAGENT_ID_NICOTINE,REAGENT_ID_COFFEE))
+GLOBAL_LIST_INIT(reagent_addictive_fast,list(REAGENT_ID_HYPERZINE,REAGENT_ID_BLISS))
+GLOBAL_LIST_INIT(reagent_addictive_poison,list())
+
+/proc/get_addictive_reagents(var/addict_type)
+ RETURN_TYPE(/list)
+ switch(addict_type)
+ if(ADDICT_NORMAL)
+ return GLOB.reagent_addictive_standard // Most reagents go here
+ if(ADDICT_SLOW)
+ return GLOB.reagent_addictive_slow // Booze, Cigs
+ if(ADDICT_FAST)
+ return GLOB.reagent_addictive_fast // Bliss, hyperzine, hardcore drugs
+ if(ADDICT_POISON)
+ return GLOB.reagent_addictive_poison // Poisons that use handle_addiction() for unique longterm poisoning
+ return GLOB.reagent_addictive_standard + GLOB.reagent_addictive_fast + GLOB.reagent_addictive_slow + GLOB.reagent_addictive_poison
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 672cb44a4f..64e5058fdb 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -13,6 +13,9 @@
integer = FALSE
default = 1.0
+/datum/config_entry/flag/can_addict_during_round
+ default = FALSE
+
// FIXME: Unused
///datum/config_entry/flag/revival_pod_plants
// default = TRUE
diff --git a/code/controllers/subsystems/internal_wiki.dm b/code/controllers/subsystems/internal_wiki.dm
index b26da61e59..69f3fa8343 100644
--- a/code/controllers/subsystems/internal_wiki.dm
+++ b/code/controllers/subsystems/internal_wiki.dm
@@ -1092,10 +1092,10 @@ SUBSYSTEM_DEF(internal_wiki)
SSinternal_wiki.add_icon(data, initial(beaker_path.icon), initial(beaker_path.icon_state), R.color)
// Get internal data
data["description"] = R.description
- /* Downstream features
data["addictive"] = 0
- if(R.id in addictives)
- data["addictive"] = (R.id in fast_addictives) ? 2 : 1
+ if(R.id in get_addictive_reagents(ADDICT_ALL))
+ data["addictive"] = TRUE
+ /* Downstream features
data["industrial_use"] = R.industrial_use
data["supply_points"] = R.supply_conversion_value ? R.supply_conversion_value : 0
var/value = R.supply_conversion_value * REAGENTS_PER_SHEET * SSsupply.points_per_money
@@ -1111,9 +1111,9 @@ SUBSYSTEM_DEF(internal_wiki)
/datum/internal_wiki/page/chemical/get_print()
var/body = ""
body += "Description: [data["description"]]
"
- /* Downstream features
if(data["addictive"])
- body += "DANGER, [data["addictive"] > 1 ? "highly " : ""]addictive.
"
+ body += "DANGER, addictive.
"
+ /* Downstream features
if(data["industrial_use"])
body += "Industrial Use: [data["industrial_use"]]
"
var/tank_size = CARGOTANKER_VOLUME
diff --git a/code/game/objects/items/devices/scanners/health.dm b/code/game/objects/items/devices/scanners/health.dm
index 233e20f674..a44f617940 100644
--- a/code/game/objects/items/devices/scanners/health.dm
+++ b/code/game/objects/items/devices/scanners/health.dm
@@ -308,6 +308,29 @@
dat += span_warning("Anatomical irregularities detected in subject.")
dat += "
"
//CHOMPedit end
+ // Addictions
+ if(H.get_addiction_to_reagent(REAGENT_ID_ASUSTENANCE) > 0)
+ dat += span_warning("Biologically unstable, requires [REAGENT_ASUSTENANCE] to function properly.")
+ dat += "
"
+ for(var/addic in H.get_all_addictions())
+ if(H.get_addiction_to_reagent(addic) > 0 && (advscan >= 2 || H.get_addiction_to_reagent(addic) <= 120)) // high enough scanner upgrade detects addiction even if not almost withdrawling
+ var/datum/reagent/R = SSchemistry.chemical_reagents[addic]
+ if(R.id == REAGENT_ID_ASUSTENANCE)
+ continue
+ if(advscan >= 1)
+ // Shows multiple
+ if(advscan >= 2 && H.get_addiction_to_reagent(addic) <= 80)
+ dat += span_warning("Experiencing withdrawls from [R.name], [REAGENT_INAPROVALINE] treatment recomended.")
+ dat += "
"
+ else
+ dat += span_warning("Chemical dependance detected: [R.name].")
+ dat += "
"
+ else
+ // Shows single
+ dat += span_warning("Chemical dependance detected.")
+ dat += "
"
+ break
+ // Appendix
for(var/obj/item/organ/internal/appendix/a in H.internal_organs)
var/severity = ""
if(a.inflamed > 3)
diff --git a/code/game/objects/structures/ghost_pods/event_vr.dm b/code/game/objects/structures/ghost_pods/event_vr.dm
index 00757ea022..d1d6ae07fe 100644
--- a/code/game/objects/structures/ghost_pods/event_vr.dm
+++ b/code/game/objects/structures/ghost_pods/event_vr.dm
@@ -244,6 +244,7 @@
M.client.prefs.copy_to(new_character)
new_character.dna.ResetUIFrom(new_character)
new_character.sync_organ_dna()
+ new_character.sync_addictions()
new_character.key = M.key
new_character.mind.loaded_from_ckey = picked_ckey
new_character.mind.loaded_from_slot = picked_slot
diff --git a/code/game/objects/structures/trash_pile_vr.dm b/code/game/objects/structures/trash_pile_vr.dm
index 477f75225d..fbec657561 100644
--- a/code/game/objects/structures/trash_pile_vr.dm
+++ b/code/game/objects/structures/trash_pile_vr.dm
@@ -315,6 +315,7 @@
prob(2);/obj/item/selectable_item/chemistrykit/size,
prob(2);/obj/item/selectable_item/chemistrykit/gender,
prob(2);/obj/item/clothing/gloves/bluespace/emagged,
+ prob(2);/obj/item/reagent_containers/glass/beaker/vial/sustenance,
prob(1);/obj/item/clothing/suit/storage/vest/heavy/merc,
prob(1);/obj/item/nif/bad,
prob(1);/obj/item/radio_jammer,
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index bf20c5b2eb..1bcac7f914 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -525,6 +525,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
new_character.dna.ResetUIFrom(new_character)
new_character.sync_dna_traits(TRUE) // Traitgenes Sync traits to genetics if needed
new_character.sync_organ_dna()
+ new_character.sync_addictions() // These are addicitions our profile wants... May as well give them!
new_character.initialize_vessel()
if(inhabit)
new_character.key = player_key
diff --git a/code/modules/mob/living/carbon/addictions.dm b/code/modules/mob/living/carbon/addictions.dm
new file mode 100644
index 0000000000..2a4f905629
--- /dev/null
+++ b/code/modules/mob/living/carbon/addictions.dm
@@ -0,0 +1,162 @@
+// Addictions
+
+// Addictions work using a combined counter var. If a reagent is in the list returned by get_addictive_reagents(), it will be added to the addiction_counters[] assoc list by its id.
+// If it is present in your body, and you are NOT addicted, it will count DOWNWARD. When it reaches the "addiction proc" you will become addicted. By default you will become addicted at
+// ADDICTION_PROC negative points. FASTADDICT_PROC and SLOWADDICT_PROC are also used depending on the reagent's addiction speed. This is to prevent booze being as addictive as bliss.
+// If you are addicted to a chem, and have it present in your body, the counter will quickly climb back up to ADDICTION_PEAK. Refreshing your addiction entirely.
+
+// Once the addiction proc is reached you will become addicted. The counter will be set to ADDICTION_PEAK and begin counting DOWNWARD. Each addicted reagent has a handle_addiction() proc. The default
+// implimentation of it will perform various effects once you are under 100 points of addiction. Such as vomiting, organ damage, and other bad effects for not feeding the addiction. Check that
+// code for exact logic. Inaprovaline is intended to suppress withdrawl effects. Reagents may override the handle_addiction proc to have their own special handling. Like reagents that kill you if you
+// do not feed their withdrawls. The handle_addiction proc also handles if you become cured of your addiction! If it returns 0, it will end your addiction.
+
+#define ADDICTION_PROC -4000
+#define SLOWADDICT_PROC -8000
+#define FASTADDICT_PROC -1000
+#define POISONADDICT_PROC -100
+#define ADDICTION_PEAK 300
+
+/mob/living/carbon/proc/sync_addictions()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(!species)
+ return
+ if(!species.traits)
+ return
+ // Rebuild addictions from traits
+ LAZYCLEARLIST(addictions)
+ LAZYCLEARLIST(addiction_counters)
+ for(var/TR in species.traits)
+ var/datum/trait/T = GLOB.all_traits[TR]
+ if(!T)
+ continue
+ if(!T.addiction)
+ continue
+ addict_to_reagent(T.addiction, TRUE)
+
+/mob/living/proc/handle_addictions()
+ PROTECTED_PROC(TRUE)
+ // Empty so it can be overridden
+
+/mob/living/carbon/handle_addictions()
+ // Don't process during vore stuff... It was originally just absorbed, but lets give some mercy to rp focused servers.
+ if(isbelly(loc))
+ return
+ // All addictions start at 0.
+ var/list/addict = list()
+ for(var/datum/reagent/R in bloodstr.reagent_list)
+ var/reagentid = R.id
+ if(istype(SSchemistry.chemical_reagents[reagentid], /datum/reagent/ethanol))
+ reagentid = REAGENT_ID_ETHANOL
+ if(reagentid in get_addictive_reagents(ADDICT_ALL))
+ addict.Add(reagentid)
+ // Only needed for alcohols, will interfere with pills if you detect other things!
+ for(var/datum/reagent/R in ingested.reagent_list)
+ var/reagentid = R.id
+ if(istype(SSchemistry.chemical_reagents[reagentid], /datum/reagent/ethanol))
+ reagentid = REAGENT_ID_ETHANOL
+ if(istype(SSchemistry.chemical_reagents[reagentid], /datum/reagent/drink/coffee))
+ reagentid = REAGENT_ID_COFFEE
+ if(reagentid in get_addictive_reagents(ADDICT_ALL))
+ addict.Add(reagentid)
+
+ for(var/A in addict)
+ if(!LAZYFIND(addictions,A))
+ LAZYADD(addictions,A)
+ LAZYSET(addiction_counters,A,0)
+
+ if(LAZYACCESS(addiction_counters,A) <= 0)
+ // Build addiction until it procs
+ if(LAZYFIND(addiction_counters, A))
+ addiction_counters[A] -= rand(1,3)
+
+ if(LAZYACCESS(addiction_counters,A) < SLOWADDICT_PROC)
+ LAZYSET(addiction_counters,A,SLOWADDICT_PROC)
+ // Check for addition
+ if(A in get_addictive_reagents(ADDICT_SLOW))
+ // Slowest addictions for some medications
+ if(LAZYACCESS(addiction_counters,A) <= SLOWADDICT_PROC)
+ addict_to_reagent(A, FALSE)
+ if(A in get_addictive_reagents(ADDICT_FAST))
+ // quickly addict to these drugs, bliss, oxyco etc
+ if(LAZYACCESS(addiction_counters,A) <= FASTADDICT_PROC)
+ addict_to_reagent(A, FALSE)
+ else
+ // slower addiction over a longer period, cigs and painkillers mostly
+ if(LAZYACCESS(addiction_counters,A) <= ADDICTION_PROC)
+ addict_to_reagent(A, FALSE)
+ else
+ // satiating addiction we already have
+ if(LAZYACCESS(addiction_counters,A) < ADDICTION_PEAK)
+ if(LAZYACCESS(addiction_counters,A) < 100)
+ LAZYSET(addiction_counters,A,100)
+ var/datum/reagent/RR = SSchemistry.chemical_reagents[A]
+ var/message = RR.addiction_refresh_message()
+ if(message)
+ to_chat(src, message)
+ if(LAZYFIND(addiction_counters, A))
+ addiction_counters[A] += rand(8,13)
+
+ // For all counters above 100, count down
+ // For all under 0, count up to 0 randomly, reducing initial addiction buildup if you didn't proc it
+ if(LAZYLEN(addictions))
+ var/C = pick(addictions)
+ // return to normal... we didn't haven't been addicted yet, but we shouldn't become addicted instantly next time if it's been a few hours!
+ if(LAZYACCESS(addiction_counters,C) < 0)
+ if(prob(15) && LAZYFIND(addiction_counters, C))
+ addiction_counters[C] += 1
+ // proc reagent's withdrawl
+ var/datum/reagent/RE = SSchemistry.chemical_reagents[C]
+ var/addict_counter_before = LAZYACCESS(addiction_counters,C)
+ if(LAZYACCESS(addiction_counters,C) > 0)
+ LAZYSET(addiction_counters,C,RE.handle_addiction(src,species.reagent_tag)) // withdrawl can modify the value however it deems fit as you are affected by it
+ // remove if finished
+ if(LAZYACCESS(addiction_counters,C) == 0)
+ var/message = RE.addiction_cure_message()
+ if(addict_counter_before > 0 && message) // Only show cure message if we were addicted to it prior!
+ to_chat(src, message)
+ LAZYREMOVE(addictions,C)
+ LAZYREMOVE(addiction_counters,C)
+
+/mob/living/carbon/proc/addict_to_reagent(var/reagentid, var/round_start)
+ PRIVATE_PROC(TRUE)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(isSynthetic()) // Should this be allowed? I guess you can roleplay Bender as an FBP? Trait in the future?
+ return
+ // Check if serverconfig allows addiction during the round. Otherwise only allow spawning with addictions
+ var/allow_addiction = round_start || CONFIG_GET(flag/can_addict_during_round)
+ if(!allow_addiction)
+ return
+ if(!LAZYFIND(addictions,reagentid))
+ LAZYADD(addictions,reagentid)
+ LAZYSET(addiction_counters,reagentid,ADDICTION_PEAK)
+
+/mob/living/carbon/proc/get_addiction_to_reagent(var/reagentid) // returns counter's value or 0
+ SHOULD_NOT_OVERRIDE(TRUE)
+ return LAZYACCESS(addiction_counters,reagentid)
+
+/mob/living/carbon/proc/refresh_all_addictions()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(!addiction_counters)
+ return
+ for(var/reagentid in addiction_counters)
+ LAZYSET(addiction_counters,reagentid,ADDICTION_PEAK)
+
+/mob/living/carbon/proc/clear_all_addictions() // This is probably not a good idea to call as some addictions are intended to be uncurable, like artificial sustenance.
+ SHOULD_NOT_OVERRIDE(TRUE)
+ LAZYCLEARLIST(addictions)
+ LAZYCLEARLIST(addiction_counters)
+
+/mob/living/carbon/proc/get_all_addictions()
+ RETURN_TYPE(/list)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ var/list/addict = list()
+ if(addiction_counters)
+ for(var/key in addiction_counters)
+ addict.Add(key)
+ return addict
+
+#undef ADDICTION_PROC
+#undef SLOWADDICT_PROC
+#undef FASTADDICT_PROC
+#undef POISONADDICT_PROC
+#undef ADDICTION_PEAK
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index dccb158a84..0cbbfaa151 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -24,6 +24,9 @@
var/does_not_breathe = 0 //Used for specific mobs that can't take advantage of the species flags (changelings)
+ VAR_PROTECTED/list/addictions = null // contains currently addicted chem reagent IDs
+ VAR_PROTECTED/list/addiction_counters = null // contains counters by reagent ID
+
//these two help govern taste. The first is the last time a taste message was shown to the plaer.
//the second is the message in question.
var/last_taste_time = 0
diff --git a/code/modules/mob/living/carbon/human/species/station/traits/negative.dm b/code/modules/mob/living/carbon/human/species/station/traits/negative.dm
index d85b103410..c047c131a4 100644
--- a/code/modules/mob/living/carbon/human/species/station/traits/negative.dm
+++ b/code/modules/mob/living/carbon/human/species/station/traits/negative.dm
@@ -761,3 +761,59 @@
cost = 0
category = 0
custom_only = FALSE
+
+
+// Addictions
+/datum/trait/neutral/addiction_alcohol
+ name = "Addiction - Alcohol"
+ desc = "You have become chemically dependant to any alcoholic drink, and need to regularly consume it or suffer withdrawals."
+ addiction = REAGENT_ID_ETHANOL
+ custom_only = FALSE
+ hidden = TRUE //Disabled on Virgo
+
+/datum/trait/neutral/addiction_bliss
+ name = "Addiction - " + REAGENT_BLISS
+ desc = "You have become chemically dependant to " + REAGENT_BLISS + ", and need to regularly consume it or suffer withdrawals."
+ addiction = REAGENT_ID_BLISS
+ custom_only = FALSE
+ hidden = TRUE //Disabled on Virgo
+
+/datum/trait/neutral/addiction_coffee
+ name = "Addiction - " + REAGENT_COFFEE
+ desc = "You have become chemically dependant to " + REAGENT_COFFEE + ", and need to regularly consume it or suffer withdrawals."
+ addiction = REAGENT_ID_COFFEE
+ custom_only = FALSE
+
+/datum/trait/neutral/addiction_hyper
+ name = "Addiction - " + REAGENT_HYPERZINE
+ desc = "You have become chemically dependant to " + REAGENT_HYPERZINE + ", and need to regularly consume it or suffer withdrawals."
+ addiction = REAGENT_ID_HYPERZINE
+ custom_only = FALSE
+ hidden = TRUE //Disabled on Virgo
+
+/datum/trait/neutral/addiction_nicotine
+ name = "Addiction - " + REAGENT_NICOTINE
+ desc = "You have become chemically dependant to " + REAGENT_NICOTINE + ", and need to regularly consume it or suffer withdrawals."
+ addiction = REAGENT_ID_NICOTINE
+ custom_only = FALSE
+
+/datum/trait/neutral/addiction_oxy
+ name = "Addiction - " + REAGENT_OXYCODONE
+ desc = "You have become chemically dependant to " + REAGENT_OXYCODONE + ", and need to regularly consume it or suffer withdrawals."
+ addiction = REAGENT_ID_OXYCODONE
+ custom_only = FALSE
+ hidden = TRUE //Disabled on Virgo
+
+/datum/trait/neutral/addiction_painkiller
+ name = "Addiction - Pain Killers"
+ desc = "You have become chemically dependant to " + REAGENT_TRAMADOL + ", and need to regularly consume it or suffer withdrawals."
+ addiction = REAGENT_ID_TRAMADOL
+ custom_only = FALSE
+ hidden = TRUE //Disabled on Virgo
+
+/datum/trait/neutral/addiction_asustenance
+ name = "Unstable Vat Grown Body"
+ desc = "You are chemically dependant to " + REAGENT_ASUSTENANCE + ", and need to regularly consume it or your body decays."
+ addiction = REAGENT_ID_ASUSTENANCE
+ custom_only = FALSE
+ hidden = TRUE //Disabled on Virgo
diff --git a/code/modules/mob/living/carbon/human/species/station/traits/trait.dm b/code/modules/mob/living/carbon/human/species/station/traits/trait.dm
index eb01f35631..5dc0d42317 100644
--- a/code/modules/mob/living/carbon/human/species/station/traits/trait.dm
+++ b/code/modules/mob/living/carbon/human/species/station/traits/trait.dm
@@ -34,6 +34,7 @@
var/mutation = 0 // Mutation to give (or 0)
var/disability = 0 // Disability to give (or 0)
var/sdisability = 0 // SDisability to give (or 0)
+ var/addiction = null // Addiction reagent, null otherwise
var/activation_message = null // If not null, shows a message when activated as a gene
var/deactivation_message = null // If not null, shows a message when deactivated as a gene
var/list/primitive_expression_messages=list() // Monkey's custom emote when they have this gene!
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index e629c65c7d..a5a82c6635 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -76,6 +76,7 @@
if(handle_regular_status_updates()) // Status & health update, are we dead or alive etc.
handle_disabilities() // eye, ear, brain damages
+ handle_addictions() // Dwugs
handle_statuses() //all special effects, stunned, weakened, jitteryness, hallucination, sleeping, etc
update_canmove()
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 4df884efb1..38524fcb44 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -434,6 +434,7 @@
new_character.dna.b_type = client.prefs.b_type
new_character.sync_dna_traits(TRUE) // Traitgenes Sync traits to genetics if needed
new_character.sync_organ_dna()
+ new_character.sync_addictions() // Handle round-start addictions
new_character.initialize_vessel()
for(var/lang in client.prefs.alternate_languages)
diff --git a/code/modules/organs/internal/kidneys.dm b/code/modules/organs/internal/kidneys.dm
index 1cfb925c9f..d64851fb7b 100644
--- a/code/modules/organs/internal/kidneys.dm
+++ b/code/modules/organs/internal/kidneys.dm
@@ -20,6 +20,11 @@
else if(is_broken())
owner.adjustToxLoss(0.3 * PROCESS_ACCURACY)
+ // General organ damage from withdraw, kidneys do a lot of the work
+ if(prob(70) && owner.chem_effects[CE_WITHDRAWL])
+ take_damage(owner.chem_effects[CE_WITHDRAWL] * 0.05 * PROCESS_ACCURACY, prob(1)) // Chance to warn them
+ owner.adjustToxLoss(owner.chem_effects[CE_WITHDRAWL] * 0.3 * PROCESS_ACCURACY)
+
/obj/item/organ/internal/kidneys/handle_organ_proc_special()
. = ..()
diff --git a/code/modules/organs/internal/liver.dm b/code/modules/organs/internal/liver.dm
index db0ed2f8ef..ef908f175f 100644
--- a/code/modules/organs/internal/liver.dm
+++ b/code/modules/organs/internal/liver.dm
@@ -43,6 +43,15 @@
if(filter_effect < 3)
owner.adjustToxLoss(owner.chem_effects[CE_ALCOHOL_TOXIC] * 0.1 * PROCESS_ACCURACY)
+ // General organ damage from withdraw
+ if(prob(20) && owner.chem_effects[CE_WITHDRAWL])
+ take_damage(owner.chem_effects[CE_WITHDRAWL] * 0.05 * PROCESS_ACCURACY, prob(1)) // Chance to warn them
+ if(filter_effect < 2) //Withdrawls intensified
+ owner.adjustToxLoss(owner.chem_effects[CE_WITHDRAWL] * 0.2 * PROCESS_ACCURACY)
+ if(filter_effect < 3)
+ owner.adjustToxLoss(owner.chem_effects[CE_WITHDRAWL] * 0.1 * PROCESS_ACCURACY)
+
+
/obj/item/organ/internal/liver/handle_germ_effects()
. = ..() //Up should return an infection level as an integer
if(!.) return
diff --git a/code/modules/organs/internal/spleen.dm b/code/modules/organs/internal/spleen.dm
index f0f0e31ddc..08fdbdc967 100644
--- a/code/modules/organs/internal/spleen.dm
+++ b/code/modules/organs/internal/spleen.dm
@@ -40,6 +40,11 @@
if(src.damage < 0)
src.damage = 0
+ // General organ damage from withdraw
+ if(prob(20) && owner.chem_effects[CE_WITHDRAWL])
+ take_damage(owner.chem_effects[CE_WITHDRAWL] * 0.05 * PROCESS_ACCURACY, prob(1)) // Chance to warn them
+ owner.adjustToxLoss(owner.chem_effects[CE_WITHDRAWL] * 0.2 * PROCESS_ACCURACY)
+
/obj/item/organ/internal/spleen/handle_germ_effects()
. = ..() //Up should return an infection level as an integer
if(!.) return
diff --git a/code/modules/reagents/machinery/chemalyzer.dm b/code/modules/reagents/machinery/chemalyzer.dm
index f8695039fd..7786b1d6b4 100644
--- a/code/modules/reagents/machinery/chemalyzer.dm
+++ b/code/modules/reagents/machinery/chemalyzer.dm
@@ -85,6 +85,9 @@
SSinternal_wiki.add_icon(subdata, initial(beaker_path.icon), initial(beaker_path.icon_state), R.color)
// Get internal data
subdata["description"] = R.description
+ subdata["addictive"] = 0
+ if(R.id in get_addictive_reagents(ADDICT_ALL))
+ subdata["addictive"] = TRUE
subdata["flavor"] = R.taste_description
subdata["allergen"] = SSinternal_wiki.assemble_allergens(R.allergen_type)
subdata["beakerAmount"] = found_reagents[ID]
diff --git a/code/modules/reagents/reactions/instant/instant.dm b/code/modules/reagents/reactions/instant/instant.dm
index b53d4bc515..3ee4b06b2f 100644
--- a/code/modules/reagents/reactions/instant/instant.dm
+++ b/code/modules/reagents/reactions/instant/instant.dm
@@ -1248,3 +1248,10 @@
result = REAGENT_ID_PROTEIN
required_reagents = list(REAGENT_ID_ENZYME = 1, REAGENT_ID_SPIDERTOXIN = 1, REAGENT_ID_SIFSAP = 1)
result_amount = 1
+
+/decl/chemical_reaction/instant/artificial_sustenance
+ name = REAGENT_ASUSTENANCE
+ id = REAGENT_ID_ASUSTENANCE
+ result = REAGENT_ID_ASUSTENANCE
+ required_reagents = list(REAGENT_ID_NUTRIMENT = 1, REAGENT_ID_MUTAGEN = 1, REAGENT_ID_PHORON = 1)
+ result_amount = 1
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index 849bcba3d8..31959fd5cd 100644
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -431,3 +431,7 @@
matter = list(MAT_WOOD = 50)
drop_sound = 'sound/items/drop/wooden.ogg'
pickup_sound = 'sound/items/pickup/wooden.ogg'
+
+/obj/item/reagent_containers/glass/beaker/vial/sustenance
+ name = "vial (artificial sustenance)"
+ prefill = list(REAGENT_ID_ASUSTENANCE = 30)
diff --git a/code/modules/reagents/reagents/dispenser.dm b/code/modules/reagents/reagents/dispenser.dm
index a12bc1ddc1..3de3c2de5e 100644
--- a/code/modules/reagents/reagents/dispenser.dm
+++ b/code/modules/reagents/reagents/dispenser.dm
@@ -214,6 +214,75 @@
to_chat(usr, span_notice("The solution dissolves the ink on the book."))
return
+/datum/reagent/ethanol/handle_addiction(var/mob/living/carbon/M, var/alien)
+ // A copy of the base with withdrawl, but with much less effects, such as vomiting.
+ var/current_addiction = M.get_addiction_to_reagent(id)
+ var/realistic_addiction = FALSE //DEFAULT set to FALSE. Toggle to TRUE for a more realistic addiction with potentially fatal side effects.
+ // slow degrade
+ if(prob(8))
+ current_addiction -= 1
+ // withdrawl mechanics
+ if(!(CE_STABLE in M.chem_effects)) //Without stabilization effects
+ if(current_addiction <= 60)
+ M.pulse = PULSE_2FAST
+ if(prob(2))
+ if(current_addiction < 90 && prob(10))
+ to_chat(M, span_warning("[pick("You feel miserable.","You feel nauseous.","You get a raging headache.")]"))
+ M.adjustHalLoss(7)
+ M.make_jittery(25) //Restlessness.
+ else if(current_addiction <= 20)
+ to_chat(M, span_danger("You feel absolutely awful. You need some some liquor. Now."))
+ if(realistic_addiction && prob(20)) //1 in 5 on a 1 in 50, so 1 in 250 chance. DTs
+ to_chat(src, span_red("You have a seizure!"))
+ for(var/mob/O in viewers(M, null))
+ if(O == src)
+ continue
+ O.show_message(span_danger("[M] starts having a seizure!"), 1)
+ M.Paralyse(10)
+ M.make_jittery(1000)
+ else if(current_addiction <= 50)
+ to_chat(M, span_warning("You're really craving some alcohol. You feel nauseated."))
+ if(realistic_addiction)
+ M.emote("vomit")
+ M.AdjustConfused(10) // Disorientation.
+ else if(current_addiction <= 100)
+ to_chat(M, span_notice("You're feeling the need for some booze."))
+ // effects
+ if(current_addiction < 60 && prob(20)) // 1 in 50 x 1 in 5 = 1 in 250
+ M.emote(pick("pale","shiver","twitch"))
+ M.drop_item() //Hand tremors
+ if(realistic_addiction)
+ M.add_chemical_effect(CE_WITHDRAWL, rand(4,10) * REM)
+ else //Stabilization effects
+ if(current_addiction <= 60)
+ M.pulse = PULSE_FAST
+ if(prob(2))
+ if(current_addiction < 90 && prob(10))
+ to_chat(M, span_warning("[pick("You feel a light throbbing in your head.","Your stomach feels upset.","Your .")]"))
+ M.adjustHalLoss(3)
+ M.make_jittery(10) //Restlessness.
+ else if(current_addiction <= 20)
+ to_chat(M, span_warning("You feel nauseated."))
+ if(realistic_addiction)
+ M.emote("vomit")
+ M.AdjustConfused(10) // Disorientation.
+ else if(current_addiction <= 50)
+ to_chat(M, span_warning("Your head throbs and the room spins."))
+ if(realistic_addiction)
+ M.AdjustConfused(3) // Disorientation.
+ else if(current_addiction <= 100)
+ to_chat(M, span_notice("A drink would be nice."))
+ // effects
+ if(current_addiction < 60 && prob(5)) // 1 in 50 x 1 in 20 = 1 in 1000
+ M.emote(pick("pale","shiver","twitch"))
+ M.drop_item() //Hand tremors
+ if(current_addiction <= 0) //safety
+ current_addiction = 0
+ return current_addiction
+
+/datum/reagent/ethanol/addiction_cure_message()
+ return span_notice("You feel your symptoms end, you no longer feel the craving for alcohol.")
+
/datum/reagent/fluorine
name = REAGENT_FLUORINE
id = REAGENT_ID_FLUORINE
diff --git a/code/modules/reagents/reagents/drugs.dm b/code/modules/reagents/reagents/drugs.dm
index f168792647..743a8ab226 100644
--- a/code/modules/reagents/reagents/drugs.dm
+++ b/code/modules/reagents/reagents/drugs.dm
@@ -210,6 +210,37 @@
color = "#181818"
high_messages = FALSE
+/datum/reagent/drugs/nicotine/handle_addiction(var/mob/living/carbon/M, var/alien)
+ // A copy of the base with withdrawl, but with much less effects, such as vomiting.
+ var/current_addiction = M.get_addiction_to_reagent(id)
+ // slow degrade
+ if(prob(8))
+ current_addiction -= 1
+ // withdrawl mechanics
+ if(prob(2))
+ if(!(CE_STABLE in M.chem_effects)) //Without stabilization effects
+ if(current_addiction < 90 && prob(10))
+ to_chat(M, span_warning("[pick("You feel miserable.","You feel nauseous.","You get a raging headache.")]"))
+ M.adjustHalLoss(5)
+ else if(current_addiction <= 20)
+ to_chat(M, span_danger("You feel absolutely awful. You need some [name]. Now."))
+ if(prob(10)) //1 in 10 on top of a 1 in 50, so thats a 1 in 500 chance. Seems low enough to not be disruptive.
+ M.emote("vomit")
+ else if(current_addiction <= 50)
+ to_chat(M, span_warning("You're really craving some [name]."))
+ else if(current_addiction <= 100)
+ to_chat(M, span_notice("You're feeling the need for some [name]."))
+ // effects
+ if(current_addiction < 60 && prob(20))
+ M.emote(pick("pale","shiver","twitch", "cough"))
+ else
+ if(current_addiction < 90 && prob(10))
+ to_chat(M, span_warning("[pick("You feel a slight craving for some [name].","Your stomach feels slightly upset.","You feel a slight pain in your head.")]"))
+ if(current_addiction <= 0) //safety
+ current_addiction = 0
+ return current_addiction
+
+
/*///////////////////////////////////////////////////////////////////////////
/// PSYCHIATRIC DRUGS /////
/// /////
diff --git a/code/modules/reagents/reagents/food_drinks.dm b/code/modules/reagents/reagents/food_drinks.dm
index 803a2506f7..93c0466d07 100644
--- a/code/modules/reagents/reagents/food_drinks.dm
+++ b/code/modules/reagents/reagents/food_drinks.dm
@@ -1634,6 +1634,28 @@
//M.apply_effect(3, STUTTER) //VOREStation Edit end
M.make_jittery(5)
+/datum/reagent/drink/coffee/handle_addiction(var/mob/living/carbon/M, var/alien)
+ // A copy of the base with withdrawl, but with much less effects, no vomiting and sometimes halloss
+ var/current_addiction = M.get_addiction_to_reagent(id)
+ // slow degrade
+ if(prob(8))
+ current_addiction -= 1
+ // withdrawl mechanics
+ if(prob(2))
+ if(current_addiction < 90 && prob(10))
+ to_chat(M, span_warning("[pick("You feel miserable.","You feel sluggish.","You get a small headache.")]"))
+ M.adjustHalLoss(2)
+ else if(current_addiction <= 50)
+ to_chat(M, span_warning("You're really craving some [name]."))
+ else if(current_addiction <= 100)
+ to_chat(M, span_notice("You're feeling the need for some [name]."))
+ // effects
+ if(current_addiction < 60 && prob(20))
+ M.emote(pick("pale","shiver","twitch"))
+ if(current_addiction <= 0) //safety
+ current_addiction = 0
+ return current_addiction
+
/datum/reagent/drink/coffee/icecoffee
name = REAGENT_ICECOFFEE
id = REAGENT_ID_ICECOFFEE
diff --git a/code/modules/reagents/reagents/medicine.dm b/code/modules/reagents/reagents/medicine.dm
index 67be4e2c4a..15583e74a2 100644
--- a/code/modules/reagents/reagents/medicine.dm
+++ b/code/modules/reagents/reagents/medicine.dm
@@ -1425,3 +1425,64 @@
M.druggy = max(M.druggy, 20)
M.hallucination = max(M.hallucination, 3)
M.adjustBrainLoss(1 * removed) //your life for your mind. The Earthmother's Tithe.
+
+
+// Vat clone stablizer
+/datum/reagent/acid/artificial_sustenance
+ name = REAGENT_ASUSTENANCE
+ id = REAGENT_ID_ASUSTENANCE
+ description = "A drug used to stablize vat grown bodies. Often used to control the lifespan of biological experiments." // Who else remembers Cybersix?
+ taste_description = "burning metal"
+ reagent_state = LIQUID
+ color = "#31d422"
+ overdose = 15
+ overdose_mod = 1.2
+ scannable = 1
+
+/datum/reagent/acid/artificial_sustenance/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed)
+ // You need me...
+ if(M.get_addiction_to_reagent(REAGENT_ID_ASUSTENANCE))
+ return
+ // Continue to acid damage, no changes on injection or splashing, as this is meant to be edible only to those pre-addicted to it! Not a snowflake acid that doesn't hurt you!
+ . = ..()
+
+/datum/reagent/acid/artificial_sustenance/handle_addiction(var/mob/living/carbon/M, var/alien)
+ // A copy of the base with withdrawl, but with death, and different messages
+ var/current_addiction = M.get_addiction_to_reagent(id)
+ // slow degrade
+ if(prob(2))
+ current_addiction -= 1
+ // withdrawl mechanics
+ if(prob(2))
+ if(current_addiction <= 40)
+ to_chat(M, span_danger("You're dying for some [name]!"))
+ else if(current_addiction <= 60)
+ to_chat(M, span_warning("You're really craving some [name]."))
+ else if(current_addiction <= 100)
+ to_chat(M, span_notice("You're feeling the need for some [name]."))
+ // effects
+ if(current_addiction < 60 && prob(20))
+ M.emote(pick("pale","shiver","twitch"))
+ // Agony and death!
+ if(current_addiction <= 20)
+ if(prob(12))
+ M.adjustToxLoss( rand(1,4) )
+ M.adjustBruteLoss( rand(1,4) )
+ M.adjustOxyLoss( rand(1,4) )
+ // proc side effect
+ if(current_addiction <= 30)
+ if(prob(3))
+ M.Weaken(2)
+ M.emote("vomit")
+ M.add_chemical_effect(CE_WITHDRAWL, rand(9,14) * REM)
+ else if(current_addiction <= 40)
+ if(prob(3))
+ M.emote("vomit")
+ M.add_chemical_effect(CE_WITHDRAWL, rand(5,9) * REM)
+ else if(current_addiction <= 50)
+ if(prob(2))
+ M.emote("vomit")
+ // Sustenance requirements cannot be escaped!
+ if(current_addiction <= 0)
+ current_addiction = 40
+ return current_addiction
diff --git a/code/modules/reagents/reagents/withdrawl.dm b/code/modules/reagents/reagents/withdrawl.dm
new file mode 100644
index 0000000000..912bd16afe
--- /dev/null
+++ b/code/modules/reagents/reagents/withdrawl.dm
@@ -0,0 +1,64 @@
+/datum/reagent/proc/handle_addiction(var/mob/living/carbon/M, var/alien)
+ // overridable proc for custom withdrawl behaviors, standard is chills, cravings, vomiting, weakness and CE_WITHDRAWL organ damage
+ if(alien == IS_DIONA)
+ return 0
+ var/current_addiction = M.get_addiction_to_reagent(id)
+ // slow degrade
+ if(prob(8))
+ current_addiction -= 1
+ // withdrawl mechanics
+ if(current_addiction < 10)
+ if(prob(2))
+ M.Weaken(1)
+ else if(current_addiction > 10)
+ if(CE_STABLE in M.chem_effects) // Inaprovaline can be used to treat addiction
+ if(prob(1))
+ switch(rand(1,3))
+ if(1)
+ to_chat(M, span_notice("You feel sluggish."))
+ if(2)
+ to_chat(M, span_notice("You feel awful."))
+ if(3)
+ to_chat(M, span_notice("Everything feels sore."))
+ // effects
+ if(current_addiction < 100 && prob(10))
+ M.emote(pick("pale","shiver","twitch"))
+ if(current_addiction <= 60)
+ if(prob(1))
+ M.Weaken(2)
+ if(prob(5) && prob(1))
+ M.emote("vomit")
+ else
+ // send a message to notify players
+ if(prob(2))
+ if(current_addiction <= 40)
+ to_chat(M, span_danger("You're dying for some [name]!"))
+ else if(current_addiction <= 60)
+ to_chat(M, span_warning("You're really craving some [name]."))
+ else if(current_addiction <= 100)
+ to_chat(M, span_notice("You're feeling the need for some [name]."))
+ // effects
+ if(current_addiction < 100 && prob(20))
+ M.emote(pick("pale","shiver","twitch"))
+ // proc side effect
+ if(current_addiction <= 30)
+ if(prob(3))
+ M.Weaken(2)
+ M.emote("vomit")
+ M.add_chemical_effect(CE_WITHDRAWL, rand(2,4) * REM)
+ else if(current_addiction <= 50)
+ if(prob(3))
+ M.emote("vomit")
+ M.add_chemical_effect(CE_WITHDRAWL, rand(1,3) * REM)
+ else if(current_addiction <= 70)
+ if(prob(2))
+ M.emote("vomit")
+ if(current_addiction <= 0) //safety
+ current_addiction = 0
+ return current_addiction
+
+/datum/reagent/proc/addiction_cure_message()
+ return span_notice("You feel your symptoms end, you no longer feel the craving for [name].")
+
+/datum/reagent/proc/addiction_refresh_message()
+ return span_notice("You feel rejuvenated as the [name] rushes through you.")
diff --git a/code/modules/resleeving/autoresleever.dm b/code/modules/resleeving/autoresleever.dm
index e2bd3f87d9..92d3aa3eb5 100644
--- a/code/modules/resleeving/autoresleever.dm
+++ b/code/modules/resleeving/autoresleever.dm
@@ -157,6 +157,7 @@
new_character.dna.ResetUIFrom(new_character)
new_character.sync_dna_traits(TRUE) // Traitgenes Sync traits to genetics if needed
new_character.sync_organ_dna()
+ new_character.sync_addictions() // These are addicitions our profile wants... May as well give them!
new_character.initialize_vessel()
if(ghost.mind)
ghost.mind.transfer_to(new_character)
diff --git a/code/modules/vore/eating/inbelly_spawn.dm b/code/modules/vore/eating/inbelly_spawn.dm
index 1971d4ad9c..f7e6ac273b 100644
--- a/code/modules/vore/eating/inbelly_spawn.dm
+++ b/code/modules/vore/eating/inbelly_spawn.dm
@@ -146,6 +146,7 @@ Please do not abuse this ability.
new_character.dna.ResetUIFrom(new_character)
new_character.sync_dna_traits(TRUE) // Traitgenes Sync traits to genetics if needed
new_character.sync_organ_dna()
+ new_character.sync_addictions()
new_character.initialize_vessel()
new_character.key = player_key
if(new_character.mind)
diff --git a/config/example/game_options.txt b/config/example/game_options.txt
index 66da511f13..313819907b 100644
--- a/config/example/game_options.txt
+++ b/config/example/game_options.txt
@@ -69,6 +69,9 @@ FOOTSTEP_VOLUME 45
## Whether or not humans show an area message when they die.
SHOW_HUMAN_DEATH_MESSAGE
+## If chemicals and drinks can give addictions during a round. Otherwise they are only accessible through traits or exotic effects.
+#CAN_ADDICT_DURING_ROUND
+
## Alert level descriptions
ALERT_DESC_GREEN All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced.
diff --git a/vorestation.dme b/vorestation.dme
index f987f5a5ea..df9820cf7d 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -33,6 +33,7 @@
#include "code\__defines\_reagents_ch.dm"
#include "code\__defines\_tick.dm"
#include "code\__defines\action.dm"
+#include "code\__defines\addictions.dm"
#include "code\__defines\admin.dm"
#include "code\__defines\admin_ch.dm"
#include "code\__defines\admin_verb.dm"
@@ -3250,6 +3251,7 @@
#include "code\modules\mob\living\bot\mulebot_vr.dm"
#include "code\modules\mob\living\bot\secbot.dm"
#include "code\modules\mob\living\bot\SLed209bot.dm"
+#include "code\modules\mob\living\carbon\addictions.dm"
#include "code\modules\mob\living\carbon\breathe.dm"
#include "code\modules\mob\living\carbon\carbon.dm"
#include "code\modules\mob\living\carbon\carbon_defense.dm"
@@ -4328,6 +4330,7 @@
#include "code\modules\reagents\reagents\virology.dm"
#include "code\modules\reagents\reagents\vore_ch.dm"
#include "code\modules\reagents\reagents\vore_vr.dm"
+#include "code\modules\reagents\reagents\withdrawl.dm"
#include "code\modules\recycling\conveyor2.dm"
#include "code\modules\recycling\disposal-construction.dm"
#include "code\modules\recycling\disposal.dm"