From 2d443ca19970c1605565986df762e09b5bcbc9ae Mon Sep 17 00:00:00 2001 From: "rastaf.zero@gmail.com" Date: Tue, 15 Feb 2011 20:09:12 +0000 Subject: [PATCH] General bots-related code cleanup. Medibot sprite fixed. Aliens now attack bots instead of opening control window. All bots can be damaged and repaired. All bots are effected to EMP now. Asteroids generation was tuned to do not spawn asteroid on z-level edges. Enabling admin verbs now wont turn on deadchat if it was turned off. An emergency toolbox now contain a red crowbar. Mime job is now available to join as. Added radio channel for miners, use :h or :d ("diggers" lol) as shortcut. Added radio channel for cargo techs, use :h or :q as shortcut. Added a personal headsets to HoP and QM. Some unrelated runtime errors was fixed. git-svn-id: http://tgstation13.googlecode.com/svn/trunk@1050 316c924e-a436-60f5-8080-3fe189b3f50e --- code/defines/global.dm | 1 + code/defines/obj/radio.dm | 31 +- code/defines/obj/weapon.dm | 8 +- code/game/asteroid/asteroid.dm | 2 +- code/game/communications.dm | 8 +- code/game/jobs/jobprocs.dm | 5 +- code/game/machinery/bots/bots.dm | 123 ++ code/game/machinery/bots/cleanbot.dm | 72 +- code/game/machinery/bots/ed209bot.dm | 1196 ++++++++------- code/game/machinery/bots/floorbot.dm | 71 +- code/game/machinery/bots/medbot.dm | 102 +- code/game/machinery/bots/mulebot.dm | 1629 +++++++++++---------- code/game/machinery/bots/secbot.dm | 1039 +++++++------ code/game/objects/devices/PDA/radio.dm | 2 +- code/game/objects/storage/toolbox.dm | 2 +- code/modules/admin/admin_verbs.dm | 2 + code/modules/mob/living/say.dm | 4 + code/modules/mob/mob.dm | 3 +- code/modules/mob/new_player/new_player.dm | 5 + code/setup.dm | 1 + icons/changelog.html | 7 +- icons/obj/aibots.dmi | Bin 27622 -> 27613 bytes icons/obj/device.dmi | Bin 19919 -> 15023 bytes icons/obj/items.dmi | Bin 31857 -> 32070 bytes icons/obj/radio.dmi | Bin 0 -> 4323 bytes 25 files changed, 2263 insertions(+), 2050 deletions(-) create mode 100644 icons/obj/radio.dmi diff --git a/code/defines/global.dm b/code/defines/global.dm index 712d0dc9e14..ba58eb24e62 100644 --- a/code/defines/global.dm +++ b/code/defines/global.dm @@ -101,6 +101,7 @@ var viroMax = 1 wardenMax = 1 cmoMax = 1 + mimeMax = 1 list/bombers = list( ) list/admin_log = list ( ) diff --git a/code/defines/obj/radio.dm b/code/defines/obj/radio.dm index 07aeb3fd96f..32ef45e0e70 100644 --- a/code/defines/obj/radio.dm +++ b/code/defines/obj/radio.dm @@ -1,4 +1,5 @@ /obj/item/device/radio + icon = 'radio.dmi' name = "station bounced radio" suffix = "\[3\]" icon_state = "walkietalkie" @@ -116,7 +117,7 @@ name = "Captain's Headset" icon_state = "com_headset" item_state = "headset" - channels = list("Command" = 1, "Science" = 0, "Medical" = 0, "Security" = 1, "Engineering" = 0) + channels = list("Command" = 1, "Science" = 0, "Medical" = 0, "Security" = 1, "Engineering" = 0, "Mining" = 0, "Cargo" = 0) /obj/item/device/radio/headset/heads/rd // -- Bar name = "Research Director's Headset" @@ -134,10 +135,34 @@ name = "Chief Engineer's Headset" icon_state = "com_headset" item_state = "headset" - channels = list("Engineering" = 1, "Command" = 1) + channels = list("Engineering" = 1, "Command" = 1, "Mining" = 1) /obj/item/device/radio/headset/heads/cmo // -- Bar name = "Chief Medical Officer's Headset" icon_state = "com_headset" item_state = "headset" - channels = list("Medical" = 1, "Command" = 1) \ No newline at end of file + channels = list("Medical" = 1, "Command" = 1) + +/obj/item/device/radio/headset/heads/hop // -- rastaf0 + name = "Head of Personnel's Headset" + icon_state = "com_headset" + item_state = "headset" + channels = list("Command" = 1, "Security" = 1, "Cargo" = 0) + +/obj/item/device/radio/headset/headset_mine // -- rastaf0 + name = "Mining Radio Headset" + icon_state = "mine_headset" + item_state = "headset" + channels = list("Mining" = 1) + +/obj/item/device/radio/headset/headset_cargo // -- rastaf0 + name = "Cargo Radio Headset" + icon_state = "cargo_headset" + item_state = "headset" + channels = list("Cargo" = 1) + +/obj/item/device/radio/headset/heads/qm // -- rastaf0 //he is not a real head but he gives orders sometimes + name = "Quartermaster's Headset" + icon_state = "cargo_headset" + item_state = "headset" + channels = list("Cargo" = 1, "Mining" = 1) //no command frequency yet diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm index becb0ee43a3..51df169d756 100644 --- a/code/defines/obj/weapon.dm +++ b/code/defines/obj/weapon.dm @@ -376,6 +376,10 @@ item_state = "wrench" w_class = 2.0 m_amt = 50 + +/obj/item/weapon/crowbar/red + icon = 'items.dmi' + icon_state = "red_crowbar" /obj/item/weapon/disk name = "disk" @@ -991,7 +995,7 @@ Total SMES charging rate should not exceed total power generation rate, or an ov /obj/item/weapon/syndicate_uplink name = "station bounced radio" - icon = 'device.dmi' + icon = 'radio.dmi' icon_state = "radio" var/temp = null var/uses = 10.0 @@ -1007,7 +1011,7 @@ Total SMES charging rate should not exceed total power generation rate, or an ov /obj/item/weapon/SWF_uplink name = "station bounced radio" - icon = 'device.dmi' + icon = 'radio.dmi' icon_state = "radio" var/temp = null var/uses = 4.0 diff --git a/code/game/asteroid/asteroid.dm b/code/game/asteroid/asteroid.dm index 62e2d97b8c5..6d288d76643 100644 --- a/code/game/asteroid/asteroid.dm +++ b/code/game/asteroid/asteroid.dm @@ -1,5 +1,5 @@ proc/spawn_asteroid(var/atom/start_loc,var/type,var/size,var/richness)//type: 0 or null - random, 1 - nothing, 2 - iron, 3 - silicon - if(start_loc.x - size < 3 || start_loc.x + size >= world.maxx - 3 || start_loc.y - size < 3 || start_loc.y + size > world.maxy -3) + if(start_loc.x - size < 5 || start_loc.x + size >= world.maxx - 5 || start_loc.y - size < 5 || start_loc.y + size > world.maxy -5) return 0 if(!size) size = pick(100;2,50;3,35;4,25;6,10;12) diff --git a/code/game/communications.dm b/code/game/communications.dm index c2748c90a2d..763e80696e4 100644 --- a/code/game/communications.dm +++ b/code/game/communications.dm @@ -74,6 +74,8 @@ Radio: 1359 - Security 1441 - death squad 1443 - Confession Intercom +1349 - Miners +1347 - Cargo techs Devices: 1451 - tracking implant @@ -104,10 +106,12 @@ var/list/radiochannels = list( "Security" = 1359, "Deathsquad" = 1441, "Syndicate" = 1213, + "Mining" = 1349, + "Cargo" = 1347, ) //depenging helpers -var/list/DEPT_FREQS = list(1351,1355,1357,1359,1213,1441) -var/const/COMM_FREQ = 1353 +var/list/DEPT_FREQS = list(1351,1355,1357,1359,1213,1441,1349,1347) +var/const/COMM_FREQ = 1353 //command, colored gold in chat window var/const/SYND_FREQ = 1213 #define TRANSMISSION_WIRE 0 diff --git a/code/game/jobs/jobprocs.dm b/code/game/jobs/jobprocs.dm index 6293afd8da7..5eff0949634 100644 --- a/code/game/jobs/jobprocs.dm +++ b/code/game/jobs/jobprocs.dm @@ -367,6 +367,7 @@ src.equip_if_possible(new /obj/item/device/t_scanner(src), slot_r_store) if ("Shaft Miner") + src.equip_if_possible(new /obj/item/device/radio/headset/headset_mine (src), slot_ears) src.equip_if_possible(new /obj/item/clothing/under/color/white(src), slot_w_uniform) src.equip_if_possible(new /obj/item/clothing/shoes/black(src), slot_shoes) src.equip_if_possible(new /obj/item/clothing/gloves/black(src), slot_gloves) @@ -475,7 +476,7 @@ if ("Head of Personnel") - src.equip_if_possible(new /obj/item/device/radio/headset/headset_com (src), slot_ears) // -- TLE + src.equip_if_possible(new /obj/item/device/radio/headset/heads/hop (src), slot_ears) // -- TLE src.equip_if_possible(new /obj/item/weapon/storage/backpack(src), slot_back) src.equip_if_possible(new /obj/item/device/pda/heads(src), slot_belt) src.equip_if_possible(new /obj/item/clothing/under/rank/head_of_personnel(src), slot_w_uniform) @@ -546,6 +547,7 @@ src.equip_if_possible(new /obj/item/weapon/storage/briefcase(src), slot_l_hand) if ("Quartermaster") + src.equip_if_possible(new /obj/item/device/radio/headset/heads/qm (src), slot_ears) src.equip_if_possible(new /obj/item/clothing/gloves/black(src), slot_gloves) src.equip_if_possible(new /obj/item/clothing/shoes/black(src), slot_shoes) src.equip_if_possible(new /obj/item/clothing/under/rank/cargo(src), slot_w_uniform) @@ -555,6 +557,7 @@ //src.equip_if_possible(new /obj/item/clothing/suit/exo_suit(src), slot_wear_suit) if ("Cargo Technician") + src.equip_if_possible(new /obj/item/device/radio/headset/headset_cargo(src), slot_ears) src.equip_if_possible(new /obj/item/clothing/gloves/black(src), slot_gloves) src.equip_if_possible(new /obj/item/clothing/shoes/black(src), slot_shoes) src.equip_if_possible(new /obj/item/clothing/under/rank/cargo(src), slot_w_uniform) diff --git a/code/game/machinery/bots/bots.dm b/code/game/machinery/bots/bots.dm index c2da21c1670..7d3b24cb6c9 100644 --- a/code/game/machinery/bots/bots.dm +++ b/code/game/machinery/bots/bots.dm @@ -4,9 +4,132 @@ icon = 'aibots.dmi' layer = MOB_LAYER var/obj/item/weapon/card/id/botcard // the ID card that the bot "holds" + var/on = 1 + var/health = 0 //do not forget to set health for your bot! + var/maxhealth = 0 + var/fire_dam_coeff = 1.0 + var/brute_dam_coeff = 1.0 + + +/obj/machinery/bot/proc/turn_on() + if (stat) + return 0 + src.on = 1 + return 1 + +/obj/machinery/bot/proc/turn_off() + src.on = 0 + +/obj/machinery/bot/proc/explode() + del(src) + +/obj/machinery/bot/proc/healthcheck() + if (src.health <= 0) + src.explode() + +/obj/machinery/bot/examine() + set src in view() + ..() + if (src.health < maxhealth) + if (src.health > maxhealth/3) + usr << text("\red [src]'s parts look loose.") + else + usr << text("\red [src]'s parts look very loose!") + return + +/obj/machinery/bot/attack_alien(var/mob/living/carbon/alien/user as mob) + /* Well, aliums dislike machines and do not want to caress them. + if (user.a_intent == "help") + for(var/mob/O in viewers(src, null)) + if ((O.client && !( O.blinded ))) + O.show_message(text("\blue [user] caresses [src.name] with its scythe like arm."), 1) + else + */ + src.health -= rand(15,30)*brute_dam_coeff + src.visible_message("\red [user] has slashed [src]!") + playsound(src.loc, 'slice.ogg', 25, 1, -1) + if(prob(10)) + new /obj/decal/cleanable/oil(src.loc) + healthcheck() +/obj/machinery/bot/attackby(obj/item/weapon/W as obj, mob/user as mob) + if (istype(W, /obj/item/weapon/screwdriver)) + if (src.health < maxhealth) + src.health = min(maxhealth, src.health+25) + user.visible_message( + "\red [user] repairs [src]!", + "\blue You repair [src]!" + ) + else + user << "\blue [src] does not need a repair!" + else + switch(W.damtype) + if("fire") + src.health -= W.force * fire_dam_coeff + if("brute") + src.health -= W.force * brute_dam_coeff + ..() + healthcheck() + +/obj/machinery/bot/bullet_act(flag, A as obj) + switch(flag) + if (PROJECTILE_BULLET) + src.health -= 20 + //if (PROJECTILE_WEAKBULLET || PROJECTILE_BEANBAG) //Detective's revolver fires marshmallows + // src.health -= 2 + if (PROJECTILE_LASER) + src.health -= 10 + if (PROJECTILE_PULSE) + src.health -= 40 + healthcheck() + +/obj/machinery/bot/meteorhit() + src.explode() + return + +/obj/machinery/bot/blob_act() + src.health -= rand(20,40)*fire_dam_coeff + healthcheck() + return + +/obj/machinery/bot/ex_act(severity) + switch(severity) + if(1.0) + src.explode() + return + if(2.0) + src.health -= rand(5,10)*fire_dam_coeff + src.health -= rand(10,20)*brute_dam_coeff + healthcheck() + return + if(3.0) + if (prob(50)) + src.health -= rand(1,5)*fire_dam_coeff + src.health -= rand(1,5)*brute_dam_coeff + healthcheck() + return + return + +/obj/machinery/bot/emp_act(severity) + var/was_on = on + stat |= EMPED + var/obj/overlay/pulse2 = new/obj/overlay ( src.loc ) + pulse2.icon = 'effects.dmi' + pulse2.icon_state = "empdisable" + pulse2.name = "emp sparks" + pulse2.anchored = 1 + pulse2.dir = pick(cardinal) + + spawn(10) + del(pulse2) + if (on) + turn_off() + spawn(severity*300) + stat &= ~EMPED + if (was_on) + turn_on() /******************************************************************/ // Navigation procs // Used for A-star pathfinding diff --git a/code/game/machinery/bots/cleanbot.dm b/code/game/machinery/bots/cleanbot.dm index 2ddb9e985a5..8afc5a5b6de 100644 --- a/code/game/machinery/bots/cleanbot.dm +++ b/code/game/machinery/bots/cleanbot.dm @@ -23,7 +23,8 @@ density = 0 anchored = 0 //weight = 1.0E7 - var/on = 1 + health = 25 + maxhealth = 25 var/cleaning = 0 var/locked = 1 var/screwloose = 0 @@ -36,15 +37,35 @@ var/oldloc = null req_access = list(access_janitor) var/path[] = new() - - + /obj/machinery/bot/cleanbot/New() ..() src.get_targets() src.icon_state = "cleanbot[src.on]" -/obj/machinery/bot/cleanbot/attack_hand(user as mob) +/obj/machinery/bot/cleanbot/turn_on() + . = ..() + src.icon_state = "cleanbot[src.on]" + src.updateUsrDialog() + +/obj/machinery/bot/cleanbot/turn_off() + ..() + src.target = null + src.oldtarget = null + src.oldloc = null + src.icon_state = "cleanbot[src.on]" + src.path = new() + src.updateUsrDialog() + +/obj/machinery/bot/cleanbot/attack_hand(mob/user as mob) + . = ..() + if (.) + return + usr.machine = src + interact(user) + +/obj/machinery/bot/cleanbot/proc/interact(mob/user as mob) var/dat dat += text({" Automatic Station Cleaner v1.0

