diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm
index c39531971c..20c68c015b 100644
--- a/code/__DEFINES/achievements.dm
+++ b/code/__DEFINES/achievements.dm
@@ -32,6 +32,9 @@
#define MEDAL_VOID_ASCENSION "Void"
#define MEDAL_TOOLBOX_SOUL "Toolsoul"
#define MEDAL_CHEM_TUT "Beginner Chemist"
+#define MEDAL_HOT_DAMN "Hot Damn!"
+#define MEDAL_CAYENNE_DISK "Very Important Piscis"
+#define MEDAL_TRAM_SURFER "Tram Surfer"
//Skill medal hub IDs
#define MEDAL_LEGENDARY_MINER "Legendary Miner"
@@ -106,3 +109,19 @@
// DB ID for amount of consumed maintenance pills
#define MAINTENANCE_PILL_SCORE "Maintenance Pill Score"
+
+// DB ID for intento score
+#define INTENTO_SCORE "Intento Score"
+
+// Tourist related achievements and scores
+
+//centcom grades (achievement)
+
+#define MEDAL_BAD_SERVICE "Bad Service"
+#define MEDAL_OKAY_SERVICE "Okay Service"
+#define MEDAL_GOOD_SERVICE "Good Service"
+
+//scores
+
+#define CHEF_TOURISTS_SERVED "Tourists Served As Chef"
+#define BARTENDER_TOURISTS_SERVED "Tourists Served As Bartender"
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index db78144689..04c91cfb9d 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -59,3 +59,4 @@
#define COLOR_SILVER "#C0C0C0"
#define COLOR_GRAY "#808080"
#define COLOR_HALF_TRANSPARENT_BLACK "#0000007A"
+#define COLOR_BRIGHT_BLUE "#2CB2E8"
diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm
index 29c7a25dad..92231d3998 100644
--- a/code/__DEFINES/cooldowns.dm
+++ b/code/__DEFINES/cooldowns.dm
@@ -28,6 +28,11 @@
//INDEXES
#define COOLDOWN_EMPLOYMENT_CABINET "employment cabinet"
+//car cooldowns
+#define COOLDOWN_CAR_HONK "car_honk"
+
+//clown car cooldowns
+#define COOLDOWN_CLOWNCAR_RANDOMNESS "clown_car_randomness"
//TIMER COOLDOWN MACROS
diff --git a/code/__DEFINES/vehicles.dm b/code/__DEFINES/vehicles.dm
index 48383546b9..1ff14f9506 100644
--- a/code/__DEFINES/vehicles.dm
+++ b/code/__DEFINES/vehicles.dm
@@ -1,9 +1,20 @@
-//Vehicle control flags
+//Vehicle control flags. control flags describe access to actions in a vehicle.
-#define VEHICLE_CONTROL_PERMISSION 1
-#define VEHICLE_CONTROL_DRIVE 2
-#define VEHICLE_CONTROL_KIDNAPPED 4 //Can't leave vehicle voluntarily, has to resist.
+///controls the vehicles movement
+#define VEHICLE_CONTROL_DRIVE (1<<0)
+///Can't leave vehicle voluntarily, has to resist.
+#define VEHICLE_CONTROL_KIDNAPPED (1<<1)
+///melee attacks/shoves a vehicle may have
+#define VEHICLE_CONTROL_MELEE (1<<2)
+///using equipment/weapons on the vehicle
+#define VEHICLE_CONTROL_EQUIPMENT (1<<3)
+///changing around settings and the like.
+#define VEHICLE_CONTROL_SETTINGS (1<<4)
+//car_traits flags
+///Will this car kidnap people by ramming into them?
+#define CAN_KIDNAP (1<<0)
-//Car trait flags
-#define CAN_KIDNAP 1
+#define CLOWN_CANNON_INACTIVE 0
+#define CLOWN_CANNON_BUSY 1
+#define CLOWN_CANNON_READY 2
diff --git a/code/datums/achievements/_achievement_data.dm b/code/datums/achievements/_achievement_data.dm
index 80f544a965..12c8256c74 100644
--- a/code/datums/achievements/_achievement_data.dm
+++ b/code/datums/achievements/_achievement_data.dm
@@ -109,7 +109,7 @@
/datum/achievement_data/ui_data(mob/user)
var/ret_data = list() // screw standards (qustinnus you must rename src.data ok)
- ret_data["categories"] = list("Bosses", "Misc", "Mafia", "Scores")
+ ret_data["categories"] = list("Bosses", "Jobs", "Misc", "Mafia", "Scores")
ret_data["achievements"] = list()
ret_data["user_key"] = user.ckey
diff --git a/code/datums/achievements/_awards.dm b/code/datums/achievements/_awards.dm
index 77485de8ba..22296b90f5 100644
--- a/code/datums/achievements/_awards.dm
+++ b/code/datums/achievements/_awards.dm
@@ -2,7 +2,7 @@
///Name of the achievement, If null it won't show up in the achievement browser. (Handy for inheritance trees)
var/name
var/desc = "You did it."
- ///Found in UI_Icons/Achievements
+ ///Found in ui_icons/achievements
var/icon = "default"
var/category = "Normal"
@@ -78,7 +78,7 @@
/datum/award/achievement/on_unlock(mob/user)
. = ..()
- to_chat(user, "Achievement unlocked: [name]!")
+ to_chat(user, span_greenannounce("Achievement unlocked: [name]!"))
///Scores are for leaderboarded things, such as killcount of a specific boss
/datum/award/score
diff --git a/code/datums/achievements/boss_achievements.dm b/code/datums/achievements/boss_achievements.dm
index 104a369405..f2ff90945f 100644
--- a/code/datums/achievements/boss_achievements.dm
+++ b/code/datums/achievements/boss_achievements.dm
@@ -12,7 +12,7 @@
name = "Boss Killer"
desc = "You've come a long ways from asking how to switch hands."
database_id = "Boss Killer"
- // icon = "firstboss"
+ icon = "firstboss"
/datum/award/achievement/boss/blood_miner_kill
name = "Blood-Drunk Miner Killer"
diff --git a/code/datums/achievements/job_achievements.dm b/code/datums/achievements/job_achievements.dm
new file mode 100644
index 0000000000..13a68462ed
--- /dev/null
+++ b/code/datums/achievements/job_achievements.dm
@@ -0,0 +1,34 @@
+
+/datum/award/achievement/jobs
+ category = "Jobs"
+ icon = "basemisc"
+
+//chemistry
+
+/datum/award/achievement/jobs/chemistry_tut
+ name = "Perfect chemistry blossom"
+ desc = "Passed the chemistry tutorial with perfect purity!"
+ database_id = MEDAL_CHEM_TUT
+ icon = "chem_tut"
+
+//all of service! hip hip!
+
+/datum/award/achievement/jobs/service_bad
+ name = "Centcom Grade: Shitty Service"
+ desc = "Well, you at least tried. How about trying harder?"
+ database_id = MEDAL_BAD_SERVICE
+ icon = "chem_tut"
+
+/datum/award/achievement/jobs/service_okay
+ name = "Centcom Grade: Acceptable Service"
+ desc = "Well, it'll do! You and your department did just fine."
+ database_id = MEDAL_OKAY_SERVICE
+ icon = "chem_tut"
+
+/datum/award/achievement/jobs/service_good
+ name = "Centcom Grade: Exemplary Service"
+ desc = "Centcom is very impressed with your department!"
+ database_id = MEDAL_GOOD_SERVICE
+ icon = "chem_tut"
+
+//civilian achievies! while not recognized by the code, it is recognized by our hearts
diff --git a/code/datums/achievements/job_scores.dm b/code/datums/achievements/job_scores.dm
new file mode 100644
index 0000000000..d860c7e21a
--- /dev/null
+++ b/code/datums/achievements/job_scores.dm
@@ -0,0 +1,14 @@
+
+//chef
+
+/datum/award/score/chef_tourist_score
+ name = "Tourists Served as Chef Highscore"
+ desc = "Your highscore on serving tourist bots as chef."
+ database_id = CHEF_TOURISTS_SERVED
+
+//bartender
+
+/datum/award/score/bartender_tourist_score
+ name = "Tourists Served as Bartender Highscore"
+ desc = "Your highscore on serving tourist bots as bartender."
+ database_id = BARTENDER_TOURISTS_SERVED
diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm
index 0da38df8f3..5ad337445b 100644
--- a/code/datums/achievements/misc_achievements.dm
+++ b/code/datums/achievements/misc_achievements.dm
@@ -154,8 +154,20 @@
database_id = MEDAL_TOOLBOX_SOUL
icon = "toolbox_soul"
-/datum/award/achievement/misc/chemistry_tut
- name = "Perfect chemistry blossom"
- desc = "Passed the chemistry tutorial with perfect purity!"
- database_id = MEDAL_CHEM_TUT
- icon = "chem_tut"
+/datum/award/achievement/misc/hot_damn
+ name = "Hot Damn!"
+ desc = "Sometimes you need to make some noise to make a point."
+ database_id = MEDAL_HOT_DAMN
+ icon = "hotdamn"
+
+/datum/award/achievement/misc/cayenne_disk
+ name = "Very Important Piscis"
+ desc = "You can rest well now."
+ database_id = MEDAL_CAYENNE_DISK
+ icon = "cayenne_disk"
+
+/datum/award/achievement/misc/tram_surfer
+ name = "Tram Surfer"
+ desc = "Lights out, guerilla radio!"
+ database_id = MEDAL_TRAM_SURFER
+ icon = "tram_surfer"
diff --git a/code/datums/achievements/misc_scores.dm b/code/datums/achievements/misc_scores.dm
index 7ffc50c015..067cd68d7d 100644
--- a/code/datums/achievements/misc_scores.dm
+++ b/code/datums/achievements/misc_scores.dm
@@ -9,3 +9,9 @@
name = "Maintenance Pills Consumed"
desc = "Wait why?"
database_id = MAINTENANCE_PILL_SCORE
+
+///How high of a score on the Intento did we get?
+/datum/award/score/intento_score
+ name = "Intento Score"
+ desc = "A blast from the future?"
+ database_id = INTENTO_SCORE
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index 59464d25a7..683232b540 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -238,72 +238,73 @@
return
return ..()
-/obj/item/toy/plush/proc/love(obj/item/toy/plush/Kisser, mob/living/user) //~<3
- var/chance = 100 //to steal a kiss, surely there's a 100% chance no-one would reject a plush such as I?
- var/concern = 20 //perhaps something might cloud true love with doubt
- var/loyalty = 30 //why should another get between us?
- var/duty = 50 //conquering another's is what I live for
+/obj/item/toy/plush/proc/love(obj/item/toy/plush/Kisser, mob/living/user) //~<3
+ var/chance = 100 //to steal a kiss, surely there's a 100% chance no-one would reject a plush such as I?
+ var/concern = 20 //perhaps something might cloud true love with doubt
+ var/loyalty = 30 //why should another get between us?
+ var/duty = 50 //conquering another's is what I live for
//we are not catholic
if(young == TRUE || Kisser.young == TRUE)
- user.show_message("[src] plays tag with [Kisser].", MSG_VISUAL,
- "They're happy.", 0)
+ user.show_message(span_notice("[src] plays tag with [Kisser]."), MSG_VISUAL,
+ span_notice("They're happy."), NONE)
Kisser.cheer_up()
cheer_up()
//never again
else if(Kisser in scorned)
//message, visible, alternate message, neither visible nor audible
- user.show_message("[src] rejects the advances of [Kisser]!", MSG_VISUAL,
- "That didn't feel like it worked.", 0)
+ user.show_message(span_notice("[src] rejects the advances of [Kisser]!"), MSG_VISUAL,
+ span_notice("That didn't feel like it worked."), NONE)
else if(src in Kisser.scorned)
- user.show_message("[Kisser] realises who [src] is and turns away.", MSG_VISUAL,
- "That didn't feel like it worked.", 0)
+ user.show_message(span_notice("[Kisser] realises who [src] is and turns away."), MSG_VISUAL,
+ span_notice("That didn't feel like it worked."), NONE)
//first comes love
- else if(Kisser.lover != src && Kisser.partner != src) //cannot be lovers or married
- if(Kisser.lover) //if the initiator has a lover
- Kisser.lover.heartbreak(Kisser) //the old lover can get over the kiss-and-run whilst the kisser has some fun
- chance -= concern //one heart already broken, what does another mean?
- if(lover) //if the recipient has a lover
- chance -= loyalty //mustn't... but those lips
- if(partner) //if the recipient has a partner
- chance -= duty //do we mate for life?
+ else if(Kisser.lover != src && Kisser.partner != src) //cannot be lovers or married
+ if(Kisser.lover) //if the initiator has a lover
+ Kisser.lover.heartbreak(Kisser) //the old lover can get over the kiss-and-run whilst the kisser has some fun
+ chance -= concern //one heart already broken, what does another mean?
+ if(lover) //if the recipient has a lover
+ chance -= loyalty //mustn't... but those lips
+ if(partner) //if the recipient has a partner
+ chance -= duty //do we mate for life?
- if(prob(chance)) //did we bag a date?
- user.visible_message("[user] makes [Kisser] kiss [src]!",
- "You make [Kisser] kiss [src]!")
- if(lover) //who cares for the past, we live in the present
+ if(prob(chance)) //did we bag a date?
+ user.visible_message(span_notice("[user] makes [Kisser] kiss [src]!"),
+ span_notice("You make [Kisser] kiss [src]!"))
+ if(lover) //who cares for the past, we live in the present
lover.heartbreak(src)
new_lover(Kisser)
Kisser.new_lover(src)
else
- user.show_message("[src] rejects the advances of [Kisser], maybe next time?", MSG_VISUAL,
- "That didn't feel like it worked, this time.", 0)
+ user.show_message(span_notice("[src] rejects the advances of [Kisser], maybe next time?"), MSG_VISUAL,
+ span_notice("That didn't feel like it worked, this time."), NONE)
//then comes marriage
- else if(Kisser.lover == src && Kisser.partner != src) //need to be lovers (assumes loving is a two way street) but not married (also assumes similar)
- user.visible_message("[user] pronounces [Kisser] and [src] married! D'aw.",
- "You pronounce [Kisser] and [src] married!")
+ else if(Kisser.lover == src && Kisser.partner != src) //need to be lovers (assumes loving is a two way street) but not married (also assumes similar)
+ user.visible_message(span_notice("[user] pronounces [Kisser] and [src] married! D'aw."),
+ span_notice("You pronounce [Kisser] and [src] married!"))
new_partner(Kisser)
Kisser.new_partner(src)
//then comes a baby in a baby's carriage, or an adoption in an adoption's orphanage
- else if(Kisser.partner == src && !plush_child) //the one advancing does not take ownership of the child and we have a one child policy in the toyshop
- user.visible_message("[user] is going to break [Kisser] and [src] by bashing them like that.",
- "[Kisser] passionately embraces [src] in your hands. Look away you perv!")
+ else if(Kisser.partner == src && !plush_child) //the one advancing does not take ownership of the child and we have a one child policy in the toyshop
+ user.visible_message(span_notice("[user] is going to break [Kisser] and [src] by bashing them like that."),
+ span_notice("[Kisser] passionately embraces [src] in your hands. Look away you perv!"))
+ user.client.give_award(/datum/award/achievement/misc/rule8, user)
if(plop(Kisser))
- user.visible_message("Something drops at the feet of [user].",
- "The miracle of oh god did that just come out of [src]?!")
+ user.visible_message(span_notice("Something drops at the feet of [user]."),
+ span_notice("The miracle of oh god did that just come out of [src]?!"))
//then comes protection, or abstinence if we are catholic
else if(Kisser.partner == src && plush_child)
- user.visible_message("[user] makes [Kisser] nuzzle [src]!",
- "You make [Kisser] nuzzle [src]!")
+ user.visible_message(span_notice("[user] makes [Kisser] nuzzle [src]!"),
+ span_notice("You make [Kisser] nuzzle [src]!"))
//then oh fuck something unexpected happened
else
- user.show_message("[Kisser] and [src] don't know what to do with one another.", 0)
+ user.show_message(span_warning("[Kisser] and [src] don't know what to do with one another."), NONE)
/obj/item/toy/plush/proc/heartbreak(obj/item/toy/plush/Brutus)
if(lover != Brutus)
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index 7a067257cb..56cf50993b 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -79,6 +79,8 @@ GLOBAL_LIST_EMPTY(rubber_toolbox_icons)
name = "mechanical toolbox"
icon_state = "blue"
item_state = "toolbox_blue"
+ /// If FALSE, someone with a ensouled soulstone can sacrifice a spirit to change the sprite of this toolbox.
+ var/has_soul = FALSE
/obj/item/storage/toolbox/mechanical/PopulateContents()
new /obj/item/screwdriver(src)
@@ -93,6 +95,7 @@ GLOBAL_LIST_EMPTY(rubber_toolbox_icons)
icon_state = "toolbox_blue_old"
has_latches = FALSE
can_rubberify = FALSE
+ has_soul = TRUE
/obj/item/storage/toolbox/mechanical/old/heirloom
name = "old, robust toolbox" //this will be named "X family toolbox"
@@ -126,7 +129,7 @@ GLOBAL_LIST_EMPTY(rubber_toolbox_icons)
/obj/item/storage/toolbox/mechanical/old/clean/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
calc_damage()
..()
-
+
/obj/item/storage/toolbox/mechanical/old/clean/PopulateContents()
new /obj/item/screwdriver(src)
new /obj/item/wrench(src)
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
index 023be4b3e7..15ac8a6cb6 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
@@ -226,16 +226,16 @@
user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shed_human_form)
if(!ishuman(user))
return
- var/mob/living/carbon/human/H = user
- H.physiology.brute_mod *= 0.5
- H.physiology.burn_mod *= 0.5
- var/datum/antagonist/heretic/heretic = user.mind.has_antag_datum(/datum/antagonist/heretic)
- var/datum/eldritch_knowledge/flesh_grasp/ghoul1 = heretic.get_knowledge(/datum/eldritch_knowledge/flesh_grasp)
- ghoul1.ghoul_amt *= 3
- var/datum/eldritch_knowledge/flesh_ghoul/ghoul2 = heretic.get_knowledge(/datum/eldritch_knowledge/flesh_ghoul)
- ghoul2.max_amt *= 3
+ var/mob/living/carbon/human/lord_of_arms = user
+ lord_of_arms.physiology.brute_mod *= 0.5
+ lord_of_arms.physiology.burn_mod *= 0.5
+ lord_of_arms.client?.give_award(/datum/award/achievement/misc/flesh_ascension, lord_of_arms)
+ var/datum/antagonist/heretic/heretic_datum = user.mind.has_antag_datum(/datum/antagonist/heretic)
+ var/datum/eldritch_knowledge/flesh_grasp/grasp_ghoul = heretic_datum.get_knowledge(/datum/eldritch_knowledge/flesh_grasp)
+ grasp_ghoul.ghoul_amt *= 3
+ var/datum/eldritch_knowledge/flesh_ghoul/better_ghoul = heretic_datum.get_knowledge(/datum/eldritch_knowledge/flesh_ghoul)
+ better_ghoul.max_amt *= 3
- return ..()
/datum/eldritch_knowledge/flesh_blade_upgrade_2
name = "Remembrance"
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
index 509c35261e..733b2725cb 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
@@ -181,13 +181,13 @@
var/mob/living/carbon/human/H = user
H.physiology.brute_mod *= 0.5
H.physiology.burn_mod *= 0.5
+ H.client?.give_award(/datum/award/achievement/misc/rust_ascension, H)
priority_announce("$^@*$^@(#&$(@^$^@# Fear the decay, for the Rustbringer, [user.real_name] has ascended! None shall escape the corrosion! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/announcer/classic/spanomalies.ogg')
new /datum/rust_spread(loc)
var/datum/antagonist/heretic/ascension = H.mind.has_antag_datum(/datum/antagonist/heretic)
ascension.ascended = TRUE
return ..()
-
/datum/eldritch_knowledge/final/rust_final/on_life(mob/user)
. = ..()
if(!finished)
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/void_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/void_lore.dm
index 97b5af0f4d..dcbc01b046 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/void_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/void_lore.dm
@@ -183,14 +183,14 @@
var/datum/weather/void_storm/storm
/datum/eldritch_knowledge/final/void_final/on_finished_recipe(mob/living/user, list/atoms, loc)
- var/mob/living/carbon/human/H = user
- user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/eldritch)
- H.physiology.brute_mod *= 0.5
- H.physiology.burn_mod *= 0.5
- ADD_TRAIT(H, TRAIT_RESISTLOWPRESSURE, MAGIC_TRAIT)
- priority_announce("$^@*$^@(#&$(@^$^@# The nobleman of void [H.real_name] has arrived, step along the Waltz that ends worlds! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/announcer/classic/spanomalies.ogg')
-
- sound_loop = new(list(user),TRUE,TRUE)
+ var/mob/living/carbon/human/waltzing = user
+ waltzing.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/eldritch)
+ waltzing.physiology.brute_mod *= 0.5
+ waltzing.physiology.burn_mod *= 0.5
+ ADD_TRAIT(waltzing, TRAIT_RESISTLOWPRESSURE, MAGIC_TRAIT)
+ waltzing.client?.give_award(/datum/award/achievement/misc/void_ascension, waltzing)
+ priority_announce("$^@*$^@(#&$(@^$^@# The nobleman of void [waltzing.real_name] has arrived, step along the Waltz that ends worlds! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/announcer/classic/spanomalies.ogg')
+ sound_loop = new(user, TRUE, TRUE)
return ..()
/datum/eldritch_knowledge/final/void_final/on_death()
diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm
index d4d70cd053..fe9770e64a 100644
--- a/code/modules/antagonists/wizard/equipment/soulstone.dm
+++ b/code/modules/antagonists/wizard/equipment/soulstone.dm
@@ -50,6 +50,14 @@
A.death()
return ..()
+/obj/item/soulstone/proc/hot_potato(mob/living/user)
+ to_chat(user, span_userdanger("Holy magics residing in \the [src] burn your hand!"))
+ var/obj/item/bodypart/affecting = user.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
+ affecting.receive_damage( 0, 10 ) // 10 burn damage
+ user.emote("scream")
+ user.update_damage_overlays()
+ user.dropItemToGround(src)
+
//////////////////////////////Capturing////////////////////////////////////////////////////////
/obj/item/soulstone/attack(mob/living/carbon/human/M, mob/living/user)
@@ -94,6 +102,35 @@
to_chat(A, "You have been released from your prison, but you are still bound to the cult's will. Help them succeed in their goals at all costs.")
was_used()
+/obj/item/soulstone/pre_attack(atom/A, mob/living/user, params)
+ var/mob/living/simple_animal/hostile/construct/shade/occupant = (locate() in src)
+ var/obj/item/storage/toolbox/mechanical/target_toolbox = A
+ if(!occupant || !istype(target_toolbox) || target_toolbox.has_soul)
+ return ..()
+
+ if(iscultist(user))
+ hot_potato(user)
+ return
+ if(!iscultist(user, TRUE) && !iswizard(user) && !usability)
+ user.Unconscious(10 SECONDS)
+ to_chat(user, span_userdanger("Your body is wracked with debilitating pain!"))
+ return
+
+ user.visible_message("[user] holds [src] above [user.p_their()] head and forces it into [target_toolbox] with a flash of light!", \
+ span_notice("You hold [src] above your head briefly, then force it into [target_toolbox], transferring the [occupant]'s soul!"), ignored_mobs = occupant)
+ to_chat(occupant, span_userdanger("[user] holds you up briefly, then forces you into [target_toolbox]!"))
+ to_chat(occupant, span_deadsay("Your eternal soul has been sacrificed to restore the soul of a toolbox. Them's the breaks!"))
+
+ occupant.client?.give_award(/datum/award/achievement/misc/toolbox_soul, occupant)
+ occupant.deathmessage = "shrieks out in unholy pain as [occupant.p_their()] soul is absorbed into [target_toolbox]!"
+ release_shades(user, TRUE)
+ occupant.death()
+
+ target_toolbox.name = "soulful toolbox"
+ target_toolbox.icon_state = "toolbox_blue_old"
+ target_toolbox.has_soul = TRUE
+ target_toolbox.has_latches = FALSE
+
///////////////////////////Transferring to constructs/////////////////////////////////////////////////////
/obj/structure/constructshell
name = "empty shell"
diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm
index 8388d6501a..747abb4b19 100644
--- a/code/modules/mob/living/simple_animal/hostile/carp.dm
+++ b/code/modules/mob/living/simple_animal/hostile/carp.dm
@@ -95,20 +95,78 @@
/mob/living/simple_animal/hostile/carp/cayenne
name = "Cayenne"
+ real_name = "Cayenne"
desc = "A failed Syndicate experiment in weaponized space carp technology, it now serves as a lovable mascot."
gender = FEMALE
- regen_amount = 8
-
speak_emote = list("squeaks")
- maxHealth = 90
- health = 90
- gold_core_spawnable = NO_SPAWN
- faction = list(ROLE_SYNDICATE, "carp") //They are still a carp
AIStatus = AI_OFF
+ gold_core_spawnable = NO_SPAWN
+ faction = list(ROLE_SYNDICATE)
+ /// Keeping track of the nuke disk for the functionality of storing it.
+ var/obj/item/disk/nuclear/disky
+ /// Location of the file storing disk overlays
+ // var/icon/disk_overlay_file = 'icons/mob/carp.dmi'
+ /// Colored disk mouth appearance for adding it as a mouth overlay
+ var/mutable_appearance/colored_disk_mouth
- harm_intent_damage = 12
- obj_damage = 70
- melee_damage_lower = 15
- melee_damage_upper = 18
+/mob/living/simple_animal/hostile/carp/cayenne/Initialize()
+ . = ..()
+ // AddElement(/datum/element/pet_bonus, "bloops happily!")
+ // colored_disk_mouth = mutable_appearance(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/carp/disk_mouth, greyscale_colors), "disk_mouth")
+ ADD_TRAIT(src, TRAIT_DISK_VERIFIER, INNATE_TRAIT) //carp can verify disky
+
+/mob/living/simple_animal/hostile/carp/cayenne/IsAdvancedToolUser()
+ return TRUE //carp SMART
+
+/mob/living/simple_animal/hostile/carp/cayenne/death(gibbed)
+ if(disky)
+ disky.forceMove(drop_location())
+ disky = null
+ return ..()
+
+/mob/living/simple_animal/hostile/carp/cayenne/Destroy(force)
+ QDEL_NULL(disky)
+ return ..()
+
+/mob/living/simple_animal/hostile/carp/cayenne/examine(mob/user)
+ . = ..()
+ if(disky)
+ . += span_notice("Wait... is that [disky] in [p_their()] mouth?")
+
+/mob/living/simple_animal/hostile/carp/cayenne/AttackingTarget(atom/attacked_target)
+ if(istype(attacked_target, /obj/item/disk/nuclear))
+ var/obj/item/disk/nuclear/potential_disky = attacked_target
+ if(potential_disky.anchored)
+ return
+ potential_disky.forceMove(src)
+ disky = potential_disky
+ to_chat(src, span_nicegreen("YES!! You manage to pick up [disky]. (Click anywhere to place it back down.)"))
+ update_icon()
+ if(!disky.fake)
+ client.give_award(/datum/award/achievement/misc/cayenne_disk, src)
+ return
+ if(disky)
+ if(isopenturf(attacked_target))
+ to_chat(src, span_notice("You place [disky] on [attacked_target]"))
+ disky.forceMove(attacked_target.drop_location())
+ disky = null
+ update_icon()
+ else
+ disky.melee_attack_chain(src, attacked_target)
+ return
+ return ..()
+
+/mob/living/simple_animal/hostile/carp/cayenne/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(disky == gone)
+ disky = null
+ update_icon()
+
+/mob/living/simple_animal/hostile/carp/cayenne/update_overlays()
+ . = ..()
+ if(!disky || stat == DEAD)
+ return
+ // . += colored_disk_mouth
+ // . += mutable_appearance(disk_overlay_file, "disk_overlay")
#undef REGENERATION_DELAY
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
index 869f29951b..938483be84 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
@@ -32,6 +32,9 @@ Difficulty: Hard
wander = FALSE
del_on_death = TRUE
blood_volume = BLOOD_VOLUME_NORMAL
+ achievement_type = /datum/award/achievement/boss/wendigo_kill
+ crusher_achievement_type = /datum/award/achievement/boss/wendigo_crusher
+ score_achievement_type = /datum/award/score/wendigo_score
deathmessage = "falls, shaking the ground around it"
deathsound = 'sound/effects/gravhit.ogg'
attack_action_types = list(/datum/action/innate/megafauna_attack/heavy_stomp,
diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm
index 12e9f365d0..d0144269b7 100644
--- a/code/modules/vehicles/_vehicle.dm
+++ b/code/modules/vehicles/_vehicle.dm
@@ -91,9 +91,9 @@
/obj/vehicle/proc/after_add_occupant(mob/M)
auto_assign_occupant_flags(M)
-/obj/vehicle/proc/auto_assign_occupant_flags(mob/M) //override for each type that needs it. Default is assign driver if drivers is not at max.
+/obj/vehicle/proc/auto_assign_occupant_flags(mob/M) //override for each type that needs it. Default is assign driver if drivers is not at max.
if(driver_amount() < max_drivers)
- add_control_flags(M, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_PERMISSION)
+ add_control_flags(M, VEHICLE_CONTROL_DRIVE)
/obj/vehicle/proc/remove_occupant(mob/M)
if(!istype(M))
diff --git a/code/modules/vehicles/cars/car.dm b/code/modules/vehicles/cars/car.dm
index 08a7986fa3..0196abe82c 100644
--- a/code/modules/vehicles/cars/car.dm
+++ b/code/modules/vehicles/cars/car.dm
@@ -16,7 +16,7 @@
. = ..()
initialize_controller_action_type(/datum/action/vehicle/sealed/remove_key, VEHICLE_CONTROL_DRIVE)
if(car_traits & CAN_KIDNAP)
- initialize_controller_action_type(/datum/action/vehicle/sealed/DumpKidnappedMobs, VEHICLE_CONTROL_DRIVE)
+ initialize_controller_action_type(/datum/action/vehicle/sealed/dump_kidnapped_mobs, VEHICLE_CONTROL_DRIVE)
/obj/vehicle/sealed/car/driver_move(mob/user, direction)
if(key_type && !is_key(inserted_key))
diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm
index 0b75dbf4db..c4da65a902 100644
--- a/code/modules/vehicles/cars/clowncar.dm
+++ b/code/modules/vehicles/cars/clowncar.dm
@@ -1,6 +1,6 @@
/obj/vehicle/sealed/car/clowncar
name = "clown car"
- desc = "How someone could even fit in there is beyond me."
+ desc = "How someone could even fit in there is byond me."
icon_state = "clowncar"
max_integrity = 150
armor = list("melee" = 70, "bullet" = 40, "laser" = 40, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
@@ -10,25 +10,36 @@
car_traits = CAN_KIDNAP
key_type = /obj/item/bikehorn
key_type_exact = FALSE
- var/droppingoil = FALSE
- var/RTDcooldown = 150
- var/lastRTDtime = 0
+ ///list of headlight colors we use to pick through when we have party mode due to emag
+ var/headlight_colors = list(COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_LIME, COLOR_BRIGHT_BLUE, COLOR_CYAN, COLOR_PURPLE)
+ ///Cooldown time inbetween [/obj/vehicle/sealed/car/clowncar/proc/roll_the_dice()] usages
+ var/dice_cooldown_time = 150
+ ///How many times kidnappers in the clown car said thanks
+ var/thankscount = 0
+ ///Current status of the cannon, alternates between CLOWN_CANNON_INACTIVE, CLOWN_CANNON_BUSY and CLOWN_CANNON_READY
+ var/cannonmode = CLOWN_CANNON_INACTIVE
+ var/light_on = TRUE
+
+/obj/vehicle/sealed/car/clowncar/Initialize()
+ . = ..()
+ START_PROCESSING(SSobj,src)
+
+/obj/vehicle/sealed/car/clowncar/process()
+ if(light_on && (obj_flags & EMAGGED))
+ set_light_color(pick(headlight_colors))
/obj/vehicle/sealed/car/clowncar/generate_actions()
. = ..()
- initialize_controller_action_type(/datum/action/vehicle/sealed/horn/clowncar, VEHICLE_CONTROL_DRIVE)
-
-/obj/vehicle/sealed/car/clowncar/driver_move(mob/user, direction) //Prevent it from moving onto space
- if(isspaceturf(get_step(src, direction)))
- return FALSE
- else
- return ..()
+ initialize_controller_action_type(/datum/action/vehicle/sealed/horn, VEHICLE_CONTROL_DRIVE)
+ initialize_controller_action_type(/datum/action/vehicle/sealed/thank, VEHICLE_CONTROL_KIDNAPPED)
/obj/vehicle/sealed/car/clowncar/auto_assign_occupant_flags(mob/M)
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.mind && HAS_TRAIT(H.mind, TRAIT_CLOWN_MENTALITY)) //Ensures only clowns can drive the car. (Including more at once)
- add_control_flags(M, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_PERMISSION)
+ add_control_flags(H, VEHICLE_CONTROL_DRIVE)
+ RegisterSignal(H, COMSIG_MOB_CLICKON, .proc/fire_cannon_at)
+ M.log_message("has entered [src] as a possible driver", LOG_ATTACK)
return
add_control_flags(M, VEHICLE_CONTROL_KIDNAPPED)
@@ -36,106 +47,195 @@
. = ..()
playsound(src, pick('sound/vehicles/clowncar_load1.ogg', 'sound/vehicles/clowncar_load2.ogg'), 75)
+/obj/vehicle/sealed/car/clowncar/after_add_occupant(mob/M, control_flags)
+ . = ..()
+ if(return_controllers_with_flag(VEHICLE_CONTROL_KIDNAPPED).len >= 30)
+ for(var/mob/voreman as anything in return_drivers())
+ voreman.client.give_award(/datum/award/achievement/misc/round_and_full, voreman)
+
+/obj/vehicle/sealed/car/clowncar/attack_animal(mob/living/simple_animal/user, list/modifiers)
+ if((user.loc != src) || user.environment_smash & (ENVIRONMENT_SMASH_WALLS|ENVIRONMENT_SMASH_RWALLS))
+ return ..()
+
+/obj/vehicle/sealed/car/clowncar/mob_exit(mob/M, silent = FALSE, randomstep = FALSE)
+ . = ..()
+ UnregisterSignal(M, COMSIG_MOB_CLICKON)
+
/obj/vehicle/sealed/car/clowncar/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
. = ..()
if(prob(33))
- visible_message("[src] spews out a ton of space lube!")
+ visible_message(span_danger("[src] spews out a ton of space lube!"))
new /obj/effect/particle_effect/foam(loc) //YEET
-/obj/vehicle/sealed/car/clowncar/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
+/obj/vehicle/sealed/car/clowncar/attacked_by(obj/item/I, mob/living/user)
. = ..()
- if(istype(I, /obj/item/reagent_containers/food/snacks/grown/banana))
- var/obj/item/reagent_containers/food/snacks/grown/banana/banana = I
- obj_integrity += min(banana.seed.potency, max_integrity-obj_integrity)
- to_chat(user, "You use the [banana] to repair the [src]!")
- qdel(banana)
+ if(!istype(I, /obj/item/reagent_containers/food/snacks/grown/banana))
+ return
+ var/obj/item/reagent_containers/food/snacks/grown/banana/banana = I
+ obj_integrity += min(banana.seed.potency, max_integrity-obj_integrity)
+ to_chat(user, span_danger("You use the [banana] to repair the [src]!"))
+ qdel(banana)
-/obj/vehicle/sealed/car/clowncar/Bump(atom/movable/M)
+/obj/vehicle/sealed/car/clowncar/Bump(atom/bumped)
. = ..()
- if(isliving(M))
- if(ismegafauna(M))
+ if(isliving(bumped))
+ if(ismegafauna(bumped))
return
- var/mob/living/L = M
- if(iscarbon(L))
- var/mob/living/carbon/C = L
- C.DefaultCombatKnockdown(40) //I play to make sprites go horizontal
- L.visible_message("[src] rams into [L] and sucks him up!") //fuck off shezza this isn't ERP.
- mob_forced_enter(L)
+ var/mob/living/hittarget_living = bumped
+ if(iscarbon(hittarget_living))
+ var/mob/living/carbon/carb = hittarget_living
+ carb.DefaultCombatKnockdown(40) //I play to make sprites go horizontal
+ hittarget_living.visible_message(span_warning("[src] rams into [hittarget_living] and sucks [hittarget_living.p_them()] up!")) //fuck off shezza this isn't ERP.
+ mob_forced_enter(hittarget_living)
playsound(src, pick('sound/vehicles/clowncar_ram1.ogg', 'sound/vehicles/clowncar_ram2.ogg', 'sound/vehicles/clowncar_ram3.ogg'), 75)
- else if(istype(M, /turf/closed) || istype(M, /obj/machinery/door/airlock/external))
- visible_message("[src] rams into [M] and crashes!")
- playsound(src, pick('sound/vehicles/clowncar_crash1.ogg', 'sound/vehicles/clowncar_crash2.ogg'), 75)
- playsound(src, 'sound/vehicles/clowncar_crashpins.ogg', 75)
- DumpMobs(TRUE)
+ log_combat(src, hittarget_living, "sucked up")
+ return
+ if(!istype(bumped, /turf/closed))
+ return
+ visible_message(span_warning("[src] rams into [bumped] and crashes!"))
+ playsound(src, pick('sound/vehicles/clowncar_crash1.ogg', 'sound/vehicles/clowncar_crash2.ogg'), 75)
+ playsound(src, 'sound/vehicles/clowncar_crashpins.ogg', 75)
+ DumpMobs(TRUE)
+ log_combat(src, bumped, "crashed into", null, "dumping all passengers")
/obj/vehicle/sealed/car/clowncar/emag_act(mob/user)
. = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
- to_chat(user, "You scramble the clowncar child safety lock and a panel with 6 colorful buttons appears!")
- initialize_controller_action_type(/datum/action/vehicle/sealed/RollTheDice, VEHICLE_CONTROL_DRIVE)
+ to_chat(user, span_danger("You scramble \the [src]'s child safety lock, and a panel with six colorful buttons appears!"))
+ initialize_controller_action_type(/datum/action/vehicle/sealed/roll_the_dice, VEHICLE_CONTROL_DRIVE)
+ initialize_controller_action_type(/datum/action/vehicle/sealed/cannon, VEHICLE_CONTROL_DRIVE)
return TRUE
-/obj/vehicle/sealed/car/clowncar/Destroy()
+/obj/vehicle/sealed/car/clowncar/obj_destruction(damage_flag)
playsound(src, 'sound/vehicles/clowncar_fart.ogg', 100)
+ STOP_PROCESSING(SSobj,src)
return ..()
-/obj/vehicle/sealed/car/clowncar/after_move(direction)
- . = ..()
- if(droppingoil)
- new /obj/effect/decal/cleanable/oil/slippery(loc)
-
-/obj/vehicle/sealed/car/clowncar/proc/RollTheDice(mob/user)
- if(world.time - lastRTDtime < RTDcooldown)
- to_chat(user, "The button panel is currently recharging.")
+/**
+ * Plays a random funky effect
+ * Only available while car is emagged
+ * Possible effects:
+ * * Spawn bananapeel
+ * * Spawn random reagent foam
+ * * Make the clown car look like a singulo temporarily
+ * * Spawn Laughing chem gas
+ * * Drop oil
+ * * Fart and make everyone nearby laugh
+ */
+/obj/vehicle/sealed/car/clowncar/proc/roll_the_dice(mob/user)
+ if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_CLOWNCAR_RANDOMNESS))
+ to_chat(user, span_notice("The button panel is currently recharging."))
return
- lastRTDtime = world.time
- var/randomnum = rand(1,6)
- switch(randomnum)
+ TIMER_COOLDOWN_START(src, COOLDOWN_CLOWNCAR_RANDOMNESS, dice_cooldown_time)
+ switch(rand(1,6))
if(1)
- visible_message("[user] has pressed one of the colorful buttons on [src] and a special banana peel drops out of it.")
+ visible_message(span_danger("[user] presses one of the colorful buttons on [src], and a special banana peel drops out of it."))
new /obj/item/grown/bananapeel/specialpeel(loc)
if(2)
- visible_message("[user] has pressed one of the colorful buttons on [src] and unknown chemicals flood out of it.")
- var/datum/reagents/R = new/datum/reagents(300)
- R.my_atom = src
- R.add_reagent(get_random_reagent_id(), 100)
+ visible_message(span_danger("[user] presses one of the colorful buttons on [src], and unknown chemicals flood out of it."))
+ var/datum/reagents/randomchems = new/datum/reagents(300)
+ randomchems.my_atom = src
+ randomchems.add_reagent(get_random_reagent_id(), 100)
var/datum/effect_system/foam_spread/foam = new
- foam.set_up(200, loc, R)
+ foam.set_up(200, loc, randomchems)
foam.start()
if(3)
- visible_message("[user] has pressed one of the colorful buttons on [src] and the clown car turns on its singularity disguise system.")
+ visible_message(span_danger("[user] presses one of the colorful buttons on [src], and the clown car turns on its singularity disguise system."))
icon = 'icons/obj/singularity.dmi'
icon_state = "singularity_s1"
- addtimer(CALLBACK(src, .proc/ResetIcon), 100)
+ addtimer(CALLBACK(src, .proc/reset_icon), 10 SECONDS)
if(4)
- visible_message("[user] has pressed one of the colorful buttons on [src] and the clown car spews out a cloud of laughing gas.")
- var/datum/reagents/R = new/datum/reagents(300)
- R.my_atom = src
- R.add_reagent(/datum/reagent/consumable/superlaughter, 50)
+ visible_message(span_danger("[user] presses one of the colorful buttons on [src], and the clown car spews out a cloud of laughing gas."))
+ var/datum/reagents/funnychems = new/datum/reagents(300)
+ funnychems.my_atom = src
+ funnychems.add_reagent(/datum/reagent/consumable/superlaughter, 50)
var/datum/effect_system/smoke_spread/chem/smoke = new()
- smoke.set_up(R, 4)
+ smoke.set_up(funnychems, 4)
smoke.attach(src)
smoke.start()
if(5)
- visible_message("[user] has pressed one of the colorful buttons on [src] and the clown car starts dropping an oil trail.")
- droppingoil = TRUE
- addtimer(CALLBACK(src, .proc/StopDroppingOil), 30)
+ visible_message(span_danger("[user] presses one of the colorful buttons on [src], and the clown car starts dropping an oil trail."))
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/cover_in_oil)
+ addtimer(CALLBACK(src, .proc/stop_dropping_oil), 3 SECONDS)
if(6)
- visible_message("[user] has pressed one of the colorful buttons on [src] and the clown car lets out a comedic toot.")
+ visible_message(span_danger("[user] presses one of the colorful buttons on [src], and the clown car lets out a comedic toot."))
playsound(src, 'sound/vehicles/clowncar_fart.ogg', 100)
for(var/mob/living/L in orange(loc, 6))
L.emote("laughs")
- for(var/mob/living/L in occupants)
+ for(var/mob/living/L as anything in occupants)
L.emote("laughs")
-/obj/vehicle/sealed/car/clowncar/proc/ResetIcon()
+///resets the icon and iconstate of the clowncar after it was set to singulo states
+/obj/vehicle/sealed/car/clowncar/proc/reset_icon()
icon = initial(icon)
icon_state = initial(icon_state)
-/obj/vehicle/sealed/car/clowncar/proc/StopDroppingOil()
- droppingoil = FALSE
+///Deploys oil when the clowncar moves in oil deploy mode
+/obj/vehicle/sealed/car/clowncar/proc/cover_in_oil()
+ SIGNAL_HANDLER
+ new /obj/effect/decal/cleanable/oil/slippery(loc)
+
+///Stops dropping oil after the time has run up
+/obj/vehicle/sealed/car/clowncar/proc/stop_dropping_oil()
+ UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
+
+///Toggles the on and off state of the clown cannon that shoots random kidnapped people
+/obj/vehicle/sealed/car/clowncar/proc/toggle_cannon(mob/user)
+ if(cannonmode == CLOWN_CANNON_BUSY)
+ to_chat(user, span_notice("Please wait for the vehicle to finish its current action first."))
+ return
+ if(cannonmode) //canon active, deactivate
+ flick("clowncar_fromfire", src)
+ icon_state = "clowncar"
+ addtimer(CALLBACK(src, .proc/deactivate_cannon), 2 SECONDS)
+ playsound(src, 'sound/vehicles/clowncar_cannonmode2.ogg', 75)
+ visible_message(span_danger("The [src] starts going back into mobile mode."))
+ else
+ canmove = FALSE //anchor and activate canon
+ flick("clowncar_tofire", src)
+ icon_state = "clowncar_fire"
+ visible_message(span_danger("The [src] opens up and reveals a large cannon."))
+ addtimer(CALLBACK(src, .proc/activate_cannon), 2 SECONDS)
+ playsound(src, 'sound/vehicles/clowncar_cannonmode1.ogg', 75)
+ cannonmode = CLOWN_CANNON_BUSY
+
+///Finalizes canon activation
+/obj/vehicle/sealed/car/clowncar/proc/activate_cannon()
+ // mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi'
+ cannonmode = CLOWN_CANNON_READY
+ for(var/mob/living/driver as anything in return_controllers_with_flag(VEHICLE_CONTROL_DRIVE))
+ driver.update_mouse_pointer()
+
+///Finalizes canon deactivation
+/obj/vehicle/sealed/car/clowncar/proc/deactivate_cannon()
+ canmove = TRUE
+ // mouse_pointer = null
+ cannonmode = CLOWN_CANNON_INACTIVE
+ for(var/mob/living/driver as anything in return_controllers_with_flag(VEHICLE_CONTROL_DRIVE))
+ driver.update_mouse_pointer()
+
+///Fires the cannon where the user clicks
+/obj/vehicle/sealed/car/clowncar/proc/fire_cannon_at(mob/user, atom/A, params)
+ SIGNAL_HANDLER
+ if(cannonmode != CLOWN_CANNON_READY || !length(return_controllers_with_flag(VEHICLE_CONTROL_KIDNAPPED)))
+ return NONE
+ var/mob/living/unlucky_sod = pick(return_controllers_with_flag(VEHICLE_CONTROL_KIDNAPPED))
+ mob_exit(unlucky_sod, TRUE)
+ flick("clowncar_recoil", src)
+ playsound(src, pick('sound/vehicles/carcannon1.ogg', 'sound/vehicles/carcannon2.ogg', 'sound/vehicles/carcannon3.ogg'), 75)
+ unlucky_sod.throw_at(A, 10, 2)
+ log_combat(user, unlucky_sod, "fired", src, "towards [A]") //this doesn't catch if the mob hits something between the car and the target
+ return COMSIG_MOB_CANCEL_CLICKON
+
+///Increments the thanks counter every time someone thats been kidnapped thanks the driver
+/obj/vehicle/sealed/car/clowncar/proc/increment_thanks_counter()
+ thankscount++
+ if(thankscount < 100)
+ return
+ for(var/mob/busdriver as anything in return_drivers())
+ busdriver.client.give_award(/datum/award/achievement/misc/the_best_driver, busdriver)
/obj/vehicle/sealed/car/clowncar/twitch_plays
key_type = null
@@ -144,12 +244,10 @@
/obj/vehicle/sealed/car/clowncar/twitch_plays/Initialize()
. = ..()
AddComponent(/datum/component/twitch_plays/simple_movement)
- START_PROCESSING(SSfastprocess, src)
GLOB.poi_list |= src
notify_ghosts("Twitch Plays: Clown Car")
/obj/vehicle/sealed/car/clowncar/twitch_plays/Destroy()
- STOP_PROCESSING(SSfastprocess, src)
GLOB.poi_list -= src
return ..()
@@ -158,3 +256,4 @@
if(!dir)
return
driver_move(null, dir)
+ ..()
diff --git a/code/modules/vehicles/vehicle_actions.dm b/code/modules/vehicles/vehicle_actions.dm
index 5de2c8961f..8b2c72008c 100644
--- a/code/modules/vehicles/vehicle_actions.dm
+++ b/code/modules/vehicles/vehicle_actions.dm
@@ -126,44 +126,70 @@
desc = "Honk your classy horn."
button_icon_state = "car_horn"
var/hornsound = 'sound/items/carhorn.ogg'
- var/last_honk_time
/datum/action/vehicle/sealed/horn/Trigger()
- if(world.time - last_honk_time > 20)
- vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks")
- to_chat(owner, "You press the vehicle's horn.")
- playsound(vehicle_entered_target, hornsound, 75)
- last_honk_time = world.time
+ if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_CAR_HONK))
+ return
+ TIMER_COOLDOWN_START(src, COOLDOWN_CAR_HONK, 2 SECONDS)
+ vehicle_entered_target.visible_message(span_danger("[vehicle_entered_target] loudly honks!"))
+ to_chat(owner, span_notice("You press [vehicle_entered_target]'s horn."))
+ if(istype(vehicle_target.inserted_key, /obj/item/bikehorn))
+ vehicle_target.inserted_key.attack_self(owner) //The bikehorn plays a sound instead
+ return
+ playsound(vehicle_entered_target, hornsound, 75)
-/datum/action/vehicle/sealed/horn/clowncar/Trigger()
- if(world.time - last_honk_time > 20)
- vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks")
- to_chat(owner, "You press the vehicle's horn.")
- last_honk_time = world.time
- if(vehicle_target.inserted_key)
- vehicle_target.inserted_key.attack_self(owner) //The key plays a sound
- else
- playsound(vehicle_entered_target, hornsound, 75)
-
-/datum/action/vehicle/sealed/DumpKidnappedMobs
- name = "Dump kidnapped mobs"
+/datum/action/vehicle/sealed/dump_kidnapped_mobs
+ name = "Dump Kidnapped Mobs"
desc = "Dump all objects and people in your car on the floor."
button_icon_state = "car_dump"
-/datum/action/vehicle/sealed/DumpKidnappedMobs/Trigger()
- vehicle_entered_target.visible_message("[vehicle_entered_target] starts dumping the people inside of it.")
+/datum/action/vehicle/sealed/dump_kidnapped_mobs/Trigger()
+ vehicle_entered_target.visible_message(span_danger("[vehicle_entered_target] starts dumping the people inside of it."))
vehicle_entered_target.DumpSpecificMobs(VEHICLE_CONTROL_KIDNAPPED)
-/datum/action/vehicle/sealed/RollTheDice
- name = "Press a colorful button"
+/datum/action/vehicle/sealed/roll_the_dice
+ name = "Press Colorful Button"
desc = "Press one of those colorful buttons on your display panel!"
button_icon_state = "car_rtd"
-/datum/action/vehicle/sealed/RollTheDice/Trigger()
- if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar))
- var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target
- C.RollTheDice(owner)
+/datum/action/vehicle/sealed/roll_the_dice/Trigger()
+ if(!istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar))
+ return
+ var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target
+ C.roll_the_dice(owner)
+
+/datum/action/vehicle/sealed/cannon
+ name = "Toggle Siege Mode"
+ desc = "Destroy them with their own fodder!"
+ button_icon_state = "car_cannon"
+
+/datum/action/vehicle/sealed/cannon/Trigger()
+ if(!istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar))
+ return
+ var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target
+ C.toggle_cannon(owner)
+
+
+/datum/action/vehicle/sealed/thank
+ name = "Thank the Clown Car Driver"
+ desc = "They're just doing their job."
+ button_icon_state = "car_thanktheclown"
+ COOLDOWN_DECLARE(thank_time_cooldown)
+
+
+/datum/action/vehicle/sealed/thank/Trigger()
+ if(!istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar))
+ return
+ if(!COOLDOWN_FINISHED(src, thank_time_cooldown))
+ return
+ COOLDOWN_START(src, thank_time_cooldown, 6 SECONDS)
+ var/obj/vehicle/sealed/car/clowncar/clown_car = vehicle_entered_target
+ var/mob/living/carbon/human/clown = pick(clown_car.return_drivers())
+ if(!clown)
+ return
+ owner.say("Thank you for the fun ride, [clown.name]!")
+ clown_car.increment_thanks_counter()
/datum/action/vehicle/ridden/scooter/skateboard/ollie
@@ -197,7 +223,9 @@
L.Move(landing_turf, vehicle_target.dir)
passtable_off(L, VEHICLE_TRAIT)
V.pass_flags &= ~PASSTABLE
- if(locate(/obj/structure/table) in V.loc.contents)
+ if((locate(/obj/structure/table) in V.loc.contents) || (locate(/obj/structure/fluff/railing) in V.loc.contents))
+ if(locate(/obj/structure/fluff/railing) in V.loc.contents)
+ L.client.give_award(/datum/award/achievement/misc/tram_surfer, L)
V.grinding = TRUE
V.icon_state = "[V.board_icon]-grind"
addtimer(CALLBACK(V, /obj/vehicle/ridden/scooter/skateboard/.proc/grind), 2)
diff --git a/sound/vehicles/carcannon1.ogg b/sound/vehicles/carcannon1.ogg
new file mode 100644
index 0000000000..751fa6f754
Binary files /dev/null and b/sound/vehicles/carcannon1.ogg differ
diff --git a/sound/vehicles/carcannon2.ogg b/sound/vehicles/carcannon2.ogg
new file mode 100644
index 0000000000..7bc86d7cbc
Binary files /dev/null and b/sound/vehicles/carcannon2.ogg differ
diff --git a/sound/vehicles/carcannon3.ogg b/sound/vehicles/carcannon3.ogg
new file mode 100644
index 0000000000..80407e553f
Binary files /dev/null and b/sound/vehicles/carcannon3.ogg differ
diff --git a/sound/vehicles/clowncar_cannonmode1.ogg b/sound/vehicles/clowncar_cannonmode1.ogg
new file mode 100644
index 0000000000..aa21c8f990
Binary files /dev/null and b/sound/vehicles/clowncar_cannonmode1.ogg differ
diff --git a/sound/vehicles/clowncar_cannonmode2.ogg b/sound/vehicles/clowncar_cannonmode2.ogg
new file mode 100644
index 0000000000..931e146422
Binary files /dev/null and b/sound/vehicles/clowncar_cannonmode2.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index fea0217b78..86814261c5 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -458,6 +458,8 @@
#include "code\datums\achievements\_awards.dm"
#include "code\datums\achievements\boss_achievements.dm"
#include "code\datums\achievements\boss_scores.dm"
+#include "code\datums\achievements\job_achievements.dm"
+#include "code\datums\achievements\job_scores.dm"
#include "code\datums\achievements\mafia_achievements.dm"
#include "code\datums\achievements\misc_achievements.dm"
#include "code\datums\achievements\misc_scores.dm"