diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 04ce1bbe36..5f1ae13336 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -181,4 +181,4 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list(
/obj/item/stack/sheet/plasmaglass,
/obj/item/stack/sheet/plasmarglass)))
-#define is_glass_sheet(O) (is_type_in_typecache(O, GLOB.glass_sheet_types))
+#define is_glass_sheet(O) (is_type_in_typecache(O, GLOB.glass_sheet_types))
\ No newline at end of file
diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm
index 38dc9e48cc..e08cdb8816 100644
--- a/code/__DEFINES/robots.dm
+++ b/code/__DEFINES/robots.dm
@@ -34,6 +34,7 @@
#define FLOOR_BOT 4 // Floorbots
#define CLEAN_BOT 8 // Cleanbots
#define MED_BOT 16 // Medibots
+#define HONK_BOT 32 // Honkbots & ED-Honks
//AI notification defines
#define NEW_BORG 1
@@ -41,3 +42,7 @@
#define RENAME 3
#define AI_SHELL 4
#define DISCONNECT 5
+
+//Assembly defines
+#define ASSEMBLY_FIRST_STEP 0
+#define ASSEMBLY_SECOND_STEP 1
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 5f254d759d..5eb85379b7 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -256,6 +256,18 @@ GLOBAL_LIST_INIT(cardboard_recipes, list ( \
/obj/item/stack/sheet/cardboard/fifty
amount = 50
+/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! Its a clown box! Honk!")
+ if (amount >= 0)
+ new/obj/item/storage/box/clown(droploc) //bugfix
+ else
+ . = ..()
+
+
/*
* Runed Metal
*/
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index 7cd28e3bc9..26ea13cb04 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -607,6 +607,29 @@
playsound(loc, "rustle", 50, 1, -5)
user.visible_message("[user] hugs \the [src].","You hug \the [src].")
+/////clown box & honkbot assembly
+obj/item/storage/box/clown
+ name = "clown box"
+ desc = "A colorful cardboard box for the clown"
+ icon_state = "clownbox"
+ illustration = null
+
+/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params)
+ if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot)))
+ if(contents.len) //prevent accidently deleting contents
+ to_chat(user, "You need to empty [src] out first!")
+ return
+ if(!user.temporarilyRemoveItemFromInventory(I))
+ return
+ qdel(I)
+ to_chat(user, "You add some wheels to the [src]! You've got an honkbot assembly now! Honk!")
+ var/obj/item/honkbot_assembly/A = new
+ qdel(src)
+ user.put_in_hands(A)
+ else
+ return ..()
+
+//////
/obj/item/storage/box/hug/medical/PopulateContents()
new /obj/item/stack/medical/bruise_pack(src)
new /obj/item/stack/medical/ointment(src)
@@ -730,7 +753,7 @@
/obj/item/storage/box/ingredients //This box is for the randomely chosen version the chef spawns with, it shouldn't actually exist.
name = "ingredients box"
illustration = "fruit"
- var/theme_name
+ var/theme_name
/obj/item/storage/box/ingredients/Initialize()
. = ..()
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 13a3343e06..7ed7822888 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -180,4 +180,6 @@
soundin = pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg')
if("law")
soundin = pick('sound/voice/bgod.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bsecureday.ogg', 'sound/voice/bradio.ogg', 'sound/voice/binsult.ogg', 'sound/voice/bcreep.ogg')
+ if("honkbot_e")
+ soundin = pick('sound/items/bikehorn.ogg', 'sound/items/AirHorn2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/items/AirHorn.ogg', 'sound/effects/reee.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/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index c53925e076..9d0a623b35 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -347,6 +347,51 @@
S.name = created_name
qdel(src)
+
+//Honkbot Assembly
+/obj/item/honkbot_assembly
+ name = "incomplete honkbot assembly"
+ desc = "The clown's up to no good once more"
+ icon = 'icons/mob/aibots.dmi'
+ icon_state = "honkbot_arm"
+ var/build_step = ASSEMBLY_FIRST_STEP
+ var/created_name = "Honkbot"
+
+/obj/item/honkbot_assembly/attackby(obj/item/I, mob/user, params)
+
+ if(isprox(I) && (build_step == ASSEMBLY_FIRST_STEP))
+ if(!user.temporarilyRemoveItemFromInventory(I))
+ return
+ build_step++
+ to_chat(user, "You add the [I] to [src]!")
+ icon_state = "honkbot_proxy"
+ name = "incomplete Honkbot assembly"
+ qdel(I)
+
+ else if(istype(I, /obj/item/bikehorn) && (build_step == ASSEMBLY_SECOND_STEP))
+ if(istype(loc, /obj/item/storage/backpack)) //don't build them in your backpacks!
+ return
+ if(!user.temporarilyRemoveItemFromInventory(I))
+ return
+ to_chat(user, "You add the [I] to [src]! Honk!")
+ var/T = get_turf(loc) //important to spawn on turf.
+ var/mob/living/simple_animal/bot/honkbot/S = new(drop_location(T))
+ S.name = created_name
+ S.spam_flag = TRUE // only long enough to hear the first ping.
+ addtimer(CALLBACK (S, .mob/living/simple_animal/bot/honkbot/proc/react_ping), 5)
+ qdel(I)
+ qdel(src)
+
+ else if(istype(I, /obj/item/pen))
+ var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN)
+ if(!t)
+ return
+ if(!in_range(src, usr) && loc != usr)
+ return
+ created_name = t
+
+ else return ..()
+
//Secbot Assembly
/obj/item/secbot_assembly
name = "incomplete securitron assembly"
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 0000000000..be41ba791d
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -0,0 +1,371 @@
+/mob/living/simple_animal/bot/honkbot
+ name = "\improper honkbot"
+ desc = "A little robot. It looks happy with its bike horn."
+ icon = 'icons/mob/aibots.dmi'
+ icon_state = "honkbot"
+ density = FALSE
+ anchored = FALSE
+ health = 25
+ maxHealth = 25
+ damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
+ pass_flags = PASSMOB
+
+ radio_key = /obj/item/device/encryptionkey/headset_service //doesn't have security key
+ radio_channel = "Service" //Doesn't even use the radio anyway.
+ bot_type = HONK_BOT
+ 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
+
+ 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/declare_arrests = FALSE // speak, you shall not, unless to Honk
+ var/idcheck = TRUE
+ var/fcheck = TRUE
+ var/check_records = TRUE
+ var/arrest_type = FALSE
+ var/weaponscheck = TRUE
+
+/mob/living/simple_animal/bot/honkbot/Initialize()
+ . = ..()
+ icon_state = "honkbot[on]"
+ 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/blink_end() //used for addtimer
+ icon_state = "honkbot[on]"
+
+/mob/living/simple_animal/bot/honkbot/proc/sensor_blink()
+ icon_state = "honkbot-c"
+ addtimer(CALLBACK(src, .proc/blink_end), 5)
+
+//honkbots react with sounds.
+/mob/living/simple_animal/bot/honkbot/proc/react_ping()
+ playsound(src, 'sound/machines/ping.ogg', 50, 1, -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, 1, -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 panel is [open ? "opened" : "closed"]"},
+
+"[on ? "On" : "Off"]" )
+
+ if(!locked || issilicon(user) || IsAdminGhost(user))
+ dat += text({"
Auto Patrol: []"},
+
+"[auto_patrol ? "On" : "Off"]" )
+ return dat
+
+/mob/living/simple_animal/bot/honkbot/proc/judgement_criteria()
+ var/final = NONE
+ if(check_records)
+ final = final|JUDGE_RECORDCHECK
+ if(emagged)
+ final = final|JUDGE_EMAGGED
+ return final
+
+/mob/living/simple_animal/bot/honkbot/proc/retaliate(mob/living/carbon/human/H)
+ var/judgement_criteria = judgement_criteria()
+ threatlevel = H.assess_threat(judgement_criteria)
+ 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 == "harm")
+ retaliate(H)
+ addtimer(CALLBACK(src, .proc/react_buzz), 5)
+ return ..()
+
+
+/mob/living/simple_animal/bot/honkbot/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM).
+ return
+ if(!istype(W, /obj/item/screwdriver) && (W.force) && (!target) && (W.damtype != STAMINA) ) // Check for welding tool to fix #2432.
+ retaliate(user)
+ addtimer(CALLBACK(src, .proc/react_buzz), 5)
+ ..()
+
+/mob/living/simple_animal/bot/honkbot/emag_act(mob/user)
+ ..()
+ if(emagged == 2)
+ if(user)
+ user << "You short out [src]'s sound control system. 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
+ icon_state = "honkbot[on]"
+
+/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))
+ 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.IsStun() || arrest_type)
+ 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 = 0, hitpush = 1, blocked = 0)
+ if(istype(AM, /obj/item))
+ playsound(src, honksound, 50, 1, -1)
+ var/obj/item/I = AM
+ if(I.throwforce < health && I.thrownby && (istype(I.thrownby, /mob/living/carbon/human)))
+ 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, 1, -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/blink_end), 30)
+ 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, 1, -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(loc, 'sound/items/AirHorn.ogg', 100, 1, -1) //HEEEEEEEEEEEENK!!
+ sensor_blink()
+ if(spam_flag == 0)
+ if(ishuman(C))
+ C.stuttering = 20
+ C.adjustEarDamage(0, 5) //far less damage than the H.O.N.K.
+ C.Jitter(50)
+ C.Knockdown(60)
+ var/mob/living/carbon/human/H = C
+ if(client) //prevent spam from players..
+ spam_flag = TRUE
+ if (emagged <= 1) //HONK once, then leave
+ var/judgement_criteria = judgement_criteria()
+ threatlevel = H.assess_threat(judgement_criteria)
+ 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_logs(src,C,"honked")
+
+ C.visible_message("[src] has honked [C]!",\
+ "[src] has honked you!")
+ else
+ C.stuttering = 20
+ C.Knockdown(80)
+ 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
+
+ var/judgement_criteria = judgement_criteria()
+ threatlevel = C.assess_threat(judgement_criteria)
+
+ 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/bodypart/l_arm/robot/(Tsec)
+ new /obj/item/bikehorn(Tsec)
+ new /obj/item/device/assembly/prox_sensor(Tsec)
+
+ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ s.set_up(3, 1, src)
+ s.start()
+
+ new /obj/effect/decal/cleanable/oil(loc)
+ ..()
+
+/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))
+ 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.Knockdown(10)
+ playsound(loc, 'sound/misc/sadtrombone.ogg', 50, 1, -1)
+ speak("Honk!")
+ sensor_blink()
+ return
+ ..()
+
+/obj/machinery/bot_core/honkbot
+ req_one_access = list(ACCESS_THEATRE, ACCESS_ROBOTICS)
\ No newline at end of file
diff --git a/icons/mob/aibots.dmi b/icons/mob/aibots.dmi
index 860f76001b..c6ee573913 100644
Binary files a/icons/mob/aibots.dmi and b/icons/mob/aibots.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index 6fdb531956..024a00b067 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/sound/machines/honkbot_evil_laugh.ogg b/sound/machines/honkbot_evil_laugh.ogg
new file mode 100644
index 0000000000..4101d82742
Binary files /dev/null and b/sound/machines/honkbot_evil_laugh.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index c2ee860831..49ef73f89a 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1787,6 +1787,7 @@
#include "code\modules\mob\living\simple_animal\bot\construction.dm"
#include "code\modules\mob\living\simple_animal\bot\ed209bot.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"