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