diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm
index 6db88cc37d951..e4f3733008eb5 100644
--- a/code/__DEFINES/cooldowns.dm
+++ b/code/__DEFINES/cooldowns.dm
@@ -41,14 +41,14 @@
//TIMER COOLDOWN MACROS
-#define COMSIG_CD_STOP(cd_index) "cooldown_[cd_index]"
-#define COMSIG_CD_RESET(cd_index) "cd_reset_[cd_index]"
+#define COMSIG_CD_STOP(cd_name) "cooldown_[cd_name]"
+#define COMSIG_CD_RESET(cd_name) "cd_reset_[cd_name]"
-#define TIMER_COOLDOWN_START(cd_source, cd_index, cd_time) LAZYSET(cd_source.cooldowns, cd_index, addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(end_cooldown), cd_source, cd_index), cd_time))
+#define TIMER_COOLDOWN_START(cd_source, cd_name, cd_time) LAZYSET(cd_source.cooldowns, cd_name, addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(end_cooldown), cd_source, cd_name), cd_time))
-#define TIMER_COOLDOWN_CHECK(cd_source, cd_index) LAZYACCESS(cd_source.cooldowns, cd_index)
+#define TIMER_COOLDOWN_CHECK(cd_source, cd_name) LAZYACCESS(cd_source.cooldowns, cd_name)
-#define TIMER_COOLDOWN_END(cd_source, cd_index) LAZYREMOVE(cd_source.cooldowns, cd_index)
+#define TIMER_COOLDOWN_END(cd_source, cd_name) LAZYREMOVE(cd_source.cooldowns, cd_name)
/*
* Stoppable timer cooldowns.
@@ -57,11 +57,11 @@
* A bit more expensive than the regular timers, but can be reset before they end and the time left can be checked.
*/
-#define S_TIMER_COOLDOWN_START(cd_source, cd_index, cd_time) LAZYSET(cd_source.cooldowns, cd_index, addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(end_cooldown), cd_source, cd_index), cd_time, TIMER_STOPPABLE))
+#define S_TIMER_COOLDOWN_START(cd_source, cd_name, cd_time) LAZYSET(cd_source.cooldowns, cd_name, addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(end_cooldown), cd_source, cd_name), cd_time, TIMER_STOPPABLE))
-#define S_TIMER_COOLDOWN_RESET(cd_source, cd_index) reset_cooldown(cd_source, cd_index)
+#define S_TIMER_COOLDOWN_RESET(cd_source, cd_name) reset_cooldown(cd_source, cd_name)
-#define S_TIMER_COOLDOWN_TIMELEFT(cd_source, cd_index) (timeleft(TIMER_COOLDOWN_CHECK(cd_source, cd_index)))
+#define S_TIMER_COOLDOWN_TIMELEFT(cd_source, cd_name) (timeleft(TIMER_COOLDOWN_CHECK(cd_source, cd_name)))
/*
@@ -69,17 +69,36 @@
* Better performance over timer cooldowns, lower control. Same functionality.
*/
-#define COOLDOWN_DECLARE(cd_index) var/##cd_index = 0
+#define COOLDOWN_DECLARE(cd_name) var/##cd_name = 0
-#define COOLDOWN_STATIC_DECLARE(cd_index) var/static/##cd_index = 0
+#define COOLDOWN_STATIC_DECLARE(cd_name) var/static/##cd_name = 0
-#define COOLDOWN_START(cd_source, cd_index, cd_time) (cd_source.cd_index = world.time + (cd_time))
+#define COOLDOWN_START(cd_source, cd_name, cd_time) (cd_source.cd_name = world.time + (cd_time))
//Returns true if the cooldown has run its course, false otherwise
-#define COOLDOWN_FINISHED(cd_source, cd_index) (cd_source.cd_index < world.time)
+#define COOLDOWN_FINISHED(cd_source, cd_name) (cd_source.cd_name < world.time)
-#define COOLDOWN_RESET(cd_source, cd_index) cd_source.cd_index = 0
+#define COOLDOWN_RESET(cd_source, cd_name) cd_source.cd_name = 0
-#define COOLDOWN_TIMELEFT(cd_source, cd_index) (max(0, cd_source.cd_index - world.time))
+#define COOLDOWN_TIMELEFT(cd_source, cd_name) (max(0, cd_source.cd_name - world.time))
-#define COOLDOWN_TIMELEFT_TEXT(cd_source, cd_index) DisplayTimeText(COOLDOWN_TIMELEFT(cd_source, cd_index))
+#define COOLDOWN_TIMELEFT_TEXT(cd_source, cd_name) DisplayTimeText(COOLDOWN_TIMELEFT(cd_source, cd_name))
+
+
+// when a timer should track targets individually
+// this is useful when an item should individually trigger cooldown time per mob
+#define COOLDOWN_LIST_DECLARE(cd_name) var/list/##cd_name = list()
+
+#define COOLDOWN_STATIC_LIST_DECLARE(cd_name) var/static/list/##cd_name = list()
+
+/// IMPORTANT: "cd_target_index" should be individual because it's assoc key
+#define COOLDOWN_LIST_START(cd_source, cd_name, cd_target_index, cd_time) (cd_source.cd_name[cd_target_index] = world.time + (cd_time))
+
+#define COOLDOWN_LIST_FINISHED(cd_source, cd_name, cd_target_index) (cd_source.cd_name[cd_target_index] < world.time)
+
+#define COOLDOWN_LIST_RESET(cd_source, cd_name, cd_target_index) cd_source.cd_name[cd_target_index] = 0
+
+#define COOLDOWN_LIST_TIMELEFT(cd_source, cd_name, cd_target_index) (max(0, cd_source.cd_name[cd_target_index] - world.time))
+
+/// use to change existing cooldown
+#define COOLDOWN_LIST_ADJUST_TIME(cd_source, cd_name, cd_target_index, cd_time) (cd_source.cd_name[cd_target_index] = cd_source.cd_name[cd_target_index] - (cd_time))
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index d27af0811c81c..1f4651031d458 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -56,7 +56,7 @@
/obj/item/reagent_containers/food/snacks/grown/proc/add_juice()
if(reagents)
if(bitesize_mod)
- bitesize = 1 + round(reagents.total_volume / bitesize_mod)
+ bitesize = max(1, round(reagents.total_volume / bitesize_mod))
return 1
return 0
diff --git a/code/modules/hydroponics/grown/cannabis.dm b/code/modules/hydroponics/grown/cannabis.dm
index 3aa6700245301..002d02e1e9a23 100644
--- a/code/modules/hydroponics/grown/cannabis.dm
+++ b/code/modules/hydroponics/grown/cannabis.dm
@@ -125,5 +125,6 @@
desc = "You feel dizzy looking at it. What the fuck?"
icon_state = "ocannabis"
volume = 420
+ bitesize = 1
wine_power = 90
discovery_points = 300
diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm
index 1257fd5306beb..3ad5407fccd44 100644
--- a/code/modules/hydroponics/plant_genes.dm
+++ b/code/modules/hydroponics/plant_genes.dm
@@ -251,10 +251,14 @@
// Also affects plant batteries see capatative cell production datum
name = "Electrical Activity"
rate = 0.2
+ COOLDOWN_STATIC_LIST_DECLARE(recent_victims)
/datum/plant_gene/trait/cell_charge/on_slip(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/C)
+ if(!COOLDOWN_LIST_FINISHED(src, recent_victims, FAST_REF(C))) // if you were a victim recently, you won't get it again
+ return
var/power = round(G.seed.potency*rate)
if(prob(power))
+ COOLDOWN_LIST_START(src, recent_victims, FAST_REF(C), 2 SECONDS)
C.electrocute_act(power, G, 1, 1)
var/turf/T = get_turf(C)
if(C.ckey != G.fingerprintslast)
@@ -262,10 +266,13 @@
log_combat(C, G, "slipped on and got electrocuted by", null, "with the power of 10. Last fingerprint: [G.fingerprintslast]")
/datum/plant_gene/trait/cell_charge/on_squash(obj/item/reagent_containers/food/snacks/grown/G, atom/target)
+ if(!COOLDOWN_LIST_FINISHED(src, recent_victims, target)) // if you were a victim recently, you won't get it again
+ return
if(iscarbon(target))
var/mob/living/carbon/C = target
var/power = G.seed.potency*rate
if(prob(power))
+ COOLDOWN_LIST_START(src, recent_victims, FAST_REF(C), 2 SECONDS)
C.electrocute_act(round(power), G, 1, 1)
if(C.ckey != G.fingerprintslast)
log_combat(G.thrownby, C, "hit and electrocuted", G, "at [AREACOORD(G)] with power of [power]")
@@ -439,6 +446,7 @@
/datum/plant_gene/trait/stinging
name = "Hypodermic Prickles"
+ COOLDOWN_STATIC_LIST_DECLARE(recent_victims)
/datum/plant_gene/trait/stinging/on_slip(obj/item/reagent_containers/food/snacks/grown/G, atom/target)
if(!isliving(target) || !G.reagents || !G.reagents.total_volume)
@@ -464,11 +472,23 @@
if(!L.reagents && !L.can_inject(null, 0))
return FALSE
- var/injecting_amount = max(1, G.seed.potency*0.2) // Minimum of 1, max of 20
- var/fraction = min(injecting_amount/G.reagents.total_volume, 1)
+ // Mechanism: It will always inject the amount as "injecting_amount" variable no matter the total size is
+ var/prick_efficiency = 0.02 // default (maximum: 2u)
+ if(locate(/datum/plant_gene/trait/squash) in G.seed.genes)
+ prick_efficiency = 0.01 // how can a smooth plant be deadly? (maximum: 1u)
+ else if(istype(G, /obj/item/seeds/nettle/death)) // as long as it has not Liquid Content
+ prick_efficiency = 0.06 // bonus to "death" nettle (maximum: 6u)
+ else if(G.force)
+ prick_efficiency = 0.04 // bonus to blunt plants (miaxmum: 4u)
+ if(!COOLDOWN_LIST_FINISHED(src, recent_victims, L)) // until 10s cooldown time from a victim is finished, the effective becomes half
+ prick_efficiency = round(prick_efficiency/2, 0.01) // i.e) 4u to 2u
+
+ var/injecting_amount = round(max(1, G.seed.potency * prick_efficiency), 0.1) // mimumum 1u to maximum 5/10/15/20u
+ var/fraction = min(injecting_amount/G.reagents.total_volume, 1) // Let's say you have 90u + 10u. Injecting 5u will be "4.5u + 0.5u" by the fraction
G.reagents.reaction(L, INJECT, fraction)
G.reagents.trans_to(L, injecting_amount)
to_chat(L, "You are pricked by [G]!")
+ COOLDOWN_LIST_START(src, recent_victims, FAST_REF(L), 10 SECONDS) // this refreshes 10s cooldown time. repeated attack will not be effective
return TRUE
/datum/plant_gene/trait/smoke
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index 5987fc61442c9..8191bdae974a8 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -248,6 +248,10 @@
return initial(plant.name)
else
return "[initial(plant.name)] (renamed as [plantname])"
+
+/obj/item/seeds/proc/log_rename(mob/user, obj/item, type, input)
+ src.investigate_log("[key_name(user)] has changed [src] ([type]) into '[input]'", INVESTIGATE_BOTANY)
+ // log_game() might fit, but I wanted botany logs contained in a specific log file.
//---------
/// Setters procs ///
@@ -405,20 +409,32 @@
var/input = stripped_input(user,"What do you want to name the plant?", default=plantname, max_length=MAX_NAME_LEN)
if(QDELETED(src) || !user.canUseTopic(src, BE_CLOSE))
return
+ if(CHAT_FILTER_CHECK(input))
+ to_chat(src, "That message contained a word prohibited in OOC chat! Consider reviewing the server rules.\n\"[input]\"")
+ return
+ log_rename(user, src, "plant name", input)
name = "pack of [input] seeds"
plantname = input
renamedByPlayer = TRUE
if(penchoice == "Plant Description")
- var/input = stripped_input(user,"What do you want to change the description of the plant to?", default=plantdesc, max_length=MAX_NAME_LEN)
+ var/input = stripped_input(user,"What do you want to change the description of the plant to?", default=plantdesc, max_length=MAX_MESSAGE_LEN)
if(QDELETED(src) || !user.canUseTopic(src, BE_CLOSE))
return
+ if(CHAT_FILTER_CHECK(input))
+ to_chat(src, "That message contained a word prohibited in OOC chat! Consider reviewing the server rules.\n\"[input]\"")
+ return
+ log_rename(user, src, "plant desc", input)
plantdesc = input
if(penchoice == "Seed Description")
- var/input = stripped_input(user,"What do you want to change the description of the seeds to?", default=desc, max_length=MAX_NAME_LEN)
+ var/input = stripped_input(user,"What do you want to change the description of the seeds to?", default=desc, max_length=MAX_MESSAGE_LEN)
if(QDELETED(src) || !user.canUseTopic(src, BE_CLOSE))
return
+ if(CHAT_FILTER_CHECK(input))
+ to_chat(src, "That message contained a word prohibited in OOC chat! Consider reviewing the server rules.\n\"[input]\"")
+ return
+ log_rename(user, src, "seed desc", input)
desc = input
..() // Fallthrough to item/attackby() so that bags can pick seeds up