diff --git a/code/__DEFINES/bots.dm b/code/__DEFINES/bots.dm
index 1fad6d6cd4b..75bf8a0a553 100644
--- a/code/__DEFINES/bots.dm
+++ b/code/__DEFINES/bots.dm
@@ -31,6 +31,7 @@
#define FLOOR_BOT 4 // Floorbots
#define CLEAN_BOT 8 // Cleanbots
#define MED_BOT 16 // Medibots
+#define HONK_BOT 32 // Honkbots
//Sentience types
#define SENTIENCE_ORGANIC 1
diff --git a/code/controllers/communications.dm b/code/controllers/communications.dm
index eaf993939ea..8d22febee61 100644
--- a/code/controllers/communications.dm
+++ b/code/controllers/communications.dm
@@ -176,6 +176,7 @@ var/const/RADIO_ATMOSIA = "radio_atmos"
var/const/RADIO_NAVBEACONS = "radio_navbeacon"
var/const/RADIO_AIRLOCK = "radio_airlock"
var/const/RADIO_SECBOT = "radio_secbot"
+var/const/RADIO_HONKBOT = "radio_honkbot"
var/const/RADIO_MULEBOT = "radio_mulebot"
var/const/RADIO_CLEANBOT = "10"
var/const/RADIO_FLOORBOT = "11"
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 40c775d0e87..d26fa182fe1 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -252,6 +252,16 @@ var/global/list/datum/stack_recipe/cardboard_recipes = list (
new/datum/stack_recipe("cardboard cutout", /obj/item/cardboard_cutout, 5),
)
+/obj/item/stack/sheet/cardboard/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/stamp/clown) && !istype(loc, /obj/item/storage))
+ var/atom/droploc = drop_location()
+ if(use(1))
+ playsound(I, 'sound/items/bikehorn.ogg', 50, 1, -1)
+ to_chat(user, "You stamp the cardboard! It's a clown box! Honk!")
+ new/obj/item/storage/box/clown(droploc) //bugfix
+ else
+ . = ..()
+
/obj/item/stack/sheet/cardboard //BubbleWrap
name = "cardboard"
desc = "Large sheets of card, like boxes folded flat."
diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm
index 1ca94de01dc..acea165b9ae 100644
--- a/code/game/objects/items/weapons/storage/boxes.dm
+++ b/code/game/objects/items/weapons/storage/boxes.dm
@@ -998,6 +998,11 @@
new /obj/item/reagent_containers/food/pill/patch/synthflesh(src)
new /obj/item/reagent_containers/hypospray/autoinjector(src)
+/obj/item/storage/box/clown
+ name = "clown box"
+ desc = "A colorful cardboard box for the clown"
+ icon_state = "box_clown"
+
#undef NODESIGN
#undef NANOTRASEN
#undef SYNDI
diff --git a/code/game/sound.dm b/code/game/sound.dm
index ed42af1223f..66fdd2cbc39 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -149,4 +149,6 @@
if("bonebreak")
soundin = pick('sound/effects/bone_break_1.ogg', 'sound/effects/bone_break_2.ogg', 'sound/effects/bone_break_3.ogg', 'sound/effects/bone_break_4.ogg', 'sound/effects/bone_break_5.ogg', 'sound/effects/bone_break_6.ogg')
+ if("honkbot_e")
+ soundin = pick('sound/items/bikehorn.ogg', 'sound/items/AirHorn2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/items/AirHorn.ogg', 'sound/items/WEEOO1.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bcreep.ogg','sound/magic/Fireball.ogg' ,'sound/effects/pray.ogg', 'sound/voice/hiss1.ogg','sound/machines/buzz-sigh.ogg', 'sound/machines/ping.ogg', 'sound/weapons/flashbang.ogg', 'sound/weapons/bladeslice.ogg')
return soundin
diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm
index 4fc0d229d3f..659cab87b4f 100644
--- a/code/modules/crafting/recipes.dm
+++ b/code/modules/crafting/recipes.dm
@@ -90,6 +90,17 @@
time = 40
category = CAT_ROBOT
+/datum/crafting_recipe/honkbot
+ name = "Honkbot"
+ result = /mob/living/simple_animal/bot/honkbot
+ reqs = list(/obj/item/robot_parts/r_arm = 1,
+ /obj/item/bikehorn = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/storage/box/clown = 1,
+ /obj/item/instrument/trombone = 1)
+ time = 40
+ category = CAT_ROBOT
+
/datum/crafting_recipe/floorbot
name = "Floorbot"
result = /mob/living/simple_animal/bot/floorbot
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index e223511223b..9c11a3d872c 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -480,3 +480,56 @@
new /obj/item/robot_parts/l_arm(get_turf(src))
to_chat(user, "You remove the robot arm from [src].")
build_step--
+
+//Honkbot Assembly
+/obj/item/storage/box/clown/attackby(obj/item/W, mob/user, params)
+ if(!istype(W, /obj/item/robot_parts/l_arm) && !istype(W, /obj/item/robot_parts/r_arm))
+ return ..()
+
+ if(contents.len)
+ to_chat(user, "You need to empty [src] out first!")
+ return
+
+ var/obj/item/honkbot_arm_assembly/A = new /obj/item/honkbot_arm_assembly
+ qdel(W)
+ user.put_in_hands(A)
+ to_chat(user, "You add the robot arm to the honkbot.")
+ user.unEquip(src, 1)
+ qdel(src)
+
+/obj/item/honkbot_arm_assembly
+ name = "incomplete honkbot assembly."
+ desc = "A clown box with a robot arm permanently grafted to it."
+ icon = 'icons/obj/aibots.dmi'
+ icon_state = "honkbot_arm"
+ w_class = WEIGHT_CLASS_NORMAL
+ req_one_access = list(access_clown, access_robotics, access_mime)
+ var/build_step = 0
+ var/created_name = "Honkbot" //To preserve the name if it's a unique medbot I guess
+
+/obj/item/honkbot_arm_assembly/attackby(obj/item/W, mob/user, params)
+ ..()
+ if(build_step == 0)
+ if(istype(W, /obj/item/assembly/prox_sensor))
+ if(!user.unEquip(W))
+ return
+ build_step++
+ to_chat(user, "You add the proximity sensor to [src].")
+ icon_state = "honkbot_proxy"
+ qdel(W)
+ else if(build_step == 1)
+ if(istype(W, /obj/item/bikehorn))
+ if(!user.unEquip(W))
+ return
+ build_step++
+ to_chat(user, "You add the bikehorn to [src]! Honk!")
+ desc = "A clown box with a robot arm and a bikehorn permanently grafted to it. It needs a trombone to be finished"
+ qdel(W)
+ else if(build_step == 2)
+ if(istype(W, /obj/item/instrument/trombone))
+ if(!user.unEquip(W))
+ return
+ to_chat(user, "You add the trombone to [src]! Heeeenk! ")
+ qdel(W)
+ new /mob/living/simple_animal/bot/honkbot(get_turf(loc))
+ qdel(src)
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm
new file mode 100644
index 00000000000..1505e71e6db
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -0,0 +1,321 @@
+/mob/living/simple_animal/bot/honkbot
+ name = "\improper honkbot"
+ desc = "A little robot. It looks happy with its bike horn."
+ icon = 'icons/obj/aibots.dmi'
+ icon_state = "honkbot"
+ density = FALSE
+ anchored = FALSE
+ health = 25
+ maxHealth = 25
+ damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
+ pass_flags = PASSMOB
+ radio_channel = "Service" //Service
+ bot_type = HONK_BOT
+ bot_filter = RADIO_HONKBOT
+ model = "Honkbot"
+ bot_core_type = /obj/machinery/bot_core/honkbot
+ window_id = "autohonk"
+ window_name = "Honkomatic Bike Horn Unit v1.0.7"
+ data_hud_type = DATA_HUD_SECURITY_BASIC // show jobs
+ path_image_color = "#FF69B4"
+
+ var/honksound = 'sound/items/bikehorn.ogg' //customizable sound
+ var/spam_flag = FALSE
+ var/cooldowntime = 30
+ var/cooldowntimehorn = 10
+ var/mob/living/carbon/target
+ var/oldtarget_name
+ var/target_lastloc = FALSE //Loc of target when arrested.
+ var/last_found = FALSE //There's a delay
+ var/threatlevel = FALSE
+ var/arrest_type = FALSE
+
+/obj/machinery/bot_core/honkbot
+ req_one_access = list(access_clown, access_robotics, access_mime)
+
+/mob/living/simple_animal/bot/honkbot/Initialize()
+ . = ..()
+ update_icon()
+ auto_patrol = TRUE
+ var/datum/job/clown/J = new/datum/job/clown
+ access_card.access += J.get_access()
+ prev_access = access_card.access
+
+/mob/living/simple_animal/bot/honkbot/proc/spam_flag_false() //used for addtimer
+ spam_flag = FALSE
+
+/mob/living/simple_animal/bot/honkbot/proc/sensor_blink()
+ icon_state = "honkbot-c"
+ addtimer(CALLBACK(src, .proc/update_icon), 5, TIMER_OVERRIDE|TIMER_UNIQUE)
+
+//honkbots react with sounds.
+/mob/living/simple_animal/bot/honkbot/proc/react_ping()
+ playsound(src, 'sound/machines/ping.ogg', 50, TRUE, -1) //the first sound upon creation!
+ spam_flag = TRUE
+ sensor_blink()
+ addtimer(CALLBACK(src, .proc/spam_flag_false), 18) // calibrates before starting the honk
+
+/mob/living/simple_animal/bot/honkbot/proc/react_buzz()
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE, -1)
+ sensor_blink()
+
+/mob/living/simple_animal/bot/honkbot/bot_reset()
+ ..()
+ target = null
+ oldtarget_name = null
+ anchored = FALSE
+ walk_to(src, 0)
+ last_found = world.time
+ spam_flag = FALSE
+
+/mob/living/simple_animal/bot/honkbot/set_custom_texts()
+ text_hack = "You overload [name]'s sound control system"
+ text_dehack = "You reboot [name] and restore the sound control system."
+ text_dehack_fail = "[name] refuses to accept your authority!"
+
+/mob/living/simple_animal/bot/honkbot/get_controls(mob/user)
+ var/dat
+ dat += hack(user)
+ dat += showpai(user)
+ dat += text({"
+ Honkomatic Bike Horn Unit v1.0.7 controls
+ Status: []
+ Behaviour controls are [locked ? "locked" : "unlocked"]
+ Maintenance panel is [open ? "opened" : "closed"]
"},
+
+ "[on ? "On" : "Off"]")
+
+ if(!locked || issilicon(user) || user.can_admin_interact())
+ dat += "Auto Patrol [auto_patrol ? "On" : "Off"]
"
+
+ return dat
+
+/mob/living/simple_animal/bot/honkbot/proc/retaliate(mob/living/carbon/human/H)
+ threatlevel = H.assess_threat(src)
+ threatlevel += 6
+ if(threatlevel >= 4)
+ target = H
+ mode = BOT_HUNT
+
+/mob/living/simple_animal/bot/honkbot/attack_hand(mob/living/carbon/human/H)
+ if(H.a_intent == INTENT_HARM)
+ retaliate(H)
+ addtimer(CALLBACK(src, .proc/react_buzz), 5)
+ return ..()
+
+/mob/living/simple_animal/bot/honkbot/emag_act(mob/user)
+ ..()
+ if(emagged == 2)
+ if(user)
+ to_chat(user, "You short out [src]'s target assessment circuits. It gives out an evil laugh!!")
+ oldtarget_name = user.name
+ audible_message("[src] gives out an evil laugh!")
+ playsound(src, 'sound/machines/honkbot_evil_laugh.ogg', 75, 1, -1) // evil laughter
+ update_icon()
+
+/mob/living/simple_animal/bot/honkbot/bullet_act(obj/item/projectile/Proj)
+ if((istype(Proj,/obj/item/projectile/beam)) || (istype(Proj,/obj/item/projectile/bullet) && (Proj.damage_type == BURN))||(Proj.damage_type == BRUTE) && (!Proj.nodamage && Proj.damage < health && ishuman(Proj.firer)))
+ retaliate(Proj.firer)
+ ..()
+
+/mob/living/simple_animal/bot/honkbot/UnarmedAttack(atom/A)
+ if(!on)
+ return
+ if(iscarbon(A))
+ var/mob/living/carbon/C = A
+ if(emagged <= 1)
+ honk_attack(A)
+ else
+ if(!C.stunned || arrest_type) //originaly was paralisysed in tg ported as stun
+ stun_attack(A)
+ ..()
+ else if(!spam_flag) //honking at the ground
+ bike_horn(A)
+
+/mob/living/simple_animal/bot/honkbot/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum)
+ if(istype(AM, /obj/item))
+ playsound(src, honksound, 50, TRUE, -1)
+ var/obj/item/I = AM
+ if(I.throwforce < src.health && I.thrownby && ishuman(I.thrownby))
+ var/mob/living/carbon/human/H = I.thrownby
+ retaliate(H)
+ ..()
+
+/mob/living/simple_animal/bot/honkbot/proc/bike_horn() //use bike_horn
+ if(emagged <= 1)
+ if(!spam_flag)
+ playsound(src, honksound, 50, TRUE, -1)
+ spam_flag = TRUE //prevent spam
+ sensor_blink()
+ addtimer(CALLBACK(src, .proc/spam_flag_false), cooldowntimehorn)
+ else if(emagged == 2) //emagged honkbots will spam short and memorable sounds.
+ if(!spam_flag)
+ playsound(src, "honkbot_e", 50, 0)
+ spam_flag = TRUE // prevent spam
+ icon_state = "honkbot-e"
+ addtimer(CALLBACK(src, .proc/update_icon), 30, TIMER_OVERRIDE|TIMER_UNIQUE)
+ addtimer(CALLBACK(src, .proc/spam_flag_false), cooldowntimehorn)
+
+/mob/living/simple_animal/bot/honkbot/proc/honk_attack(mob/living/carbon/C) // horn attack
+ if(!spam_flag)
+ playsound(loc, honksound, 50, TRUE, -1)
+ spam_flag = TRUE // prevent spam
+ sensor_blink()
+ addtimer(CALLBACK(src, .proc/spam_flag_false), cooldowntimehorn)
+
+/mob/living/simple_animal/bot/honkbot/proc/stun_attack(mob/living/carbon/C) // airhorn stun
+ if(!spam_flag)
+ playsound(src, 'sound/items/AirHorn.ogg', 100, TRUE, -1) //HEEEEEEEEEEEENK!!
+ sensor_blink()
+ if(!spam_flag)
+ if(ishuman(C))
+ C.stuttering = 20 //stammer
+ C.MinimumDeafTicks(0, 5) //far less damage than the H.O.N.K.
+ C.Jitter(50)
+ C.Weaken(5)
+ C.Stun(5) // Paralysis from tg ported as stun
+ if(client) //prevent spam from players..
+ spam_flag = TRUE
+ if(emagged <= 1) //HONK once, then leave
+ threatlevel -= 6
+ target = oldtarget_name
+ else // you really don't want to hit an emagged honkbot
+ threatlevel = 6 // will never let you go
+ addtimer(CALLBACK(src, .proc/spam_flag_false), cooldowntime)
+ add_attack_logs(src, C, "honked by [src]")
+ C.visible_message("[src] has honked [C]!",\
+ "[src] has honked you!")
+ else
+ C.stuttering = 20
+ C.Stun(10)
+ addtimer(CALLBACK(src, .proc/spam_flag_false), cooldowntime)
+
+
+/mob/living/simple_animal/bot/honkbot/handle_automated_action()
+ if(!..())
+ return
+ switch(mode)
+ if(BOT_IDLE) // idle
+ walk_to(src, 0)
+ look_for_perp()
+ if(!mode && auto_patrol)
+ mode = BOT_START_PATROL
+ if(BOT_HUNT)
+ // if can't reach perp for long enough, go idle
+ if(frustration >= 5) //gives up easier than beepsky
+ walk_to(src, 0)
+ back_to_idle()
+ return
+ if(target) // make sure target exists
+ if(Adjacent(target) && isturf(target.loc))
+ if(threatlevel <= 4)
+ honk_attack(target)
+ else
+ if(threatlevel >= 6)
+ set waitfor = 0
+ stun_attack(target)
+ anchored = FALSE
+ target_lastloc = target.loc
+ return
+ else // not next to perp
+ var/turf/olddist = get_dist(src, target)
+ walk_to(src, target, 1, 4)
+ if((get_dist(src, target)) >= (olddist))
+ frustration++
+ else
+ frustration = 0
+ else
+ back_to_idle()
+
+ if(BOT_START_PATROL)
+ look_for_perp()
+ start_patrol()
+
+ if(BOT_PATROL)
+ look_for_perp()
+ bot_patrol()
+ return
+
+/mob/living/simple_animal/bot/honkbot/proc/back_to_idle()
+ anchored = FALSE
+ mode = BOT_IDLE
+ target = null
+ last_found = world.time
+ frustration = 0
+ INVOKE_ASYNC(src, .proc/handle_automated_action) //responds quickly
+
+/mob/living/simple_animal/bot/honkbot/proc/back_to_hunt()
+ anchored = FALSE
+ frustration = 0
+ mode = BOT_HUNT
+ INVOKE_ASYNC(src, .proc/handle_automated_action) // responds quickly
+
+/mob/living/simple_animal/bot/honkbot/proc/look_for_perp()
+ anchored = FALSE
+ for(var/mob/living/carbon/C in view(7, src))
+ if((C.stat) || (C.handcuffed))
+ continue
+
+ if((C.name == oldtarget_name) && (world.time < last_found + 100))
+ continue
+
+ if(threatlevel <= 3)
+ if(C in view(4, src)) //keep the range short for patrolling
+ if(!spam_flag)
+ bike_horn()
+ else if(threatlevel >= 10)
+ bike_horn() //just spam the shit outta this
+ else if(threatlevel >= 4)
+ if(!spam_flag)
+ target = C
+ oldtarget_name = C.name
+ bike_horn()
+ speak("Honk!")
+ visible_message("[src] starts chasing [C.name]!")
+ mode = BOT_HUNT
+ INVOKE_ASYNC(src, .proc/handle_automated_action)
+ break
+ else
+ continue
+
+/mob/living/simple_animal/bot/honkbot/explode()
+ walk_to(src, 0)
+ visible_message("[src] blows apart!")
+ var/turf/Tsec = get_turf(src)
+ //doesn't drop cardboard nor its assembly, since its a very frail material.
+ if(prob(50))
+ new /obj/item/robot_parts/r_arm(Tsec)
+ new /obj/item/bikehorn(Tsec)
+ new /obj/item/assembly/prox_sensor(Tsec)
+
+ var/datum/effect_system/spark_spread/s = new
+ s.set_up(3, 1, src)
+ s.start()
+ ..()
+
+/mob/living/simple_animal/bot/honkbot/attack_alien(var/mob/living/carbon/alien/user as mob)
+ ..()
+ if(!isalien(target))
+ target = user
+ mode = BOT_HUNT
+
+/mob/living/simple_animal/bot/honkbot/Crossed(atom/movable/AM)
+ if(ismob(AM) && on) //only if its online
+ if(prob(30)) //you're far more likely to trip on a honkbot
+ var/mob/living/carbon/C = AM
+ if(!istype(C) || !C || in_range(src, target))
+ return
+ C.visible_message("[pick( \
+ "[C] dives out of [src]'s way!", \
+ "[C] stumbles over [src]!", \
+ "[C] jumps out of [src]'s path!", \
+ "[C] trips over [src] and falls!", \
+ "[C] topples over [src]!", \
+ "[C] leaps out of [src]'s way!")]")
+ C.Weaken(5)
+ playsound(loc, 'sound/misc/sadtrombone.ogg', 50, 1, -1)
+ if(!client)
+ speak("Honk!")
+ sensor_blink()
+ return
+ ..()
\ No newline at end of file
diff --git a/code/modules/pda/radio.dm b/code/modules/pda/radio.dm
index 8ff24a183b4..e6d7f29f6ac 100644
--- a/code/modules/pda/radio.dm
+++ b/code/modules/pda/radio.dm
@@ -96,6 +96,9 @@
if(radio_controller)
radio_controller.add_object(src, control_freq, filter = bot_filter)
+/obj/item/integrated_radio/honkbot
+ bot_filter = RADIO_HONKBOT
+ bot_type = HONK_BOT
/obj/item/integrated_radio/beepsky
bot_filter = RADIO_SECBOT
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index b1b5bf61a29..686987a3711 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 88b949270ce..9e36bf7b5af 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1842,6 +1842,7 @@
#include "code\modules\mob\living\simple_animal\bot\ed209bot.dm"
#include "code\modules\mob\living\simple_animal\bot\emote.dm"
#include "code\modules\mob\living\simple_animal\bot\floorbot.dm"
+#include "code\modules\mob\living\simple_animal\bot\honkbot.dm"
#include "code\modules\mob\living\simple_animal\bot\medbot.dm"
#include "code\modules\mob\living\simple_animal\bot\mulebot.dm"
#include "code\modules\mob\living\simple_animal\bot\secbot.dm"
diff --git a/sound/machines/honkbot_evil_laugh.ogg b/sound/machines/honkbot_evil_laugh.ogg
new file mode 100644
index 00000000000..4101d82742d
Binary files /dev/null and b/sound/machines/honkbot_evil_laugh.ogg differ