#define SECBOT_IDLE 0 // idle #define SECBOT_HUNT 1 // found target, hunting #define SECBOT_ARREST 2 // arresting target #define SECBOT_START_PATROL 3 // start patrol #define SECBOT_WAIT_PATROL 4 // waiting for signals #define SECBOT_PATROL 5 // patrolling #define SECBOT_SUMMON 6 // summoned by PDA /mob/living/bot/secbot name = "Securitron" desc = "A little security robot. He looks less than thrilled." icon_state = "secbot0" maxHealth = 50 health = 50 req_one_access = list(access_security, access_forensics_lockers, access_weapons) botcard_access = list(access_security, access_sec_doors, access_forensics_lockers, access_morgue, access_maint_tunnels) var/mob/target var/idcheck = 0 // If true, arrests for having weapons without authorization. var/check_records = 0 // If true, arrests people without a record. var/check_arrest = 1 // If true, arrests people who are set to arrest. var/arrest_type = 0 // If true, doesn't handcuff. You monster. var/declare_arrests = 0 // If true, announces arrests over sechuds. var/auto_patrol = 0 // If true, patrols on its own var/mode = 0 var/is_attacking = 0 var/is_ranged = 0 var/awaiting_surrender = 0 var/obj/secbot_listener/listener = null var/beacon_freq = 1445 // Navigation beacon frequency var/control_freq = BOT_FREQ // Bot control frequency var/list/turf/path = list() var/frustration = 0 var/turf/patrol_target = null // This is where we are headed var/closest_dist // Used to find the closest beakon var/destination = "__nearest__" // This is the current beacon's ID var/next_destination = "__nearest__" // This is the next beacon's ID var/nearest_beacon // Tag of the beakon that we assume to be the closest one var/bot_version = 1.5 var/list/threat_found_sounds = list( 'sound/voice/bcriminal.ogg', 'sound/voice/bjustice.ogg', 'sound/voice/bfreeze.ogg' ) var/list/preparing_arrest_sounds = list( 'sound/voice/bgod.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bsecureday.ogg', 'sound/voice/bradio.ogg', 'sound/voice/binsult.ogg', 'sound/voice/bcreep.ogg' ) var/datum/callback/patrol_callback // this is here so we don't constantly recreate this datum, it being identical each time. var/move_to_delay = 4 //delay for the automated movement. can_take_pai = FALSE /mob/living/bot/secbot/beepsky name = "Officer Beepsky" desc = "It's Officer Beep O'sky! Powered by a potato and a shot of whiskey." auto_patrol = 1 /mob/living/bot/secbot/Initialize() . = ..() listener = new /obj/secbot_listener(src) listener.secbot = src if(SSradio) SSradio.add_object(listener, control_freq, filter = RADIO_SECBOT) SSradio.add_object(listener, beacon_freq, filter = RADIO_NAVBEACONS) /mob/living/bot/secbot/Destroy() QDEL_NULL(listener) target = null return ..() /mob/living/bot/secbot/turn_off() ..() walk_to(src, src, 0, move_to_delay) target = null frustration = 0 mode = SECBOT_IDLE /mob/living/bot/secbot/update_icon() if(on && is_attacking) icon_state = "secbot-c" else icon_state = "secbot[on]" if(on) set_light(1.4, 1, "#FF6A00") else set_light(0) /mob/living/bot/secbot/attack_hand(var/mob/user) if (!has_ui_access(user)) to_chat(user, "The unit's interface refuses to unlock!") return user.set_machine(src) var/dat = "" dat += "Status: [on ? "On" : "Off"]
" dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
" dat += "Maintenance panel is [open ? "opened" : "closed"]" if(!locked || issilicon(user)) dat += "
Check for Weapon Authorization: [idcheck ? "Yes" : "No"]
" dat += "Check Security Records: [check_records ? "Yes" : "No"]
" dat += "Check Arrest Status: [check_arrest ? "Yes" : "No"]
" dat += "Operating Mode: [arrest_type ? "Detain" : "Arrest"]
" dat += "Report Arrests: [declare_arrests ? "Yes" : "No"]
" dat += "Auto Patrol: [auto_patrol ? "On" : "Off"]" var/datum/browser/bot_win = new(user, "autosec", "Automatic Securitron v[bot_version] Controls") bot_win.set_content(dat) bot_win.open() /mob/living/bot/secbot/Topic(href, href_list) if(..()) return usr.set_machine(src) add_fingerprint(usr) if (!has_ui_access(usr)) to_chat(usr, "Insufficient permissions.") return if(href_list["power"]) if(on) turn_off() else turn_on() attack_hand(usr) if (locked && !issilicon(usr)) return switch(href_list["operation"]) if("idcheck") idcheck = !idcheck if("ignorerec") check_records = !check_records if("ignorearr") check_arrest = !check_arrest if("switchmode") arrest_type = !arrest_type if("patrol") auto_patrol = !auto_patrol mode = SECBOT_IDLE if("declarearrests") declare_arrests = !declare_arrests attack_hand(usr) /mob/living/bot/secbot/attackby(var/obj/item/O, var/mob/user) var/curhealth = health ..() if(health < curhealth) target = user awaiting_surrender = 5 mode = SECBOT_HUNT /mob/living/bot/secbot/think() ..() if(!on) return if(QDELETED(target)) scan_view() if(!locked && (mode == SECBOT_START_PATROL || mode == SECBOT_PATROL)) // Stop running away when we set you up mode = SECBOT_IDLE switch(mode) if(SECBOT_IDLE) if(auto_patrol && locked) mode = SECBOT_START_PATROL return if(SECBOT_HUNT) // Target is in the view or has been recently - chase it if(frustration > 7) target = null frustration = 0 awaiting_surrender = 0 mode = SECBOT_IDLE return if(target) var/threat = check_threat(target) if(threat < 4) // Re-evaluate in case they dropped the weapon or something target = null frustration = 0 awaiting_surrender = 0 mode = SECBOT_IDLE return if(!(target in view(7, src))) ++frustration if(Adjacent(target)) mode = SECBOT_ARREST return else if(is_ranged) RangedAttack(target) else walk_to(src, target, 1, move_to_delay) // Melee bots chase a bit faster if(SECBOT_ARREST) // Target is next to us - attack it if(!target) mode = SECBOT_IDLE if(!Adjacent(target)) awaiting_surrender = 5 // I'm done playing nice mode = SECBOT_HUNT return var/threat = check_threat(target) walk_to(src, src, 0, move_to_delay) if(threat < 4) target = null awaiting_surrender = 0 frustration = 0 mode = SECBOT_IDLE return if(awaiting_surrender < 5 && ishuman(target) && !target.lying) if(awaiting_surrender == 0) say("Down on the floor, [target]! You have five seconds to comply.") ++awaiting_surrender else UnarmedAttack(target) if(ishuman(target) && declare_arrests) var/area/location = get_area(src) broadcast_security_hud_message("[src] is [arrest_type ? "detaining" : "arresting"] a level [check_threat(target)] suspect [target] in [location].", src) return if(SECBOT_START_PATROL) if(path.len && patrol_target) mode = SECBOT_PATROL return else if(patrol_target) spawn(0) calc_path() if(!path.len) patrol_target = null mode = SECBOT_IDLE else mode = SECBOT_PATROL if(!patrol_target) if(next_destination) find_next_target() else find_patrol_target() say("Engaging patrol mode.") mode = SECBOT_WAIT_PATROL return if(SECBOT_WAIT_PATROL) if(patrol_target) mode = SECBOT_START_PATROL else ++frustration if(frustration > 120) frustration = 0 mode = SECBOT_IDLE if(SECBOT_PATROL) patrol_step() return if(SECBOT_SUMMON) patrol_step() return /mob/living/bot/secbot/UnarmedAttack(var/mob/M, var/proximity) if(!..()) return if(!istype(M)) return if(istype(M, /mob/living/carbon)) var/mob/living/carbon/C = M var/cuff = 1 if(istype(C, /mob/living/carbon/human)) var/mob/living/carbon/human/H = C if(istype(H.back, /obj/item/rig) && istype(H.gloves,/obj/item/clothing/gloves/rig)) cuff = 0 if(!C.lying || C.handcuffed || arrest_type) cuff = 0 if(!cuff) C.stun_effect_act(0, 60, null) playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) do_attack_animation(C) is_attacking = 1 update_icon() addtimer(CALLBACK(src, .proc/stop_attacking_cb), 2) visible_message("[C] was prodded by [src] with a stun baton!") else playsound(loc, 'sound/weapons/handcuffs.ogg', 30, 1, -2) visible_message("[src] is trying to put handcuffs on [C]!") if(do_mob(src, C, 60)) if(!C.handcuffed) C.handcuffed = new /obj/item/handcuffs(C) C.update_inv_handcuffed() if(preparing_arrest_sounds.len) playsound(loc, pick(preparing_arrest_sounds), 50, 0) else if(istype(M, /mob/living/simple_animal) && !istype(M, /mob/living/bot/secbot)) var/mob/living/simple_animal/S = M S.adjustBruteLoss(15) do_attack_animation(M) playsound(loc, /decl/sound_category/swing_hit_sound, 50, 1, -1) is_attacking = 1 update_icon() addtimer(CALLBACK(src, .proc/stop_attacking_cb), 2) visible_message("[M] was beaten by [src] with a stun baton!") /mob/living/bot/secbot/proc/stop_attacking_cb() is_attacking = FALSE update_icon() /mob/living/bot/secbot/explode() visible_message("[src] blows apart!") var/turf/Tsec = get_turf(src) var/obj/item/secbot_assembly/Sa = new /obj/item/secbot_assembly(Tsec) Sa.build_step = 1 Sa.add_overlay("hs_hole") Sa.created_name = name new /obj/item/device/assembly/prox_sensor(Tsec) new /obj/item/melee/baton(Tsec) if(prob(50)) new /obj/item/robot_parts/l_arm(Tsec) spark(src, 3, alldirs) new /obj/effect/decal/cleanable/blood/oil(Tsec) qdel(src) /mob/living/bot/secbot/emag_act(var/remaining_charges, var/mob/user, var/feedback) if(!emagged) emagged = 1 to_chat(user, (feedback ? feedback : "You short out the lock of \the [src].")) return 1 /mob/living/bot/secbot/proc/scan_view() target = null for(var/mob/living/M in view(7, src)) if(M.invisibility >= INVISIBILITY_LEVEL_ONE) continue if(M.stat) continue var/threat = check_threat(M) if(threat >= 4) target = M say("Level [threat] infraction alert!") custom_emote(VISIBLE_MESSAGE, "points at [M.name]!") mode = SECBOT_HUNT break return /mob/living/bot/secbot/proc/calc_path(var/turf/avoid = null) path = AStar(loc, patrol_target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 120, id=botcard, exclude=avoid) if(isnull(path) || !path.len) path = list() return var/list/path_new = list() var/turf/last = path[path.len] path_new.Add(path[1]) for(var/i = 2, i < path.len, i++) if((path[i + 1].x == path[i].x) || (path[i + 1].y == path[i].y)) // we have a straight line, scan for more to cut down path_new.Add(path[i]) for(var/j = i + 1, j < path.len, j++) if((path[j + 1].x != path[j - 1].x) && (path[j + 1].y != path[j - 1].y)) // This is a corner and end point of our line path_new.Add(path[j]) i = j + 1 break else if(j == path.len - 1) path = list() path = path_new.Copy() path.Add(last) return else path_new.Add(path[i]) path = list() path = path_new.Copy() path.Add(last) /mob/living/bot/secbot/proc/check_threat(var/mob/living/M) if(!M || !istype(M) || M.stat || src == M) return 0 if(emagged) return 10 return M.assess_perp(access_scanner, 0, idcheck, check_records, check_arrest) /mob/living/bot/secbot/proc/patrol_step() if(loc == patrol_target) patrol_target = null path = list() mode = SECBOT_IDLE walk_to(src, src, 0, move_to_delay + 2) return if(path.len && patrol_target) var/turf/next = path[1] if(loc == next) path -= next walk_to(src, src, 0, move_to_delay + 2) return walk_to(src, next, 0, move_to_delay + 2) return else mode = SECBOT_START_PATROL /mob/living/bot/secbot/proc/find_patrol_target() send_status() nearest_beacon = null next_destination = "__nearest__" listener.post_signal(beacon_freq, "findbeacon", "patrol") /mob/living/bot/secbot/proc/find_next_target() send_status() nearest_beacon = null listener.post_signal(beacon_freq, "findbeacon", "patrol") /mob/living/bot/secbot/proc/send_status() var/list/kv = list( "type" = "secbot", "name" = name, "loca" = get_area(loc), "mode" = mode ) listener.post_signal_multiple(control_freq, kv) /obj/secbot_listener var/mob/living/bot/secbot/secbot = null /obj/secbot_listener/Destroy() secbot = null return ..() /obj/secbot_listener/proc/post_signal(var/freq, var/key, var/value) // send a radio signal with a single data key/value pair post_signal_multiple(freq, list("[key]" = value)) /obj/secbot_listener/proc/post_signal_multiple(var/freq, var/list/keyval) // send a radio signal with multiple data key/values var/datum/radio_frequency/frequency = SSradio.return_frequency(freq) if(!frequency) return var/datum/signal/signal = new() signal.source = secbot signal.transmission_method = 1 signal.data = keyval.Copy() if(signal.data["findbeacon"]) frequency.post_signal(secbot, signal, filter = RADIO_NAVBEACONS) else if(signal.data["type"] == "secbot") frequency.post_signal(secbot, signal, filter = RADIO_SECBOT) else frequency.post_signal(secbot, signal) /obj/secbot_listener/receive_signal(datum/signal/signal) if(!secbot || !secbot.on) return var/recv = signal.data["command"] if(recv == "bot_status") secbot.send_status() return if(signal.data["active"] == secbot) switch(recv) if("stop") secbot.mode = SECBOT_IDLE secbot.auto_patrol = 0 return if("go") secbot.mode = SECBOT_IDLE secbot.auto_patrol = 1 return if("summon") secbot.patrol_target = signal.data["target"] secbot.next_destination = secbot.destination secbot.destination = null //secbot.awaiting_beacon = 0 secbot.mode = SECBOT_SUMMON secbot.calc_path() secbot.say("Responding.") return recv = signal.data["beacon"] var/valid = signal.data["patrol"] if(!recv || !valid) return if(recv == secbot.next_destination) // This beacon is our target secbot.destination = secbot.next_destination secbot.patrol_target = signal.source.loc secbot.next_destination = signal.data["next_patrol"] else if(secbot.next_destination == "__nearest__") var/dist = get_dist(secbot, signal.source.loc) if(dist <= 1) return if(secbot.nearest_beacon) if(dist < secbot.closest_dist) secbot.nearest_beacon = recv secbot.patrol_target = secbot.nearest_beacon secbot.next_destination = signal.data["next_patrol"] secbot.closest_dist = dist return else secbot.nearest_beacon = recv secbot.patrol_target = secbot.nearest_beacon secbot.next_destination = signal.data["next_patrol"] secbot.closest_dist = dist /mob/living/bot/secbot/attack_hand(mob/living/carbon/human/M as mob) ..() if(M.a_intent == I_HURT ) //assume he wants to hurt us. idcheck = TRUE target = M mode = SECBOT_HUNT var/mob/living/carbon/human/H = M var/perpname = H.name var/obj/item/card/id/id = H.GetIdCard() if(id) perpname = id.registered_name var/datum/record/general/R = SSrecords.find_record("name", perpname) if(R && R.security) R.security.criminal = "*Arrest*" else check_records = TRUE broadcast_security_hud_message("[src] is under attack by [target], [arrest_type ? "detaining" : "arresting"] a level [check_threat(target)] suspect in [get_area(src)]. Requesting backup", src) /mob/living/bot/secbot/attack_generic(var/mob/user, var/damage, var/attack_message) ..() target = user mode = SECBOT_HUNT if(ishuman(user)) idcheck = TRUE var/mob/living/carbon/human/H = user var/perpname = H.name var/obj/item/card/id/id = H.GetIdCard() if(id) perpname = id.registered_name var/datum/record/general/R = SSrecords.find_record("name", perpname) if(R && R.security) R.security.criminal = "*Arrest*" else check_records = TRUE broadcast_security_hud_message("[src] is under attack by [target], [arrest_type ? "detaining" : "arresting"] a level [check_threat(target)] suspect in [get_area(src)]. Requesting backup", src) /mob/living/bot/secbot/bullet_act(var/obj/item/projectile/P, var/def_zone) ..() if (ismob(P.firer)) var/found = 0 // Check if we can see them. for(var/mob/living/M in view(7, src)) if(M.invisibility >= INVISIBILITY_LEVEL_ONE) continue if(M.stat) continue if(M == P.firer) found = 1 break if(!found) broadcast_security_hud_message("[src] was shot with [P], Unable to locate source! Requesting backup", src) return target = P.firer mode = SECBOT_HUNT if(ishuman(P.firer)) idcheck = TRUE var/mob/living/carbon/human/H = P.firer var/perpname = H.name var/obj/item/card/id/id = H.GetIdCard() if(id) perpname = id.registered_name var/datum/record/general/R = SSrecords.find_record("name", perpname) if(R && R.security) R.security.criminal = "*Arrest*" else check_records = TRUE broadcast_security_hud_message("[src] was shot with [P], projectile came from [target], [arrest_type ? "detaining" : "arresting"] a level [check_threat(target)] suspect in [get_area(src)]. Requesting backup", src) /mob/living/bot/secbot/attackby(var/obj/item/O, var/mob/user) ..() if(istype(O, /obj/item/card/id) || O.ispen() || istype(O, /obj/item/device/pda)) return target = user mode = SECBOT_HUNT if(ishuman(user)) idcheck = TRUE var/mob/living/carbon/human/H = user var/perpname = H.name var/obj/item/card/id/id = H.GetIdCard() if(id) perpname = id.registered_name var/datum/record/general/R = SSrecords.find_record("name", perpname) if(R && R.security) R.security.criminal = "*Arrest*" else check_records = TRUE broadcast_security_hud_message("[src] is under attack by [target] with [O], [arrest_type ? "detaining" : "arresting"] a level [check_threat(target)] suspect in [get_area(src)]. Requesting backup", src) /mob/living/bot/secbot/hitby(atom/movable/AM as mob|obj,var/speed = THROWFORCE_SPEED_DIVISOR) ..() if(istype(AM,/obj/)) var/obj/O = AM if(ismob(O.thrower)) target = O.thrower mode = SECBOT_HUNT if(ishuman(O.thrower)) idcheck = TRUE var/mob/living/carbon/human/H = O.thrower var/perpname = H.name var/obj/item/card/id/id = H.GetIdCard() if(id) perpname = id.registered_name var/datum/record/general/R = SSrecords.find_record("name", perpname) if(R && R.security) R.security.criminal = "*Arrest*" else check_records = TRUE broadcast_security_hud_message("[src] is under attack by [target] with [O], [arrest_type ? "detaining" : "arresting"] a level [check_threat(target)] suspect in [get_area(src)]. Requesting backup", src) //Secbot Construction /obj/item/clothing/head/helmet/attackby(var/obj/item/device/assembly/signaler/S, mob/user as mob) ..() if(!issignaler(S)) ..() return if(type != /obj/item/clothing/head/helmet) //Eh, but we don't want people making secbots out of space helmets. return if(S.secured) qdel(S) var/obj/item/secbot_assembly/A = new /obj/item/secbot_assembly user.put_in_hands(A) to_chat(user, "You add the signaler to the helmet.") user.drop_from_inventory(src) qdel(src) return 1 else return /obj/item/secbot_assembly name = "helmet/signaler assembly" desc = "Some sort of bizarre assembly." icon = 'icons/obj/aibots.dmi' icon_state = "helmet_signaler" item_state = "helmet" var/build_step = 0 var/created_name = "Securitron" /obj/item/secbot_assembly/attackby(var/obj/item/O, var/mob/user) ..() if(O.iswelder() && !build_step) var/obj/item/weldingtool/WT = O if(WT.remove_fuel(0, user)) build_step = 1 add_overlay("hs_hole") to_chat(user, "You weld a hole in \the [src].") return 1 else if(isprox(O) && (build_step == 1)) build_step = 2 to_chat(user, "You add \the [O] to [src].") add_overlay("hs_eye") name = "helmet/signaler/prox sensor assembly" user.drop_from_inventory(O,get_turf(src)) qdel(O) return 1 else if((istype(O, /obj/item/robot_parts/l_arm) || istype(O, /obj/item/robot_parts/r_arm)) && build_step == 2) build_step = 3 to_chat(user, "You add \the [O] to [src].") name = "helmet/signaler/prox sensor/robot arm assembly" add_overlay("hs_arm") user.drop_from_inventory(O,get_turf(src)) qdel(O) return 1 else if(istype(O, /obj/item/melee/baton) && build_step == 3) to_chat(user, "You complete the Securitron! Beep boop.") var/mob/living/bot/secbot/S = new /mob/living/bot/secbot(get_turf(src)) S.name = created_name user.drop_from_inventory(O,get_turf(src)) qdel(O) qdel(src) return 1 else if(O.ispen()) var/t = sanitizeSafe(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 #undef SECBOT_IDLE #undef SECBOT_HUNT #undef SECBOT_ARREST #undef SECBOT_START_PATROL #undef SECBOT_WAIT_PATROL #undef SECBOT_PATROL #undef SECBOT_SUMMON