@@ -73,13 +94,10 @@ text("[src.oddbutton ? "Yes" : "No" src.add_fingerprint(usr) switch(href_list["operation"]) if("start") - src.on = !src.on - src.target = null - src.oldtarget = null - src.oldloc = null - src.icon_state = "cleanbot[src.on]" - src.path = new() - src.updateUsrDialog() + if (src.on) + turn_off() + else + turn_on() if("blood") src.blood =!src.blood src.get_targets() @@ -94,12 +112,10 @@ text("[src.oddbutton ? "Yes" : "No" src.updateUsrDialog() /obj/machinery/bot/cleanbot/attack_ai() - src.on = !src.on - src.target = null - src.oldtarget = null - src.oldloc = null - src.icon_state = "cleanbot[src.on]" - src.path = new() + if (src.on) + turn_off() + else + turn_on() /obj/machinery/bot/cleanbot/attackby(obj/item/weapon/W, mob/user as mob) if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) @@ -108,7 +124,7 @@ text("[src.oddbutton ? "Yes" : "No" user << "You [ src.locked ? "lock" : "unlock"] the [src] behaviour controls." else user << "\red This [src] doesn't seem to accept your authority." - if (istype(W, /obj/item/weapon/screwdriver)) + else if (istype(W, /obj/item/weapon/screwdriver)) if(!src.locked) src.panelopen = !src.panelopen user << "You [ src.panelopen ? "open" : "close"] the hidden panel on [src]." @@ -118,6 +134,8 @@ text("[src.oddbutton ? "Yes" : "No" src.screwloose = 1 src.panelopen = 0 src.locked = 1*/ + else + return ..() /obj/machinery/bot/cleanbot/process() set background = 1 @@ -211,6 +229,24 @@ text("[src.oddbutton ? "Yes" : "No" src.anchored = 0 src.target = null +/obj/machinery/bot/cleanbot/explode() + src.on = 0 + src.visible_message("\red [src] blows apart!", 1) + var/turf/Tsec = get_turf(src) + + new /obj/item/weapon/reagent_containers/glass/bucket(Tsec) + + new /obj/item/device/prox_sensor(Tsec) + + if (prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + var/datum/effects/system/spark_spread/s = new /datum/effects/system/spark_spread + s.set_up(3, 1, src) + s.start() + del(src) + return + /obj/item/weapon/bucket_sensor/attackby(var/obj/item/W, mob/user as mob) if(istype(W, /obj/item/robot_parts/l_arm) || istype(W, /obj/item/robot_parts/r_arm)) var/obj/machinery/bot/cleanbot/A = new /obj/machinery/bot/cleanbot diff --git a/code/game/machinery/bots/ed209bot.dm b/code/game/machinery/bots/ed209bot.dm index 62cda87a4df..ed8b745127d 100644 --- a/code/game/machinery/bots/ed209bot.dm +++ b/code/game/machinery/bots/ed209bot.dm @@ -8,13 +8,16 @@ anchored = 0 // weight = 1.0E7 req_access = list(access_security) + health = 100 + maxhealth = 100 + fire_dam_coeff = 0.7 + brute_dam_coeff = 0.5 var/lastfired = 0 var/shot_delay = 3 //.3 seconds between shots - var/lasers = 0 + //var/lasers = 0 - var/on = 1 var/locked = 1 //Behavior Controls lock var/mob/living/carbon/target var/oldtarget_name @@ -23,7 +26,6 @@ var/last_found //There's a delay var/frustration = 0 var/emagged = 0 //Emagged Secbots view everyone as a criminal - var/health = 100 var/idcheck = 1 //If false, all station IDs are authorized for weapons. var/check_records = 1 //Does it check security records? var/arrest_type = 0 //If true, don't handcuff @@ -68,44 +70,50 @@ var/created_name = "ED-209 Security Robot" //To preserve the name if it's a unique securitron I guess +/obj/machinery/bot/ed209/New() + ..() + src.icon_state = "ed209[src.on]" + spawn(3) + src.botcard = new /obj/item/weapon/card/id(src) + src.botcard.access = get_access("Detective") + src.cam = new /obj/machinery/camera(src) + src.cam.c_tag = src.name + src.cam.network = "SS13" + if(radio_controller) + radio_controller.add_object(src, control_freq, filter = RADIO_SECBOT) + radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS) -/obj/machinery/bot/ed209 - New() - ..() - src.icon_state = "ed209[src.on]" - spawn(3) - src.botcard = new /obj/item/weapon/card/id(src) - src.botcard.access = get_access("Detective") - src.cam = new /obj/machinery/camera(src) - src.cam.c_tag = src.name - src.cam.network = "SS13" - if(radio_controller) - radio_controller.add_object(src, control_freq, filter = RADIO_SECBOT) - radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS) +/obj/machinery/bot/ed209/turn_on() + . = ..() + src.icon_state = "ed209[src.on]" + src.mode = SECBOT_IDLE + src.updateUsrDialog() - examine() - set src in view() - ..() +/obj/machinery/bot/ed209/turn_off() + ..() + src.target = null + src.oldtarget_name = null + src.anchored = 0 + src.mode = SECBOT_IDLE + walk_to(src,0) + src.icon_state = "ed209[src.on]" + src.updateUsrDialog() - if (src.health < 25) - if (src.health > 15) - usr << text("\red [src]'s parts look loose.") - else - usr << text("\red [src]'s parts look very loose!") +/obj/machinery/bot/ed209/attack_hand(mob/user as mob) + . = ..() + if (.) return + var/dat - attack_hand(user as mob) - var/dat - - dat += text({" + dat += text({" Automatic Security Unit v2.5

Status: []
Behaviour controls are [src.locked ? "locked" : "unlocked"]"}, "
[src.on ? "On" : "Off"]" ) - if(!src.locked) - dat += text({"
+ if(!src.locked) + dat += text({"
Check for Weapon Authorization: []
Check Security Records: []
Operating Mode: []
@@ -117,650 +125,669 @@ Auto Patrol: []"}, "[auto_patrol ? "On" : "Off"]" ) - user << browse("Securitron v2.5 controls[dat]", "window=autosec") - onclose(user, "autosec") + user << browse("Securitron v2.5 controls[dat]", "window=autosec") + onclose(user, "autosec") + return + +/obj/machinery/bot/ed209/Topic(href, href_list) + if (..()) + return + usr.machine = src + src.add_fingerprint(usr) + if ((href_list["power"]) && (src.allowed(usr))) + if (src.on) + turn_off() + else + turn_on() return - Topic(href, href_list) - usr.machine = src - src.add_fingerprint(usr) - if ((href_list["power"]) && (src.allowed(usr))) - src.on = !src.on - src.target = null - src.oldtarget_name = null - src.anchored = 0 - src.mode = SECBOT_IDLE - walk_to(src,0) - src.icon_state = "ed209[src.on]" + switch(href_list["operation"]) + if ("idcheck") + src.idcheck = !src.idcheck src.updateUsrDialog() - - switch(href_list["operation"]) - if ("idcheck") - src.idcheck = !src.idcheck - src.updateUsrDialog() - if ("ignorerec") - src.check_records = !src.check_records - src.updateUsrDialog() - if ("switchmode") - src.arrest_type = !src.arrest_type - src.updateUsrDialog() - if("patrol") - auto_patrol = !auto_patrol - mode = SECBOT_IDLE - updateUsrDialog() - - attack_ai(mob/user as mob) - src.on = !src.on - src.target = null - src.oldtarget_name = null - mode = SECBOT_IDLE - src.anchored = 0 - src.icon_state = "ed209[src.on]" - walk_to(src,0) - - attackby(obj/item/weapon/W as obj, mob/user as mob) - if ((istype(W, /obj/item/weapon/card/emag)) && (!src.emagged)) - user << "\red You short out [src]'s target assessment circuits." - spawn(0) - for(var/mob/O in hearers(src, null)) - O.show_message("\red [src] buzzes oddly!", 1) - src.target = null - src.oldtarget_name = user.name - src.last_found = world.time - src.anchored = 0 - src.emagged = 1 - src.on = 1 - src.icon_state = "ed209[src.on]" + if ("ignorerec") + src.check_records = !src.check_records + src.updateUsrDialog() + if ("switchmode") + src.arrest_type = !src.arrest_type + src.updateUsrDialog() + if("patrol") + auto_patrol = !auto_patrol mode = SECBOT_IDLE - else if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) - if (src.allowed(user)) - src.locked = !src.locked - user << "Controls are now [src.locked ? "locked." : "unlocked."]" - else - user << "\red Access denied." + updateUsrDialog() - else if (istype(W, /obj/item/weapon/screwdriver)) - if (src.health < 25) - src.health = 25 - for(var/mob/O in viewers(src, null)) - O << "\red [user] repairs [src]!" +/obj/machinery/bot/ed209/attack_ai(mob/user as mob) + if (src.on) + turn_off() + else + turn_on() + +/obj/machinery/bot/ed209/attackby(obj/item/weapon/W as obj, mob/user as mob) + if ((istype(W, /obj/item/weapon/card/emag)) && (!src.emagged)) + user << "\red You short out [src]'s target assessment circuits." + spawn(0) + for(var/mob/O in hearers(src, null)) + O.show_message("\red [src] buzzes oddly!", 1) + src.target = null + src.oldtarget_name = user.name + src.last_found = world.time + src.anchored = 0 + src.emagged = 1 + src.on = 1 + src.icon_state = "ed209[src.on]" + mode = SECBOT_IDLE + else if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) + if (src.allowed(user)) + src.locked = !src.locked + user << "Controls are now [src.locked ? "locked." : "unlocked."]" else - switch(W.damtype) - if("fire") - src.health -= W.force * 0.75 - if("brute") - src.health -= W.force * 0.5 - else - if (src.health <= 0) - src.explode() - else if ((W.force) && (!src.target)) - src.target = user - src.mode = SECBOT_HUNT - ..() + user << "\red Access denied." + else + ..() + if (!istype(W, /obj/item/weapon/screwdriver) && (W.force) && (!src.target)) + src.target = user + src.mode = SECBOT_HUNT +/obj/machinery/bot/ed209/process() + set background = 1 + if (!src.on) + return + var/list/targets = list() + for (var/mob/living/carbon/C in view(12,src)) //Let's find us a target + var/threatlevel = 0 + if ((C.stat) || (C.lying)) + continue + if (istype(C, /mob/living/carbon/human)) + threatlevel = src.assess_perp(C) + else if ((istype(C, /mob/living/carbon/monkey)) && (C.client) && (ticker.mode.name == "monkey")) + threatlevel = 4 + //src.speak(C.real_name + text(": threat: []", threatlevel)) + if (threatlevel < 4 ) + continue - process() - set background = 1 + var/dst = get_dist(src, C) + if ( dst <= 1 || dst > 12) + continue - if (!src.on) - return - var/list/targets = list() - for (var/mob/living/carbon/C in view(12,src)) //Let's find us a target - var/threatlevel = 0 - if ((C.stat) || (C.lying)) - continue - if (istype(C, /mob/living/carbon/human)) - threatlevel = src.assess_perp(C) - else if ((istype(C, /mob/living/carbon/monkey)) && (C.client) && (ticker.mode.name == "monkey")) - threatlevel = 4 - //src.speak(C.real_name + text(": threat: []", threatlevel)) - if (threatlevel < 4 ) - continue + targets += C + if (targets.len>0) + var/mob/t = pick(targets) + if (istype(t, /mob/living)) + if (t.stat!=2) + //src.speak("selected target: " + t.real_name) + src.shootAt(t) + switch(mode) - var/dst = get_dist(src, C) - if ( dst <= 1 || dst > 12) - continue + if(SECBOT_IDLE) // idle + walk_to(src,0) + look_for_perp() // see if any criminals are in range + if(!mode && auto_patrol) // still idle, and set to patrol + mode = SECBOT_START_PATROL // switch to patrol mode - targets += C - if (targets.len>0) - var/mob/t = pick(targets) - if (istype(t, /mob/living)) - if (t.stat!=2) - //src.speak("selected target: " + t.real_name) - src.shootAt(t) - switch(mode) + if(SECBOT_HUNT) // hunting for perp - if(SECBOT_IDLE) // idle + // if can't reach perp for long enough, go idle + if (src.frustration >= 8) + // for(var/mob/O in hearers(src, null)) + // O << "[src] beeps, \"Backup requested! Suspect has evaded arrest.\"" + src.target = null + src.last_found = world.time + src.frustration = 0 + src.mode = 0 walk_to(src,0) - look_for_perp() // see if any criminals are in range - if(!mode && auto_patrol) // still idle, and set to patrol - mode = SECBOT_START_PATROL // switch to patrol mode - if(SECBOT_HUNT) // hunting for perp - - // if can't reach perp for long enough, go idle - if (src.frustration >= 8) - // for(var/mob/O in hearers(src, null)) - // O << "[src] beeps, \"Backup requested! Suspect has evaded arrest.\"" - src.target = null - src.last_found = world.time - src.frustration = 0 - src.mode = 0 - walk_to(src,0) - - if (target) // make sure target exists - if (get_dist(src, src.target) <= 1) // if right next to perp - playsound(src.loc, 'Egloves.ogg', 50, 1, -1) - src.icon_state = "ed209-c" - spawn(2) - src.icon_state = "ed209[src.on]" - var/mob/living/carbon/M = src.target - var/maxstuns = 4 - if (istype(M, /mob/living/carbon/human)) - if (M.weakened < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) - M.weakened = 10 - if (M.stuttering < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) - M.stuttering = 10 - if (M.stunned < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) - M.stunned = 10 - else + if (target) // make sure target exists + if (get_dist(src, src.target) <= 1) // if right next to perp + playsound(src.loc, 'Egloves.ogg', 50, 1, -1) + src.icon_state = "ed209-c" + spawn(2) + src.icon_state = "ed209[src.on]" + var/mob/living/carbon/M = src.target + var/maxstuns = 4 + if (istype(M, /mob/living/carbon/human)) + if (M.weakened < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) M.weakened = 10 + if (M.stuttering < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) M.stuttering = 10 + if (M.stunned < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) M.stunned = 10 - maxstuns-- - if (maxstuns <= 0) - target = null - for(var/mob/O in viewers(src, null)) - O.show_message("\red [src.target] has been stunned by [src]!", 1, "\red You hear someone fall", 2) - - mode = SECBOT_PREP_ARREST - src.anchored = 1 - src.target_lastloc = M.loc - return - - else // not next to perp - var/turf/olddist = get_dist(src, src.target) - walk_to(src, src.target,1,4) - if ((get_dist(src, src.target)) >= (olddist)) - src.frustration++ - else - src.frustration = 0 - - if(SECBOT_PREP_ARREST) // preparing to arrest target - - // see if he got away - if ((get_dist(src, src.target) > 1) || ((src.target:loc != src.target_lastloc) && src.target:weakened < 2)) - src.anchored = 0 - mode = SECBOT_HUNT - return - - if (!src.target.handcuffed && !src.arrest_type) - playsound(src.loc, 'handcuffs.ogg', 30, 1, -2) - mode = SECBOT_ARREST + else + M.weakened = 10 + M.stuttering = 10 + M.stunned = 10 + maxstuns-- + if (maxstuns <= 0) + target = null for(var/mob/O in viewers(src, null)) - O.show_message("\red [src] is trying to put handcuffs on [src.target]!", 1) + O.show_message("\red [src.target] has been stunned by [src]!", 1, "\red You hear someone fall", 2) - spawn(60) - if (get_dist(src, src.target) <= 1) - if (src.target.handcuffed) - return - - if(istype(src.target,/mob/living/carbon)) - src.target.handcuffed = new /obj/item/weapon/handcuffs(src.target) - - mode = SECBOT_IDLE - src.target = null - src.anchored = 0 - src.last_found = world.time - src.frustration = 0 - - // playsound(src.loc, pick('bgod.ogg', 'biamthelaw.ogg', 'bsecureday.ogg', 'bradio.ogg', 'binsult.ogg', 'bcreep.ogg'), 50, 0) - // var/arrest_message = pick("Have a secure day!","I AM THE LAW.", "God made tomorrow for the crooks we don't catch today.","You can't outrun a radio.") - // src.speak(arrest_message) - - if(SECBOT_ARREST) // arresting - if (src.target.handcuffed) - src.anchored = 0 - mode = SECBOT_IDLE + mode = SECBOT_PREP_ARREST + src.anchored = 1 + src.target_lastloc = M.loc return + else // not next to perp + var/turf/olddist = get_dist(src, src.target) + walk_to(src, src.target,1,4) + if ((get_dist(src, src.target)) >= (olddist)) + src.frustration++ + else + src.frustration = 0 - if(SECBOT_START_PATROL) // start a patrol + if(SECBOT_PREP_ARREST) // preparing to arrest target + if (!target) + mode = SECBOT_IDLE + src.anchored = 0 + return + // see if he got away + if ((get_dist(src, src.target) > 1) || ((src.target:loc != src.target_lastloc) && src.target:weakened < 2)) + src.anchored = 0 + mode = SECBOT_HUNT + return - if(path.len > 0 && patrol_target) // have a valid path, so just resume - mode = SECBOT_PATROL - return + if (!src.target.handcuffed && !src.arrest_type) + playsound(src.loc, 'handcuffs.ogg', 30, 1, -2) + mode = SECBOT_ARREST + for(var/mob/O in viewers(src, null)) + O.show_message("\red [src] is trying to put handcuffs on [src.target]!", 1) - else if(patrol_target) // has patrol target already - spawn(0) - calc_path() // so just find a route to it - if(path.len == 0) - patrol_target = 0 + spawn(60) + if (get_dist(src, src.target) <= 1) + if (src.target.handcuffed) return - mode = SECBOT_PATROL + if(istype(src.target,/mob/living/carbon)) + src.target.handcuffed = new /obj/item/weapon/handcuffs(src.target) - else // no patrol target, so need a new one - find_patrol_target() - speak("Engaging patrol mode.") + mode = SECBOT_IDLE + src.target = null + src.anchored = 0 + src.last_found = world.time + src.frustration = 0 + // playsound(src.loc, pick('bgod.ogg', 'biamthelaw.ogg', 'bsecureday.ogg', 'bradio.ogg', 'binsult.ogg', 'bcreep.ogg'), 50, 0) + // var/arrest_message = pick("Have a secure day!","I AM THE LAW.", "God made tomorrow for the crooks we don't catch today.","You can't outrun a radio.") + // src.speak(arrest_message) - if(SECBOT_PATROL) // patrol mode - patrol_step() - spawn(5) - if(mode == SECBOT_PATROL) - patrol_step() - - if(SECBOT_SUMMON) // summoned to PDA - patrol_step() - spawn(4) - if(mode == SECBOT_SUMMON) - patrol_step() - sleep(4) - patrol_step() - - return - - - // perform a single patrol step - - proc/patrol_step() - - if(loc == patrol_target) // reached target - at_patrol_target() - return - - else if(path.len > 0 && patrol_target) // valid path - - var/turf/next = path[1] - if(next == loc) - path -= next + if(SECBOT_ARREST) // arresting + if (!target || src.target.handcuffed) + src.anchored = 0 + mode = SECBOT_IDLE return - if(istype( next, /turf/simulated)) + if(SECBOT_START_PATROL) // start a patrol - var/moved = step_towards(src, next) // attempt to move - if(moved) // successful move - blockcount = 0 - path -= loc - - look_for_perp() - else // failed to move - - blockcount++ - - if(blockcount > 5) // attempt 5 times before recomputing - // find new path excluding blocked turf - - spawn(2) - calc_path(next) - if(path.len == 0) - find_patrol_target() - else - blockcount = 0 + if(path.len > 0 && patrol_target) // have a valid path, so just resume + mode = SECBOT_PATROL + return + else if(patrol_target) // has patrol target already + spawn(0) + calc_path() // so just find a route to it + if(path.len == 0) + patrol_target = 0 return + mode = SECBOT_PATROL + + + else // no patrol target, so need a new one + find_patrol_target() + speak("Engaging patrol mode.") + + + if(SECBOT_PATROL) // patrol mode + patrol_step() + spawn(5) + if(mode == SECBOT_PATROL) + patrol_step() + + if(SECBOT_SUMMON) // summoned to PDA + patrol_step() + spawn(4) + if(mode == SECBOT_SUMMON) + patrol_step() + sleep(4) + patrol_step() + + return + + +// perform a single patrol step + +/obj/machinery/bot/ed209/proc/patrol_step() + + if(loc == patrol_target) // reached target + at_patrol_target() + return + + else if(path.len > 0 && patrol_target) // valid path + + var/turf/next = path[1] + if(next == loc) + path -= next + return + + + if(istype( next, /turf/simulated)) + + var/moved = step_towards(src, next) // attempt to move + if(moved) // successful move + blockcount = 0 + path -= loc + + look_for_perp() + else // failed to move + + blockcount++ + + if(blockcount > 5) // attempt 5 times before recomputing + // find new path excluding blocked turf + + spawn(2) + calc_path(next) + if(path.len == 0) + find_patrol_target() + else + blockcount = 0 return - else // not a valid turf - mode = SECBOT_IDLE return - else // no path, so calculate new one - mode = SECBOT_START_PATROL - - - // finds a new patrol target - proc/find_patrol_target() - send_status() - if(awaiting_beacon) // awaiting beacon response - awaiting_beacon++ - if(awaiting_beacon > 5) // wait 5 secs for beacon response - find_nearest_beacon() // then go to nearest instead + else // not a valid turf + mode = SECBOT_IDLE return - if(next_destination) - set_destination(next_destination) + else // no path, so calculate new one + mode = SECBOT_START_PATROL + + +// finds a new patrol target +/obj/machinery/bot/ed209/proc/find_patrol_target() + send_status() + if(awaiting_beacon) // awaiting beacon response + awaiting_beacon++ + if(awaiting_beacon > 5) // wait 5 secs for beacon response + find_nearest_beacon() // then go to nearest instead + return + + if(next_destination) + set_destination(next_destination) + else + find_nearest_beacon() + return + + +// finds the nearest beacon to self +// signals all beacons matching the patrol code +/obj/machinery/bot/ed209/proc/find_nearest_beacon() + nearest_beacon = null + new_destination = "__nearest__" + post_signal(beacon_freq, "findbeacon", "patrol") + awaiting_beacon = 1 + spawn(10) + awaiting_beacon = 0 + if(nearest_beacon) + set_destination(nearest_beacon) else - find_nearest_beacon() - return - - - // finds the nearest beacon to self - // signals all beacons matching the patrol code - proc/find_nearest_beacon() - nearest_beacon = null - new_destination = "__nearest__" - post_signal(beacon_freq, "findbeacon", "patrol") - awaiting_beacon = 1 - spawn(10) - awaiting_beacon = 0 - if(nearest_beacon) - set_destination(nearest_beacon) - else - auto_patrol = 0 - mode = SECBOT_IDLE - speak("Disengaging patrol mode.") - send_status() - - - proc/at_patrol_target() - find_patrol_target() - return - - - // sets the current destination - // signals all beacons matching the patrol code - // beacons will return a signal giving their locations - proc/set_destination(var/new_dest) - new_destination = new_dest - post_signal(beacon_freq, "findbeacon", "patrol") - awaiting_beacon = 1 - - - // receive a radio signal - // used for beacon reception - - receive_signal(datum/signal/signal) - - if(!on) - return - - /* - world << "rec signal: [signal.source]" - for(var/x in signal.data) - world << "* [x] = [signal.data[x]]" - */ - - var/recv = signal.data["command"] - // process all-bot input - if(recv=="bot_status") + auto_patrol = 0 + mode = SECBOT_IDLE + speak("Disengaging patrol mode.") send_status() - // check to see if we are the commanded bot - if(signal.data["active"] == src) - // process control input - switch(recv) - if("stop") - mode = SECBOT_IDLE - auto_patrol = 0 - return - if("go") - mode = SECBOT_IDLE - auto_patrol = 1 - return - - if("summon") - patrol_target = signal.data["target"] - next_destination = destination - destination = null - awaiting_beacon = 0 - mode = SECBOT_SUMMON - calc_path() - speak("Responding.") - - return +/obj/machinery/bot/ed209/proc/at_patrol_target() + find_patrol_target() + return +// sets the current destination +// signals all beacons matching the patrol code +// beacons will return a signal giving their locations +/obj/machinery/bot/ed209/proc/set_destination(var/new_dest) + new_destination = new_dest + post_signal(beacon_freq, "findbeacon", "patrol") + awaiting_beacon = 1 - // receive response from beacon - recv = signal.data["beacon"] - var/valid = signal.data["patrol"] - if(!recv || !valid) - return - if(recv == new_destination) // if the recvd beacon location matches the set destination - // the we will navigate there - destination = new_destination - patrol_target = signal.source.loc - next_destination = signal.data["next_patrol"] - awaiting_beacon = 0 +// receive a radio signal +// used for beacon reception - // if looking for nearest beacon - else if(new_destination == "__nearest__") - var/dist = get_dist(src,signal.source.loc) - if(nearest_beacon) +/obj/machinery/bot/ed209/receive_signal(datum/signal/signal) - // note we ignore the beacon we are located at - if(dist>1 && dist 1) - nearest_beacon = recv - nearest_beacon_loc = signal.source.loc + if(!on) return + /* + world << "rec signal: [signal.source]" + for(var/x in signal.data) + world << "* [x] = [signal.data[x]]" + */ - // send a radio signal with a single data key/value pair - proc/post_signal(var/freq, var/key, var/value) - post_signal_multiple(freq, list("[key]" = value) ) + var/recv = signal.data["command"] + // process all-bot input + if(recv=="bot_status") + send_status() - // send a radio signal with multiple data key/values - proc/post_signal_multiple(var/freq, var/list/keyval) + // check to see if we are the commanded bot + if(signal.data["active"] == src) + // process control input + switch(recv) + if("stop") + mode = SECBOT_IDLE + auto_patrol = 0 + return - var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) + if("go") + mode = SECBOT_IDLE + auto_patrol = 1 + return - if(!frequency) return + if("summon") + patrol_target = signal.data["target"] + next_destination = destination + destination = null + awaiting_beacon = 0 + mode = SECBOT_SUMMON + calc_path() + speak("Responding.") - var/datum/signal/signal = new() - signal.source = src - signal.transmission_method = 1 - //for(var/key in keyval) - // signal.data[key] = keyval[key] - //world << "sent [key],[keyval[key]] on [freq]" - signal.data = keyval - if (signal.data["findbeacon"]) - frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS) - else if (signal.data["type"] == "secbot") - frequency.post_signal(src, signal, filter = RADIO_SECBOT) - else - frequency.post_signal(src, signal) + return - // signals bot status etc. to controller - proc/send_status() - var/list/kv = list( - "type" = "secbot", - "name" = name, - "loca" = loc.loc, // area - "mode" = mode, - ) - post_signal_multiple(control_freq, kv) + + + // receive response from beacon + recv = signal.data["beacon"] + var/valid = signal.data["patrol"] + if(!recv || !valid) + return + + if(recv == new_destination) // if the recvd beacon location matches the set destination + // the we will navigate there + destination = new_destination + patrol_target = signal.source.loc + next_destination = signal.data["next_patrol"] + awaiting_beacon = 0 + + // if looking for nearest beacon + else if(new_destination == "__nearest__") + var/dist = get_dist(src,signal.source.loc) + if(nearest_beacon) + + // note we ignore the beacon we are located at + if(dist>1 && dist 1) + nearest_beacon = recv + nearest_beacon_loc = signal.source.loc + return + + +// send a radio signal with a single data key/value pair +/obj/machinery/bot/ed209/proc/post_signal(var/freq, var/key, var/value) + post_signal_multiple(freq, list("[key]" = value) ) + +// send a radio signal with multiple data key/values +/obj/machinery/bot/ed209/proc/post_signal_multiple(var/freq, var/list/keyval) + + var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) + + if(!frequency) return + + var/datum/signal/signal = new() + signal.source = src + signal.transmission_method = 1 + //for(var/key in keyval) + // signal.data[key] = keyval[key] + //world << "sent [key],[keyval[key]] on [freq]" + signal.data = keyval + if (signal.data["findbeacon"]) + frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS) + else if (signal.data["type"] == "secbot") + frequency.post_signal(src, signal, filter = RADIO_SECBOT) + else + frequency.post_signal(src, signal) + +// signals bot status etc. to controller +/obj/machinery/bot/ed209/proc/send_status() + var/list/kv = list( + "type" = "secbot", + "name" = name, + "loca" = loc.loc, // area + "mode" = mode, + ) + post_signal_multiple(control_freq, kv) // calculates a path to the current destination // given an optional turf to avoid - proc/calc_path(var/turf/avoid = null) - src.path = AStar(src.loc, patrol_target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 120, id=botcard, exclude=avoid) - src.path = reverselist(src.path) +/obj/machinery/bot/ed209/proc/calc_path(var/turf/avoid = null) + src.path = AStar(src.loc, patrol_target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 120, id=botcard, exclude=avoid) + src.path = reverselist(src.path) // look for a criminal in view of the bot - proc/look_for_perp() - src.anchored = 0 - src.threatlevel = 0 - for (var/mob/living/carbon/C in view(12,src)) //Let's find us a criminal - if ((C.stat) || (C.handcuffed)) - continue +/obj/machinery/bot/ed209/proc/look_for_perp() + src.anchored = 0 + src.threatlevel = 0 + for (var/mob/living/carbon/C in view(12,src)) //Let's find us a criminal + if ((C.stat) || (C.handcuffed)) + continue - if ((C.name == src.oldtarget_name) && (world.time < src.last_found + 100)) - continue + if ((C.name == src.oldtarget_name) && (world.time < src.last_found + 100)) + continue - if (istype(C, /mob/living/carbon/human)) - src.threatlevel = src.assess_perp(C) - else if ((istype(C, /mob/living/carbon/monkey)) && (C.client) && (ticker.mode.name == "monkey")) - src.threatlevel = 4 + if (istype(C, /mob/living/carbon/human)) + src.threatlevel = src.assess_perp(C) + else if ((istype(C, /mob/living/carbon/monkey)) && (C.client) && (ticker.mode.name == "monkey")) + src.threatlevel = 4 - if (!src.threatlevel) - continue + if (!src.threatlevel) + continue - else if (src.threatlevel >= 4) - src.target = C - src.oldtarget_name = C.name - src.speak("Level [src.threatlevel] infraction alert!") - playsound(src.loc, pick('ed209_20sec.ogg', 'ed209_15sec.ogg'), 50, 0) - src.visible_message("[src] points at [C.name]!") - mode = SECBOT_HUNT - spawn(0) - process() // ensure bot quickly responds to a perp - break - else - continue + else if (src.threatlevel >= 4) + src.target = C + src.oldtarget_name = C.name + src.speak("Level [src.threatlevel] infraction alert!") + playsound(src.loc, pick('ed209_20sec.ogg', 'ed209_15sec.ogg'), 50, 0) + src.visible_message("[src] points at [C.name]!") + mode = SECBOT_HUNT + spawn(0) + process() // ensure bot quickly responds to a perp + break + else + continue //If the security records say to arrest them, arrest them //Or if they have weapons and aren't security, arrest them. - proc/assess_perp(mob/living/carbon/human/perp as mob) - var/threatcount = 0 +/obj/machinery/bot/ed209/proc/assess_perp(mob/living/carbon/human/perp as mob) + var/threatcount = 0 - if(src.emagged) return 10 //Everyone is a criminal! + if(src.emagged) return 10 //Everyone is a criminal! - if((src.idcheck) || (isnull(perp:wear_id)) || (istype(perp:wear_id, /obj/item/weapon/card/id/syndicate))) - if(src.allowed(perp)) //Corrupt cops cannot exist beep boop - return 0 + if((src.idcheck) || (isnull(perp:wear_id)) || (istype(perp:wear_id, /obj/item/weapon/card/id/syndicate))) + if(src.allowed(perp)) //Corrupt cops cannot exist beep boop + return 0 - if((istype(perp.l_hand, /obj/item/weapon/gun) && !istype(perp.l_hand, /obj/item/weapon/gun/shotgun)) || istype(perp.l_hand, /obj/item/weapon/baton)) - threatcount += 4 + if((istype(perp.l_hand, /obj/item/weapon/gun) && !istype(perp.l_hand, /obj/item/weapon/gun/shotgun)) || istype(perp.l_hand, /obj/item/weapon/baton)) + threatcount += 4 - if((istype(perp.r_hand, /obj/item/weapon/gun) && !istype(perp.r_hand, /obj/item/weapon/gun/shotgun)) || istype(perp.r_hand, /obj/item/weapon/baton)) - threatcount += 4 + if((istype(perp.r_hand, /obj/item/weapon/gun) && !istype(perp.r_hand, /obj/item/weapon/gun/shotgun)) || istype(perp.r_hand, /obj/item/weapon/baton)) + threatcount += 4 - if(istype(perp:belt, /obj/item/weapon/gun) || istype(perp:belt, /obj/item/weapon/baton)) - threatcount += 2 + if(istype(perp:belt, /obj/item/weapon/gun) || istype(perp:belt, /obj/item/weapon/baton)) + threatcount += 2 - if(istype(perp:wear_suit, /obj/item/clothing/suit/wizrobe)) - threatcount += 2 + if(istype(perp:wear_suit, /obj/item/clothing/suit/wizrobe)) + threatcount += 2 - if(perp.mutantrace && perp.mutantrace != "none") - threatcount += 2 + if(perp.mutantrace && perp.mutantrace != "none") + threatcount += 2 - //Agent cards lower threatlevel when normal idchecking is off. - if((istype(perp:wear_id, /obj/item/weapon/card/id/syndicate)) && src.idcheck) - threatcount -= 2 +//Agent cards lower threatlevel when normal idchecking is off. + if((istype(perp:wear_id, /obj/item/weapon/card/id/syndicate)) && src.idcheck) + threatcount -= 2 - if (src.check_records) - for (var/datum/data/record/E in data_core.general) - var/perpname = perp.name - if (perp:wear_id) - var/obj/item/weapon/card/id/id = perp:wear_id - if(istype(perp:wear_id, /obj/item/device/pda)) - var/obj/item/device/pda/pda = perp:wear_id - id = pda.id - if (id) - perpname = id.registered - else - var/obj/item/device/pda/pda = perp:wear_id - perpname = pda.owner - if (E.fields["name"] == perpname) - for (var/datum/data/record/R in data_core.security) - if ((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "*Arrest*")) - threatcount = 4 - break + if (src.check_records) + for (var/datum/data/record/E in data_core.general) + var/perpname = perp.name + if (perp:wear_id) + var/obj/item/weapon/card/id/id = perp:wear_id + if(istype(perp:wear_id, /obj/item/device/pda)) + var/obj/item/device/pda/pda = perp:wear_id + id = pda.id + if (id) + perpname = id.registered + else + var/obj/item/device/pda/pda = perp:wear_id + perpname = pda.owner + if (E.fields["name"] == perpname) + for (var/datum/data/record/R in data_core.security) + if ((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "*Arrest*")) + threatcount = 4 + break - return threatcount + return threatcount - Bump(M as mob|obj) //Leave no door unopened! - spawn(0) - if ((istype(M, /obj/machinery/door)) && (!isnull(src.botcard))) - var/obj/machinery/door/D = M - if (!istype(D, /obj/machinery/door/firedoor) && D.check_access(src.botcard)) - D.open() - src.frustration = 0 - else if ((istype(M, /mob/living/)) && (!src.anchored)) - src.loc = M:loc +/obj/machinery/bot/ed209/Bump(M as mob|obj) //Leave no door unopened! + spawn(0) + if ((istype(M, /obj/machinery/door)) && (!isnull(src.botcard))) + var/obj/machinery/door/D = M + if (!istype(D, /obj/machinery/door/firedoor) && D.check_access(src.botcard)) + D.open() src.frustration = 0 + else if ((istype(M, /mob/living/)) && (!src.anchored)) + src.loc = M:loc + src.frustration = 0 - return + return + return + +/obj/machinery/bot/ed209/Bumped(M as mob|obj) + spawn(0) + var/turf/T = get_turf(src) + M:loc = T + +/obj/machinery/bot/ed209/proc/speak(var/message) + for(var/mob/O in hearers(src, null)) + O << "[src] beeps, \"[message]\"" + return + +/obj/machinery/bot/ed209/explode() + walk_to(src,0) + src.visible_message("\red [src] blows apart!", 1) + var/turf/Tsec = get_turf(src) + + var/obj/item/weapon/ed209_assembly/Sa = new /obj/item/weapon/ed209_assembly(Tsec) + Sa.build_step = 1 + Sa.overlays += image('aibots.dmi', "hs_hole") + Sa.created_name = src.name + new /obj/item/device/prox_sensor(Tsec) + + var/obj/item/weapon/baton/B = new /obj/item/weapon/baton(Tsec) + B.charges = 0 + + if (prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + var/datum/effects/system/spark_spread/s = new /datum/effects/system/spark_spread + s.set_up(3, 1, src) + s.start() + + new /obj/decal/cleanable/oil(src.loc) + del(src) + + +/obj/machinery/bot/ed209/proc/shootAt(var/mob/target) + if(lastfired && world.time - lastfired < shot_delay) + return + lastfired = world.time + var/turf/T = loc + var/atom/U = (istype(target, /atom/movable) ? target.loc : target) + if ((!( U ) || !( T ))) + return + while(!( istype(U, /turf) )) + U = U.loc + if (!( istype(T, /turf) )) return - Bumped(M as mob|obj) - spawn(0) - var/turf/T = get_turf(src) - M:loc = T + //if(lastfired && world.time - lastfired < 100) + // playsound(src.loc, 'ed209_shoot.ogg', 50, 0) - bullet_act(flag, A as obj) - switch(flag) - if (PROJECTILE_BULLET) - src.health -= 20 - //if (PROJECTILE_WEAKBULLET || PROJECTILE_BEANBAG) //Detective's revolver fires marshmallows - // src.health -= 2 - if (PROJECTILE_LASER) - src.health -= 10 - if (PROJECTILE_PULSE) - src.health -=40 - if (src.health <= 0) - src.explode() + var/obj/beam/a_laser/A + if (src.emagged) + A = new /obj/beam/a_laser( loc ) + else + A = new /obj/bullet/electrode( loc ) - proc/speak(var/message) - for(var/mob/O in hearers(src, null)) - O << "[src] beeps, \"[message]\"" + if (!( istype(U, /turf) )) + //A = null + del(A) return - - //Generally we want to explode() instead of just deleting the securitron. - ex_act(severity) - switch(severity) - if(1.0) - src.explode() - return - if(2.0) - src.health -= 15 - if (src.health <= 0) - src.explode() - return + A.current = U + A.yo = U.y - T.y + A.xo = U.x - T.x + spawn( 0 ) + A.process() return + return - meteorhit() - src.explode() - return +/obj/machinery/bot/ed209/attack_alien(var/mob/living/carbon/alien/user as mob) + ..() + if (!isalien(target)) + src.target = user + src.mode = SECBOT_HUNT - blob_act() - if(prob(50)) - src.explode() - return - - proc/explode() - - walk_to(src,0) - for(var/mob/O in hearers(src, null)) - O.show_message("\red [src] blows apart!", 1) - var/turf/Tsec = get_turf(src) - - var/obj/item/weapon/ed209_assembly/Sa = new /obj/item/weapon/ed209_assembly(Tsec) - Sa.build_step = 1 - Sa.overlays += image('aibots.dmi', "hs_hole") - Sa.created_name = src.name - new /obj/item/device/prox_sensor(Tsec) - - var/obj/item/weapon/baton/B = new /obj/item/weapon/baton(Tsec) - B.charges = 0 +/obj/machinery/bot/ed209/emp_act(severity) + if (severity > 2) + ..(severity-1) + else if (severity==2 && prob(70)) + ..(severity-1) + else + var/obj/overlay/pulse2 = new/obj/overlay ( src.loc ) + pulse2.icon = 'effects.dmi' + pulse2.icon_state = "empdisable" + pulse2.name = "emp sparks" + pulse2.anchored = 1 + pulse2.dir = pick(cardinal) + spawn(10) + del(pulse2) + var/list/mob/living/carbon/targets = new + for (var/mob/living/carbon/C in view(12,src)) + if (C.stat==2) + continue + targets += C if (prob(50)) - new /obj/item/robot_parts/l_arm(Tsec) - - var/datum/effects/system/spark_spread/s = new /datum/effects/system/spark_spread - s.set_up(3, 1, src) - s.start() - del(src) - - - - - + var/mob/toshoot = pick(targets) + if (toshoot) + targets-=toshoot + if (prob(50) && !emagged) + emagged = 1 + shootAt(toshoot) + emagged = 0 + else + shootAt(toshoot) + if (/*prob(50)*/ 1 ) + var/mob/toarrest = pick(targets) + if (toarrest) + //targets-=toarrest + src.target = toarrest + src.mode = SECBOT_HUNT /obj/item/weapon/ed209_assembly/attackby(obj/item/weapon/W as obj, mob/user as mob) - - if (((istype(W, /obj/item/robot_parts/l_leg)) || (istype(W, /obj/item/robot_parts/r_leg))) && (src.build_step == 0 || src.build_step == 1)) src.build_step++ user << "You add the robot leg to [src]!" @@ -845,36 +872,3 @@ Auto Patrol: []"}, src.created_name = t -/obj/machinery/bot/ed209/proc/shootAt(var/mob/target) - if(lastfired && world.time - lastfired < shot_delay) - return - lastfired = world.time - var/turf/T = loc - var/atom/U = (istype(target, /atom/movable) ? target.loc : target) - if ((!( U ) || !( T ))) - return - while(!( istype(U, /turf) )) - U = U.loc - if (!( istype(T, /turf) )) - return - - //if(lastfired && world.time - lastfired < 100) - // playsound(src.loc, 'ed209_shoot.ogg', 50, 0) - - var/obj/beam/a_laser/A - if (src.emagged) - A = new /obj/beam/a_laser( loc ) - else - A = new /obj/bullet/electrode( loc ) - - if (!( istype(U, /turf) )) - //A = null - del(A) - return - A.current = U - A.yo = U.y - T.y - A.xo = U.x - T.x - spawn( 0 ) - A.process() - return - return \ No newline at end of file diff --git a/code/game/machinery/bots/floorbot.dm b/code/game/machinery/bots/floorbot.dm index ae279596d10..ea1711ca088 100644 --- a/code/game/machinery/bots/floorbot.dm +++ b/code/game/machinery/bots/floorbot.dm @@ -34,9 +34,10 @@ layer = 5.0 density = 0 anchored = 0 + health = 25 + maxhealth = 25 //weight = 1.0E7 var/amount = 10 - var/on = 1 var/repairing = 0 var/improvefloors = 0 var/eattiles = 0 @@ -54,7 +55,28 @@ ..() src.updateicon() -/obj/machinery/bot/floorbot/attack_hand(user as mob) +/obj/machinery/bot/floorbot/turn_on() + . = ..() + src.updateicon() + src.updateUsrDialog() + +/obj/machinery/bot/floorbot/turn_off() + ..() + src.target = null + src.oldtarget = null + src.oldloc = null + src.updateicon() + src.path = new() + src.updateUsrDialog() + +/obj/machinery/bot/floorbot/attack_hand(mob/user as mob) + . = ..() + if (.) + return + usr.machine = src + interact(user) + +/obj/machinery/bot/floorbot/proc/interact(mob/user as mob) var/dat dat += "Automatic Station Floor Repairer v1.0

" dat += "Status: [src.on ? "On" : "Off"]
" @@ -86,13 +108,15 @@ src.amount += loaded user << "\red You load [loaded] tiles into the floorbot. He now contains [src.amount] tiles!" src.updateicon() - if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) + else if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) if(src.allowed(usr)) src.locked = !src.locked user << "You [src.locked ? "lock" : "unlock"] the [src] behaviour controls." else user << "The [src] doesn't seem to accept your authority." src.updateUsrDialog() + else + ..() /obj/machinery/bot/floorbot/Topic(href, href_list) @@ -102,13 +126,10 @@ src.add_fingerprint(usr) switch(href_list["operation"]) if("start") - src.on = !src.on - src.target = null - src.oldtarget = null - src.oldloc = null - src.updateicon() - src.path = new() - src.updateUsrDialog() + if (src.on) + turn_off() + else + turn_on() if("improve") src.improvefloors = !src.improvefloors src.updateUsrDialog() @@ -136,12 +157,10 @@ /obj/machinery/bot/floorbot/attack_ai() - src.on = !src.on - src.target = null - src.oldtarget = null - src.oldloc = null - src.updateicon() - src.path = new() + if (src.on) + turn_off() + else + turn_on() /obj/machinery/bot/floorbot/process() set background = 1 @@ -327,6 +346,26 @@ else src.icon_state = "floorbot[src.on]e" +/obj/machinery/bot/floorbot/explode() + src.on = 0 + src.visible_message("\red [src] blows apart!", 1) + var/turf/Tsec = get_turf(src) + + new /obj/item/weapon/storage/toolbox/mechanical(Tsec) + + new /obj/item/device/prox_sensor(Tsec) + + if (prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + if (amount) + new /obj/item/stack/tile(Tsec) // only one tile, yes + + var/datum/effects/system/spark_spread/s = new /datum/effects/system/spark_spread + s.set_up(3, 1, src) + s.start() + del(src) + return /obj/item/weapon/storage/toolbox/mechanical/attackby(var/obj/item/stack/tile/T, mob/user as mob) diff --git a/code/game/machinery/bots/medbot.dm b/code/game/machinery/bots/medbot.dm index 07b38a56ed1..3ff800b2d33 100644 --- a/code/game/machinery/bots/medbot.dm +++ b/code/game/machinery/bots/medbot.dm @@ -11,9 +11,9 @@ layer = 5.0 density = 1 anchored = 0 + health = 20 + maxhealth = 20 req_access =list(access_medical) - var/on = 1 - var/health = 20 var/stunned = 0 //It can be stunned by tasers. Delicate circuits. var/locked = 1 var/emagged = 0 @@ -79,16 +79,22 @@ src.cam.c_tag = src.name src.cam.network = "SS13" -/obj/machinery/bot/medbot/examine() - set src in view() - ..() +/obj/machinery/bot/medbot/turn_on() + . = ..() + src.icon_state = "medibot[src.on]" + src.updateUsrDialog() + +/obj/machinery/bot/medbot/turn_off() + ..() + src.patient = null + src.oldpatient = null + src.oldloc = null + src.path = new() + src.currently_healing = 0 + src.last_found = world.time + src.icon_state = "medibot[src.on]" + src.updateUsrDialog() - if (src.health < 20) - if (src.health > 15) - usr << text("\red [src]'s parts look loose.") - else - usr << text("\red [src]'s parts look very loose!") - return /obj/machinery/bot/medbot/attack_ai(mob/user as mob) return toggle_power() @@ -97,6 +103,9 @@ return attack_hand(user) /obj/machinery/bot/medbot/attack_hand(mob/user as mob) + . = ..() + if (.) + return var/dat dat += "Automatic Medical Unit v1.0

" dat += "Status: [src.on ? "On" : "Off"]
" @@ -190,12 +199,6 @@ else user << "\red Access denied." - else if (istype(W, /obj/item/weapon/screwdriver)) - if (src.health < initial(src.health)) - src.health = initial(src.health) - for(var/mob/O in viewers(src, null)) - O << "\red [user] repairs [src]!" - else if (istype(W, /obj/item/weapon/reagent_containers/glass)) if(src.locked) user << "You cannot insert a beaker because the panel is locked!" @@ -212,17 +215,9 @@ return else - switch(W.damtype) - if("fire") - src.health -= W.force * 0.75 - if("brute") - src.health -= W.force * 0.5 - else - if (src.health <= 0) - src.explode() - else if (W.force) - step_to(src, (get_step_away(src,user))) ..() + if (health < maxhealth && !istype(W, /obj/item/weapon/screwdriver) && W.force) + step_to(src, (get_step_away(src,user))) /obj/machinery/bot/medbot/process() @@ -317,15 +312,10 @@ /obj/machinery/bot/medbot/proc/toggle_power() - src.on = !src.on - src.patient = null - src.oldpatient = null - src.oldloc = null - src.path = new() - src.currently_healing = 0 - src.last_found = world.time - src.icon_state = "medibot[src.on]" - src.updateUsrDialog() + if (src.on) + turn_off() + else + turn_on() return /obj/machinery/bot/medbot/proc/assess_patient(mob/living/carbon/C as mob) @@ -453,43 +443,11 @@ return /obj/machinery/bot/medbot/bullet_act(flag, A as obj) - if (flag == PROJECTILE_BULLET) - src.health -= 18 + if (flag == PROJECTILE_TASER) + src.stunned = min(stunned+10,20) + ..() - else if (flag == PROJECTILE_TASER) - src.stunned += 10 - if(src.stunned > 20) - src.stunned = 20 - - else if (flag == PROJECTILE_LASER) - src.health -= 8 - - - if (src.health <= 0) - src.explode() - -/obj/machinery/bot/medbot/ex_act(severity) - switch(severity) - if(1.0) - src.explode() - return - if(2.0) - src.health -= 15 - if (src.health <= 0) - src.explode() - return - return - -/obj/machinery/bot/medbot/meteorhit() - src.explode() - return - -/obj/machinery/bot/medbot/blob_act() - if(prob(50)) - src.explode() - return - -/obj/machinery/bot/medbot/proc/explode() +/obj/machinery/bot/medbot/explode() src.on = 0 for(var/mob/O in hearers(src, null)) O.show_message("\red [src] blows apart!", 1) diff --git a/code/game/machinery/bots/mulebot.dm b/code/game/machinery/bots/mulebot.dm index 890cac9d833..5461bd1bf10 100644 --- a/code/game/machinery/bots/mulebot.dm +++ b/code/game/machinery/bots/mulebot.dm @@ -11,7 +11,10 @@ density = 1 anchored = 1 animate_movement=1 - var/on = 1 + health = 150 //yeah, it's tougher than ed209 because it is a big metal box with wheels --rastaf0 + maxhealth = 150 + fire_dam_coeff = 0.7 + brute_dam_coeff = 0.5 var/locked = 1 var/atom/movable/load = null // the loaded crate (usually) @@ -69,853 +72,895 @@ var/bloodiness = 0 // count of bloodiness - New() - ..() - botcard = new(src) - botcard.access = get_access("Quartermaster") - cell = new(src) - cell.charge = 2000 - cell.maxcharge = 2000 - setup_wires() +/obj/machinery/bot/mulebot/New() + ..() + botcard = new(src) + botcard.access = get_access("Quartermaster") + cell = new(src) + cell.charge = 2000 + cell.maxcharge = 2000 + setup_wires() - spawn(5) // must wait for map loading to finish - if(radio_controller) - radio_controller.add_object(src, control_freq, filter = RADIO_MULEBOT) - radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS) + spawn(5) // must wait for map loading to finish + if(radio_controller) + radio_controller.add_object(src, control_freq, filter = RADIO_MULEBOT) + radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS) - var/count = 0 - for(var/obj/machinery/bot/mulebot/other in world) - count++ - if(!suffix) - suffix = "#[count]" - name = "Mulebot ([suffix])" + var/count = 0 + for(var/obj/machinery/bot/mulebot/other in world) + count++ + if(!suffix) + suffix = "#[count]" + name = "Mulebot ([suffix])" - verbs -= /atom/movable/verb/pull + verbs -= /atom/movable/verb/pull - // set up the wire colours in random order - // and the random wire display order - // needs 10 wire colours - proc/setup_wires() - var/list/colours = list("Red", "Green", "Blue", "Magenta", "Cyan", "Yellow", "Black", "White", "Orange", "Grey") - var/list/orders = list("0","1","2","3","4","5","6","7","8","9") - wire_text = list() - wire_order = list() - while(colours.len > 0) - var/colour = colours[ rand(1,colours.len) ] - wire_text += colour - colours -= colour +// set up the wire colours in random order +// and the random wire display order +// needs 10 wire colours +/obj/machinery/bot/mulebot/proc/setup_wires() + var/list/colours = list("Red", "Green", "Blue", "Magenta", "Cyan", "Yellow", "Black", "White", "Orange", "Grey") + var/list/orders = list("0","1","2","3","4","5","6","7","8","9") + wire_text = list() + wire_order = list() + while(colours.len > 0) + var/colour = colours[ rand(1,colours.len) ] + wire_text += colour + colours -= colour - var/order = orders[ rand(1,orders.len) ] - wire_order += text2num(order) - orders -= order + var/order = orders[ rand(1,orders.len) ] + wire_order += text2num(order) + orders -= order - // attack by item - // emag : lock/unlock, - // screwdriver: open/close hatch - // cell: insert it - // other: chance to knock rider off bot - attackby(var/obj/item/I, var/mob/user) - if(istype(I,/obj/item/weapon/card/emag)) - locked = !locked - user << "\blue You [locked ? "lock" : "unlock"] the mulebot's controls!" - flick("mulebot-emagged", src) - playsound(src.loc, 'sparks1.ogg', 100, 0) - else if(istype(I,/obj/item/weapon/cell) && open && !cell) - var/obj/item/weapon/cell/C = I - user.drop_item() - C.loc = src - cell = C - updateDialog() - else if(istype(I,/obj/item/weapon/screwdriver)) - if(locked) - user << "\blue The maintenance hatch cannot be opened or closed while the controls are locked." - return +// attack by item +// emag : lock/unlock, +// screwdriver: open/close hatch +// cell: insert it +// other: chance to knock rider off bot +/obj/machinery/bot/mulebot/attackby(var/obj/item/I, var/mob/user) + if(istype(I,/obj/item/weapon/card/emag)) + locked = !locked + user << "\blue You [locked ? "lock" : "unlock"] the mulebot's controls!" + flick("mulebot-emagged", src) + playsound(src.loc, 'sparks1.ogg', 100, 0) + else if(istype(I,/obj/item/weapon/cell) && open && !cell) + var/obj/item/weapon/cell/C = I + user.drop_item() + C.loc = src + cell = C + updateDialog() + else if(istype(I,/obj/item/weapon/screwdriver)) + if(locked) + user << "\blue The maintenance hatch cannot be opened or closed while the controls are locked." + return - open = !open - if(open) - src.visible_message("[user] opens the maintenance hatch of [src]", "\blue You open [src]'s maintenance hatch.") - on = 0 - icon_state="mulebot-hatch" - else - src.visible_message("[user] closes the maintenance hatch of [src]", "\blue You close [src]'s maintenance hatch.") - icon_state = "mulebot0" - - updateDialog() - else if(load && ismob(load)) // chance to knock off rider - if(prob(1+I.force * 2)) - unload(0) - user.visible_message("\red [user] knocks [load] off [src] with \the [I]!", "\red You knock [load] off [src] with \the [I]!") - else - user << "You hit [src] with \the [I] but to no effect." + open = !open + if(open) + src.visible_message("[user] opens the maintenance hatch of [src]", "\blue You open [src]'s maintenance hatch.") + on = 0 + icon_state="mulebot-hatch" else - ..() - return + src.visible_message("[user] closes the maintenance hatch of [src]", "\blue You close [src]'s maintenance hatch.") + icon_state = "mulebot0" - - ex_act(var/severity) - unload(0) - switch(severity) - if(1) - del(src) - if(2) - wires &= ~(1 << rand(0,9)) - wires &= ~(1 << rand(0,9)) - wires &= ~(1 << rand(0,9)) - if(3) - wires &= ~(1 << rand(0,9)) - - return - - bullet_act() - if(prob(50) && !isnull(load)) - load.bullet_act() + updateDialog() + else if (istype(I, /obj/item/weapon/wrench)) + if (src.health < maxhealth) + src.health = min(maxhealth, src.health+25) + user.visible_message( + "\red [user] repairs [src]!", + "\blue You repair [src]!" + ) + else + user << "\blue [src] does not need a repair!" + else if(load && ismob(load)) // chance to knock off rider + if(prob(1+I.force * 2)) unload(0) - if(prob(25)) - src.visible_message("Something shorts out inside [src]!") - var/index = 1<< (rand(0,9)) - if(wires & index) - wires &= ~index - else - wires |= index - - - attack_ai(var/mob/user) - interact(user, 1) - - attack_hand(var/mob/user) - interact(user, 0) - - proc/interact(var/mob/user, var/ai=0) - var/dat - dat += "Multiple Utility Load Effector Mk. III

" - dat += "ID: [suffix]
" - dat += "Power: [on ? "On" : "Off"]
" - - if(!open) - - dat += "Status: " - switch(mode) - if(0) - dat += "Ready" - if(1) - dat += "Loading/Unloading" - if(2) - dat += "Navigating to Delivery Location" - if(3) - dat += "Navigating to Home" - if(4) - dat += "Waiting for clear path" - if(5,6) - dat += "Calculating navigation path" - if(7) - dat += "Unable to locate destination" - - - dat += "
Current Load: [load ? load.name : "none"]
" - dat += "Destination: [!destination ? "none" : destination]
" - dat += "Power level: [cell ? cell.percent() : 0]%
" - - if(locked && !ai) - dat += "
Controls are locked (unlock)" - else - dat += "
Controls are unlocked (lock)

" - - dat += "Toggle Power
" - dat += "Stop
" - dat += "Proceed
" - dat += "Return to Home
" - dat += "Set Destination
" - dat += "Set Bot ID
" - dat += "Set Home
" - dat += "Toggle Auto Return Home ([auto_return ? "On":"Off"])
" - dat += "Toggle Auto Pickup Crate ([auto_pickup ? "On":"Off"])
" - - if(load) - dat += "Unload Now
" - dat += "
The maintenance hatch is closed.
" - + user.visible_message("\red [user] knocks [load] off [src] with \the [I]!", "\red You knock [load] off [src] with \the [I]!") else - if(!ai) - dat += "The maintenance hatch is open.

" - dat += "Power cell: " - if(cell) - dat += "Installed
" - else - dat += "Removed
" + user << "You hit [src] with \the [I] but to no effect." + else + ..() + return - dat += wires() - else - dat += "The bot is in maintenance mode and cannot be controlled.
" - user << browse("Mulebot [suffix ? "([suffix])" : ""][dat]", "window=mulebot;size=350x500") - onclose(user, "mulebot") +/obj/machinery/bot/mulebot/ex_act(var/severity) + unload(0) + switch(severity) + if(2) + wires &= ~(1 << rand(0,9)) + wires &= ~(1 << rand(0,9)) + wires &= ~(1 << rand(0,9)) + if(3) + wires &= ~(1 << rand(0,9)) + ..() + return + +/obj/machinery/bot/mulebot/bullet_act() + if(prob(50) && !isnull(load)) + load.bullet_act() + unload(0) + if(prob(25)) + src.visible_message("\red Something shorts out inside [src]!") + var/index = 1<< (rand(0,9)) + if(wires & index) + wires &= ~index + else + wires |= index + ..() + + +/obj/machinery/bot/mulebot/attack_ai(var/mob/user) + usr.machine = src + interact(user, 1) + +/obj/machinery/bot/mulebot/attack_hand(var/mob/user) + . = ..() + if (.) return + usr.machine = src + interact(user, 0) - // returns the wire panel text - proc/wires() - var/t = "" - for(var/i = 0 to 9) - var/index = 1<" + + if(!open) + + dat += "Status: " + switch(mode) + if(0) + dat += "Ready" + if(1) + dat += "Loading/Unloading" + if(2) + dat += "Navigating to Delivery Location" + if(3) + dat += "Navigating to Home" + if(4) + dat += "Waiting for clear path" + if(5,6) + dat += "Calculating navigation path" + if(7) + dat += "Unable to locate destination" + + + dat += "
Current Load: [load ? load.name : "none"]
" + dat += "Destination: [!destination ? "none" : destination]
" + dat += "Power level: [cell ? cell.percent() : 0]%
" + + if(locked && !ai) + dat += "
Controls are locked (unlock)" + else + dat += "
Controls are unlocked (lock)

" + + dat += "Toggle Power
" + dat += "Stop
" + dat += "Proceed
" + dat += "Return to Home
" + dat += "Set Destination
" + dat += "Set Bot ID
" + dat += "Set Home
" + dat += "Toggle Auto Return Home ([auto_return ? "On":"Off"])
" + dat += "Toggle Auto Pickup Crate ([auto_pickup ? "On":"Off"])
" + + if(load) + dat += "Unload Now
" + dat += "
The maintenance hatch is closed.
" + + else + if(!ai) + dat += "The maintenance hatch is open.

" + dat += "Power cell: " + if(cell) + dat += "Installed
" else - t += "(mend)
" + dat += "Removed
" - return t + dat += wires() + else + dat += "The bot is in maintenance mode and cannot be controlled.
" + + user << browse("Mulebot [suffix ? "([suffix])" : ""][dat]", "window=mulebot;size=350x500") + onclose(user, "mulebot") + return + +// returns the wire panel text +/obj/machinery/bot/mulebot/proc/wires() + var/t = "" + for(var/i = 0 to 9) + var/index = 1<=2) - mode = 0 - updateDialog() - - if("go") - if(mode == 0) - start() - updateDialog() - - if("home") - if(mode == 0 || mode == 2) - start_home() - updateDialog() - - if("destination") - refresh=0 - var/new_dest = input("Enter new destination tag", "Mulebot [suffix ? "([suffix])" : ""]", destination) as text|null - refresh=1 - if(new_dest) - set_destination(new_dest) - - - if("setid") - refresh=0 - var/new_id = input("Enter new bot ID", "Mulebot [suffix ? "([suffix])" : ""]", suffix) as text|null - refresh=1 - if(new_id) - suffix = new_id - name = "Mulebot ([suffix])" - updateDialog() - - if("sethome") - refresh=0 - var/new_home = input("Enter new home tag", "Mulebot [suffix ? "([suffix])" : ""]", home_destination) as text|null - refresh=1 - if(new_home) - home_destination = new_home - updateDialog() - - if("unload") - if(load && mode !=1) - if(loc == target) - unload(loaddir) - else - unload(0) - - if("autoret") - auto_return = !auto_return - - if("autopick") - auto_pickup = !auto_pickup - - if("close") - usr.machine = null - usr << browse(null,"window=mulebot") - - - if("wirecut") - if(istype(usr.equipped(), /obj/item/weapon/wirecutters)) - var/wirebit = text2num(href_list["wire"]) - wires &= ~wirebit - else - usr << "\blue You need wirecutters!" - if("wiremend") - if(istype(usr.equipped(), /obj/item/weapon/wirecutters)) - var/wirebit = text2num(href_list["wire"]) - wires |= wirebit - else - usr << "\blue You need wirecutters!" - - if("wirepulse") - if(istype(usr.equipped(), /obj/item/device/multitool)) - switch(href_list["wire"]) - if("1","2") - usr << "\blue \icon[src] The charge light flickers." - if("4") - usr << "\blue \icon[src] The external warning lights flash briefly." - if("8") - usr << "\blue \icon[src] The load platform clunks." - if("16", "32") - usr << "\blue \icon[src] The drive motor whines briefly." - else - usr << "\blue \icon[src] You hear a radio crackle." - else - usr << "\blue You need a multitool!" - - - - updateDialog() - //src.updateUsrDialog() - else - usr << browse(null, "window=mulebot") - usr.machine = null - return - - - - // returns true if the bot has power - proc/has_power() - return !open && cell && cell.charge>0 && (wires & wire_power1) && (wires & wire_power2) - - // mousedrop a crate to load the bot - // can load anything if emagged - - MouseDrop_T(var/atom/movable/C, mob/user) - - if(user.stat) - return - - if (!on || !istype(C)|| C.anchored || get_dist(user, src) > 1 || get_dist(src,C) > 1 ) - return - - if(load) - return - - load(C) - - - // called to load a crate - proc/load(var/atom/movable/C) - if((wires & wire_loadcheck) && !istype(C,/obj/crate)) - src.visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") - playsound(src.loc, 'buzz-sigh.ogg', 50, 0) - return // if not emagged, only allow crates to be loaded - - if(get_dist(C, src) > 1 || load || !on) - return - mode = 1 - - // if a create, close before loading - var/obj/crate/crate = C - if(istype(crate)) - crate.close() - - C.loc = src.loc - sleep(2) - C.loc = src - load = C - - C.pixel_y += 9 - if(C.layer < layer) - C.layer = layer + 0.1 - overlays += C - - if(ismob(C)) - var/mob/M = C - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src - - mode = 0 - send_status() - - // called to unload the bot - // argument is optional direction to unload - // if zero, unload at bot's location - proc/unload(var/dirn = 0) - if(!load) - return - - mode = 1 - overlays = null - - load.loc = src.loc - load.pixel_y -= 9 - load.layer = initial(load.layer) - if(ismob(load)) - var/mob/M = load - if(M.client) - M.client.perspective = MOB_PERSPECTIVE - M.client.eye = src - - - if(dirn) - step(load, dirn) - - load = null - - // in case non-load items end up in contents, dump every else too - // this seems to happen sometimes due to race conditions - // with items dropping as mobs are loaded - - for(var/atom/movable/AM in src) - if(AM == cell || AM == botcard) continue - - AM.loc = src.loc - AM.layer = initial(AM.layer) - AM.pixel_y = initial(AM.pixel_y) - if(ismob(AM)) - var/mob/M = AM - if(M.client) - M.client.perspective = MOB_PERSPECTIVE - M.client.eye = src - mode = 0 - - - process() - if(!has_power()) - on = 0 - return - if(on) - var/speed = ((wires & wire_motor1) ? 1:0) + ((wires & wire_motor2) ? 2:0) - //world << "speed: [speed]" - switch(speed) - if(0) - // do nothing - if(1) - process_bot() - spawn(3) - process_bot() - sleep(3) - process_bot() - if(2) - process_bot() - spawn(5) - process_bot() - if(3) - process_bot() - - if(refresh) updateDialog() - - proc/process_bot() - //if(mode) world << "Mode: [mode]" - switch(mode) - if(0) // idle - icon_state = "mulebot0" - return - if(1) // loading/unloading - return - if(2,3,4) // navigating to deliver,home, or blocked - - if(loc == target) // reached target - at_target() - return - - else if(path.len > 0 && target) // valid path - - var/turf/next = path[1] - reached_target = 0 - if(next == loc) - path -= next - return - - - if(istype( next, /turf/simulated)) - //world << "at ([x],[y]) moving to ([next.x],[next.y])" - - - if(bloodiness) - var/obj/decal/cleanable/blood/tracks/B = new(loc) - var/newdir = get_dir(next, loc) - if(newdir == dir) - B.dir = newdir - else - newdir = newdir | dir - if(newdir == 3) - newdir = 1 - else if(newdir == 12) - newdir = 4 - B.dir = newdir - bloodiness-- - - - - var/moved = step_towards(src, next) // attempt to move - if(cell) cell.use(1) - if(moved) // successful move - //world << "Successful move." - blockcount = 0 - path -= loc - - - if(mode==4) - spawn(1) - send_status() - - if(destination == home_destination) - mode = 3 - else - mode = 2 - - else // failed to move - - //world << "Unable to move." - - - - blockcount++ - mode = 4 - if(blockcount == 3) - src.visible_message("[src] makes an annoyed buzzing sound", "You hear an electronic buzzing sound.") - playsound(src.loc, 'buzz-two.ogg', 50, 0) - - if(blockcount > 5) // attempt 5 times before recomputing - // find new path excluding blocked turf - src.visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") - playsound(src.loc, 'buzz-sigh.ogg', 50, 0) - - spawn(2) - calc_path(next) - if(path.len > 0) - src.visible_message("[src] makes a delighted ping!", "You hear a ping.") - playsound(src.loc, 'ping.ogg', 50, 0) - mode = 4 - mode =6 - return - return - else - src.visible_message("[src] makes an annoyed buzzing sound", "You hear an electronic buzzing sound.") - playsound(src.loc, 'buzz-two.ogg', 50, 0) - //world << "Bad turf." - mode = 5 - return - else - //world << "No path." - mode = 5 - return - - if(5) // calculate new path - //world << "Calc new path." - mode = 6 - spawn(0) - - calc_path() - - if(path.len > 0) - blockcount = 0 - mode = 4 - src.visible_message("[src] makes a delighted ping!", "You hear a ping.") - playsound(src.loc, 'ping.ogg', 50, 0) - - else - src.visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") - playsound(src.loc, 'buzz-sigh.ogg', 50, 0) - - mode = 7 - //if(6) - //world << "Pending path calc." - //if(7) - //world << "No dest / no route." - return - - - // calculates a path to the current destination - // given an optional turf to avoid - proc/calc_path(var/turf/avoid = null) - src.path = AStar(src.loc, src.target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 250, id=botcard, exclude=avoid) - src.path = reverselist(src.path) - - - // sets the current destination - // signals all beacons matching the delivery code - // beacons will return a signal giving their locations - proc/set_destination(var/new_dest) - spawn(0) - new_destination = new_dest - post_signal(beacon_freq, "findbeacon", "delivery") - updateDialog() - - // starts bot moving to current destination - proc/start() - if(destination == home_destination) - mode = 3 - else - mode = 2 - icon_state = "mulebot[(wires & wire_mobavoid) == wire_mobavoid]" - - // starts bot moving to home - // sends a beacon query to find - proc/start_home() - spawn(0) - set_destination(home_destination) - mode = 4 - icon_state = "mulebot[(wires & wire_mobavoid) == wire_mobavoid]" - - // called when bot reaches current target - proc/at_target() - if(!reached_target) - src.visible_message("[src] makes a chiming sound!", "You hear a chime.") - playsound(src.loc, 'chime.ogg', 50, 0) - reached_target = 1 - - if(load) // if loaded, unload at target - unload(loaddir) - else - // not loaded - if(auto_pickup) // find a crate - var/atom/movable/AM - if(!(wires & wire_loadcheck)) // if emagged, load first unanchored thing we find - for(var/atom/movable/A in get_step(loc, loaddir)) - if(!A.anchored) - AM = A - break - else // otherwise, look for crates only - AM = locate(/obj/crate) in get_step(loc,loaddir) - if(AM) - load(AM) - // whatever happened, check to see if we return home - - if(auto_return && destination != home_destination) - // auto return set and not at home already - start_home() - mode = 4 - else - mode = 0 // otherwise go idle - - send_status() // report status to anyone listening - - return - - // called when bot bumps into anything - Bump(var/atom/obs) - if(!(wires & wire_mobavoid)) //usually just bumps, but if avoidance disabled knock over mobs - var/mob/M = obs - if(ismob(M)) - if(istype(M,/mob/living/silicon/robot)) - src.visible_message("\red [src] bumps into [M]!") - else - src.visible_message("\red [src] knocks over [M]!") - M.pulling = null - M.stunned = 8 - M.weakened = 5 - M.lying = 1 - ..() - - alter_health() - return get_turf(src) - - - // called from mob/living/carbon/human/HasEntered() - // when mulebot is in the same loc - proc/RunOver(var/mob/living/carbon/human/H) - src.visible_message("\red [src] drives over [H]!") - playsound(src.loc, 'splat.ogg', 50, 1) - - /* - if(ismob(load)) - var/mob/M = load - if(M.reagents.has_reagent("beer")) - M.unlock_medal("DUI", 1) - if(M.reagents.has_reagent("space_drugs") && istype(H) && H.wear_id.assignment == "Security Officer") - M.unlock_medel("Ridin' Dirty",1) - */ - - var/damage = rand(5,15) - - H.TakeDamage("head", 2*damage, 0) - H.TakeDamage("chest",2*damage, 0) - H.TakeDamage("l_leg",0.5*damage, 0) - H.TakeDamage("r_leg",0.5*damage, 0) - H.TakeDamage("l_arm",0.5*damage, 0) - H.TakeDamage("r_arm",0.5*damage, 0) - - var/obj/decal/cleanable/blood/B = new(src.loc) - B.blood_DNA = H.dna.unique_enzymes - B.blood_type = H.b_type - - bloodiness += 4 - - // player on mulebot attempted to move - relaymove(var/mob/user) - if(user.stat) - return - if(load == user) - unload(0) - return - - // receive a radio signal - // used for control and beacon reception - - receive_signal(datum/signal/signal) - - if(!on) - return - - /* - world << "rec signal: [signal.source]" - for(var/x in signal.data) - world << "* [x] = [signal.data[x]]" - */ - var/recv = signal.data["command"] - // process all-bot input - if(recv=="bot_status" && (wires & wire_remote_rx)) - send_status() - - - recv = signal.data["command [suffix]"] - if(wires & wire_remote_rx) - // process control input - switch(recv) - if("stop") + if("stop") + if(mode >=2) mode = 0 - return + updateDialog() - if("go") + if("go") + if(mode == 0) start() - return + updateDialog() - if("target") - set_destination(signal.data["destination"] ) - return + if("home") + if(mode == 0 || mode == 2) + start_home() + updateDialog() - if("unload") + if("destination") + refresh=0 + var/new_dest = input("Enter new destination tag", "Mulebot [suffix ? "([suffix])" : ""]", destination) as text|null + refresh=1 + if(new_dest) + set_destination(new_dest) + + + if("setid") + refresh=0 + var/new_id = input("Enter new bot ID", "Mulebot [suffix ? "([suffix])" : ""]", suffix) as text|null + refresh=1 + if(new_id) + suffix = new_id + name = "Mulebot ([suffix])" + updateDialog() + + if("sethome") + refresh=0 + var/new_home = input("Enter new home tag", "Mulebot [suffix ? "([suffix])" : ""]", home_destination) as text|null + refresh=1 + if(new_home) + home_destination = new_home + updateDialog() + + if("unload") + if(load && mode !=1) if(loc == target) unload(loaddir) else unload(0) - return - if("home") - start_home() - return + if("autoret") + auto_return = !auto_return - if("bot_status") - send_status() - return + if("autopick") + auto_pickup = !auto_pickup - if("autoret") - auto_return = text2num(signal.data["value"]) - return + if("close") + usr.machine = null + usr << browse(null,"window=mulebot") - if("autopick") - auto_pickup = text2num(signal.data["value"]) - return - // receive response from beacon - recv = signal.data["beacon"] - if(wires & wire_beacon_rx) - if(recv == new_destination) // if the recvd beacon location matches the set destination - // the we will navigate there - destination = new_destination - target = signal.source.loc - var/direction = signal.data["dir"] // this will be the load/unload dir - if(direction) - loaddir = text2num(direction) + if("wirecut") + if(istype(usr.equipped(), /obj/item/weapon/wirecutters)) + var/wirebit = text2num(href_list["wire"]) + wires &= ~wirebit else - loaddir = 0 - icon_state = "mulebot[(wires & wire_mobavoid) == wire_mobavoid]" + usr << "\blue You need wirecutters!" + if("wiremend") + if(istype(usr.equipped(), /obj/item/weapon/wirecutters)) + var/wirebit = text2num(href_list["wire"]) + wires |= wirebit + else + usr << "\blue You need wirecutters!" + + if("wirepulse") + if(istype(usr.equipped(), /obj/item/device/multitool)) + switch(href_list["wire"]) + if("1","2") + usr << "\blue \icon[src] The charge light flickers." + if("4") + usr << "\blue \icon[src] The external warning lights flash briefly." + if("8") + usr << "\blue \icon[src] The load platform clunks." + if("16", "32") + usr << "\blue \icon[src] The drive motor whines briefly." + else + usr << "\blue \icon[src] You hear a radio crackle." + else + usr << "\blue You need a multitool!" + + + + updateDialog() + //src.updateUsrDialog() + else + usr << browse(null, "window=mulebot") + usr.machine = null + return + + + +// returns true if the bot has power +/obj/machinery/bot/mulebot/proc/has_power() + return !open && cell && cell.charge>0 && (wires & wire_power1) && (wires & wire_power2) + +// mousedrop a crate to load the bot +// can load anything if emagged + +/obj/machinery/bot/mulebot/MouseDrop_T(var/atom/movable/C, mob/user) + + if(user.stat) + return + + if (!on || !istype(C)|| C.anchored || get_dist(user, src) > 1 || get_dist(src,C) > 1 ) + return + + if(load) + return + + load(C) + + +// called to load a crate +/obj/machinery/bot/mulebot/proc/load(var/atom/movable/C) + if((wires & wire_loadcheck) && !istype(C,/obj/crate)) + src.visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") + playsound(src.loc, 'buzz-sigh.ogg', 50, 0) + return // if not emagged, only allow crates to be loaded + + if(get_dist(C, src) > 1 || load || !on) + return + mode = 1 + + // if a create, close before loading + var/obj/crate/crate = C + if(istype(crate)) + crate.close() + + C.loc = src.loc + sleep(2) + C.loc = src + load = C + + C.pixel_y += 9 + if(C.layer < layer) + C.layer = layer + 0.1 + overlays += C + + if(ismob(C)) + var/mob/M = C + if(M.client) + M.client.perspective = EYE_PERSPECTIVE + M.client.eye = src + + mode = 0 + send_status() + +// called to unload the bot +// argument is optional direction to unload +// if zero, unload at bot's location +/obj/machinery/bot/mulebot/proc/unload(var/dirn = 0) + if(!load) + return + + mode = 1 + overlays = null + + load.loc = src.loc + load.pixel_y -= 9 + load.layer = initial(load.layer) + if(ismob(load)) + var/mob/M = load + if(M.client) + M.client.perspective = MOB_PERSPECTIVE + M.client.eye = src + + + if(dirn) + step(load, dirn) + + load = null + + // in case non-load items end up in contents, dump every else too + // this seems to happen sometimes due to race conditions + // with items dropping as mobs are loaded + + for(var/atom/movable/AM in src) + if(AM == cell || AM == botcard) continue + + AM.loc = src.loc + AM.layer = initial(AM.layer) + AM.pixel_y = initial(AM.pixel_y) + if(ismob(AM)) + var/mob/M = AM + if(M.client) + M.client.perspective = MOB_PERSPECTIVE + M.client.eye = src + mode = 0 + + +/obj/machinery/bot/mulebot/process() + if(!has_power()) + on = 0 + return + if(on) + var/speed = ((wires & wire_motor1) ? 1:0) + ((wires & wire_motor2) ? 2:0) + //world << "speed: [speed]" + switch(speed) + if(0) + // do nothing + if(1) + process_bot() + spawn(3) + process_bot() + sleep(3) + process_bot() + if(2) + process_bot() + spawn(5) + process_bot() + if(3) + process_bot() + + if(refresh) updateDialog() + +/obj/machinery/bot/mulebot/proc/process_bot() + //if(mode) world << "Mode: [mode]" + switch(mode) + if(0) // idle + icon_state = "mulebot0" + return + if(1) // loading/unloading + return + if(2,3,4) // navigating to deliver,home, or blocked + + if(loc == target) // reached target + at_target() + return + + else if(path.len > 0 && target) // valid path + + var/turf/next = path[1] + reached_target = 0 + if(next == loc) + path -= next + return + + + if(istype( next, /turf/simulated)) + //world << "at ([x],[y]) moving to ([next.x],[next.y])" + + + if(bloodiness) + var/obj/decal/cleanable/blood/tracks/B = new(loc) + var/newdir = get_dir(next, loc) + if(newdir == dir) + B.dir = newdir + else + newdir = newdir | dir + if(newdir == 3) + newdir = 1 + else if(newdir == 12) + newdir = 4 + B.dir = newdir + bloodiness-- + + + + var/moved = step_towards(src, next) // attempt to move + if(cell) cell.use(1) + if(moved) // successful move + //world << "Successful move." + blockcount = 0 + path -= loc + + + if(mode==4) + spawn(1) + send_status() + + if(destination == home_destination) + mode = 3 + else + mode = 2 + + else // failed to move + + //world << "Unable to move." + + + + blockcount++ + mode = 4 + if(blockcount == 3) + src.visible_message("[src] makes an annoyed buzzing sound", "You hear an electronic buzzing sound.") + playsound(src.loc, 'buzz-two.ogg', 50, 0) + + if(blockcount > 5) // attempt 5 times before recomputing + // find new path excluding blocked turf + src.visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") + playsound(src.loc, 'buzz-sigh.ogg', 50, 0) + + spawn(2) + calc_path(next) + if(path.len > 0) + src.visible_message("[src] makes a delighted ping!", "You hear a ping.") + playsound(src.loc, 'ping.ogg', 50, 0) + mode = 4 + mode =6 + return + return + else + src.visible_message("[src] makes an annoyed buzzing sound", "You hear an electronic buzzing sound.") + playsound(src.loc, 'buzz-two.ogg', 50, 0) + //world << "Bad turf." + mode = 5 + return + else + //world << "No path." + mode = 5 + return + + if(5) // calculate new path + //world << "Calc new path." + mode = 6 + spawn(0) + calc_path() - updateDialog() - // send a radio signal with a single data key/value pair - proc/post_signal(var/freq, var/key, var/value) - post_signal_multiple(freq, list("[key]" = value) ) + if(path.len > 0) + blockcount = 0 + mode = 4 + src.visible_message("[src] makes a delighted ping!", "You hear a ping.") + playsound(src.loc, 'ping.ogg', 50, 0) - // send a radio signal with multiple data key/values - proc/post_signal_multiple(var/freq, var/list/keyval) + else + src.visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") + playsound(src.loc, 'buzz-sigh.ogg', 50, 0) - if(freq == beacon_freq && !(wires & wire_beacon_tx)) - return - if(freq == control_freq && !(wires & wire_remote_tx)) - return - - var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) - - if(!frequency) return + mode = 7 + //if(6) + //world << "Pending path calc." + //if(7) + //world << "No dest / no route." + return +// calculates a path to the current destination +// given an optional turf to avoid +/obj/machinery/bot/mulebot/proc/calc_path(var/turf/avoid = null) + src.path = AStar(src.loc, src.target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 250, id=botcard, exclude=avoid) + src.path = reverselist(src.path) - var/datum/signal/signal = new() - signal.source = src - signal.transmission_method = 1 - //for(var/key in keyval) - // signal.data[key] = keyval[key] - signal.data = keyval - //world << "sent [key],[keyval[key]] on [freq]" - if (signal.data["findbeacon"]) - frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS) - else if (signal.data["type"] == "mulebot") - frequency.post_signal(src, signal, filter = RADIO_MULEBOT) + +// sets the current destination +// signals all beacons matching the delivery code +// beacons will return a signal giving their locations +/obj/machinery/bot/mulebot/proc/set_destination(var/new_dest) + spawn(0) + new_destination = new_dest + post_signal(beacon_freq, "findbeacon", "delivery") + updateDialog() + +// starts bot moving to current destination +/obj/machinery/bot/mulebot/proc/start() + if(destination == home_destination) + mode = 3 + else + mode = 2 + icon_state = "mulebot[(wires & wire_mobavoid) == wire_mobavoid]" + +// starts bot moving to home +// sends a beacon query to find +/obj/machinery/bot/mulebot/proc/start_home() + spawn(0) + set_destination(home_destination) + mode = 4 + icon_state = "mulebot[(wires & wire_mobavoid) == wire_mobavoid]" + +// called when bot reaches current target +/obj/machinery/bot/mulebot/proc/at_target() + if(!reached_target) + src.visible_message("[src] makes a chiming sound!", "You hear a chime.") + playsound(src.loc, 'chime.ogg', 50, 0) + reached_target = 1 + + if(load) // if loaded, unload at target + unload(loaddir) else - frequency.post_signal(src, signal) + // not loaded + if(auto_pickup) // find a crate + var/atom/movable/AM + if(!(wires & wire_loadcheck)) // if emagged, load first unanchored thing we find + for(var/atom/movable/A in get_step(loc, loaddir)) + if(!A.anchored) + AM = A + break + else // otherwise, look for crates only + AM = locate(/obj/crate) in get_step(loc,loaddir) + if(AM) + load(AM) + // whatever happened, check to see if we return home - // signals bot status etc. to controller - proc/send_status() - var/list/kv = list( - "type" = "mulebot", - "name" = suffix, - "loca" = loc.loc, // area - "mode" = mode, - "powr" = (cell ? cell.percent() : 0), - "dest" = destination, - "home" = home_destination, - "load" = load, - "retn" = auto_return, - "pick" = auto_pickup, - ) - post_signal_multiple(control_freq, kv) + if(auto_return && destination != home_destination) + // auto return set and not at home already + start_home() + mode = 4 + else + mode = 0 // otherwise go idle + + send_status() // report status to anyone listening + + return + +// called when bot bumps into anything +/obj/machinery/bot/mulebot/Bump(var/atom/obs) + if(!(wires & wire_mobavoid)) //usually just bumps, but if avoidance disabled knock over mobs + var/mob/M = obs + if(ismob(M)) + if(istype(M,/mob/living/silicon/robot)) + src.visible_message("\red [src] bumps into [M]!") + else + src.visible_message("\red [src] knocks over [M]!") + M.pulling = null + M.stunned = 8 + M.weakened = 5 + M.lying = 1 + ..() + +/obj/machinery/bot/mulebot/alter_health() + return get_turf(src) + + +// called from mob/living/carbon/human/HasEntered() +// when mulebot is in the same loc +/obj/machinery/bot/mulebot/proc/RunOver(var/mob/living/carbon/human/H) + src.visible_message("\red [src] drives over [H]!") + playsound(src.loc, 'splat.ogg', 50, 1) + + /* + if(ismob(load)) + var/mob/M = load + if(M.reagents.has_reagent("beer")) + M.unlock_medal("DUI", 1) + if(M.reagents.has_reagent("space_drugs") && istype(H) && H.wear_id.assignment == "Security Officer") + M.unlock_medel("Ridin' Dirty",1) + */ + + var/damage = rand(5,15) + + H.TakeDamage("head", 2*damage, 0) + H.TakeDamage("chest",2*damage, 0) + H.TakeDamage("l_leg",0.5*damage, 0) + H.TakeDamage("r_leg",0.5*damage, 0) + H.TakeDamage("l_arm",0.5*damage, 0) + H.TakeDamage("r_arm",0.5*damage, 0) + + var/obj/decal/cleanable/blood/B = new(src.loc) + B.blood_DNA = H.dna.unique_enzymes + B.blood_type = H.b_type + + bloodiness += 4 + +// player on mulebot attempted to move +/obj/machinery/bot/mulebot/relaymove(var/mob/user) + if(user.stat) + return + if(load == user) + unload(0) + return + +// receive a radio signal +// used for control and beacon reception + +/obj/machinery/bot/mulebot/receive_signal(datum/signal/signal) + + if(!on) + return + + /* + world << "rec signal: [signal.source]" + for(var/x in signal.data) + world << "* [x] = [signal.data[x]]" + */ + var/recv = signal.data["command"] + // process all-bot input + if(recv=="bot_status" && (wires & wire_remote_rx)) + send_status() + + + recv = signal.data["command [suffix]"] + if(wires & wire_remote_rx) + // process control input + switch(recv) + if("stop") + mode = 0 + return + + if("go") + start() + return + + if("target") + set_destination(signal.data["destination"] ) + return + + if("unload") + if(loc == target) + unload(loaddir) + else + unload(0) + return + + if("home") + start_home() + return + + if("bot_status") + send_status() + return + + if("autoret") + auto_return = text2num(signal.data["value"]) + return + + if("autopick") + auto_pickup = text2num(signal.data["value"]) + return + + // receive response from beacon + recv = signal.data["beacon"] + if(wires & wire_beacon_rx) + if(recv == new_destination) // if the recvd beacon location matches the set destination + // the we will navigate there + destination = new_destination + target = signal.source.loc + var/direction = signal.data["dir"] // this will be the load/unload dir + if(direction) + loaddir = text2num(direction) + else + loaddir = 0 + icon_state = "mulebot[(wires & wire_mobavoid) == wire_mobavoid]" + calc_path() + updateDialog() + +// send a radio signal with a single data key/value pair +/obj/machinery/bot/mulebot/proc/post_signal(var/freq, var/key, var/value) + post_signal_multiple(freq, list("[key]" = value) ) + +// send a radio signal with multiple data key/values +/obj/machinery/bot/mulebot/proc/post_signal_multiple(var/freq, var/list/keyval) + + if(freq == beacon_freq && !(wires & wire_beacon_tx)) + return + if(freq == control_freq && !(wires & wire_remote_tx)) + return + + var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) + + if(!frequency) return + var/datum/signal/signal = new() + signal.source = src + signal.transmission_method = 1 + //for(var/key in keyval) + // signal.data[key] = keyval[key] + signal.data = keyval + //world << "sent [key],[keyval[key]] on [freq]" + if (signal.data["findbeacon"]) + frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS) + else if (signal.data["type"] == "mulebot") + frequency.post_signal(src, signal, filter = RADIO_MULEBOT) + else + frequency.post_signal(src, signal) + +// signals bot status etc. to controller +/obj/machinery/bot/mulebot/proc/send_status() + var/list/kv = list( + "type" = "mulebot", + "name" = suffix, + "loca" = loc.loc, // area + "mode" = mode, + "powr" = (cell ? cell.percent() : 0), + "dest" = destination, + "home" = home_destination, + "load" = load, + "retn" = auto_return, + "pick" = auto_pickup, + ) + post_signal_multiple(control_freq, kv) + +/obj/machinery/bot/mulebot/emp_act(severity) + if (cell) + cell.emp_act(severity) + if(load) + load.emp_act(severity) + ..() + + +/obj/machinery/bot/mulebot/explode() + src.visible_message("\red [src] blows apart!", 1) + var/turf/Tsec = get_turf(src) + + new /obj/item/device/prox_sensor(Tsec) + new /obj/item/stack/rods(Tsec) + new /obj/item/stack/rods(Tsec) + new /obj/item/weapon/cable_coil/cut(Tsec) + if (cell) + cell.loc = Tsec + cell.update_icon() + cell = null + + var/datum/effects/system/spark_spread/s = new /datum/effects/system/spark_spread + s.set_up(3, 1, src) + s.start() + + new /obj/decal/cleanable/oil(src.loc) + del(src) \ No newline at end of file diff --git a/code/game/machinery/bots/secbot.dm b/code/game/machinery/bots/secbot.dm index 294f6d1445b..b76a5964d26 100644 --- a/code/game/machinery/bots/secbot.dm +++ b/code/game/machinery/bots/secbot.dm @@ -6,9 +6,12 @@ layer = 5.0 density = 1 anchored = 0 + health = 25 + maxhealth = 25 + fire_dam_coeff = 0.7 + brute_dam_coeff = 0.5 // weight = 1.0E7 req_access = list(access_security) - var/on = 1 var/locked = 1 //Behavior Controls lock var/mob/living/carbon/target var/oldtarget_name @@ -17,7 +20,6 @@ var/last_found //There's a delay var/frustration = 0 var/emagged = 0 //Emagged Secbots view everyone as a criminal - var/health = 25 var/idcheck = 1 //If false, all station IDs are authorized for weapons. var/check_records = 1 //Does it check security records? var/arrest_type = 0 //If true, don't handcuff @@ -83,33 +85,45 @@ radio_controller.add_object(src, control_freq, filter = RADIO_SECBOT) radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS) - examine() - set src in view() - ..() - if (src.health < 25) - if (src.health > 15) - usr << text("\red [src]'s parts look loose.") - else - usr << text("\red [src]'s parts look very loose!") +/obj/machinery/bot/secbot/turn_on() + ..() + src.icon_state = "secbot[src.on]" + src.updateUsrDialog() + +/obj/machinery/bot/secbot/turn_off() + ..() + src.target = null + src.oldtarget_name = null + src.anchored = 0 + src.mode = SECBOT_IDLE + walk_to(src,0) + src.icon_state = "secbot[src.on]" + src.updateUsrDialog() + +/obj/machinery/bot/secbot/attack_hand(mob/user as mob) + . = ..() + if (.) return + usr.machine = src + interact(user) - attack_hand(user as mob) - var/dat +/obj/machinery/bot/secbot/proc/interact(mob/user as mob) + var/dat - dat += text({" + dat += text({" Automatic Security Unit v1.3

-Status: []
-Behaviour controls are [src.locked ? "locked" : "unlocked"]"}, +/obj/machinery/bot/secbot/Status: []
+/obj/machinery/bot/secbot/Behaviour controls are [src.locked ? "locked" : "unlocked"]"}, "[src.on ? "On" : "Off"]" ) - if(!src.locked) - dat += text({"
-Check for Weapon Authorization: []
-Check Security Records: []
-Operating Mode: []
-Auto Patrol: []"}, + if(!src.locked) + dat += text({"
+/obj/machinery/bot/secbot/Check for Weapon Authorization: []
+/obj/machinery/bot/secbot/Check Security Records: []
+/obj/machinery/bot/secbot/Operating Mode: []
+/obj/machinery/bot/secbot/Auto Patrol: []"}, "[src.idcheck ? "Yes" : "No"]", "[src.check_records ? "Yes" : "No"]", @@ -117,621 +131,570 @@ Auto Patrol: []"}, "[auto_patrol ? "On" : "Off"]" ) - user << browse("Securitron v1.3 controls[dat]", "window=autosec") - onclose(user, "autosec") + user << browse("Securitron v1.3 controls[dat]", "window=autosec") + onclose(user, "autosec") + return + +/obj/machinery/bot/secbot/Topic(href, href_list) + usr.machine = src + src.add_fingerprint(usr) + if ((href_list["power"]) && (src.allowed(usr))) + if (src.on) + turn_off() + else + turn_on() return - Topic(href, href_list) - usr.machine = src - src.add_fingerprint(usr) - if ((href_list["power"]) && (src.allowed(usr))) - src.on = !src.on - src.target = null - src.oldtarget_name = null - src.anchored = 0 - src.mode = SECBOT_IDLE - walk_to(src,0) - src.icon_state = "secbot[src.on]" + switch(href_list["operation"]) + if ("idcheck") + src.idcheck = !src.idcheck src.updateUsrDialog() - - switch(href_list["operation"]) - if ("idcheck") - src.idcheck = !src.idcheck - src.updateUsrDialog() - if ("ignorerec") - src.check_records = !src.check_records - src.updateUsrDialog() - if ("switchmode") - src.arrest_type = !src.arrest_type - src.updateUsrDialog() - if("patrol") - auto_patrol = !auto_patrol - mode = SECBOT_IDLE - updateUsrDialog() - - attack_ai(mob/user as mob) - src.on = !src.on - src.target = null - src.oldtarget_name = null - mode = SECBOT_IDLE - src.anchored = 0 - src.icon_state = "secbot[src.on]" - walk_to(src,0) - - attackby(obj/item/weapon/W as obj, mob/user as mob) - if ((istype(W, /obj/item/weapon/card/emag)) && (!src.emagged)) - user << "\red You short out [src]'s target assessment circuits." - spawn(0) - for(var/mob/O in hearers(src, null)) - O.show_message("\red [src] buzzes oddly!", 1) - src.target = null - src.oldtarget_name = user.name - src.last_found = world.time - src.anchored = 0 - src.emagged = 1 - src.on = 1 - src.icon_state = "secbot[src.on]" + if ("ignorerec") + src.check_records = !src.check_records + src.updateUsrDialog() + if ("switchmode") + src.arrest_type = !src.arrest_type + src.updateUsrDialog() + if("patrol") + auto_patrol = !auto_patrol mode = SECBOT_IDLE - else if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) - if (src.allowed(user)) - src.locked = !src.locked - user << "Controls are now [src.locked ? "locked." : "unlocked."]" - else - user << "\red Access denied." + updateUsrDialog() - else if (istype(W, /obj/item/weapon/screwdriver)) - if (src.health < 25) - src.health = 25 - for(var/mob/O in viewers(src, null)) - O << "\red [user] repairs [src]!" +/obj/machinery/bot/secbot/attack_ai(mob/user as mob) + if (src.on) + turn_off() + else + turn_on() + +/obj/machinery/bot/secbot/attackby(obj/item/weapon/W as obj, mob/user as mob) + if ((istype(W, /obj/item/weapon/card/emag)) && (!src.emagged)) + user << "\red You short out [src]'s target assessment circuits." + spawn(0) + for(var/mob/O in hearers(src, null)) + O.show_message("\red [src] buzzes oddly!", 1) + src.target = null + src.oldtarget_name = user.name + src.last_found = world.time + src.anchored = 0 + src.emagged = 1 + src.on = 1 + src.icon_state = "secbot[src.on]" + mode = SECBOT_IDLE + else if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) + if (src.allowed(user)) + src.locked = !src.locked + user << "Controls are now [src.locked ? "locked." : "unlocked."]" else - switch(W.damtype) - if("fire") - src.health -= W.force * 0.75 - if("brute") - src.health -= W.force * 0.5 - else - if (src.health <= 0) - src.explode() - else if ((W.force) && (!src.target)) - src.target = user - src.mode = SECBOT_HUNT - ..() + user << "\red Access denied." + else + ..() + if (!istype(W, /obj/item/weapon/screwdriver) && (W.force) && (!src.target)) + src.target = user + src.mode = SECBOT_HUNT - process() - set background = 1 +/obj/machinery/bot/secbot/process() + set background = 1 - if (!src.on) - return + if (!src.on) + return - switch(mode) + switch(mode) - if(SECBOT_IDLE) // idle + if(SECBOT_IDLE) // idle + walk_to(src,0) + look_for_perp() // see if any criminals are in range + if(!mode && auto_patrol) // still idle, and set to patrol + mode = SECBOT_START_PATROL // switch to patrol mode + + if(SECBOT_HUNT) // hunting for perp + + // if can't reach perp for long enough, go idle + if (src.frustration >= 8) + // for(var/mob/O in hearers(src, null)) + // O << "[src] beeps, \"Backup requested! Suspect has evaded arrest.\"" + src.target = null + src.last_found = world.time + src.frustration = 0 + src.mode = 0 walk_to(src,0) - look_for_perp() // see if any criminals are in range - if(!mode && auto_patrol) // still idle, and set to patrol - mode = SECBOT_START_PATROL // switch to patrol mode - if(SECBOT_HUNT) // hunting for perp - - // if can't reach perp for long enough, go idle - if (src.frustration >= 8) - // for(var/mob/O in hearers(src, null)) - // O << "[src] beeps, \"Backup requested! Suspect has evaded arrest.\"" - src.target = null - src.last_found = world.time - src.frustration = 0 - src.mode = 0 - walk_to(src,0) - - if (target) // make sure target exists - if (get_dist(src, src.target) <= 1) // if right next to perp - playsound(src.loc, 'Egloves.ogg', 50, 1, -1) - src.icon_state = "secbot-c" - spawn(2) - src.icon_state = "secbot[src.on]" - var/mob/living/carbon/M = src.target - var/maxstuns = 4 - if (istype(M, /mob/living/carbon/human)) - if (M.weakened < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) - M.weakened = 10 - if (M.stuttering < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) - M.stuttering = 10 - if (M.stunned < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) - M.stunned = 10 - else + if (target) // make sure target exists + if (get_dist(src, src.target) <= 1) // if right next to perp + playsound(src.loc, 'Egloves.ogg', 50, 1, -1) + src.icon_state = "secbot-c" + spawn(2) + src.icon_state = "secbot[src.on]" + var/mob/living/carbon/M = src.target + var/maxstuns = 4 + if (istype(M, /mob/living/carbon/human)) + if (M.weakened < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) M.weakened = 10 + if (M.stuttering < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) M.stuttering = 10 + if (M.stunned < 10 && (!(M.mutations & 8)) /*&& (!istype(M:wear_suit, /obj/item/clothing/suit/judgerobe))*/) M.stunned = 10 - maxstuns-- - if (maxstuns <= 0) - target = null - for(var/mob/O in viewers(src, null)) - O.show_message("\red [src.target] has been stunned by [src]!", 1, "\red You hear someone fall", 2) - - mode = SECBOT_PREP_ARREST - src.anchored = 1 - src.target_lastloc = M.loc - return - - else // not next to perp - var/turf/olddist = get_dist(src, src.target) - walk_to(src, src.target,1,4) - if ((get_dist(src, src.target)) >= (olddist)) - src.frustration++ - else - src.frustration = 0 - - if(SECBOT_PREP_ARREST) // preparing to arrest target - - // see if he got away - if ((get_dist(src, src.target) > 1) || ((src.target:loc != src.target_lastloc) && src.target:weakened < 2)) - src.anchored = 0 - mode = SECBOT_HUNT - return - - if (!src.target.handcuffed && !src.arrest_type) - playsound(src.loc, 'handcuffs.ogg', 30, 1, -2) - mode = SECBOT_ARREST + else + M.weakened = 10 + M.stuttering = 10 + M.stunned = 10 + maxstuns-- + if (maxstuns <= 0) + target = null for(var/mob/O in viewers(src, null)) - O.show_message("\red [src] is trying to put handcuffs on [src.target]!", 1) + O.show_message("\red [src.target] has been stunned by [src]!", 1, "\red You hear someone fall", 2) - spawn(60) - if (get_dist(src, src.target) <= 1) - if (src.target.handcuffed) - return - - if(istype(src.target,/mob/living/carbon)) - src.target.handcuffed = new /obj/item/weapon/handcuffs(src.target) - - mode = SECBOT_IDLE - src.target = null - src.anchored = 0 - src.last_found = world.time - src.frustration = 0 - - playsound(src.loc, pick('bgod.ogg', 'biamthelaw.ogg', 'bsecureday.ogg', 'bradio.ogg', 'binsult.ogg', 'bcreep.ogg'), 50, 0) - // var/arrest_message = pick("Have a secure day!","I AM THE LAW.", "God made tomorrow for the crooks we don't catch today.","You can't outrun a radio.") - // src.speak(arrest_message) - - if(SECBOT_ARREST) // arresting - - if (src.target.handcuffed) - src.anchored = 0 - mode = SECBOT_IDLE + mode = SECBOT_PREP_ARREST + src.anchored = 1 + src.target_lastloc = M.loc return + else // not next to perp + var/turf/olddist = get_dist(src, src.target) + walk_to(src, src.target,1,4) + if ((get_dist(src, src.target)) >= (olddist)) + src.frustration++ + else + src.frustration = 0 - if(SECBOT_START_PATROL) // start a patrol + if(SECBOT_PREP_ARREST) // preparing to arrest target - if(path.len > 0 && patrol_target) // have a valid path, so just resume - mode = SECBOT_PATROL - return + // see if he got away + if ((get_dist(src, src.target) > 1) || ((src.target:loc != src.target_lastloc) && src.target:weakened < 2)) + src.anchored = 0 + mode = SECBOT_HUNT + return - else if(patrol_target) // has patrol target already - spawn(0) - calc_path() // so just find a route to it - if(path.len == 0) - patrol_target = 0 + if (!src.target.handcuffed && !src.arrest_type) + playsound(src.loc, 'handcuffs.ogg', 30, 1, -2) + mode = SECBOT_ARREST + for(var/mob/O in viewers(src, null)) + O.show_message("\red [src] is trying to put handcuffs on [src.target]!", 1) + + spawn(60) + if (get_dist(src, src.target) <= 1) + if (src.target.handcuffed) return - mode = SECBOT_PATROL + if(istype(src.target,/mob/living/carbon)) + src.target.handcuffed = new /obj/item/weapon/handcuffs(src.target) - else // no patrol target, so need a new one - find_patrol_target() - speak("Engaging patrol mode.") + mode = SECBOT_IDLE + src.target = null + src.anchored = 0 + src.last_found = world.time + src.frustration = 0 + playsound(src.loc, pick('bgod.ogg', 'biamthelaw.ogg', 'bsecureday.ogg', 'bradio.ogg', 'binsult.ogg', 'bcreep.ogg'), 50, 0) + // var/arrest_message = pick("Have a secure day!","I AM THE LAW.", "God made tomorrow for the crooks we don't catch today.","You can't outrun a radio.") + // src.speak(arrest_message) - if(SECBOT_PATROL) // patrol mode + if(SECBOT_ARREST) // arresting - patrol_step() - spawn(5) - if(mode == SECBOT_PATROL) - patrol_step() - - if(SECBOT_SUMMON) // summoned to PDA - patrol_step() - spawn(4) - if(mode == SECBOT_SUMMON) - patrol_step() - sleep(4) - patrol_step() - - return - - - // perform a single patrol step - - proc/patrol_step() - - if(loc == patrol_target) // reached target - at_patrol_target() - return - - else if(path.len > 0 && patrol_target) // valid path - - var/turf/next = path[1] - if(next == loc) - path -= next + if (src.target.handcuffed) + src.anchored = 0 + mode = SECBOT_IDLE return - if(istype( next, /turf/simulated)) + if(SECBOT_START_PATROL) // start a patrol - var/moved = step_towards(src, next) // attempt to move - if(moved) // successful move - blockcount = 0 - path -= loc - - look_for_perp() - else // failed to move - - blockcount++ - - if(blockcount > 5) // attempt 5 times before recomputing - // find new path excluding blocked turf - - spawn(2) - calc_path(next) - if(path.len == 0) - find_patrol_target() - else - blockcount = 0 + if(path.len > 0 && patrol_target) // have a valid path, so just resume + mode = SECBOT_PATROL + return + else if(patrol_target) // has patrol target already + spawn(0) + calc_path() // so just find a route to it + if(path.len == 0) + patrol_target = 0 return + mode = SECBOT_PATROL + + + else // no patrol target, so need a new one + find_patrol_target() + speak("Engaging patrol mode.") + + + if(SECBOT_PATROL) // patrol mode + + patrol_step() + spawn(5) + if(mode == SECBOT_PATROL) + patrol_step() + + if(SECBOT_SUMMON) // summoned to PDA + patrol_step() + spawn(4) + if(mode == SECBOT_SUMMON) + patrol_step() + sleep(4) + patrol_step() + + return + + +// perform a single patrol step + +/obj/machinery/bot/secbot/proc/patrol_step() + + if(loc == patrol_target) // reached target + at_patrol_target() + return + + else if(path.len > 0 && patrol_target) // valid path + + var/turf/next = path[1] + if(next == loc) + path -= next + return + + + if(istype( next, /turf/simulated)) + + var/moved = step_towards(src, next) // attempt to move + if(moved) // successful move + blockcount = 0 + path -= loc + + look_for_perp() + else // failed to move + + blockcount++ + + if(blockcount > 5) // attempt 5 times before recomputing + // find new path excluding blocked turf + + spawn(2) + calc_path(next) + if(path.len == 0) + find_patrol_target() + else + blockcount = 0 return - else // not a valid turf - mode = SECBOT_IDLE return - else // no path, so calculate new one - mode = SECBOT_START_PATROL - - - // finds a new patrol target - proc/find_patrol_target() - send_status() - if(awaiting_beacon) // awaiting beacon response - awaiting_beacon++ - if(awaiting_beacon > 5) // wait 5 secs for beacon response - find_nearest_beacon() // then go to nearest instead + else // not a valid turf + mode = SECBOT_IDLE return - if(next_destination) - set_destination(next_destination) + else // no path, so calculate new one + mode = SECBOT_START_PATROL + + +// finds a new patrol target +/obj/machinery/bot/secbot/proc/find_patrol_target() + send_status() + if(awaiting_beacon) // awaiting beacon response + awaiting_beacon++ + if(awaiting_beacon > 5) // wait 5 secs for beacon response + find_nearest_beacon() // then go to nearest instead + return + + if(next_destination) + set_destination(next_destination) + else + find_nearest_beacon() + return + + +// finds the nearest beacon to self +// signals all beacons matching the patrol code +/obj/machinery/bot/secbot/proc/find_nearest_beacon() + nearest_beacon = null + new_destination = "__nearest__" + post_signal(beacon_freq, "findbeacon", "patrol") + awaiting_beacon = 1 + spawn(10) + awaiting_beacon = 0 + if(nearest_beacon) + set_destination(nearest_beacon) else - find_nearest_beacon() - return - - - // finds the nearest beacon to self - // signals all beacons matching the patrol code - proc/find_nearest_beacon() - nearest_beacon = null - new_destination = "__nearest__" - post_signal(beacon_freq, "findbeacon", "patrol") - awaiting_beacon = 1 - spawn(10) - awaiting_beacon = 0 - if(nearest_beacon) - set_destination(nearest_beacon) - else - auto_patrol = 0 - mode = SECBOT_IDLE - speak("Disengaging patrol mode.") - send_status() - - - proc/at_patrol_target() - find_patrol_target() - return - - - // sets the current destination - // signals all beacons matching the patrol code - // beacons will return a signal giving their locations - proc/set_destination(var/new_dest) - new_destination = new_dest - post_signal(beacon_freq, "findbeacon", "patrol") - awaiting_beacon = 1 - - - // receive a radio signal - // used for beacon reception - - receive_signal(datum/signal/signal) - //log_admin("DEBUG \[[world.timeofday]\]: /obj/machinery/bot/secbot/receive_signal([signal.debug_print()])") - if(!on) - return - - /* - world << "rec signal: [signal.source]" - for(var/x in signal.data) - world << "* [x] = [signal.data[x]]" - */ - - var/recv = signal.data["command"] - // process all-bot input - if(recv=="bot_status") + auto_patrol = 0 + mode = SECBOT_IDLE + speak("Disengaging patrol mode.") send_status() - // check to see if we are the commanded bot - if(signal.data["active"] == src) - // process control input - switch(recv) - if("stop") - mode = SECBOT_IDLE - auto_patrol = 0 - return - if("go") - mode = SECBOT_IDLE - auto_patrol = 1 - return - - if("summon") - patrol_target = signal.data["target"] - next_destination = destination - destination = null - awaiting_beacon = 0 - mode = SECBOT_SUMMON - calc_path() - speak("Responding.") - - return +/obj/machinery/bot/secbot/proc/at_patrol_target() + find_patrol_target() + return +// sets the current destination +// signals all beacons matching the patrol code +// beacons will return a signal giving their locations +/obj/machinery/bot/secbot/proc/set_destination(var/new_dest) + new_destination = new_dest + post_signal(beacon_freq, "findbeacon", "patrol") + awaiting_beacon = 1 - // receive response from beacon - recv = signal.data["beacon"] - var/valid = signal.data["patrol"] - if(!recv || !valid) - return - if(recv == new_destination) // if the recvd beacon location matches the set destination - // the we will navigate there - destination = new_destination - patrol_target = signal.source.loc - next_destination = signal.data["next_patrol"] - awaiting_beacon = 0 +// receive a radio signal +// used for beacon reception - // if looking for nearest beacon - else if(new_destination == "__nearest__") - var/dist = get_dist(src,signal.source.loc) - if(nearest_beacon) - - // note we ignore the beacon we are located at - if(dist>1 && dist 1) - nearest_beacon = recv - nearest_beacon_loc = signal.source.loc +/obj/machinery/bot/secbot/receive_signal(datum/signal/signal) + //log_admin("DEBUG \[[world.timeofday]\]: /obj/machinery/bot/secbot/receive_signal([signal.debug_print()])") + if(!on) return + /* + world << "rec signal: [signal.source]" + for(var/x in signal.data) + world << "* [x] = [signal.data[x]]" + */ - // send a radio signal with a single data key/value pair - proc/post_signal(var/freq, var/key, var/value) - post_signal_multiple(freq, list("[key]" = value) ) + var/recv = signal.data["command"] + // process all-bot input + if(recv=="bot_status") + send_status() - // send a radio signal with multiple data key/values - proc/post_signal_multiple(var/freq, var/list/keyval) + // check to see if we are the commanded bot + if(signal.data["active"] == src) + // process control input + switch(recv) + if("stop") + mode = SECBOT_IDLE + auto_patrol = 0 + return - var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) + if("go") + mode = SECBOT_IDLE + auto_patrol = 1 + return - if(!frequency) return + if("summon") + patrol_target = signal.data["target"] + next_destination = destination + destination = null + awaiting_beacon = 0 + mode = SECBOT_SUMMON + calc_path() + speak("Responding.") - var/datum/signal/signal = new() - signal.source = src - signal.transmission_method = 1 - //for(var/key in keyval) - // signal.data[key] = keyval[key] - signal.data = keyval - //world << "sent [key],[keyval[key]] on [freq]" - if (signal.data["findbeacon"]) - frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS) - else if (signal.data["type"] == "secbot") - frequency.post_signal(src, signal, filter = RADIO_SECBOT) - else - frequency.post_signal(src, signal) + return - // signals bot status etc. to controller - proc/send_status() - var/list/kv = new() - kv["type"] = "secbot" - kv["name"] = name - kv["loca"] = loc.loc // area - kv["mode"] = mode - post_signal_multiple(control_freq, kv) + + + // receive response from beacon + recv = signal.data["beacon"] + var/valid = signal.data["patrol"] + if(!recv || !valid) + return + + if(recv == new_destination) // if the recvd beacon location matches the set destination + // the we will navigate there + destination = new_destination + patrol_target = signal.source.loc + next_destination = signal.data["next_patrol"] + awaiting_beacon = 0 + + // if looking for nearest beacon + else if(new_destination == "__nearest__") + var/dist = get_dist(src,signal.source.loc) + if(nearest_beacon) + + // note we ignore the beacon we are located at + if(dist>1 && dist 1) + nearest_beacon = recv + nearest_beacon_loc = signal.source.loc + return + + +// send a radio signal with a single data key/value pair +/obj/machinery/bot/secbot/proc/post_signal(var/freq, var/key, var/value) + post_signal_multiple(freq, list("[key]" = value) ) + +// send a radio signal with multiple data key/values +/obj/machinery/bot/secbot/proc/post_signal_multiple(var/freq, var/list/keyval) + + var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) + + if(!frequency) return + + var/datum/signal/signal = new() + signal.source = src + signal.transmission_method = 1 + //for(var/key in keyval) + // signal.data[key] = keyval[key] + signal.data = keyval + //world << "sent [key],[keyval[key]] on [freq]" + if (signal.data["findbeacon"]) + frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS) + else if (signal.data["type"] == "secbot") + frequency.post_signal(src, signal, filter = RADIO_SECBOT) + else + frequency.post_signal(src, signal) + +// signals bot status etc. to controller +/obj/machinery/bot/secbot/proc/send_status() + var/list/kv = list( + "type" = "secbot", + "name" = name, + "loca" = loc.loc, // area + "mode" = mode + ) + post_signal_multiple(control_freq, kv) // calculates a path to the current destination // given an optional turf to avoid - proc/calc_path(var/turf/avoid = null) - src.path = AStar(src.loc, patrol_target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 120, id=botcard, exclude=avoid) - src.path = reverselist(src.path) +/obj/machinery/bot/secbot/proc/calc_path(var/turf/avoid = null) + src.path = AStar(src.loc, patrol_target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 120, id=botcard, exclude=avoid) + src.path = reverselist(src.path) // look for a criminal in view of the bot - proc/look_for_perp() - src.anchored = 0 - for (var/mob/living/carbon/C in view(7,src)) //Let's find us a criminal - if ((C.stat) || (C.handcuffed)) - continue +/obj/machinery/bot/secbot/proc/look_for_perp() + src.anchored = 0 + for (var/mob/living/carbon/C in view(7,src)) //Let's find us a criminal + if ((C.stat) || (C.handcuffed)) + continue - if ((C.name == src.oldtarget_name) && (world.time < src.last_found + 100)) - continue + if ((C.name == src.oldtarget_name) && (world.time < src.last_found + 100)) + continue - if (istype(C, /mob/living/carbon/human)) - src.threatlevel = src.assess_perp(C) - else if ((istype(C, /mob/living/carbon/monkey)) && (C.client) && (ticker.mode.name == "monkey")) - src.threatlevel = 4 + if (istype(C, /mob/living/carbon/human)) + src.threatlevel = src.assess_perp(C) + else if ((istype(C, /mob/living/carbon/monkey)) && (C.client) && (ticker.mode.name == "monkey")) + src.threatlevel = 4 - if (!src.threatlevel) - continue + if (!src.threatlevel) + continue - else if (src.threatlevel >= 4) - src.target = C - src.oldtarget_name = C.name - src.speak("Level [src.threatlevel] infraction alert!") - playsound(src.loc, pick('bcriminal.ogg', 'bjustice.ogg', 'bfreeze.ogg'), 50, 0) - src.visible_message("[src] points at [C.name]!") - mode = SECBOT_HUNT - spawn(0) - process() // ensure bot quickly responds to a perp - break - else - continue + else if (src.threatlevel >= 4) + src.target = C + src.oldtarget_name = C.name + src.speak("Level [src.threatlevel] infraction alert!") + playsound(src.loc, pick('bcriminal.ogg', 'bjustice.ogg', 'bfreeze.ogg'), 50, 0) + src.visible_message("[src] points at [C.name]!") + mode = SECBOT_HUNT + spawn(0) + process() // ensure bot quickly responds to a perp + break + else + continue //If the security records say to arrest them, arrest them //Or if they have weapons and aren't security, arrest them. - proc/assess_perp(mob/living/carbon/human/perp as mob) - var/threatcount = 0 +/obj/machinery/bot/secbot/proc/assess_perp(mob/living/carbon/human/perp as mob) + var/threatcount = 0 - if(src.emagged) return 10 //Everyone is a criminal! + if(src.emagged) return 10 //Everyone is a criminal! - if((src.idcheck) || (isnull(perp:wear_id)) || (istype(perp:wear_id, /obj/item/weapon/card/id/syndicate))) - if(src.allowed(perp)) //Corrupt cops cannot exist beep boop - return 0 + if((src.idcheck) || (isnull(perp:wear_id)) || (istype(perp:wear_id, /obj/item/weapon/card/id/syndicate))) + if(src.allowed(perp)) //Corrupt cops cannot exist beep boop + return 0 - if((istype(perp.l_hand, /obj/item/weapon/gun) && !istype(perp.l_hand, /obj/item/weapon/gun/shotgun)) || istype(perp.l_hand, /obj/item/weapon/baton)) - threatcount += 4 + if((istype(perp.l_hand, /obj/item/weapon/gun) && !istype(perp.l_hand, /obj/item/weapon/gun/shotgun)) || istype(perp.l_hand, /obj/item/weapon/baton)) + threatcount += 4 - if((istype(perp.r_hand, /obj/item/weapon/gun) && !istype(perp.r_hand, /obj/item/weapon/gun/shotgun)) || istype(perp.r_hand, /obj/item/weapon/baton)) - threatcount += 4 + if((istype(perp.r_hand, /obj/item/weapon/gun) && !istype(perp.r_hand, /obj/item/weapon/gun/shotgun)) || istype(perp.r_hand, /obj/item/weapon/baton)) + threatcount += 4 - if(istype(perp:belt, /obj/item/weapon/gun) || istype(perp:belt, /obj/item/weapon/baton)) - threatcount += 2 + if(istype(perp:belt, /obj/item/weapon/gun) || istype(perp:belt, /obj/item/weapon/baton)) + threatcount += 2 - if(istype(perp:wear_suit, /obj/item/clothing/suit/wizrobe)) - threatcount += 2 + if(istype(perp:wear_suit, /obj/item/clothing/suit/wizrobe)) + threatcount += 2 - if(perp.mutantrace && perp.mutantrace != "none") - threatcount += 2 + if(perp.mutantrace && perp.mutantrace != "none") + threatcount += 2 - //Agent cards lower threatlevel when normal idchecking is off. - if((istype(perp:wear_id, /obj/item/weapon/card/id/syndicate)) && src.idcheck) - threatcount -= 2 +//Agent cards lower threatlevel when normal idchecking is off. + if((istype(perp:wear_id, /obj/item/weapon/card/id/syndicate)) && src.idcheck) + threatcount -= 2 - if (src.check_records) - for (var/datum/data/record/E in data_core.general) - var/perpname = perp.name - if (perp:wear_id) - var/obj/item/weapon/card/id/id = perp:wear_id - if(istype(perp:wear_id, /obj/item/device/pda)) - var/obj/item/device/pda/pda = perp:wear_id - id = pda.id - if (id) - perpname = id.registered - else - var/obj/item/device/pda/pda = perp:wear_id - perpname = pda.owner - if (E.fields["name"] == perpname) - for (var/datum/data/record/R in data_core.security) - if ((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "*Arrest*")) - threatcount = 4 - break + if (src.check_records) + for (var/datum/data/record/E in data_core.general) + var/perpname = perp.name + if (perp:wear_id) + var/obj/item/weapon/card/id/id = perp:wear_id + if(istype(perp:wear_id, /obj/item/device/pda)) + var/obj/item/device/pda/pda = perp:wear_id + id = pda.id + if (id) + perpname = id.registered + else + var/obj/item/device/pda/pda = perp:wear_id + perpname = pda.owner + if (E.fields["name"] == perpname) + for (var/datum/data/record/R in data_core.security) + if ((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "*Arrest*")) + threatcount = 4 + break - return threatcount + return threatcount - Bump(M as mob|obj) //Leave no door unopened! - spawn(0) - if ((istype(M, /obj/machinery/door)) && (!isnull(src.botcard))) - var/obj/machinery/door/D = M - if (!istype(D, /obj/machinery/door/firedoor) && D.check_access(src.botcard)) - D.open() - src.frustration = 0 - else if ((istype(M, /mob/living/)) && (!src.anchored)) - src.loc = M:loc +/obj/machinery/bot/secbot/Bump(M as mob|obj) //Leave no door unopened! + spawn(0) + if ((istype(M, /obj/machinery/door)) && (!isnull(src.botcard))) + var/obj/machinery/door/D = M + if (!istype(D, /obj/machinery/door/firedoor) && D.check_access(src.botcard)) + D.open() src.frustration = 0 + else if ((istype(M, /mob/living/)) && (!src.anchored)) + src.loc = M:loc + src.frustration = 0 - return return + return - Bumped(M as mob|obj) - spawn(0) - var/turf/T = get_turf(src) - M:loc = T +/obj/machinery/bot/secbot/Bumped(M as mob|obj) + spawn(0) + var/turf/T = get_turf(src) + M:loc = T - bullet_act(flag, A as obj) - switch(flag) - if (PROJECTILE_BULLET) - src.health -= 20 - //if (PROJECTILE_WEAKBULLET || PROJECTILE_BEANBAG) //Detective's revolver fires marshmallows - // src.health -= 2 - if (PROJECTILE_LASER) - src.health -= 10 - if (PROJECTILE_PULSE) - src.health -=40 - if (src.health <= 0) - src.explode() - - proc/speak(var/message) - for(var/mob/O in hearers(src, null)) - O << "[src] beeps, \"[message]\"" - return - - //Generally we want to explode() instead of just deleting the securitron. - ex_act(severity) - switch(severity) - if(1.0) - src.explode() - return - if(2.0) - src.health -= 15 - if (src.health <= 0) - src.explode() - return - return - - meteorhit() - src.explode() - return - - blob_act() - if(prob(50)) - src.explode() - return - - proc/explode() - - walk_to(src,0) - for(var/mob/O in hearers(src, null)) - O.show_message("\red [src] blows apart!", 1) - var/turf/Tsec = get_turf(src) - - var/obj/item/weapon/secbot_assembly/Sa = new /obj/item/weapon/secbot_assembly(Tsec) - Sa.build_step = 1 - Sa.overlays += image('aibots.dmi', "hs_hole") - Sa.created_name = src.name - new /obj/item/device/prox_sensor(Tsec) - - var/obj/item/weapon/baton/B = new /obj/item/weapon/baton(Tsec) - B.charges = 0 - - if (prob(50)) - new /obj/item/robot_parts/l_arm(Tsec) - - var/datum/effects/system/spark_spread/s = new /datum/effects/system/spark_spread - s.set_up(3, 1, src) - s.start() - del(src) +/obj/machinery/bot/secbot/proc/speak(var/message) + for(var/mob/O in hearers(src, null)) + O << "[src] beeps, \"[message]\"" + return +/obj/machinery/bot/secbot/explode() + walk_to(src,0) + src.visible_message("\red [src] blows apart!", 1) + var/turf/Tsec = get_turf(src) + var/obj/item/weapon/secbot_assembly/Sa = new /obj/item/weapon/secbot_assembly(Tsec) + Sa.build_step = 1 + Sa.overlays += image('aibots.dmi', "hs_hole") + Sa.created_name = src.name + new /obj/item/device/prox_sensor(Tsec) + var/obj/item/weapon/baton/B = new /obj/item/weapon/baton(Tsec) + B.charges = 0 + + if (prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + var/datum/effects/system/spark_spread/s = new /datum/effects/system/spark_spread + s.set_up(3, 1, src) + s.start() + + new /obj/decal/cleanable/oil(src.loc) + del(src) + +/obj/machinery/bot/secbot/attack_alien(var/mob/living/carbon/alien/user as mob) + ..() + if (!isalien(target)) + src.target = user + src.mode = SECBOT_HUNT //Secbot Construction @@ -798,4 +761,4 @@ Auto Patrol: []"}, if (!in_range(src, usr) && src.loc != usr) return - src.created_name = t \ No newline at end of file + src.created_name = t diff --git a/code/game/objects/devices/PDA/radio.dm b/code/game/objects/devices/PDA/radio.dm index de232604e5f..8cd587dac5d 100644 --- a/code/game/objects/devices/PDA/radio.dm +++ b/code/game/objects/devices/PDA/radio.dm @@ -127,7 +127,7 @@ radio_controller.add_object(src, control_freq, filter = RADIO_MULEBOT) radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS) spawn(10) - post_signal(beacon_freq, "findbeacon", "delivery", filter = RADIO_NAVBEACONS) + post_signal(beacon_freq, "findbeacon", "delivery", s_filter = RADIO_NAVBEACONS) // receive radio signals // can detect bot status signals diff --git a/code/game/objects/storage/toolbox.dm b/code/game/objects/storage/toolbox.dm index 410ad545262..c91050ce95c 100644 --- a/code/game/objects/storage/toolbox.dm +++ b/code/game/objects/storage/toolbox.dm @@ -6,7 +6,7 @@ /obj/item/weapon/storage/toolbox/emergency/New() ..() - new /obj/item/weapon/crowbar(src) + new /obj/item/weapon/crowbar/red(src) new /obj/item/weapon/extinguisher(src) new /obj/item/device/flashlight(src) new /obj/item/device/radio(src) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 3621ca01c8c..47b88f6af7e 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -1402,7 +1402,9 @@ src << "Here's your rightclick admin verbs back" src.verbs -= /client/proc/unstealthadmin + var/temp = src.deadchat src.update_admins(src.holder.rank) + src.deadchat = temp /client/proc/stealthadmin() set name = "Toggle admin verb visibility" diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 3d9dc9f9edf..a4aec223567 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -63,6 +63,8 @@ ":b" = "binary", ":a" = "alientalk", ":t" = "Syndicate", + ":d" = "Mining", + ":q" = "Cargo", //kinda localization -- rastaf0 //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. @@ -79,6 +81,8 @@ ":è" = "binary", ":ô" = "alientalk", ":å" = "Syndicate", + ":â" = "Mining", + ":é" = "Cargo", ) message_mode = keys[channel_prefix] diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index fb868f79b66..1a8b86fa36a 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1222,7 +1222,8 @@ var/obj/item/weapon/grab/G = src.l_hand if (!( L.container.Find(G.affecting) )) L.container += G.affecting - G.affecting.ret_grab(L, 1) + if (G.affecting) + G.affecting.ret_grab(L, 1) if (istype(src.r_hand, /obj/item/weapon/grab)) var/obj/item/weapon/grab/G = src.r_hand if (!( L.container.Find(G.affecting) )) diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index b7b66db72c8..275f637b5d2 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -232,6 +232,8 @@ mob/new_player AttemptLateSpawn("Warden", wardenMax) if ("29") AttemptLateSpawn("Shaft Miner", minerMax) + if ("30") + AttemptLateSpawn("Mime", mimeMax) if(!ready && href_list["preferences"]) preferences.process_link(src, href_list) @@ -376,6 +378,9 @@ mob/new_player if (IsJobAvailable("Clown",clownMax)) dat += "Clown
" + if (IsJobAvailable("Mime",mimeMax)) + dat += "Mime
" + if (IsJobAvailable("Chef",chefMax)) dat += "Chef
" diff --git a/code/setup.dm b/code/setup.dm index b1e928ac466..9d4f7003476 100644 --- a/code/setup.dm +++ b/code/setup.dm @@ -135,6 +135,7 @@ #define NOPOWER 2 #define POWEROFF 4 // tbd #define MAINT 8 // under maintaince +#define EMPED 16 // temporary broken by EMP pulse #define ENGINE_EJECT_Z 3 diff --git a/icons/changelog.html b/icons/changelog.html index 6647b9b4595..ba0ac8e8d06 100644 --- a/icons/changelog.html +++ b/icons/changelog.html @@ -30,7 +30,7 @@ Credits:
- Coders: TLE, NEO, Errorage, muskets, veryinky, Skie, Numbers, Agouri, Noka, Urist McDorf, Uhangi, Darem, Mport
+ Coders: TLE, NEO, Errorage, muskets, veryinky, Skie, Numbers, Agouri, Noka, Urist McDorf, Uhangi, Darem, Mport, rastaf0
Spriters: Agouri, Cheridan, Cruazy Guest, Deeaych, Deuryn, Matty406, Microwave, ShiftyEyesShady, Skie, Uhangi, Veyveyr
Sounds: Skie
Thanks to: CDK Station devs, GoonStation devs, the original SpaceStation developers and Erikat for the title image @@ -43,6 +43,11 @@
  • Rastaf0 updated:
      +
    • Added radio channels and headsets for miners (:h or :d ("diggers" lol)) and for cargo techs (:h or :q )
    • +
    • Added a personal headsets to HoP and QM.
    • +
    • Aliens now attack bots instead of opening control window.
    • +
    • All bots can be damaged and repaired.
    • +
    • All bots are effected to EMP now.
    • Atmos now starts with nitrous oxide in storage tank.
  • diff --git a/icons/obj/aibots.dmi b/icons/obj/aibots.dmi index f1cfac3305e9e88b2bd7ee14a2079c4608b3fee6..8651fa47b27545a16b8441e05d23bfe70581614c 100644 GIT binary patch delta 6568 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 G=>Gw@;U-1^ delta 6577 zcmW+)1yEGq7hVKGmXwfCK)Sn>?vjvH8l^j>;h{f5xT=wk!?wyzf2X=W!3R41`^D46EK3Z=Svf3MpVDmfi=#om!Ln%2ynyGd0p*ke<}~@8#rT=OfaH`- z$CFSizR#TMl04vMRA6>jeuDX)KoGKC9G*UxhF9KE?*Nek;d5 zjEj$9jDPcEDiS1k@?DwB7$i#cwY8>{Z7GyGUu@4&M=b(2iQ2 zM9oSS0(lIJ`|sv5(2W64((7ePwoWu}iBmz~$`r%nq&&^xK$we^{D(ISCK0EN9o~I(6)-GsS zhCzP7`902VH1aio?jX1mLsjCxdq9o=S}lJNz2%=vWxnL9soFj1A}3%kM{Y>`NqFX; zeCS|p%mDDmS6Q9zi=}`~q~vLU_=Fv+$Kw7h8X~B5a!3+p!cEC{DGbxuufkFv6wXzQaRjCS>b?* z=rQZNJJC8tIv$z0LCUL}7eq6KL`~fG#s@UarpPD-sc;^@!!Zyuj$B8fsn=Su$9l#D zK9Ahq1#k6_-R61coq4UEN!f=a9E_hpvGKTF;$J9sX=;UpDt9q;Hz2GFNv6iSn;ajp z?+=Q$!-M94Y7#lR<3JX63pwq1DWXc;0w%_U_pyj-HzcIh9Jk;c2KC;aAankljfAqj zJXMI`PkJ^Xm3Q#Gwj*aX7i0vR|kUq<>@E&X#15GJ1A<#v$uu6*Q{%f zv4AW*G6w37{GA_)U~yS^?FsQ^nV{pk_oW6o!@7V?s`9UZGZ=B^@xdEF|}Dm#RydA=-=gXggd$5 zG~Q=vdULz|W~8GW<-1ix(rklIb;ahJ$o6$4B{C-1eiRBlO3!$hrgm(#c|_Ljx{t-d z0dQ~_V3|}mw>7>ls&`}v`-EA2D^aeBa$wuf)0h0hx+^1*-YUmWkYF$v0CMa=6H+hD z%2u59&ECrC$}@6_zbQ6pa=4uv)Zm18_p9RTn#D18gQ(sRHHp;hoVk(JH3o`#nuj=r ztBt5N`tz$SS&E!GsudlYR`R&d@vZ^1q7W1%#Sz=HJnTm_lw3FFlZB?*Y=uU=F$R3p zEYWQ7**~GM#*tYOnRcUqpN2ZVDQE zlm-fa-g78$@v$w=W?SwB)CXA4>70*NAF;_0)fb14*S=A9UN%FslQ_bb8E%0foAgof zQnrllPiMMYpbDyX!k)@TDsX;eS23GqpyoF;5yjPA$5M$#AR_r=p_Y{@U%uq4Jb9grO@&}rl9;jxx#xs|a$#Y)I{~43 zFYrQ<(SzehjLcd?qpaS!HH3gnfJ@mt?wKDs>C%e_or>(D{_?yrJ zO{slorL0%1wC$$@{qqD!X?s3)M-&8&PdnmsBvKVJ<=+`S~VUcv_Zbqs)d443t-e{X+ zdJt$jZ`Bv#oQSHt!~2qUANJ>QNl*||Gi@ziVnK2QHj7KTdwiB28VnZ2&kh1@v7cz> zcBU1>1{-||5N9nE0FV*-AHDt1)}+JQE|y*j*;JWmjuh%?Q@9*|%=DTbF{LZvj8l_B zid=lSNriGfi|5*ZAsj!cW(H~>mRctWiP#W!AKWAanKbp!jnjd;x321(Ml|&$5?q#J zrZ1^nKi#AT?cvbV9dD!1ojx(k+*;slMqJdHqY*C{`m(y*8_1~NzIBh&&aEnTP7GH4 z_1AA_d7KXk*CijdT)!+j2UTQ(iFyu{xd)azWltBerN4Nb3-G_QI+@zuncio7pK!0< zhZH_Pl1yrHAIJUEPHgTm=TLUI7h?iZu(XkDGo&PljoI-N0dttj+de#lVRufoP)o8Y zge@vZtXloC1LUzDMnp)_J4=0VLF~VJQRgnsyNEMY?oCCkd^e#ZDR#=pCFB1`K7&De zpb!0@v5-dy6$3_dB7o@>H|q)29dYU+N;s+kO8W!L4JmJ&`=l zYt-JVvfc0he_dflm6UW6e*JSR*y$QSF5*OfI*HPM=+Ld4qoANj%E;23`QhCW8PgUf z`v({B&;D@B`iF~5IV_*@M{4mQzgd{dXEJQuuWFmv(LF>~FL6;O&5(B+b}emfh4cF( z;}c4?^ZJvejJW@O{M2FA9_#=EFBEOum1KAwm6J=QCXZq;=+n|oP|qGW%5Lr#AxnH` z!WIvl`!5vdz)vvPL^SDa2<@5(gEH9sJ_2Oc=nUe448h4vQEOJ6Crj@^|Ll+cXTRJV z#Fx3S(th;D5RM-gVHBsnqilZ_2Apa8UkQ=E}rF7|gQ`nQ}`TlJKu8@BbNjZU~ zq@to>Qf=gOeD-Mk&UD=nD#MiyzFMRQ<-|&=$BJRGm`kZPzX>t1F)t$Z03H%EF$SHx zgO&-TVzGFxKPQ`pExq<6jXpP>4+)biAT1+c37Ctj5;6IX|D0JX%96l(U+UP75BEQ; zI9O_91Un4X_m}gJYUC{MnY5i_(T-6vJPGVgds<$lo-{yt9sl*Bc=&Ya17CG;y z>>*rNwA;3PEAZxqC^#F)GP!|tn+g$cb%rf^miElH#|1n8Oha_fe(AUt)?I?(ba{E@5qhsnnX_3fm>Q zjC{#haG5*gp}*FWC3V>RV)Mq|)))HAxnV3$n`35Q#3(k55z zD-zlmdpiEYW>>4YHn)?rQ{y?rl049at*tKs6&-J)km>yk!s{~WYDnCQ}^~3$%E0PeS;ORQ7-(TnDQcg}zHY1$B9_%7WFfq03DA6EXyRp9h4^?VS z2LArPdgsP-W4HglMGaOO%>t*85hmGVHm9a6qWoZoxko`y5z^D53ug&?P{^~0_IAlUdU{(zxrOf|ee=*{ z=8?i#^B?oj$B3z93`tPUk|qt##n*J+00=c%Uj6><&xWuk#=)MOV7o3Z78^5aac^(X zqo=Yn6z*4jaHhS?`zdAzP}Joy<8UNjz*GY7j8wJJu;=1sP+EbGMd~NF70bKR%m)R{ zQTF)C4hBw&RV9+5!3q8!ww|I;uUOa<>Aus}eD_KtdGm;tT2nrhek7AGE7FXI5tzin zAtDs}5#oLU9$Eg97R3s$ns(4!6R)+5w}ds9tObaBYxN5vLeBgnA|m9}t?lq3DA#RY z?XfP&JN)46>x`!=nS~qaP*?FMl!C5{BI|ItvW11k*PwT^Yy_^B){XNt07~J8-@MYO9|YPCx(NDQtI>*#D4%&y$Ny{P?)<{(TxLi6 z`)I9z911bT;Zmf_O2b#5m2@F)53`kJ&S8FI-AxbXJR+>PQt+9=e)a;?yO8_33p9HZErcZ%6)p-a`%Q}90P#n(#C%+{1N%$3MgBiugN*c6{H zpYg|(g!1~k3>O8TqfMOVDt@$NvIz!FgLMOc=bIt*YfH`!f#z}FH|ShRzYN`z`k|sz z*O@BKx8@pN_?AniP#3XkeLpsJs>yZp$%P6Yo?+ET@7ml}CF`glDA*7Ud1J3u%@VJr z(fQWe_&xo~i^+Pth91e|4t*v-zeMx##s0iK8ijn1dB~_u(c|>xuefX3Z2u0~Ly}Nu zL=Tqm$S#1T$AK5S+RM8yoPzu%coV@BMPppgjq!10?_6kDPX_;{Gl@&4Cd$55-MSz& z;B%6>_mSS-(5~~oZC(;9Y1@s}fw1S(vi&=5efgU@<~$hHD-llGQST2)GX3NQ*O`Vq zJ{`}{Fbw^A*Uk)rk=J9ajIZ|L#*=yR--0X&fIW4yN?hWM=upCB<&xWk!nAJNF?KtW zDq7X!@_iVQW#D_Uf=N(RTDRy}eAtl@NgdbQM!R?s+ffSL;(1}uKAiRWz6ET)9B862 zv=65Wygv4G{66+)DS>V`-M6!KW+mxI7r}c5#%Jg(0T2jZU0q4u08POa^~1KHf$SRC zo?34S`uT2gtW}}f6H-?8c|$d{RB_vFAYWQ*-Xa9!)39EvmKEfnm4F8;C)Qd?=(E^G z)u@RTOtbS(06)K7X@`Ko!+GVy(+*Elf)S! z>tI!9Zogxa!mslnGeb8IHFE_Vq}Qea#!hRrL$Y;F&r}nheKg!Cqq7Q`!1{AlD+B+w z&i_Dwv#eF0s5qdupu+f{=@^^qZ%_fNUA7<-UIrs3&7KNU@I@eKrb||5yI&Qw6MX$B zI`Oib7imz(PI_PBpV(gOwb9{I=whi{PhmgbRw{W#!mIHrvVt=)NR`|$Q`ZVcz`Tm&Z2Uq{@w zT^5&>?KHk~QzOn!LGg<$sM*CryShx$d&;N=qZxG)nY1z$FyL{R`A)Oc zy%-(~m?(y>YN6XM4Yf~eU74-YJ>jOCU3U(* zxQ|=iWvfl0Wrz;W7b>l|f1vlFI{QJA&jEEZ+Bf2t_gAVnB*a_uo$VYmREX=7*D8rL z{OG&iLvw{V|F-lD^rP?rf4x-xQ;&Q3gFDU#>9LC~x}1`!Y+4EO0 zZz7@em4{}JPmMWk*#K;#2-198vkiW3z;*3;3LmSHp%?|1dTUglGoEyBME846o?&*$ zGYKv#(eC3V#o^&;Oso8N@WMInE9u~l;6|>H!8?+op*SjQws6ml8&QnbkA=KMFtY!R zDr@;}eE<_6T2J2aEXwOF14mVc_;6PlZx#Pn9B|8!O&j~<)ZB={PGVPvy*Edy8x}(p z^na%2_bUyMumO8Wf$`M*dGw@4FR%W?v;(Zo&E=}SDvlRwt~FQ+P>D>SM0{~E6c+NSqLy@|J*6TC1SobNEBq7u6h*v| zU2R$TtEJ)c5XzOFss2t_sno4gz1!sS4^@y_ca{d`>ySRG)(!c(5!NkXEXMj4Y}wdv zW1w@OQ6Z2J@TI~Ri*7+CsU<&t;|3!BpY3Yf*6#05#jm2TZ^W4DLad?%V#r%1@v?Pr zOm8nu^?VML>w&v{UJ%7D!Am;tG_j9Y-e?6WW(7+oK6&;Brq@QhFsjS~a?HVBRD{3V z)|Q9N5e^?8pM%-T=Y{(C$IObmzrMlOnh|T|9B?40m5%p%^!YLFWqTdO`&L+cL>4#4_{T`KxtVn!M<6|Gp9t<}(GgEs30J!NI%0JQhzpsC653ueGR7JpY zWmp89*S#KN{`|E2I;ZSTwn(oWBRXh+WL5v`^}b`48^Abm=EIu<1TO;(4d)N~L^gB3 zADa#9$6RYUS#Qyhdi#b8U(K;qHw%({<{!BU!koUy_NK&VY=YAZio3I>Fnt~A52{b0 zS>b?rR39vkkd7N=@?9l`C7kpTA_`M8w0zYr0DO0dkcndJ~8bp?;ys_4Vf;4Tt(>_@29j_t;g0q_K&|eDL z`$%2Zy37}5Q=m^Q1VMAL{kT0jnWf_tsU@1MM`luMZpfT1boI$OVrzsw3IQJkB!l@X z5By!WC+Ie9Y`tCz=CS96eC%ni_IDrW`qkO^b2nW*FZghFX=mAaWiOreNV2fUHnFgq z?uDA>`A4tWB>0dLL%~-7aGoXA{uS@+3%jatmQsQw_(@|%2+vFR$5uhh=5R~;b!Vn$ z^owoTfuISce~OuVgEGD}S(PW<&8aecRw|nIxID)vjC_B26l=3tHeHfheDH3oL;T0E zZqe^{ezO0*eQ7M!DVTQnEJKP<9kMcMQunjmdK{8|Kanf>Y8lA%yD&9w{e75)g!0uhm34?5th;gD$9QPT4u)JbdSYG;oV|L= z3}+``upd+U9#;6IITL1FpltazOar3uJUM_jzOax9(RFi{xL+$jK$nu|e{~X?9B~b9ICB^6T8SKeKjnDq*V#6zAd>9`i zriQMT@E7)@V~k4O$ifirh7hl(H*$*{>ILf-LcsGt)Ir!8o05Gi2IyuXzaz7Hk8iG% z>uT3oku%t37A_XJCbX@TG*slSqGOiqbk0bM&3D_U^W`Z~ZZ>A3M*=_1IosJ5buGmq z(a|z;2d6tiHRSITO@YCgFVIE!cOLUb!!GFt-Vr>FVm|>dsQ$9yT0GpH;F8N*8Ogmo af|4be-$7MPbO?hmOI=x8sYb!-)BgavdmD5B diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 24f78b1d3348b38b0e308efa157c1fd1a4552ae8..7b5aa3b599cfc8842a9610b84b5414124eae37e3 100644 GIT binary patch literal 15023 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 literal 19919 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 diff --git a/icons/obj/items.dmi b/icons/obj/items.dmi index 2c875cd0dfb5c2cd895663c48b32384147e2091a..498c0e7d151c6905fcf7039c74d429e822a6a3c0 100644 GIT binary patch delta 11034 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 A+yDRo delta 10819 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 Ys&dn5z+L(-7W8nbDrzaz$XP}H7Xp4I#{d8T diff --git a/icons/obj/radio.dmi b/icons/obj/radio.dmi new file mode 100644 index 0000000000000000000000000000000000000000..d066b144abb937632bafaba24812712dfd60f14e GIT binary patch literal 4323 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