From 25195208a96f821f34b30baea9d64da0b84fea12 Mon Sep 17 00:00:00 2001 From: Tastyfish Date: Mon, 21 Mar 2016 17:59:16 -0400 Subject: [PATCH 1/4] Ports tg simple_animal bots --- .../MetaStation/MetaStation.v41A.II.dmm | 48 +- _maps/map_files/MetaStation/z5.dmm | 2 +- _maps/map_files/RandomZLevels/evil_santa.dmm | 2 +- .../RandomZLevels/stationCollision.dmm | 2 +- _maps/map_files/cyberiad/cyberiad.dmm | 68 +- _maps/map_files/cyberiad/z5.dmm | 2 +- _maps/map_files/cyberiad/z6.dmm | 2 +- code/__DEFINES/bots.dm | 38 + code/__DEFINES/hud.dm | 3 +- code/_globalvars/lists/mobs.dm | 1 + code/_globalvars/lists/objects.dm | 6 +- code/_onclick/hud/_defines.dm | 6 +- code/_onclick/hud/bot.dm | 70 + code/_onclick/hud/hud.dm | 4 + code/controllers/Processes/animal.dm | 37 + code/datums/cargoprofile.dm | 2 +- code/datums/spells/conjure.dm | 4 +- code/datums/supplypacks.dm | 10 +- code/datums/wires/mulebot.dm | 6 +- code/defines/procs/AStar.dm | 63 +- code/game/asteroid.dm | 2 +- code/game/data_huds.dm | 43 +- code/game/gamemodes/blob/blobs/blob_mobs.dm | 9 +- code/game/gamemodes/blob/theblob.dm | 5 + .../gamemodes/changeling/powers/tiny_prick.dm | 2 +- .../gamemodes/miniantags/bot_swarm/swarmer.dm | 11 +- .../gamemodes/miniantags/guardian/guardian.dm | 9 +- code/game/gamemodes/miniantags/morph/morph.dm | 9 +- .../miniantags/revenant/revenant_abilities.dm | 6 +- .../miniantags/slaughter/slaughter.dm | 9 +- code/game/gamemodes/vampire/vampire_powers.dm | 2 +- code/game/jobs/access.dm | 24 +- code/game/machinery/bots/bots.dm | 801 ----------- code/game/machinery/bots/ed209bot.dm | 696 ---------- code/game/machinery/bots/mulebot.dm | 942 ------------- code/game/machinery/bots/secbot.dm | 502 ------- code/game/machinery/computer/medical.dm | 2 +- code/game/machinery/doors/airlock.dm | 2 +- code/game/machinery/doors/door.dm | 20 +- code/game/machinery/doors/windowdoor.dm | 16 +- code/game/machinery/navbeacon.dm | 108 +- .../effects/decals/Cleanable/tracks.dm | 4 +- code/game/objects/objs.dm | 3 + code/game/objects/structures/grille.dm | 6 + code/game/objects/structures/morgue.dm | 19 +- code/game/objects/structures/tables_racks.dm | 12 + code/game/objects/structures/window.dm | 8 + code/modules/computer3/computers/medical.dm | 2 +- code/modules/crafting/recipes.dm | 10 +- code/modules/events/ion_storm.dm | 4 +- code/modules/mining/equipment_locker.dm | 9 +- code/modules/mob/hear_say.dm | 4 +- code/modules/mob/living/carbon/alien/alien.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 6 +- code/modules/mob/living/silicon/ai/ai.dm | 13 +- code/modules/mob/living/simple_animal/bees.dm | 270 ++-- .../mob/living/simple_animal/bot/bot.dm | 1006 ++++++++++++++ .../mob/living/simple_animal/bot}/cleanbot.dm | 509 +++---- .../living/simple_animal/bot/construction.dm | 480 +++++++ .../mob/living/simple_animal/bot/ed209bot.dm | 569 ++++++++ .../mob/living/simple_animal/bot/emote.dm | 119 ++ .../mob/living/simple_animal/bot}/floorbot.dm | 963 ++++++------- .../mob/living/simple_animal/bot}/medbot.dm | 1222 ++++++++--------- .../mob/living/simple_animal/bot/mulebot.dm | 869 ++++++++++++ .../mob/living/simple_animal/bot/secbot.dm | 462 +++++++ .../mob/living/simple_animal/constructs.dm | 11 +- .../living/simple_animal/friendly/corgi.dm | 27 +- .../living/simple_animal/friendly/mouse.dm | 20 +- .../mob/living/simple_animal/friendly/pug.dm | 4 +- .../simple_animal/friendly/spiderbot.dm | 4 +- .../mob/living/simple_animal/hostile/alien.dm | 49 +- .../mob/living/simple_animal/hostile/bat.dm | 10 +- .../mob/living/simple_animal/hostile/bear.dm | 12 +- .../mob/living/simple_animal/hostile/carp.dm | 9 +- .../living/simple_animal/hostile/faithless.dm | 9 +- .../living/simple_animal/hostile/hivebot.dm | 72 +- .../living/simple_animal/hostile/hostile.dm | 33 +- .../mob/living/simple_animal/hostile/mimic.dm | 9 +- .../simple_animal/hostile/mining_mobs.dm | 11 +- .../living/simple_animal/hostile/pirate.dm | 9 +- .../simple_animal/hostile/poison_bees.dm | 9 +- .../simple_animal/hostile/retaliate/clown.dm | 9 - .../simple_animal/hostile/retaliate/drone.dm | 9 +- .../simple_animal/hostile/retaliate/pet.dm | 5 +- .../simple_animal/hostile/retaliate/undead.dm | 27 +- .../living/simple_animal/hostile/russian.dm | 8 - .../simple_animal/hostile/spaceworms.dm | 4 +- .../living/simple_animal/hostile/statue.dm | 24 +- .../living/simple_animal/hostile/syndicate.dm | 35 +- .../mob/living/simple_animal/hostile/tree.dm | 10 +- .../simple_animal/hostile/winter_mobs.dm | 9 +- .../mob/living/simple_animal/parrot.dm | 17 +- code/modules/mob/living/simple_animal/pony.dm | 4 +- .../modules/mob/living/simple_animal/shade.dm | 4 +- .../mob/living/simple_animal/simple_animal.dm | 89 +- code/modules/mob/mob.dm | 12 +- code/modules/mob/mob_defines.dm | 1 + code/modules/mob/mob_helpers.dm | 5 + code/modules/nano/interaction/default.dm | 6 + code/modules/nano/nanoui.dm | 2 +- code/modules/pda/cart_apps.dm | 8 +- code/modules/pda/radio.dm | 119 +- .../reagent_containers/glass_containers.dm | 2 +- code/modules/shuttle/supply.dm | 15 +- icons/effects/fluidtracks.dmi | Bin 3978 -> 3990 bytes icons/mob/hud.dmi | Bin 12050 -> 12564 bytes icons/mob/screen1_bot.dmi | Bin 0 -> 20085 bytes icons/mob/screen1_robot.dmi | Bin 29786 -> 29952 bytes nano/templates/pda_mule.tmpl | 2 +- paradise.dme | 20 +- 110 files changed, 5551 insertions(+), 5360 deletions(-) create mode 100644 code/__DEFINES/bots.dm create mode 100644 code/_onclick/hud/bot.dm create mode 100644 code/controllers/Processes/animal.dm delete mode 100644 code/game/machinery/bots/bots.dm delete mode 100644 code/game/machinery/bots/ed209bot.dm delete mode 100644 code/game/machinery/bots/mulebot.dm delete mode 100644 code/game/machinery/bots/secbot.dm create mode 100644 code/modules/mob/living/simple_animal/bot/bot.dm rename code/{game/machinery/bots => modules/mob/living/simple_animal/bot}/cleanbot.dm (56%) create mode 100644 code/modules/mob/living/simple_animal/bot/construction.dm create mode 100644 code/modules/mob/living/simple_animal/bot/ed209bot.dm create mode 100644 code/modules/mob/living/simple_animal/bot/emote.dm rename code/{game/machinery/bots => modules/mob/living/simple_animal/bot}/floorbot.dm (58%) rename code/{game/machinery/bots => modules/mob/living/simple_animal/bot}/medbot.dm (57%) create mode 100644 code/modules/mob/living/simple_animal/bot/mulebot.dm create mode 100644 code/modules/mob/living/simple_animal/bot/secbot.dm create mode 100644 icons/mob/screen1_bot.dmi diff --git a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm index d1d713fcf26..3fc5ba2f9e1 100644 --- a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm +++ b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm @@ -378,7 +378,7 @@ "ahn" = (/obj/machinery/atmospherics/pipe/simple/hidden,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "aho" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/machinery/recharger{pixel_y = 0},/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "ahp" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/machinery/cell_charger,/obj/item/weed_extract,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) -"ahq" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/machinery/bot/floorbot{on = 0},/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) +"ahq" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/mob/living/simple_animal/bot/floorbot{on = 0},/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "ahr" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/item/broken_device,/obj/item/robot_parts/chest,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "ahs" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/item/weapon/pickaxe,/obj/item/weapon/storage/firstaid/toxin,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "aht" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/item/weapon/scalpel,/obj/item/stack/cable_coil,/obj/item/weapon/storage/firstaid/regular,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) @@ -511,7 +511,7 @@ "ajQ" = (/obj/structure/table,/obj/item/weapon/paper_bin{pixel_x = -3; pixel_y = 7},/turf/simulated/floor/plasteel{tag = "icon-vault"; icon_state = "vault"},/area/security/prison) "ajR" = (/obj/machinery/flasher/portable,/obj/machinery/firealarm{dir = 8; pixel_x = -26; pixel_y = 0},/obj/machinery/flasher/portable,/turf/simulated/floor/plasteel{tag = "icon-vault (NORTH)"; icon_state = "vault"; dir = 1},/area/security/warden) "ajS" = (/obj/structure/rack,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/item/weapon/gun/projectile/shotgun/riot{pixel_x = -3; pixel_y = 3},/obj/item/weapon/gun/projectile/shotgun/riot{pixel_x = 2; pixel_y = -1},/turf/simulated/floor/plasteel{tag = "icon-vault (NORTH)"; icon_state = "vault"; dir = 1},/area/security/warden) -"ajT" = (/obj/machinery/bot/secbot{health = 35; name = "Securitron #359"; on = 0},/turf/simulated/floor/plasteel{tag = "icon-vault (WEST)"; icon_state = "vault"; dir = 8},/area/security/warden) +"ajT" = (/mob/living/simple_animal/bot/secbot{health = 35; name = "Securitron #359"; on = 0},/turf/simulated/floor/plasteel{tag = "icon-vault (WEST)"; icon_state = "vault"; dir = 8},/area/security/warden) "ajU" = (/obj/structure/rack,/obj/item/weapon/gun/energy/gun{pixel_x = -3; pixel_y = 3},/obj/item/weapon/gun/energy/gun,/obj/item/weapon/gun/energy/gun{pixel_x = 3; pixel_y = -3},/turf/simulated/floor/plasteel{tag = "icon-vault (EAST)"; icon_state = "vault"; dir = 4},/area/security/warden) "ajV" = (/obj/structure/rack,/obj/item/clothing/suit/armor/bulletproof,/obj/item/clothing/head/helmet/alt,/obj/item/clothing/suit/armor/bulletproof,/obj/item/clothing/head/helmet/alt,/obj/item/clothing/suit/armor/bulletproof,/obj/item/clothing/head/helmet/alt,/obj/structure/extinguisher_cabinet{pixel_x = 27; pixel_y = 0},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/security/warden) "ajW" = (/obj/effect/spawner/window/reinforced{useFull = 1; tag = "fullReinWin"},/obj/structure/cable/yellow,/obj/machinery/door/poddoor/preopen{id_tag = "hosprivacy"; name = "privacy shutters"},/turf/simulated/floor/plating,/area/security/hos) @@ -951,7 +951,7 @@ "aso" = (/obj/structure/table,/obj/item/weapon/folder/red,/obj/item/weapon/book/manual/security_space_law{pixel_x = -3; pixel_y = 5},/obj/item/stack/packageWrap,/turf/simulated/floor/plasteel,/area/security/main) "asp" = (/turf/simulated/floor/plasteel{icon_state = "red"; dir = 4},/area/security/main) "asq" = (/obj/machinery/door/window/eastright{base_state = "left"; dir = 8; icon_state = "left"; name = "Security Delivery"; req_access_txt = "1"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/security/main) -"asr" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "Security"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/security/main) +"asr" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "Security"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/security/main) "ass" = (/obj/structure/cable/yellow{d1 = 1; d2 = 4; icon_state = "1-4"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 5},/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/turf/simulated/floor/plating{icon_state = "warnplate"; dir = 9},/area/maintenance/fore) "ast" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plating,/area/maintenance/fore) "asu" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plating{tag = "icon-platingdmg2"; icon_state = "platingdmg2"},/area/maintenance/fore) @@ -987,7 +987,7 @@ "asY" = (/obj/structure/rack,/obj/item/weapon/tank/jetpack/oxygen,/obj/item/weapon/tank/air,/obj/item/clothing/mask/breath,/obj/item/clothing/shoes/magboots,/obj/machinery/camera{c_tag = "Engineering Secure Storage South"; dir = 4; network = list("SS13")},/obj/item/clothing/suit/space/rig/security,/obj/item/clothing/head/helmet/space/rig/security,/turf/simulated/floor/plasteel,/area/security/podbay) "asZ" = (/turf/simulated/floor/engine,/area/security/podbay) "ata" = (/obj/effect/spawner/lootdrop/maintenance,/turf/simulated/floor/plating/airless,/area/space) -"atb" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=1"; freq = 1400; location = "Disposals"},/obj/structure/plasticflaps{opacity = 0},/obj/machinery/conveyor{dir = 2; id = "garbage"},/obj/machinery/door/window/northright{dir = 2; name = "delivery door"; pixel_y = 0; req_access_txt = "31"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plating,/area/maintenance/disposal) +"atb" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 1; location = "Disposals"},/obj/structure/plasticflaps{opacity = 0},/obj/machinery/conveyor{dir = 2; id = "garbage"},/obj/machinery/door/window/northright{dir = 2; name = "delivery door"; pixel_y = 0; req_access_txt = "31"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plating,/area/maintenance/disposal) "atc" = (/obj/machinery/door/airlock/maintenance{name = "Disposal Conveyor Access"; req_access_txt = "12"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plating,/area/maintenance/disposal) "atd" = (/obj/structure/sign/securearea{name = "\improper STAY CLEAR HEAVY MACHINERY"; pixel_y = 0},/turf/simulated/wall,/area/maintenance/disposal) "ate" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 8},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plating,/area/maintenance/fpmaint2{name = "Port Maintenance"}) @@ -999,7 +999,7 @@ "atk" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plating,/area/maintenance/fpmaint2{name = "Port Maintenance"}) "atl" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 6},/obj/structure/disposalpipe/segment{dir = 4; icon_state = "pipe-c"},/obj/structure/cable/yellow{d1 = 2; d2 = 4; icon_state = "2-4"},/turf/simulated/floor/plating,/area/maintenance/fore) "atm" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 9},/obj/structure/disposalpipe/segment{dir = 8; icon_state = "pipe-c"},/obj/structure/cable/yellow{d1 = 1; d2 = 8; icon_state = "1-8"},/turf/simulated/floor/plating,/area/maintenance/fore) -"atn" = (/obj/machinery/bot/cleanbot{name = "Mopfficer Sweepsky"; on = 0},/turf/simulated/floor/plating{tag = "icon-platingdmg1"; icon_state = "platingdmg1"},/area/maintenance/fore) +"atn" = (/mob/living/simple_animal/bot/cleanbot{name = "Mopfficer Sweepsky"; on = 0},/turf/simulated/floor/plating{tag = "icon-platingdmg1"; icon_state = "platingdmg1"},/area/maintenance/fore) "ato" = (/obj/item/weapon/storage/box/lights/mixed,/obj/item/device/flashlight{pixel_x = 1; pixel_y = 5},/obj/machinery/light/small{dir = 4},/turf/simulated/floor/plating,/area/maintenance/fore) "atp" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/extinguisher_cabinet{pixel_x = -27; pixel_y = 0},/turf/simulated/floor/plasteel{icon_state = "redcorner"; dir = 1},/area/security/brig) "atq" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel,/area/security/brig) @@ -1455,7 +1455,7 @@ "aBY" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plating{tag = "icon-platingdmg1"; icon_state = "platingdmg1"},/area/maintenance/starboard) "aBZ" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/effect/spawner/lootdrop{loot = list(/obj/structure/grille,/obj/structure/grille,/obj/structure/grille,/obj/structure/grille,/obj/structure/grille,/obj/item/weapon/cigbutt,/obj/item/trash/cheesie,/obj/item/trash/candy,/obj/item/trash/chips,/obj/item/trash/pistachios,/obj/item/trash/plate,/obj/item/trash/popcorn,/obj/item/trash/raisins,/obj/item/trash/sosjerky,/obj/item/trash/syndi_cakes); name = "maint grille or trash spawner"},/turf/simulated/floor/plating,/area/maintenance/starboard) "aCa" = (/obj/structure/disposalpipe/segment{dir = 8; icon_state = "pipe-c"},/obj/structure/cable/yellow{d1 = 1; d2 = 8; icon_state = "1-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 9},/turf/simulated/floor/plating,/area/maintenance/starboard) -"aCb" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=4"; freq = 1400; location = "Engineering"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/starboard) +"aCb" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 4; location = "Engineering"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/starboard) "aCc" = (/obj/machinery/door/window/southright{dir = 4; name = "Engineering Deliveries"; req_access_txt = "10"; req_one_access_txt = "0"},/turf/simulated/floor/plasteel{icon_state = "delivery"; name = "floor"},/area/engine/engineering) "aCd" = (/obj/structure/sign/securearea{pixel_y = 32},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel{dir = 9; icon_state = "warning"},/area/engine/engineering) "aCe" = (/obj/structure/disposalpipe/segment,/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 5},/turf/simulated/floor/plasteel{dir = 1; icon_state = "warning"},/area/engine/engineering) @@ -1830,7 +1830,7 @@ "aJj" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{dir = 1; initialize_directions = 11},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "aJk" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "aJl" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) -"aJm" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/unary/vent_pump{dir = 2; on = 1},/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=1-BrigCells"; location = "0-SecurityDesk"},/obj/machinery/bot/secbot/beepsky{desc = "It's Officer Beepsky! Powered by a potato and a shot of whiskey, and with a sturdier reinforced chassis, too. "; health = 45; maxhealth = 45; name = "Officer Beepsky"},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) +"aJm" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/unary/vent_pump{dir = 2; on = 1},/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=1-BrigCells"; location = "0-SecurityDesk"},/mob/living/simple_animal/bot/secbot/beepsky,/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "aJn" = (/obj/structure/cable/yellow{d1 = 1; d2 = 8; icon_state = "1-8"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 9},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "aJo" = (/obj/structure/table/reinforced,/obj/machinery/door/window/westleft{base_state = "right"; dir = 8; icon_state = "right"; name = "Outer Window"; req_access_txt = "0"},/obj/machinery/door/window/brigdoor{dir = 4; name = "Security Desk"; req_access_txt = "1"},/obj/structure/cable/yellow{d1 = 1; d2 = 4; icon_state = "1-4"},/obj/structure/cable/yellow{d1 = 2; d2 = 4; icon_state = "2-4"},/obj/item/device/radio/off,/obj/machinery/door/poddoor/shutters/preopen{id_tag = "briglockdown"; name = "brig shutters"},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/security/brig) "aJp" = (/obj/structure/stool/bed/chair/office/dark{dir = 8},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/security/brig) @@ -2101,7 +2101,7 @@ "aOu" = (/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel,/area/quartermaster/storage) "aOv" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{dir = 8; initialize_directions = 11},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel{tag = "icon-warningcorner (WEST)"; icon_state = "warningcorner"; dir = 8},/area/quartermaster/storage) "aOw" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 10},/obj/structure/window/reinforced{dir = 1; pixel_y = 1},/turf/simulated/floor/plasteel{dir = 8; icon_state = "loadingarea"; tag = "loading"},/area/quartermaster/storage) -"aOx" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "QM #1"},/obj/machinery/bot/mulebot{beacon_freq = 1400; home_destination = "QM #1"; suffix = "#1"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/door/window/northleft,/obj/machinery/light{dir = 4},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) +"aOx" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "QM #1"},/mob/living/simple_animal/bot/mulebot{home_destination = "QM #1"; suffix = "#1"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/door/window/northleft,/obj/machinery/light{dir = 4},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) "aOy" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{req_access_txt = 1},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/door/airlock/maintenance{req_access_txt = "0"; req_one_access_txt = "12;63;48;50"},/turf/simulated/floor/plating,/area/maintenance/fpmaint2{name = "Port Maintenance"}) "aOz" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/door/airlock/maintenance{req_access_txt = "0"; req_one_access_txt = "12;63;48;50"},/turf/simulated/floor/plating,/area/maintenance/fpmaint2{name = "Port Maintenance"}) "aOA" = (/turf/simulated/wall,/area/storage/primary) @@ -2172,7 +2172,7 @@ "aPN" = (/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/effect/spawner/lootdrop/maintenance,/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) "aPO" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{dir = 4; initialize_directions = 11},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel,/area/quartermaster/storage) "aPP" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel{dir = 8; icon_state = "loadingarea"; tag = "loading"},/area/quartermaster/storage) -"aPQ" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "QM #2"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) +"aPQ" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "QM #2"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) "aPR" = (/obj/machinery/door/window/northleft{dir = 8; name = "MuleBot Supply Access"; req_access_txt = "50"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plating,/area/maintenance/fpmaint2{name = "Port Maintenance"}) "aPS" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 5},/obj/structure/cable/yellow{d1 = 1; d2 = 4; icon_state = "1-4"},/turf/simulated/floor/plating{tag = "icon-warnplate (NORTHWEST)"; icon_state = "warnplate"; dir = 9},/area/maintenance/fpmaint2{name = "Port Maintenance"}) "aPT" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/turf/simulated/floor/plating,/area/maintenance/fpmaint2{name = "Port Maintenance"}) @@ -2252,7 +2252,7 @@ "aRp" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{req_access_txt = 1},/turf/simulated/floor/plasteel,/area/quartermaster/storage) "aRq" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/effect/landmark/start{name = "Cargo Technician"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel,/area/quartermaster/storage) "aRr" = (/obj/machinery/atmospherics/unary/vent_scrubber{dir = 1; on = 1; scrub_N2O = 0; scrub_Toxins = 0},/turf/simulated/floor/plasteel{dir = 8; icon_state = "loadingarea"; tag = "loading"},/area/quartermaster/storage) -"aRs" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "QM #3"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/bot/mulebot{home_destination = "QM #3"; suffix = "#3"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) +"aRs" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "QM #3"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/mob/living/simple_animal/bot/mulebot{home_destination = "QM #3"; suffix = "#3"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) "aRt" = (/obj/structure/mirror{pixel_x = -28},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/shower{tag = "icon-shower (EAST)"; icon_state = "shower"; dir = 4},/obj/structure/curtain/open/shower,/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/crew_quarters/locker/locker_toilet{name = "\improper Restrooms"}) "aRu" = (/turf/simulated/floor/plasteel,/area/storage/primary) "aRv" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 5},/turf/simulated/floor/plasteel,/area/storage/primary) @@ -2318,7 +2318,7 @@ "aSD" = (/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) "aSE" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel{dir = 2; icon_state = "warningcorner"; tag = "icon-warningcorner (EAST)"},/area/quartermaster/storage) "aSF" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/window/reinforced,/turf/simulated/floor/plasteel{dir = 8; icon_state = "loadingarea"; tag = "loading"},/area/quartermaster/storage) -"aSG" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "QM #4"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/door/window/southleft,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) +"aSG" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "QM #4"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/door/window/southleft,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) "aSH" = (/obj/effect/spawner/window/reinforced{useFull = 1; tag = "fullReinWin"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plating,/area/quartermaster/qm) "aSI" = (/obj/structure/closet/secure_closet/quartermaster,/obj/machinery/atmospherics/unary/vent_scrubber{on = 1; scrub_N2O = 0; scrub_Toxins = 0},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{dir = 9; icon_state = "brown"},/area/quartermaster/qm) "aSJ" = (/obj/machinery/camera/autoname{dir = 2; network = list("SS13")},/obj/machinery/hologram/holopad,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 10},/obj/machinery/power/apc{dir = 1; name = "Quartermaster's Office APC"; pixel_x = 0; pixel_y = 30},/obj/structure/cable/yellow{d2 = 2; icon_state = "0-2"},/turf/simulated/floor/plasteel{dir = 1; icon_state = "brown"},/area/quartermaster/qm) @@ -2392,7 +2392,7 @@ "aTZ" = (/obj/effect/landmark/start{name = "Quartermaster"},/obj/structure/disposalpipe/segment{dir = 2; icon_state = "pipe-c"},/obj/structure/stool/bed/chair/office/dark{dir = 4},/turf/simulated/floor/plasteel,/area/quartermaster/qm) "aUa" = (/obj/structure/table,/obj/item/weapon/folder/yellow,/obj/item/weapon/pen{pixel_x = 4; pixel_y = 4},/obj/item/weapon/pen/red,/obj/machinery/requests_console{department = "Cargo Bay"; departmentType = 2; pixel_x = 32; pixel_y = 0},/turf/simulated/floor/plasteel,/area/quartermaster/qm) "aUb" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/turf/simulated/floor/plating{icon_state = "warnplate"; dir = 4},/area/maintenance/fpmaint2{name = "Port Maintenance"}) -"aUc" = (/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery;dir=4"; freq = 1400; location = "Tool Storage"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/storage/primary) +"aUc" = (/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 4; location = "Tool Storage"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/storage/primary) "aUd" = (/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/storage/primary) "aUe" = (/obj/structure/table,/obj/item/weapon/weldingtool,/obj/item/weapon/crowbar,/obj/item/stack/packageWrap,/obj/item/stack/packageWrap,/obj/item/stack/packageWrap,/obj/item/stack/packageWrap,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel{dir = 2; icon_state = "browncorner"},/area/storage/primary) "aUf" = (/obj/structure/table/woodentable,/obj/item/weapon/book/manual/security_space_law,/obj/item/weapon/book/manual/security_space_law,/obj/machinery/computer/security/telescreen{desc = "Used for watching Prison Wing holding areas."; name = "Prison Monitor"; network = list("Prison"); pixel_x = 0; pixel_y = 30},/obj/item/weapon/cartridge/lawyer,/obj/item/weapon/pen/multi,/turf/simulated/floor/wood,/area/lawoffice) @@ -3633,7 +3633,7 @@ "brS" = (/obj/structure/reagent_dispensers/watertank,/turf/simulated/floor/plating,/area/maintenance/maintcentral{name = "Central Maintenance"}) "brT" = (/obj/machinery/atmospherics/unary/portables_connector{dir = 1},/obj/machinery/portable_atmospherics/canister/air,/turf/simulated/floor/plating,/area/maintenance/maintcentral{name = "Central Maintenance"}) "brU" = (/obj/item/device/radio/off,/turf/simulated/floor/plating{icon_state = "warnplate"; dir = 4},/area/maintenance/maintcentral{name = "Central Maintenance"}) -"brV" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=4"; freq = 1400; location = "Bridge"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/maintcentral{name = "Central Maintenance"}) +"brV" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 4; location = "Bridge"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/maintcentral{name = "Central Maintenance"}) "brW" = (/obj/machinery/door/window/westleft{dir = 4; name = "Bridge Deliveries"; req_access_txt = "19"},/obj/machinery/door/poddoor/preopen{id_tag = "bridge blast"; name = "bridge blast door"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/bridge) "brX" = (/obj/structure/closet/crate{icon_state = "crateopen"; opened = 1},/obj/effect/spawner/lootdrop/maintenance,/turf/simulated/floor/plasteel{dir = 8; icon_state = "brown"},/area/hallway/primary/port) "brY" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 6},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/bridge) @@ -3762,7 +3762,7 @@ "bur" = (/obj/structure/cable/yellow{d1 = 1; d2 = 4; icon_state = "1-4"},/obj/structure/cable/yellow{d1 = 2; d2 = 4; icon_state = "2-4"},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/bridge) "bus" = (/obj/structure/sink/kitchen{pixel_y = 28},/turf/simulated/floor/wood,/area/crew_quarters/bar) "but" = (/obj/machinery/door/window/southleft{base_state = "left"; dir = 2; icon_state = "left"; name = "Bar Delivery"; req_access_txt = "25"},/obj/structure/window/reinforced{dir = 8},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/crew_quarters/bar) -"buu" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=1"; freq = 1400; location = "Bar"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/crew_quarters/bar) +"buu" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 1; location = "Bar"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/crew_quarters/bar) "buv" = (/obj/item/weapon/storage/box/lights/mixed,/obj/effect/spawner/lootdrop/maintenance,/turf/simulated/floor/plating,/area/maintenance/starboard) "buw" = (/obj/effect/spawner/lootdrop{loot = list(/obj/structure/grille,/obj/structure/grille,/obj/structure/grille,/obj/structure/grille,/obj/structure/grille,/obj/item/weapon/cigbutt,/obj/item/trash/cheesie,/obj/item/trash/candy,/obj/item/trash/chips,/obj/item/trash/pistachios,/obj/item/trash/plate,/obj/item/trash/popcorn,/obj/item/trash/raisins,/obj/item/trash/sosjerky,/obj/item/trash/syndi_cakes); name = "maint grille or trash spawner"},/turf/simulated/floor/plating{tag = "icon-panelscorched"; icon_state = "panelscorched"},/area/maintenance/starboard) "bux" = (/obj/structure/disposalpipe/segment,/turf/simulated/floor/plating{icon_state = "warnplate"; dir = 1},/area/maintenance/starboard) @@ -4669,7 +4669,7 @@ "bLO" = (/obj/machinery/atmospherics/unary/portables_connector{dir = 4},/obj/machinery/portable_atmospherics/scrubber,/obj/machinery/light/small{dir = 8},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/window/reinforced,/turf/simulated/floor/plasteel{dir = 8; icon_state = "escape"},/area/hallway/primary/starboard) "bLP" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{dir = 4; initialize_directions = 11},/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/turf/simulated/floor/plasteel{dir = 2; icon_state = "neutralcorner"},/area/hallway/primary/starboard) "bLQ" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{icon_state = "caution"; dir = 4},/area/hallway/primary/starboard) -"bLR" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery;dir=4"; freq = 1400; location = "Atmospherics"},/obj/machinery/door/poddoor{density = 0; icon_state = "pdoor0"; id_tag = "atmos"; name = "Atmos Blast Door"; opacity = 0},/turf/simulated/floor/plasteel{icon_state = "delivery"; name = "floor"},/area/atmos) +"bLR" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 4; location = "Atmospherics"},/obj/machinery/door/poddoor{density = 0; icon_state = "pdoor0"; id_tag = "atmos"; name = "Atmos Blast Door"; opacity = 0},/turf/simulated/floor/plasteel{icon_state = "delivery"; name = "floor"},/area/atmos) "bLS" = (/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{dir = 4; icon_state = "loadingarea"; tag = "loading"},/area/atmos) "bLT" = (/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{dir = 2; icon_state = "neutralcorner"},/area/atmos) "bLU" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel{dir = 2; icon_state = "neutralcorner"},/area/atmos) @@ -5452,7 +5452,7 @@ "caR" = (/obj/structure/closet/crate/hydroponics,/obj/item/weapon/shovel/spade,/obj/item/weapon/wrench,/obj/item/weapon/screwdriver,/obj/item/weapon/reagent_containers/glass/bucket,/obj/item/weapon/minihoe,/turf/simulated/floor/plasteel{icon_state = "warning"},/area/hydroponics) "caS" = (/obj/structure/closet{name = "spare parts locker"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 5},/obj/item/weapon/rack_parts,/obj/item/weapon/rack_parts,/obj/item/weapon/wrench,/obj/structure/window/reinforced{dir = 4},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/crew_quarters/kitchen) "caT" = (/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/door/window/eastright{dir = 1; name = "Kitchen Delivery"; req_access_txt = "28"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/crew_quarters/kitchen) -"caU" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "Kitchen"},/obj/structure/plasticflaps{opacity = 1},/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/crew_quarters/kitchen) +"caU" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "Kitchen"},/obj/structure/plasticflaps{opacity = 1},/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/crew_quarters/kitchen) "caV" = (/obj/structure/disposalpipe/sortjunction{dir = 2; sortType = 20},/obj/machinery/atmospherics/pipe/simple/hidden/supply{req_access_txt = 1},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plating{tag = "icon-warnplate (WEST)"; icon_state = "warnplate"; dir = 8},/area/maintenance/starboard) "caW" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 10},/obj/structure/cable/yellow{d1 = 1; d2 = 4; icon_state = "1-4"},/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/turf/simulated/floor/plating,/area/maintenance/starboard) "caX" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/structure/cable/yellow{d1 = 1; d2 = 4; icon_state = "1-4"},/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 5},/turf/simulated/floor/plating,/area/maintenance/starboard) @@ -5721,7 +5721,7 @@ "cga" = (/obj/machinery/light/small{dir = 1},/obj/structure/mopbucket,/obj/item/weapon/mop,/turf/simulated/floor/plating,/area/maintenance/aft{name = "Aft Maintenance"}) "cgb" = (/obj/item/weapon/storage/toolbox/emergency,/obj/item/weapon/hand_labeler,/obj/effect/spawner/lootdrop/maintenance,/turf/simulated/floor/plating,/area/maintenance/aft{name = "Aft Maintenance"}) "cgc" = (/obj/item/weapon/cigbutt,/obj/structure/disposalpipe/segment{dir = 4; icon_state = "pipe-c"},/turf/simulated/floor/plating{icon_state = "warnplate"; dir = 4},/area/maintenance/aft{name = "Aft Maintenance"}) -"cgd" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=4"; freq = 1400; location = "Medbay"},/obj/structure/plasticflaps{opacity = 1},/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/door/window/northleft{dir = 8; name = "MuleBot Access"; req_access_txt = "50"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/medical/medbay2{name = "Medbay Storage"}) +"cgd" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 4; location = "Medbay"},/obj/structure/plasticflaps{opacity = 1},/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/door/window/northleft{dir = 8; name = "MuleBot Access"; req_access_txt = "50"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/medical/medbay2{name = "Medbay Storage"}) "cge" = (/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{dir = 8; icon_state = "whiteblue"},/area/medical/medbay2{name = "Medbay Storage"}) "cgf" = (/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/medbay2{name = "Medbay Storage"}) "cgg" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 2; on = 1},/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/medbay2{name = "Medbay Storage"}) @@ -5849,7 +5849,7 @@ "ciy" = (/obj/item/weapon/wrench,/obj/item/clothing/suit/apron,/obj/item/clothing/accessory/armband/hydro,/obj/structure/table/glass,/turf/simulated/floor/plasteel{dir = 9; icon_state = "warning"},/area/hydroponics) "ciz" = (/obj/item/weapon/reagent_containers/spray/plantbgone{pixel_x = 0; pixel_y = 3},/obj/item/weapon/reagent_containers/spray/plantbgone{pixel_x = 8; pixel_y = 8},/obj/item/weapon/reagent_containers/spray/plantbgone{pixel_x = 13; pixel_y = 5},/obj/item/weapon/watertank,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 5},/obj/item/weapon/grenade/chem_grenade/antiweed,/obj/structure/table/glass,/turf/simulated/floor/plasteel{dir = 1; icon_state = "warning"},/area/hydroponics) "ciA" = (/obj/machinery/door/window/eastright{dir = 1; name = "Hydroponics Delivery"; req_access_txt = "35"},/obj/structure/window/reinforced{dir = 8},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/hydroponics) -"ciB" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "Hydroponics"},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/hydroponics) +"ciB" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "Hydroponics"},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/hydroponics) "ciC" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{req_access_txt = 1},/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plating{tag = "icon-warnplate (WEST)"; icon_state = "warnplate"; dir = 8},/area/maintenance/starboard) "ciD" = (/obj/effect/decal/cleanable/cobweb,/obj/machinery/atmospherics/unary/tank/toxins{dir = 4},/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/maintenance/incinerator) "ciE" = (/obj/machinery/atmospherics/binary/pump{dir = 4; name = "plasma tank pump"},/obj/structure/sign/nosmoking_2{pixel_x = 0; pixel_y = 28},/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/maintenance/incinerator) @@ -6027,7 +6027,7 @@ "clU" = (/obj/structure/table,/obj/item/weapon/paper_bin{pixel_x = -2; pixel_y = 4},/obj/machinery/light_switch{pixel_x = 11; pixel_y = 23},/turf/simulated/floor/plasteel{tag = "icon-vault"; icon_state = "vault"},/area/medical/sleeper{name = "Sleepers"}) "clV" = (/obj/item/weapon/storage/firstaid/regular{pixel_x = 3; pixel_y = -3},/obj/item/weapon/storage/firstaid/brute{pixel_x = 3; pixel_y = 3},/obj/item/weapon/storage/firstaid/brute,/obj/item/weapon/storage/firstaid/brute{pixel_x = -3; pixel_y = -3},/obj/machinery/power/apc{dir = 2; name = "Medbay Storage APC"; pixel_y = -24},/obj/structure/cable/yellow{d2 = 4; icon_state = "0-4"},/obj/machinery/light_switch{pixel_x = -26; pixel_y = 0},/obj/machinery/light/small,/obj/structure/table/glass,/turf/simulated/floor/plasteel{dir = 10; icon_state = "whiteblue"},/area/medical/medbay2{name = "Medbay Storage"}) "clW" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 5},/turf/simulated/floor/plasteel{dir = 2; icon_state = "whiteblue"},/area/medical/medbay2{name = "Medbay Storage"}) -"clX" = (/obj/structure/cable/yellow{d1 = 2; d2 = 8; icon_state = "2-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 10},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/bot/cleanbot{name = "Scrubs, MD"; on = 0},/turf/simulated/floor/plasteel{dir = 2; icon_state = "whiteblue"},/area/medical/medbay2{name = "Medbay Storage"}) +"clX" = (/obj/structure/cable/yellow{d1 = 2; d2 = 8; icon_state = "2-8"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 10},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/mob/living/simple_animal/bot/cleanbot{name = "Scrubs, MD"; on = 0},/turf/simulated/floor/plasteel{dir = 2; icon_state = "whiteblue"},/area/medical/medbay2{name = "Medbay Storage"}) "clY" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel{dir = 2; icon_state = "whiteblue"},/area/medical/medbay2{name = "Medbay Storage"}) "clZ" = (/obj/item/weapon/storage/firstaid/regular{pixel_x = 3; pixel_y = -3},/obj/item/weapon/storage/firstaid/o2{pixel_x = 3; pixel_y = 3},/obj/item/weapon/storage/firstaid/o2,/obj/item/weapon/storage/firstaid/o2{pixel_x = -3; pixel_y = -3},/obj/structure/table/glass,/turf/simulated/floor/plasteel{dir = 6; icon_state = "whiteblue"},/area/medical/medbay2{name = "Medbay Storage"}) "cma" = (/obj/machinery/alarm{dir = 4; pixel_x = -23; pixel_y = 0},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel{dir = 10; icon_state = "whiteblue"},/area/medical/medbay{name = "Medbay Central"}) @@ -6335,7 +6335,7 @@ "crQ" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/unary/vent_pump{dir = 8; on = 1},/turf/simulated/floor/plasteel{dir = 2; icon_state = "warnwhite"; tag = "icon-warnwhite (NORTH)"},/area/medical/research{name = "Research Division"}) "crR" = (/obj/machinery/shower{tag = "icon-shower (WEST)"; icon_state = "shower"; dir = 8},/obj/item/device/radio/intercom{dir = 8; name = "Station Intercom (General)"; pixel_x = 0; pixel_y = -28},/turf/simulated/floor/plasteel{dir = 6; icon_state = "warnwhite"},/area/medical/research{name = "Research Division"}) "crS" = (/obj/machinery/firealarm{dir = 4; pixel_x = 24},/obj/structure/closet/firecloset,/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/medical/research{name = "Research Division"}) -"crT" = (/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery;dir=2"; freq = 1400; location = "Research Division"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/aft{name = "Aft Maintenance"}) +"crT" = (/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 2; location = "Research Division"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/aft{name = "Aft Maintenance"}) "crU" = (/turf/simulated/wall/r_wall,/area/maintenance/aft{name = "Aft Maintenance"}) "crV" = (/obj/item/stack/packageWrap,/obj/item/stack/packageWrap,/obj/item/stack/packageWrap,/obj/item/stack/packageWrap,/obj/item/stack/packageWrap,/obj/item/weapon/hand_labeler,/obj/structure/table/glass,/obj/item/weapon/folder/white{pixel_y = 2},/turf/simulated/floor/plasteel{dir = 4; icon_state = "whiteyellowfull"; tag = "icon-whitehall (WEST)"},/area/medical/chemistry) "crW" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atm{pixel_x = 32; pixel_y = 0},/turf/simulated/floor/plasteel{dir = 2; icon_state = "neutralcorner"},/area/hallway/primary/aft) @@ -6640,7 +6640,7 @@ "cxJ" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{req_access_txt = 1},/obj/item/device/radio/intercom{frequency = 1459; name = "Station Intercom (General)"; pixel_x = -29},/turf/simulated/floor/plasteel{dir = 1; icon_state = "yellowcorner"},/area/hallway/primary/aft) "cxK" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/cable/yellow{d1 = 1; d2 = 4; icon_state = "1-4"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel,/area/hallway/primary/aft) "cxL" = (/obj/machinery/power/apc{cell_type = 5000; dir = 4; name = "Aft Hallway APC"; pixel_x = 24; pixel_y = 0},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/cable/yellow{d2 = 8; icon_state = "0-8"},/turf/simulated/floor/plasteel{dir = 4; icon_state = "purplecorner"},/area/hallway/primary/aft) -"cxM" = (/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery;dir=1"; freq = 1400; location = "Research and Development"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/toxins/lab) +"cxM" = (/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 1; location = "Research and Development"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/toxins/lab) "cxN" = (/obj/structure/disposalpipe/segment,/obj/machinery/door/airlock/maintenance{name = "Research Lab Maintenance"; req_access_txt = "7"},/turf/simulated/floor/plating{icon_state = "warnplate"; dir = 1},/area/toxins/lab) "cxO" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/disposalpipe/segment,/obj/machinery/door/firedoor,/turf/simulated/floor/plasteel{dir = 5; icon_state = "whitehall"},/area/medical/research{name = "Research Division"}) "cxP" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/door/firedoor,/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/research{name = "Research Division"}) @@ -7630,7 +7630,7 @@ "cQL" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{dir = 1; icon_state = "warning"},/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) "cQM" = (/obj/structure/cable/yellow{d1 = 1; d2 = 4; icon_state = "1-4"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 6},/turf/simulated/floor/plasteel{dir = 1; icon_state = "warning"},/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) "cQN" = (/obj/structure/cable/yellow{d1 = 2; d2 = 8; icon_state = "2-8"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 9},/obj/machinery/firealarm{dir = 4; pixel_x = 24},/turf/simulated/floor/plasteel{dir = 5; icon_state = "warning"},/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) -"cQO" = (/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery;dir=1"; freq = 1400; location = "Robotics"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/assembly/robotics) +"cQO" = (/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 1; location = "Robotics"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/assembly/robotics) "cQP" = (/obj/machinery/door/airlock/maintenance{name = "Robotics Maintenance"; req_access_txt = "29"},/turf/simulated/floor/plating,/area/assembly/robotics) "cQQ" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/door/airlock/research{name = "Research Shuttle and Xenobiology"; req_access_txt = "47"},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/research{name = "Research Division"}) "cQR" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 8},/turf/simulated/floor/plating{icon_state = "warnplate"; dir = 4},/area/maintenance/aft{name = "Aft Maintenance"}) @@ -7906,7 +7906,7 @@ "cWb" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{tag = "icon-warningcorner (WEST)"; icon_state = "warningcorner"; dir = 8},/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) "cWc" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/item/device/radio/beacon,/obj/machinery/embedded_controller/radio/airlock/docking_port_multi{id_tag = "escape_dock_north_airlock"; master_tag = "escape_dock"; pixel_y = 30; req_one_access_txt = "13"; tag_airpump = "escape_dock_north_pump"; tag_chamber_sensor = "escape_dock_north_sensor"; tag_exterior_door = "escape_dock_north_outer"; tag_interior_door = "escape_dock_north_inner"},/turf/simulated/floor/plasteel{dir = 1; icon_state = "warning"},/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) "cWd" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{tag = "icon-warningcorner (EAST)"; icon_state = "warningcorner"; dir = 4},/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) -"cWe" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 4},/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=9.5-Escape2"; location = "9-Escape"},/obj/machinery/bot/secbot{auto_patrol = 1; health = 35; maxhealth = 35; name = "Inspector Johnson"},/turf/simulated/floor/plasteel,/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) +"cWe" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 4},/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=9.5-Escape2"; location = "9-Escape"},/mob/living/simple_animal/bot/secbot{auto_patrol = 1; health = 35; maxHealth = 35; name = "Inspector Johnson"},/turf/simulated/floor/plasteel,/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) "cWf" = (/obj/machinery/camera{c_tag = "Departure Lounge - Starboard Aft"; dir = 8; network = list("SS13")},/obj/machinery/light{dir = 4},/obj/item/device/radio/intercom{frequency = 1459; name = "Station Intercom (General)"; pixel_x = 29},/obj/structure/flora/kirbyplants{icon_state = "plant-16"; layer = 4.1; tag = "icon-plant-16"},/turf/simulated/floor/plasteel{dir = 4; icon_state = "warning"},/area/hallway/secondary/exit{name = "\improper Departure Lounge"}) "cWg" = (/obj/structure/sink{icon_state = "sink"; dir = 8; pixel_x = -12; pixel_y = 2},/obj/machinery/atmospherics/unary/vent_scrubber{dir = 4; on = 1},/obj/machinery/firealarm{dir = 8; pixel_x = -26; pixel_y = 0},/turf/simulated/floor/plasteel{dir = 9; icon_state = "warnwhite"; tag = "icon-warnwhite (NORTHEAST)"},/area/toxins/xenobiology{name = "\improper Secure Lab"}) "cWh" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 10},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/turf/simulated/floor/plasteel{tag = "icon-warnwhite (NORTH)"; icon_state = "warnwhite"; dir = 1},/area/toxins/xenobiology{name = "\improper Secure Lab"}) @@ -8106,7 +8106,7 @@ "cZT" = (/obj/structure/stool/bed/chair{dir = 8},/turf/simulated/shuttle/floor{icon_state = "floor3"},/area/shuttle/escape) "cZU" = (/obj/structure/stool/bed/chair{dir = 4},/turf/simulated/shuttle/floor{icon_state = "floor3"},/area/shuttle/escape) "cZV" = (/obj/item/weapon/cigbutt,/turf/simulated/shuttle/plating{icon_state = "floorgrime"},/area/shuttle/escape) -"cZW" = (/obj/machinery/bot/medbot{name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2},/obj/structure/closet/crate/medical{name = "medical crate"},/obj/item/weapon/storage/firstaid/regular,/obj/item/weapon/storage/firstaid/o2{pixel_x = 3; pixel_y = 3},/obj/item/weapon/storage/firstaid/toxin{pixel_x = -4; pixel_y = 3},/obj/item/device/healthanalyzer{pixel_x = 3; pixel_y = 3},/obj/item/weapon/lazarus_injector,/turf/simulated/shuttle/plating{icon_state = "bot"},/area/shuttle/escape) +"cZW" = (/mob/living/simple_animal/bot/medbot{name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2},/obj/structure/closet/crate/medical{name = "medical crate"},/obj/item/weapon/storage/firstaid/regular,/obj/item/weapon/storage/firstaid/o2{pixel_x = 3; pixel_y = 3},/obj/item/weapon/storage/firstaid/toxin{pixel_x = -4; pixel_y = 3},/obj/item/device/healthanalyzer{pixel_x = 3; pixel_y = 3},/obj/item/weapon/lazarus_injector,/turf/simulated/shuttle/plating{icon_state = "bot"},/area/shuttle/escape) "cZX" = (/obj/machinery/door/window/northleft{base_state = "right"; dir = 8; icon_state = "right"; name = "Containment Pen #1"; req_access_txt = "55"},/obj/machinery/door/poddoor/preopen{id_tag = "xenobio3"; name = "containment blast door"},/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/turf/simulated/floor/plasteel,/area/toxins/xenobiology{name = "\improper Secure Lab"}) "cZY" = (/obj/machinery/door/window/northleft{dir = 4; name = "Containment Pen #1"; req_access_txt = "55"},/turf/simulated/floor/plasteel{dir = 4; icon_state = "warning"},/area/toxins/xenobiology{name = "\improper Secure Lab"}) "cZZ" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel{icon_state = "white"},/area/toxins/xenobiology{name = "\improper Secure Lab"}) diff --git a/_maps/map_files/MetaStation/z5.dmm b/_maps/map_files/MetaStation/z5.dmm index d4ec96886f7..aaf53a80095 100644 --- a/_maps/map_files/MetaStation/z5.dmm +++ b/_maps/map_files/MetaStation/z5.dmm @@ -938,7 +938,7 @@ "sb" = (/obj/machinery/light/small{dir = 4},/obj/structure/stool/bed/roller,/turf/simulated/floor/plasteel{dir = 2; icon_state = "whiteblue"},/area/mine/laborcamp) "sc" = (/obj/structure/closet/crate/internals,/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/tank/emergency_oxygen,/obj/item/clothing/mask/breath,/obj/item/clothing/mask/breath,/obj/item/clothing/mask/breath,/obj/item/clothing/mask/breath,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel{icon_state = "floorgrime"; temperature = 273.15},/area/mine/laborcamp) "sd" = (/obj/machinery/light/small,/turf/simulated/floor/plasteel{icon_state = "floorgrime"; temperature = 273.15},/area/mine/laborcamp) -"se" = (/obj/machinery/bot/secbot/beepsky{desc = "Powered by tears and swet of laborer."; name = "Prison Ofitser"},/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=2-NMine"; location = "1-Storage"},/turf/simulated/floor/plasteel{icon_state = "asteroidfloor"; temperature = 273.15},/area/mine/laborcamp) +"se" = (/mob/living/simple_animal/bot/secbot/beepsky{desc = "Powered by tears and swet of laborer."; name = "Prison Ofitser"},/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=2-NMine"; location = "1-Storage"},/turf/simulated/floor/plasteel{icon_state = "asteroidfloor"; temperature = 273.15},/area/mine/laborcamp) "sf" = (/obj/structure/plasticflaps/mining,/turf/simulated/floor/plasteel{icon_state = "asteroidfloor"; temperature = 273.15},/area/mine/laborcamp) "sg" = (/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=3-SMine"; location = "2-NMine"},/turf/simulated/floor/plasteel{icon_state = "asteroidfloor"; temperature = 273.15},/area/mine/laborcamp) "sh" = (/turf/simulated/floor/plating{icon_plating = "asteroidplating"; icon_state = "asteroidplating"; temperature = 273.15},/area/mine/explored) diff --git a/_maps/map_files/RandomZLevels/evil_santa.dmm b/_maps/map_files/RandomZLevels/evil_santa.dmm index f144c50064a..7d56e430210 100644 --- a/_maps/map_files/RandomZLevels/evil_santa.dmm +++ b/_maps/map_files/RandomZLevels/evil_santa.dmm @@ -67,7 +67,7 @@ "bo" = (/obj/structure/table,/obj/item/weapon/storage/box/beakers,/obj/item/weapon/storage/box/syringes,/turf/simulated/floor/wood,/area/awaymission/challenge/main) "bp" = (/turf/simulated/floor/wood,/area/awaymission/challenge/main) "bq" = (/obj/structure/mineral_door/wood{tag = "icon-wood"; icon_state = "wood"},/turf/simulated/floor/wood,/area/awaymission/challenge/main) -"br" = (/obj/machinery/bot/medbot/mysterious,/turf/simulated/floor/wood,/area/awaymission/challenge/main) +"br" = (/mob/living/simple_animal/bot/medbot/mysterious,/turf/simulated/floor/wood,/area/awaymission/challenge/main) "bs" = (/obj/machinery/space_heater,/turf/simulated/floor/wood,/area/awaymission/challenge/main) "bt" = (/obj/structure/table,/obj/item/clothing/accessory/stethoscope,/obj/item/bodybag,/obj/item/weapon/storage/box/beakers,/turf/simulated/floor/wood,/area/awaymission/challenge/main) "bu" = (/obj/machinery/vending/cola,/turf/simulated/floor/plasteel,/area/awaymission/challenge/main) diff --git a/_maps/map_files/RandomZLevels/stationCollision.dmm b/_maps/map_files/RandomZLevels/stationCollision.dmm index 7fdd6dff7ea..4402d7c10d9 100644 --- a/_maps/map_files/RandomZLevels/stationCollision.dmm +++ b/_maps/map_files/RandomZLevels/stationCollision.dmm @@ -254,7 +254,7 @@ "eT" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 1; external_pressure_bound = 120; icon_state = "in"; initialize_directions = 1; internal_pressure_bound = 4000; on = 1; pressure_checks = 2; pump_direction = 0},/turf/simulated/floor/plasteel{icon_state = "showroomfloor"},/area/awaymission/research) "eU" = (/obj/machinery/r_n_d/server{stat = 1},/turf/simulated/floor/plasteel{icon_state = "showroomfloor"},/area/awaymission/research) "eV" = (/obj/machinery/power/emitter{anchored = 1; dir = 1; icon_state = "emitter"; state = 2},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/awaymission/research) -"eW" = (/obj/machinery/bot/medbot/mysterious{desc = "A dark little medical robot. She looks somewhat underwhelmed."; name = "Nightingale"},/turf/simulated/shuttle/floor{icon_state = "floor4"; nitrogen = 0.01; oxygen = 0.01; temperature = 2.7},/area/awaymission/syndishuttle) +"eW" = (/mob/living/simple_animal/bot/medbot/mysterious{desc = "A dark little medical robot. She looks somewhat underwhelmed."; name = "Nightingale"},/turf/simulated/shuttle/floor{icon_state = "floor4"; nitrogen = 0.01; oxygen = 0.01; temperature = 2.7},/area/awaymission/syndishuttle) "eX" = (/obj/structure/table,/turf/simulated/shuttle/floor{icon_state = "floor4"; nitrogen = 0.01; oxygen = 0.01; temperature = 2.7},/area/awaymission/syndishuttle) "eY" = (/obj/structure/window/reinforced{dir = 8},/turf/simulated/floor/plasteel/airless{icon_state = "floorscorched1"},/area/awaymission/northblock) "eZ" = (/turf/simulated/floor/plasteel,/area/awaymission/northblock) diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm index a4bdd72dbe3..02b41c7d98d 100644 --- a/_maps/map_files/cyberiad/cyberiad.dmm +++ b/_maps/map_files/cyberiad/cyberiad.dmm @@ -690,7 +690,7 @@ "ann" = (/obj/structure/disposalpipe/segment,/turf/simulated/wall,/area/security/main) "ano" = (/obj/machinery/door/firedoor,/obj/machinery/door/airlock/glass_security{name = "Security Office"; req_access_txt = "63"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel,/area/security/main) "anp" = (/obj/machinery/door/firedoor,/obj/machinery/door/airlock/glass_security{name = "Security Office"; req_access_txt = "63"},/turf/simulated/floor/plasteel,/area/security/main) -"anq" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=1"; dir = 1; freq = 1400; location = "Security"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plating,/area/maintenance/fsmaint) +"anq" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/mob/living/simple_animal/bot/floorbot{on = 0},/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "anr" = (/obj/machinery/light/small{dir = 1},/obj/structure/closet/emcloset,/obj/machinery/camera{c_tag = "Security Pod"; dir = 2; network = list("SS13")},/turf/simulated/floor/plating,/area/maintenance/fsmaint) "ans" = (/turf/simulated/shuttle/wall{tag = "icon-swall12"; icon_state = "swall12"; dir = 2},/area/shuttle/pod_3) "ant" = (/turf/simulated/floor/plating,/obj/structure/shuttle/engine/propulsion/burst{dir = 4},/turf/simulated/shuttle/wall{tag = "icon-swall_f6"; icon_state = "swall_f6"; dir = 2},/area/shuttle/pod_3) @@ -1255,7 +1255,7 @@ "ayg" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "ayh" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 2; on = 1},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "ayi" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) -"ayj" = (/obj/machinery/bot/secbot/beepsky{name = "Officer Beepsky"},/obj/machinery/hologram/holopad,/turf/simulated/floor/plasteel,/area/hallway/primary/fore) +"ayj" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 1; location = "Security"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plating,/area/maintenance/fsmaint) "ayk" = (/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "ayl" = (/obj/machinery/atmospherics/unary/vent_scrubber{on = 1; scrub_N2O = 1; scrub_Toxins = 1},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "aym" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 6},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) @@ -2334,7 +2334,7 @@ "aST" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{level = 1},/obj/structure/sign/securearea{desc = "Under the painting a plaque reads: 'While the meat grinder may not have spared you, fear not. Not one part of you has gone to waste... You were delicious.'"; icon_state = "monkey_painting"; name = "Mr. Deempisi portrait"; pixel_x = -28; pixel_y = 4},/obj/structure/reagent_dispensers/beerkeg,/turf/simulated/floor/wood,/area/crew_quarters/bar) "aSU" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/obj/structure/disposalpipe/segment,/turf/simulated/floor/wood,/area/crew_quarters/bar) "aSV" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/obj/machinery/door/window/southleft{tag = "icon-left (WEST)"; name = "Bar Delivery"; icon_state = "left"; dir = 8; req_access_txt = "25"; base_state = "left"},/obj/effect/decal/warning_stripes/yellow,/turf/simulated/floor/plasteel,/area/crew_quarters/bar) -"aSW" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 1; level = 1},/obj/machinery/navbeacon{codes_txt = "delivery;dir=2"; freq = 1400; location = "Bar"},/obj/structure/plasticflaps{opacity = 1},/obj/effect/decal/warning_stripes/yellow/hollow,/turf/simulated/floor/plasteel,/area/maintenance/fsmaint2) +"aSW" = (/obj/machinery/hologram/holopad,/mob/living/simple_animal/bot/secbot/beepsky{name = "Officer Beepsky"},/turf/simulated/floor/plasteel,/area/hallway/primary/fore) "aSX" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/turf/simulated/floor/plating,/area/maintenance/fsmaint2) "aSY" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/turf/simulated/floor/plating,/area/maintenance/fsmaint2) "aSZ" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/obj/structure/disposalpipe/segment,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/turf/simulated/floor/plating,/area/maintenance/fsmaint2) @@ -2342,7 +2342,7 @@ "aTb" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/turf/simulated/wall,/area/crew_quarters/kitchen) "aTc" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 1; level = 1},/turf/simulated/wall,/area/crew_quarters/kitchen) "aTd" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/obj/structure/disposalpipe/segment,/obj/machinery/door/airlock/maintenance{name = "Kitchen Maintenance"; req_access_txt = "28"},/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/turf/simulated/floor/plating,/area/maintenance/fsmaint2) -"aTe" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery;dir=2"; freq = 1400; location = "Kitchen"},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/maintenance/fsmaint2) +"aTe" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 1; level = 1},/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "Bar"},/obj/structure/plasticflaps{opacity = 1},/obj/effect/decal/warning_stripes/yellow/hollow,/turf/simulated/floor/plasteel,/area/maintenance/fsmaint2) "aTf" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plating,/area/maintenance/fsmaint2) "aTg" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply,/turf/simulated/floor/plating,/area/maintenance/fsmaint2) "aTh" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/turf/simulated/floor/plating,/area/maintenance/fsmaint2) @@ -2399,7 +2399,7 @@ "aUg" = (/obj/structure/table,/obj/item/weapon/storage/belt/utility,/obj/item/weapon/storage/firstaid/regular,/turf/simulated/floor/plasteel,/area/storage/primary) "aUh" = (/obj/structure/reagent_dispensers/fueltank,/turf/simulated/floor/plasteel,/area/storage/primary) "aUi" = (/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/storage/primary) -"aUj" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "Tool Storage"},/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel{icon_state = "bot"},/area/storage/primary) +"aUj" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4; level = 1},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/navbeacon{codes_txt = "delivery"; location = "Kitchen"},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/maintenance/fsmaint2) "aUk" = (/obj/structure/disposalpipe/trunk,/obj/machinery/disposal,/turf/simulated/floor/plasteel,/area/storage/primary) "aUl" = (/obj/effect/spawner/window/reinforced,/obj/machinery/door/firedoor{dir = 2},/obj/structure/cable{d2 = 2; icon_state = "0-2"},/turf/simulated/floor/plating,/area/hallway/primary/port) "aUm" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel{icon_state = "vault"; dir = 5},/area/hallway/primary/port) @@ -2441,7 +2441,7 @@ "aUW" = (/obj/machinery/light/small{dir = 1},/obj/item/device/radio/intercom{broadcasting = 0; name = "station intercom (General)"; pixel_y = 25},/obj/structure/closet/chefcloset,/turf/simulated/floor/plasteel{icon_state = "showroomfloor"},/area/crew_quarters/kitchen) "aUX" = (/obj/machinery/camera{c_tag = "Kitchen Freezer"; network = list("SS13")},/obj/machinery/chem_master/condimaster{name = "CondiMaster Neo"; pixel_x = -5},/obj/structure/window/reinforced{dir = 4},/turf/simulated/floor/plasteel{icon_state = "showroomfloor"},/area/crew_quarters/kitchen) "aUY" = (/obj/machinery/door/window/southleft{base_state = "left"; dir = 2; icon_state = "left"; name = "Kitchen Delivery"; req_access_txt = "28"},/obj/effect/decal/warning_stripes/yellow,/turf/simulated/floor/plasteel{icon_state = "showroomfloor"},/area/crew_quarters/kitchen) -"aUZ" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=2"; freq = 1400; location = "Hydroponics"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/maintenance/fsmaint2) +"aUZ" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "Tool Storage"},/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel{icon_state = "bot"},/area/storage/primary) "aVa" = (/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/wall,/area/hydroponics) "aVb" = (/turf/simulated/wall,/area/hydroponics) "aVc" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{level = 1},/turf/simulated/wall,/area/hydroponics) @@ -3885,7 +3885,7 @@ "bwK" = (/obj/item/weapon/folder/yellow,/obj/item/weapon/pen{pixel_x = 4; pixel_y = 4},/obj/structure/table/reinforced,/turf/simulated/floor/plasteel{icon_state = "arrival"; dir = 4},/area/quartermaster/office) "bwL" = (/obj/machinery/atmospherics/unary/vent_pump{on = 1},/turf/simulated/floor/plasteel{dir = 8; icon_state = "browncorner"},/area/hallway/primary/central/west) "bwM" = (/obj/structure/extinguisher_cabinet{pixel_x = 27; pixel_y = 0},/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/turf/simulated/floor/plasteel{icon_state = "bluecorner"},/area/hallway/primary/central/west) -"bwN" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=1"; dir = 1; freq = 1400; location = "Bridge"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/bridge/meeting_room) +"bwN" = (/obj/machinery/navbeacon{codes_txt = "delivery"; location = "Hydroponics"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"; dir = 1},/area/maintenance/fsmaint2) "bwO" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/structure/disposalpipe/segment,/obj/machinery/door/firedoor,/obj/machinery/door/airlock/command{name = "Conference Room"; req_access = null; req_access_txt = "19"},/turf/simulated/floor/wood,/area/bridge/meeting_room) "bwP" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 6},/obj/structure/lattice,/turf/space,/area/space) "bwQ" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers,/turf/space,/area/space) @@ -4499,7 +4499,7 @@ "bIA" = (/turf/simulated/floor/plating,/area/quartermaster/storage) "bIB" = (/turf/simulated/floor/plasteel{dir = 9; icon_state = "warning"},/area/quartermaster/storage) "bIC" = (/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/quartermaster/storage) -"bID" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "QM #1"},/obj/machinery/bot/mulebot{beacon_freq = 1400; home_destination = "QM #1"; suffix = "#1"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) +"bID" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 1; location = "Bridge"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/bridge/meeting_room) "bIE" = (/obj/structure/table,/obj/machinery/requests_console{department = "Cargo Bay"; name = "Cargo Requests Console"; departmentType = 2; pixel_x = -30; pixel_y = 0},/obj/machinery/camera{c_tag = "Cargo Office"; dir = 4; network = list("SS13")},/obj/item/stack/sheet/glass{amount = 50; pixel_x = 3; pixel_y = 3},/obj/item/stack/sheet/metal{amount = 50},/obj/item/device/multitool,/turf/simulated/floor/plasteel,/area/quartermaster/office) "bIF" = (/obj/structure/filingcabinet/filingcabinet,/turf/simulated/floor/plasteel,/area/quartermaster/office) "bIG" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/office) @@ -4588,7 +4588,7 @@ "bKl" = (/turf/space,/turf/simulated/shuttle/wall{icon_state = "swall_f5"; dir = 2},/area/shuttle/research) "bKm" = (/obj/machinery/door/firedoor{dir = 2},/obj/machinery/door/airlock/external{id_tag = "supply_home"; name = "Cargo Docking Hatch"; req_access_txt = "31"},/turf/simulated/floor/plating,/area/quartermaster/storage) "bKn" = (/obj/effect/decal/warning_stripes/southeastcorner,/obj/effect/decal/warning_stripes/northeastcorner,/turf/simulated/floor/plasteel,/area/hallway/secondary/exit) -"bKo" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "QM #2"},/obj/machinery/bot/mulebot{home_destination = "QM #2"; suffix = "#2"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) +"bKo" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "QM #1"},/mob/living/simple_animal/bot/mulebot{home_destination = "QM #1"; suffix = "#1"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) "bKp" = (/obj/structure/table,/obj/machinery/firealarm{dir = 8; pixel_x = -24},/obj/item/weapon/folder/yellow,/obj/item/device/eftpos,/turf/simulated/floor/plasteel,/area/quartermaster/office) "bKq" = (/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/unary/vent_pump/on{dir = 4},/turf/simulated/floor/plasteel,/area/quartermaster/office) "bKr" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 4; initialize_directions = 11; level = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plasteel,/area/quartermaster/office) @@ -4648,7 +4648,7 @@ "bLt" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 6},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/research{name = "Research Division"}) "bLu" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/door/firedoor,/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/research{name = "Research Division"}) "bLv" = (/obj/machinery/door/window/eastright{base_state = "left"; dir = 8; icon_state = "left"; name = "Research Division Delivery"; req_access_txt = "47"},/obj/structure/window/reinforced{dir = 1},/obj/machinery/door/poddoor{density = 0; icon_state = "pdoor0"; id_tag = "Biohazard"; name = "Biohazard Shutter"; opacity = 0},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/medical/research{name = "Research Division"}) -"bLw" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "Research Division"},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/medical/research{name = "Research Division"}) +"bLw" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "QM #2"},/mob/living/simple_animal/bot/mulebot{home_destination = "QM #2"; suffix = "#2"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) "bLx" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/structure/cable{d1 = 2; d2 = 4; icon_state = "2-4"; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{dir = 2; icon_state = "whitepurplecorner"},/area/medical/research{name = "Research Division"}) "bLy" = (/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_x = 0; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{dir = 2; icon_state = "whitepurplecorner"},/area/medical/research{name = "Research Division"}) "bLz" = (/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_x = 0; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/door/firedoor,/obj/machinery/door/poddoor{density = 0; icon_state = "pdoor0"; id_tag = "Biohazard"; name = "Biohazard Shutter"; opacity = 0},/obj/machinery/door/airlock/glass_research{name = "Research Shuttle Dock"; req_access_txt = "65"},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/research_shuttle_dock) @@ -4660,7 +4660,7 @@ "bLF" = (/obj/machinery/door/airlock/external{id_tag = "s_docking_airlock"; name = "Shuttle Hatch"; req_access_txt = "31"},/turf/simulated/shuttle/floor,/area/shuttle/supply) "bLG" = (/obj/machinery/conveyor_switch/oneway{convdir = -1; id = "QMLoad"},/turf/simulated/floor/plasteel,/area/quartermaster/storage) "bLH" = (/obj/machinery/atmospherics/unary/vent_scrubber{dir = 1; on = 1},/turf/simulated/floor/plasteel,/area/quartermaster/storage) -"bLI" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "QM #3"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) +"bLI" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "Research Division"},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/medical/research{name = "Research Division"}) "bLJ" = (/obj/structure/table,/obj/machinery/alarm{dir = 4; icon_state = "alarm0"; pixel_x = -22},/obj/item/weapon/storage/belt/utility,/turf/simulated/floor/plasteel,/area/quartermaster/office) "bLK" = (/obj/machinery/atmospherics/unary/vent_pump/on{dir = 1},/turf/simulated/floor/plasteel,/area/quartermaster/office) "bLL" = (/obj/machinery/light,/obj/machinery/computer/merch,/turf/simulated/floor/plasteel,/area/quartermaster/office) @@ -4747,7 +4747,7 @@ "bNo" = (/obj/machinery/light,/turf/simulated/floor/plasteel{dir = 8; icon_state = "loadingarea"},/area/quartermaster/storage) "bNp" = (/obj/machinery/camera{c_tag = "Cargo Bay South"; dir = 1},/obj/machinery/power/apc{dir = 2; name = "south bump"; pixel_y = -24},/obj/structure/cable,/turf/simulated/floor/plasteel,/area/quartermaster/storage) "bNq" = (/obj/machinery/atmospherics/unary/vent_pump/on{dir = 1},/turf/simulated/floor/plasteel,/area/quartermaster/storage) -"bNr" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "QM #4"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) +"bNr" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "QM #3"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) "bNs" = (/obj/structure/table,/obj/item/weapon/storage/firstaid/regular{pixel_x = 6; pixel_y = -5},/obj/machinery/newscaster{pixel_x = -27; pixel_y = 1},/turf/simulated/floor/plasteel,/area/quartermaster/office) "bNt" = (/obj/machinery/power/apc{dir = 2; name = "south bump"; pixel_y = -24},/obj/structure/cable{d2 = 4; icon_state = "0-4"},/obj/machinery/hologram/holopad,/turf/simulated/floor/plasteel,/area/quartermaster/office) "bNu" = (/obj/machinery/light,/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/obj/machinery/computer/guestpass{pixel_y = -28},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"},/turf/simulated/floor/plasteel{dir = 2; icon_state = "browncorner"},/area/quartermaster/office) @@ -5242,7 +5242,7 @@ "bWP" = (/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel,/area/janitor) "bWQ" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/structure/cable{d1 = 2; d2 = 4; icon_state = "2-4"; tag = ""},/obj/structure/reagent_dispensers/spacecleanertank{pixel_y = 30},/turf/simulated/floor/plasteel,/area/janitor) "bWR" = (/obj/machinery/door/window/westleft{name = "Janitoral Delivery"; req_access_txt = "26"},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/janitor) -"bWS" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=8"; freq = 1400; location = "Janitor"},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_y = 0; tag = ""},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/asmaint) +"bWS" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "QM #4"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/quartermaster/storage) "bWT" = (/obj/structure/cable{d1 = 2; d2 = 8; icon_state = "2-8"; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/turf/simulated/floor/plating,/area/maintenance/asmaint) "bWU" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/wall,/area/medical/paramedic) "bWV" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/light{dir = 8},/obj/item/device/radio/intercom{broadcasting = 0; name = "station intercom (General)"; pixel_y = 25},/obj/machinery/light_switch{pixel_x = -23; pixel_y = 0},/turf/simulated/floor/plasteel{dir = 4; icon_state = "warnwhite"; tag = "icon-warnwhite (NORTH)"},/area/medical/paramedic) @@ -5651,7 +5651,7 @@ "ceI" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/grille{density = 0; icon_state = "brokengrille"},/turf/simulated/floor/plating,/area/maintenance/asmaint) "ceJ" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plating,/area/maintenance/asmaint) "ceK" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{level = 1},/turf/simulated/floor/plating,/area/maintenance/asmaint) -"ceL" = (/obj/machinery/navbeacon{codes_txt = "delivery;dir=4"; freq = 1400; location = "Medbay"},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 10},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/asmaint) +"ceL" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 8; location = "Janitor"},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_y = 0; tag = ""},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/asmaint) "ceM" = (/obj/machinery/door/window/eastleft{name = "Medical Delivery"; req_access_txt = "5"},/obj/effect/decal/warning_stripes/yellow,/turf/simulated/floor/plasteel,/area/medical/sleeper) "ceN" = (/obj/machinery/atmospherics/pipe/simple/hidden{dir = 5; icon_state = "intact"},/turf/simulated/floor/plasteel{icon_state = "blue"; dir = 8},/area/medical/sleeper) "ceO" = (/obj/machinery/atmospherics/pipe/simple/hidden{dir = 9; icon_state = "intact"},/turf/simulated/floor/plasteel,/area/medical/sleeper) @@ -5791,7 +5791,7 @@ "chs" = (/obj/machinery/status_display{density = 0; layer = 4; pixel_x = -32; pixel_y = 0},/obj/structure/disposalpipe/junction{tag = "icon-pipe-j2"; icon_state = "pipe-j2"; dir = 2},/turf/simulated/floor/plasteel{dir = 8; icon_state = "cautioncorner"},/area/hallway/primary/aft) "cht" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/machinery/atmospherics/unary/vent_scrubber{on = 1; scrub_N2O = 1; scrub_Toxins = 1},/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel,/area/hallway/primary/aft) "chu" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{dir = 2; icon_state = "yellowcorner"},/area/hallway/primary/aft) -"chv" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/navbeacon{codes_txt = "delivery;dir=4"; freq = 1400; location = "Engineering"},/obj/machinery/door/firedoor{dir = 8; name = "Firelock West"},/obj/machinery/door/window/westright{name = "Engineering Monitoring Station"; req_access = null; req_access_txt = "0"; req_one_access_txt = "11;24"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/engine/controlroom) +"chv" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 4; location = "Medbay"},/obj/structure/plasticflaps{opacity = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 10},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/maintenance/asmaint) "chw" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/door/window/northleft{dir = 4; icon_state = "left"; name = "Engineering Monitoring Station"; req_access_txt = "0"; req_one_access_txt = "11;24"},/turf/simulated/floor/plasteel{icon_state = "delivery"},/area/engine/controlroom) "chx" = (/obj/structure/disposalpipe/segment{dir = 2; icon_state = "pipe-c"},/turf/simulated/floor/plasteel{dir = 4; icon_state = "loadingarea"; tag = "loading"},/area/engine/controlroom) "chy" = (/turf/simulated/floor/plasteel,/area/engine/controlroom) @@ -7917,17 +7917,17 @@ "cWm" = (/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 8; initialize_directions = 11; level = 1},/obj/machinery/alarm{dir = 4; icon_state = "alarm0"; pixel_x = -22},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) "cWn" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) "cWo" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 8; on = 1},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) -"cWp" = (/obj/machinery/bot/secbot/pingsky,/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) +"cWp" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 4; location = "Engineering"},/obj/machinery/door/firedoor{dir = 8; name = "Firelock West"},/obj/machinery/door/window/westright{name = "Engineering Monitoring Station"; req_access = null; req_access_txt = "0"; req_one_access_txt = "11;24;34"},/turf/simulated/floor/plasteel{icon_state = "bot"},/area/engine/controlroom) "cWq" = (/obj/machinery/atmospherics/unary/vent_scrubber{dir = 4; on = 1},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) "cWr" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) "cWs" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{dir = 4; initialize_directions = 11; level = 1},/obj/structure/cable,/obj/machinery/power/apc{dir = 4; name = "AI Satellite Antechamber APC"; pixel_x = 25},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) "cWt" = (/obj/structure/window/reinforced{dir = 8},/obj/structure/window/reinforced{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/light/small{dir = 4},/obj/machinery/camera/motion{c_tag = "Mini Satellite Exterior North-West"; dir = 8; network = list("SS13","MiniSat")},/turf/simulated/floor/plating,/area/aisat) "cWu" = (/turf/simulated/wall,/area/aisat) -"cWv" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/firealarm{dir = 8; pixel_x = -24},/obj/machinery/bot/floorbot{on = 0},/turf/simulated/floor/plasteel{dir = 8; icon_state = "vault"; tag = "icon-vault (WEST)"},/area/turret_protected/aisat_interior) +"cWv" = (/mob/living/simple_animal/bot/secbot/pingsky,/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) "cWw" = (/obj/machinery/ai_status_display{pixel_y = -32},/obj/machinery/porta_turret{dir = 1},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) "cWx" = (/obj/machinery/camera/motion{c_tag = "Mini Satellite Antechamber"; dir = 1; network = list("SS13","MiniSat")},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) "cWy" = (/obj/machinery/status_display{pixel_x = 0; pixel_y = -32},/obj/machinery/porta_turret{dir = 1},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/turret_protected/aisat_interior) -"cWz" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/bot/cleanbot{on = 0},/turf/simulated/floor/plasteel{dir = 8; icon_state = "vault"; tag = "icon-vault (WEST)"},/area/turret_protected/aisat_interior) +"cWz" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/firealarm{dir = 8; pixel_x = -24},/mob/living/simple_animal/bot/floorbot{on = 0},/turf/simulated/floor/plasteel{dir = 8; icon_state = "vault"; tag = "icon-vault (WEST)"},/area/turret_protected/aisat_interior) "cWA" = (/obj/structure/window/reinforced{dir = 8},/obj/structure/window/reinforced{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/machinery/light/small{dir = 8},/obj/machinery/camera/motion{c_tag = "Mini Satellite Exterior North-East"; dir = 4; network = list("SS13","MiniSat")},/turf/simulated/floor/plating,/area/aisat) "cWB" = (/turf/simulated/wall/r_wall,/area/turret_protected/ai) "cWC" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/wall/r_wall,/area/turret_protected/ai) @@ -8472,7 +8472,7 @@ "dgV" = (/obj/machinery/door/poddoor/shutters{density = 0; dir = 8; icon_state = "shutter0"; id_tag = "voxshutters"; name = "Blast Shutters"; opacity = 0},/obj/structure/grille,/obj/structure/shuttle/window{dir = 4; icon = 'icons/turf/shuttle.dmi'; icon_state = "window5_mid"; tag = "icon-window5 (EAST)"},/turf/simulated/shuttle/plating/vox,/area/shuttle/vox) "dgW" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/machinery/recharger{pixel_y = 0},/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "dgX" = (/obj/machinery/atmospherics/pipe/simple/hidden,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) -"dgY" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/machinery/bot/floorbot{on = 0},/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) +"dgY" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/mob/living/simple_animal/bot/cleanbot{on = 0},/turf/simulated/floor/plasteel{dir = 8; icon_state = "vault"; tag = "icon-vault (WEST)"},/area/turret_protected/aisat_interior) "dgZ" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/machinery/cell_charger,/obj/item/weed_extract,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "dha" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/item/broken_device,/obj/item/robot_parts/chest,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) "dhb" = (/obj/structure/window/reinforced{dir = 1},/obj/structure/table,/obj/item/weapon/scalpel,/obj/item/stack/cable_coil,/obj/item/weapon/storage/firstaid/regular,/turf/simulated/shuttle/floor4/vox,/area/shuttle/vox) @@ -8781,7 +8781,7 @@ bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbi bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbFldiSdiUdiTbFlbikbFldiVdiVdiVbFlbikbFldiWdiYdiXbFlbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaalabXabYabZacaacbaccacdaceacfacfaalbikbikbikbikbikabKabLabLabKbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdgwdgNdgwbikbikdgOdgGdgydgPdgQdgydgydgGdgRbikbikdgwdgNdgwbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbFldiVdiVdiVbFlbikcVGdiZdjbdjadelbikbFldiVdiVdiVbFlbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacgacgacgacgacgacgachaciacjackaclaalacmbikbikbikbikbikabKabLabLabKbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdgwdgNdgwdgwdgwdgwdgwdgwdgwdgSdgwdgwdgwdgwdgwdgwdgwdgNdgwbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikcVGdiZdjbdjadelbikbikbikbikbikbikbikcVGdiZdjbdjadelbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacgacnacoacpacqacracsacdaalaalaalaalbikbikbikbikbikbikbikabKabLabLabKactactactacuacvactactactabKabKabKabKabKbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdgHdgNdgTdgTdgTdgTdgTdgTdgmdgUdgmdgTdgTdgTdgTdgTdgTdgNdgJbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacgacwacxacyaczacAacBacCaalacDacEacEacEacEacEbikbikbikbikbikabKacFacGactacHacIacJacKacLacMactabLabLabLabLabLabKbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdgVdgXdgWdgZdgYdhadgmdgSdgwdgSdgwdgSdgmdhcdhbdhedhddgXdhfbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacgacwacxacyaczacAacBacCaalacDacEacEacEacEacEbikbikbikbikbikabKacFacGactacHacIacJacKacLacMactabLabLabLabLabLabKbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdgVdgXdgWdgZanqdhadgmdgSdgwdgSdgwdgSdgmdhcdhbdhedhddgXdhfbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabaabbikbikbikbikbikbikaahbikbikbikbikbikbikbikbikbikbikacgacNacOacPacQacRacSacCaalacEacEacUacTacVacEacEacEbikbikbikbikabKacGactacWacXacYacXacXacZactadaadaadaadaadaadaabLadbbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdgOdgXdgydgydgydhgdgmdgydgydgydgydgydgmdgydgydhhdgydgXdgRbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacgacgacgacgacgacgadcaddacEacEadeadgadfadiadhacEacEbikbikbikbikbikabKactadjadkadladmadnadoactadpadqadradsadtaduadvbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdgmdhjdhidhldhkdhmdgmdhndgydgydgydhodgmdhqdhpdhsdhrdhjdgmbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikadwadxadyadzadAadwadBadCacEadEadDadGadFadIadHadJacEbikbikbikbikbikbikactadKadLadMadNacXadOactadPadtadtadtadtaduadvbikbikbikbikbikbikbikaahbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdgmdhtdhtdgwdgwdgwdgmdhudgydgydgydhvdgmdgwdgwdgwdhtdhtdgmbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik @@ -8800,7 +8800,7 @@ bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbi bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGbikbikaabbikaabbikbikajQbikbikaabbikaabbikbikacGbikbikbikbikbikbikbikbikbikbikbikbikaeAakFafyakGakHakIakJakKakLajtakMakNakOakOakPakQakRajuakSakTakUakVakiakWakXakYakZalaalbalcaldaleakqalfalgalhalialjalkallalmaiCaiCaiCalnajOaieaieaieaieaieaieaieaieahAaabaloaabaabaabaloaabbikbikaabaloaabaabbikbikaloaabbikaabaabajkajPajmbikbikagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaahbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGaabalpalpalpalpalpbikalqbikalpalpalpalpalpaabacGbikbikbikbikbikbikbikbikbikbikbikbikaeAalralsakGafyaltakJalualvajtalwalxalyalzakQakQakRajualAalBalCalDalEajBalFalGaklalHalIajFajXalKalLalLalMajdalNalOalPalQaiCaiFaiGaiCalRahAahAahAahAahAahAahAahAahAahAaabaloaabbikajkajlajmbikbikajkajlajmbikbikajkajlajmaabaabbikajkajPajmbikbikagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGbikalSalTalTalTalTalUalValWalXalXalXalXalYbikacGbikbikbikamaalZaowanxambbikbikbikamcaeAaeAaeAamdamdameamfamgamdajuamhamiamjamkamlammamnajuamoampamqakVamrajGamsamtaXNamvamwajGamxamyamzamAamBamCamDamEamFamGaiCajhamHamIamJahAamKamKamKamKamKbikbikbikbikaabaloaabbikajkajPajmbikbikajkajPajmbikbikajkajPajmbikbikbikajkajPajmbikbikagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGaabamLamLamLamLamLbikamMbikamLamLamLamLamLaabacGbikbikbikamOamNamQamPamOamRamSamSamTamUamVamWamXamYamZanaanbancajuajuandaneajuajuajuajuajuanfanganhanianfajGajBajCanjajEajFajGankanlanmannahUahUahUahUannanoanpanmahUahAanqahAanrantansansanubikbikbikbikbikaloaabbikajkajPajmbikbikajkajPajmbikbikajkajPajmbikbikbikajkajPajmbikbikagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGaabamLamLamLamLamLbikamMbikamLamLamLamLamLaabacGbikbikbikamOamNamQamPamOamRamSamSamTamUamVamWamXamYamZanaanbancajuajuandaneajuajuajuajuajuanfanganhanianfajGajBajCanjajEajFajGankanlanmannahUahUahUahUannanoanpanmahUahAayjahAanrantansansanubikbikbikbikbikaloaabbikajkajPajmbikbikajkajPajmbikbikajkajPajmbikbikbikajkajPajmbikbikagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGbikbikbikaabbikbikbikanvbikbikbikaabbikbikbikaabbikbikbikaEEanwanzanyanBanAanDanCanAanGanGanHanIanJanKanLanManNanOanPanQanRanSanTanSanSanUanVanWanXanYanSanZaoaaobaocaodaoeaofaogaohanSaoiaojaokaolaomaonaooaopaoqaoraosaplaotaovaEHaouaoyaoxbikbikbikbikbikaloaabbikajkajPajmbikbikajkajPajmbikbikajkajPajmbikbikbikajkajPajmbikbikagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGaabalpalpalpalpalpbikamMbikalpalpalpalpalpaabacGbikbikbikamOanEaoAanFamOamRamSamSamSaoBaoHaoIaoJaoKaoLaoMaoNaoOaoPaoQaoRaoSaoTaoUaoVaoWaoXaoYaoWaoZapaaoTapbapcapdaoTaoTapdaoXapcapeaoTaoTapbapcaoVapfapgaphapiapjapkaplapmapnaovaozansansapobikaacapqaacbikaloaabbikajkajPajmbikbikajkajPajmbikbikajkajPajmbikaabaabajkajPajmaabaabagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikapsapsapsapsapsbikbikbikbikbikbikajjbikalSalTalTalTalTalUalValWalXalXalXalXalYbikacGbikbikbikamOaoCaoCaoDamObikbikbikapwapwapxapyapzapAapAapAapBapBapBapBapCapDapEapEapEapFapGapHapFapEapEapEapIapJapKapLapMapKapNapJapOapOapOapPapQapOapRapSapTapUapVapWapXapmapnapYapYamKapZapZapZamKaqaamKaabaloaabbikajkajPajmbikbikajkajPajmbikbikajkajPajmaabaabbikaabaiIaabbikbikagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik @@ -8813,7 +8813,7 @@ bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbi bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikapsapsapsapsapsbikbikbikbikbikbikacGacGacGaabaabaabaabavtavuavtaabaabaabaabacGacGacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikapwavvanGavwasUascavxavyavzavAashasiavBashavCavDashavEaspashavFavGavHavGavGavHavGavIapOatiavJapOavKapOavLavMavNavOavPavQavRavSavTavUavUavUavVavWavXavYavYavYbikbikbikbikbikbikbikbikbikbikbikbikaabaabbikbikbikbikaabaabavZawaavZaabaabbikaabbikbikaabaabbikbikbikbikbikbikasMawbawcawdaweawfawgatJatJatJawhawiatJawjasMbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikapsapsapsapsapsbikaahbikbikbikbikaabbikaabbikbikaabbikavtawkawlbikbikaabbikbikaabbikbikbikbikbikbikbikbikbikbikawmawmawmawmawmawmapwapwawnapwasUasUasUawoawpawqawrawrawsawrawsawrawrawtasZasZasvawvawwawuawvawwawuawyawzawzawzawzawzapOawAawBawCawCawDawCawEawFavUawGawHawIavVawJawKawLawMavWbikbikbikbikbikbikbikbikbikbikbikaabaabbikbikbikbikbikaabbikavZawNavZbikaabbikaabbikbikbikaabaabbikbikbikbikbikasMawOawPavlawQawRawSawTatJawUawVawWawXasMawYaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikapsapsapsapsapsbikbikbikbikbikbikaabbikaabbikbikaabavtavtawZaxaavtbikaabbikbikaabbikbikbikbikbikbikbikbikbikbikawmaxbaxcaxdaxeaxfaxgaxhaxiaxhaxjaxhaxhaxkaxlaxlaxlaxlaxlaxmaxnaxoaxlaxlaxlaxlaxpaxlaxlaxlaxlaxqaxraxsaxtaxuaxvaxwaxxapWaxyaxzaxAaxBaxCaxDaxEaxFavUaxGaxHaxIavVaxKaxLaxJaAZavWbikbikbikbikbikbikbikbikbikbikaabaabbikbikbikbikbikbikaabavZavZaxMavZavZaabbikaabbikbikbikbikaabaabbikbikbikbikasMaxNaxOavlasMavlavlaxPaxQasMasMasMasMasMasMaabaabbikbikbikbikbikaahbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikapsapsapsbikbikbikbikbikbikaabaabbikaabbikbikaabaxRaxSaxTaxUaxRaxVaxVbikbikaabbikbikbikbikbikbikbikbikbikbikawmaxWaxXaxYaxZayaaybaycaydayeayfaxqaxqaygaxqaxqaxqaxqaxqayhaxqaxqaxqaxqaxqaxqayiaxqaxqaxqaxqayjaxqaykaxqaylaxqaymaynapWayoaypayqayraysaytaxEayuavUayvaywayxayyayzayAayBawMavWbikbikbikbikbikbikbikbikbikaabaabbikbikbikbikbikbikbikaabayCayDayEayFayCaabbikaabbikbikaabbikbikaabaabbikbikayGasMavlasMavlayHayIayHayJayHayHayHayKayGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikapsapsapsbikbikbikbikbikbikaabaabbikaabbikbikaabaxRaxSaxTaxUaxRaxVaxVbikbikaabbikbikbikbikbikbikbikbikbikbikawmaxWaxXaxYaxZayaaybaycaydayeayfaxqaxqaygaxqaxqaxqaxqaxqayhaxqaxqaxqaxqaxqaxqayiaxqaxqaxqaxqaSWaxqaykaxqaylaxqaymaynapWayoaypayqayraysaytaxEayuavUayvaywayxayyayzayAayBawMavWbikbikbikbikbikbikbikbikbikaabaabbikbikbikbikbikbikbikaabayCayDayEayFayCaabbikaabbikbikaabbikbikaabaabbikbikayGasMavlasMavlayHayIayHayJayHayHayHayKayGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabbikbikbikbikbikbikbikbikbikbikaxVaxVaxVaxVaxVaabaabaxRayLayMayNaxRayOaxVbikbikaabbikbikbikbikbikbikbikbikbikbikawmayPayQayRaySayTawmayUayVayWayXayYayZazaazbazcayXayXayXazdayXayXayXayXayXayXazeazfazgazgazgazhaziazjaziazkazlazmaznapWazoazoazoazoazoazoaxEaxFavUazpaxHazqavVcscazraxJcvYavWbikbikbikbikbikbikbikbikaabaabbikbikbikbikbikbikbikbikaabayCazsaztazuayCaabbikaabbikbikaabbikbikayGazvayGazvayGayKayHayHayHayHayHayHayHayHayHazwazxayGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabbikazyazzazybikbikbikaxVaxVaxVaxVazAazBazCaxVbikbikaxRazDazEazFazGazHaxVbikbikaabbikbikbikbikbikbikbikbikbikbikawmawmawmawmawmawmawmazIazIazJazKazKazLazMazNazOazPazPazOazQazOazRazRazRazRazRazRazSazRazRazTazUazVazWazIazXazIazYazIazIaqVazZaovaovaovaovaAaaxFavUaAbaxHaAcavVaAdayAayBawMavWbikbikbikbikbikbikbikbikaabaabbikbikbikbikazyaAeazybikaabayCaAfaAgaAhayCaAiaAiaAiaAiaAiazvazvazvayGaAjayHazwazxayJayHayJayJayJayGaAkayGayGayGayGayGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabbikaAlaAmaAlbikbikaabaxVaAnaAoaApazHaAqazHaxVaAlaAlaxRaxRaAraAsazGazHaxVaxVaAlaAlaAlaxVaxVbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikazJaAtaAuaAvazMaAwazOaAxaAyaAzaAAaABaACaADaAEaAFaAGaAHaAIaAJaAKaALaxqaxraAMaANaAOaAPaAQaAPaAPaAPaARaAPaAPaAPaAPaASaATaAUaAVaAWaAXaAYcNsaBaaUadcuavWaabaBbaBcaBcaBcaBcaBcaBdaabaabbikbikbikbikazvaBeazvbikaAiayCayCaBfaBgayCaBhayHayHayHayJaBiayHaBjayGayHayHayHaBkayJayHayGaBlaBmayGayHaBnayGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik @@ -8831,8 +8831,8 @@ bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbi bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaNhaNiaNjaNiaNiaNkaNlaNiaNiaNjaNmaNnaCuaKBaNoaMoaNpaNqaNraNsaNtaNuaKHaLHaMqaLJaNvaMqaMqaLHaKHaNwaNxaNyaNzaNAaNBaNCaNDaNEaNFaMvaabaLRaNGaNHaNIaNJaNKaLRaabaJGaJLaJMaNLaNMaNNaNOaNPaJMaGvaIKaJNaJOaNQaNRaLXaJOaNSaJOaLXaNTaNUaJOaJYaIKaJZaxqaNVazIaKbaEeaEfaCZaDaaDbaNWaNXaMcaNYaMQaMQaMQaNZaOaaObaOcaMeaOdaOeaOfaOgaOhazxbikbikbikbikbikbikbikbikbikayGaOiaOjaOkaOlaOmayGaEtayHaHVaDxayHayGbikayGayHayHayHayHaDxaOnaOoaOpaOpaOpaOpaOqaOraOsaOtaOuaOvaOuaOwaOxaOxaOyaOzaOAaOBaOCaOCbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaNhaNmaNiaODaOEaOFaOGaOHaOIaOJaOKaOLaOMaONaOOaKzaKBaOPaMoaOQaORaOSaOTaOSaOUaKHaOVaMqaOWaOXaOYaOXaOZaPaaPbaPcaPdaPdaPdaPdaPdaPdaPeaPfaMvbikaLRaPgaNHaPhaNJaPiaLRbikaJGaJLaJMaPjaPkaPlaPmaPnaJMaGvaIKaPoaPoaKPaPpaMHaKSaPqaKSaMHaPraKWaPsaPsaIKaPtazUaPuazIaKbaEeaPvaPvaPvaPvaPwaPxaPyaPzaPAaPBaPBaPBaPCaPBaPDaPEaPFaPGaPHaPEaPIaPJazxayGayGayGayGayGayGayGayGayGayGayGayGaBMayGayGayGayGayHaDxaOiazvaabazvayHaPLaPMaPNaPOaPPaPQaPRaPRaPRaPRaPSaPTaPUaPVaPWaPXaPWaPYaPWaPWaPZaQaaQbaQbaQcaQdaQeaQeaQeaQeaQeaQeaQeaQeaQeabLadbbikbikbikbikbikbikbikaaabdBaQfbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaONaQgaQhaQiaOMaQjaOFaQjaOFaQjaOFaQjaOMaQkaQlaKzaKBaQmaMoaQnaQoaQpaQqaQraQsaKHaQtaMqaQuaQvaQwaQxaQyaKHaQzaPdaPdaPdaPdaPdaPdaPdaPeaQAaMvaabaLRaQBaQCaQDaQEaQFaLRaabaJGaJLaJMaQGaQHaQIaQJaQKaJMaGvaIKaIKaIKaIKaQLaLXaJOaLYaJOaLXaQMaIKaIKaIKaIKaQNaQOaQPazIaMKaQQaQRaQRaQRaQSaQTaQUaQVaQWaQXaQVaQYaQZaRaaQVaRbaRcaRdaReaRfaMeaRgaRhaRiaRjaRjaRhaRkaRlaRlaRlaRlaRlaRmaRnaRlaRoaRpaRqaRraRsaRvaRuaTkaRwaRxaRwaRyaRzayHayGaRAaRBaRCaRDaREaRFaRGaRHaRIaRJaRIaRKaRLaRMaRNaROaRPaRQaRRaRSaRTaRUaQdaRVaRWaRXaRYaRZaSaaSbaScaSdaprbikbikbikbikbikbikbikaaabfmbifbieaQfbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaSfaOFaOFaNjaOMaQjaOFaQjaSgaQjaOFaQjaOMaQkaQlaKzaKBaShaMoaMoaSiaSjaSkaSjaMoaKHaKHaKHaKHaSlaSmaSnaSoaKHaSpaSqaSraPdaPdaSsaPdaStaSuaSvaMvbikaLRaLRaSwaSxaSwaLRaLRbikaJGaJLaJMaSyaSzaSAaQJaSBaJMaGvaIKaJNaJOaSCaNRaLXaJOaLYaJOaLXaNTaSDaIKaIKaIKaSEaSFaSGaSHaSIaSJamKamKaSKaSLaSMaSNaMcaSOaMQaMcaSPaMcaSQaMcaSRaSSaSTaSUaSVaSWaSXaSYaSZaTaaTbaTcaTdaTbaTbaTeaTbaSYaTfaSYaOvaTgaSYaThaTiaTjaTmaTlaToaTnaTqaTpaWIaTraOuaTsaTtaTuaTvaTwaTxaTyaTzaRHaTAaTBaRIaTCaTDaTEaTFaROaTGaRQaRRaRSaTHaTIaQdaTJaTKaTKaTLaScaScaScaScaSdaprbikbikbikbikbikbikaaabfmbigbijbihbieaQfbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaONaTMaTNaQiaOMaQjaOFaQjaOFaQjaOFaQjaOMaQkaQlaKzaKBaTOaTPaTQaTRaTSaTSaJraTTaTUaTVaTWaTXaTYaTZaMqcoCaKHaUbaUcaUdaUeaUfaUgaUhaUiaUjaUkaMvbikaabbikaUlaUmaUlbikaabbikaCuaJLaJMaUnaUoaUpaUqaUraJMaGvaIKaUsaUtaKPaUuaUvaKSaUwaKSaUxaUyaUzaIKaUAaUBaUCaUDaUEaUFaUGaUHbikbikaSKaUIaUJaUKaMcaULaUMaMQaMQaUNaMQaMQaMQaUOaMeaUPaMeaUQaURaMeaUSaMeaUTaUUaUVaUWaUXaUYaUTaUZaVaaVbaVcaVbaVbaVbaVbaVdaWLaVbaVeaYEaVfaVgaVbaVhayHaRDaViaTvaVjaVkaVlaVmaVnaVoaVpaVqaRIaVraVsaVtaVuaVvaTGaRQaRRaRSaRTaVwaQdaVxaTKaTKaTLaScaScaScaScaSdaprbikbikbikbikbikbikbdBblqbjMbmKbjMbqQbdBbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaSfaOFaOFaNjaOMaQjaOFaQjaSgaQjaOFaQjaOMaQkaQlaKzaKBaShaMoaMoaSiaSjaSkaSjaMoaKHaKHaKHaKHaSlaSmaSnaSoaKHaSpaSqaSraPdaPdaSsaPdaStaSuaSvaMvbikaLRaLRaSwaSxaSwaLRaLRbikaJGaJLaJMaSyaSzaSAaQJaSBaJMaGvaIKaJNaJOaSCaNRaLXaJOaLYaJOaLXaNTaSDaIKaIKaIKaSEaSFaSGaSHaSIaSJamKamKaSKaSLaSMaSNaMcaSOaMQaMcaSPaMcaSQaMcaSRaSSaSTaSUaSVaTeaSXaSYaSZaTaaTbaTcaTdaTbaTbaUjaTbaSYaTfaSYaOvaTgaSYaThaTiaTjaTmaTlaToaTnaTqaTpaWIaTraOuaTsaTtaTuaTvaTwaTxaTyaTzaRHaTAaTBaRIaTCaTDaTEaTFaROaTGaRQaRRaRSaTHaTIaQdaTJaTKaTKaTLaScaScaScaScaSdaprbikbikbikbikbikbikaaabfmbigbijbihbieaQfbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaONaTMaTNaQiaOMaQjaOFaQjaOFaQjaOFaQjaOMaQkaQlaKzaKBaTOaTPaTQaTRaTSaTSaJraTTaTUaTVaTWaTXaTYaTZaMqcoCaKHaUbaUcaUdaUeaUfaUgaUhaUiaUZaUkaMvbikaabbikaUlaUmaUlbikaabbikaCuaJLaJMaUnaUoaUpaUqaUraJMaGvaIKaUsaUtaKPaUuaUvaKSaUwaKSaUxaUyaUzaIKaUAaUBaUCaUDaUEaUFaUGaUHbikbikaSKaUIaUJaUKaMcaULaUMaMQaMQaUNaMQaMQaMQaUOaMeaUPaMeaUQaURaMeaUSaMeaUTaUUaUVaUWaUXaUYaUTbwNaVaaVbaVcaVbaVbaVbaVbaVdaWLaVbaVeaYEaVfaVgaVbaVhayHaRDaViaTvaVjaVkaVlaVmaVnaVoaVpaVqaRIaVraVsaVtaVuaVvaTGaRQaRRaRSaRTaVwaQdaVxaTKaTKaTLaScaScaScaScaSdaprbikbikbikbikbikbikbdBblqbjMbmKbjMbqQbdBbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaVyaVzaNiaODaOMaOFaVAaVBaOFaOFaVCaVAaOMaONaVDaKzaKBaVEaVFaVGaVHaVGaVGaVGaVGaVGaVIaJraKHaVJaVKaVJaKHaKHaVLaVMaVLaMuaMuaMuaMuaVLaVNaVOaMvaVPaVQaVQaVRaVSaVRaVQaVQaVTaVUaVVaJMaVWaJMaVXaVYaVZaJMaWaaIKaIKaIKaIKaWbaWcaWdaWeaWdaWcaWbaIKaIKaWfaUDaUDaUDaUDaWgaWhaWiaWjaWkaSKaWlaUJaWmaMcaMcaMcaMcaMcaMcaMcaMeaMeaUOaWnaWoaWpaWqaWraWsaWuaWtaUTaWvaWwaWxaWyaWzaUTaWAaWBaWCaWDaWCaWEaWFaWGaWHaYFaWJaWKaYGaWMaWNaVbaWOaRtaWPaWQaWRaVjaWSaVlaVmaWTaRIaWUaWVaWWaWXaWYaTFaTFaRIaRIaRQaRRaRSaTHaWZaQdaXaaXbaXcaXdaXeaXfaScaScaXgaprbikbikbikbikbikaaabfmbqRbmKbmKbmKbqSbieaQfbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaVyaNiaNjaNiaNiaNkaNlaNiaNiaNjaVzaXhaCuaXiaVEaInaXjaXjaXkaXjaXlaDEaXmaXnaJraXoaXpaXqaXqaXraXsaXqaXtaXqaXqaXqaXuaXqaXqaXvaXwaXxaXyaXzaXAaXyaXBaXyaXyaXyaXyaXCaXDaXEaXqaXqaXFaXGaXHaXIaXJaXKaXLaXMaXRaXPaXPaXPaXPaXPaXPaXPaXQaXWaXXaXSaXTaXUaXVaXYaZHaXZaUDaUDaYaaYbaYcaYbaYcaYdaYeaYfaYgaYhaYiaMeaYkaYjaYmaYlaYnaYnaYoaYpaYqaYraUTaYsaYtaYuaYuaYvaUTaYwaYxaYyaYzaYAaYAaYBaYCaYDbbHaYHcIgbjrcYtcSNaVbaVhaKsaRDaYIaYJaYJaYKaYLaYJaYMaRIaYNaYOaRIaYPaYQaTFaYRaRIaYSaYTaYUaRSaQbaQbaQdaYVaQeaYWaYXaQeaQeaQeaQeaQeabLadbbikbikbikbikbqUbqTbqVbmKbmKbrUbthbmKbqUbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaInaYYaKzaKzaKzaKzaKzaKzaYYaInaMlaCuaYZaZaaInaZbaZcaZdaZdaZeaDEaZfaXnaZgaZhaXpaZiaZjaZkaZlaZlaZmaZnaZnaZnaZoaZpaZpaZqaZraZsaZlaZlaZtaZpaZvaZwaZxaZxaZyaZzaZAaZuaZuaZuaZBaXGaXHaXIaXJaXJaXJaXJaZCaXJaXJaXJaXJaXJaXJaZDaZEaZNaZFaZGaZJaZIaZLaZKaZMaZNaZNaZNaZOaZPaZPaZPaZQaYiaYiaYiaZRaZSaYiaMebbsaZUaYnaYnaZVaYnaZWaZXaYqaZYaUTaZZbaababbacbadaUTbaebafbagbahbaibajaVbbakbalcZqaVbaVbaVbaVeaVbaVbaVhayHaRDbamaTvaTvaTvbanaTvbaoaRIaRIaRIaRIaRIaRIaRIbapaRIaRSaRSaRRaRSbaqbaraQbbasbatbaubavbawbaxbaybazbaAbikbikbikbikaaabtibtjbtibtibtibtkbtibtlbtibtjbtiaQfbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik @@ -8851,30 +8851,30 @@ bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbi bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbBGbDCbBHdjhbDDdjidjidjkdjjbtqbxZbjNaInaInaKBbrWbrXbrYbrXbrXbrXbrXbrZbsabsbbcsbcsbscbsdblCbmUbsebsfbpAblCbsgbshbsgbcobcobcobcobsibcobsjbskbngbslbsmbpFbsnbsobngboCbnmbspbpIbpIbpIbsqbnmbsrbpNboIbpObssbrwbrxbstbsubrAbpTbsvbmgbswbsxbnvbsybszbsAboSbsBbsCbsDbsEbsFbsGbsHbsIbsJbqbbsKbnAbsLbsMbqibqjbqkbqlbqnbsNbqnbqnbqnbqnbsObsPbsPbsPbsPbsQbqnbqnbqnbqnbsRbqnbsNbsSbqnbsTbsUbsVbqnbsWbsXbsXbsYbsZbtabtbbmFbmFbmFbmFbmFbmFbmFbtcbtdbmFbmFbmFbmFbmFbmFbmFbmFbmFbmFbmFbtebtfbtfbtgddSddRddUddTddTddTddTddVddWbaAaabbikbikaabbqUddXddZddYdeabqUbikbqUdecdebdecdecbqUbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbBGbDCdjldjidjidjidjidjidjidjideHdjnaGddjnaJrbtrbrXbtsbttbtubtvbtwbrZbtxbtybtzbtzbtAbtBblCblCblCbosbtCborbcsbcsbcsbcsbtDbejbejbskbejbsjbskbngbtEbtFbtGbtHboAbngbtIbnmbtJbpIbpIbpIbtKbtLbtMbpNbtNbnqbtObtPbpRbpRbpRbpRbpTbtQbmgbtRbtSbmgbmgbmgbmgbmgbmgbmgbnzbnAbqrbtUbtVbtWbtXbtYbtZbnAbvubuabuabucbucbucbucbucbucbucbudbudbuebufbugbugbufbuebudbudbuhbuhbuhbuhbuhbuhbuibuibujbuibuibuibuibuibukafkbumbunbmFbmFbuobuobupbupbupbupbupbuqbtfbtfbtebtfbmFbtfbtfbtfbtfburbusbusbusbusdedbuubuubuubuubuubuubuubuubuubikbikbikbikbdBddYddYddYdeebqUbikbqUdefdebdebdegbqUbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbBGbDCdjodjpdjpdjidjpdjpdjpbtqdjqaInaInaInaKBbuvbrXbuwbuxbuxbuxbuybrZbejbuzbuAbuBbuCbuDblCbmUbuEbosbuFborbuGbuHbuIbuJbuKbuLbuLbuMbuLbuNbuObngbuPbuQbuRbuSbuTbuUbuVbnmbuWbpIbpIbuXbuYbuZbtMbpNbvabnqbvbbvcbvdbvebvfbvgbvhbnqbnqbvibvjbvkbvlbvmbvlbvnbvjbikbvobnAbvpbvqbvrbvsbtXbqbbvtbnAbxebvwbxfbucbzcbvybvzbvAbvBbvCbvDbvEbvFbvGbvHbvHbvGbvIbvJbvKbuhbvLbvMbvNbvObuhbvPbvQbvRbvSbvTbvUbvUbvVbukafNbvXbvYbvZbvYbwabwabupbwbbwcbwdbupbwebwfbwebupbwgbtfburbusbwhbwibwhbusbwjbwkbusdeicngbxXbwobikbikbikbikbikbikbikbikbikbikbqUddXddZddYdejbqUbikbqUdekdekdekdekbqUbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjrbtqbtqbtqbtqbtqbtqbtqbtqdjqbikbikaInbwqbdFbwrbrXbwsbrXbwtbuxbwubwvbrZbwwbwxbwybwzbuDblCblCblCblCblCblCbwAbwBbwCbwDbwEbngbngbngbngbngbngbngbwFbngbngbngbwGbngbwHbwIbwJbpIbpIbpIbwKbtLbwLbpNbwMbnqbwNbvcbnrbnrbnrbnqbwObnqbwPbwQbwRbwSbwTbwUbwVbwWbwXbwYbwZbnAbxabqbbxbbxcbtXbxdbqbbnAbzbbxgbANbucbxhbxibxjbxkbxlbxmbxnbvHbvHbvHbvHbvHbvHbvHbvHbxobxpbxqbxrbxsbxtbuhbxubxvbxwbxxbxybxzbxvbxAbukbDdbvXbxCbxDbxEbxFbxFbxGbxHbxIbxJbxKbxLbxMbxNbupbxObxPbxQbusbxRbxSbxTbxUbxUbxVbusbxWbxXbxYbwobikbikbikbikbikbikbikbikbikbikbDybtldemdemdemdenbikdeodepdepdepbtlbDzbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjrbtqbtqbtqbtqbtqbtqbtqbtqdjqbikbikaInbwqbdFbwrbrXbwsbrXbwtbuxbwubwvbrZbwwbwxbwybwzbuDblCblCblCblCblCblCbwAbwBbwCbwDbwEbngbngbngbngbngbngbngbwFbngbngbngbwGbngbwHbwIbwJbpIbpIbpIbwKbtLbwLbpNbwMbnqbIDbvcbnrbnrbnrbnqbwObnqbwPbwQbwRbwSbwTbwUbwVbwWbwXbwYbwZbnAbxabqbbxbbxcbtXbxdbqbbnAbzbbxgbANbucbxhbxibxjbxkbxlbxmbxnbvHbvHbvHbvHbvHbvHbvHbvHbxobxpbxqbxrbxsbxtbuhbxubxvbxwbxxbxybxzbxvbxAbukbDdbvXbxCbxDbxEbxFbxFbxGbxHbxIbxJbxKbxLbxMbxNbupbxObxPbxQbusbxRbxSbxTbxUbxUbxVbusbxWbxXbxYbwobikbikbikbikbikbikbikbikbikbikbDybtldemdemdemdenbikdeodepdepdepbtlbDzbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaLyaCuaKzaKzaInaInaInaInaInaInaInaKBaJrbyabrXbybbycbydbyebyebyfbygbyhbyibyibyjbykbtzbtzbtzbtzbtzbtzbylbymbymbynbyobngbypbyqbyrbysbytbyubyvbywbngbyxbyybyzbuVbtLbwJbpIbpIbyAbnmbnmbyBbyCbyDbyEbyFbyGbyHbyIbyJbyKbyLbyMbyNbyObwXbyPbyQbyPbyQbyRbwXbwYbySbyTbyUbyVbyWbyXbyYbyZbzabyTbAObvwbvwbucbzebzdcSCbzfbzgbucbxnbvHbvHbvHbvHbvHbvHbvHbzhbzibzjbzkbzlbxqbzmbuhbznbzobzpbxxbxAbxzbzqbzrbukbIbbztbzubzvbzvbzwbzxbzybzzbzAbzAbzAbzBbzAbzCbzDbzEbzFbzGbzHbzIbzJbzKbzKbzKbzLbusbzMbxXbzNbwobikbikbikbikbikbikbikbikbikbikbikbDyderderderbDzbikbDyderderderbDzbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaIndjtdjsdjuaIoaIoaIobdFaJraJrbrXbzVbrXbzWbzXbzYbzZbrXbcsbAabAabAbbAcbAbbAbbAbbAcbAbbcsbcsbngbngbngbAdbngbAebAebAebAebAebAebyvbAfbAgbAhbAibAjbAkbAlbAmbAnbAobApbAqbArbAsbAtbAubAvbAwbAwbAxbAybAzbAAbABbACbADbAEbvjbAFbwVbAGbwTbAHbvjbikbAIbAJbAKbALbAJbAJbAJbAJbAMbnAbCBbvwbCCbAPbAQbARbASbATbAUbucbAVbAWbAXbAYbAZbBabBbbBcbvHbBdbuhbBebzlbBfbBgbuhbBhbBibxwbxxbxAbxzbxvbxAbukbDdbvXbBjbBkbBlbBmbBnbBobBpbBqbBrbBsbBtbBubBvbupbBwbBxbBybusbBzbBAbBBbxUbBCbBDbusbzMbxXbBEbwobikbikbikbikaabaabaabaabaabaabaabaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaIndjwdjvdjuaJqaJqbBIbBJaJqbBKbrXbBLbycbBMbBNbBObBPbrXbikaabaabdetdesdesdesdesdesdeubikbikbBRbBSbBTbBUbBVbAebAebAebAebAebAebBWbBXbBYbBZbCabCbbCcbCdbnmbnmbCebCfbnmbnmbCgbChbCibCjbCkbClbClbCmbClbCnbCobCpbCqbAEbvjbCrbvlbvlbvlbCsbvjbikbAIbAJbCtbCubCvbCwbCxbCybCzbCAbEsbvwbEtbucbvxbCDbCEbCFbCGbCHbCIbCJbCKbCLbCMbCNbCObCPbCQbCRbCSbCTbzlbCUbCVbuhbCWbCXbCYbCZbDabDbbDcbvVbukbIcbDebDfbDgbDgbDhbDibDjbDkbDlbDmbDnbDobDpbDqbupbDrbBxbDsbusbDtbDubDvbxUbxUbDwbusbxWbxXbDxbwobikbikbikbikaabbDAbFfbDBbFfbDBbFfbFgaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaIndjudjubDEaInaInaLyaCubDFbDGbrXbDHbycbycbycbrXbrXbrXbikbikbikdewdevdevdexdevdevdewbikbikbBRbDIbAebDJbDKbDKbDKbDKbDKbDLbDMbDKbDNbDObDPbDQbDRbDSbDTbnmbDUbpIbDVbDWbtLbDXbDYbDZbCjbCjbCpbEabEbbEcbEdbEebCpbvibAEbvjbEfbEgbEhbqtbEjbvjbikbEkbAJbElbEmbEnbEobEpbEobEqbErbFWbvwbHBbucbEubEubEvbEwbExbEybEzbEAbEBbECbEDbEDbEEbEFbEGbEHbuhbCTbEIaJSbuhbuhbuibEKbELbEMbuibukbukbukbENbIdbvXbEPbBkbBlbEQbDgbDjbERbESbESbETbxJbEUbEVbEWbEXbxPbEYbusbEZbFabxUbFbbFcbFdbusbzMbFebwobwobikbikbikbikaabbFhbFjbFibGSbFkbGTbFhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjxdjydjydjydjydjAdjzdjydjydjydjydjBbikbikbikbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevdewbFnbBRbBRbFobFpbFqbFrbFrbFrbFpbAebyvbAfbAebAebAgbFsbFtbFubFvbFwbFxbFybpIbpIbFzbtLbDXbDYbFAbFBbFCbFDbFEbFFbFGbFHbFIbCpbFJbFKbwRbFLbFMbFNbFObFPbwXbwYbFQbAJbFRbFSbFTbEobFUbEobFVbErbJebvwbKObucbFYbFZbGabGbbGcbGdbGebGfbGgbGgbGhbGibGgbGgbGjbGkbGlbGmbGnbGobGpbGqbGrbGsbGtbGubGvbGwbKhbIebKjbKibvXbGBbGCbGDbGEbGFbxGbGGbESbESbGHbGIbEUbGJbupbGKbGKbGLbusbGMbGNbGObGPbxUbGQbusbzMbGRbwobikbikbikbikbikaabbGUbIybFibFkbFibKkbDBaabbikbikbikbikbikaahbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjxdjCdjEdjDdjGdjFdjIdjHdjFdjJdjLdjKdjydjydjBbikbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevbGWbmrbNkbmrbGXbGYbGZbGYbGYbGYbGYbHabyvbAfbHbbngbngbHcbpIbAibFvbHdbtLbHebHfbHfbHgbHhbHibHjbHkbHlbHmbHnbHobHpbHqbHrbHsbCpbCqbAEbvjbEibHubHvbHwbHxbvjbikbEkbHybHzbHybHybHybHybHybHAbHybKPbvwbKObucbHCbHDbHDbHEbHFbEybHGbHHbHIbHJbHKbHLbHMbHIbHNbHObHPbHQbHRbHSbHTbHTbHUbHVbHWbHXbHYbLibLCbLBbNdbNcbNebIfbIfbIgbIhbIibxGbIjbxJbxJbIkbIlbEUbImbInbIobIpbIqbIrbIsbItbIubIvbIubusbusbIwbEXbGxbLDbLDbLDbLDbLDbGxbFhbFkbFibFkbFkbGTbFhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjxdjydjHdjHdjHdjMdjFdjNdjNdjFdjOdjHdjHdjQdjPdjydjBbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevbLFbKmbIAbKmbIBbAebFqbFrbFrbFrbAebAebyvbAfbICbIDbngbIEbpIbAibFvbIFbtLbIGbpIbrsbpIbIHbDXbvvbIIbHlbHmbIJbIKbILbIMbINbIObIPbIQbIRbISbISbITbIUbIVbvjbvjbikbEkbHybIWbIXbIYbIZbJabJbbJcbJdbMhbvwbKObCHbJfbJgbJhbJibJjbEybJkbJlbJmbJnbJobJpbJqbJrbJsbJtbJubJvbJwbJxbJybJzbJAbJBbJCbJDbJEbGwchEbIecnfcjubvYbJIbJJbJKbJLbJLbxGbJMbJNbJObxGbJPbEUbJQbxGbJRbJSbJTbJUbJVbJWbJXbJYbJZbKabKbbKcbKdbKebKfbKfbIxbJFbJHbKgbKlbFfbDBbNhbDBbFfbLEaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjxdjydjSdjRdjUdjTdjVdjFdjWdjHdjXdjHdjHdjHdjHdjHdjMdjFbikbikbikbikbikbikbikbikbikbikdewdeCdevdevdevcOfdewbFnbBRbBRdezbAebFqbAebAebAebAebAebyvbAfbICbKobngbKpbpIbKqbKrbpIbKsbpHbpIbpIbKtbtLbDXbvvbIIbHlbHmbCpbKubKvbqzbKxbKybKzbKAbKBbKCbKDbwXbKEbKFbwXbwYbwYbKGbHybKHbKIbKJbKKbKLbKLbKMbKNbMibvwbMjbucbEybKQbEubKRbKSbEybJkbKTbKUbKVbKWbKXbKYbKUbJkbKZbLabLbcchbLbbLcbLbbLdbLdbLebLfbLgbLhbLhcpLbLgbLgbLhbLhbLjbLkbLjbLjbxGbxGbxGbxGbxGbLlbLmbBobxGbLnbLobLpbLqbLqbLrbLsbLtbLubLvbLwbLxbLybLzbLAbLAbEObGAbHZbGxbGzbGzbGzbIabGxbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjZdjYdjHdjHdkadkadkadjFdjHdjHdjFdkbdkddkcdkfdkedkgdjFbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevbWobKmbIAbKmbFobLGbLHbAebAebAebLGbAebyvbAfbICbLIbngbLJbpIbAibFvbpIbnmbLKbpIbpIbLLbtLbDXbvvbLMbHlbLNbCpbLObLPbLQbLRbLSbKzbLTbLUbLVbLWbLXbLYbLZbMabikbikbikbHybMbbMcbMdbMdbMebMfbMgbHybNSbNRbKObNTbMkbMlbMmbMnbMobMpbMqbJlbMrbMsbMtbMubMvbMrbJkbKZbMwbMxbMybMzbMAbMBbLdbMCbMDbMEbLgbMFbMGbMHbMIbMJbMKbMLbKabMMbLqbLqbMNbMObLqbMPbMQbMRbMSbMTbLqbMUbiTbMWbMXbMYbMZbNabNbbNabNabNabIwbEXbGxbLDbLDbGxcnccndcnebOQbOSbUHbYdbGzbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkhdjYdjHdjHdjHdjHdjHdkidjHdjHdjFdjydjydjydjydjydjydkjbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevdeEdeDdeGdeDbNlbNmbNnbNnbNnbNnbNnbNobNpbNqbICbNrbngbNsbNtbNubNvbNwbNxbNybNybNybNzbNzbNAbvvbNBbNCbHmbCpbNDbNEbNFbKvbNGbKzbNHbNIbNJbNKbNLbFNbNMbMabikbikbikbHybNNbNObNPbNQbMebMfbMgbHybPibNUbQObPjbNVbNWbNXbNYbNZbOabObbOcbKUbKUbOdbOebKUbKUbJkbKZbOfbOgbOhbOibOjbOkbLdbOlbOmbOnbOobOpbOqbOrbOrbOsbOtbOubOvbOwbJVbJVbOxbJVbOybJVbOzbJVbOAbOBbOBbOCbODbOEbOFbOGbOHcsjbOJbOKbOLbNabOMbONbikbikbikbGxbORbJFbOPbOQbKgbOObNibGxbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjxdjydjHdjHdjHdjMdjFdjNdjNdjFdjOdjHdjHdjQdjPdjydjBbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevbLFbKmbIAbKmbIBbAebFqbFrbFrbFrbAebAebyvbAfbICbKobngbIEbpIbAibFvbIFbtLbIGbpIbrsbpIbIHbDXbvvbIIbHlbHmbIJbIKbILbIMbINbIObIPbIQbIRbISbISbITbIUbIVbvjbvjbikbEkbHybIWbIXbIYbIZbJabJbbJcbJdbMhbvwbKObCHbJfbJgbJhbJibJjbEybJkbJlbJmbJnbJobJpbJqbJrbJsbJtbJubJvbJwbJxbJybJzbJAbJBbJCbJDbJEbGwchEbIecnfcjubvYbJIbJJbJKbJLbJLbxGbJMbJNbJObxGbJPbEUbJQbxGbJRbJSbJTbJUbJVbJWbJXbJYbJZbKabKbbKcbKdbKebKfbKfbIxbJFbJHbKgbKlbFfbDBbNhbDBbFfbLEaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjxdjydjSdjRdjUdjTdjVdjFdjWdjHdjXdjHdjHdjHdjHdjHdjMdjFbikbikbikbikbikbikbikbikbikbikdewdeCdevdevdevcOfdewbFnbBRbBRdezbAebFqbAebAebAebAebAebyvbAfbICbLwbngbKpbpIbKqbKrbpIbKsbpHbpIbpIbKtbtLbDXbvvbIIbHlbHmbCpbKubKvbqzbKxbKybKzbKAbKBbKCbKDbwXbKEbKFbwXbwYbwYbKGbHybKHbKIbKJbKKbKLbKLbKMbKNbMibvwbMjbucbEybKQbEubKRbKSbEybJkbKTbKUbKVbKWbKXbKYbKUbJkbKZbLabLbcchbLbbLcbLbbLdbLdbLebLfbLgbLhbLhcpLbLgbLgbLhbLhbLjbLkbLjbLjbxGbxGbxGbxGbxGbLlbLmbBobxGbLnbLobLpbLqbLqbLrbLsbLtbLubLvbLIbLxbLybLzbLAbLAbEObGAbHZbGxbGzbGzbGzbIabGxbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjZdjYdjHdjHdkadkadkadjFdjHdjHdjFdkbdkddkcdkfdkedkgdjFbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevbWobKmbIAbKmbFobLGbLHbAebAebAebLGbAebyvbAfbICbNrbngbLJbpIbAibFvbpIbnmbLKbpIbpIbLLbtLbDXbvvbLMbHlbLNbCpbLObLPbLQbLRbLSbKzbLTbLUbLVbLWbLXbLYbLZbMabikbikbikbHybMbbMcbMdbMdbMebMfbMgbHybNSbNRbKObNTbMkbMlbMmbMnbMobMpbMqbJlbMrbMsbMtbMubMvbMrbJkbKZbMwbMxbMybMzbMAbMBbLdbMCbMDbMEbLgbMFbMGbMHbMIbMJbMKbMLbKabMMbLqbLqbMNbMObLqbMPbMQbMRbMSbMTbLqbMUbiTbMWbMXbMYbMZbNabNbbNabNabNabIwbEXbGxbLDbLDbGxcnccndcnebOQbOSbUHbYdbGzbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkhdjYdjHdjHdjHdjHdjHdkidjHdjHdjFdjydjydjydjydjydjydkjbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevdeEdeDdeGdeDbNlbNmbNnbNnbNnbNnbNnbNobNpbNqbICbWSbngbNsbNtbNubNvbNwbNxbNybNybNybNzbNzbNAbvvbNBbNCbHmbCpbNDbNEbNFbKvbNGbKzbNHbNIbNJbNKbNLbFNbNMbMabikbikbikbHybNNbNObNPbNQbMebMfbMgbHybPibNUbQObPjbNVbNWbNXbNYbNZbOabObbOcbKUbKUbOdbOebKUbKUbJkbKZbOfbOgbOhbOibOjbOkbLdbOlbOmbOnbOobOpbOqbOrbOrbOsbOtbOubOvbOwbJVbJVbOxbJVbOybJVbOzbJVbOAbOBbOBbOCbODbOEbOFbOGbOHcsjbOJbOKbOLbNabOMbONbikbikbikbGxbORbJFbOPbOQbKgbOObNibGxbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkkdjydkmdkldkodkndjHdjFdjHdjHdjFdjHdkqdkpdkrbikbikbikbikbikbikbikbikbikbikbikbikbikdewdevdevdevdevdevdewbFnbBRbBRbngbngbBRbBRbBRbBRbBRbngbngbBRbBRbBRbngbOVbOVbOVbOWbOVbNzbOXbOYbOZbPabNzbDXbvvbPbbPcbPcbCpbCpbCpbCpbCpbPdbKzbISbIRbPebNKbPgbPhbMabMabvjbvjbvjbHybHzbHybHybHybHybHybHybHybQPbvwbQQbPkbPlbPmbPnbPlbPlbPobPpbPqbPrbPsbPtbPubPvbPwbMqbPxbPybPzbPAbPBbPCbPDbPEbPFbPGbPHbPIbPJbPKbPLbMHbPMbLhbLhbPNbPObPNbPPbPQbPPbPPbPPbPRbPSbPTbPTbPUbPVbPWbPXbOFbPYbPZbQabQbbQcbQdbNabOMbONbikbikbikbGxbNfbNgbGxbKgbGxbGxbKgbGxbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjydjydjydjydjydjydjFdjOdjHdksdjHdkudktdkvbikbikbikbikbikbikbikbikbikbikbikbikbikdeJdeIdevdeKdevdeLdeMbikbikbikbikbikbikbikbikbikbikbQgaabbikbikbikbikbOVbQhbQibQjbQkbNzbQlbQmbQnbQobNzbDXbvvbQpbQqbQrbQsbQtbQubQvbQwbQxbQybQzbQAbQBbQCbQDbQEbQFbQGbQHbQIbQJbQKbQLbQMbQRbuabQSbvwbShbSgbSibvwbSjbPlbQTbQUbQVbQWbQXbJkbQYbQZbRabRbbRcbRdbRebRfbRgbRhbRibLbbRjbOibRkbRlbRmbRnbRobRpbLgbRqbMHbMHbMHbRrbLhbPNbRsbRtbRubRvbRwbRxbRybPPbRzbRAbRBbPTbPTbRCbPWbRDbREbtTbRGbRHbRIbRJbRKbNabRLbwobikbikbikbGxbGxbGxbGxbikbikaabbikbikbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjxdjydkxdkwdkydkxdkzdjFdjHdjHdjFdjHdkBdkAdkCbikbikbikbikbikbikbikbikbikbikbikbikbikdeOdeNdePdePdePdeNdeQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbOVbRMbRNbRObRPbNybRQbRRbRSbRTbNzbDXbRUbRVbvvbvvbFXbRWbubbQNbQMbRXbRYbRXbRZbSabSabSbbScbSdbSabSabSebSabSfbSabQMbQRbuabSlbSkbTJbTIbTLbTKbvwbPlbSmbQUbSnbSobSpbJkbSqbSrbSsbStbSubSvbSwbSxbJkbSybSzbLbbSAbSBbSBbSCbSDbSEbSFbSGbLgbSHbSIbMHbSJbSKbLhbPNbSLbSMbSNbSObSPbSQbSRbPPbSSbRAbSTbSTbPTbSUbSVbSWbSXbSYbSZbTabTbbTcbTdbNabOMbwobTebikbikbikbikbikbikbikbikaabbikbikbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdjZdjYdkDdkDdkDdkDdkDdkEdjHdjHdjFdjydjydjydjydjydjydjBbikbikbikbikbikbikbikbikbikbikbikdeRdeSdeSdeSdeTbikbikbikbikbikbikbikbikbOUbOTbQebOTbQfbikbikbikbOVbOVbTjbTkbRObTlbNybTmbQnbTnbTobNzbDXbTpbTqbTrbTsbTtbTubTvbTwbTxbTybTzbTAbTBbTCbTCbTDbTEbTEbTEbTEbTEbTEbTFbTGbTHbTNbTMbTPbTOdlidjmdlkdljdllbPlbTQbQUbQVbTRbSpbJkbSqbSrbTSbTTbTUbTVbTWbTXbJsbTYbTZbLbbLbbUabUabLbbSDbLdbUbbUcbLgbUdbUebUfbUgbUhbLhbPNbUibUjbUkbUlbUmbUnbUobPPbUpbRAbSTbSTbPTbUqbUrbUsbUtbUubUvbUwbUxbUybUzbNabOMbUAbUBbUAbUCbUDbUCbUCbUCbikbikbikbikaabaabaabaabbikbikbikbikbikbikbikbikbikbikbikbikaabaabaabaabaabbikbikbikbikaabaabaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkhdjYdkDdkDdkDdkFdkGdjFdjHdjHdjFdjHdjHdjHdkHdjHdkIdjFbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbTgbTfbTibThbTgbUIbUIbUIbOVbUJbUKbULbUMbUNbUObUPbUQbUQbURbNzbUSbUTbUUbUVbUVbUWbUXbUYbUVbUZbUZbVabVbbVcbUZbUZbVdbQMbQMbVebVfbVfbVfbVgbVhbVfbVfbVibVjbVkbVkbVlbVkbVmbVnbVkbVobVpbVqbPlbVrbVsbSqbVtbVubVvbVwbVxbVybVubJkbSybVzbVAbVBbVCbVDbVEbVFbVGbVHbVIbVJbVKbVLbVMbVNbVObLhbPNbPNbVPbVQbVRbVSbVRbVTbPPbVUbVVbVWbVWbPTbVXbVYbVZbNabNabNabNabNabNabNabNabOMbUAbWabWbbWcbWdbWdbWebUCbikbikbikbikbikbikbikaabbikbikbikbikbikbikbikbikbikbikbikbikaabbWfbWfbWfbWfbWgbWgbWhbWhbWhbWhbWiaabaabbikbikbikaahbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkkdjydkKdkJdkDdkLdkMdjFdjHdjHdkNdjHdjHdjHdkOdjHdkPdjFbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbQebUEbUFbUEbQebUIbWmbWlbOVbWnbWpbWqbWrbWsbNzbWtbWubWvbWwbNzbUTbUTbWxbUVbWybWzbWAbWBbWCbUZbWDbWEbWFbWGbWHbUZbWIbWJbWKbVfbWLbWMbWObWNbWPbWQbWRbWSbWTbWUbWVbWWbWXbWYbWZbXabXbbXcbXdbXebXfbXgbXhbSrbXibXjbXkbXlbXmbXnbJsbXobXpbXqbXrbVDbXsbXsbXtbXubLgbXvbXwbXxbXybXzbXAbXBbLhbXCbXDbXEbXFbXEbXEbXGbXHbXIbXJbXKbXLbXMbPTbXNbXObPXbEXbxXbXPbXQbXRbXRbXQbXRbXSbUAbXTbXUbWcbWdbXWbXVbUCbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbWfbXXbXYbXXbWgbWhbWhbXZbYabXZbWhbWhaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkkdjydkKdkJdkDdkLdkMdjFdjHdjHdkNdjHdjHdjHdkOdjHdkPdjFbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbQebUEbUFbUEbQebUIbWmbWlbOVbWnbWpbWqbWrbWsbNzbWtbWubWvbWwbNzbUTbUTbWxbUVbWybWzbWAbWBbWCbUZbWDbWEbWFbWGbWHbUZbWIbWJbWKbVfbWLbWMbWObWNbWPbWQbWRceLbWTbWUbWVbWWbWXbWYbWZbXabXbbXcbXdbXebXfbXgbXhbSrbXibXjbXkbXlbXmbXnbJsbXobXpbXqbXrbVDbXsbXsbXtbXubLgbXvbXwbXxbXybXzbXAbXBbLhbXCbXDbXEbXFbXEbXEbXGbXHbXIbXJbXKbXLbXMbPTbXNbXObPXbEXbxXbXPbXQbXRbXRbXQbXRbXSbUAbXTbXUbWcbWdbXWbXVbUCbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbWfbXXbXYbXXbWgbWhbWhbXZbYabXZbWhbWhaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkkdjydkQdkDdkDdkRdjFdjHdjHdjFdkSdjHdjHdkUdkTdjydkjbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbTgbUGbUEbUEdeycOWbTkbTkdeAbZAbTkbWqbYhbYibNzbNzbNzbNzbNzbNzbYjbUTbYkbYlbYmbYnbYnbYnbYobUZbYpbYqbYqbYqbYrbYsbYtbYubYvbYwbYxbYybYzbYAbYBbYCbVfbVibYDbVkbYEbYFbYGbYHbYIbVkbSmbQVbSnbSobYJbYKbYLbYMbYNbYObYPbYQbYRbVubYSbYTbYUbYVbYWbYXbYYbYZbZabZbbLgbLgbLgbLgbLgbZcbZdbZebLhbZfbXEbXEbZgbZhbXEbZibZjbZkbZlbRAbZmbZmbPTbPVbZnbPXbEXbUAbZobUAbUAbUAbUAbUAbUAbUAbZpbZqbUCbZrbZtbZsbUCaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacbZubXXbXXbZvbWhbWhbXZbXZbZwbXZbXZbWhbWhaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkkdjydkWdkVdkXdjFdjHdkYdjFdkZdladjHdjydjydkjbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbQebUFbUFbUFbQebUIbYfbYebZzbZAbTkbZBbZCbZDbZEbZFbZFbZFbZGbUTbUTbUTbWxbUVbZHbYnbZIbYnbZJbUZbYpbYqbZKbYqbZLbUZbZMbZNbZObVfbZPbZQbZRbZSbZTbZUbZVbZWbZXbZYbZZcaacabcaccadbVkbTQbQVbQVcaebYJbYKcafcagcahbXjcaicajbKwcalcamcancaocapcaqcarcascasbXscatbVAcaucavcawcaxcaycazcaAbZjbZfcaBbXEbXEbXEcaCcaDbZjbZkcaEbRAbZmbZmbPTcaFcaGbPXbEXcaHcaIcaJbUAcaKcaLcaMcaNcaOcaPcaQbUCcaRcaScaTbUCbUAbUAbUAbUAbUAbUAbUAbUAbikbikbikbikbikaabbikbikbikbikbikbikbikcaUbWhbZubWhbWhbXZbXZbXZbXZbXZbXZbXZbWhbWhaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikdkkdjydjydjydjydjydjydjydjydjydjydkjbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaahbikbikbikbikbikbikbTgbWjbYbbWkbTgbUIbUIbUIbOVcaYcaZcaZcaZbOVbOVbUTcbacbacbbcbcbUTcbdcbebUVbZHbYnbYnbYncbfbUZbYpbYqbYqcbgcbhcbicbjcbkcblbVfbVfbVfbVfbVfcbmbVfbVfcbncbobVkbVkcbpbVkbVnbVkbVkbPlcbqcbqbPlccicbrcbscbtcbucbvcbwcbxcbycbzcbAcbBcaocapcbCcbDcbEcbFcbGcbHbVAcbIcbJcbKcbLcbMcbNcbOcbPcbQcbPcbRcbRcbRcbPcbScbPcbPcbTcbUbZmbZmbPTcbVbZnbPXcbWcbXcbYcbZccaccbccccccccdcceccfccgcckccjcdqcclcfeccmccnccoccpccqccrccscctaabaabaabaabaabaabaabaabbikbikbikbikbikbWgbWhccuccvccvbXZbXZbXZbXZbXZbXZbXZbXZbWhbWhaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbYcbOTbYgbOTbZxbikaabbikbOVbOVbUIbUIbUIbOVbikbUTccAccBccCccDbZFccEccFbUVccGccHccIbYnccJccKccLccMccNccOccPbUZccQccRccSccTccUccUccUccUccVccWccXccYccYccZccZcdacdbcdcbVibPlcddcdecdfcdgcdhbYKcdicbtcdjcdkcdlcdmbSwcdncdocdpcffcdrcdrcdscdrcdrcdrcdtbVAcducdvcdwcdxcdycdzcdAcbPcdBcdCcdDcdEcdFcdCcdGcdHcdCcdIcdJbRBbPTbPTcdKcdLcdMcdNcdOcdPcdQcdRcdScdTcdUcdVcdWcdXcdYcdZceacebceaceccebcebcebcedceecefcegcehaabbikbikbikbikaabbikbikbikbikbikbikbikbWhbWhceibXZbXZbXZbXZbXZbXZbXZbXZbXZbXZbXZbWhbWhaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabaabaabaabaabaabaabaabaabaabaabaabaabaabaabaabcejbUTbUTbUTcekbUTbUTcelbUVcemcenceocepceqcercescetceucevcewccKcexceycezceAceBceBceBceBceCceDceEceFceGceFceHceIceJceKceLceMceNceOcePceQceRceSceTceUceVceWceXceYceZcfacfbcfccfgbMmbMmcfichOcgHceYchVcfjcfjcfkcfjcflcfmcfncfocbPcfpcfqcfrcfscfrcftcfucfvcdCbPTcfwbPTbPTcfxcfybZnbBxbEXbUAcfzbUAbUAcfAcfBcfCcfDcfEcebcfFcfGcfHcdUcfHcdUcfIcdUcdUbUAcfJcfKcfLcfMaabaabaabaabaabaabaabaabbikbikbikbikbikbWhbXZbXZbXZbXZbXZbXZcfNbXZcfObXZbXZbXZbXZbXZbWhbWhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaabaabaabaabaabaabaabaabaabaabaabaabaabaabaabaabcejbUTbUTbUTcekbUTbUTcelbUVcemcenceocepceqcercescetceucevcewccKcexceycezceAceBceBceBceBceCceDceEceFceGceFceHceIceJceKchvceMceNceOcePceQceRceSceTceUceVceWceXceYceZcfacfbcfccfgbMmbMmcfichOcgHceYchVcfjcfjcfkcfjcflcfmcfncfocbPcfpcfqcfrcfscfrcftcfucfvcdCbPTcfwbPTbPTcfxcfybZnbBxbEXbUAcfzbUAbUAcfAcfBcfCcfDcfEcebcfFcfGcfHcdUcfHcdUcfIcdUcdUbUAcfJcfKcfLcfMaabaabaabaabaabaabaabaabbikbikbikbikbikbWhbXZbXZbXZbXZbXZbXZcfNbXZcfObXZbXZbXZbXZbXZbWhbWhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikcejcfPcfQcfRbUTcfSbUVcfTcfUcfVbZHcfWcfXcfYbYpcfZcgacgbcfXcgccgdcgecgfcgfcggcghcgicgjcgkcglcgmbGycgnbVicgobGybGycgpcgqcgrcgscgtceQcgucgvcgwcgxcgycgzbMVcgBcgCcgzcgzcgDcgEcgEcgEcgEcgEcgGcgFciOcgJcgJcgKcgLcgMcgNcgOcgPcbPcgQcgRcgScgTcgScgUcgVcfrcgWcgXcgYbBxcgZbBxcfybZnbBxbEXchachbchcbUAbUAbUAbUAbUAchdcdUchechfcfIcdUchgcdUbZpcdUchhbUAchichjchkbUAaabbikbikbikbikaabbikbikbikbikbikbikbikbWhchlchlchlchlchlchlchmbXZchnchlcfObXZbXZbXZbXZbWhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikcejchochpchqbUTcfSchrchrchrchrchrchrbUZbUZbUZbUZbUZbUZbUZchschtchuchvchwchxchychychzchAcglbGybGychBchCchDbGybGybGycqOchFchGceOchHchIchJcgEchKchLchMbSqchLchNcfdcfdcfdcjncjlcjlcjlcjlckacgIbSqclPchPchQchRchSchTchUclQcbPchWchXcfrchYcfrchZciacibciccidciecifbBxcigcfybZncihbEXbFeciicijcikbwobxXbxXbUAcilcimcinciocipciqcirciscitciucivbUAciwciycixcizbikbikbikbikaahbikbikbikbikbikbikbikbikciAbXXbXXbXXbXXbXXbXXbXXciBciCbXZbXZbXZbXZciDbYabWhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikcejchochpchqbUTcfSchrchrchrchrchrchrbUZbUZbUZbUZbUZbUZbUZchschtchucWpchwchxchychychzchAcglbGybGychBchCchDbGybGybGycqOchFchGceOchHchIchJcgEchKchLchMbSqchLchNcfdcfdcfdcjncjlcjlcjlcjlckacgIbSqclPchPchQchRchSchTchUclQcbPchWchXcfrchYcfrchZciacibciccidciecifbBxcigcfybZncihbEXbFeciicijcikbwobxXbxXbUAcilcimcinciocipciqcirciscitciucivbUAciwciycixcizbikbikbikbikaahbikbikbikbikbikbikbikbikciAbXXbXXbXXbXXbXXbXXbXXciBciCbXZbXZbXZbXZciDbYabWhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikcejcejcejcejbUTcfScejbikbikbikbikciEbaTciGciHciIciJciKciLciMciNcmPciPciQciRciSciSciTciUciVciWciWciWciWciXciWbGybGybPlciYciZcjacjbcjccjdchScjecjfcjgcjhcjicjjcjkcjlcfdcnicjpcjpcjpcjpckNclSckUcnjcjqcjrcjscjtcbAcwncjvcbPcjwcjxcjycjzcjAcfrcjBcbPcbPbEXcjCbEXbEXbEXcjDcjEcjFbEXbxXciicjGcjHbwobwobwobUAbUAbUAbUAbUAbUAbUAbUAbUAbUAbUAbUAbUAbUAbUAbUAbUAbikbikbikbikbikaabbikbikbikbikbikbikbikbWhccvccvccvccvccvccvcjIbXZccuccvcjJbXZbXZbXZbXZbWhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikcAjbikaabaabcjKcjKcjKcjKcjKcjKcjLcjKbUTcelcjMbikcjNcjOcjPcjQcjRcjScjTcjUcjVcjWcjXcjYcjZcosckbckcckdckeckfckgckhciVckickjckjckjckkciWbGybGycklcklcklcklckmcklckncknckocklcklckpcklckqckrckscktcksclZaPKcmacmmcmlcmocmnckzckAckBckCckDckEckFcdccbPcbPckGcbPckHcbPckIckJckKcgnckLckMckvckOckPckQckRckSckPbxXciickTbxXbxXckVckWckXchabxXbxXbxXbxXbxXbxXbxXckYckZbONbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbWhbXZbXZbXZbXZbXZbXZcfNbXZcjJbXZbXZbXZbXZbXZbWhbWhaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikclaclbclbclbclccldcleclfclgclhclicjLcljcelcjMbikclkcllclmclnclocjSclpclqclrclsciLcltcgdcoaciPcluchyclvchyclwclxclyclzclAclAclAclBciWbGybGycklclCclDclEclFclGclHclHclIclJclKclLclMclNclOcotcfhcpwcmpcmrcmqctbcsZcuoctcckDclVclWclXckzclYcmtcmscmbcmbcmccmdcmecmfcmgcmhcmfcmfcmfcmicmfcmfcmjcmucnpcnlcqRcoXcrGcqScrHcrHcrIcrHcrHcrHcrHcrJcrKcrHcrHcrHcrMcmvbxXbONbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbWhbWhceibXZbXZbXZbXZbXZbXZbXZbXZbXZbXZbXZbWhbWhaabaabbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik @@ -8941,8 +8941,8 @@ bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbi bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucVvcVEcVHcVIcVJcVJcVKcVLcVJcVMcVNcVJcVJcVIcVOcVvcVEcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikagQbikaabbikagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucVPcVQcVRcVRcVScVTcVUcVVcVWcVWcVXcVYcVZcWacWacWbcWccVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikagQagQagQagQagQbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucWdcWecWfcWfcVJcWgcWhcWhcWicWhcWhcWjcVJcWfcWfcWkcWlcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucWdcVFbikbikcVJcWmcWncWocWpcWqcWrcWscVJbikbikcVucWlcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik -bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucWtcWucWucWucVJcWvcWwcWhcWhcWxcWycWzcVJcWucWucWucWAcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucWdcVFbikbikcVJcWmcWncWocWvcWqcWrcWscVJbikbikcVucWlcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik +bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucWtcWucWucWucVJcWzcWwcWhcWhcWxcWydgYcVJcWucWucWucWAcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucWdcVFbikbikcWBcWCcWBcWDcWEcWBcWBcWFcWBbikbikcVucWlcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaahbikbikbikbikbikbikacGcVucWdcVFbikbikcWBcWGcWHcWIcWJcWKcWLcWMcWBbikbikcVucWlcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikaahbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik bikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikacGcVucWdcVFbikcWBcWBcWNcWLboSboSboScWLcWOcWBcWBbikcVucWlcVFacGbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbikbik diff --git a/_maps/map_files/cyberiad/z5.dmm b/_maps/map_files/cyberiad/z5.dmm index 02e4b76f929..861681ce06f 100644 --- a/_maps/map_files/cyberiad/z5.dmm +++ b/_maps/map_files/cyberiad/z5.dmm @@ -886,7 +886,7 @@ "rc" = (/obj/machinery/light/small{dir = 4},/obj/structure/stool/bed/roller,/obj/machinery/light_switch{pixel_x = 27; pixel_y = 0},/turf/simulated/floor/plasteel{dir = 2; icon_state = "whiteblue"},/area/mine/laborcamp) "rd" = (/obj/machinery/atmospherics/pipe/simple/hidden,/obj/structure/closet/crate/internals,/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/tank/emergency_oxygen,/obj/item/clothing/mask/breath,/obj/item/clothing/mask/breath,/obj/item/clothing/mask/breath,/obj/item/clothing/mask/breath,/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/mine/laborcamp) "re" = (/obj/machinery/light/small,/obj/machinery/light_switch{pixel_y = -28},/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/mine/laborcamp) -"rf" = (/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=2-NMine"; location = "1-Storage"},/obj/machinery/bot/secbot/ofitser,/turf/simulated/floor/plasteel{icon_state = "asteroidfloor"},/area/mine/laborcamp) +"rf" = (/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=2-NMine"; location = "1-Storage"},/mob/living/simple_animal/bot/secbot/ofitser,/turf/simulated/floor/plasteel{icon_state = "asteroidfloor"},/area/mine/laborcamp) "rg" = (/obj/structure/plasticflaps/mining,/turf/simulated/floor/plasteel{icon_state = "asteroidfloor"},/area/mine/laborcamp) "rh" = (/obj/machinery/navbeacon{codes_txt = "patrol;next_patrol=3-SMine"; location = "2-NMine"},/turf/simulated/floor/plasteel{icon_state = "asteroidfloor"},/area/mine/laborcamp) "ri" = (/turf/simulated/floor/plating{icon_plating = "asteroidplating"; icon_state = "asteroidplating"},/area/mine/laborcamp) diff --git a/_maps/map_files/cyberiad/z6.dmm b/_maps/map_files/cyberiad/z6.dmm index 825ed6d3f0e..104c590cccc 100644 --- a/_maps/map_files/cyberiad/z6.dmm +++ b/_maps/map_files/cyberiad/z6.dmm @@ -191,7 +191,7 @@ "dI" = (/turf/simulated/floor/plasteel/airless{icon_state = "damaged2"},/area/derelict/singularity_engine) "dJ" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; pixel_y = 0; tag = ""},/turf/simulated/floor/plasteel,/area/derelict/bridge/access) "dK" = (/obj/machinery/door/window/eastleft{name = "Heads of Staff"; req_access_txt = "19"},/turf/simulated/floor/plasteel,/area/derelict/bridge/access) -"dL" = (/obj/machinery/bot/secbot/buzzsky,/turf/simulated/floor/plasteel,/area/derelict/bridge) +"dL" = (/mob/living/simple_animal/bot/secbot/buzzsky,/turf/simulated/floor/plasteel,/area/derelict/bridge) "dM" = (/obj/structure/table,/obj/item/device/paicard,/turf/simulated/floor/plasteel,/area/derelict/bridge) "dN" = (/obj/structure/stool,/turf/simulated/floor/plasteel,/area/derelict/bridge) "dO" = (/obj/structure/table,/obj/item/weapon/stock_parts/cell,/obj/structure/window/reinforced{dir = 4},/turf/simulated/floor/plasteel,/area/derelict/bridge) diff --git a/code/__DEFINES/bots.dm b/code/__DEFINES/bots.dm new file mode 100644 index 00000000000..bb69bfe1e4f --- /dev/null +++ b/code/__DEFINES/bots.dm @@ -0,0 +1,38 @@ +//Bot defines, placed here so they can be read by other things! + +#define BOT_STEP_DELAY 4 //Delay between movemements +#define BOT_STEP_MAX_RETRIES 5 //Maximum times a bot will retry to step from its position + +#define DEFAULT_SCAN_RANGE 7 //default view range for finding targets. + +//Mode defines +#define BOT_IDLE 0 // idle +#define BOT_HUNT 1 // found target, hunting +#define BOT_PREP_ARREST 2 // at target, preparing to arrest +#define BOT_ARREST 3 // arresting target +#define BOT_START_PATROL 4 // start patrol +#define BOT_PATROL 5 // patrolling +#define BOT_SUMMON 6 // summoned by PDA +#define BOT_CLEANING 7 // cleaning (cleanbots) +#define BOT_REPAIRING 8 // repairing hull breaches (floorbots) +#define BOT_MOVING 9 // for clean/floor/med bots, when moving. +#define BOT_HEALING 10 // healing people (medbots) +#define BOT_RESPONDING 11 // responding to a call from the AI +#define BOT_DELIVER 12 // moving to deliver +#define BOT_GO_HOME 13 // returning to home +#define BOT_BLOCKED 14 // blocked +#define BOT_NAV 15 // computing navigation +#define BOT_WAIT_FOR_NAV 16 // waiting for nav computation +#define BOT_NO_ROUTE 17 // no destination beacon found (or no route) + +//Bot types +#define SEC_BOT 1 // Secutritrons (Beepsky) and ED-209s +#define MULE_BOT 2 // MULEbots +#define FLOOR_BOT 4 // Floorbots +#define CLEAN_BOT 8 // Cleanbots +#define MED_BOT 16 // Medibots + +//Sentience types +#define SENTIENCE_ORGANIC 1 +#define SENTIENCE_ARTIFICIAL 2 +#define SENTIENCE_OTHER 3 \ No newline at end of file diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index ab41a74ecd3..9bc80cd7bd5 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -14,7 +14,8 @@ #define DIAG_HUD "9" // Silicon health bar #define DIAG_BATT_HUD "10"// Borg/Mech power meter #define DIAG_MECH_HUD "11"// Mech health bar -#define STATUS_HUD_OOC "12" // STATUS_HUD without virus db check for someone being ill. +#define STATUS_HUD_OOC "12"// STATUS_HUD without virus db check for someone being ill. +#define DIAG_BOT_HUD "13"// Bot HUDS //for antag huds. these are used at the /mob level #define SPECIALROLE_HUD "13" diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 01cfb1c6b39..e900dfdb093 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -22,6 +22,7 @@ var/global/list/spirits = list() //List of all the spirits, including Masks var/global/list/living_mob_list = list() //List of all alive mobs, including clientless. Excludes /mob/new_player var/global/list/dead_mob_list = list() //List of all dead mobs, including clientless. Excludes /mob/new_player var/global/list/respawnable_list = list() //List of all mobs, dead or in mindless creatures that still be respawned. +var/global/list/simple_animal_list = list() //List of all simple animals, including clientless //global var of unsafe-to-spawn-on-reaction mobs var/global/list/blocked_mobs = list(/mob/living/simple_animal, diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index d6689af71b2..9d3f788b6b7 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -11,8 +11,6 @@ var/global/list/joblist = list() //list of all jobstypes, minus borg and AI var/global/list/airlocks = list() //list of all airlocks var/global/list/singularities = list() //list of all singularities var/global/list/janitorial_equipment = list() //list of janitorial equipment - -var/global/list/aibots = list() // AI controlled bots var/global/list/table_recipes = list() //list of all table craft recipes var/global/list/all_areas = list() @@ -24,6 +22,10 @@ var/global/list/apcs = list() var/global/list/air_alarms = list() var/global/list/power_monitors = list() +var/global/list/navbeacons = list() //list of all bot nagivation beacons, used for patrolling. +var/global/list/deliverybeacons = list() //list of all MULEbot delivery beacons. +var/global/list/deliverybeacontags = list() //list of all tags associated with delivery beacons. + var/global/list/beacons = list() var/global/list/shuttle_caller_list = list() //list of all communication consoles and AIs, for automatic shuttle calls when there are none. var/global/list/tracked_implants = list() //list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented... diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 63cf67b20e6..b11df7a0f63 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -130,4 +130,8 @@ #define ui_ai_pda_log "SOUTH:6,WEST+11" #define ui_ai_take_picture "SOUTH:6,WEST+12" #define ui_ai_view_images "SOUTH:6,WEST+13" -#define ui_ai_sensor "SOUTH:6,WEST+14" \ No newline at end of file +#define ui_ai_sensor "SOUTH:6,WEST+14" + +// Bots +#define ui_bot_radio "EAST-1:28,SOUTH:7" +#define ui_bot_pull "EAST-2:26,SOUTH:7" \ No newline at end of file diff --git a/code/_onclick/hud/bot.dm b/code/_onclick/hud/bot.dm new file mode 100644 index 00000000000..ee8d1e49e2b --- /dev/null +++ b/code/_onclick/hud/bot.dm @@ -0,0 +1,70 @@ +/obj/screen/bot + icon = 'icons/mob/screen1_bot.dmi' + +/obj/screen/bot/radio + name = "radio" + icon_state = "radio" + +/obj/screen/bot/radio/Click() + if(isbot(usr)) + var/mob/living/simple_animal/bot/B = usr + B.Radio.interact(usr) + +/datum/hud/proc/bot_hud() + src.adding = list() + src.other = list() + + var/obj/screen/using + + using = new /obj/screen/bot/radio() + using.name = "radio" + using.dir = SOUTHWEST + using.icon_state = "radio" + using.screen_loc = ui_bot_radio + using.layer = 20 + src.adding += using + + mymob.healths = new /obj/screen() + mymob.healths.icon = 'icons/mob/screen1_bot.dmi' + mymob.healths.icon_state = "health0" + mymob.healths.name = "health" + mymob.healths.screen_loc = ui_borg_health + + mymob.oxygen = new /obj/screen() + mymob.oxygen.icon = 'icons/mob/screen1_bot.dmi' + mymob.oxygen.icon_state = "oxy0" + mymob.oxygen.name = "oxygen" + mymob.oxygen.screen_loc = ui_oxygen + + mymob.fire = new /obj/screen() + mymob.fire.icon = 'icons/mob/screen1_bot.dmi' + mymob.fire.icon_state = "fire0" + mymob.fire.name = "fire" + mymob.fire.screen_loc = ui_fire + + mymob.pullin = new /obj/screen/pull() + mymob.pullin.icon = 'icons/mob/screen1_bot.dmi' + mymob.pullin.update_icon(mymob) + mymob.pullin.screen_loc = ui_bot_pull + + mymob.blind = new /obj/screen() + mymob.blind.icon = 'icons/mob/screen1_full.dmi' + mymob.blind.icon_state = "blackimageoverlay" + mymob.blind.name = " " + mymob.blind.screen_loc = "CENTER-7,CENTER-7" + mymob.blind.layer = 0 + mymob.blind.mouse_opacity = 0 + + mymob.flash = new /obj/screen() + mymob.flash.icon = 'icons/mob/screen1_bot.dmi' + mymob.flash.icon_state = "blank" + mymob.flash.name = "flash" + mymob.flash.screen_loc = "WEST,SOUTH to EAST,NORTH" + mymob.flash.layer = 17 + + mymob.client.screen = list() + + mymob.client.screen += list(mymob.oxygen, mymob.fire, mymob.healths, mymob.pullin, mymob.blind, mymob.flash) + mymob.client.screen += adding + other + mymob.client.screen += mymob.client.void + return \ No newline at end of file diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index da62e32929b..555df404e28 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -194,6 +194,8 @@ datum/hud/New(mob/owner) ai_hud() else if(isrobot(mymob)) robot_hud() + else if(isbot(mymob)) + bot_hud() else if(isobserver(mymob)) ghost_hud() else if(isovermind(mymob)) @@ -206,6 +208,8 @@ datum/hud/New(mob/owner) guardian_hud() else if(ispet(mymob)) corgi_hud() + else + //Triggered when F12 is pressed (Unless someone changed something in the DMF) /mob/verb/button_pressed_F12(var/full = 0 as null) diff --git a/code/controllers/Processes/animal.dm b/code/controllers/Processes/animal.dm new file mode 100644 index 00000000000..e3840974d2c --- /dev/null +++ b/code/controllers/Processes/animal.dm @@ -0,0 +1,37 @@ +var/global/datum/controller/process/animal/animal_master + +/datum/controller/process/animal + var/current_cycle + +/datum/controller/process/animal/setup() + name = "animal" + schedule_interval = 20 // every 2 seconds + start_delay = 16 + log_startup_progress("Simple animal ticker starting up.") + if(animal_master) + qdel(animal_master) //only one mob master + animal_master = src + +/datum/controller/process/animal/started() + ..() + if(!simple_animal_list) + simple_animal_list = list() + +/datum/controller/process/animal/statProcess() + ..() + stat(null, "[simple_animal_list.len] simple animals") + +/datum/controller/process/animal/doWork() + for(last_object in simple_animal_list) + var/mob/living/simple_animal/M = last_object + if(istype(M) && isnull(M.gcDestroyed)) + if(!M.client && M.stat == CONSCIOUS) + try + M.process_ai() + catch(var/exception/e) + catchException(e, M) + SCHECK + else + catchBadType(M) + simple_animal_list -= M + current_cycle++ \ No newline at end of file diff --git a/code/datums/cargoprofile.dm b/code/datums/cargoprofile.dm index ca822d1ce96..971a6df70f3 100644 --- a/code/datums/cargoprofile.dm +++ b/code/datums/cargoprofile.dm @@ -277,7 +277,7 @@ name = "Completed Robots" id = "finished" blacklist = null - whitelist = list(/obj/mecha,/obj/machinery/bot,/mob/living/silicon/robot) + whitelist = list(/obj/mecha,/mob/living/simple_animal/bot,/mob/living/silicon/robot) mobcheck = 1 //todo: detect and allow finished cyborg endoskeletons with no brain contains(var/atom/A) diff --git a/code/datums/spells/conjure.dm b/code/datums/spells/conjure.dm index 52a38b8a198..d3fd8a31527 100644 --- a/code/datums/spells/conjure.dm +++ b/code/datums/spells/conjure.dm @@ -3,7 +3,7 @@ desc = "This spell conjures objs of the specified types in range." var/list/summon_type = list() //determines what exactly will be summoned - //should be text, like list("/obj/machinery/bot/ed209") + //should be text, like list("/mob/simple_animal/bot/ed209") var/summon_lifespan = 0 // 0=permanent, any other time in deciseconds var/summon_amt = 1 //amount of objects summoned @@ -61,7 +61,7 @@ name = "Dispense Wizard Justice" desc = "This spell dispenses wizard justice." - summon_type = list(/obj/machinery/bot/ed209) + summon_type = list(/mob/living/simple_animal/bot/ed209) summon_amt = 10 range = 3 newVars = list("emagged" = 1,"name" = "Wizard's Justicebot") \ No newline at end of file diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm index c94c78fa29f..c5e918f924b 100644 --- a/code/datums/supplypacks.dm +++ b/code/datums/supplypacks.dm @@ -77,10 +77,10 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/emergency/evac name = "Emergency Equipment Crate" - contains = list(/obj/machinery/bot/floorbot, - /obj/machinery/bot/floorbot, - /obj/machinery/bot/medbot, - /obj/machinery/bot/medbot, + contains = list(/mob/living/simple_animal/bot/floorbot, + /mob/living/simple_animal/bot/floorbot, + /mob/living/simple_animal/bot/medbot, + /mob/living/simple_animal/bot/medbot, /obj/item/weapon/tank/air, /obj/item/weapon/tank/air, /obj/item/weapon/tank/air, @@ -1145,7 +1145,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/misc/mule name = "MULEbot Crate" - contains = list(/obj/machinery/bot/mulebot) + contains = list(/mob/living/simple_animal/bot/mulebot) cost = 20 containertype = /obj/structure/largecrate/mule containername = "\improper MULEbot crate" diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index 75d81cc0040..30d767eef21 100644 --- a/code/datums/wires/mulebot.dm +++ b/code/datums/wires/mulebot.dm @@ -1,6 +1,6 @@ /datum/wires/mulebot random = 1 - holder_type = /obj/machinery/bot/mulebot + holder_type = /mob/living/simple_animal/bot/mulebot wire_count = 10 var/const/WIRE_POWER1 = 1 // power connections @@ -14,7 +14,7 @@ var/const/WIRE_REMOTE_TX = 128 // remote trans status var/const/WIRE_BEACON_RX = 256 // beacon ping recv /datum/wires/mulebot/CanUse(var/mob/living/L) - var/obj/machinery/bot/mulebot/M = holder + var/mob/living/simple_animal/bot/mulebot/M = holder if(M.open) return 1 return 0 @@ -22,7 +22,7 @@ var/const/WIRE_BEACON_RX = 256 // beacon ping recv // So the wires do not open a new window, handle the interaction ourselves. /datum/wires/mulebot/Interact(var/mob/living/user) if(CanUse(user)) - var/obj/machinery/bot/mulebot/M = holder + var/mob/living/simple_animal/bot/mulebot/M = holder M.interact(user) /datum/wires/mulebot/UpdatePulsed(var/index) diff --git a/code/defines/procs/AStar.dm b/code/defines/procs/AStar.dm index 1479db76e5d..2b49d5a1024 100644 --- a/code/defines/procs/AStar.dm +++ b/code/defines/procs/AStar.dm @@ -62,14 +62,19 @@ Actual Adjacent procs : return b.f - a.f //wrapper that returns an empty list if A* failed to find a path -/proc/get_path_to(start, end, atom, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) - var/list/path = AStar(start, end, atom, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only) +/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) + var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent, id, exclude, simulated_only) if(!path) path = list() return path //the actual algorithm -/proc/AStar(start, end, atom, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) +/proc/AStar(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) + + //sanitation + var/start = get_turf(caller) + if(!start) + return 0 if(maxnodes) //if start turf is farther than maxnodes from end turf, no need to do anything @@ -82,11 +87,6 @@ Actual Adjacent procs : var/list/path = null //the returned path, if any var/PathNode/cur //current processed turf - //sanitation - start = get_turf(start) - if(!start) - return 0 - //initialization open.Insert(new /PathNode(start,null,0,call(start,dist)(end),0)) @@ -118,9 +118,9 @@ Actual Adjacent procs : break //get adjacents turfs using the adjacent proc, checking for access with id - var/list/L = call(cur.source,adjacent)(atom,id, simulated_only) + var/list/L = call(cur.source,adjacent)(caller, id, simulated_only) for(var/turf/T in L) - if(T == exclude || T in closed) + if(T == exclude || (T in closed)) continue var/newg = cur.g + call(cur.source,dist)(T) @@ -151,7 +151,7 @@ Actual Adjacent procs : //Returns adjacent turfs in cardinal directions that are reachable //simulated_only controls whether only simulated turfs are considered or not -/turf/proc/reachableAdjacentTurfs(atom, ID, simulated_only) +/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only) var/list/L = new() var/turf/simulated/T @@ -159,45 +159,26 @@ Actual Adjacent procs : T = get_step(src,dir) if(simulated_only && !istype(T)) continue - if(!T.density && !LinkBlockedWithAccess(T, ID)) + if(!T.density && !LinkBlockedWithAccess(T,caller, ID)) L.Add(T) return L //Returns adjacent turfs in cardinal directions that are reachable via atmos /turf/proc/reachableAdjacentAtmosTurfs() - var/list/L = new() - var/turf/simulated/T + return atmos_adjacent_turfs - for(var/dir in cardinal) - if(dir & atmos_adjacent_turfs) - T = get_step(src,dir) - if(!istype(T)) - continue - if(CanAtmosPass(T)) - L.Add(T) - return L - -/turf/proc/LinkBlockedWithAccess(turf/T, obj/item/weapon/card/id/ID) +/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID) var/adir = get_dir(src, T) var/rdir = get_dir(T, src) - if(DirBlockedWithAccess(src, adir, ID)) - return 1 - if(DirBlockedWithAccess(T, rdir, ID)) - return 1 - for(var/obj/O in T) - if(O.density && !istype(O, /obj/machinery/door) && !(O.flags & ON_BORDER)) - return 1 - return 0 -/proc/DirBlockedWithAccess(turf/T, dir, ID) - for(var/obj/structure/window/D in T) - if(!D.density) - continue - if(D.dir == SOUTHWEST) + for(var/obj/structure/window/W in src) + if(!W.CanAStarPass(ID, adir)) return 1 - if(D.dir == dir) + for(var/obj/machinery/door/window/W in src) + if(!W.CanAStarPass(ID, adir)) return 1 - for(var/obj/machinery/door/D in T) - if(!D.CanAStarPass(ID, dir)) + for(var/obj/O in T) + if(!O.CanAStarPass(ID, rdir, caller)) return 1 - return 0 + + return 0 \ No newline at end of file diff --git a/code/game/asteroid.dm b/code/game/asteroid.dm index d38e05c701b..6071a804aa8 100644 --- a/code/game/asteroid.dm +++ b/code/game/asteroid.dm @@ -60,7 +60,7 @@ var/global/max_secret_rooms = 6 if("organharvest") walltypes = list(/turf/simulated/wall/r_wall=2,/turf/simulated/wall=2,/turf/simulated/mineral/random/high_chance=1) floortypes = list(/turf/simulated/floor,/turf/simulated/floor/engine) - treasureitems = list(/obj/machinery/bot/medbot/mysterious=1, /obj/item/weapon/circular_saw=1, /obj/structure/closet/critter/cat=2) + treasureitems = list(/mob/living/simple_animal/bot/medbot/mysterious=1, /obj/item/weapon/circular_saw=1, /obj/structure/closet/critter/cat=2) fluffitems = list(/obj/effect/decal/cleanable/blood=5,/obj/item/organ/internal/appendix=2,/obj/structure/closet/crate/freezer=2, /obj/machinery/optable=1,/obj/item/weapon/scalpel=1,/obj/item/weapon/storage/firstaid/regular=3, /obj/item/weapon/tank/anesthetic=1, /obj/item/weapon/surgical_drapes=2, /obj/item/device/mass_spectrometer/adv=1,/obj/item/clothing/glasses/hud/health=1) diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index ce96bca33e0..058d6f02250 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -44,7 +44,7 @@ hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD) /datum/atom_hud/data/diagnostic - hud_icons = list (DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD) + hud_icons = list (DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD) /* MED/SEC/DIAG HUD HOOKS */ @@ -253,4 +253,43 @@ var/image/holder = hud_list[DIAG_STAT_HUD] holder.icon_state = null if(internal_damage) - holder.icon_state = "hudwarn" \ No newline at end of file + holder.icon_state = "hudwarn" + +/*~~~~~~~~~ + Bots! +~~~~~~~~~~*/ +/mob/living/simple_animal/bot/proc/diag_hud_set_bothealth() + var/image/holder = hud_list[DIAG_HUD] + if(stat == DEAD) + holder.icon_state = "huddiagdead" + else + holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]" + +/mob/living/simple_animal/bot/proc/diag_hud_set_botstat() //On (With wireless on or off), Off, EMP'ed + var/image/holder = hud_list[DIAG_STAT_HUD] + if(on) + holder.icon_state = "hudstat" + else if(stat) //Generally EMP causes this + holder.icon_state = "hudoffline" + else //Bot is off + holder.icon_state = "huddead2" + +/mob/living/simple_animal/bot/proc/diag_hud_set_botmode() //Shows a bot's current operation + var/image/holder = hud_list[DIAG_BOT_HUD] + if(client) //If the bot is player controlled, it will not be following mode logic! + holder.icon_state = "hudsentient" + return + + switch(mode) + if(BOT_SUMMON, BOT_RESPONDING) //Responding to PDA or AI summons + holder.icon_state = "hudcalled" + if(BOT_CLEANING, BOT_REPAIRING, BOT_HEALING) //Cleanbot cleaning, Floorbot fixing, or Medibot Healing + holder.icon_state = "hudworking" + if(BOT_PATROL, BOT_START_PATROL) //Patrol mode + holder.icon_state = "hudpatrol" + if(BOT_PREP_ARREST, BOT_ARREST, BOT_HUNT, BOT_BLOCKED, BOT_NO_ROUTE) //STOP RIGHT THERE, CRIMINAL SCUM! + holder.icon_state = "hudalert" + if(BOT_MOVING, BOT_DELIVER, BOT_GO_HOME, BOT_NAV, BOT_WAIT_FOR_NAV) //Moving to target for normal bots, moving to deliver or go home for MULES. + holder.icon_state = "hudmove" + else + holder.icon_state = "" \ No newline at end of file diff --git a/code/game/gamemodes/blob/blobs/blob_mobs.dm b/code/game/gamemodes/blob/blobs/blob_mobs.dm index a2c6cc9591e..bfaad382adf 100644 --- a/code/game/gamemodes/blob/blobs/blob_mobs.dm +++ b/code/game/gamemodes/blob/blobs/blob_mobs.dm @@ -8,14 +8,7 @@ icon = 'icons/mob/blob.dmi' pass_flags = PASSBLOB faction = list("blob") - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 maxbodytemp = 360 universal_speak = 1 //So mobs can understand them when a blob uses Blob Broadcast diff --git a/code/game/gamemodes/blob/theblob.dm b/code/game/gamemodes/blob/theblob.dm index 2853964215b..d06d060de92 100644 --- a/code/game/gamemodes/blob/theblob.dm +++ b/code/game/gamemodes/blob/theblob.dm @@ -35,6 +35,11 @@ if(istype(mover) && mover.checkpass(PASSBLOB)) return 1 return 0 +/obj/effect/blob/CanAStarPass(ID, dir, caller) + . = 0 + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSBLOB) /obj/effect/blob/process() Life() diff --git a/code/game/gamemodes/changeling/powers/tiny_prick.dm b/code/game/gamemodes/changeling/powers/tiny_prick.dm index dbfb14f66d7..aae84f00530 100644 --- a/code/game/gamemodes/changeling/powers/tiny_prick.dm +++ b/code/game/gamemodes/changeling/powers/tiny_prick.dm @@ -43,7 +43,7 @@ return if(!isturf(user.loc)) return - if(!AStar(user.loc, target.loc, null, /turf/proc/Distance, user.mind.changeling.sting_range, simulated_only = 0)) + if(!AStar(user, target.loc, /turf/proc/Distance, user.mind.changeling.sting_range, simulated_only = 0)) return if(target.mind && target.mind.changeling) sting_feedback(user,target) diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm index 1a6e687e206..86de7b48585 100644 --- a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm +++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm @@ -46,19 +46,12 @@ harm_intent_damage = 5 minbodytemp = 0 maxbodytemp = 500 - min_oxy = 0 - max_oxy = INFINITY - min_tox = 0 - max_tox = INFINITY - min_co2 = 0 - max_co2 = INFINITY - min_n2 = 0 - max_n2 = INFINITY + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) unsuitable_atmos_damage = 0 melee_damage_lower = 15 melee_damage_upper = 15 melee_damage_type = STAMINA - ignored_damage_types = list(BRUTE = 0, BURN = 0, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1) + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) environment_smash = 0 attacktext = "shocks" attack_sound = 'sound/effects/EMPulse.ogg' diff --git a/code/game/gamemodes/miniantags/guardian/guardian.dm b/code/game/gamemodes/miniantags/guardian/guardian.dm index 0d0870d375a..bf4edccf420 100644 --- a/code/game/gamemodes/miniantags/guardian/guardian.dm +++ b/code/game/gamemodes/miniantags/guardian/guardian.dm @@ -17,14 +17,7 @@ attack_sound = 'sound/weapons/punch1.ogg' minbodytemp = 0 maxbodytemp = INFINITY - min_oxy = 0 - max_oxy = INFINITY - min_tox = 0 - max_tox = INFINITY - min_co2 = 0 - max_co2 = INFINITY - min_n2 = 0 - max_n2 = INFINITY + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) attacktext = "punches" maxHealth = INFINITY //The spirit itself is invincible health = INFINITY diff --git a/code/game/gamemodes/miniantags/morph/morph.dm b/code/game/gamemodes/miniantags/morph/morph.dm index 378ef84990a..d882f9fb20b 100644 --- a/code/game/gamemodes/miniantags/morph/morph.dm +++ b/code/game/gamemodes/miniantags/morph/morph.dm @@ -17,14 +17,7 @@ pass_flags = PASSTABLE ventcrawler = 2 - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 maxHealth = 150 diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm index 29b58c981ab..0d9af1bcbe2 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm @@ -164,18 +164,18 @@ if(attempt_cast(user)) for(var/turf/T in targets) spawn(0) - for(var/obj/machinery/bot/bot in T.contents) + for(var/mob/living/simple_animal/bot/bot in T.contents) if(!bot.emagged) new/obj/effect/overlay/temp/revenant(bot.loc) bot.locked = 0 bot.open = 1 - bot.Emag(null) + bot.emag_act(null) for(var/mob/living/carbon/human/human in T.contents) human << "You feel [pick("your sense of direction flicker out", "a stabbing pain in your head", "your mind fill with static")]." new/obj/effect/overlay/temp/revenant(human.loc) human.emp_act(1) for(var/obj/thing in T.contents) - if(istype(thing, /obj/machinery/power/apc) || istype(thing, /obj/machinery/power/smes) || istype(thing, /obj/machinery/bot)) //Doesn't work on dominators, SMES and APCs, to prevent kekkery + if(istype(thing, /obj/machinery/power/apc) || istype(thing, /obj/machinery/power/smes)) //Doesn't work on dominators, SMES and APCs, to prevent kekkery continue if(prob(20)) if(prob(50)) diff --git a/code/game/gamemodes/miniantags/slaughter/slaughter.dm b/code/game/gamemodes/miniantags/slaughter/slaughter.dm index 9d6e3959963..ddba767dd02 100644 --- a/code/game/gamemodes/miniantags/slaughter/slaughter.dm +++ b/code/game/gamemodes/miniantags/slaughter/slaughter.dm @@ -18,14 +18,7 @@ stop_automated_movement = 1 status_flags = CANPUSH attack_sound = 'sound/misc/demon_attack1.ogg' - min_oxy = 0 - max_oxy = INFINITY - min_tox = 0 - max_tox = INFINITY - min_co2 = 0 - max_co2 = INFINITY - min_n2 = 0 - max_n2 = INFINITY + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 maxbodytemp = INFINITY faction = list("slaughter") diff --git a/code/game/gamemodes/vampire/vampire_powers.dm b/code/game/gamemodes/vampire/vampire_powers.dm index 0c69bd5b2a7..02f79ab4c85 100644 --- a/code/game/gamemodes/vampire/vampire_powers.dm +++ b/code/game/gamemodes/vampire/vampire_powers.dm @@ -389,7 +389,7 @@ if(locs.len == num_bats) //we found 2 locations and thats all we need break var/turf/T = get_step(usr, direction) //getting a loc in that direction - if(AStar(usr.loc, T, /turf/proc/AdjacentTurfs, /turf/proc/Distance, 1, simulated_only = 0)) // if a path exists, so no dense objects in the way its valid salid + if(AStar(user, T, /turf/proc/Distance, 1, simulated_only = 0)) // if a path exists, so no dense objects in the way its valid salid locs += T // pad with player location diff --git a/code/game/jobs/access.dm b/code/game/jobs/access.dm index d76e4e8abb4..08f9a73cc2f 100644 --- a/code/game/jobs/access.dm +++ b/code/game/jobs/access.dm @@ -108,7 +108,6 @@ //returns 1 if this mob has sufficient access to use this object /obj/proc/allowed(mob/M) - generate_req_lists() //check if we don't require any access at all if(check_access()) return 1 @@ -151,23 +150,12 @@ req_one_access += n /obj/proc/check_access(obj/item/I) - if(!istype(req_access, /list)) - return 1 - - if(!req_access.len && (!req_one_access || !req_one_access.len)) - return 1 - if(!I) - return 0 - for(var/req in src.req_access) - if(!(req in I.GetAccess())) //doesn't have this access - return 0 - if(req_one_access && req_one_access.len) - for(var/req in req_one_access) - if(req in I.GetAccess()) //has an access from the single access list - return 1 - return 0 - return 1 - + var/list/L + if(I) + L = I.GetAccess() + else + L = list() + return check_access_list(L) /obj/proc/check_access_list(var/list/L) generate_req_lists() diff --git a/code/game/machinery/bots/bots.dm b/code/game/machinery/bots/bots.dm deleted file mode 100644 index 6f902e6b785..00000000000 --- a/code/game/machinery/bots/bots.dm +++ /dev/null @@ -1,801 +0,0 @@ -// AI (i.e. game AI, not the AI player) controlled bots -/obj/machinery/bot - icon = 'icons/obj/aibots.dmi' - layer = MOB_LAYER - light_range = 3 - use_power = 0 - var/obj/item/weapon/card/id/botcard // the ID card that the bot "holds" - var/list/prev_access = list() - 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 - var/open = 0//Maint panel - var/locked = 1 - var/hacked = 0 //Used to differentiate between being hacked by silicons and emagged by humans. - var/text_hack = "" //Custom text returned to a silicon upon hacking a bot. - var/text_dehack = "" //Text shown when resetting a bots hacked status to normal. - var/text_dehack_fail = "" //Shown when a silicon tries to reset a bot emagged with the emag item, which cannot be reset. - var/declare_message = "" //What the bot will display to the HUD user. - var/frustration = 0 //Used by some bots for tracking failures to reach their target. - var/speed = 2 //The speed at which the bot moves, or the number of times it moves per process() tick. - var/turf/ai_waypoint //The end point of a bot's path, or the target location. - var/list/path = list() //List of turfs through which a bot 'steps' to reach the waypoint. - var/pathset = 0 - var/list/ignore_list = list() //List of unreachable targets for an ignore-list enabled bot to ignore. - var/mode = 0 //Standardizes the vars that indicate the bot is busy with its function. - var/tries = 0 //Number of times the bot tried and failed to move. - var/remote_disabled = 0 //If enabled, the AI cannot *Remotely* control a bot. It can still control it through cameras. - var/mob/living/silicon/ai/calling_ai //Links a bot to the AI calling it. - var/obj/item/device/radio/Radio //The bot's radio, for speaking to people. - var/radio_name = "Common" - //var/emagged = 0 //Urist: Moving that var to the general /bot tree as it's used by most bots - var/auto_patrol = 0// set to make bot automatically patrol - var/turf/patrol_target // this is turf to navigate to (location of beacon) - var/turf/summon_target // The turf of a user summoning a bot. - var/new_destination // pending new destination (waiting for beacon response) - var/destination // destination description tag - var/next_destination // the next destination in the patrol route - - var/blockcount = 0 //number of times retried a blocked path - var/awaiting_beacon = 0 // count of pticks awaiting a beacon response - - var/nearest_beacon // the nearest beacon's tag - var/turf/nearest_beacon_loc // the nearest beacon's location - - var/beacon_freq = 1445 // navigation beacon frequency - var/control_freq = BOT_FREQ // bot control frequency - - var/bot_filter // The radio filter the bot uses to identify itself on the network. - - var/bot_type = 0 //The type of bot it is, for radio control. - #define SEC_BOT 1 // Secutritrons (Beepsky) and ED-209s - #define MULE_BOT 2 // MULEbots - #define FLOOR_BOT 3 // Floorbots - #define CLEAN_BOT 4 // Cleanbots - #define MED_BOT 5 // Medibots - var/bot_type_name = "Bot" - - #define DEFAULT_SCAN_RANGE 7 //default view range for finding targets. - - //Mode defines - #define BOT_IDLE 0 // idle - #define BOT_HUNT 1 // found target, hunting - #define BOT_PREP_ARREST 2 // at target, preparing to arrest - #define BOT_ARREST 3 // arresting target - #define BOT_START_PATROL 4 // start patrol - #define BOT_PATROL 5 // patrolling - #define BOT_SUMMON 6 // summoned by PDA - #define BOT_CLEANING 7 // cleaning (cleanbots) - #define BOT_REPAIRING 8 // repairing hull breaches (floorbots) - #define BOT_MOVING 9 // for clean/floor bots, when moving. - #define BOT_HEALING 10 // healing people (medbots) - #define BOT_RESPONDING 11 // responding to a call from the AI - #define BOT_LOADING 12 // loading/unloading - #define BOT_DELIVER 13 // moving to deliver - #define BOT_GO_HOME 14 // returning to home - #define BOT_BLOCKED 15 // blocked - #define BOT_NAV 16 // computing navigation - #define BOT_WAIT_FOR_NAV 17 // waiting for nav computation - #define BOT_NO_ROUTE 18 // no destination beacon found (or no route) - var/list/mode_name = list("In Pursuit","Preparing to Arrest","Arresting","Beginning Patrol","Patrolling","Summoned by PDA", \ - "Cleaning", "Repairing", "Proceeding to work site","Healing","Responding","Loading/Unloading","Navigating to Delivery Location","Navigating to Home", \ - "Waiting for clear path","Calculating navigation path","Pinging beacon network","Unable to reach destination") - //This holds text for what the bot is mode doing, reported on the AI's bot control interface. - -/obj/machinery/bot/proc/turn_on() - if(stat) return 0 - on = 1 - set_light(initial(light_range)) - return 1 - -/obj/machinery/bot/proc/turn_off() - on = 0 - set_light(0) - bot_reset() //Resets an AI's call, should it exist. - -/obj/machinery/bot/New() - ..() - aibots += src //Global bot list - botcard = new /obj/item/weapon/card/id(src) - set_custom_texts() - Radio = new /obj/item/device/radio(src) - Radio.listening = 0 //Makes bot radios transmit only so no one hears things while adjacent to one. - Radio.config(list("[radio_name]" = 0)) - Radio.follow_target = src - - -/obj/machinery/bot/Destroy() - aibots -= src - qdel(Radio) - Radio = null - qdel(botcard) - botcard = null - return ..() - -/obj/machinery/bot/proc/add_to_beacons(bot_filter) //Master filter control for bots. Must be placed in the bot's local New() to support map spawned bots. - spawn(20) - if(radio_controller) - radio_controller.add_object(src, beacon_freq, RADIO_NAVBEACONS) - if(bot_filter) - radio_controller.add_object(src, control_freq, bot_filter) - -/obj/machinery/bot/proc/explode() - qdel(src) - -/obj/machinery/bot/proc/healthcheck() - if (health <= 0) - explode() - -/obj/machinery/bot/proc/Emag(mob/user as mob) //Master Emag proc. Ensure this is called in your bot before setting unique functions. - if(locked) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again. - locked = 0 - emagged = 1 - user << "You bypass [src]'s controls." - if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. - emagged = 2 - remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel. - locked = 1 //Access denied forever! - bot_reset() - turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP. - else //Bot is unlocked, but the maint panel has not been opened with a screwdriver yet. - user << "You need to open maintenance panel first." - -/obj/machinery/bot/examine(mob/user) - ..(user) - if (health < maxhealth) - if (health > maxhealth/3) - user << "[src]'s parts look loose." - else - user << "[src]'s parts look very loose." - else - user << "[src] is in pristine condition." - -/obj/machinery/bot/attack_alien(var/mob/living/carbon/alien/user as mob) - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src) - health -= rand(15,30)*brute_dam_coeff - visible_message("[user] has slashed [src]!") - playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) - if(prob(10)) - new /obj/effect/decal/cleanable/blood/oil(loc) - healthcheck() - - -/obj/machinery/bot/attack_animal(var/mob/living/simple_animal/M as mob) - M.do_attack_animation(src) - if(M.melee_damage_upper == 0) - return - health -= M.melee_damage_upper - visible_message("[M] has [M.attacktext] [src]!") - add_logs(M, src, "attacked", admin=0) - if(prob(10)) - new /obj/effect/decal/cleanable/blood/oil(loc) - healthcheck() - -/obj/machinery/bot/Topic(href, href_list) //Master Topic to handle common functions. - . = ..() - if (.) - return - if(topic_denied(usr)) - usr << "[src]'s interface is not responding!" - href_list = list() - return - - usr.set_machine(src) - add_fingerprint(usr) - if((href_list["power"]) && (allowed(usr) || !locked)) - if (on) - turn_off() - else - turn_on() - - switch(href_list["operation"]) - if("patrol") - auto_patrol = !auto_patrol - mode = BOT_IDLE - if("remote") - remote_disabled = !remote_disabled - if("hack") - if(emagged != 2) - emagged = 2 - hacked = 1 - locked = 1 - usr << "[text_hack]" - bot_reset() - else if(!hacked) - usr << "[text_dehack_fail]" - else - emagged = 0 - hacked = 0 - usr << "[text_dehack]" - bot_reset() - updateUsrDialog() - -/obj/machinery/bot/proc/topic_denied(mob/user) //Access check proc for bot topics! Remember to place in a bot's individual Topic if desired. - // 0 for access, 1 for denied. - if(emagged == 2) //An emagged bot cannot be controlled by humans, silicons can if one hacked it. - if(!hacked) //Manually emagged by a human - access denied to all. - return 1 - else if(!issilicon(user)) //Bot is hacked, so only silicons are allowed access. - return 1 - else - return 0 - -/obj/machinery/bot/proc/bot_process() //Master process which handles code common across most bots. - - set background = BACKGROUND_ENABLED - - if(!on) - return - - switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command. - if(BOT_RESPONDING) //Called by the AI. - call_mode() - return - if(BOT_SUMMON) //Called by PDA - bot_summon() - return - return 1 //Successful completion. Used to prevent child process() continuing if this one is ended early. - - -/obj/machinery/bot/attackby(obj/item/weapon/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/weapon/screwdriver)) - if(!locked) - open = !open - user << "Maintenance panel is now [open ? "opened" : "closed"]." - else - user << "Maintenance panel is locked." - else - if(istype(W, /obj/item/weapon/weldingtool) && user.a_intent != I_HARM) - if(health >= maxhealth) - user << "[src] does not need a repair." - return - if(!open) - user << "Unable to repair with the maintenance panel closed." - return - var/obj/item/weapon/weldingtool/WT = W - if(WT.remove_fuel(0, user)) - health = min(maxhealth, health+10) - user.visible_message("[user] repairs [src]!","You repair [src]!") - else - user << "The welder must be on for this task." - else - if(W.force) //if force is non-zero - var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread - s.set_up(5, 1, src) - switch(W.damtype) - if("fire") - health -= W.force * fire_dam_coeff - s.start() - if("brute") - health -= W.force * brute_dam_coeff - s.start() - ..() - healthcheck() - -/obj/machinery/bot/emag_act(user as mob) - if (emagged < 2) - Emag(user) - -/obj/machinery/bot/bullet_act(var/obj/item/projectile/Proj) - if((Proj.damage_type == BRUTE || Proj.damage_type == BURN)) - health -= Proj.damage - if(prob(75) && Proj.damage > 0) - var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - ..() - healthcheck() - return - -/obj/machinery/bot/blob_act() - health -= rand(20,40)*fire_dam_coeff - healthcheck() - return - -/obj/machinery/bot/ex_act(severity) - switch(severity) - if(1.0) - explode() - return - if(2.0) - health -= rand(5,10)*fire_dam_coeff - health -= rand(10,20)*brute_dam_coeff - healthcheck() - return - if(3.0) - if (prob(50)) - health -= rand(1,5)*fire_dam_coeff - health -= rand(1,5)*brute_dam_coeff - healthcheck() - return - return - -/obj/machinery/bot/emp_act(severity) - var/was_on = on - stat |= EMPED - var/obj/effect/overlay/pulse2 = new/obj/effect/overlay ( loc ) - pulse2.icon = 'icons/effects/effects.dmi' - pulse2.icon_state = "empdisable" - pulse2.name = "emp sparks" - pulse2.anchored = 1 - pulse2.dir = pick(cardinal) - - spawn(10) - pulse2.delete() - if (on) - turn_off() - spawn(severity*300) - stat &= ~EMPED - if (was_on) - turn_on() - -/obj/machinery/bot/proc/hack(mob/user) - var/hack - if(issilicon(user)) //Allows silicons to toggle the emag status of a bot. - hack += "[emagged == 2 ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]
" - hack += "Harm Prevention Safety System: [emagged ? "DANGER" : "Engaged"]
" - else if(!locked) //Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control. - hack += "Remote network control radio: [remote_disabled ? "Disconnected" : "Connected"]

" - return hack - -/obj/machinery/bot/proc/set_custom_texts() //Superclass for setting hack texts. Appears only if a set is not given to a bot locally. - text_hack = "You hack [name]." - text_dehack = "You reset [name]." - text_dehack_fail = "You fail to reset [name]." - -/obj/machinery/bot/attack_ai(mob/user as mob) - attack_hand(user) - -/obj/machinery/bot/proc/speak(var/message, var/freqname = null) //Pass a message to have the bot say() it. Pass a frequency to say it on the radio. - if((!on) || (!message)) - return - if(freqname) - Radio.autosay(message, src.name, freqname, list(src.z)) - else - for(var/mob/O in hearers(src, null)) - O.show_message("[src] beeps, \"[message]\"",2) - return - - //Generalized behavior code, override where needed! - -/* -scan() will search for a given type (such as turfs, human mobs, or objects) in the bot's view range, and return a single result. -Arguments: The object type to be searched (such as "/mob/living/carbon/human"), the old scan result to be ignored, if one exists, -and the view range, which defaults to 7 (full screen) if an override is not passed. -If the bot maintains an ignore list, it is also checked here. - -Example usage: patient = scan(/mob/living/carbon/human, oldpatient, 1) -The proc would return a human next to the bot to be set to the patient var. -Pass the desired type path itself, declaring a temporary var beforehand is not required. -*/ -obj/machinery/bot/proc/scan(var/scan_type, var/old_target, var/scan_range) - var/final_result - for (var/scan in view (scan_range ? scan_range : DEFAULT_SCAN_RANGE, src) ) //Search for something in range! - if(!istype(scan, scan_type)) //Check that the thing we found is the type we want! - continue //If not, keep searching! - if( !(scan in ignore_list) && !(scan in old_target) ) //Filter for blacklisted elements, usually unreachable or previously processed oness - var/scan_result = process_scan(scan) //Some bots may require additional processing when a result is selected. - if( scan_result ) - final_result = scan_result - else - continue //The current element failed assessment, move on to the next. - else - continue - return final_result - -//When the scan finds a target, run bot specific processing to select it for the next step. Empty by default. -obj/machinery/bot/proc/process_scan(var/scan_target) - return scan_target - - -/obj/machinery/bot/proc/add_to_ignore(var/subject) - if(ignore_list.len < 50) //This will help keep track of them, so the bot is always trying to reach a blocked spot. - ignore_list |= subject - else if (ignore_list.len >= subject) //If the list is full, insert newest, delete oldest. - ignore_list -= ignore_list[1] - ignore_list |= subject - -/* -Movement proc for stepping a bot through a path generated through A-star. -Pass a positive integer as an argument to override a bot's default speed. -*/ -obj/machinery/bot/proc/bot_move(var/dest, var/move_speed) - if(!dest || !path || path.len == 0) //A-star failed or a path/destination was not set. - path = list() - return 0 - if(get_turf(src) == get_turf(dest)) //We have arrived, no need to move again. - return 1 - var/success - var/step_count = move_speed ? move_speed : speed //If a value is passed into move_speed, use that instead of the default speed var. - if(step_count >= 1 && tries < 4) - for(step_count, step_count >= 1,step_count--) - success = bot_step(dest) - if (success) - tries = 0 - else - tries++ - break - sleep(4) - else - return 0 - return 1 - - -obj/machinery/bot/proc/bot_step(var/dest) - if(path && path.len > 1) - step_towards(src, path[1]) - if(get_turf(src) == path[1]) //Successful move - path -= path[1] - else - return 0 - else if(path.len == 1) - step_towards(src, dest) - path = list() - return 1 - - -/obj/machinery/bot/proc/check_bot_access() - if(mode != BOT_SUMMON && mode != BOT_RESPONDING) - botcard.access = prev_access - -/obj/machinery/bot/proc/call_bot(var/caller, var/turf/waypoint) - bot_reset() //Reset a bot before setting it to call mode. - var/area/end_area = get_area(waypoint) - - //For giving the bot temporary all-access. - var/obj/item/weapon/card/id/all_access = new /obj/item/weapon/card/id - var/datum/job/captain/All = new/datum/job/captain - all_access.access = All.get_access() - - path = get_path_to(src, waypoint, src, /turf/proc/Distance_cardinal, 0, 200, id=all_access) - calling_ai = caller //Link the AI to the bot! - ai_waypoint = waypoint - - if(path && path.len) //Ensures that a valid path is calculated! - if(!on) - turn_on() //Saves the AI the hassle of having to activate a bot manually. - botcard = all_access //Give the bot all-access while under the AI's command. - calling_ai << "\icon[src] [name] called to [end_area.name]. [path.len-1] meters to destination." - pathset = 1 - mode = BOT_RESPONDING - tries = 0 - else - calling_ai << "Failed to calculate a valid route. Ensure destination is clear of obstructions and within range." - calling_ai = null - path = list() - -/obj/machinery/bot/proc/call_mode() //Handles preparing a bot for a call, as well as calling the move proc. -//Handles the bot's movement during a call. - var/success = bot_move(ai_waypoint, 3) - if (!success) - if(calling_ai) - calling_ai << "\icon[src] [get_turf(src) == ai_waypoint ? "[src] successfully arrived to waypoint." : "[src] failed to reach waypoint."]" - calling_ai = null - bot_reset() - -obj/machinery/bot/proc/bot_reset() - if(calling_ai) //Simple notification to the AI if it called a bot. It will not know the cause or identity of the bot. - calling_ai << "Call command to a bot has been reset." - calling_ai = null - path = list() - summon_target = null - pathset = 0 - botcard.access = prev_access - tries = 0 - mode = BOT_IDLE - - - - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -//Patrol and summon code! -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -/obj/machinery/bot/proc/bot_patrol() - patrol_step() - spawn(5) - if(mode == BOT_PATROL) - patrol_step() - return - -obj/machinery/bot/proc/start_patrol() - - if(tries >= 4) //Bot is trapped, so stop trying to patrol. - auto_patrol = 0 - tries = 0 - speak("Unable to start patrol.") - - return - - if(!auto_patrol) //A bot not set to patrol should not be patrolling. - mode = BOT_IDLE - return - - if(patrol_target) // has patrol target - spawn(0) - calc_path() // Find a route to it - if(path.len == 0) - patrol_target = 0 - return - mode = BOT_PATROL - else if(!awaiting_beacon) // no patrol target, so need a new one - find_patrol_target() - speak("Engaging patrol mode.") - tries++ - return - -// perform a single patrol step - -/obj/machinery/bot/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 - - - var/moved = bot_move(patrol_target)//step_towards(src, next) // attempt to move - if(moved) // successful move - blockcount = 0 - - 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 - tries = 0 - - return - - return - - else // no path, so calculate new one - mode = BOT_START_PATROL - - return - -// finds a new patrol target -/obj/machinery/bot/proc/find_patrol_target() - send_status() - if(awaiting_beacon) // awaiting beacon response - awaiting_beacon++ - if(awaiting_beacon > 40) // wait 40 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/proc/find_nearest_beacon() - nearest_beacon = null - new_destination = "__nearest__" - post_signal(beacon_freq, "findbeacon", "patrol") - awaiting_beacon = 1 - spawn(200) - awaiting_beacon = 0 - if(nearest_beacon) - set_destination(nearest_beacon) - tries = 0 - else - auto_patrol = 0 - mode = BOT_IDLE - speak("Disengaging patrol mode.") - send_status() - - -/obj/machinery/bot/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/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 - -/obj/machinery/bot/receive_signal(datum/signal/signal) - //log_admin("DEBUG \[[// world.timeofday]\]: /obj/machinery/bot/receive_signal([signal.debug_print()])") - if(!on) - return -/* - if(!signal.data["beacon"]) - - for(var/x in signal.data) - world << "* [x] = [signal.data[x]]" - */ - - var/recv = signal.data["command"] - // process all-bot input - - if(recv=="bot_status") - send_status() - - // check to see if we are the commanded bot - if(signal.data["active"] == src) - if(emagged == 2 || remote_disabled) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands. - return - // process control input - switch(recv) - if("stop") - bot_reset() //HOLD IT!! - auto_patrol = 0 - return - - if("go") - auto_patrol = 1 - return - - if("summon") - bot_reset() - var/list/user_access = signal.data["useraccess"] - summon_target = signal.data["target"] //Location of the user - if(user_access.len != 0) - botcard.access = user_access + prev_access //Adds the user's access, if any. - mode = BOT_SUMMON - calc_summon_path() - speak("Responding.", radio_name) - return - - // 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/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/proc/post_signal_multiple(var/freq, var/list/keyval) - if(!z || z != 1) //Bot control will only work on station. - 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"] == bot_type) - frequency.post_signal(src, signal, filter = bot_filter) - else - frequency.post_signal(src, signal) - -// signals bot status etc. to controller -/obj/machinery/bot/proc/send_status() - if(remote_disabled || emagged == 2) - return - var/list/kv = list( - "type" = bot_type, - "name" = name, - "loca" = get_area(src), // area - "mode" = mode - ) - post_signal_multiple(control_freq, kv) - - -obj/machinery/bot/proc/bot_summon() - // summoned to PDA - summon_step() - return - -// calculates a path to the current destination -// given an optional turf to avoid -/obj/machinery/bot/proc/calc_path(var/turf/avoid) - check_bot_access() - path = get_path_to(loc, patrol_target, src, /turf/proc/Distance_cardinal, 0, 120, id=botcard, exclude=avoid) - if(!path) - path = list() - -/obj/machinery/bot/proc/calc_summon_path(var/turf/avoid) - check_bot_access() - path = get_path_to(loc, summon_target, src, /turf/proc/Distance_cardinal, 0, 150, id=botcard, exclude=avoid) - if(!path || tries >= 5) //Cannot reach target. Give up and announce the issue. - speak("Summon command failed, destination unreachable.", radio_name) - bot_reset() - -/obj/machinery/bot/proc/summon_step() - - if(loc == summon_target) // Arrived to summon location. - bot_reset() - return - - else if(path.len > 0 && summon_target) //Proper path acquired! - var/turf/next = path[1] - if(next == loc) - path -= next - return - - var/moved = bot_move(summon_target, 3) // Move attempt - if(moved) - blockcount = 0 - - else // failed to move - blockcount++ - - if(blockcount > 5) // attempt 5 times before recomputing - // find new path excluding blocked turf - spawn(2) - calc_summon_path(next) - tries++ - return - - return - - else // no path, so calculate new one - calc_summon_path() - - return - - -/obj/machinery/bot/Bump(M as mob|obj) //Leave no door unopened! - if((istype(M, /obj/machinery/door/airlock) || istype(M, /obj/machinery/door/window)) && (!isnull(botcard))) - var/obj/machinery/door/D = M - if(D.check_access(botcard)) - D.open() - frustration = 0 - else if((istype(M, /mob/living/)) && (!anchored)) - var/mob/living/Mb = M - loc = Mb.loc - frustration = 0 - return diff --git a/code/game/machinery/bots/ed209bot.dm b/code/game/machinery/bots/ed209bot.dm deleted file mode 100644 index 99ff5796acc..00000000000 --- a/code/game/machinery/bots/ed209bot.dm +++ /dev/null @@ -1,696 +0,0 @@ -/obj/machinery/bot/ed209 - name = "\improper ED-209 Security Robot" - desc = "A security robot. He looks less than thrilled." - icon = 'icons/obj/aibots.dmi' - icon_state = "ed2090" - layer = 5.0 - density = 1 - 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/lasercolor = "" - var/disabled = 0//A holder for if it needs to be disabled, if true it will not seach for targets, shoot at targets, or move, currently only used for lasertag - - //var/lasers = 0 - - var/mob/living/carbon/target - var/oldtarget_name - var/threatlevel = 0 - var/target_lastloc //Loc of target when arrested. - var/last_found //There's a delay -//var/emagged = 0 //Emagged Secbots view everyone as a criminal - var/declare_arrests = 1 //When making an arrest, should it notify everyone wearing sechuds? - var/idcheck = 0 //If true, arrest people with no IDs - var/weaponscheck = 1 //If true, arrest people for weapons if they don't have access - var/check_records = 1 //Does it check security records? - var/arrest_type = 0 //If true, don't handcuff - var/projectile = /obj/item/projectile/energy/electrode //Holder for projectile type - var/shoot_sound = 'sound/weapons/Taser.ogg' - radio_name = "Security" - bot_type = SEC_BOT - bot_type_name = "ED-209" - bot_filter = RADIO_SECBOT - -/obj/item/weapon/ed209_assembly - name = "\improper ED-209 assembly" - desc = "Some sort of bizarre assembly." - icon = 'icons/obj/aibots.dmi' - icon_state = "ed209_frame" - item_state = "ed209_frame" - var/build_step = 0 - var/created_name = "ED-209 Security Robot" //To preserve the name if it's a unique securitron I guess - var/lasercolor = "" - -/obj/machinery/bot/ed209/New(loc,created_name,created_lasercolor) - ..() - if(created_name) - name = created_name - if(created_lasercolor) - lasercolor = created_lasercolor - icon_state = "[lasercolor]ed209[on]" - set_weapon() //giving it the right projectile and firing sound. - spawn(3) - var/datum/job/officer/J = new/datum/job/officer - botcard.access = J.get_access() - prev_access = botcard.access - - - add_to_beacons(bot_filter) - if(lasercolor) - shot_delay = 6//Longer shot delay because JESUS CHRIST - check_records = 0//Don't actively target people set to arrest - arrest_type = 1//Don't even try to cuff - req_access = list(access_maint_tunnels, access_theatre) - arrest_type = 1 - if((lasercolor == "b") && (name == "\improper ED-209 Security Robot"))//Picks a name if there isn't already a custome one - name = pick("BLUE BALLER","SANIC","BLUE KILLDEATH MURDERBOT") - if((lasercolor == "r") && (name == "\improper ED-209 Security Robot")) - name = pick("RED RAMPAGE","RED ROVER","RED KILLDEATH MURDERBOT") - -/obj/machinery/bot/ed209/turn_on() - . = ..() - icon_state = "[lasercolor]ed209[on]" - mode = BOT_IDLE - updateUsrDialog() - -/obj/machinery/bot/ed209/turn_off() - ..() - icon_state = "[lasercolor]ed209[on]" - updateUsrDialog() - -/obj/machinery/bot/ed209/bot_reset() - ..() - target = null - oldtarget_name = null - anchored = 0 - walk_to(src,0) - last_found = world.time - set_weapon() - -/obj/machinery/bot/ed209/set_custom_texts() - text_hack = "You disable [name]'s combat inhibitor." - text_dehack = "You restore [name]'s combat inhibitor." - text_dehack_fail = "[name] ignores your attempts to restrict him!" - -/obj/machinery/bot/ed209/attack_hand(mob/user as mob) - . = ..() - if (.) - return - usr.set_machine(src) - var/dat - dat += hack(user) - dat += text({" -Security Unit v2.6 controls

-Status: []
-Behaviour controls are [locked ? "locked" : "unlocked"]
-Maintenance panel panel is [open ? "opened" : "closed"]
"}, - -"[on ? "On" : "Off"]" ) - - if(!locked || issilicon(user)) - if(!lasercolor) - dat += text({"
-Arrest Unidentifiable Persons: []
-Arrest for Unauthorized Weapons: []
-Arrest for Warrant: []
-
-Operating Mode: []
-Report Arrests[]
-Auto Patrol[]"}, - -"[idcheck ? "Yes" : "No"]", -"[weaponscheck ? "Yes" : "No"]", -"[check_records ? "Yes" : "No"]", -"[arrest_type ? "Detain" : "Arrest"]", -"[declare_arrests ? "Yes" : "No"]", -"[auto_patrol ? "On" : "Off"]" ) - var/datum/browser/popup = new(user, "autoed209", "Automatic Security Unit v2.6") - popup.set_content(dat) - popup.open() - return - -/obj/machinery/bot/ed209/Topic(href, href_list) - if(lasercolor && (istype(usr,/mob/living/carbon/human))) - var/mob/living/carbon/human/H = usr - if((lasercolor == "b") && (istype(H.wear_suit, /obj/item/clothing/suit/redtag)))//Opposing team cannot operate it - return - else if((lasercolor == "r") && (istype(H.wear_suit, /obj/item/clothing/suit/bluetag))) - return - ..() - - switch(href_list["operation"]) - if ("idcheck") - idcheck = !idcheck - if("weaponscheck") - weaponscheck = !weaponscheck - updateUsrDialog() - if ("ignorerec") - check_records = !check_records - updateUsrDialog() - if ("switchmode") - arrest_type = !arrest_type - updateUsrDialog() - if("declarearrests") - declare_arrests = !declare_arrests - updateUsrDialog() - -/obj/machinery/bot/ed209/attackby(obj/item/weapon/W as obj, mob/user as mob, params) - if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) - if (allowed(user) && !open && !emagged) - locked = !locked - user << "Controls are now [locked ? "locked" : "unlocked"]." - updateUsrDialog() - else - if(emagged) - user << "ERROR" - if(open) - user << "Please close the access panel before locking it." - else - user << "Access denied." - else - ..() - if(istype(W, /obj/item/weapon/weldingtool) && user.a_intent != I_HARM) // Any intent but harm will heal, so we shouldn't get angry. - return - if (!istype(W, /obj/item/weapon/screwdriver) && (!target)) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. - if(W.force)//If force is non-zero and damage type isn't stamina. - threatlevel = user.assess_threat(src) - threatlevel += 6 - if(threatlevel >= 4) - target = user - if(lasercolor)//To make up for the fact that lasertag bots don't hunt - shootAt(user) - mode = BOT_HUNT - -/obj/machinery/bot/ed209/Emag(mob/user as mob) - ..() - if(emagged == 2) - if(user) - user << "You short out [src]'s target assessment circuits." - oldtarget_name = user.name - visible_message("[src] buzzes oddly!") - declare_arrests = 0 - icon_state = "[lasercolor]ed209[on]" - set_weapon() - -/obj/machinery/bot/ed209/bot_process() - if (!..()) - return - - if(disabled) - return - - var/list/targets = list() - for (var/mob/living/carbon/C in view(7,src)) //Let's find us a target - var/threatlevel = 0 - if ((C.stat) || (C.lying)) - continue - threatlevel = C.assess_threat(src, lasercolor) - //speak(C.real_name + text(": threat: []", threatlevel)) - if (threatlevel < 4 ) - continue - - var/dst = get_dist(src, C) - if ( dst <= 1 || dst > 7) - continue - - targets += C - if (targets.len>0) - var/mob/living/carbon/t = pick(targets) - if ((t.stat!=2) && (t.lying != 1) && (!t.handcuffed)) //we don't shoot people who are dead, cuffed or lying down. - shootAt(t) - switch(mode) - - if(BOT_IDLE) // idle - walk_to(src,0) - if(!lasercolor) //lasertag bots don't want to arrest anyone - look_for_perp() // see if any criminals are in range - if(!mode && auto_patrol) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode - - if(BOT_HUNT) // hunting for perp - // if can't reach perp for long enough, go idle - if (frustration >= 8) - walk_to(src,0) - back_to_idle() - - if(target) // make sure target exists - if(Adjacent(target) && isturf(target.loc)) // if right next to perp - playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) - icon_state = "[lasercolor]ed209-c" - spawn(2) - icon_state = "[lasercolor]ed209[on]" - var/mob/living/carbon/M = target - if(istype(M, /mob/living/carbon/human)) - if( M.stuttering < 5 && !(HULK in M.mutations) ) - M.stuttering = 5 - M.Stun(5) - M.Weaken(5) - else - M.Weaken(5) - M.stuttering = 5 - M.Stun(5) - - if(declare_arrests) - var/area/location = get_area(src) - speak("[arrest_type ? "Detaining" : "Arresting"] level [threatlevel] scumbag [target] in [location].", radio_name) - target.visible_message("[target] has been stunned by [src]!",\ - "[target] has been stunned by [src]!") - - mode = BOT_PREP_ARREST - anchored = 1 - target_lastloc = M.loc - return - - else // not next to perp - var/turf/olddist = get_dist(src, target) - walk_to(src, target,1,4) - if ((get_dist(src, target)) >= (olddist)) - frustration++ - else - frustration = 0 - else - back_to_idle() - - if(BOT_PREP_ARREST) // preparing to arrest target - - // see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again. - if( !Adjacent(target) || !isturf(target.loc) || target.weakened < 2 ) - back_to_hunt() - return - - if(iscarbon(target) && target.canBeHandcuffed()) - if(!arrest_type) - if(!target.handcuffed) //he's not cuffed? Try to cuff him! - mode = BOT_ARREST - playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2) - target.visible_message("[src] is trying to put zipties on [target]!",\ - "[src] is trying to put zipties on [target]!") - spawn(30) - if( !Adjacent(target) || !isturf(target.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. - return - if(!target.handcuffed) - target.handcuffed = new /obj/item/weapon/restraints/handcuffs/cable/zipties/used(target) - target.update_inv_handcuffed(1) //update the handcuffs overlay - back_to_idle() - else - back_to_idle() - return - else - back_to_idle() - return - - if(BOT_ARREST) - if (!target) - anchored = 0 - mode = BOT_IDLE - last_found = world.time - frustration = 0 - return - - if(target.handcuffed) //no target or target cuffed? back to idle. - back_to_idle() - return - - if( !Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && target.weakened < 2) ) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. - back_to_hunt() - return - else - mode = BOT_PREP_ARREST - anchored = 0 - - if(BOT_START_PATROL) - look_for_perp() - start_patrol() - - if(BOT_PATROL) - look_for_perp() - bot_patrol() - - - return - -/obj/machinery/bot/ed209/proc/back_to_idle() - anchored = 0 - mode = BOT_IDLE - target = null - last_found = world.time - frustration = 0 - spawn(0) - bot_process() //ensure bot quickly responds - -/obj/machinery/bot/ed209/proc/back_to_hunt() - anchored = 0 - frustration = 0 - mode = BOT_HUNT - spawn(0) - bot_process() //ensure bot quickly responds - -// look for a criminal in view of the bot - -/obj/machinery/bot/ed209/proc/look_for_perp() - if(disabled) - return - anchored = 0 - threatlevel = 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 == oldtarget_name) && (world.time < last_found + 100)) - continue - - threatlevel = C.assess_threat(src, lasercolor) - - if (!threatlevel) - continue - - else if (threatlevel >= 4) - target = C - oldtarget_name = C.name - speak("Level [threatlevel] infraction alert!") - playsound(loc, pick('sound/voice/ed209_20sec.ogg', 'sound/voice/EDPlaceholder.ogg'), 50, 0) - visible_message("[src] points at [C.name]!") - mode = BOT_HUNT - spawn(0) - bot_process() // ensure bot quickly responds to a perp - break - else - continue - -/obj/machinery/bot/ed209/proc/check_for_weapons(var/obj/item/slot_item) - if(slot_item && slot_item.needs_permit) - return 1 - return 0 - -/obj/machinery/bot/ed209/explode() - walk_to(src,0) - visible_message("[src] blows apart!") - 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('icons/obj/aibots.dmi', "hs_hole") - Sa.created_name = name - new /obj/item/device/assembly/prox_sensor(Tsec) - - if(!lasercolor) - var/obj/item/weapon/gun/energy/advtaser/G = new /obj/item/weapon/gun/energy/advtaser(Tsec) - G.power_supply.charge = 0 - G.update_icon() - else if(lasercolor == "b") - var/obj/item/weapon/gun/energy/laser/bluetag/G = new /obj/item/weapon/gun/energy/laser/bluetag(Tsec) - G.power_supply.charge = 0 - G.update_icon() - else if(lasercolor == "r") - var/obj/item/weapon/gun/energy/laser/redtag/G = new /obj/item/weapon/gun/energy/laser/redtag(Tsec) - G.power_supply.charge = 0 - G.update_icon() - - if (prob(50)) - new /obj/item/robot_parts/l_leg(Tsec) - if (prob(25)) - new /obj/item/robot_parts/r_leg(Tsec) - if (prob(25))//50% chance for a helmet OR vest - if (prob(50)) - new /obj/item/clothing/head/helmet(Tsec) - else - if(!lasercolor) - new /obj/item/clothing/suit/armor/vest(Tsec) - if(lasercolor == "b") - new /obj/item/clothing/suit/bluetag(Tsec) - if(lasercolor == "r") - new /obj/item/clothing/suit/redtag(Tsec) - - var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - - new /obj/effect/decal/cleanable/blood/oil(loc) - qdel(src) - -/obj/machinery/bot/ed209/proc/set_weapon() //used to update the projectile type and firing sound - shoot_sound = 'sound/weapons/laser.ogg' - if(emagged == 2) - if(lasercolor) - projectile = /obj/item/projectile/lasertag - else - projectile = /obj/item/projectile/beam - else - if(!lasercolor) - shoot_sound = 'sound/weapons/Taser.ogg' - projectile = /obj/item/projectile/energy/electrode - else if(lasercolor == "b") - projectile = /obj/item/projectile/lasertag/blue - else if(lasercolor == "r") - projectile = /obj/item/projectile/lasertag/red - -/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) - - if(!projectile) - return - - if (!( istype(U, /turf) )) - return - var/obj/item/projectile/A = new projectile (loc) - playsound(loc, shoot_sound, 50, 1) - A.current = U - A.yo = U.y - T.y - A.xo = U.x - T.x - spawn( 0 ) - A.process() - return - return - -/obj/machinery/bot/ed209/attack_alien(var/mob/living/carbon/alien/user as mob) - ..() - if (!isalien(target)) - target = user - mode = BOT_HUNT - - -/obj/machinery/bot/ed209/emp_act(severity) - - if(severity==2 && prob(70)) - ..(severity-1) - else - var/obj/effect/overlay/pulse2 = new/obj/effect/overlay ( loc ) - pulse2.icon = 'icons/effects/effects.dmi' - pulse2.icon_state = "empdisable" - pulse2.name = "emp sparks" - pulse2.anchored = 1 - pulse2.dir = pick(cardinal) - spawn(10) - pulse2.delete() - 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(targets.len) - if(prob(50)) - var/mob/toshoot = pick(targets) - if (toshoot) - targets-=toshoot - if (prob(50) && emagged < 2) - emagged = 2 - set_weapon() - shootAt(toshoot) - emagged = 0 - set_weapon() - else - shootAt(toshoot) - else if(prob(50)) - if(targets.len) - var/mob/toarrest = pick(targets) - if (toarrest) - target = toarrest - mode = BOT_HUNT - - - -/obj/item/weapon/ed209_assembly/attackby(obj/item/weapon/W as obj, mob/user as mob, params) - ..() - - if(istype(W, /obj/item/weapon/pen)) - var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) - if(!t) return - if(!in_range(src, usr) && loc != usr) return - created_name = t - return - - switch(build_step) - if(0,1) - if(istype(W, /obj/item/robot_parts/l_leg) || istype(W, /obj/item/robot_parts/r_leg)) - user.drop_item() - qdel(W) - build_step++ - user << "You add the robot leg to [src]." - name = "legs/frame assembly" - if(build_step == 1) - item_state = "ed209_leg" - icon_state = "ed209_leg" - else - item_state = "ed209_legs" - icon_state = "ed209_legs" - - if(2) - if(istype(W, /obj/item/clothing/suit/redtag)) - lasercolor = "r" - else if(istype(W, /obj/item/clothing/suit/bluetag)) - lasercolor = "b" - if(lasercolor || istype(W, /obj/item/clothing/suit/armor/vest)) - user.drop_item() - qdel(W) - build_step++ - user << "You add the armor to [src]." - name = "vest/legs/frame assembly" - item_state = "[lasercolor]ed209_shell" - icon_state = "[lasercolor]ed209_shell" - - if(3) - if(istype(W, /obj/item/weapon/weldingtool)) - var/obj/item/weapon/weldingtool/WT = W - if(WT.remove_fuel(0,user)) - build_step++ - name = "shielded frame assembly" - user << "You welded the vest to [src]." - if(4) - switch(lasercolor) - //if("b") - //if(!istype(W, /obj/item/clothing/head/helmet/bluetaghelm)) - //return - - //if("r") - //if(!istype(W, /obj/item/clothing/head/helmet/redtaghelm)) - //return - - if("") - if(!istype(W, /obj/item/clothing/head/helmet)) - return - - user.drop_item() - qdel(W) - build_step++ - user << "You add the helmet to [src]." - name = "covered and shielded frame assembly" - item_state = "[lasercolor]ed209_hat" - icon_state = "[lasercolor]ed209_hat" - - if(5) - if(isprox(W)) - user.drop_item() - qdel(W) - build_step++ - user << "You add the prox sensor to [src]." - name = "covered, shielded and sensored frame assembly" - item_state = "[lasercolor]ed209_prox" - icon_state = "[lasercolor]ed209_prox" - - if(6) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/coil = W - if (coil.amount < 1) - user << "You need one length of cable to wire the ED-209." - return - user << "You start to wire [src]..." - if (do_after(user, 40, target = src)) - if (coil.amount >= 1 && build_step == 6) - coil.use(1) - build_step = 7 - user << "You wire the ED-209 assembly." - name = "wired ED-209 assembly" - - if(7) - switch(lasercolor) - if("b") - if(!istype(W, /obj/item/weapon/gun/energy/laser/bluetag)) - return - name = "bluetag ED-209 assembly" - if("r") - if(!istype(W, /obj/item/weapon/gun/energy/laser/redtag)) - return - name = "redtag ED-209 assembly" - if("") - if(!istype(W, /obj/item/weapon/gun/energy/advtaser)) - return - name = "taser ED-209 assembly" - else - return - build_step++ - user << "You add [W] to [src]." - item_state = "[lasercolor]ed209_taser" - icon_state = "[lasercolor]ed209_taser" - user.drop_item() - qdel(W) - - if(8) - if(istype(W, /obj/item/weapon/screwdriver)) - playsound(loc, 'sound/items/Screwdriver.ogg', 100, 1) - var/turf/T = get_turf(user) - if(get_turf(user) == T) - build_step++ - name = "armed [name]" - user << "Taser gun attached." - - if(9) - if(istype(W, /obj/item/weapon/stock_parts/cell)) - build_step++ - user << "You complete the ED-209." - var/turf/T = get_turf(src) - new /obj/machinery/bot/ed209(T,created_name,lasercolor) - user.drop_item() - qdel(W) - user.unEquip(src, 1) - qdel(src) - - -/obj/machinery/bot/ed209/bullet_act(var/obj/item/projectile/Proj) - if(!disabled) - var/lasertag_check = 0 - if((lasercolor == "b")) - if(istype(Proj, /obj/item/projectile/lasertag/red)) - lasertag_check++ - else if((lasercolor == "r")) - if(istype(Proj, /obj/item/projectile/lasertag/blue)) - lasertag_check++ - if(lasertag_check) - icon_state = "[lasercolor]ed2090" - disabled = 1 - target = null - spawn(100) - disabled = 0 - icon_state = "[lasercolor]ed2091" - return 1 - else - ..(Proj) - else - ..(Proj) - -/obj/machinery/bot/ed209/bluetag/New()//If desired, you spawn red and bluetag bots easily - new /obj/machinery/bot/ed209(get_turf(src),null,"b") - qdel(src) - - -/obj/machinery/bot/ed209/redtag/New() - new /obj/machinery/bot/ed209(get_turf(src),null,"r") - qdel(src) \ No newline at end of file diff --git a/code/game/machinery/bots/mulebot.dm b/code/game/machinery/bots/mulebot.dm deleted file mode 100644 index 3b63764d783..00000000000 --- a/code/game/machinery/bots/mulebot.dm +++ /dev/null @@ -1,942 +0,0 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 - -// Mulebot - carries crates around for Quartermaster -// Navigates via floor navbeacons -// Remote Controlled from QM's PDA - -var/global/mulebot_count = 0 - -/obj/machinery/bot/mulebot - name = "\improper MULEbot" - desc = "A Multiple Utility Load Effector bot." - icon_state = "mulebot0" - layer = MOB_LAYER - density = 1 - anchored = 1 - animate_movement=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/atom/movable/load = null // the loaded crate (usually) - var/list/delivery_beacons = list() //List of beacons that serve as delivery locations. - beacon_freq = 1400 - bot_type = MULE_BOT - bot_type_name = "MULEbot" - bot_filter = RADIO_MULEBOT - - suffix = "" - - var/turf/target // this is turf to navigate to (location of beacon) - var/loaddir = 0 // this the direction to unload onto/load from - var/home_destination = "" // tag of home beacon - req_access = list(access_cargo) // added robotics access so assembly line drop-off works properly -veyveyr //I don't think so, Tim. You need to add it to the MULE's hidden robot ID card. -NEO - - mode = BOT_IDLE - - blockcount = 0 //number of times retried a blocked path - var/reached_target = 1 //true if already reached the target - - var/refresh = 1 // true to refresh dialogue - var/auto_return = 1 // true if auto return to home beacon after unload - var/auto_pickup = 1 // true if auto-pickup at beacon - var/report_delivery = 1 // true if bot will announce an arrival to a location. - - var/obj/item/weapon/stock_parts/cell/cell - var/datum/wires/mulebot/wires = null - // the installed power cell - - // constants for internal wiring bitflags - /* - - var/wires = 1023 // all flags on - - var/list/wire_text // list of wire colours - var/list/wire_order // order of wire indices - */ - - - var/bloodiness = 0 // count of bloodiness - var/currentBloodColor = "#A10808" - -/obj/machinery/bot/mulebot/New() - ..() - wires = new(src) - var/datum/job/cargo_tech/J = new/datum/job/cargo_tech - botcard.access = J.get_access() - prev_access = botcard.access -// botcard.access += access_robotics //Why --Ikki - cell = new(src) - cell.charge = 2000 - cell.maxcharge = 2000 - - spawn(5) // must wait for map loading to finish - add_to_beacons(bot_filter) - - mulebot_count += 1 - if(!suffix) - suffix = "#[mulebot_count]" - name = "\improper Mulebot ([suffix])" - -/obj/machinery/bot/mulebot/Destroy() - unload(0) - qdel(wires) - wires = null - if(cell) - qdel(cell) - cell = null - 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, params) - if(istype(I, /obj/item/weapon/card/id) || istype(I, /obj/item/device/pda)) - if(toggle_lock(user)) - user << "Controls [(locked ? "locked" : "unlocked")]." - updateUsrDialog() - else if(istype(I,/obj/item/weapon/stock_parts/cell) && open && !cell) - var/obj/item/weapon/stock_parts/cell/C = I - user.drop_item() - C.loc = src - cell = C - updateDialog() - else if(istype(I,/obj/item/weapon/screwdriver)) - if(locked) - user << "The maintenance hatch cannot be opened or closed while the controls are locked." - return - - open = !open - if(open) - visible_message("[user] opens the maintenance hatch of [src]", "You open [src]'s maintenance hatch.") - on = 0 - icon_state="mulebot-hatch" - else - visible_message("[user] closes the maintenance hatch of [src]", "You close [src]'s maintenance hatch.") - icon_state = "mulebot0" - - updateDialog() - else if (istype(I, /obj/item/weapon/wrench)) - if (health < maxhealth) - health = min(maxhealth, health+25) - user.visible_message( - "[user] repairs [src]!", - "You repair [src]!" - ) - else - user << " [src] does not need a repair!" - else if(istype(I, /obj/item/device/multitool) || istype(I, /obj/item/weapon/wirecutters)) - if(open) - attack_hand(usr) - else if(load && ismob(load)) // chance to knock off rider - if(prob(1+I.force * 2)) - unload(0) - user.visible_message(" [user] knocks [load] off [src] with \the [I]!", " You knock [load] off [src] with \the [I]!") - else - user << "You hit [src] with \the [I] but to no effect." - else - ..() - return - -/obj/machinery/bot/mulebot/emag_act(user as mob) - locked = !locked - user << "You [locked ? "lock" : "unlock"] the mulebot's controls!" - flick("mulebot-emagged", src) - playsound(loc, 'sound/effects/sparks1.ogg', 100, 0) - -/obj/machinery/bot/mulebot/ex_act(var/severity) - unload(0) - switch(severity) - if(2) - for(var/i = 1; i < 3; i++) - wires.RandomCut() - if(3) - wires.RandomCut() - ..() - return - -/obj/machinery/bot/mulebot/bullet_act() - if(prob(50) && !isnull(load)) - unload(0) - if(prob(25)) - visible_message(" Something shorts out inside [src]!") - wires.RandomCut() - ..() - - -/obj/machinery/bot/mulebot/attack_ai(var/mob/user) - user.set_machine(src) - interact(user, 1) - -/obj/machinery/bot/mulebot/attack_hand(var/mob/user) - . = ..() - if (.) - return - user.set_machine(src) - interact(user, 0) - -/obj/machinery/bot/mulebot/interact(var/mob/user, var/ai=0) - var/dat - dat += "

Multiple Utility Load Effector Mk. V

" - dat += "ID: [suffix]
" - dat += "Power: [on ? "On" : "Off"]
" - - if(!open) - - dat += "

Status

" - - dat += "
" - switch(mode) - if(BOT_IDLE) - dat += "Ready" - if(BOT_LOADING) - dat += "[mode_name[BOT_LOADING]]" - if(BOT_DELIVER) - dat += "[mode_name[BOT_DELIVER]]" - if(BOT_GO_HOME) - dat += "[mode_name[BOT_GO_HOME]]" - if(BOT_BLOCKED) - dat += "[mode_name[BOT_BLOCKED]]" - if(BOT_NAV,BOT_WAIT_FOR_NAV) - dat += "[mode_name[BOT_NAV]]" - if(BOT_NO_ROUTE) - dat += "[mode_name[BOT_NO_ROUTE]]" - dat += "
" - - 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 Controls" - else - dat += " 
Controls are unlocked
Lock Controls

" - - 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"])
" - dat += "Toggle Delivery Reporting ([report_delivery ? "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 - dat += "Removed
" - - dat += wires() - else - dat += "
The bot is in maintenance mode and cannot be controlled.

" - - //user << browse("M.U.L.E. Mk. III [suffix ? "([suffix])" : ""][dat]", "window=mulebot;size=350x500") - //onclose(user, "mulebot") - var/datum/browser/popup = new(user, "mulebot", "M.U.L.E. Mk. V [suffix ? "([suffix])" : ""]", 350, 620) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - return - -// returns the wire panel text -/obj/machinery/bot/mulebot/proc/wires() - return wires.GetInteractWindow() - - -/obj/machinery/bot/mulebot/Topic(href, href_list) - if(..()) - return - if (usr.stat) - return - if ((in_range(src, usr) && istype(loc, /turf)) || (istype(usr, /mob/living/silicon))) - usr.set_machine(src) - - switch(href_list["op"]) - if("lock", "unlock") - toggle_lock(usr) - - if("power") - if (on) - turn_off() - else if (cell && !open) - if (!turn_on()) - usr << "You can't switch on [src]." - return - else - return - visible_message("[usr] switches [on ? "on" : "off"] [src].") - updateDialog() - - - if("cellremove") - if(open && cell && !usr.get_active_hand()) - cell.updateicon() - usr.put_in_active_hand(cell) - cell.add_fingerprint(usr) - cell = null - - usr.visible_message("[usr] removes the power cell from [src].", "You remove the power cell from [src].") - updateDialog() - - if("cellinsert") - if(open && !cell) - var/obj/item/weapon/stock_parts/cell/C = usr.get_active_hand() - if(istype(C)) - usr.drop_item() - cell = C - C.loc = src - C.add_fingerprint(usr) - - usr.visible_message("[usr] inserts a power cell into [src].", "You insert the power cell into [src].") - updateDialog() - - - if("stop") - if(mode >= BOT_DELIVER) - bot_reset() - updateDialog() - - if("go") - if(mode == BOT_IDLE) - start() - updateDialog() - - if("home") - if(mode == BOT_IDLE || mode == BOT_DELIVER) - start_home() - updateDialog() - - if("destination") - if(!delivery_beacons.len) - post_signal(beacon_freq, "findbeacon", "delivery") - refresh=0 - var/new_dest = input("Select M.U.L.E. Destination", "Mulebot [suffix ? "([suffix])" : ""]", destination) as null|anything in delivery_beacons - refresh=1 - if(new_dest) - set_destination(new_dest) - - - if("setid") - refresh=0 - var/new_id = sanitize(copytext(input("Enter new bot ID", "Mulebot [suffix ? "([suffix])" : ""]", suffix) as text|null,1,MAX_NAME_LEN)) - refresh=1 - if(new_id) - suffix = new_id - name = "\improper 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("report") - report_delivery = !report_delivery - - if("close") - usr.unset_machine() - usr << browse(null,"window=mulebot") - - updateDialog() - //updateUsrDialog() - else - usr << browse(null, "window=mulebot") - usr.unset_machine() - return - - - -// returns true if the bot has power -/obj/machinery/bot/mulebot/proc/has_power() - return !open && cell && cell.charge > 0 && wires.HasPower() - -/obj/machinery/bot/mulebot/proc/toggle_lock(var/mob/user) - if(allowed(user)) - locked = !locked - updateDialog() - return 1 - else - user << "Access denied." - return 0 - -// 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.LoadCheck() && !istype(C,/obj/structure/closet/crate)) - visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") - playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0) - return // if not emagged, only allow crates to be loaded - - //I'm sure someone will come along and ask why this is here... well people were dragging screen items onto the mule, and that was not cool. - //So this is a simple fix that only allows a selection of item types to be considered. Further narrowing-down is below. - if(!istype(C,/obj/item) && !istype(C,/obj/machinery) && !istype(C,/obj/structure) && !ismob(C)) - return - if(!isturf(C.loc)) //To prevent the loading from stuff from someone's inventory, which wouldn't get handled properly. - return - - if(get_dist(C, src) > 1 || load || !on) - return - mode = BOT_LOADING - - // if a create, close before loading - var/obj/structure/closet/crate/crate = C - if(istype(crate)) - crate.close() - - C.loc = loc - sleep(2) - if(C.loc != loc) //To prevent you from going onto more thano ne bot. - return - 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 = BOT_IDLE - 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) - if(!load) - return - - mode = BOT_LOADING - overlays.Cut() - - if(ismob(load)) - var/mob/M = load - if(M.client) - M.client.perspective = MOB_PERSPECTIVE - M.client.eye = src - - - load.loc = loc - load.pixel_y -= 9 - load.layer = initial(load.layer) - if(dirn) - var/turf/T = loc - var/turf/newT = get_step(T,dirn) - if(load.CanPass(load,newT)) //Can't get off onto anything that wouldn't let you pass normally - 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 || istype(AM , botcard) || AM == Radio) continue - - AM.loc = 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 = BOT_IDLE - -/obj/machinery/bot/mulebot/call_bot() - ..() - var/area/dest_area - if (path && path.len) - target = ai_waypoint //Target is the end point of the path, the waypoint set by the AI. - dest_area = get_area(target) - destination = format_text(dest_area.name) - pathset = 1 //Indicates the AI's custom path is initialized. - start() - -/obj/machinery/bot/mulebot/bot_process() - if(!has_power()) - on = 0 - return - if(on) - var/speed = (wires.Motor1() ? 1 : 0) + (wires.Motor2() ? 2 : 0) - //world << "speed: [speed]" - var/num_steps = 0 - switch(speed) - if(0) - // do nothing - if(1) - num_steps = 10 - if(2) - num_steps = 5 - if(3) - num_steps = 3 - - if(num_steps) - process_bot() - num_steps-- - spawn(0) - for(var/i=num_steps,i>0,i--) - sleep(2) - process_bot() - - if(refresh) updateDialog() - -/obj/machinery/bot/mulebot/proc/process_bot() - //if(mode) world << "Mode: [mode]" - - switch(mode) - if(BOT_IDLE) // idle - icon_state = "mulebot0" - return - if(BOT_LOADING) // loading/unloading - return - if(BOT_DELIVER,BOT_GO_HOME,BOT_BLOCKED) // 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/effect/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 == BOT_BLOCKED) - spawn(1) - send_status() - - if(destination == home_destination) - mode = BOT_GO_HOME - else - mode = BOT_DELIVER - - else // failed to move - - //world << "Unable to move." - - - - blockcount++ - mode = BOT_BLOCKED - if(blockcount == 3) - visible_message("[src] makes an annoyed buzzing sound.", "You hear an electronic buzzing sound.") - playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0) - - if(blockcount > 10) // attempt 10 times before recomputing - // find new path excluding blocked turf - visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") - playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0) - - spawn(2) - calc_path(next) - if(path.len > 0) - visible_message("[src] makes a delighted ping!", "You hear a ping.") - playsound(loc, 'sound/machines/ping.ogg', 50, 0) - mode = BOT_BLOCKED - mode = BOT_WAIT_FOR_NAV - return - return - else - visible_message("[src] makes an annoyed buzzing sound.", "You hear an electronic buzzing sound.") - playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0) - //world << "Bad turf." - mode = BOT_NAV - return - else - //world << "No path." - mode = BOT_NAV - return - - if(BOT_NAV) // calculate new path - //world << "Calc new path." - mode = BOT_WAIT_FOR_NAV - spawn(0) - - calc_path() - - if(path.len > 0) - blockcount = 0 - mode = BOT_BLOCKED - visible_message("[src] makes a delighted ping!", "You hear a ping.") - playsound(loc, 'sound/machines/ping.ogg', 50, 0) - - else - visible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") - playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0) - - mode = BOT_NO_ROUTE - //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/calc_path(var/turf/avoid = null) - path = AStar(loc, target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance_cardinal, 0, 250, id=botcard, exclude=avoid) - if(!path) - path = list() - - -// sets the current destination -// signals all beacons matching the delivery code -// beacons will return a signal giving their locations -/obj/machinery/bot/mulebot/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 = BOT_GO_HOME - else - mode = BOT_DELIVER - icon_state = "mulebot[(wires.MobAvoid() != 0)]" - -// 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 = BOT_BLOCKED - icon_state = "mulebot[(wires.MobAvoid() != 0)]" - -// called when bot reaches current target -/obj/machinery/bot/mulebot/proc/at_target() - if(!reached_target) - radio_name = "Supply" - Radio.config(list("[radio_name]" = 0)) - visible_message("[src] makes a chiming sound!", "You hear a chime.") - playsound(loc, 'sound/machines/chime.ogg', 50, 0) - reached_target = 1 - - if(pathset) //The AI called us here, so notify it of our arrival. - loaddir = dir //The MULE will attempt to load a crate in whatever direction the MULE is "facing". - if(calling_ai) - calling_ai << "\icon[src] [src] wirelessly plays a chiming sound!" - playsound(calling_ai, 'sound/machines/chime.ogg',40, 0) - calling_ai = null - radio_name = "AI Private" - Radio.config(list("[radio_name]" = 0)) - - if(load) // if loaded, unload at target - speak("Destination [destination] reached. Unloading [load].", radio_name) - if(istype(load, /obj/structure/closet/crate)) - var/obj/structure/closet/crate/C = load - C.notifyRecipient(destination) - unload(loaddir) - else - // not loaded - if(auto_pickup) // find a crate - var/atom/movable/AM - if(!wires.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/structure/closet/crate) in get_step(loc,loaddir) - if(AM) - load(AM) - if(report_delivery) - speak("Now loading [load] at [get_area(src)].", radio_name) - // 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 = BOT_BLOCKED - else - bot_reset() // 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.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)) - visible_message("[src] bumps into [M]!") - else - visible_message("[src] knocks over [M]!") - M.stop_pulling() - M.Stun(8) - M.Weaken(5) - ..() - -/obj/machinery/bot/mulebot/alter_health() - return get_turf(src) - - -// called from mob/living/carbon/human/Crossed() -// when mulebot is in the same loc -/obj/machinery/bot/mulebot/proc/RunOver(var/mob/living/carbon/human/H) - visible_message("\red [src] drives over [H]!") - playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) - - var/damage = rand(5,15) - H.apply_damage(2*damage, BRUTE, "head") - H.apply_damage(2*damage, BRUTE, "chest") - H.apply_damage(0.5*damage, BRUTE, "l_leg") - H.apply_damage(0.5*damage, BRUTE, "r_leg") - H.apply_damage(0.5*damage, BRUTE, "l_arm") - H.apply_damage(0.5*damage, BRUTE, "r_arm") - - var/obj/effect/decal/cleanable/blood/B = new(src.loc) - B.blood_DNA = list() - B.blood_DNA[H.dna.unique_enzymes] = H.dna.b_type - - bloodiness += 4 - currentBloodColor="#A10808" // For if species get different blood colors. - -// 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.RemoteRX()) - send_status() - - - recv = signal.data["command [suffix]"] - if(wires.RemoteRX()) - // process control input - switch(recv) - if("stop") - bot_reset() - 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.BeaconRX()) - if((recv == new_destination) && (destination != 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.MobAvoid() != null)]" - calc_path() - updateDialog() - - //Detects and stores current active delivery beacons. - if(signal.data["beacon"]) - if(!delivery_beacons) - delivery_beacons = new() - delivery_beacons[signal.data["beacon"] ] = signal.source - -// send a radio signal with multiple data key/values -/obj/machinery/bot/mulebot/post_signal_multiple(var/freq, var/list/keyval) - - if(freq == beacon_freq && !(wires.BeaconRX())) - return - if(freq == control_freq && !(wires.RemoteTX())) - 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"] == MULE_BOT) - frequency.post_signal(src, signal, filter = RADIO_MULEBOT) - else - frequency.post_signal(src, signal) - -// signals bot status etc. to controller -/obj/machinery/bot/mulebot/send_status() - var/list/kv = list( - "type" = MULE_BOT, - "name" = suffix, - "loca" = get_area(src), - "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() - visible_message("[src] blows apart!") - var/turf/Tsec = get_turf(src) - - new /obj/item/device/assembly/prox_sensor(Tsec) - new /obj/item/stack/rods(Tsec) - new /obj/item/stack/rods(Tsec) - new /obj/item/stack/cable_coil/cut(Tsec) - if (cell) - cell.loc = Tsec - cell.update_icon() - cell = null - - var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - - new /obj/effect/decal/cleanable/blood/oil(loc) - unload(0) - qdel(src) diff --git a/code/game/machinery/bots/secbot.dm b/code/game/machinery/bots/secbot.dm deleted file mode 100644 index 729ce464b8c..00000000000 --- a/code/game/machinery/bots/secbot.dm +++ /dev/null @@ -1,502 +0,0 @@ -/obj/machinery/bot/secbot - name = "\improper Securitron" - desc = "A little security robot. He looks less than thrilled." - icon = 'icons/obj/aibots.dmi' - icon_state = "secbot0" - light_color = LIGHT_COLOR_PURE_RED - layer = 5.0 - density = 0 - 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/mob/living/carbon/target - var/oldtarget_name - var/threatlevel = 0 - var/target_lastloc //Loc of target when arrested. - var/last_found //There's a delay -// var/emagged = 0 //Emagged Secbots view everyone as a criminal - var/declare_arrests = 1 //When making an arrest, should it notify everyone on the security channel? - var/idcheck = 0 //If true, arrest people with no IDs - var/weaponscheck = 1 //If true, arrest people for weapons if they lack access - var/check_records = 1 //Does it check security records? - var/arrest_type = 0 //If true, don't handcuff - var/harmbaton = 0 - var/base_icon = "secbot" - radio_name = "Security" - bot_type = SEC_BOT - bot_type_name = "Secbot" - bot_filter = RADIO_SECBOT - -/obj/machinery/bot/secbot/beepsky - name = "Officer Beepsky" - desc = "It's Officer Beepsky! Powered by a potato and a shot of whiskey." - idcheck = 0 - weaponscheck = 1 - auto_patrol = 1 - -/obj/machinery/bot/secbot/pingsky - name = "Officer Pingsky" - desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment." - radio_name = "AI Private" - -/obj/machinery/bot/secbot/ofitser - name = "Prison Ofitser" - desc = "It's Prison Ofitser! Powered by the tears and sweat of prisoners." - idcheck = 0 - weaponscheck = 1 - auto_patrol = 1 - -/obj/machinery/bot/secbot/buzzsky - name = "Officer Buzzsky" - desc = "It's Officer Buzzsky! Rusted and falling apart, he seems less than thrilled with the crew for leaving him in his current state." - base_icon = "rustbot" - icon_state = "rustbot0" - declare_arrests = 0 - arrest_type = 1 - harmbaton = 1 - emagged = 2 - -/obj/item/weapon/secbot_assembly - name = "incomplete securitron assembly" - desc = "Some sort of bizarre assembly made from a proximity sensor, helmet, and signaler." - icon = 'icons/obj/aibots.dmi' - icon_state = "helmet_signaler" - item_state = "helmet" - var/build_step = 0 - var/created_name = "Securitron" //To preserve the name if it's a unique securitron I guess - - - -/obj/machinery/bot/secbot/New() - ..() - icon_state = "[base_icon][on]" - spawn(3) - - var/datum/job/officer/J = new/datum/job/officer - botcard.access = J.get_access() - prev_access = botcard.access - add_to_beacons(bot_filter) - - -/obj/machinery/bot/secbot/turn_on() - ..() - icon_state = "[base_icon][on]" - updateUsrDialog() - -/obj/machinery/bot/secbot/turn_off() - ..() - icon_state = "[base_icon][on]" - updateUsrDialog() - -/obj/machinery/bot/secbot/bot_reset() - ..() - target = null - oldtarget_name = null - anchored = 0 - walk_to(src,0) - last_found = world.time - -/obj/machinery/bot/secbot/set_custom_texts() - - text_hack = "You overload [name]'s target identification system." - text_dehack = "You reboot [name] and restore the target identification." - text_dehack_fail = "[name] refuses to accept your authority!" - -/obj/machinery/bot/secbot/attack_hand(mob/user as mob) - . = ..() - if(.) - return - usr.set_machine(src) - interact(user) - -/obj/machinery/bot/secbot/interact(mob/user as mob) - var/dat - dat += hack(user) - dat += text({" -Securitron v1.6 controls

-Status: []
-Behaviour controls are [locked ? "locked" : "unlocked"]
-Maintenance panel panel is [open ? "opened" : "closed"]"}, - -"[on ? "On" : "Off"]" ) - - if(!locked || issilicon(user)) - dat += text({"
-Arrest Unidentifiable Persons: []
-Arrest for Unauthorized Weapons: []
-Arrest for Warrant: []
-Operating Mode: []
-Report Arrests[]
-Auto Patrol: []"}, - -"[idcheck ? "Yes" : "No"]", -"[weaponscheck ? "Yes" : "No"]", -"[check_records ? "Yes" : "No"]", -"[arrest_type ? "Detain" : "Arrest"]", -"[declare_arrests ? "Yes" : "No"]", -"[auto_patrol ? "On" : "Off"]" ) - - var/datum/browser/popup = new(user, "autosec", "Automatic Security Unit v1.6") - popup.set_content(dat) - popup.open() - return - -/obj/machinery/bot/secbot/Topic(href, href_list) - - ..() - - switch(href_list["operation"]) - if("idcheck") - idcheck = !idcheck - updateUsrDialog() - if("weaponscheck") - weaponscheck = !weaponscheck - updateUsrDialog() - if("ignorerec") - check_records = !check_records - updateUsrDialog() - if("switchmode") - arrest_type = !arrest_type - updateUsrDialog() - if("declarearrests") - declare_arrests = !declare_arrests - updateUsrDialog() - - -/obj/machinery/bot/secbot/attackby(obj/item/weapon/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) - if(allowed(user) && !open && !emagged) - locked = !locked - user << "Controls are now [locked ? "locked." : "unlocked."]" - updateUsrDialog() - else - if(emagged) - user << "ERROR" - if(open) - user << "Please close the access panel before locking it." - else - user << " Access denied." - else - ..() - if(istype(W, /obj/item/weapon/weldingtool) && user.a_intent != I_HARM) // Any intent but harm will heal, so we shouldn't get angry. - return - if(!istype(W, /obj/item/weapon/screwdriver) && (W.force) && (!target)) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. - threatlevel = user.assess_threat(src) - threatlevel += 6 - if(threatlevel >= 4) - target = user - mode = BOT_HUNT - -/obj/machinery/bot/secbot/Emag(mob/user as mob) - ..() - - if(emagged == 2) - if(user) - user << " You short out [src]'s target assessment circuits." - oldtarget_name = user.name - visible_message("[src] buzzes oddly!") - declare_arrests = 0 - icon_state = "[base_icon][on]" - -/obj/machinery/bot/secbot/bot_process() - if (!..()) - return - - switch(mode) - - if(BOT_IDLE) // idle - flashing_lights() //make sure they disengage properly - 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 = BOT_START_PATROL // switch to patrol mode - - if(BOT_HUNT) // hunting for perp - flashing_lights() - // if can't reach perp for long enough, go idle - if(frustration >= 8) - walk_to(src,0) - back_to_idle() - return - - if(target) // make sure target exists - if(Adjacent(target) && isturf(target.loc)) // if right next to perp - playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) - if(harmbaton) - playsound(loc, 'sound/weapons/genhit1.ogg', 50, 1, -1) - icon_state = "[base_icon]-c" - spawn(2) - icon_state = "[base_icon][on]" - var/mob/living/carbon/M = target - if(istype(M, /mob/living/carbon/human)) - if( M.stuttering < 5 && !(HULK in M.mutations) ) - M.stuttering = 5 - if(harmbaton) // Bots with harmbaton enabled become shitcurity. - Dave - M.apply_damage(10) - M.Stun(5) - M.Weaken(5) - else - M.Weaken(5) - M.stuttering = 5 - M.Stun(5) - - if(declare_arrests) - var/area/location = get_area(src) - speak("[arrest_type ? "Detaining" : "Arresting"] level [threatlevel] scumbag [target] in [location].", radio_name) - target.visible_message("[target] has been [harmbaton ? "beaten" : "stunned"] by [src]!",\ - "[target] has been [harmbaton ? "beaten" : "stunned"] by [src]!") - - mode = BOT_PREP_ARREST - anchored = 1 - target_lastloc = M.loc - return - - else // not next to perp - var/turf/olddist = get_dist(src, target) - walk_to(src, target,1,4) - if((get_dist(src, target)) >= (olddist)) - frustration++ - else - frustration = 0 - else - back_to_idle() - - if(BOT_PREP_ARREST) // preparing to arrest target - flashing_lights() //make sure they disengage properly - // see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again. - if( !Adjacent(target) || !isturf(target.loc) || target.weakened < 2 ) - back_to_hunt() - return - - if(iscarbon(target) && target.canBeHandcuffed()) - if(!arrest_type) - if(!target.handcuffed) //he's not cuffed? Try to cuff him! - mode = BOT_ARREST - playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2) - target.visible_message("[src] is trying to put zipties on [target]!",\ - "[src] is trying to put zipties on [target]!") - spawn(30) - if( !Adjacent(target) || !isturf(target.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. - return - if(!target.handcuffed) - target.handcuffed = new /obj/item/weapon/restraints/handcuffs/cable/zipties/used(target) - target.update_inv_handcuffed(1) //update the handcuffs overlay - playsound(loc, pick('sound/voice/bgod.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bsecureday.ogg', 'sound/voice/bradio.ogg', 'sound/voice/binsult.ogg', 'sound/voice/bcreep.ogg'), 50, 0) - back_to_idle() - else - back_to_idle() - return - else - back_to_idle() - return - - if(BOT_ARREST) - if (!target) - anchored = 0 - mode = BOT_IDLE - last_found = world.time - frustration = 0 - return - - if(target.handcuffed) //no target or target cuffed? back to idle. - back_to_idle() - return - - if( !Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && target.weakened < 2) ) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. - back_to_hunt() - return - else //Try arresting again if the target escapes. - mode = BOT_PREP_ARREST - anchored = 0 - - if(BOT_START_PATROL) - look_for_perp() - start_patrol() - - if(BOT_PATROL) - look_for_perp() - bot_patrol() - - - return - -/obj/machinery/bot/secbot/proc/back_to_idle() - anchored = 0 - mode = BOT_IDLE - target = null - last_found = world.time - frustration = 0 - spawn(0) - bot_process() //ensure bot quickly responds - -/obj/machinery/bot/secbot/proc/back_to_hunt() - anchored = 0 - frustration = 0 - mode = BOT_HUNT - spawn(0) - bot_process() //ensure bot quickly responds -// look for a criminal in view of the bot - -/obj/machinery/bot/secbot/proc/look_for_perp() - 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 == oldtarget_name) && (world.time < last_found + 100)) - continue - - threatlevel = C.assess_threat(src) - - if(!threatlevel) - continue - - else if(threatlevel >= 4) - target = C - oldtarget_name = C.name - speak("Level [threatlevel] infraction alert!") - playsound(loc, pick('sound/voice/bcriminal.ogg', 'sound/voice/bjustice.ogg', 'sound/voice/bfreeze.ogg'), 50, 0) - visible_message("[src] points at [C.name]!") - mode = BOT_HUNT - spawn(0) - bot_process() // ensure bot quickly responds to a perp - break - else - continue -/obj/machinery/bot/secbot/proc/check_for_weapons(var/obj/item/slot_item) - if(slot_item && slot_item.needs_permit) - return 1 - return 0 - -/obj/machinery/bot/secbot/explode() - - walk_to(src,0) - visible_message("[src] blows apart!") - 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 += "hs_hole" - Sa.created_name = name - new /obj/item/device/assembly/prox_sensor(Tsec) - new /obj/item/weapon/melee/baton(Tsec) - - if(prob(50)) - new /obj/item/robot_parts/l_arm(Tsec) - - var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - - new /obj/effect/decal/cleanable/blood/oil(loc) - qdel(src) - -/obj/machinery/bot/secbot/attack_alien(var/mob/living/carbon/alien/user as mob) - ..() - if(!isalien(target)) - target = user - mode = BOT_HUNT - -/obj/machinery/bot/secbot/proc/flashing_lights() - if(mode == BOT_HUNT) - switch(light_color) - if(LIGHT_COLOR_PURE_RED) - light_color = LIGHT_COLOR_PURE_BLUE - if(LIGHT_COLOR_PURE_BLUE) - light_color = LIGHT_COLOR_PURE_RED - else - light_color = LIGHT_COLOR_PURE_RED - -//Secbot Construction - -/obj/item/clothing/head/helmet/attackby(var/obj/item/device/assembly/signaler/S, mob/user as mob, params) - ..() - if(!issignaler(S)) - ..() - return - - if(type != /obj/item/clothing/head/helmet) //Eh, but we don't want people making secbots out of space helmets. - return - - if(S.secured) - qdel(S) - var/obj/item/weapon/secbot_assembly/A = new /obj/item/weapon/secbot_assembly - user.put_in_hands(A) - user << "You add the signaler to the helmet." - user.unEquip(src, 1) - qdel(src) - else - return - -/obj/item/weapon/secbot_assembly/attackby(obj/item/I, mob/user, params) - ..() - if(istype(I, /obj/item/weapon/weldingtool)) - if(!build_step) - var/obj/item/weapon/weldingtool/WT = I - if(WT.remove_fuel(0, user)) - build_step++ - overlays += "hs_hole" - user << "You weld a hole in [src]!" - else if(build_step == 1) - var/obj/item/weapon/weldingtool/WT = I - if(WT.remove_fuel(0, user)) - build_step-- - overlays -= "hs_hole" - user << "You weld the hole in [src] shut!" - - else if(isprox(I) && (build_step == 1)) - user.drop_item() - build_step++ - user << "You add the prox sensor to [src]!" - overlays += "hs_eye" - name = "helmet/signaler/prox sensor assembly" - qdel(I) - - else if(((istype(I, /obj/item/robot_parts/l_arm)) || (istype(I, /obj/item/robot_parts/r_arm))) && (build_step == 2)) - user.drop_item() - build_step++ - user << "You add the robot arm to [src]!" - name = "helmet/signaler/prox sensor/robot arm assembly" - overlays += "hs_arm" - qdel(I) - - else if((istype(I, /obj/item/weapon/melee/baton)) && (build_step >= 3)) - user.drop_item() - build_step++ - user << "You complete the Securitron! Beep boop." - var/obj/machinery/bot/secbot/S = new /obj/machinery/bot/secbot - S.loc = get_turf(src) - S.name = created_name - qdel(I) - qdel(src) - - else if(istype(I, /obj/item/weapon/pen)) - var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) - if(!t) - return - if(!in_range(src, usr) && loc != usr) - return - created_name = t - - else if(istype(I, /obj/item/weapon/screwdriver)) - if(!build_step) - new /obj/item/device/assembly/signaler(get_turf(src)) - new /obj/item/clothing/head/helmet(get_turf(src)) - user << "You disconnect the signaler from the helmet." - qdel(src) - - else if(build_step == 2) - overlays -= "hs_eye" - new /obj/item/device/assembly/prox_sensor(get_turf(src)) - user << "You detach the proximity sensor from [src]." - build_step-- - - else if(build_step == 3) - overlays -= "hs_arm" - new /obj/item/robot_parts/l_arm(get_turf(src)) - user << "You remove the robot arm from [src]." - build_step-- \ No newline at end of file diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index ddcff089b81..3695a169849 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -103,7 +103,7 @@ dat += "Back" dat += "
Medical Robots:" var/bdat = null - for(var/obj/machinery/bot/medbot/M in world) + for(var/mob/living/simple_animal/bot/medbot/M in world) if(M.z != src.z) continue //only find medibots on the same z-level as the computer var/turf/bl = get_turf(M) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index c929e279d6b..2fcdd7a7745 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1046,7 +1046,7 @@ About the new airlock wires panel: /obj/machinery/door/airlock/CanAStarPass(var/obj/item/weapon/card/id/ID) //Airlock is passable if it is open (!density), bot has access, and is not bolted shut) - return !density || (check_access(ID) && !locked) + return !density || (check_access(ID) && !locked && arePowerSystemsOn()) /obj/machinery/door/airlock/emp_act(var/severity) if(prob(40/severity)) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 573bdc5566b..980bf06dc61 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -68,13 +68,6 @@ bumpopen(M) return - if(istype(AM, /obj/machinery/bot)) - var/obj/machinery/bot/bot = AM - if(src.check_access(bot.botcard) || emergency == 1) - if(density) - open() - return - if(istype(AM, /obj/mecha)) var/obj/mecha/mecha = AM if(density) @@ -95,20 +88,19 @@ /obj/machinery/door/CanAtmosPass() return !density -//used in the AStar algorithm to determinate if the turf the door is on is passable -/obj/machinery/door/proc/CanAStarPass(var/obj/item/weapon/card/id/ID) - return !density - /obj/machinery/door/proc/bumpopen(mob/user as mob) if(operating) return - src.add_fingerprint(user) - if(!src.requiresID()) + add_fingerprint(user) + if(!requiresID()) user = null if(density) - if(allowed(user) || src.emergency == 1) + if(allowed(user) || emergency == 1) open() + if(istype(user, /mob/living/simple_animal/bot)) + var/mob/living/simple_animal/bot/B = user + B.door_opened(src) else flick("door_deny", src) return diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index a4bc690ed10..858600b676a 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -40,23 +40,17 @@ close() /obj/machinery/door/window/Bumped(atom/movable/AM as mob|obj) - if( operating || !src.density ) + if(operating || !density) return - if (!( ismob(AM) )) - var/obj/machinery/bot/bot = AM - if(istype(bot)) - if(src.check_access(bot.botcard)) - open_and_close() - else - flick(text("[]deny", src.base_state), src) - else if(istype(AM, /obj/mecha)) + if (!ismob(AM)) + if(istype(AM, /obj/mecha)) var/obj/mecha/mecha = AM if(mecha.occupant && src.allowed(mecha.occupant)) open_and_close() else flick(text("[]deny", src.base_state), src) return - if (!( ticker )) + if (!ticker) return var/mob/M = AM if(!M.restrained() && !M.small) @@ -92,7 +86,7 @@ //used in the AStar algorithm to determinate if the turf the door is on is passable /obj/machinery/door/window/CanAStarPass(var/obj/item/weapon/card/id/ID, var/to_dir) - return !density || (dir != to_dir) || check_access(ID) + return !density || (dir != to_dir) || (check_access(ID) && !(stat & NOPOWER)) /obj/machinery/door/window/CheckExit(atom/movable/mover as mob|obj, turf/target as turf) if(istype(mover) && mover.checkpass(PASSGLASS)) diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm index 3559013ce54..78e775a47cb 100644 --- a/code/game/machinery/navbeacon.dm +++ b/code/game/machinery/navbeacon.dm @@ -1,5 +1,5 @@ // Navigation beacon for AI robots -// Functions as a transponder: looks for incoming signal matching +// No longer exists on the radio controller, it is managed by a global list. /obj/machinery/navbeacon @@ -13,12 +13,11 @@ var/open = 0 // true if cover is open var/locked = 1 // true if controls are locked - var/freq = 1445 // radio frequency var/location = "" // location response text var/list/codes // assoc. list of transponder codes var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value" - req_access = list(access_engine) + req_access = list(access_engine, access_robotics) /obj/machinery/navbeacon/New() ..() @@ -27,14 +26,17 @@ var/turf/T = loc hide(T.intact) - - spawn(50) // must wait for map loading to finish - if(radio_controller) - radio_controller.add_object(src, freq, RADIO_NAVBEACONS) + if(codes["patrol"]) + if(!navbeacons["[z]"]) + navbeacons["[z]"] = list() + navbeacons["[z]"] += src //Register with the patrol list! + if(codes["delivery"]) + deliverybeacons += src + deliverybeacontags += location /obj/machinery/navbeacon/Destroy() - if(radio_controller) - radio_controller.remove_object(src, freq) + navbeacons["[z]"] -= src //Remove from beacon list, if in one. + deliverybeacons -= src return ..() // set the transponder codes assoc list from codes_txt @@ -58,7 +60,7 @@ // called when turf state changes // hide the object if turf is intact -/obj/machinery/navbeacon/hide(var/intact) +/obj/machinery/navbeacon/hide(intact) invisibility = intact ? 101 : 0 updateicon() @@ -72,36 +74,7 @@ else icon_state = "[state]" - -// look for a signal of the form "findbeacon=X" -// where X is any -// or the location -// or one of the set transponder keys -// if found, return a signal -/obj/machinery/navbeacon/receive_signal(datum/signal/signal) - var/request = signal.data["findbeacon"] - if(request && ((request in codes) || request == "any" || request == location)) - spawn(1) - post_signal() - -// return a signal giving location and transponder codes - -/obj/machinery/navbeacon/proc/post_signal() - 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 - signal.data["beacon"] = location - - for(var/key in codes) - signal.data[key] = codes[key] - frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS) - - -/obj/machinery/navbeacon/attackby(var/obj/item/I, var/mob/user, params) +/obj/machinery/navbeacon/attackby(obj/item/I, mob/user, params) var/turf/T = loc if(T.intact) return // prevent intraction when T-scanner revealed @@ -109,7 +82,7 @@ if(istype(I, /obj/item/weapon/screwdriver)) open = !open - user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") + user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") updateicon() @@ -117,30 +90,30 @@ if(open) if (src.allowed(user)) src.locked = !src.locked - user << "Controls are now [src.locked ? "locked." : "unlocked."]" + user << "Controls are now [src.locked ? "locked" : "unlocked"]." else user << "Access denied." updateDialog() else - user << "You must open the cover first!" + user << "You must open the cover first!" return -/obj/machinery/navbeacon/attack_ai(var/mob/user) +/obj/machinery/navbeacon/attack_ai(mob/user) interact(user, 1) +/obj/machinery/navbeacon/attack_animal() + return -/obj/machinery/navbeacon/attack_hand(var/mob/user) - if(!user.IsAdvancedToolUser()) - return 0 +/obj/machinery/navbeacon/attack_hand(mob/user) interact(user, 0) -/obj/machinery/navbeacon/interact(var/mob/user, var/ai = 0) +/obj/machinery/navbeacon/interact(mob/user, ai = 0) var/turf/T = loc if(T.intact) return // prevent intraction when T-scanner revealed if(!open && !ai) // can't alter controls if not open, unless you're an AI - user << "The beacon's control cover is closed." + user << "The beacon's control cover is closed!" return @@ -149,7 +122,6 @@ if(locked && !ai) t = {"Navigation Beacon

(swipe card to unlock controls)
-Frequency: [format_frequency(freq)]

Location: [location ? location : "(none)"]
Transponder Codes:
    "} @@ -161,25 +133,21 @@ Transponder Codes:
      "} t = {"Navigation Beacon

      (swipe card to lock controls)
      -Frequency: -- -- -[format_frequency(freq)] -+ -+
      +
      -Location: [location ? location : "(none)"]
      +Location: [location ? location : "None"]
      Transponder Codes:
        "} for(var/key in codes) t += "
      • [key] ... [codes[key]]" - t += " (edit)" - t += " (delete)
        " - t += "(add new)
        " + t += " Edit" + t += " Delete
        " + t += " Add New
        " t+= "
          " - user << browse(t, "window=navbeacon") - onclose(user, "navbeacon") + var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) + popup.set_content(t) + popup.open() return /obj/machinery/navbeacon/Topic(href, href_list) @@ -188,12 +156,8 @@ Transponder Codes:
            "} if(open && !locked) usr.set_machine(src) - if (href_list["freq"]) - freq = sanitize_frequency(freq + text2num(href_list["freq"])) - updateDialog() - - else if(href_list["locedit"]) - var/newloc = sanitize(copytext(input("Enter New Location", "Navigation Beacon", location) as text|null,1,MAX_MESSAGE_LEN)) + if(href_list["locedit"]) + var/newloc = copytext(sanitize(input("Enter New Location", "Navigation Beacon", location) as text|null),1,MAX_MESSAGE_LEN) if(newloc) location = newloc updateDialog() @@ -201,12 +165,12 @@ Transponder Codes:
              "} else if(href_list["edit"]) var/codekey = href_list["code"] - var/newkey = input("Enter Transponder Code Key", "Navigation Beacon", codekey) as text|null + var/newkey = stripped_input(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey) if(!newkey) return var/codeval = codes[codekey] - var/newval = input("Enter Transponder Code Value", "Navigation Beacon", codeval) as text|null + var/newval = stripped_input(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval) if(!newval) newval = codekey return @@ -223,11 +187,11 @@ Transponder Codes:
                "} else if(href_list["add"]) - var/newkey = input("Enter New Transponder Code Key", "Navigation Beacon") as text|null + var/newkey = stripped_input(usr, "Enter New Transponder Code Key", "Navigation Beacon") if(!newkey) return - var/newval = input("Enter New Transponder Code Value", "Navigation Beacon") as text|null + var/newval = stripped_input(usr, "Enter New Transponder Code Value", "Navigation Beacon") if(!newval) newval = "1" return diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm index 86c03a683e8..48e865c78a3 100644 --- a/code/game/objects/effects/decals/Cleanable/tracks.dm +++ b/code/game/objects/effects/decals/Cleanable/tracks.dm @@ -157,8 +157,8 @@ var/global/list/image/fluidtrack_cache=list() dryname = "dried tracks" desc = "Whoops..." drydesc = "Whoops..." - coming_state = "wheels" - going_state = "" + coming_state = "wheels1" + going_state = "wheels2" desc = "They look like tracks left by wheels." gender = PLURAL random_icon_states = null diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 13dcb4d790e..8f40f7a9848 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -268,3 +268,6 @@ a { tesla_zap(src, 3, power_bounced) spawn(10) being_shocked = 0 + +/obj/proc/CanAStarPass() + . = !density \ No newline at end of file diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index 0b2ca129317..12bdb0c42d9 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -122,6 +122,12 @@ else return !density +/obj/structure/grille/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSGRILLE) + /obj/structure/grille/bullet_act(var/obj/item/projectile/Proj) if(!Proj) return diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index fedce12962c..4e255976e18 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -201,6 +201,22 @@ connected = null return ..() +/obj/structure/tray/m_tray/CanPass(atom/movable/mover, turf/target, height=0) + if(height == 0) + return 1 + + if(istype(mover) && mover.checkpass(PASSTABLE)) + return 1 + if(locate(/obj/structure/table) in get_turf(mover)) + return 1 + else + return 0 + +/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSTABLE) /* * Crematorium @@ -479,5 +495,4 @@ if (istype(B.loc,/obj/structure/morgue)) Morgue = B.loc if (Morgue) - Morgue.update() - + Morgue.update() \ No newline at end of file diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 6a921a014a3..a915ebcdd32 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -140,6 +140,12 @@ return 1 return 0 +/obj/structure/table/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSTABLE) + //checks if projectile 'P' from turf 'from' can hit whatever is behind the table. Returns 1 if it can, 0 if bullet stops. /obj/structure/table/proc/check_cover(obj/item/projectile/P, turf/from) var/turf/cover = flipped ? get_turf(src) : get_step(loc, get_dir(from, loc)) @@ -661,6 +667,12 @@ else return 0 +/obj/structure/rack/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSTABLE) + /obj/structure/rack/MouseDrop_T(obj/O as obj, mob/user as mob) if ((!( istype(O, /obj/item/weapon) ) || user.get_active_hand() != O)) return diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index b49d491f1b4..fbfc694432e 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -97,6 +97,14 @@ var/global/wcCommon = pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e", "#8f else return 1 +/obj/structure/window/CanAStarPass(ID, to_dir) + if(!density) + return 1 + if((dir == SOUTHWEST) || (dir == to_dir)) + return 0 + + return 1 + /obj/structure/window/hitby(AM as mob|obj) ..() var/tforce = 0 diff --git a/code/modules/computer3/computers/medical.dm b/code/modules/computer3/computers/medical.dm index a2aba803e5d..25d97966e8a 100644 --- a/code/modules/computer3/computers/medical.dm +++ b/code/modules/computer3/computers/medical.dm @@ -135,7 +135,7 @@ dat += "Back" dat += "
                Medical Robots:" var/bdat = null - for(var/obj/machinery/bot/medbot/M in world) + for(var/mob/living/simple_animal/bot/medbot/M in world) if(M.z != computer.z) continue //only find medibots on the same z-level as the computer var/turf/bl = get_turf(M) diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm index db39efa3845..c578c452bbc 100644 --- a/code/modules/crafting/recipes.dm +++ b/code/modules/crafting/recipes.dm @@ -36,7 +36,7 @@ /datum/table_recipe/ed209 name = "ED209" - result = /obj/machinery/bot/ed209 + result = /mob/living/simple_animal/bot/ed209 reqs = list(/obj/item/robot_parts/robot_suit = 1, /obj/item/clothing/head/helmet = 1, /obj/item/clothing/suit/armor/vest = 1, @@ -53,7 +53,7 @@ /datum/table_recipe/secbot name = "Secbot" - result = /obj/machinery/bot/secbot + result = /mob/living/simple_animal/bot/secbot reqs = list(/obj/item/device/assembly/signaler = 1, /obj/item/clothing/head/helmet = 1, /obj/item/weapon/melee/baton = 1, @@ -64,7 +64,7 @@ /datum/table_recipe/cleanbot name = "Cleanbot" - result = /obj/machinery/bot/cleanbot + result = /mob/living/simple_animal/bot/cleanbot reqs = list(/obj/item/weapon/reagent_containers/glass/bucket = 1, /obj/item/device/assembly/prox_sensor = 1, /obj/item/robot_parts/r_arm = 1) @@ -72,7 +72,7 @@ /datum/table_recipe/floorbot name = "Floorbot" - result = /obj/machinery/bot/floorbot + result = /mob/living/simple_animal/bot/floorbot reqs = list(/obj/item/weapon/storage/toolbox/mechanical = 1, /obj/item/stack/tile/plasteel = 1, /obj/item/device/assembly/prox_sensor = 1, @@ -81,7 +81,7 @@ /datum/table_recipe/medbot name = "Medbot" - result = /obj/machinery/bot/medbot + result = /mob/living/simple_animal/bot/medbot reqs = list(/obj/item/device/healthanalyzer = 1, /obj/item/weapon/storage/firstaid = 1, /obj/item/device/assembly/prox_sensor = 1, diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm index 351324c9ba8..980319a1979 100644 --- a/code/modules/events/ion_storm.dm +++ b/code/modules/events/ion_storm.dm @@ -32,9 +32,9 @@ M << "
                " if(botEmagChance) - for(var/obj/machinery/bot/bot in machines) + for(var/mob/living/simple_animal/bot/bot in machines) if(prob(botEmagChance)) - bot.Emag() + bot.emag_act() /proc/generate_ion_law(ionMessage) if(ionMessage) diff --git a/code/modules/mining/equipment_locker.dm b/code/modules/mining/equipment_locker.dm index a1a4ff10d0f..b03392e0dac 100644 --- a/code/modules/mining/equipment_locker.dm +++ b/code/modules/mining/equipment_locker.dm @@ -693,14 +693,7 @@ mouse_opacity = 1 faction = list("neutral") a_intent = I_HARM - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 wander = 0 idle_vision_range = 5 diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index 538bbc24d3f..872b53ee6dd 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -114,7 +114,7 @@ message = stars(message) var/speaker_name = "unknown" - if(speaker) + if(speaker) speaker_name = speaker.name if(vname) @@ -160,7 +160,7 @@ else track = "[speaker_name] ([jobname])" else - if(istype(follow_target, /obj/machinery/bot)) + if(istype(follow_target, /mob/living/simple_animal/bot)) track = "[speaker_name] ([jobname])" else track = "[speaker_name] ([jobname])" diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index 3dc4575a20b..1c445f2a003 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -196,7 +196,7 @@ usr.hud_used.nightvisionicon.icon_state = "nightvision0" -/mob/living/carbon/alien/assess_threat(var/obj/machinery/bot/secbot/judgebot, var/lasercolor) +/mob/living/carbon/alien/assess_threat(var/mob/living/simple_animal/bot/secbot/judgebot, var/lasercolor) if(judgebot.emagged == 2) return 10 //Everyone is a criminal! var/threatcount = 0 diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index ad27272cd94..7aadabe066a 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -636,8 +636,8 @@ // called when something steps onto a human // this handles mulebots and vehicles /mob/living/carbon/human/Crossed(var/atom/movable/AM) - if(istype(AM, /obj/machinery/bot/mulebot)) - var/obj/machinery/bot/mulebot/MB = AM + if(istype(AM, /mob/living/simple_animal/bot/mulebot)) + var/mob/living/simple_animal/bot/mulebot/MB = AM MB.RunOver(src) if(istype(AM, /obj/vehicle)) @@ -1753,7 +1753,7 @@ if(M.stat == 2) M.gib() -/mob/living/carbon/human/assess_threat(var/obj/machinery/bot/secbot/judgebot, var/lasercolor) +/mob/living/carbon/human/assess_threat(var/mob/living/simple_animal/bot/secbot/judgebot, var/lasercolor) if(judgebot.emagged == 2) return 10 //Everyone is a criminal! diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index b65cd8dfb2b..a834f9e2569 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -41,6 +41,7 @@ var/list/ai_verbs_default = list( anchored = 1 // -- TLE density = 1 status_flags = CANSTUN|CANPARALYSE|CANPUSH + can_strip = 0 var/list/network = list("SS13","Telecomms","Research Outpost","Mining Outpost") var/obj/machinery/camera/current = null var/list/connected_robots = list() @@ -80,7 +81,7 @@ var/list/ai_verbs_default = list( var/can_shunt = 1 var/last_announcement = "" var/datum/announcement/priority/announcement - var/obj/machinery/bot/Bot + var/mob/living/simple_animal/bot/Bot var/turf/waypoint //Holds the turf of the currently selected waypoint. var/waypoint_mode = 0 //Waypoint mode is for selecting a turf via clicking. @@ -513,7 +514,7 @@ var/list/ai_verbs_default = list( return if (href_list["trackbot"]) - var/obj/machinery/bot/target = locate(href_list["trackbot"]) in aibots + var/mob/living/simple_animal/bot/target = locate(href_list["trackbot"]) in simple_animal_list if(target) ai_actual_track(target) else @@ -521,7 +522,7 @@ var/list/ai_verbs_default = list( return if (href_list["callbot"]) //Command a bot to move to a selected location. - Bot = locate(href_list["callbot"]) in aibots + Bot = locate(href_list["callbot"]) in simple_animal_list if(!Bot || Bot.remote_disabled || src.control_disabled) return //True if there is no bot found, the bot is manually emagged, or the AI is carded with wireless off. waypoint_mode = 1 @@ -529,7 +530,7 @@ var/list/ai_verbs_default = list( return if (href_list["interface"]) //Remotely connect to a bot! - Bot = locate(href_list["interface"]) in aibots + Bot = locate(href_list["interface"]) in simple_animal_list if(!Bot || Bot.remote_disabled || src.control_disabled) return Bot.attack_ai(src) @@ -643,10 +644,10 @@ var/list/ai_verbs_default = list( d += "Query network status
                " d += "" - for (Bot in aibots) + for (var/mob/living/simple_animal/bot/Bot in simple_animal_list) if((Bot.z in ai_allowed_Zlevel) && !Bot.remote_disabled) //Only non-emagged bots on the allowed Z-level are detected! bot_area = get_area(Bot) - d += "" + d += "" //If the bot is on, it will display the bot's current mode status. If the bot is not mode, it will just report "Idle". "Inactive if it is not on at all. d += "" d += "" diff --git a/code/modules/mob/living/simple_animal/bees.dm b/code/modules/mob/living/simple_animal/bees.dm index f88f41e6d17..c5fcf9fc938 100644 --- a/code/modules/mob/living/simple_animal/bees.dm +++ b/code/modules/mob/living/simple_animal/bees.dm @@ -17,14 +17,6 @@ var/obj/machinery/portable_atmospherics/hydroponics/my_hydrotray // Allow final solutions. - min_oxy = 5 - max_oxy = 0 - min_tox = 0 - max_tox = 1 - min_co2 = 0 - max_co2 = 5 - min_n2 = 0 - max_n2 = 0 minbodytemp = 0 maxbodytemp = 360 @@ -37,145 +29,143 @@ parent.owned_bee_swarms.Remove(src) return ..() -/mob/living/simple_animal/bee/Life() - ..() +/mob/living/simple_animal/bee/process_ai() + //if we're strong enough, sting some people + var/mob/living/carbon/human/M = target_mob + var/sting_prob = 100 // Bees will always try to sting. + if(M in view(src,1)) // Can I see my target? + if(prob(max(feral * 10, 0))) // Am I mad enough to want to sting? And yes, when I initially appear, I AM mad enough + var/obj/item/clothing/worn_suit = M.wear_suit + var/obj/item/clothing/worn_helmet = M.head + if(worn_suit) // Are you wearing clothes? + sting_prob -= min(worn_suit.armor["bio"],70) // Is it sealed? I can't get to 70% of your body. + if(worn_helmet) + sting_prob -= min(worn_helmet.armor["bio"],30) // Is your helmet sealed? I can't get to 30% of your body. + if( prob(sting_prob) && (M.stat == CONSCIOUS || (M.stat == UNCONSCIOUS && prob(25))) ) // Try to sting! If you're not moving, think about stinging. + M.apply_damage(min(strength,2)+mut, BRUTE, sharp=1) // Stinging. The more mutated I am, the harder I sting. + M.apply_damage((round(feral/10,1)*(max((round(strength/20,1)),1)))+toxic, TOX) // Bee venom based on how angry I am and how many there are of me! + M << "\red You have been stung!" - if(stat == CONSCIOUS) - //if we're strong enough, sting some people - var/mob/living/carbon/human/M = target_mob - var/sting_prob = 100 // Bees will always try to sting. - if(M in view(src,1)) // Can I see my target? - if(prob(max(feral * 10, 0))) // Am I mad enough to want to sting? And yes, when I initially appear, I AM mad enough - var/obj/item/clothing/worn_suit = M.wear_suit - var/obj/item/clothing/worn_helmet = M.head - if(worn_suit) // Are you wearing clothes? - sting_prob -= min(worn_suit.armor["bio"],70) // Is it sealed? I can't get to 70% of your body. - if(worn_helmet) - sting_prob -= min(worn_helmet.armor["bio"],30) // Is your helmet sealed? I can't get to 30% of your body. - if( prob(sting_prob) && (M.stat == CONSCIOUS || (M.stat == UNCONSCIOUS && prob(25))) ) // Try to sting! If you're not moving, think about stinging. - M.apply_damage(min(strength,2)+mut, BRUTE, sharp=1) // Stinging. The more mutated I am, the harder I sting. - M.apply_damage((round(feral/10,1)*(max((round(strength/20,1)),1)))+toxic, TOX) // Bee venom based on how angry I am and how many there are of me! - M << "\red You have been stung!" - M.flash_pain() + //if we're chasing someone, get a little bit angry + if(target_mob && prob(10)) + feral++ + M.flash_pain() - //if we're chasing someone, get a little bit angry - if(target_mob && prob(10)) - feral++ - - //calm down a little bit - if(feral > 0) - if(prob(feral * 10)) - feral -= 1 - else - //if feral is less than 0, we're becalmed by smoke or steam - if(feral < 0) - feral += 1 - - if(target_mob) - target_mob = null - target_turf = null - if(strength > 5) - //calm down and spread out a little - var/mob/living/simple_animal/bee/B = new(get_turf(pick(orange(src,1)))) - B.strength = rand(1,5) - src.strength -= B.strength - if(src.strength <= 5) - src.icon_living = "bees[src.strength]" - B.icon_living = "bees[B.strength]" - if(src.parent) - B.parent = src.parent - src.parent.owned_bee_swarms.Add(B) - - //make some noise - if(prob(0.5)) - src.visible_message("\blue [pick("Buzzzz.","Hmmmmm.","Bzzz.")]") - - //smoke, water and steam calms us down - var/calming = 0 - var/list/calmers = list(/obj/effect/effect/chem_smoke, \ - /obj/effect/effect/water, \ - /obj/effect/effect/foam, \ - /obj/effect/effect/steam, \ - /obj/effect/mist) - - for(var/obj/effect/check_effect in src.loc) - if(check_effect.type in calmers) - calming = 1 - break - - if(calming) - if(feral > 0) - src.visible_message("\blue The bees calm down!") - feral = -10 - target_mob = null - target_turf = null - wander = 1 - - for(var/mob/living/simple_animal/bee/B in src.loc) - if(B == src) - continue - - if(feral > 0) - src.strength += B.strength - qdel(B) - src.icon_living = "bees[src.strength]" - if(strength > 5) - icon_living = "bees_swarm" - else if(prob(10)) - //make the other swarm of bees stronger, then move away - var/total_bees = B.strength + src.strength - if(total_bees < 10) - B.strength = min(5, total_bees) - src.strength = total_bees - B.strength - - B.icon_living = "bees[B.strength]" - if(src.strength <= 0) - qdel(src) - return - src.icon_living = "bees[B.strength]" - var/turf/simulated/floor/T = get_turf(get_step(src, pick(1,2,4,8))) - density = 1 - if(T.Enter(src, get_turf(src))) - src.loc = T - density = 0 - break + //calm down a little bit + if(feral > 0) + if(prob(feral * 10)) + feral -= 1 + else + //if feral is less than 0, we're becalmed by smoke or steam + if(feral < 0) + feral += 1 if(target_mob) - if(target_mob in view(src,7)) - target_turf = get_turf(target_mob) - wander = 0 + target_mob = null + target_turf = null + if(strength > 5) + //calm down and spread out a little + var/mob/living/simple_animal/bee/B = new(get_turf(pick(orange(src,1)))) + B.strength = rand(1,5) + src.strength -= B.strength + if(src.strength <= 5) + src.icon_living = "bees[src.strength]" + B.icon_living = "bees[B.strength]" + if(src.parent) + B.parent = src.parent + src.parent.owned_bee_swarms.Add(B) - else // My target's gone! But I might still be pissed! You there. You look like a good stinging target! - for(var/mob/living/carbon/G in view(src,7)) - target_mob = G - break + //make some noise + if(prob(0.5)) + src.visible_message("\blue [pick("Buzzzz.","Hmmmmm.","Bzzz.")]") - if(target_turf) - if (!(DirBlocked(get_step(src, get_dir(src,target_turf)),get_dir(src,target_turf)))) // Check for windows and doors! - Move(get_step(src, get_dir(src,target_turf))) - if (prob(0.1)) - src.visible_message("\blue The bees swarm after [target_mob]!") - if(src.loc == target_turf) - target_turf = null - wander = 1 - else - //find some flowers, harvest - //angry bee swarms don't hang around - if(feral > 0) - turns_per_move = rand(1,3) - else if(feral < 0) - turns_since_move = 0 - else if(!my_hydrotray || my_hydrotray.loc != src.loc || my_hydrotray.dead || !my_hydrotray.seed) - var/obj/machinery/portable_atmospherics/hydroponics/my_hydrotray = locate() in src.loc - if(my_hydrotray) - if(!my_hydrotray.dead && my_hydrotray.seed) - turns_per_move = rand(20,50) - else - my_hydrotray = null + //smoke, water and steam calms us down + var/calming = 0 + var/list/calmers = list(/obj/effect/effect/chem_smoke, \ + /obj/effect/effect/water, \ + /obj/effect/effect/foam, \ + /obj/effect/effect/steam, \ + /obj/effect/mist) - pixel_x = rand(-12,12) - pixel_y = rand(-12,12) + for(var/obj/effect/check_effect in src.loc) + if(check_effect.type in calmers) + calming = 1 + break + if(calming) + if(feral > 0) + src.visible_message("\blue The bees calm down!") + feral = -10 + target_mob = null + target_turf = null + wander = 1 + + for(var/mob/living/simple_animal/bee/B in src.loc) + if(B == src) + continue + + if(feral > 0) + src.strength += B.strength + qdel(B) + src.icon_living = "bees[src.strength]" + if(strength > 5) + icon_living = "bees_swarm" + else if(prob(10)) + //make the other swarm of bees stronger, then move away + var/total_bees = B.strength + src.strength + if(total_bees < 10) + B.strength = min(5, total_bees) + src.strength = total_bees - B.strength + + B.icon_living = "bees[B.strength]" + if(src.strength <= 0) + qdel(src) + return + src.icon_living = "bees[B.strength]" + var/turf/simulated/floor/T = get_turf(get_step(src, pick(1,2,4,8))) + density = 1 + if(T.Enter(src, get_turf(src))) + src.loc = T + density = 0 + break + + if(target_mob) + if(target_mob in view(src,7)) + target_turf = get_turf(target_mob) + wander = 0 + + else // My target's gone! But I might still be pissed! You there. You look like a good stinging target! + for(var/mob/living/carbon/G in view(src,7)) + target_mob = G + break + + if(target_turf) + if (!(DirBlocked(get_step(src, get_dir(src,target_turf)),get_dir(src,target_turf)))) // Check for windows and doors! + Move(get_step(src, get_dir(src,target_turf))) + if (prob(0.1)) + src.visible_message("\blue The bees swarm after [target_mob]!") + if(src.loc == target_turf) + target_turf = null + wander = 1 + else + //find some flowers, harvest + //angry bee swarms don't hang around + if(feral > 0) + turns_per_move = rand(1,3) + else if(feral < 0) + turns_since_move = 0 + else if(!my_hydrotray || my_hydrotray.loc != src.loc || my_hydrotray.dead || !my_hydrotray.seed) + var/obj/machinery/portable_atmospherics/hydroponics/my_hydrotray = locate() in src.loc + if(my_hydrotray) + if(!my_hydrotray.dead && my_hydrotray.seed) + turns_per_move = rand(20,50) + else + my_hydrotray = null + + pixel_x = rand(-12,12) + pixel_y = rand(-12,12) + +/mob/living/simple_animal/bee/handle_state_icons() if(!parent && prob(10)) strength -= 1 if(strength <= 0) @@ -187,3 +177,5 @@ /*icon_living = "[strength]" if(strength > 5) icon_living = "unknown"*/ + + ..() \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm new file mode 100644 index 00000000000..b535d1d3870 --- /dev/null +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -0,0 +1,1006 @@ +//Defines for bots are now found in code\__DEFINES\bots.dm + +// AI (i.e. game AI, not the AI player) controlled bots +/mob/living/simple_animal/bot + icon = 'icons/obj/aibots.dmi' + layer = MOB_LAYER + light_range = 3 + stop_automated_movement = 1 + wander = 0 + healable = 0 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + sentience_type = SENTIENCE_ARTIFICIAL + status_flags = 0 //no default canpush + can_strip = 0 + + speak_emote = list("states") + friendly = "boops" + + faction = list("neutral", "silicon") + + var/obj/machinery/bot_core/bot_core = null + var/bot_core_type = /obj/machinery/bot_core + var/list/users = list() //for dialog updates + var/window_id = "bot_control" + var/window_name = "Protobot 1.0" //Popup title + var/window_width = 0 //0 for default size + var/window_height = 0 + var/obj/item/device/paicard/paicard // Inserted pai card. + var/allow_pai = 1 // Are we even allowed to insert a pai card. + var/bot_name + + var/list/player_access = list() + var/emagged = 0 + var/obj/item/weapon/card/id/access_card // the ID card that the bot "holds" + var/list/prev_access = list() + var/on = 1 + var/open = 0//Maint panel + var/locked = 1 + var/hacked = 0 //Used to differentiate between being hacked by silicons and emagged by humans. + var/text_hack = "" //Custom text returned to a silicon upon hacking a bot. + var/text_dehack = "" //Text shown when resetting a bots hacked status to normal. + var/text_dehack_fail = "" //Shown when a silicon tries to reset a bot emagged with the emag item, which cannot be reset. + var/declare_message = "" //What the bot will display to the HUD user. + var/frustration = 0 //Used by some bots for tracking failures to reach their target. + var/base_speed = 2 //The speed at which the bot moves, or the number of times it moves per process() tick. + var/turf/ai_waypoint //The end point of a bot's path, or the target location. + var/list/path = list() //List of turfs through which a bot 'steps' to reach the waypoint. + var/pathset = 0 + var/list/ignore_list = list() //List of unreachable targets for an ignore-list enabled bot to ignore. + var/mode = BOT_IDLE //Standardizes the vars that indicate the bot is busy with its function. + var/tries = 0 //Number of times the bot tried and failed to move. + var/remote_disabled = 0 //If enabled, the AI cannot *Remotely* control a bot. It can still control it through cameras. + var/mob/living/silicon/ai/calling_ai //Links a bot to the AI calling it. + var/obj/item/device/radio/Radio //The bot's radio, for speaking to people. + var/list/radio_config = null //which channels can the bot listen to + var/radio_channel = "Common" //The bot's default radio channel + var/auto_patrol = 0// set to make bot automatically patrol + var/turf/patrol_target // this is turf to navigate to (location of beacon) + var/turf/summon_target // The turf of a user summoning a bot. + var/new_destination // pending new destination (waiting for beacon response) + var/destination // destination description tag + var/next_destination // the next destination in the patrol route + + var/blockcount = 0 //number of times retried a blocked path + var/awaiting_beacon = 0 // count of pticks awaiting a beacon response + + var/nearest_beacon // the nearest beacon's tag + var/turf/nearest_beacon_loc // the nearest beacon's location + + var/model = "" //The type of bot it is. + var/bot_purpose = "improve the station to the best of your ability" + var/control_freq = BOT_FREQ // bot control frequency + var/bot_filter // The radio filter the bot uses to identify itself on the network. + var/bot_type = 0 //The type of bot it is, for radio control. + //This holds text for what the bot is mode doing, reported on the remote bot control interface. + var/list/mode_name = list("In Pursuit","Preparing to Arrest", "Arresting", \ + "Beginning Patrol", "Patrolling", "Summoned by PDA", \ + "Cleaning", "Repairing", "Proceeding to work site", "Healing", \ + "Responding", "Navigating to Delivery Location", "Navigating to Home", \ + "Waiting for clear path", "Calculating navigation path", "Pinging beacon network", "Unable to reach destination") + + hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD) //Diagnostic HUD views + +/mob/living/simple_animal/bot/proc/get_mode() + if(client) //Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player. + if(paicard) + return "pAI Controlled" + else + return "Autonomous" + else if(!on) + return "Inactive" + else if(!mode) + return "Idle" + else + return "[mode_name[mode]]" + +/mob/living/simple_animal/bot/proc/turn_on() + if(stat) + return 0 + on = 1 + set_light(initial(light_range)) + update_icon() + update_controls() + diag_hud_set_botstat() + return 1 + +/mob/living/simple_animal/bot/proc/turn_off() + on = 0 + set_light(0) + bot_reset() //Resets an AI's call, should it exist. + update_icon() + update_controls() + +/mob/living/simple_animal/bot/New() + ..() + icon_living = icon_state + icon_dead = icon_state + icon_resting = icon_state + access_card = new /obj/item/weapon/card/id(src) +//This access is so bots can be immediately set to patrol and leave Robotics, instead of having to be let out first. + access_card.access += access_robotics + set_custom_texts() + Radio = new/obj/item/device/radio/headset(src) + Radio.subspace_transmission = 1 + Radio.canhear_range = 0 // anything greater will have the bot broadcast the channel as if it were saying it out loud. + if(!radio_config) + radio_config = list("AI Private" = 1) + if(!(radio_channel in radio_config)) // put it first so it's the :h channel + radio_config.Insert(1, "[radio_channel]") + radio_config["[radio_channel]"] = 1 + Radio.config(radio_config) + + bot_core = new bot_core_type(src) + spawn(30) + if(radio_controller && bot_filter) + radio_controller.add_object(bot_core, control_freq, bot_filter) + + prepare_huds() + var/datum/atom_hud/data/diagnostic/diag_hud = huds[DATA_HUD_DIAGNOSTIC] + diag_hud.add_to_hud(src) + diag_hud_set_bothealth() + diag_hud_set_botstat() + diag_hud_set_botmode() + // give us the hud too! + diag_hud.add_hud_to(src) + permanent_huds |= diag_hud + +/mob/living/simple_animal/bot/update_canmove() + . = ..() + if(!on) + . = 0 + canmove = . + +/mob/living/simple_animal/bot/Destroy() + if(paicard) + ejectpai() + qdel(Radio) + Radio = null + qdel(access_card) + access_card = null + if(radio_controller && bot_filter) + radio_controller.remove_object(bot_core, control_freq) + qdel(bot_core) + bot_core = null + return ..() + +/mob/living/simple_animal/bot/death(gibbed) + explode() + ..() + +/mob/living/simple_animal/bot/proc/explode() + qdel(src) + +/mob/living/simple_animal/bot/emag_act(mob/user) + if(locked) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again. + locked = 0 + emagged = 1 + user << "You bypass [src]'s controls." + return + if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. + emagged = 2 + remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel. + locked = 1 //Access denied forever! + bot_reset() + turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP. + src << "(#$*#$^^( OVERRIDE DETECTED" + show_laws() + add_logs(user, src, "emagged") + return + else //Bot is unlocked, but the maint panel has not been opened with a screwdriver yet. + user << "You need to open maintenance panel first!" + +/mob/living/simple_animal/bot/examine(mob/user) + ..() + if(health < maxHealth) + if(health > maxHealth/3) + user << "[src]'s parts look loose." + else + user << "[src]'s parts look very loose!" + else + user << "[src] is in pristine condition." + +/mob/living/simple_animal/bot/adjustBruteLoss(amount) + . = ..() + + if(amount > 0 && prob(10)) + new /obj/effect/decal/cleanable/blood/oil(loc) + +/mob/living/simple_animal/bot/adjustFireLoss(amount) + . = ..() + + if(amount > 0 && prob(5)) + new /obj/effect/decal/cleanable/blood/oil(loc) + +/mob/living/simple_animal/bot/updatehealth() + ..() + diag_hud_set_bothealth() + +/mob/living/simple_animal/bot/handle_automated_action() + diag_hud_set_botmode() + + if(!on) + return + + switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command. + if(BOT_RESPONDING) //Called by the AI. + call_mode() + return + if(BOT_SUMMON) //Called by PDA + bot_summon() + return + return 1 //Successful completion. Used to prevent child process() continuing if this one is ended early. + +/mob/living/simple_animal/bot/attack_alien(var/mob/living/carbon/alien/user as mob) + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src) + apply_damage(rand(15,30), BRUTE) + visible_message("[user] has slashed [src]!") + playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) + if(prob(10)) + new /obj/effect/decal/cleanable/blood/oil(loc) + +/mob/living/simple_animal/bot/attack_animal(var/mob/living/simple_animal/M as mob) + M.do_attack_animation(src) + if(M.melee_damage_upper == 0) + return + apply_damage(M.melee_damage_upper, BRUTE) + visible_message("[M] has [M.attacktext] [src]!") + add_logs(M, src, "attacked", admin=0) + if(prob(10)) + new /obj/effect/decal/cleanable/blood/oil(loc) + +/mob/living/simple_animal/bot/attack_hand(mob/living/carbon/human/H) + if(H.a_intent == "help") + interact(H) + else + return ..() + +/mob/living/simple_animal/bot/attack_ai(mob/user) + if(!topic_denied(user)) + interact(user) + else + user << "[src]'s interface is not responding!" + +/mob/living/simple_animal/bot/proc/interact(mob/user) + show_controls(user) + +/mob/living/simple_animal/bot/attackby(obj/item/weapon/W, mob/user, params) + if(istype(W, /obj/item/weapon/screwdriver)) + if(!locked) + open = !open + user << "The maintenance panel is now [open ? "opened" : "closed"]." + else + user << "The maintenance panel is locked." + else if(istype(W, /obj/item/weapon/card/id) || istype(W, /obj/item/device/pda)) + if(bot_core.allowed(user) && !open && !emagged) + locked = !locked + user << "Controls are now [locked ? "locked." : "unlocked."]" + else + if(emagged) + user << "ERROR" + if(open) + user << "Please close the access panel before locking it." + else + user << "Access denied." + else if(istype(W, /obj/item/device/paicard)) + if(paicard) + user << "A [paicard] is already inserted!" + else if(allow_pai && !key) + if(!locked && !open) + var/obj/item/device/paicard/card = W + if(card.pai && card.pai.mind) + if(!user.drop_item()) + return + W.forceMove(src) + paicard = card + user.visible_message("[user] inserts [W] into [src]!","You insert [W] into [src].") + paicard.pai.mind.transfer_to(src) + src << "You sense your form change as you are uploaded into [src]." + bot_name = name + name = paicard.pai.name + faction = user.faction + add_logs(user, paicard.pai, "uploaded to [src.bot_name],") + else + user << "[W] is inactive." + else + user << "The personality slot is locked." + else + user << "[src] is not compatible with [W]" + else if(istype(W, /obj/item/weapon/hemostat) && paicard) + if(open) + user << "Close the access panel before manipulating the personality slot!" + else + user << "You attempt to pull [paicard] free..." + if(do_after(user, 30, target = src)) + if (paicard) + user.visible_message("[user] uses [W] to pull [paicard] out of [bot_name]!","You pull [paicard] out of [bot_name] with [W].") + ejectpai(user) + else + user.changeNext_move(CLICK_CD_MELEE) + if(istype(W, /obj/item/weapon/weldingtool) && user.a_intent != "harm") + if(health >= maxHealth) + user << "[src] does not need a repair!" + return + if(!open) + user << "Unable to repair with the maintenance panel closed!" + return + var/obj/item/weapon/weldingtool/WT = W + if(WT.remove_fuel(0, user)) + adjustBruteLoss(-10) + adjustFireLoss(-10) + updatehealth() + add_fingerprint(user) + user.visible_message("[user] repairs [src]!","You repair [src].") + else + user << "The welder must be on for this task!" + else + if(W.force) //if force is non-zero + var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread + s.set_up(5, 1, src) + s.start() + ..() + +/mob/living/simple_animal/bot/bullet_act(obj/item/projectile/Proj) + if(Proj && (Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + if(prob(75) && Proj.damage > 0) + var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread + s.set_up(5, 1, src) + s.start() + return ..() + +/mob/living/simple_animal/bot/emp_act(severity) + var/was_on = on + stat |= EMPED + var/obj/effect/overlay/pulse2 = new/obj/effect/overlay ( loc ) + pulse2.icon = 'icons/effects/effects.dmi' + pulse2.icon_state = "empdisable" + pulse2.name = "emp sparks" + pulse2.anchored = 1 + pulse2.dir = pick(cardinal) + spawn(10) + pulse2.delete() + + if(paicard) + paicard.emp_act(severity) + src.visible_message("[paicard] is flies out of [bot_name]!","You are forcefully ejected from [bot_name]!") + ejectpai(0) + if(on) + turn_off() + spawn(severity*300) + stat &= ~EMPED + if(was_on) + turn_on() + +/mob/living/simple_animal/bot/rename_character(oldname, newname) + if(!..(oldname, newname)) + return 0 + + set_custom_texts() + return 1 + +/mob/living/simple_animal/bot/proc/set_custom_texts() //Superclass for setting hack texts. Appears only if a set is not given to a bot locally. + text_hack = "You hack [name]." + text_dehack = "You reset [name]." + text_dehack_fail = "You fail to reset [name]." + +/mob/living/simple_animal/bot/proc/speak(message, channel) //Pass a message to have the bot say() it. Pass a frequency to say it on the radio. + if((!on) || (!message)) + return + if(channel && Radio.channels[channel])// Use radio if we have channel key + Radio.autosay(message, name, channel) + else + say(message) + return + +//Generalized behavior code, override where needed! + +/* +scan() will search for a given type (such as turfs, human mobs, or objects) in the bot's view range, and return a single result. +Arguments: The object type to be searched (such as "/mob/living/carbon/human"), the old scan result to be ignored, if one exists, +and the view range, which defaults to 7 (full screen) if an override is not passed. +If the bot maintains an ignore list, it is also checked here. + +Example usage: patient = scan(/mob/living/carbon/human, oldpatient, 1) +The proc would return a human next to the bot to be set to the patient var. +Pass the desired type path itself, declaring a temporary var beforehand is not required. +*/ +/mob/living/simple_animal/bot/proc/scan(scan_type, old_target, scan_range = DEFAULT_SCAN_RANGE) + var/final_result + for (var/scan in shuffle(view(scan_range, src))) //Search for something in range! + if(!istype(scan, scan_type)) //Check that the thing we found is the type we want! + continue //If not, keep searching! + if( (scan in ignore_list) || (scan == old_target) ) //Filter for blacklisted elements, usually unreachable or previously processed oness + continue + var/scan_result = process_scan(scan) //Some bots may require additional processing when a result is selected. + if(scan_result) + final_result = scan_result + else + continue //The current element failed assessment, move on to the next. + return final_result + +//When the scan finds a target, run bot specific processing to select it for the next step. Empty by default. +/mob/living/simple_animal/bot/proc/process_scan(scan_target) + return scan_target + + +/mob/living/simple_animal/bot/proc/add_to_ignore(subject) + if(ignore_list.len < 50) //This will help keep track of them, so the bot is always trying to reach a blocked spot. + ignore_list |= subject + else if(ignore_list.len >= subject) //If the list is full, insert newest, delete oldest. + ignore_list -= ignore_list[1] + ignore_list |= subject + +/* +Movement proc for stepping a bot through a path generated through A-star. +Pass a positive integer as an argument to override a bot's default speed. +*/ +/mob/living/simple_animal/bot/proc/bot_move(dest, move_speed) + + if(!dest || !path || path.len == 0) //A-star failed or a path/destination was not set. + path = list() + return 0 + dest = get_turf(dest) //We must always compare turfs, so get the turf of the dest var if dest was originally something else. + var/turf/last_node = get_turf(path[path.len]) //This is the turf at the end of the path, it should be equal to dest. + if(get_turf(src) == dest) //We have arrived, no need to move again. + return 1 + else if(dest != last_node) //The path should lead us to our given destination. If this is not true, we must stop. + path = list() + return 0 + var/step_count = move_speed ? move_speed : base_speed //If a value is passed into move_speed, use that instead of the default speed var. + + if(step_count >= 1 && tries < BOT_STEP_MAX_RETRIES) + for(var/step_number = 0, step_number < step_count,step_number++) + spawn(BOT_STEP_DELAY*step_number) + bot_step(dest) + else + return 0 + return 1 + + +/mob/living/simple_animal/bot/proc/bot_step(dest) //Step,increase tries if failed + if(!path) + return 0 + if(path.len > 1) + step_towards(src, path[1]) + if(get_turf(src) == path[1]) //Successful move + path -= path[1] + tries = 0 + else + tries++ + return 0 + else if(path.len == 1) + step_to(src, dest) + path = list() + return 1 + + +/mob/living/simple_animal/bot/proc/check_bot_access() + if(mode != BOT_SUMMON && mode != BOT_RESPONDING) + access_card.access = prev_access + +/mob/living/simple_animal/bot/proc/call_bot(caller, turf/waypoint, message=TRUE) + bot_reset() //Reset a bot before setting it to call mode. + var/area/end_area = get_area(waypoint) + + //For giving the bot temporary all-access. + var/obj/item/weapon/card/id/all_access = new /obj/item/weapon/card/id + var/datum/job/captain/All = new/datum/job/captain + all_access.access = All.get_access() + + path = get_path_to(src, waypoint, /turf/proc/Distance_cardinal, 0, 200, id=all_access) + calling_ai = caller //Link the AI to the bot! + ai_waypoint = waypoint + + if(path && path.len) //Ensures that a valid path is calculated! + if(!on) + turn_on() //Saves the AI the hassle of having to activate a bot manually. + access_card = all_access //Give the bot all-access while under the AI's command. + if(message) + calling_ai << "\icon[src] [name] called to [end_area.name]. [path.len-1] meters to destination." + pathset = 1 + mode = BOT_RESPONDING + tries = 0 + else + if(message) + calling_ai << "Failed to calculate a valid route. Ensure destination is clear of obstructions and within range." + calling_ai = null + path = list() + +/mob/living/simple_animal/bot/proc/call_mode() //Handles preparing a bot for a call, as well as calling the move proc. +//Handles the bot's movement during a call. + var/success = bot_move(ai_waypoint, 3) + if(!success) + if(calling_ai) + calling_ai << "\icon[src] [get_turf(src) == ai_waypoint ? "[src] successfully arrived to waypoint." : "[src] failed to reach waypoint."]" + calling_ai = null + bot_reset() + +/mob/living/simple_animal/bot/proc/bot_reset() + if(calling_ai) //Simple notification to the AI if it called a bot. It will not know the cause or identity of the bot. + calling_ai << "Call command to a bot has been reset." + calling_ai = null + path = list() + summon_target = null + pathset = 0 + access_card.access = prev_access + tries = 0 + mode = BOT_IDLE + diag_hud_set_botstat() + diag_hud_set_botmode() + + + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +//Patrol and summon code! +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/mob/living/simple_animal/bot/proc/bot_patrol() + patrol_step() + spawn(5) + if(mode == BOT_PATROL) + patrol_step() + return + +/mob/living/simple_animal/bot/proc/start_patrol() + + if(tries >= BOT_STEP_MAX_RETRIES) //Bot is trapped, so stop trying to patrol. + auto_patrol = 0 + tries = 0 + speak("Unable to start patrol.") + + return + + if(!auto_patrol) //A bot not set to patrol should not be patrolling. + mode = BOT_IDLE + return + + if(patrol_target) // has patrol target + spawn(0) + calc_path() // Find a route to it + if(path.len == 0) + patrol_target = null + return + mode = BOT_PATROL + else // no patrol target, so need a new one + speak("Engaging patrol mode.") + find_patrol_target() + tries++ + return + +// perform a single patrol step + +/mob/living/simple_animal/bot/proc/patrol_step() + + if(client) // In use by player, don't actually move. + return + + if(loc == patrol_target) // reached target + //Find the next beacon matching the target. + if(!get_next_patrol_target()) + find_patrol_target() //If it fails, look for the nearest one instead. + return + + else if(path.len > 0 && patrol_target) // valid path + var/turf/next = path[1] + if(next == loc) + path -= next + return + + + var/moved = bot_move(patrol_target)//step_towards(src, next) // attempt to move + if(!moved) //Couldn't proceed the next step of the path BOT_STEP_MAX_RETRIES times + spawn(2) + calc_path() + if(path.len == 0) + find_patrol_target() + tries = 0 + + else // no path, so calculate new one + mode = BOT_START_PATROL + +// finds the nearest beacon to self +/mob/living/simple_animal/bot/proc/find_patrol_target() + send_status() + nearest_beacon = null + new_destination = null + find_nearest_beacon() + if(nearest_beacon) + patrol_target = nearest_beacon_loc + destination = next_destination + else + auto_patrol = 0 + mode = BOT_IDLE + speak("Disengaging patrol mode.") + send_status() + +/mob/living/simple_animal/bot/proc/get_next_patrol_target() + // search the beacon list for the next target in the list. + for(var/obj/machinery/navbeacon/NB in navbeacons["[z]"]) + if(NB.location == next_destination) //Does the Beacon location text match the destination? + destination = new_destination //We now know the name of where we want to go. + patrol_target = NB.loc //Get its location and set it as the target. + next_destination = NB.codes["next_patrol"] //Also get the name of the next beacon in line. + return 1 + +/mob/living/simple_animal/bot/proc/find_nearest_beacon() + for(var/obj/machinery/navbeacon/NB in navbeacons["[z]"]) + var/dist = get_dist(src, NB) + if(nearest_beacon) //Loop though the beacon net to find the true closest beacon. + //Ignore the beacon if were are located on it. + if(dist>1 && dist 1) //Begin the search, save this one for comparison on the next loop. + nearest_beacon = NB.location + nearest_beacon_loc = NB.loc + patrol_target = nearest_beacon_loc + destination = nearest_beacon + +/mob/living/simple_animal/bot/proc/bot_control_message(command, mob/user, user_turf) + switch(command) + if("stop") + src << "STOP PATROL" + if("go") + src << "START PATROL" + if("summon") + var/area/a = get_area(user_turf) + src << "PRIORITY ALERT: [user] in [a.name]!" + if("home") + src << "RETURN HOME!" + if("ejectpai") + else + src << "Unidentified control sequence recieved: [command]" + +/obj/machinery/bot_core/receive_signal(datum/signal/signal) + owner.receive_signal(signal) + +/mob/living/simple_animal/bot/proc/receive_signal(datum/signal/signal) + if(!on) + return 1 //ACCESS DENIED + + var/recv = signal.data["command"] + var/user = signal.data["user"] + + // process all-bot input + if(recv == "bot_status" && (!signal.data["active"] || signal.data["active"] == src)) + send_status() + return 1 + + // check to see if we are the commanded bot + if(signal.data["active"] == src) + if(emagged == 2 || remote_disabled) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands. + return 1 + if(client) + bot_control_message(recv, user, signal.data["target"] ? signal.data["target"] : "Unknown") + // process control input + switch(recv) + if("stop") + bot_reset() //HOLD IT!! + auto_patrol = 0 + + if("go") + auto_patrol = 1 + + if("summon") + bot_reset() + var/list/user_access = signal.data["useraccess"] + summon_target = signal.data["target"] //Location of the user + if(user_access.len != 0) + access_card.access = user_access + prev_access //Adds the user's access, if any. + mode = BOT_SUMMON + calc_summon_path() + speak("Responding.", radio_channel) + + else + return 0 + return 1 + +// send a radio signal with a single data key/value pair +/mob/living/simple_animal/bot/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 +/mob/living/simple_animal/bot/proc/post_signal_multiple(var/freq, var/list/keyval) + if(z != 1) //Bot control will only work on station. + return + var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) + if(!frequency) + return + + var/datum/signal/signal = new() + signal.source = bot_core + signal.transmission_method = 1 + signal.data = keyval + spawn() + if(signal.data["type"] == bot_type) + frequency.post_signal(bot_core, signal, filter = bot_filter) + else + frequency.post_signal(bot_core, signal) + +// signals bot status etc. to controller +/mob/living/simple_animal/bot/proc/send_status() + if(remote_disabled || emagged == 2) + return + var/list/kv = list( + "type" = bot_type, + "name" = name, + "loca" = get_area(src), // area + "mode" = mode + ) + post_signal_multiple(control_freq, kv) + +/mob/living/simple_animal/bot/proc/bot_summon() // summoned to PDA + summon_step() + +// calculates a path to the current destination +// given an optional turf to avoid +/mob/living/simple_animal/bot/proc/calc_path(turf/avoid) + check_bot_access() + path = get_path_to(src, patrol_target, /turf/proc/Distance_cardinal, 0, 120, id=access_card, exclude=avoid) + +/mob/living/simple_animal/bot/proc/calc_summon_path(turf/avoid) + check_bot_access() + spawn() + path = get_path_to(src, summon_target, /turf/proc/Distance_cardinal, 0, 150, id=access_card, exclude=avoid) + if(!path.len) //Cannot reach target. Give up and announce the issue. + speak("Summon command failed, destination unreachable.",radio_channel) + bot_reset() + +/mob/living/simple_animal/bot/proc/summon_step() + + if(client) // In use by player, don't actually move. + return + + if(loc == summon_target) // Arrived to summon location. + bot_reset() + return + + else if(path.len > 0 && summon_target) //Proper path acquired! + var/turf/next = path[1] + if(next == loc) + path -= next + return + + var/moved = bot_move(summon_target, 3) // Move attempt + if(!moved) + spawn(2) + calc_summon_path() + tries = 0 + + else // no path, so calculate new one + calc_summon_path() + +/mob/living/simple_animal/bot/proc/openedDoor(obj/machinery/door/D) + frustration = 0 + +/mob/living/simple_animal/bot/show_inv() + +/mob/living/simple_animal/bot/proc/show_controls(mob/M) + users |= M + var/dat = "" + dat = get_controls(M) + var/datum/browser/popup = new(M,window_id,window_name,350,600) + popup.set_content(dat) + popup.open() + onclose(M,window_id,ref=src) + return + +/mob/living/simple_animal/bot/proc/update_controls() + for(var/mob/M in users) + show_controls(M) + +/mob/living/simple_animal/bot/proc/get_controls(mob/M) + return "PROTOBOT - NOT FOR USE" + +/mob/living/simple_animal/bot/Topic(href, href_list) + if(href_list["close"])// HUE HUE + if(usr in users) + users.Remove(usr) + return 1 + + if(topic_denied(usr)) + usr << "[src]'s interface is not responding!" + return 1 + add_fingerprint(usr) + + if((href_list["power"]) && (bot_core.allowed(usr) || !locked)) + if(on) + turn_off() + else + turn_on() + + switch(href_list["operation"]) + if("patrol") + auto_patrol = !auto_patrol + bot_reset() + if("remote") + remote_disabled = !remote_disabled + if("hack") + if(emagged != 2) + emagged = 2 + hacked = 1 + locked = 1 + usr << "[text_hack]" + show_laws() + bot_reset() + else if(!hacked) + usr << "[text_dehack_fail]" + else + emagged = 0 + hacked = 0 + usr << "[text_dehack]" + show_laws() + bot_reset() + if("ejectpai") + if(paicard && (!locked || issilicon(usr) || check_rights(R_ADMIN, 0, usr))) + usr << "You eject [paicard] from [bot_name]" + ejectpai(usr) + update_controls() + +/mob/living/simple_animal/bot/handle_state_icons() + +/mob/living/simple_animal/bot/proc/update_icon() + icon_state = "[initial(icon_state)][on]" + +// Machinery to simplify topic and access calls +/obj/machinery/bot_core + use_power = 0 + var/mob/living/simple_animal/bot/owner = null + +/obj/machinery/bot_core/New(loc) + ..() + owner = loc + if(!istype(owner)) + qdel(src) + +/mob/living/simple_animal/bot/proc/topic_denied(mob/user) //Access check proc for bot topics! Remember to place in a bot's individual Topic if desired. + if(user.incapacitated() || !(issilicon(user) || in_range(src, user))) + return 1 + // 0 for access, 1 for denied. + if(emagged == 2) //An emagged bot cannot be controlled by humans, silicons can if one hacked it. + if(!hacked) //Manually emagged by a human - access denied to all. + return 1 + else if(!issilicon(user) && !check_rights(R_ADMIN, 0, user)) //Bot is hacked, so only silicons and admins are allowed access. + return 1 + return 0 + +/mob/living/simple_animal/bot/proc/hack(mob/user) + var/hack + if(issilicon(user) || check_rights(R_ADMIN, 0, user)) //Allows silicons or admins to toggle the emag status of a bot. + hack += "[emagged == 2 ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]
                " + hack += "Harm Prevention Safety System: [emagged ? "DANGER" : "Engaged"]
                " + else if(!locked) //Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control. + hack += "Remote network control radio: [remote_disabled ? "Disconnected" : "Connected"]
                " + return hack + +/mob/living/simple_animal/bot/proc/showpai(mob/user) + var/eject = "" + if(!locked || issilicon(usr) || check_rights(R_ADMIN, 0, user)) + if(paicard || allow_pai) + eject += "Personality card status: " + if(paicard) + if(client) + eject += "Active" + else + eject += "Inactive" + else if(!allow_pai || key) + eject += "Unavailable" + else + eject += "Not inserted" + eject += "
                " + eject += "
                " + return eject + +/mob/living/simple_animal/bot/proc/ejectpai(mob/user = null, announce = 1) + if(paicard) + if(mind && paicard.pai) + mind.transfer_to(paicard.pai) + else if(paicard.pai) + paicard.pai.key = key + else + ghostize(0) // The pAI card that just got ejected was dead. + key = null + paicard.forceMove(loc) + if(user) + add_logs(user, paicard.pai, "ejected from [src.bot_name],") + else + add_logs(src, paicard.pai, "ejected") + if(announce) + paicard.pai << "You feel your control fade as [paicard] ejects from [bot_name]." + paicard = null + name = bot_name + faction = initial(faction) + +/mob/living/simple_animal/bot/proc/ejectpairemote(mob/user) + if(bot_core.allowed(user) && paicard) + speak("Ejecting personality chip.", radio_channel) + ejectpai(user) + +/mob/living/simple_animal/bot/Login() + . = ..() + access_card.access += player_access + diag_hud_set_botmode() + show_laws() + +/mob/living/simple_animal/bot/Logout() + . = ..() + bot_reset() + +/mob/living/simple_animal/bot/revive(full_heal = 0, admin_revive = 0) + if(..()) + update_icon() + . = 1 + +/mob/living/simple_animal/bot/ghost() + if(stat != DEAD) // Only ghost if we're doing this while alive, the pAI probably isn't dead yet. + ..() + if(paicard && (!client || stat == DEAD)) + ejectpai(0) + +/mob/living/simple_animal/bot/sentience_act() + faction -= "silicon" + +/mob/living/simple_animal/bot/verb/show_laws() + set name = "Show Directives" + set category = "IC" + + src << "Directives:" + if(paicard && paicard.pai && paicard.pai.master && paicard.pai.pai_law0) + src << "Your master, [paicard.pai.master], may overrule any and all laws." + src << "0. [paicard.pai.pai_law0]" + if(emagged >= 2) + src << "1. #$!@#$32K#$" + else + src << "1. You are a machine built to serve the station's crew and AI(s)." + src << "2. Your function is to [bot_purpose]." + src << "3. You cannot serve your function if you are broken." + src << "4. Serve your function to the best of your ability." + if(paicard && paicard.pai && paicard.pai.pai_laws) + src << "Supplemental Directive(s):" + src << "[paicard.pai.pai_laws]" + +/mob/living/simple_animal/bot/get_access() + . = ..() + + if(access_card) + . |= access_card.GetAccess() + +/mob/living/simple_animal/bot/proc/door_opened(obj/machinery/door/D) + frustration = 0 + +/mob/living/simple_animal/bot/handle_message_mode(var/message_mode, var/message, var/verb, var/speaking, var/used_radios) + switch(message_mode) + if("intercom") + for(var/obj/item/device/radio/intercom/I in view(1, src)) + spawn(0) + I.talk_into(src, message, null, verb, speaking) + used_radios += I + if("headset") + Radio.talk_into(src, message, null, verb, speaking) + used_radios += Radio + else + if(message_mode) + Radio.talk_into(src, message, message_mode, verb, speaking) + used_radios += Radio + +/mob/living/simple_animal/bot/handle_hud_icons_health() + ..() + if(bodytemp) + switch(bodytemperature) //310.055 optimal body temp + if(335 to INFINITY) + bodytemp.icon_state = "temp2" + if(320 to 335) + bodytemp.icon_state = "temp1" + if(300 to 320) + bodytemp.icon_state = "temp0" + if(260 to 300) + bodytemp.icon_state = "temp-1" + else + bodytemp.icon_state = "temp-2" \ No newline at end of file diff --git a/code/game/machinery/bots/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm similarity index 56% rename from code/game/machinery/bots/cleanbot.dm rename to code/modules/mob/living/simple_animal/bot/cleanbot.dm index 902ee0becde..a8bb4eee8f1 100644 --- a/code/game/machinery/bots/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -1,282 +1,227 @@ -//Cleanbot assembly -/obj/item/weapon/bucket_sensor - desc = "It's a bucket. With a sensor attached." - name = "proxy bucket" - icon = 'icons/obj/aibots.dmi' - icon_state = "bucket_proxy" - force = 3.0 - throwforce = 5.0 - throw_speed = 2 - throw_range = 5 - w_class = 3.0 - var/created_name = "Cleanbot" - - -//Cleanbot -/obj/machinery/bot/cleanbot - name = "\improper Cleanbot" - desc = "A little cleaning robot, he looks so excited!" - icon = 'icons/obj/aibots.dmi' - icon_state = "cleanbot0" - layer = 5.0 - density = 0 - anchored = 0 - //weight = 1.0E7 - health = 25 - maxhealth = 25 - var/blood = 1 - var/list/target_types = list() - var/obj/effect/decal/cleanable/target - var/obj/effect/decal/cleanable/oldtarget - var/max_targets = 50 //Maximum number of targets a cleanbot can ignore. - var/oldloc = null - req_one_access = list(access_janitor, access_robotics) -// var/patrol_path[] = null -// var/beacon_freq = 1445 // navigation beacon frequency - var/closest_dist - var/closest_loc - var/failed_steps - var/next_dest - var/next_dest_loc - radio_name = "Service" - bot_type = CLEAN_BOT - bot_type_name = "Cleanbot" - bot_filter = RADIO_CLEANBOT - -/obj/machinery/bot/cleanbot/New() - ..() - get_targets() - icon_state = "cleanbot[on]" - - var/datum/job/janitor/J = new/datum/job/janitor - botcard.access = J.get_access() - prev_access = botcard.access - - spawn(5) - add_to_beacons(bot_filter) - -/obj/machinery/bot/cleanbot/turn_on() - ..() - icon_state = "cleanbot[on]" - updateUsrDialog() - -/obj/machinery/bot/cleanbot/turn_off() - ..() - icon_state = "cleanbot[on]" - updateUsrDialog() - -/obj/machinery/bot/cleanbot/bot_reset() - ..() - ignore_list = list() //Allows the bot to clean targets it previously ignored due to being unreachable. - target = null - oldtarget = null - oldloc = null - -/obj/machinery/bot/cleanbot/set_custom_texts() - text_hack = "You corrupt [name]'s cleaning software." - text_dehack = "[name]'s software has been reset!" - text_dehack_fail = "[name] does not seem to respond to your repair code!" - -/obj/machinery/bot/cleanbot/attack_hand(mob/user as mob) - . = ..() - if (.) - return - usr.set_machine(src) - interact(user) - -/obj/machinery/bot/cleanbot/interact(mob/user as mob) - var/dat - dat += hack(user) - dat += text({" -Cleaner v1.1 controls

                -Status: []
                -Behaviour controls are [locked ? "locked" : "unlocked"]
                -Maintenance panel panel is [open ? "opened" : "closed"]"}, -text("[on ? "On" : "Off"]")) - if(!locked || issilicon(user)) - dat += text({"
                Cleans Blood: []
                "}, text("[blood ? "Yes" : "No"]")) - dat += text({"
                Patrol station: []
                "}, text("[auto_patrol ? "Yes" : "No"]")) - - var/datum/browser/popup = new(user, "autoclean", "Automatic Station Cleaner v1.1") - popup.set_content(dat) - popup.open() - return - -/obj/machinery/bot/cleanbot/Topic(href, href_list) - - ..() - switch(href_list["operation"]) - if("blood") - blood =!blood - get_targets() - updateUsrDialog() - if("freq") - var/freq = text2num(input("Select frequency for navigation beacons", "Frequency", num2text(beacon_freq / 10))) * 10 - if (freq > 0) - beacon_freq = freq - updateUsrDialog() - -/obj/machinery/bot/cleanbot/attackby(obj/item/weapon/W, mob/user as mob, params) - if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) - if(allowed(usr) && !open && !emagged) - locked = !locked - user << "You [ locked ? "lock" : "unlock"] the [src] behaviour controls." - updateUsrDialog() - else - if(emagged) - user << "ERROR" - if(open) - user << "Please close the access panel before locking it." - else - user << "This [src] doesn't seem to respect your authority." - else - return ..() - -/obj/machinery/bot/cleanbot/Emag(mob/user as mob) - ..() - if(emagged == 2) - if(user) - user << "[src] buzzes and beeps." - -/obj/machinery/bot/cleanbot/process_scan(var/obj/effect/decal/cleanable/D) - for(var/T in target_types) - if(istype(D, T)) - return D - -/obj/machinery/bot/cleanbot/bot_process() - if (!..()) - return - - if(mode == BOT_CLEANING) - return - - if(emagged == 2) //Emag functions - if(istype(loc,/turf/simulated)) - - if(prob(10)) //Wets floors randomly - var/turf/simulated/T = loc - T.MakeSlippery() - - if(prob(5)) //Spawns foam! - visible_message("[src] whirs and bubbles violently, before releasing a plume of froth!") - new /obj/effect/effect/foam(loc) - - else if (prob(5)) - visible_message("[src] makes an excited beeping booping sound!") - - if(!target) //Search for cleanables it can see. - target = scan(/obj/effect/decal/cleanable/, oldtarget) - oldtarget = target - - if(!target) - if(loc != oldloc) - oldtarget = null - - if(auto_patrol) - if(mode == BOT_IDLE || mode == BOT_START_PATROL) - start_patrol() - - if(mode == BOT_PATROL) - bot_patrol() - - return - - if(target) - if(!path || path.len == 0) //No path, need a new one - //Try to produce a path to the target, and ignore airlocks to which it has access. - path = get_path_to(loc, target.loc, src, /turf/proc/Distance_cardinal, 0, 30, id=botcard) - if (!bot_move(target)) - add_to_ignore(target) - target = null - path = list() - return - mode = BOT_MOVING - else if (!bot_move(target)) - target = null - mode = BOT_IDLE - return - - if(target && loc == target.loc) - clean(target) - path = list() - target = null - - oldloc = loc - -/obj/machinery/bot/cleanbot/proc/get_targets() - target_types = new/list() - - target_types += /obj/effect/decal/cleanable/blood/oil - target_types += /obj/effect/decal/cleanable/vomit - target_types += /obj/effect/decal/cleanable/poop - target_types += /obj/effect/decal/cleanable/blood/gibs/robot - target_types += /obj/effect/decal/cleanable/crayon - target_types += /obj/effect/decal/cleanable/liquid_fuel - target_types += /obj/effect/decal/cleanable/molten_item - target_types += /obj/effect/decal/cleanable/tomato_smudge - target_types += /obj/effect/decal/cleanable/egg_smudge - target_types += /obj/effect/decal/cleanable/pie_smudge - target_types += /obj/effect/decal/cleanable/flour - target_types += /obj/effect/decal/cleanable/ash - target_types += /obj/effect/decal/cleanable/greenglow - target_types += /obj/effect/decal/cleanable/dirt - target_types += /obj/effect/decal/cleanable/mucus - - if(blood) - target_types += /obj/effect/decal/cleanable/blood/xeno/ - target_types += /obj/effect/decal/cleanable/blood/gibs/xeno - target_types += /obj/effect/decal/cleanable/blood/ - target_types += /obj/effect/decal/cleanable/blood/gibs/ - target_types += /obj/effect/decal/cleanable/blood/tracks - target_types += /obj/effect/decal/cleanable/dirt - -/obj/machinery/bot/cleanbot/proc/clean(var/obj/effect/decal/cleanable/target) - anchored = 1 - icon_state = "cleanbot-c" - visible_message("[src] begins to clean up [target]") - mode = BOT_CLEANING - spawn(25) - if(mode == BOT_CLEANING) - qdel(target) - anchored = 0 - target = null - mode = BOT_IDLE - icon_state = "cleanbot[on]" - -/obj/machinery/bot/cleanbot/explode() - on = 0 - visible_message("[src] blows apart!") - var/turf/Tsec = get_turf(src) - - new /obj/item/weapon/reagent_containers/glass/bucket(Tsec) - - new /obj/item/device/assembly/prox_sensor(Tsec) - - if (prob(50)) - new /obj/item/robot_parts/l_arm(Tsec) - - var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - qdel(src) - return - -/obj/item/weapon/bucket_sensor/attackby(var/obj/item/W, mob/user as mob, params) - ..() - if(istype(W, /obj/item/robot_parts/l_arm) || istype(W, /obj/item/robot_parts/r_arm)) - user.drop_item() - qdel(W) - var/turf/T = get_turf(loc) - var/obj/machinery/bot/cleanbot/A = new /obj/machinery/bot/cleanbot(T) - A.name = created_name - user << "You add the robot arm to the bucket and sensor assembly. Beep boop!" - user.unEquip(src, 1) - qdel(src) - - else if (istype(W, /obj/item/weapon/pen)) - var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) - if (!t) - return - if (!in_range(src, usr) && loc != usr) - return - created_name = t +//Cleanbot +/mob/living/simple_animal/bot/cleanbot + name = "\improper Cleanbot" + desc = "A little cleaning robot, he looks so excited!" + icon = 'icons/obj/aibots.dmi' + icon_state = "cleanbot0" + density = 0 + anchored = 0 + health = 25 + maxHealth = 25 + radio_channel = "Service" //Service + bot_filter = RADIO_CLEANBOT + bot_type = CLEAN_BOT + model = "Cleanbot" + bot_purpose = "seek out messes and clean them" + bot_core_type = /obj/machinery/bot_core/cleanbot + window_id = "autoclean" + window_name = "Automatic Station Cleaner v1.1" + pass_flags = PASSMOB + + var/blood = 1 + var/list/target_types = list() + var/obj/effect/decal/cleanable/target + var/max_targets = 50 //Maximum number of targets a cleanbot can ignore. + var/oldloc = null + var/closest_dist + var/closest_loc + var/failed_steps + var/next_dest + var/next_dest_loc + +/mob/living/simple_animal/bot/cleanbot/New() + ..() + get_targets() + icon_state = "cleanbot[on]" + + var/datum/job/janitor/J = new/datum/job/janitor + access_card.access += J.get_access() + prev_access = access_card.access + +/mob/living/simple_animal/bot/cleanbot/turn_on() + ..() + icon_state = "cleanbot[on]" + +/mob/living/simple_animal/bot/cleanbot/turn_off() + ..() + icon_state = "cleanbot[on]" + +/mob/living/simple_animal/bot/cleanbot/bot_reset() + ..() + ignore_list = list() //Allows the bot to clean targets it previously ignored due to being unreachable. + target = null + oldloc = null + +/mob/living/simple_animal/bot/cleanbot/set_custom_texts() + text_hack = "You corrupt [name]'s cleaning software." + text_dehack = "[name]'s software has been reset!" + text_dehack_fail = "[name] does not seem to respond to your repair code!" + +/mob/living/simple_animal/bot/cleanbot/attackby(obj/item/weapon/W, mob/user, params) + if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) + if(bot_core.allowed(user) && !open && !emagged) + locked = !locked + user << "You [ locked ? "lock" : "unlock"] \the [src] behaviour controls." + else + if(emagged) + user << "ERROR" + if(open) + user << "Please close the access panel before locking it." + else + user << "\The [src] doesn't seem to respect your authority." + else + return ..() + +/mob/living/simple_animal/bot/cleanbot/emag_act(mob/user) + ..() + if(emagged == 2) + if(user) + user << "[src] buzzes and beeps." + +/mob/living/simple_animal/bot/cleanbot/process_scan(obj/effect/decal/cleanable/D) + for(var/T in target_types) + if(istype(D, T)) + return D + +/mob/living/simple_animal/bot/cleanbot/handle_automated_action() + if(!..()) + return + + if(mode == BOT_CLEANING) + return + + if(emagged == 2) //Emag functions + if(istype(loc,/turf/simulated)) + if(prob(10)) //Wets floors randomly + var/turf/simulated/T = loc + T.MakeSlippery() + + if(prob(5)) //Spawns foam! + visible_message("[src] whirs and bubbles violently, before releasing a plume of froth!") + new /obj/effect/effect/foam(loc) + + else if(prob(5)) + audible_message("[src] makes an excited beeping booping sound!") + + if(!target) //Search for cleanables it can see. + target = scan(/obj/effect/decal/cleanable/) + + if(!target && auto_patrol) //Search for cleanables it can see. + if(mode == BOT_IDLE || mode == BOT_START_PATROL) + start_patrol() + + if(mode == BOT_PATROL) + bot_patrol() + + if(target) + if(!path || path.len == 0) //No path, need a new one + //Try to produce a path to the target, and ignore airlocks to which it has access. + path = get_path_to(src, target.loc, /turf/proc/Distance_cardinal, 0, 30, id=access_card) + if(!bot_move(target)) + add_to_ignore(target) + target = null + path = list() + return + mode = BOT_MOVING + else if(!bot_move(target)) + target = null + mode = BOT_IDLE + return + + if(target && loc == target.loc) + clean(target) + path = list() + target = null + + oldloc = loc + +/mob/living/simple_animal/bot/cleanbot/proc/get_targets() + target_types = new/list() + + target_types += /obj/effect/decal/cleanable/blood/oil + target_types += /obj/effect/decal/cleanable/vomit + target_types += /obj/effect/decal/cleanable/poop + target_types += /obj/effect/decal/cleanable/blood/gibs/robot + target_types += /obj/effect/decal/cleanable/crayon + target_types += /obj/effect/decal/cleanable/liquid_fuel + target_types += /obj/effect/decal/cleanable/molten_item + target_types += /obj/effect/decal/cleanable/tomato_smudge + target_types += /obj/effect/decal/cleanable/egg_smudge + target_types += /obj/effect/decal/cleanable/pie_smudge + target_types += /obj/effect/decal/cleanable/flour + target_types += /obj/effect/decal/cleanable/ash + target_types += /obj/effect/decal/cleanable/greenglow + target_types += /obj/effect/decal/cleanable/dirt + target_types += /obj/effect/decal/cleanable/mucus + + if(blood) + target_types += /obj/effect/decal/cleanable/blood/xeno/ + target_types += /obj/effect/decal/cleanable/blood/gibs/xeno + target_types += /obj/effect/decal/cleanable/blood/ + target_types += /obj/effect/decal/cleanable/blood/gibs/ + target_types += /obj/effect/decal/cleanable/blood/tracks + target_types += /obj/effect/decal/cleanable/dirt + +/mob/living/simple_animal/bot/cleanbot/proc/clean(obj/effect/decal/cleanable/target) + anchored = 1 + icon_state = "cleanbot-c" + visible_message("[src] begins to clean up [target]") + mode = BOT_CLEANING + spawn(50) + if(mode == BOT_CLEANING) + qdel(target) + anchored = 0 + target = null + mode = BOT_IDLE + icon_state = "cleanbot[on]" + +/mob/living/simple_animal/bot/cleanbot/explode() + on = 0 + visible_message("[src] blows apart!") + var/turf/Tsec = get_turf(src) + + new /obj/item/weapon/reagent_containers/glass/bucket(Tsec) + + new /obj/item/device/assembly/prox_sensor(Tsec) + + if(prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + ..() + +/obj/machinery/bot_core/cleanbot + req_one_access = list(access_janitor, access_robotics) + + +/mob/living/simple_animal/bot/cleanbot/get_controls(mob/user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += text({" +Cleaner v1.1 controls

                +Status: []
                +Behaviour controls are [locked ? "locked" : "unlocked"]
                +Maintenance panel panel is [open ? "opened" : "closed"]"}, +text("[on ? "On" : "Off"]")) + if(!locked || issilicon(user) || check_rights(R_ADMIN, 0, user)) + dat += text({"
                Cleans Blood: []
                "}, text("[blood ? "Yes" : "No"]")) + dat += text({"
                Patrol station: []
                "}, text("[auto_patrol ? "Yes" : "No"]")) + return dat + +/mob/living/simple_animal/bot/cleanbot/Topic(href, href_list) + if(..()) + return 1 + switch(href_list["operation"]) + if("blood") + blood =!blood + get_targets() + update_controls() + +/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A) + if(istype(A,/obj/effect/decal/cleanable)) + clean(A) + else + ..() diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm new file mode 100644 index 00000000000..e5bbdcf1852 --- /dev/null +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -0,0 +1,480 @@ +//Bot Construction + +//Cleanbot assembly +/obj/item/weapon/bucket_sensor + desc = "It's a bucket. With a sensor attached." + name = "proxy bucket" + icon = 'icons/obj/aibots.dmi' + icon_state = "bucket_proxy" + force = 3 + throwforce = 5 + throw_speed = 2 + throw_range = 5 + w_class = 3 + var/created_name = "Cleanbot" + +/obj/item/weapon/bucket_sensor/attackby(obj/item/W, mob/user as mob, params) + ..() + if(istype(W, /obj/item/robot_parts/l_arm) || istype(W, /obj/item/robot_parts/r_arm)) + if(!user.unEquip(W)) + return + qdel(W) + var/turf/T = get_turf(loc) + var/mob/living/simple_animal/bot/cleanbot/A = new /mob/living/simple_animal/bot/cleanbot(T) + A.name = created_name + user << "You add the robot arm to the bucket and sensor assembly. Beep boop!" + user.unEquip(src, 1) + qdel(src) + + else if(istype(W, /obj/item/weapon/pen)) + var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + created_name = t + +//Edbot Assembly + +/obj/item/weapon/ed209_assembly + name = "\improper ED-209 assembly" + desc = "Some sort of bizarre assembly." + icon = 'icons/obj/aibots.dmi' + icon_state = "ed209_frame" + item_state = "ed209_frame" + var/build_step = 0 + var/created_name = "ED-209 Security Robot" //To preserve the name if it's a unique securitron I guess + var/lasercolor = "" + +/obj/item/weapon/ed209_assembly/attackby(obj/item/weapon/W, mob/user, params) + ..() + + if(istype(W, /obj/item/weapon/pen)) + var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + created_name = t + return + + switch(build_step) + if(0,1) + if(istype(W, /obj/item/robot_parts/l_leg) || istype(W, /obj/item/robot_parts/r_leg)) + if(!user.unEquip(W)) + return + qdel(W) + build_step++ + user << "You add the robot leg to [src]." + name = "legs/frame assembly" + if(build_step == 1) + item_state = "ed209_leg" + icon_state = "ed209_leg" + else + item_state = "ed209_legs" + icon_state = "ed209_legs" + + if(2) + var/newcolor = "" + if(istype(W, /obj/item/clothing/suit/redtag)) + newcolor = "r" + else if(istype(W, /obj/item/clothing/suit/bluetag)) + newcolor = "b" + if(newcolor || istype(W, /obj/item/clothing/suit/armor/vest)) + if(!user.unEquip(W)) + return + lasercolor = newcolor + qdel(W) + build_step++ + user << "You add the armor to [src]." + name = "vest/legs/frame assembly" + item_state = "[lasercolor]ed209_shell" + icon_state = "[lasercolor]ed209_shell" + + if(3) + if(istype(W, /obj/item/weapon/weldingtool)) + var/obj/item/weapon/weldingtool/WT = W + if(WT.remove_fuel(0,user)) + build_step++ + name = "shielded frame assembly" + user << "You weld the vest to [src]." + if(4) + switch(lasercolor) + if("b") + if(!istype(W, /obj/item/clothing/head/helmet/bluetaghelm)) + return + + if("r") + if(!istype(W, /obj/item/clothing/head/helmet/redtaghelm)) + return + + if("") + if(!istype(W, /obj/item/clothing/head/helmet)) + return + + if(!user.unEquip(W)) + return + qdel(W) + build_step++ + user << "You add the helmet to [src]." + name = "covered and shielded frame assembly" + item_state = "[lasercolor]ed209_hat" + icon_state = "[lasercolor]ed209_hat" + + if(5) + if(isprox(W)) + if(!user.unEquip(W)) + return + qdel(W) + build_step++ + user << "You add the prox sensor to [src]." + name = "covered, shielded and sensored frame assembly" + item_state = "[lasercolor]ed209_prox" + icon_state = "[lasercolor]ed209_prox" + + if(6) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil = W + if(coil.get_amount() < 1) + user << "You need one length of cable to wire the ED-209!" + return + user << "You start to wire [src]..." + if(do_after(user, 40, target = src)) + if(coil.get_amount() >= 1 && build_step == 6) + coil.use(1) + build_step = 7 + user << "You wire the ED-209 assembly." + name = "wired ED-209 assembly" + + if(7) + var/newname = "" + switch(lasercolor) + if("b") + if(!istype(W, /obj/item/weapon/gun/energy/laser/bluetag)) + return + newname = "bluetag ED-209 assembly" + if("r") + if(!istype(W, /obj/item/weapon/gun/energy/laser/redtag)) + return + newname = "redtag ED-209 assembly" + if("") + if(!istype(W, /obj/item/weapon/gun/energy/advtaser)) + return + newname = "taser ED-209 assembly" + else + return + if(!user.unEquip(W)) + return + name = newname + build_step++ + user << "You add [W] to [src]." + item_state = "[lasercolor]ed209_taser" + icon_state = "[lasercolor]ed209_taser" + qdel(W) + + if(8) + if(istype(W, /obj/item/weapon/screwdriver)) + playsound(loc, 'sound/items/Screwdriver.ogg', 100, 1) + user << "You start attaching the gun to the frame..." + if(do_after(user, 40, target = src)) + build_step++ + name = "armed [name]" + user << "Taser gun attached." + + if(9) + if(istype(W, /obj/item/weapon/stock_parts/cell)) + if(!user.unEquip(W)) + return + build_step++ + user << "You complete the ED-209." + var/turf/T = get_turf(src) + new /mob/living/simple_animal/bot/ed209(T,created_name,lasercolor) + qdel(W) + user.unEquip(src, 1) + qdel(src) + +//Floorbot assemblies +/obj/item/weapon/toolbox_tiles + desc = "It's a toolbox with tiles sticking out the top" + name = "tiles and toolbox" + icon = 'icons/obj/aibots.dmi' + icon_state = "toolbox_tiles" + force = 3 + throwforce = 10 + throw_speed = 2 + throw_range = 5 + w_class = 3 + var/created_name = "Floorbot" + +/obj/item/weapon/toolbox_tiles_sensor + desc = "It's a toolbox with tiles sticking out the top and a sensor attached" + name = "tiles, toolbox and sensor arrangement" + icon = 'icons/obj/aibots.dmi' + icon_state = "toolbox_tiles_sensor" + force = 3 + throwforce = 10 + throw_speed = 2 + throw_range = 5 + w_class = 3 + var/created_name = "Floorbot" + +/obj/item/weapon/storage/toolbox/mechanical/attackby(obj/item/stack/tile/plasteel/T, mob/user, params) + if(!istype(T, /obj/item/stack/tile/plasteel)) + ..() + return + if(contents.len >= 1) + user << "They won't fit in, as there is already stuff inside." + return + if(T.use(10)) + if(user.s_active) + user.s_active.close(user) + var/obj/item/weapon/toolbox_tiles/B = new /obj/item/weapon/toolbox_tiles + user.put_in_hands(B) + user << "You add the tiles into the empty toolbox. They protrude from the top." + user.unEquip(src, 1) + qdel(src) + else + user << "You need 10 floor tiles to start building a floorbot." + return + +/obj/item/weapon/toolbox_tiles/attackby(obj/item/W, mob/user, params) + ..() + if(isprox(W)) + qdel(W) + var/obj/item/weapon/toolbox_tiles_sensor/B = new /obj/item/weapon/toolbox_tiles_sensor() + B.created_name = created_name + user.put_in_hands(B) + user << "You add the sensor to the toolbox and tiles." + user.unEquip(src, 1) + qdel(src) + + else if(istype(W, /obj/item/weapon/pen)) + var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + + created_name = t + +/obj/item/weapon/toolbox_tiles_sensor/attackby(obj/item/W, mob/user, params) + ..() + if(istype(W, /obj/item/robot_parts/l_arm) || istype(W, /obj/item/robot_parts/r_arm)) + qdel(W) + var/turf/T = get_turf(user.loc) + var/mob/living/simple_animal/bot/floorbot/A = new /mob/living/simple_animal/bot/floorbot(T) + A.name = created_name + user << "You add the robot arm to the odd looking toolbox assembly. Boop beep!" + user.unEquip(src, 1) + qdel(src) + else if(istype(W, /obj/item/weapon/pen)) + var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + + created_name = t + +//Medbot Assembly +/obj/item/weapon/firstaid_arm_assembly + name = "incomplete medibot assembly." + desc = "A first aid kit with a robot arm permanently grafted to it." + icon = 'icons/obj/aibots.dmi' + icon_state = "firstaid_arm" + var/build_step = 0 + var/created_name = "Medibot" //To preserve the name if it's a unique medbot I guess + var/skin = null //Same as medbot, set to tox or ointment for the respective kits. + w_class = 3 + var/treatment_brute = "salglu_solution" + var/treatment_oxy = "salbutamol" + var/treatment_fire = "salglu_solution" + var/treatment_tox = "charcoal" + var/treatment_virus = "spaceacillin" + req_one_access = list(access_medical, access_robotics) + + /obj/item/weapon/firstaid_arm_assembly/New() + ..() + spawn(5) + if(skin) + overlays += image('icons/obj/aibots.dmi', "kit_skin_[skin]") + +/obj/item/weapon/storage/firstaid/attackby(obj/item/robot_parts/S, mob/user, params) + + if((!istype(S, /obj/item/robot_parts/l_arm)) && (!istype(S, /obj/item/robot_parts/r_arm))) + ..() + return + + //Making a medibot! + if(contents.len >= 1) + user << "You need to empty [src] out first!" + return + + var/obj/item/weapon/firstaid_arm_assembly/A = new /obj/item/weapon/firstaid_arm_assembly + if(istype(src,/obj/item/weapon/storage/firstaid/fire)) + A.skin = "ointment" + else if(istype(src,/obj/item/weapon/storage/firstaid/toxin)) + A.skin = "tox" + else if(istype(src,/obj/item/weapon/storage/firstaid/o2)) + A.skin = "o2" + else if(istype(src,/obj/item/weapon/storage/firstaid/brute)) + A.skin = "brute" + else if(istype(src,/obj/item/weapon/storage/firstaid/adv)) + A.skin = "adv" + else if(istype(src,/obj/item/weapon/storage/firstaid/tactical)) + A.skin = "bezerk" + else if(istype(src,/obj/item/weapon/storage/firstaid/aquatic_kit)) + A.skin = "fish" + + A.req_one_access = req_one_access + A.treatment_oxy = treatment_oxy + A.treatment_brute = treatment_brute + A.treatment_fire = treatment_fire + A.treatment_tox = treatment_tox + A.treatment_virus = treatment_virus + + qdel(S) + user.put_in_hands(A) + user << "You add the robot arm to the first aid kit." + user.unEquip(src, 1) + qdel(src) + + +/obj/item/weapon/firstaid_arm_assembly/attackby(obj/item/weapon/W, mob/user, params) + ..() + if(istype(W, /obj/item/weapon/pen)) + var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + created_name = t + else + switch(build_step) + if(0) + if(istype(W, /obj/item/device/healthanalyzer)) + if(!user.unEquip(W)) + return + qdel(W) + build_step++ + user << "You add the health sensor to [src]." + name = "First aid/robot arm/health analyzer assembly" + overlays += image('icons/obj/aibots.dmi', "na_scanner") + + if(1) + if(isprox(W)) + if(!user.unEquip(W)) + return + qdel(W) + build_step++ + user << "You complete the Medibot. Beep boop!" + var/turf/T = get_turf(src) + var/mob/living/simple_animal/bot/medbot/S = new /mob/living/simple_animal/bot/medbot(T) + S.skin = skin + S.name = created_name + S.bot_core.req_one_access = req_one_access + S.treatment_oxy = treatment_oxy + S.treatment_brute = treatment_brute + S.treatment_fire = treatment_fire + S.treatment_tox = treatment_tox + S.treatment_virus = treatment_virus + user.unEquip(src, 1) + qdel(src) + +//Secbot Assembly +/obj/item/weapon/secbot_assembly + name = "incomplete securitron assembly" + desc = "Some sort of bizarre assembly made from a proximity sensor, helmet, and signaler." + icon = 'icons/obj/aibots.dmi' + icon_state = "helmet_signaler" + item_state = "helmet" + var/build_step = 0 + var/created_name = "Securitron" //To preserve the name if it's a unique securitron I guess + +/obj/item/clothing/head/helmet/attackby(obj/item/device/assembly/signaler/S, mob/user, params) + ..() + if(!issignaler(S)) + ..() + return + + if(!S.secured) + qdel(S) + var/obj/item/weapon/secbot_assembly/A = new /obj/item/weapon/secbot_assembly + user.put_in_hands(A) + user << "You add the signaler to the helmet." + user.unEquip(src, 1) + qdel(src) + else + return + +/obj/item/weapon/secbot_assembly/attackby(obj/item/I, mob/user, params) + ..() + if(istype(I, /obj/item/weapon/weldingtool)) + if(!build_step) + var/obj/item/weapon/weldingtool/WT = I + if(WT.remove_fuel(0, user)) + build_step++ + overlays += "hs_hole" + user << "You weld a hole in [src]!" + else if(build_step == 1) + var/obj/item/weapon/weldingtool/WT = I + if(WT.remove_fuel(0, user)) + build_step-- + overlays -= "hs_hole" + user << "You weld the hole in [src] shut!" + + else if(isprox(I) && (build_step == 1)) + if(!user.unEquip(I)) + return + build_step++ + user << "You add the prox sensor to [src]!" + overlays += "hs_eye" + name = "helmet/signaler/prox sensor assembly" + qdel(I) + + else if(((istype(I, /obj/item/robot_parts/l_arm)) || (istype(I, /obj/item/robot_parts/r_arm))) && (build_step == 2)) + if(!user.unEquip(I)) + return + build_step++ + user << "You add the robot arm to [src]!" + name = "helmet/signaler/prox sensor/robot arm assembly" + overlays += "hs_arm" + qdel(I) + + else if((istype(I, /obj/item/weapon/melee/baton)) && (build_step >= 3)) + if(!user.unEquip(I)) + return + build_step++ + user << "You complete the Securitron! Beep boop." + var/mob/living/simple_animal/bot/secbot/S = new /mob/living/simple_animal/bot/secbot + S.forceMove(get_turf(src)) + S.name = created_name + qdel(I) + qdel(src) + + else if(istype(I, /obj/item/weapon/pen)) + var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + created_name = t + + else if(istype(I, /obj/item/weapon/screwdriver)) + if(!build_step) + new /obj/item/device/assembly/signaler(get_turf(src)) + new /obj/item/clothing/head/helmet(get_turf(src)) + user << "You disconnect the signaler from the helmet." + qdel(src) + + else if(build_step == 2) + overlays -= "hs_eye" + new /obj/item/device/assembly/prox_sensor(get_turf(src)) + user << "You detach the proximity sensor from [src]." + build_step-- + + else if(build_step == 3) + overlays -= "hs_arm" + new /obj/item/robot_parts/l_arm(get_turf(src)) + user << "You remove the robot arm from [src]." + build_step-- \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm new file mode 100644 index 00000000000..1e1117f110a --- /dev/null +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -0,0 +1,569 @@ +/mob/living/simple_animal/bot/ed209 + name = "\improper ED-209 Security Robot" + desc = "A security robot. He looks less than thrilled." + icon = 'icons/obj/aibots.dmi' + icon_state = "ed2090" + density = 1 + anchored = 0 + health = 100 + maxHealth = 100 + damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + + environment_smash = 2 //Walls can't stop THE LAW + + radio_channel = "Security" + bot_type = SEC_BOT + bot_filter = RADIO_SECBOT + model = "ED-209" + bot_purpose = "seek out criminals, handcuff them, and report their location to security" + bot_core = /obj/machinery/bot_core/secbot + window_id = "autoed209" + window_name = "Automatic Security Unit v2.6" + + var/lastfired = 0 + var/shot_delay = 3 //.3 seconds between shots + var/lasercolor = "" + var/disabled = 0//A holder for if it needs to be disabled, if true it will not seach for targets, shoot at targets, or move, currently only used for lasertag + + + var/mob/living/carbon/target + var/oldtarget_name + var/threatlevel = 0 + var/target_lastloc //Loc of target when arrested. + var/last_found //There's a delay + var/declare_arrests = 1 //When making an arrest, should it notify everyone wearing sechuds? + var/idcheck = 1 //If true, arrest people with no IDs + var/weaponscheck = 1 //If true, arrest people for weapons if they don't have access + var/check_records = 1 //Does it check security records? + var/arrest_type = 0 //If true, don't handcuff + var/projectile = /obj/item/projectile/energy/electrode //Holder for projectile type + var/shoot_sound = 'sound/weapons/Taser.ogg' + + +/mob/living/simple_animal/bot/ed209/New(loc,created_name,created_lasercolor) + ..() + if(created_name) + name = created_name + if(created_lasercolor) + lasercolor = created_lasercolor + icon_state = "[lasercolor]ed209[on]" + set_weapon() //giving it the right projectile and firing sound. + spawn(3) + var/datum/job/detective/J = new/datum/job/detective + access_card.access += J.get_access() + prev_access = access_card.access + + if(lasercolor) + shot_delay = 6//Longer shot delay because JESUS CHRIST + check_records = 0//Don't actively target people set to arrest + arrest_type = 1//Don't even try to cuff + bot_core.req_access = list(access_maint_tunnels, access_theatre) + arrest_type = 1 + if((lasercolor == "b") && (name == "\improper ED-209 Security Robot"))//Picks a name if there isn't already a custome one + name = pick("BLUE BALLER","SANIC","BLUE KILLDEATH MURDERBOT") + if((lasercolor == "r") && (name == "\improper ED-209 Security Robot")) + name = pick("RED RAMPAGE","RED ROVER","RED KILLDEATH MURDERBOT") + + //SECHUD + var/datum/atom_hud/secsensor = huds[DATA_HUD_SECURITY_ADVANCED] + secsensor.add_hud_to(src) + permanent_huds |= secsensor + +/mob/living/simple_animal/bot/ed209/turn_on() + . = ..() + icon_state = "[lasercolor]ed209[on]" + mode = BOT_IDLE + +/mob/living/simple_animal/bot/ed209/turn_off() + ..() + icon_state = "[lasercolor]ed209[on]" + +/mob/living/simple_animal/bot/ed209/bot_reset() + ..() + target = null + oldtarget_name = null + anchored = 0 + walk_to(src,0) + last_found = world.time + set_weapon() + +/mob/living/simple_animal/bot/ed209/set_custom_texts() + text_hack = "You disable [name]'s combat inhibitor." + text_dehack = "You restore [name]'s combat inhibitor." + text_dehack_fail = "[name] ignores your attempts to restrict him!" + +/mob/living/simple_animal/bot/ed209/get_controls(mob/user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += text({" +Security Unit v2.6 controls

                +Status: []
                +Behaviour controls are [locked ? "locked" : "unlocked"]
                +Maintenance panel panel is [open ? "opened" : "closed"]
                "}, + +"[on ? "On" : "Off"]" ) + + if(!locked || issilicon(user) || check_rights(R_ADMIN, 0, user)) + if(!lasercolor) + dat += text({"
                +Arrest Unidentifiable Persons: []
                +Arrest for Unauthorized Weapons: []
                +Arrest for Warrant: []
                +
                +Operating Mode: []
                +Report Arrests[]
                +Auto Patrol[]"}, + +"[idcheck ? "Yes" : "No"]", +"[weaponscheck ? "Yes" : "No"]", +"[check_records ? "Yes" : "No"]", +"[arrest_type ? "Detain" : "Arrest"]", +"[declare_arrests ? "Yes" : "No"]", +"[auto_patrol ? "On" : "Off"]" ) + + return dat + +/mob/living/simple_animal/bot/ed209/Topic(href, href_list) + if(lasercolor && (istype(usr,/mob/living/carbon/human))) + var/mob/living/carbon/human/H = usr + if((lasercolor == "b") && (istype(H.wear_suit, /obj/item/clothing/suit/redtag)))//Opposing team cannot operate it + return + else if((lasercolor == "r") && (istype(H.wear_suit, /obj/item/clothing/suit/bluetag))) + return + if(..()) + return 1 + + switch(href_list["operation"]) + if("idcheck") + idcheck = !idcheck + update_controls() + if("weaponscheck") + weaponscheck = !weaponscheck + update_controls() + if("ignorerec") + check_records = !check_records + update_controls() + if("switchmode") + arrest_type = !arrest_type + update_controls() + if("declarearrests") + declare_arrests = !declare_arrests + update_controls() + +/mob/living/simple_animal/bot/ed209/proc/retaliate(mob/living/carbon/human/H) + threatlevel = H.assess_threat(src) + threatlevel += 6 + if(threatlevel >= 4) + target = H + mode = BOT_HUNT + +/mob/living/simple_animal/bot/ed209/attack_hand(mob/living/carbon/human/H) + if(H.a_intent == "harm") + retaliate(H) + return ..() + +/mob/living/simple_animal/bot/ed209/attackby(obj/item/weapon/W, mob/user, params) + ..() + if(istype(W, /obj/item/weapon/weldingtool) && user.a_intent != "harm") // Any intent but harm will heal, so we shouldn't get angry. + return + if(!istype(W, /obj/item/weapon/screwdriver) && (!target)) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. + if(W.force && W.damtype != STAMINA)//If force is non-zero and damage type isn't stamina. + retaliate(user) + if(lasercolor)//To make up for the fact that lasertag bots don't hunt + shootAt(user) + +/mob/living/simple_animal/bot/ed209/emag_act(mob/user) + ..() + if(emagged == 2) + if(user) + user << "You short out [src]'s target assessment circuits." + oldtarget_name = user.name + audible_message("[src] buzzes oddly!") + declare_arrests = 0 + icon_state = "[lasercolor]ed209[on]" + set_weapon() + +/mob/living/simple_animal/bot/ed209/bullet_act(obj/item/projectile/Proj) + if(istype(Proj ,/obj/item/projectile/beam)||istype(Proj,/obj/item/projectile/bullet)) + if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) + if(!Proj.nodamage && Proj.damage < src.health) + retaliate(Proj.firer) + ..() + +/mob/living/simple_animal/bot/ed209/handle_automated_action() + if(!..()) + return + + if(disabled) + return + + var/list/targets = list() + for(var/mob/living/carbon/C in view(7, src)) //Let's find us a target + var/threatlevel = 0 + if((C.stat) || (C.lying)) + continue + threatlevel = C.assess_threat(src, lasercolor) + //speak(C.real_name + text(": threat: []", threatlevel)) + if(threatlevel < 4) + continue + + var/dst = get_dist(src, C) + if(dst <= 1 || dst > 7) + continue + + targets += C + if(targets.len>0) + var/mob/living/carbon/t = pick(targets) + if((t.stat!=2) && (t.lying != 1) && (!t.handcuffed)) //we don't shoot people who are dead, cuffed or lying down. + shootAt(t) + switch(mode) + + if(BOT_IDLE) // idle + walk_to(src,0) + if(!lasercolor) //lasertag bots don't want to arrest anyone + look_for_perp() // see if any criminals are in range + if(!mode && auto_patrol) // still idle, and set to patrol + mode = BOT_START_PATROL // switch to patrol mode + + if(BOT_HUNT) // hunting for perp + // if can't reach perp for long enough, go idle + if(frustration >= 8) + walk_to(src,0) + back_to_idle() + + if(target) // make sure target exists + if(Adjacent(target) && isturf(target.loc)) // if right next to perp + stun_attack(target) + + mode = BOT_PREP_ARREST + anchored = 1 + target_lastloc = target.loc + return + + else // not next to perp + var/turf/olddist = get_dist(src, target) + walk_to(src, target,1,4) + if((get_dist(src, target)) >= (olddist)) + frustration++ + else + frustration = 0 + else + back_to_idle() + + if(BOT_PREP_ARREST) // preparing to arrest target + + // see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again. + if(!Adjacent(target) || !isturf(target.loc) || target.weakened < 2) + back_to_hunt() + return + + if(iscarbon(target) && target.canBeHandcuffed()) + if(!arrest_type) + if(!target.handcuffed) //he's not cuffed? Try to cuff him! + cuff(target) + else + back_to_idle() + return + else + back_to_idle() + return + + if(BOT_ARREST) + if(!target) + anchored = 0 + mode = BOT_IDLE + last_found = world.time + frustration = 0 + return + + if(target.handcuffed) //no target or target cuffed? back to idle. + back_to_idle() + return + + if(!Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && target.weakened < 2)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. + back_to_hunt() + return + else + mode = BOT_PREP_ARREST + anchored = 0 + + if(BOT_START_PATROL) + look_for_perp() + start_patrol() + + if(BOT_PATROL) + look_for_perp() + bot_patrol() + + + return + +/mob/living/simple_animal/bot/ed209/proc/back_to_idle() + anchored = 0 + mode = BOT_IDLE + target = null + last_found = world.time + frustration = 0 + spawn(0) + handle_automated_action() //ensure bot quickly responds + +/mob/living/simple_animal/bot/ed209/proc/back_to_hunt() + anchored = 0 + frustration = 0 + mode = BOT_HUNT + spawn(0) + handle_automated_action() //ensure bot quickly responds + +// look for a criminal in view of the bot + +/mob/living/simple_animal/bot/ed209/proc/look_for_perp() + if(disabled) + return + anchored = 0 + threatlevel = 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 == oldtarget_name) && (world.time < last_found + 100)) + continue + + threatlevel = C.assess_threat(src, lasercolor) + + if(!threatlevel) + continue + + else if(threatlevel >= 4) + target = C + oldtarget_name = C.name + speak("Level [threatlevel] infraction alert!") + playsound(loc, pick('sound/voice/ed209_20sec.ogg', 'sound/voice/EDPlaceholder.ogg'), 50, 0) + visible_message("[src] points at [C.name]!") + mode = BOT_HUNT + spawn(0) + handle_automated_action() // ensure bot quickly responds to a perp + break + else + continue + +/mob/living/simple_animal/bot/ed209/proc/check_for_weapons(var/obj/item/slot_item) + if(slot_item && slot_item.needs_permit) + return 1 + return 0 + +/mob/living/simple_animal/bot/ed209/explode() + walk_to(src,0) + visible_message("[src] blows apart!") + 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('icons/obj/aibots.dmi', "hs_hole") + Sa.created_name = name + new /obj/item/device/assembly/prox_sensor(Tsec) + + if(!lasercolor) + var/obj/item/weapon/gun/energy/advtaser/G = new /obj/item/weapon/gun/energy/advtaser(Tsec) + G.power_supply.charge = 0 + G.update_icon() + else if(lasercolor == "b") + var/obj/item/weapon/gun/energy/laser/bluetag/G = new /obj/item/weapon/gun/energy/laser/bluetag(Tsec) + G.power_supply.charge = 0 + G.update_icon() + else if(lasercolor == "r") + var/obj/item/weapon/gun/energy/laser/redtag/G = new /obj/item/weapon/gun/energy/laser/redtag(Tsec) + G.power_supply.charge = 0 + G.update_icon() + + if(prob(50)) + new /obj/item/robot_parts/l_leg(Tsec) + if(prob(25)) + new /obj/item/robot_parts/r_leg(Tsec) + if(prob(25))//50% chance for a helmet OR vest + if(prob(50)) + new /obj/item/clothing/head/helmet(Tsec) + else + if(!lasercolor) + new /obj/item/clothing/suit/armor/vest(Tsec) + if(lasercolor == "b") + new /obj/item/clothing/suit/bluetag(Tsec) + if(lasercolor == "r") + new /obj/item/clothing/suit/redtag(Tsec) + + var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + + new /obj/effect/decal/cleanable/blood/oil(loc) + ..() + +/mob/living/simple_animal/bot/ed209/proc/set_weapon() //used to update the projectile type and firing sound + shoot_sound = 'sound/weapons/laser.ogg' + if(emagged == 2) + if(lasercolor) + projectile = /obj/item/projectile/lasertag + else + projectile = /obj/item/projectile/beam + else + if(!lasercolor) + shoot_sound = 'sound/weapons/Taser.ogg' + projectile = /obj/item/projectile/energy/electrode + else if(lasercolor == "b") + projectile = /obj/item/projectile/lasertag/blue + else if(lasercolor == "r") + projectile = /obj/item/projectile/lasertag/red + +/mob/living/simple_animal/bot/ed209/proc/shootAt(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(!projectile) + return + + if(!(istype(U, /turf))) + return + var/obj/item/projectile/A = new projectile(loc) + playsound(loc, shoot_sound, 50, 1) + A.current = U + A.yo = U.y - T.y + A.xo = U.x - T.x + spawn(0) + A.process() + +/mob/living/simple_animal/bot/ed209/attack_alien(mob/living/carbon/alien/user) + ..() + if(!isalien(target)) + target = user + mode = BOT_HUNT + + +/mob/living/simple_animal/bot/ed209/emp_act(severity) + + if(severity==2 && prob(70)) + ..(severity-1) + else + var/obj/effect/overlay/pulse2 = new/obj/effect/overlay ( loc ) + pulse2.icon = 'icons/effects/effects.dmi' + pulse2.icon_state = "empdisable" + pulse2.name = "emp sparks" + pulse2.anchored = 1 + pulse2.dir = pick(cardinal) + spawn(10) + qdel(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(targets.len) + if(prob(50)) + var/mob/toshoot = pick(targets) + if(toshoot) + targets-=toshoot + if(prob(50) && emagged < 2) + emagged = 2 + set_weapon() + shootAt(toshoot) + emagged = 0 + set_weapon() + else + shootAt(toshoot) + else if(prob(50)) + if(targets.len) + var/mob/toarrest = pick(targets) + if(toarrest) + target = toarrest + mode = BOT_HUNT + + +/mob/living/simple_animal/bot/ed209/bullet_act(obj/item/projectile/Proj) + if(!disabled) + var/lasertag_check = 0 + if((lasercolor == "b")) + if(istype(Proj, /obj/item/projectile/lasertag/red)) + lasertag_check++ + else if((lasercolor == "r")) + if(istype(Proj, /obj/item/projectile/lasertag/blue)) + lasertag_check++ + if(lasertag_check) + icon_state = "[lasercolor]ed2090" + disabled = 1 + target = null + spawn(100) + disabled = 0 + icon_state = "[lasercolor]ed2091" + return 1 + else + ..(Proj) + else + ..(Proj) + +/mob/living/simple_animal/bot/ed209/bluetag + lasercolor = "b" + +/mob/living/simple_animal/bot/ed209/redtag + lasercolor = "r" + +/mob/living/simple_animal/bot/ed209/UnarmedAttack(atom/A) + if(!on) + return + if(iscarbon(A)) + var/mob/living/carbon/C = A + if(!C.stunned || arrest_type) + stun_attack(A) + else if(C.canBeHandcuffed() && !C.handcuffed) + cuff(A) + else + ..() + +/mob/living/simple_animal/bot/ed209/RangedAttack(atom/A) + if(!on) + return + shootAt(A) + +/mob/living/simple_animal/bot/ed209/proc/stun_attack(mob/living/carbon/C) + playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) + icon_state = "[lasercolor]ed209-c" + spawn(2) + icon_state = "[lasercolor]ed209[on]" + var/threat = 5 + if(istype(C, /mob/living/carbon/human)) + C.stuttering = 5 + C.Stun(5) + C.Weaken(5) + var/mob/living/carbon/human/H = C + threat = H.assess_threat(src) + else + C.Weaken(5) + C.stuttering = 5 + C.Stun(5) + add_logs(src,C,"stunned") + if(declare_arrests) + var/area/location = get_area(src) + speak("[arrest_type ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel) + C.visible_message("[src] has stunned [C]!",\ + "[src] has stunned you!") + +/mob/living/simple_animal/bot/ed209/proc/cuff(mob/living/carbon/C) + mode = BOT_ARREST + playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2) + C.visible_message("[src] is trying to put zipties on [C]!",\ + "[src] is trying to put zipties on you!") + + spawn(60) + if( !Adjacent(C) || !isturf(C.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. + return + if(!C.handcuffed) + C.handcuffed = new /obj/item/weapon/restraints/handcuffs/cable/zipties/used(C) + C.update_inv_handcuffed(1) + back_to_idle() diff --git a/code/modules/mob/living/simple_animal/bot/emote.dm b/code/modules/mob/living/simple_animal/bot/emote.dm new file mode 100644 index 00000000000..4054e520ecf --- /dev/null +++ b/code/modules/mob/living/simple_animal/bot/emote.dm @@ -0,0 +1,119 @@ +/mob/living/simple_animal/bot/emote(var/act, var/m_type=1, var/message = null) + var/param = null + if (findtext(act, "-", 1, null)) + var/t1 = findtext(act, "-", 1, null) + param = copytext(act, t1 + 1, length(act) + 1) + act = copytext(act, 1, t1) + + if(findtext(act,"s",-1) && !findtext(act,"_",-2))//Removes ending s's unless they are prefixed with a '_' + act = copytext(act,1,length(act)) + + //Emote Cooldown System (it's so simple!) + // proc/handle_emote_CD() located in [code\modules\mob\emote.dm] + var/on_CD = 0 + switch(act) + //Cooldown-inducing emotes + if("scream", "screams") + on_CD = handle_emote_CD(50) //longer cooldown + if("ping","buzz","beep","yes","no") //halt is exempt because it's used to stop criminal scum //WHOEVER THOUGHT THAT WAS A GOOD IDEA IS GOING TO GET SHOT. + on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm + //Everything else, including typos of the above emotes + else + on_CD = 0 //If it doesn't induce the cooldown, we won't check for the cooldown + + if(on_CD == 1) // Check if we need to suppress the emote attempt. + return // Suppress emote, you're still cooling off. + //--FalseIncarnate + + switch(act) + if("ping") + var/M = null + if(param) + for (var/mob/A in view(null, null)) + if (param == A.name) + M = A + break + if(!M) + param = null + + if (param) + message = "[src] pings at [param]." + else + message = "[src] pings." + playsound(src.loc, 'sound/machines/ping.ogg', 50, 0) + m_type = 2 + + if("buzz") + var/M = null + if(param) + for (var/mob/A in view(null, null)) + if (param == A.name) + M = A + break + if(!M) + param = null + + if (param) + message = "[src] buzzes at [param]." + else + message = "[src] buzzes." + playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 0) + m_type = 2 + + if("beep") + var/M = null + if(param) + for (var/mob/A in view(null, null)) + if (param == A.name) + M = A + break + if(!M) + param = null + + if (param) + message = "[src] beeps at [param]." + else + message = "[src] beeps." + playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 0) + m_type = 2 + + if("yes") + var/M = null + if(param) + for (var/mob/A in view(null, null)) + if (param == A.name) + M = A + break + if(!M) + param = null + + if (param) + message = "[src] emits an affirmative blip at [param]." + else + message = "[src] emits an affirmative blip." + playsound(src.loc, 'sound/machines/synth_yes.ogg', 50, 0) + m_type = 2 + + if("no") + var/M = null + if(param) + for (var/mob/A in view(null, null)) + if (param == A.name) + M = A + break + if(!M) + param = null + + if (param) + message = "[src] emits a negative blip at [param]." + else + message = "[src] emits a negative blip." + playsound(src.loc, 'sound/machines/synth_no.ogg', 50, 0) + m_type = 2 + + if("scream", "screams") + message = "[src] screams!" + playsound(src.loc, 'sound/goonstation/voice/robot_scream.ogg', 80, 0) + m_type = 2 + + ..(act, m_type, message) \ No newline at end of file diff --git a/code/game/machinery/bots/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm similarity index 58% rename from code/game/machinery/bots/floorbot.dm rename to code/modules/mob/living/simple_animal/bot/floorbot.dm index 82ea0138503..c336cce0104 100644 --- a/code/game/machinery/bots/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -1,538 +1,425 @@ -//Floorbot assemblies -/obj/item/weapon/toolbox_tiles - desc = "It's a toolbox with tiles sticking out the top" - name = "tiles and toolbox" - icon = 'icons/obj/aibots.dmi' - icon_state = "toolbox_tiles" - force = 3.0 - throwforce = 10.0 - throw_speed = 2 - throw_range = 5 - w_class = 3.0 - var/created_name = "Floorbot" - -/obj/item/weapon/toolbox_tiles_sensor - desc = "It's a toolbox with tiles sticking out the top and a sensor attached" - name = "tiles, toolbox and sensor arrangement" - icon = 'icons/obj/aibots.dmi' - icon_state = "toolbox_tiles_sensor" - force = 3.0 - throwforce = 10.0 - throw_speed = 2 - throw_range = 5 - w_class = 3.0 - var/created_name = "Floorbot" - -//Floorbot -/obj/machinery/bot/floorbot - name = "\improper Floorbot" - desc = "A little floor repairing robot, he looks so excited!" - icon = 'icons/obj/aibots.dmi' - icon_state = "floorbot0" - layer = 5.0 - density = 0 - anchored = 0 - health = 25 - maxhealth = 25 - //weight = 1.0E7 - var/amount = 10 - var/replacetiles = 0 - var/eattiles = 0 - var/maketiles = 0 - var/fixfloors = 0 - var/autotile = 0 - var/nag_on_empty = 1 - var/nagged = 0 //Prevents the Floorbot nagging more than once per refill. - var/max_targets = 50 - var/turf/target - var/turf/oldtarget - var/oldloc = null - req_one_access = list(access_construction, access_robotics) - var/targetdirection - radio_name = "Engineering" - bot_type = FLOOR_BOT - bot_type_name = "Floorbot" - bot_filter = RADIO_FLOORBOT - var/process_type //Determines what to do when process_scan() recieves a target. See process_scan() for details. - #define HULL_BREACH 1 - #define BRIDGE_MODE 2 - #define FIX_TILE 3 - #define AUTO_TILE 4 - #define REPLACE_TILE 5 - #define TILE_EMAG 6 - -/obj/machinery/bot/floorbot/New() - ..() - updateicon() - var/datum/job/engineer/J = new/datum/job/engineer - botcard.access = J.get_access() - prev_access = botcard.access - - spawn(5) - add_to_beacons(bot_filter) - -/obj/machinery/bot/floorbot/turn_on() - . = ..() - updateicon() - updateUsrDialog() - -/obj/machinery/bot/floorbot/turn_off() - ..() - updateicon() - updateUsrDialog() - -/obj/machinery/bot/floorbot/bot_reset() - ..() - target = null - oldtarget = null - oldloc = null - ignore_list = list() - nagged = 0 - anchored = 0 - update_icon() - -/obj/machinery/bot/floorbot/set_custom_texts() - text_hack = "You corrupt [name]'s construction protocols." - text_dehack = "You detect errors in [name] and reset his programming." - text_dehack_fail = "[name] is not responding to reset commands!" - -/obj/machinery/bot/floorbot/attack_hand(mob/user as mob) - . = ..() - if (.) - return - usr.set_machine(src) - interact(user) - -/obj/machinery/bot/floorbot/interact(mob/user as mob) - var/dat - dat += hack(user) - dat += "Floor Repairer Controls v1.1

                " - dat += "Status: [on ? "On" : "Off"]
                " - dat += "Maintenance panel panel is [open ? "opened" : "closed"]
                " - dat += "Tiles left: [amount]
                " - dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
                " - if(!locked || issilicon(user)) - dat += "Add tiles to new hull plating: [autotile ? "Yes" : "No"]
                " - dat += "Replace floor tiles: [replacetiles ? "Yes" : "No"]
                " - dat += "Finds tiles: [eattiles ? "Yes" : "No"]
                " - dat += "Make pieces of metal into tiles when empty: [maketiles ? "Yes" : "No"]
                " - dat += "Transmit notice when empty: [nag_on_empty ? "Yes" : "No"]
                " - dat += "Repair damaged tiles and platings: [fixfloors ? "Yes" : "No"]
                " - dat += "Traction Magnets: [anchored ? "Engaged" : "Disengaged"]
                " - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
                " - var/bmode - if (targetdirection) - bmode = dir2text(targetdirection) - else - bmode = "disabled" - dat += "Bridge Mode : [bmode]
                " - - var/datum/browser/popup = new(user, "autofloor", "Automatic Station Floor Repairer v1.1") - popup.set_content(dat) - popup.open() - return - - -/obj/machinery/bot/floorbot/attackby(var/obj/item/W , mob/user as mob, params) - if(istype(W, /obj/item/stack/tile/plasteel)) - var/obj/item/stack/tile/plasteel/T = W - if(amount >= 50) - return - var/loaded = min(50-amount, T.amount) - T.use(loaded) - amount += loaded - if (loaded > 0) - user << "You load [loaded] tiles into the floorbot. He now contains [amount] tiles." - nagged = 0 - updateicon() - else - user << "You need at least one floor tile to put into [src]" - else if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) - if(allowed(user) && !open && !emagged) - locked = !locked - user << "You [locked ? "lock" : "unlock"] \the [src] behaviour controls." - updateUsrDialog() - else - if(emagged) - user << "ERROR" - if(open) - user << "Please close the access panel before locking it." - else - user << "Access denied." - updateUsrDialog() - else - ..() - -/obj/machinery/bot/floorbot/Emag(mob/user as mob) - ..() - if(emagged == 2) - if(user) - user << "[src] buzzes and beeps." - -/obj/machinery/bot/floorbot/Topic(href, href_list) - ..() - switch(href_list["operation"]) - if("replace") - replacetiles = !replacetiles - if("tiles") - eattiles = !eattiles - if("make") - maketiles = !maketiles - if("fix") - fixfloors = !fixfloors - if("autotile") - autotile = !autotile - if("emptynag") - nag_on_empty = !nag_on_empty - if("anchor") - anchored = !anchored - - if("bridgemode") - var/setdir = input("Select construction direction:") as null|anything in list("north","east","south","west","disable") - switch(setdir) - if("north") - targetdirection = 1 - if("south") - targetdirection = 2 - if("east") - targetdirection = 4 - if("west") - targetdirection = 8 - if("disable") - targetdirection = null - updateUsrDialog() - -/obj/machinery/bot/floorbot/bot_process() - if (!..()) - return - - if(mode == BOT_REPAIRING) - return - - if(amount <= 0 && !target) //Out of tiles! We must refill! - if(eattiles) //Configured to find and consume floortiles! - target = scan(/obj/item/stack/tile/plasteel, oldtarget) - process_type = null - - if(!target && maketiles) //We did not manage to find any floor tiles! Scan for metal stacks and make our own! - target = scan(/obj/item/stack/sheet/metal, oldtarget) - process_type = null - return - else - if(nag_on_empty) //Floorbot is empty and cannot acquire more tiles, nag the engineers for more! - nag() - - if(prob(5)) - visible_message("[src] makes an excited booping beeping sound!") - - //Normal scanning procedure. We have tiles loaded, are not emagged. - if(!target && emagged < 2 && amount > 0) - if(targetdirection != null) //The bot is in bridge mode. - //Try to find a space tile immediately in our selected direction. - var/turf/T = get_step(src, targetdirection) - if(istype(T, /turf/space)) - target = T - - else //Find a space tile farther way! - target = scan(/turf/space, oldtarget) - process_type = BRIDGE_MODE - - if(!target) - process_type = HULL_BREACH //Ensures the floorbot does not try to "fix" space areas or shuttle docking zones. - target = scan(/turf/space, oldtarget) - - if(!target && replacetiles) //Finds a floor without a tile and gives it one. - process_type = REPLACE_TILE //The target must be the floor and not a tile. The floor must not already have a floortile. - target = scan(/turf/simulated/floor, oldtarget) - - if(!target && fixfloors) //Repairs damaged floors and tiles. - process_type = FIX_TILE - target = scan(/turf/simulated/floor, oldtarget) - - if(!target && emagged == 2) //We are emagged! Time to rip up the floors! - process_type = TILE_EMAG - target = scan(/turf/simulated/floor, oldtarget) - - - if(!target) - - if(auto_patrol) - if(mode == BOT_IDLE || mode == BOT_START_PATROL) - start_patrol() - - if(mode == BOT_PATROL) - bot_patrol() - - if(!target) - if(loc != oldloc) - oldtarget = null - return - - if(target) - if(path.len == 0) - if(!istype(target, /turf/)) - var/turf/TL = get_turf(target) - path = get_path_to(loc, TL, src, /turf/proc/Distance_cardinal, 0, 30, id=botcard, simulated_only = 0) - else - path = get_path_to(loc, target, src, /turf/proc/Distance_cardinal, 0, 30, id=botcard, simulated_only = 0) - - if(!bot_move(target)) - add_to_ignore(target) - oldtarget = target - target = null - mode = BOT_IDLE - return - else if( !bot_move(target) ) - oldtarget = target - target = null - mode = BOT_IDLE - return - - ignore_list = list() // Reset the ignore list - - if(loc == target || loc == target.loc) - if(istype(target, /obj/item/stack/tile/plasteel)) - eattile(target) - else if(istype(target, /obj/item/stack/sheet/metal)) - maketile(target) - else if(istype(target, /turf/) && emagged < 2) - repair(target) - else if(emagged == 2 && istype(target,/turf/simulated/floor)) - var/turf/simulated/floor/F = target - anchored = 1 - mode = BOT_REPAIRING - if(prob(90)) - F.break_tile_to_plating() - else - F.ReplaceWithLattice() - visible_message("[src] makes an excited booping sound.") - spawn(50) - amount++ - anchored = 0 - mode = BOT_IDLE - target = null - path = list() - return - - oldloc = loc - -/obj/machinery/bot/floorbot/proc/nag() //Annoy everyone on the channel to refill us! - if(!nagged) - var/area/location = get_area(src) - speak("Requesting refill at [location]!", radio_name) - nagged = 1 - -/obj/machinery/bot/floorbot/proc/is_hull_breach(var/turf/t) //Ignore space tiles not considered part of a structure, also ignores shuttle docking areas. - var/area/t_area = get_area(t) - if (t_area && (t_area.name == "Space" || findtext(t_area.name, "huttle"))) - return 0 - else - return 1 - -//Floorbots, having several functions, need sort out special conditions here. -obj/machinery/bot/floorbot/process_scan(var/scan_target) - var/result - var/turf/simulated/floor/F - switch(process_type) - if(HULL_BREACH) //The most common job, patching breaches in the station's hull. - if(is_hull_breach(scan_target)) //Ensure that the targeted space turf is actually part of the station, and not random space. - result = scan_target - anchored = 1 //Prevent the floorbot being blown off-course while trying to reach a hull breach. - if(BRIDGE_MODE) //Only space turfs in our chosen direction are considered. - if(get_dir(src, scan_target) == targetdirection) - result = scan_target - anchored = 1 - if(REPLACE_TILE) - F = scan_target - if(istype(F, /turf/simulated/floor/plating)) //The floor must not already have a tile. - result = F - if(FIX_TILE) //Selects only damaged floors. - F = scan_target - if(istype(F) && (F.broken || F.burnt)) - result = F - if(TILE_EMAG) //Emag mode! Rip up the floor and cause breaches to space! - F = scan_target - if(!istype(F, /turf/simulated/floor/plating)) - result = F - else //If no special processing is needed, simply return the result. - result = scan_target - return result - -/obj/machinery/bot/floorbot/proc/repair(var/turf/target_turf) - - if(istype(target_turf, /turf/space/)) - //Must be a hull breach or in bridge mode to continue. - if(!is_hull_breach(target_turf) && !targetdirection) - target = null - return - else if(!istype(target_turf, /turf/simulated/floor)) - return - if(amount <= 0) - mode = BOT_IDLE - target = null - return - anchored = 1 - icon_state = "floorbot-c" - if(istype(target_turf, /turf/space/)) //If we are fixing an area not part of pure space, it is - visible_message("[targetdirection ? "[src] begins installing a bridge plating." : "[src] begins to repair the hole."] ") - mode = BOT_REPAIRING - spawn(50) - if(mode == BOT_REPAIRING) - if(autotile) //Build the floor and include a tile. - target_turf.ChangeTurf(/turf/simulated/floor) - else //Build a hull plating without a floor tile. - target_turf.ChangeTurf(/turf/simulated/floor/plating) - mode = BOT_IDLE - amount -= 1 - updateicon() - anchored = 0 - target = null - else - var/turf/simulated/floor/F = target_turf - mode = BOT_REPAIRING - visible_message("[src] begins repairing the floor.") - spawn(50) - if(mode == BOT_REPAIRING) - F.broken = 0 - F.burnt = 0 - F.ChangeTurf(/turf/simulated/floor) - mode = BOT_IDLE - amount -= 1 - updateicon() - anchored = 0 - target = null - -/obj/machinery/bot/floorbot/proc/eattile(var/obj/item/stack/tile/plasteel/T) - if(!istype(T, /obj/item/stack/tile/plasteel)) - return - visible_message(" [src] begins to collect tiles.") - mode = BOT_REPAIRING - spawn(20) - if(isnull(T)) - target = null - mode = BOT_IDLE - return - if(amount + T.amount > 50) - var/i = 50 - amount - amount += i - T.amount -= i - else - amount += T.amount - qdel(T) - updateicon() - target = null - mode = BOT_IDLE - -/obj/machinery/bot/floorbot/proc/maketile(var/obj/item/stack/sheet/metal/M) - if(!istype(M, /obj/item/stack/sheet/metal)) - return - visible_message(" [src] begins to create tiles.") - mode = BOT_REPAIRING - spawn(20) - if(isnull(M)) - target = null - mode = BOT_IDLE - return - var/obj/item/stack/tile/plasteel/T = new /obj/item/stack/tile/plasteel - T.amount = 4 - T.loc = M.loc - if(M.amount > 1) - M.amount-- - else - qdel(M) - target = null - mode = BOT_IDLE - -/obj/machinery/bot/floorbot/proc/updateicon() - if(amount > 0) - icon_state = "floorbot[on]" - else - icon_state = "floorbot[on]e" - -/obj/machinery/bot/floorbot/explode() - on = 0 - visible_message("[src] blows apart!") - var/turf/Tsec = get_turf(src) - - var/obj/item/weapon/storage/toolbox/mechanical/N = new /obj/item/weapon/storage/toolbox/mechanical(Tsec) - N.contents = list() - - new /obj/item/device/assembly/prox_sensor(Tsec) - - if (prob(50)) - new /obj/item/robot_parts/l_arm(Tsec) - - while (amount)//Dumps the tiles into the appropriate sized stacks - if(amount >= 16) - var/obj/item/stack/tile/plasteel/T = new (Tsec) - T.amount = 16 - amount -= 16 - else - var/obj/item/stack/tile/plasteel/T = new (Tsec) - T.amount = amount - amount = 0 - - var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - qdel(src) - return - - -/obj/item/weapon/storage/toolbox/mechanical/attackby(var/obj/item/stack/tile/plasteel/T, mob/user as mob, params) - if(!istype(T, /obj/item/stack/tile/plasteel)) - ..() - return - if(contents.len >= 1) - user << "They won't fit in, as there is already stuff inside." - return - if(T.use(10)) - if(user.s_active) - user.s_active.close(user) - var/obj/item/weapon/toolbox_tiles/B = new /obj/item/weapon/toolbox_tiles - user.put_in_hands(B) - user << "You add the tiles into the empty toolbox. They protrude from the top." - user.unEquip(src, 1) - qdel(src) - else - user << "You need 10 floor tiles to start building a floorbot." - return - -/obj/item/weapon/toolbox_tiles/attackby(var/obj/item/W, mob/user as mob, params) - ..() - if(isprox(W)) - qdel(W) - var/obj/item/weapon/toolbox_tiles_sensor/B = new /obj/item/weapon/toolbox_tiles_sensor() - B.created_name = created_name - user.put_in_hands(B) - user << "You add the sensor to the toolbox and tiles!" - user.unEquip(src, 1) - qdel(src) - - else if (istype(W, /obj/item/weapon/pen)) - var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) - if (!t) - return - if (!in_range(src, usr) && loc != usr) - return - - created_name = t - -/obj/item/weapon/toolbox_tiles_sensor/attackby(var/obj/item/W, mob/user as mob, params) - ..() - if(istype(W, /obj/item/robot_parts/l_arm) || istype(W, /obj/item/robot_parts/r_arm)) - qdel(W) - var/turf/T = get_turf(user.loc) - var/obj/machinery/bot/floorbot/A = new /obj/machinery/bot/floorbot(T) - A.name = created_name - user << "You add the robot arm to the odd looking toolbox assembly! Boop beep!" - user.unEquip(src, 1) - qdel(src) - else if (istype(W, /obj/item/weapon/pen)) - var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) - if (!t) - return - if (!in_range(src, usr) && loc != usr) - return - - created_name = t +//Floorbot +/mob/living/simple_animal/bot/floorbot + name = "\improper Floorbot" + desc = "A little floor repairing robot, he looks so excited!" + icon = 'icons/obj/aibots.dmi' + icon_state = "floorbot0" + density = 0 + anchored = 0 + health = 25 + maxHealth = 25 + + radio_channel = "Engineering" + bot_type = FLOOR_BOT + bot_filter = RADIO_FLOORBOT + model = "Floorbot" + bot_purpose = "seek out damaged or missing floor tiles, and repair or replace them as necessary" + bot_core = /obj/machinery/bot_core/floorbot + window_id = "autofloor" + window_name = "Automatic Station Floor Repairer v1.1" + + var/process_type //Determines what to do when process_scan() recieves a target. See process_scan() for details. + var/targetdirection + var/amount = 10 + var/replacetiles = 0 + var/eattiles = 0 + var/maketiles = 0 + var/fixfloors = 0 + var/autotile = 0 + var/nag_on_empty = 1 + var/nagged = 0 //Prevents the Floorbot nagging more than once per refill. + var/max_targets = 50 + var/turf/target + var/oldloc = null + + #define HULL_BREACH 1 + #define BRIDGE_MODE 2 + #define FIX_TILE 3 + #define AUTO_TILE 4 + #define REPLACE_TILE 5 + #define TILE_EMAG 6 + +/mob/living/simple_animal/bot/floorbot/New() + ..() + update_icon() + var/datum/job/engineer/J = new/datum/job/engineer + access_card.access += J.get_access() + prev_access = access_card.access + +/mob/living/simple_animal/bot/floorbot/bot_reset() + ..() + target = null + oldloc = null + ignore_list = list() + nagged = 0 + anchored = 0 + update_icon() + +/mob/living/simple_animal/bot/floorbot/set_custom_texts() + text_hack = "You corrupt [name]'s construction protocols." + text_dehack = "You detect errors in [name] and reset his programming." + text_dehack_fail = "[name] is not responding to reset commands!" + +/mob/living/simple_animal/bot/floorbot/get_controls(mob/user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += "Floor Repairer Controls v1.1

                " + dat += "Status: [on ? "On" : "Off"]
                " + dat += "Maintenance panel panel is [open ? "opened" : "closed"]
                " + dat += "Tiles left: [amount]
                " + dat += "Behvaiour controls are [locked ? "locked" : "unlocked"]
                " + if(!locked || issilicon(user) || check_rights(R_ADMIN, 0, user)) + dat += "Add tiles to new hull plating: [autotile ? "Yes" : "No"]
                " + dat += "Replace floor tiles: [replacetiles ? "Yes" : "No"]
                " + dat += "Finds tiles: [eattiles ? "Yes" : "No"]
                " + dat += "Make pieces of metal into tiles when empty: [maketiles ? "Yes" : "No"]
                " + dat += "Transmit notice when empty: [nag_on_empty ? "Yes" : "No"]
                " + dat += "Repair damaged tiles and platings: [fixfloors ? "Yes" : "No"]
                " + dat += "Traction Magnets: [anchored ? "Engaged" : "Disengaged"]
                " + dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
                " + var/bmode + if(targetdirection) + bmode = dir2text(targetdirection) + else + bmode = "disabled" + dat += "Bridge Mode : [bmode]
                " + + return dat + + +/mob/living/simple_animal/bot/floorbot/attackby(obj/item/W , mob/user, params) + if(istype(W, /obj/item/stack/tile/plasteel)) + var/obj/item/stack/tile/plasteel/T = W + if(amount >= 50) + return + var/loaded = min(50-amount, T.amount) + T.use(loaded) + amount += loaded + if(loaded > 0) + user << "You load [loaded] tiles into the floorbot. He now contains [amount] tiles." + nagged = 0 + update_icon() + else + user << "You need at least one floor tile to put into [src]!" + else + ..() + +/mob/living/simple_animal/bot/floorbot/emag_act(mob/user) + ..() + if(emagged == 2) + if(user) + user << "[src] buzzes and beeps." + +/mob/living/simple_animal/bot/floorbot/Topic(href, href_list) + if(..()) + return 1 + + switch(href_list["operation"]) + if("replace") + replacetiles = !replacetiles + if("tiles") + eattiles = !eattiles + if("make") + maketiles = !maketiles + if("fix") + fixfloors = !fixfloors + if("autotile") + autotile = !autotile + if("emptynag") + nag_on_empty = !nag_on_empty + if("anchor") + anchored = !anchored + + if("bridgemode") + var/setdir = input("Select construction direction:") as null|anything in list("north","east","south","west","disable") + switch(setdir) + if("north") + targetdirection = 1 + if("south") + targetdirection = 2 + if("east") + targetdirection = 4 + if("west") + targetdirection = 8 + if("disable") + targetdirection = null + update_controls() + +/mob/living/simple_animal/bot/floorbot/handle_automated_action() + if(!..()) + return + + if(mode == BOT_REPAIRING) + return + + if(amount <= 0 && !target) //Out of tiles! We must refill! + if(eattiles) //Configured to find and consume floortiles! + target = scan(/obj/item/stack/tile/plasteel) + process_type = null + + if(!target && maketiles) //We did not manage to find any floor tiles! Scan for metal stacks and make our own! + target = scan(/obj/item/stack/sheet/metal) + process_type = null + return + else + if(nag_on_empty) //Floorbot is empty and cannot acquire more tiles, nag the engineers for more! + nag() + + if(prob(5)) + audible_message("[src] makes an excited booping beeping sound!") + + //Normal scanning procedure. We have tiles loaded, are not emagged. + if(!target && emagged < 2 && amount > 0) + if(targetdirection != null) //The bot is in bridge mode. + //Try to find a space tile immediately in our selected direction. + var/turf/T = get_step(src, targetdirection) + if(istype(T, /turf/space)) + target = T + + else //Find a space tile farther way! + target = scan(/turf/space) + process_type = BRIDGE_MODE + + if(!target) + process_type = HULL_BREACH //Ensures the floorbot does not try to "fix" space areas or shuttle docking zones. + target = scan(/turf/space) + + if(!target && replacetiles) //Finds a floor without a tile and gives it one. + process_type = REPLACE_TILE //The target must be the floor and not a tile. The floor must not already have a floortile. + target = scan(/turf/simulated/floor) + + if(!target && fixfloors) //Repairs damaged floors and tiles. + process_type = FIX_TILE + target = scan(/turf/simulated/floor) + + if(!target && emagged == 2) //We are emagged! Time to rip up the floors! + process_type = TILE_EMAG + target = scan(/turf/simulated/floor) + + + if(!target) + + if(auto_patrol) + if(mode == BOT_IDLE || mode == BOT_START_PATROL) + start_patrol() + + if(mode == BOT_PATROL) + bot_patrol() + + if(target) + if(path.len == 0) + if(!istype(target, /turf/)) + var/turf/TL = get_turf(target) + path = get_path_to(src, TL, /turf/proc/Distance_cardinal, 0, 30, id=access_card,simulated_only = 0) + else + path = get_path_to(src, target, /turf/proc/Distance_cardinal, 0, 30, id=access_card,simulated_only = 0) + + if(!bot_move(target)) + add_to_ignore(target) + target = null + mode = BOT_IDLE + return + else if( !bot_move(target) ) + target = null + mode = BOT_IDLE + return + + if(loc == target || loc == target.loc) + if(istype(target, /obj/item/stack/tile/plasteel)) + eattile(target) + else if(istype(target, /obj/item/stack/sheet/metal)) + maketile(target) + else if(istype(target, /turf/) && emagged < 2) + repair(target) + else if(emagged == 2 && istype(target,/turf/simulated/floor)) + var/turf/simulated/floor/F = target + anchored = 1 + mode = BOT_REPAIRING + if(prob(90)) + F.break_tile_to_plating() + else + F.ReplaceWithLattice() + audible_message("[src] makes an excited booping sound.") + spawn(50) + amount ++ + anchored = 0 + mode = BOT_IDLE + target = null + path = list() + return + + oldloc = loc + +/mob/living/simple_animal/bot/floorbot/proc/nag() //Annoy everyone on the channel to refill us! + if(!nagged) + speak("Requesting refill at [get_area(src)]!", radio_channel) + nagged = 1 + +/mob/living/simple_animal/bot/floorbot/proc/is_hull_breach(turf/t) //Ignore space tiles not considered part of a structure, also ignores shuttle docking areas. + var/area/t_area = get_area(t) + if(t_area && (t_area.name == "Space" || findtext(t_area.name, "huttle"))) + return 0 + else + return 1 + +//Floorbots, having several functions, need sort out special conditions here. +/mob/living/simple_animal/bot/floorbot/process_scan(scan_target) + var/result + var/turf/simulated/floor/F + switch(process_type) + if(HULL_BREACH) //The most common job, patching breaches in the station's hull. + if(is_hull_breach(scan_target)) //Ensure that the targeted space turf is actually part of the station, and not random space. + result = scan_target + anchored = 1 //Prevent the floorbot being blown off-course while trying to reach a hull breach. + if(BRIDGE_MODE) //Only space turfs in our chosen direction are considered. + if(get_dir(src, scan_target) == targetdirection) + result = scan_target + anchored = 1 + if(REPLACE_TILE) + F = scan_target + if(istype(F, /turf/simulated/floor/plating)) //The floor must not already have a tile. + result = F + if(FIX_TILE) //Selects only damaged floors. + F = scan_target + if(istype(F) && (F.broken || F.burnt)) + result = F + if(TILE_EMAG) //Emag mode! Rip up the floor and cause breaches to space! + F = scan_target + if(!istype(F, /turf/simulated/floor/plating)) + result = F + else //If no special processing is needed, simply return the result. + result = scan_target + return result + +/mob/living/simple_animal/bot/floorbot/proc/repair(turf/target_turf) + + if(istype(target_turf, /turf/space/)) + //Must be a hull breach or in bridge mode to continue. + if(!is_hull_breach(target_turf) && !targetdirection) + target = null + return + else if(!istype(target_turf, /turf/simulated/floor)) + return + if(amount <= 0) + mode = BOT_IDLE + target = null + return + anchored = 1 + icon_state = "floorbot-c" + if(istype(target_turf, /turf/space/)) //If we are fixing an area not part of pure space, it is + visible_message("[targetdirection ? "[src] begins installing a bridge plating." : "[src] begins to repair the hole."] ") + mode = BOT_REPAIRING + spawn(50) + if(mode == BOT_REPAIRING) + if(autotile) //Build the floor and include a tile. + target_turf.ChangeTurf(/turf/simulated/floor/plasteel) + else //Build a hull plating without a floor tile. + target_turf.ChangeTurf(/turf/simulated/floor/plating) + mode = BOT_IDLE + amount -= 1 + update_icon() + anchored = 0 + target = null + else + var/turf/simulated/floor/F = target_turf + mode = BOT_REPAIRING + visible_message("[src] begins repairing the floor.") + spawn(50) + if(mode == BOT_REPAIRING) + F.broken = 0 + F.burnt = 0 + F.ChangeTurf(/turf/simulated/floor/plasteel) + mode = BOT_IDLE + amount -= 1 + update_icon() + anchored = 0 + target = null + +/mob/living/simple_animal/bot/floorbot/proc/eattile(obj/item/stack/tile/plasteel/T) + if(!istype(T, /obj/item/stack/tile/plasteel)) + return + visible_message("[src] begins to collect tiles.") + mode = BOT_REPAIRING + spawn(20) + if(isnull(T)) + target = null + mode = BOT_IDLE + return + if(amount + T.amount > 50) + var/i = 50 - amount + amount += i + T.amount -= i + else + amount += T.amount + qdel(T) + update_icon() + target = null + mode = BOT_IDLE + +/mob/living/simple_animal/bot/floorbot/proc/maketile(obj/item/stack/sheet/metal/M) + if(!istype(M, /obj/item/stack/sheet/metal)) + return + visible_message("[src] begins to create tiles.") + mode = BOT_REPAIRING + spawn(20) + if(isnull(M)) + target = null + mode = BOT_IDLE + return + var/obj/item/stack/tile/plasteel/T = new /obj/item/stack/tile/plasteel + T.amount = 4 + T.forceMove(M.loc) + if(M.amount > 1) + M.amount-- + else + qdel(M) + target = null + mode = BOT_IDLE + +/mob/living/simple_animal/bot/floorbot/update_icon() + if(amount > 0) + icon_state = "floorbot[on]" + else + icon_state = "floorbot[on]e" + +/mob/living/simple_animal/bot/floorbot/explode() + on = 0 + visible_message("[src] blows apart!") + var/turf/Tsec = get_turf(src) + + var/obj/item/weapon/storage/toolbox/mechanical/N = new /obj/item/weapon/storage/toolbox/mechanical(Tsec) + N.contents = list() + + new /obj/item/device/assembly/prox_sensor(Tsec) + + if(prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + while(amount)//Dumps the tiles into the appropriate sized stacks + if(amount >= 16) + var/obj/item/stack/tile/plasteel/T = new (Tsec) + T.amount = 16 + amount -= 16 + else + var/obj/item/stack/tile/plasteel/T = new (Tsec) + T.amount = amount + amount = 0 + + var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + ..() + +/obj/machinery/bot_core/floorbot + req_one_access = list(access_construction, access_robotics) + +/mob/living/simple_animal/bot/floorbot/UnarmedAttack(atom/A) + if(isturf(A)) + repair(A) + else if(istype(A,/obj/item/stack/tile/plasteel)) + eattile(A) + else if(istype(A,/obj/item/stack/sheet/metal)) + maketile(A) + else + ..() diff --git a/code/game/machinery/bots/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm similarity index 57% rename from code/game/machinery/bots/medbot.dm rename to code/modules/mob/living/simple_animal/bot/medbot.dm index 95dcf14267f..b1504fdb109 100644 --- a/code/game/machinery/bots/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -1,659 +1,563 @@ -//MEDBOT -//MEDBOT PATHFINDING -//MEDBOT ASSEMBLY - - -/obj/machinery/bot/medbot - name = "\improper Medibot" - desc = "A little medical robot. He looks somewhat underwhelmed." - icon = 'icons/obj/aibots.dmi' - icon_state = "medibot0" - layer = 5.0 - density = 0 - anchored = 0 - health = 20 - maxhealth = 20 - req_one_access =list(access_medical, access_robotics) - var/stunned = 0 //It can be stunned by tasers. Delicate circuits. -// var/emagged = 0 - var/list/botcard_access = list(access_medical) - var/obj/item/weapon/reagent_containers/glass/reagent_glass = null //Can be set to draw from this for reagents. - var/skin = null //Set to "tox", "ointment" or "o2" for the other two firstaid kits. - var/mob/living/carbon/patient = null - var/mob/living/carbon/oldpatient = null - var/oldloc = null - var/last_found = 0 - var/last_newpatient_speak = 0 //Don't spam the "HEY I'M COMING" messages - var/injection_amount = 15 //How much reagent do we inject at a time? - var/heal_threshold = 10 //Start healing when they have this much damage in a category - var/use_beaker = 0 //Use reagents in beaker instead of default treatment agents. - var/declare_crit = 1 //If active, the bot will transmit a critical patient alert to MedHUD users. - var/declare_cooldown = 0 //Prevents spam of critical patient alerts. - var/stationary_mode = 0 //If enabled, the Medibot will not move automatically. - radio_name = "Medical" - //Setting which reagents to use to treat what by default. By id. - var/treatment_brute = "salglu_solution" - var/treatment_oxy = "salbutamol" - var/treatment_fire = "salglu_solution" - var/treatment_tox = "charcoal" - var/treatment_virus = "spaceacillin" - var/treat_virus = 1 //If on, the bot will attempt to treat viral infections, curing them if possible. - var/shut_up = 0 //self explanatory :) - bot_type = MED_BOT - bot_type_name = "Medbot" - bot_filter = RADIO_MEDBOT - -/obj/machinery/bot/medbot/tox - skin = "tox" - -/obj/machinery/bot/medbot/o2 - skin = "o2" - -/obj/machinery/bot/medbot/brute - skin = "brute" - -/obj/machinery/bot/medbot/fire - skin = "ointment" - -/obj/machinery/bot/medbot/adv - skin = "adv" - -/obj/machinery/bot/medbot/fish - skin = "fish" - -/obj/machinery/bot/medbot/syndicate - name = "Suspicious Medibot" - desc = "You'd better have insurance!" - skin = "bezerk" - treatment_oxy = "perfluorodecalin" - treatment_brute = "styptic_powder" - treatment_fire = "silver_sulfadiazine" - treatment_tox = "charcoal" - req_one_access =list(access_syndicate) - -/obj/machinery/bot/medbot/mysterious - name = "\improper Mysterious Medibot" - desc = "International Medibot of mystery." - skin = "bezerk2" - treatment_oxy = "perfluorodecalin" - treatment_brute = "styptic_powder" - treatment_fire = "silver_sulfadiazine" - treatment_tox = "charcoal" - -/obj/item/weapon/firstaid_arm_assembly - name = "incomplete medibot assembly." - desc = "A first aid kit with a robot arm permanently grafted to it." - icon = 'icons/obj/aibots.dmi' - icon_state = "firstaid_arm" - var/build_step = 0 - var/created_name = "Medibot" //To preserve the name if it's a unique medbot I guess - var/skin = null //Same as medbot, set to tox or ointment for the respective kits. - w_class = 3.0 - var/treatment_brute = "salglu_solution" - var/treatment_oxy = "salbutamol" - var/treatment_fire = "salglu_solution" - var/treatment_tox = "charcoal" - var/treatment_virus = "spaceacillin" - req_one_access = list(access_medical, access_robotics) - - /obj/item/weapon/firstaid_arm_assembly/New() - ..() - spawn(5) - if(skin) - overlays += image('icons/obj/aibots.dmi', "kit_skin_[skin]") - -/obj/machinery/bot/medbot/proc/updateicon() - if(!on) - icon_state = "medibot0" - return - if(mode == BOT_HEALING) - icon_state = "medibots[stationary_mode]" - return - else if(stationary_mode) //Bot has yellow light to indicate stationary mode. - icon_state = "medibot2" - else - icon_state = "medibot1" - -/obj/machinery/bot/medbot/New() - ..() - updateicon() - - spawn(4) - if(skin) - overlays += image('icons/obj/aibots.dmi', "medskin_[skin]") - - if(isnull(botcard_access) || (botcard_access.len < 1)) - var/datum/job/doctor/J = new/datum/job/doctor - botcard.access = J.get_access() - else - botcard.access = botcard_access - prev_access = botcard.access - add_to_beacons(bot_filter) - -/obj/machinery/bot/medbot/turn_on() - . = ..() - updateicon() - updateUsrDialog() - -/obj/machinery/bot/medbot/turn_off() - ..() - updateicon() - updateUsrDialog() - -/obj/machinery/bot/medbot/bot_reset() - ..() - patient = null - oldpatient = null - oldloc = null - last_found = world.time - declare_cooldown = 0 - -/obj/machinery/bot/medbot/set_custom_texts() - - text_hack = "You corrupt [name]'s reagent processor circuits." - text_dehack = "You reset [name]'s reagent processor circuits." - text_dehack_fail = "[name] seems damaged and does not respond to reprogramming!" - -/obj/machinery/bot/medbot/attack_hand(mob/user as mob) - . = ..() - if (.) - return - usr.set_machine(src) - var/dat - dat += hack(user) - dat += "Medical Unit Controls v1.1

                " - dat += "Status: [on ? "On" : "Off"]
                " - dat += "Maintenance panel panel is [open ? "opened" : "closed"]
                " - dat += "Beaker: " - if (reagent_glass) - dat += "Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]" - else - dat += "None Loaded" - dat += "
                Behaviour controls are [locked ? "locked" : "unlocked"]
                " - if(!locked || issilicon(user)) - dat += "Healing Threshold: " - dat += "-- " - dat += "- " - dat += "[heal_threshold] " - dat += "+ " - dat += "++" - dat += "
                " - - dat += "Injection Level: " - dat += "- " - dat += "[injection_amount] " - dat += "+ " - dat += "
                " - - dat += "Reagent Source: " - dat += "[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]
                " - - dat += "Treat Viral Infections: [treat_virus ? "Yes" : "No"]
                " - dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle
                " - dat += "Critical Patient Alerts: [declare_crit ? "Yes" : "No"]
                " - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
                " - dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
                " - - var/datum/browser/popup = new(user, "automed", "Automatic Medical Unit v1.1") - popup.set_content(dat) - popup.open() - return - -/obj/machinery/bot/medbot/Topic(href, href_list) - ..() - - if(href_list["adj_threshold"]) - var/adjust_num = text2num(href_list["adj_threshold"]) - heal_threshold += adjust_num - if(heal_threshold < 5) - heal_threshold = 5 - if(heal_threshold > 75) - heal_threshold = 75 - - else if(href_list["adj_inject"]) - var/adjust_num = text2num(href_list["adj_inject"]) - injection_amount += adjust_num - if(injection_amount < 5) - injection_amount = 5 - if(injection_amount > 15) - injection_amount = 15 - - else if(href_list["use_beaker"]) - use_beaker = !use_beaker - - else if (href_list["eject"] && (!isnull(reagent_glass))) - reagent_glass.forceMove(get_turf(src)) - reagent_glass = null - - else if (href_list["togglevoice"]) - shut_up = !shut_up - - else if (href_list["critalerts"]) - declare_crit = !declare_crit - - else if (href_list["stationary"]) - stationary_mode = !stationary_mode - path = list() - updateicon() - - else if (href_list["virus"]) - treat_virus = !treat_virus - - updateUsrDialog() - return - -/obj/machinery/bot/medbot/attackby(obj/item/weapon/W as obj, mob/user as mob, params) - if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) - if (allowed(user) && !open && !emagged) - locked = !locked - user << "Controls are now [locked ? "locked." : "unlocked."]" - updateUsrDialog() - else - if(emagged) - user << "ERROR" - if(open) - user << "Please close the access panel before locking it." - else - user << "Access denied." - - else if (istype(W, /obj/item/weapon/reagent_containers/glass)) - if(locked) - user << "You cannot insert a beaker because the panel is locked." - return - if(!isnull(reagent_glass)) - user << "There is already a beaker loaded." - return - - if(!user.drop_item()) - user << "\The [W] is stuck to your hand!" - return - W.forceMove(src) - reagent_glass = W - user << "You insert [W]." - updateUsrDialog() - return - - else - var/current_health = health - ..() - if (health < current_health) //if medbot took some damage - step_to(src, (get_step_away(src,user))) - -/obj/machinery/bot/medbot/Emag(mob/user as mob) - ..() - if(emagged == 2) - declare_crit = 0 - if(user) - user << "You short out [src]'s reagent synthesis circuits." - spawn(0) - visible_message("[src] buzzes oddly!") - flick("medibot_spark", src) - if(user) - oldpatient = user - -/obj/machinery/bot/medbot/process_scan(var/mob/living/carbon/human/H) - if (H.stat == 2) - return - - if ((H == oldpatient) && (world.time < last_found + 100)) - return - - if(assess_patient(H)) - last_found = world.time - if((last_newpatient_speak + 300) < world.time) //Don't spam these messages! - var/message = pick("Hey, [H.name]! Hold on, I'm coming.","Wait [H.name]! I want to help!","[H.name], you appear to be injured!") - speak(message) - last_newpatient_speak = world.time - return H - else - return - -/obj/machinery/bot/medbot/bot_process() - if (!..()) - return - - if(mode == BOT_HEALING) - return - - if(stunned) - icon_state = "medibota" - stunned-- - - oldpatient = patient - patient = null - mode = BOT_IDLE - - if(stunned <= 0) - updateicon() - stunned = 0 - return - - if(frustration > 8) - oldpatient = patient - patient = null - mode = BOT_IDLE - last_found = world.time - path = list() - - if(!patient) - if(!shut_up && prob(1)) - var/message = pick("Radar, put a mask on!","There's always a catch, and it's the best there is.","I knew it, I should've been a plastic surgeon.","What kind of medbay is this? Everyone's dropping like dead flies.","Delicious!") - speak(message) - var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients. - patient = scan(/mob/living/carbon/human, oldpatient, scan_range) - oldpatient = patient - - if(patient && (get_dist(src,patient) <= 1)) //Patient is next to us, begin treatment! - if(mode != BOT_HEALING) - mode = BOT_HEALING - updateicon() - frustration = 0 - medicate_patient(patient) - return - - //Patient has moved away from us! - else if(patient && path.len && (get_dist(patient,path[path.len]) > 2)) - path = list() - mode = BOT_IDLE - last_found = world.time - - else if(stationary_mode && patient) - patient = null - mode = BOT_IDLE - last_found = world.time - return - if(patient && path.len == 0 && (get_dist(src,patient) > 1)) - spawn(0) - path = AStar(loc, get_turf(patient), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance_cardinal, 0, 30,id=botcard) - if(!path) - path = list() - - if(path.len > 0 && patient) - if(!bot_move(patient)) - oldpatient = patient - patient = null - mode = BOT_IDLE - last_found = world.time - return - - if(path.len > 8 && patient) - frustration++ - - if(auto_patrol && !stationary_mode && !patient) - if(mode == BOT_IDLE || mode == BOT_START_PATROL) - start_patrol() - - if(mode == BOT_PATROL) - bot_patrol() - - return - -/obj/machinery/bot/medbot/proc/assess_patient(mob/living/carbon/C as mob) - //Time to see if they need medical help! - if(C.stat == 2) - return 0 //welp too late for them! - - if(C.suiciding) - return 0 //Kevorkian school of robotic medical assistants. - - if(emagged == 2) //Everyone needs our medicine. (Our medicine is toxins) - return 1 - - if((skin == "bezerk") && (!("syndicate" in C.faction))) - return 0 - - if(declare_crit && C.health <= 0) //Critical condition! Call for help! - declare(C) - - //If they're injured, we're using a beaker, and don't have one of our WONDERCHEMS. - if((reagent_glass) && (use_beaker) && ((C.getBruteLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getOxyLoss() >= (heal_threshold + 15)))) - for(var/datum/reagent/R in reagent_glass.reagents.reagent_list) - if(!C.reagents.has_reagent(R.id)) - return 1 - - //They're injured enough for it! - if((C.getBruteLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_brute))) - return 1 //If they're already medicated don't bother! - - if((C.getOxyLoss() >= (15 + heal_threshold)) && (!C.reagents.has_reagent(treatment_oxy))) - return 1 - - if((C.getFireLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_fire))) - return 1 - - if((C.getToxLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_tox))) - return 1 - - if((C.virus2.len) && (!C.reagents.has_reagent(treatment_virus))) - for (var/ID in C.virus2) - if (ID in virusDB) // If the virus is known, the medbot is aware of it - return 1 - return 0 - -/obj/machinery/bot/medbot/proc/medicate_patient(mob/living/carbon/C as mob) - if(!on) - return - - if(!istype(C)) - oldpatient = patient - patient = null - mode = BOT_IDLE - last_found = world.time - return - - if(C.stat == 2) - var/death_message = pick("No! NO!","Live, damnit! LIVE!","I...I've never lost a patient before. Not today, I mean.") - speak(death_message) - oldpatient = patient - patient = null - mode = BOT_IDLE - last_found = world.time - return - - var/reagent_id = null - - if(emagged == 2) //Emagged! Time to poison everybody. - reagent_id = "pancuronium" - - else - if(treat_virus) - var/virus = 0 - if(C:virus2.len) - for (var/ID in C.virus2) - if (ID in virusDB) // If the virus is known, the medbot is aware of it and will try to cure it - virus = 1 - - if (!reagent_id && (virus)) - if(!C.reagents.has_reagent(treatment_virus)) - reagent_id = treatment_virus - - if (!reagent_id && (C.getBruteLoss() >= heal_threshold)) - if(!C.reagents.has_reagent(treatment_brute)) - reagent_id = treatment_brute - - if (!reagent_id && (C.getOxyLoss() >= (15 + heal_threshold))) - if(!C.reagents.has_reagent(treatment_oxy)) - reagent_id = treatment_oxy - - if (!reagent_id && (C.getFireLoss() >= heal_threshold)) - if(!C.reagents.has_reagent(treatment_fire)) - reagent_id = treatment_fire - - if (!reagent_id && (C.getToxLoss() >= heal_threshold)) - if(!C.reagents.has_reagent(treatment_tox)) - reagent_id = treatment_tox - - //If the patient is injured but doesn't have our special reagent in them then we should give it to them first - if(reagent_id && use_beaker && reagent_glass && reagent_glass.reagents.total_volume) - for(var/datum/reagent/R in reagent_glass.reagents.reagent_list) - if(!C.reagents.has_reagent(R.id)) - reagent_id = "internal_beaker" - break - - if(!reagent_id) //If they don't need any of that they're probably cured! - oldpatient = patient - patient = null - mode = BOT_IDLE - last_found = world.time - var/message = pick("All patched up!","An apple a day keeps me away.","Feel better soon!") - speak(message) - updateicon() - return - else - C.visible_message("[src] is trying to inject [patient]!", \ - "[src] is trying to inject [patient]!") - - spawn(30) - if ((get_dist(src, patient) <= 1) && (on)) - if(reagent_id == "internal_beaker") - if(use_beaker && reagent_glass && reagent_glass.reagents.total_volume) - reagent_glass.reagents.trans_to(patient,injection_amount) //Inject from beaker instead. - reagent_glass.reagents.reaction(patient, 2) - else - patient.reagents.add_reagent(reagent_id,injection_amount) - C.visible_message("[src] injects [patient] with the syringe!", \ - "[src] injects [patient] with the syringe!") - - mode = BOT_IDLE - updateicon() - return - - reagent_id = null - return - -/obj/machinery/bot/medbot/bullet_act(var/obj/item/projectile/Proj) - if(Proj.flag == "taser") - stunned = min(stunned+10,20) - ..() - -/obj/machinery/bot/medbot/explode() - on = 0 - visible_message("[src] blows apart!") - var/turf/Tsec = get_turf(src) - - switch(skin) - if("ointment") - new /obj/item/weapon/storage/firstaid/fire/empty(Tsec) - if("tox") - new /obj/item/weapon/storage/firstaid/toxin/empty(Tsec) - if("o2") - new /obj/item/weapon/storage/firstaid/o2/empty(Tsec) - if("brute") - new /obj/item/weapon/storage/firstaid/brute/empty(Tsec) - if("adv") - new /obj/item/weapon/storage/firstaid/adv/empty(Tsec) - if("bezerk") - new /obj/item/weapon/storage/firstaid/tactical/empty(Tsec) - if("fish") - new /obj/item/weapon/storage/firstaid/aquatic_kit(Tsec) - else - new /obj/item/weapon/storage/firstaid(Tsec) - - new /obj/item/device/assembly/prox_sensor(Tsec) - - new /obj/item/device/healthanalyzer(Tsec) - - if(reagent_glass) - reagent_glass.forceMove(Tsec) - reagent_glass = null - - if (prob(50)) - new /obj/item/robot_parts/l_arm(Tsec) - - var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - qdel(src) - return - -/obj/machinery/bot/medbot/proc/declare(var/crit_patient) - if(declare_cooldown) - return - if((skin == "bezerk")) - return - var/area/location = get_area(src) - speak("Medical emergency! [crit_patient ? "[crit_patient]" : "A patient"] is in critical condition at [location]!", radio_name) - declare_cooldown = 1 - spawn(200) //Twenty seconds - declare_cooldown = 0 - -/* - * Medbot Assembly -- Can be made out of all three medkits. - */ - -/obj/item/weapon/storage/firstaid/attackby(var/obj/item/robot_parts/S, mob/user as mob, params) - - if ((!istype(S, /obj/item/robot_parts/l_arm)) && (!istype(S, /obj/item/robot_parts/r_arm))) - ..() - return - - //Making a medibot! - if(contents.len >= 1) - user << "You need to empty [src] out first." - return - - var/obj/item/weapon/firstaid_arm_assembly/A = new /obj/item/weapon/firstaid_arm_assembly - if(istype(src,/obj/item/weapon/storage/firstaid/fire)) - A.skin = "ointment" - else if(istype(src,/obj/item/weapon/storage/firstaid/toxin)) - A.skin = "tox" - else if(istype(src,/obj/item/weapon/storage/firstaid/o2)) - A.skin = "o2" - else if(istype(src,/obj/item/weapon/storage/firstaid/brute)) - A.skin = "brute" - else if(istype(src,/obj/item/weapon/storage/firstaid/adv)) - A.skin = "adv" - else if(istype(src,/obj/item/weapon/storage/firstaid/tactical)) - A.skin = "bezerk" - else if(istype(src,/obj/item/weapon/storage/firstaid/aquatic_kit)) - A.skin = "fish" - A.req_one_access = req_one_access - A.treatment_oxy = treatment_oxy - A.treatment_brute = treatment_brute - A.treatment_fire = treatment_fire - A.treatment_tox = treatment_tox - A.treatment_virus = treatment_virus - qdel(S) - user.put_in_hands(A) - user << "You add the robot arm to the first aid kit." - user.unEquip(src, 1) - qdel(src) - - -/obj/item/weapon/firstaid_arm_assembly/attackby(obj/item/weapon/W as obj, mob/user as mob, params) - ..() - if(istype(W, /obj/item/weapon/pen)) - var/t = stripped_input(user, "Enter new robot name", name, created_name,MAX_NAME_LEN) - if (!t) - return - if (!in_range(src, usr) && loc != usr) - return - created_name = t - else - switch(build_step) - if(0) - if(istype(W, /obj/item/device/healthanalyzer) && user.drop_item()) - qdel(W) - build_step++ - user << "You add the health sensor to [src]." - name = "First aid/robot arm/health analyzer assembly" - overlays += image('icons/obj/aibots.dmi', "na_scanner") - - if(1) - if(isprox(W)) - user.drop_item() - qdel(W) - build_step++ - user << "You complete the Medibot! Beep boop." - var/turf/T = get_turf(src) - var/obj/machinery/bot/medbot/S = new /obj/machinery/bot/medbot(T) - S.skin = skin - S.name = created_name - S.treatment_oxy = treatment_oxy - S.treatment_brute = treatment_brute - S.treatment_fire = treatment_fire - S.treatment_tox = treatment_tox - S.treatment_virus = treatment_virus - S.req_one_access = req_one_access - user.unEquip(src, 1) - qdel(src) \ No newline at end of file +//Medbot +/mob/living/simple_animal/bot/medbot + name = "\improper Medibot" + desc = "A little medical robot. He looks somewhat underwhelmed." + icon = 'icons/obj/aibots.dmi' + icon_state = "medibot0" + density = 0 + anchored = 0 + health = 20 + maxHealth = 20 + pass_flags = PASSMOB + + radio_channel = "Medical" + + bot_type = MED_BOT + bot_filter = RADIO_MEDBOT + model = "Medibot" + bot_purpose = "seek out hurt crewmembers and ensure that they are healed" + bot_core_type = /obj/machinery/bot_core/medbot + window_id = "automed" + window_name = "Automatic Medical Unit v1.1" + + var/obj/item/weapon/reagent_containers/glass/reagent_glass = null //Can be set to draw from this for reagents. + var/skin = null //Set to "tox", "ointment" or "o2" for the other two firstaid kits. + var/mob/living/carbon/patient = null + var/mob/living/carbon/oldpatient = null + var/oldloc = null + var/last_found = 0 + var/last_newpatient_speak = 0 //Don't spam the "HEY I'M COMING" messages + var/injection_amount = 15 //How much reagent do we inject at a time? + var/heal_threshold = 10 //Start healing when they have this much damage in a category + var/use_beaker = 0 //Use reagents in beaker instead of default treatment agents. + var/declare_crit = 1 //If active, the bot will transmit a critical patient alert to MedHUD users. + var/declare_cooldown = 0 //Prevents spam of critical patient alerts. + var/stationary_mode = 0 //If enabled, the Medibot will not move automatically. + //Setting which reagents to use to treat what by default. By id. + var/treatment_brute = "salglu_solution" + var/treatment_oxy = "salbutamol" + var/treatment_fire = "salglu_solution" + var/treatment_tox = "charcoal" + var/treatment_virus = "spaceacillin" + var/treat_virus = 1 //If on, the bot will attempt to treat viral infections, curing them if possible. + var/shut_up = 0 //self explanatory :) + +/mob/living/simple_animal/bot/medbot/tox + skin = "tox" + +/mob/living/simple_animal/bot/medbot/o2 + skin = "o2" + +/mob/living/simple_animal/bot/medbot/brute + skin = "brute" + +/mob/living/simple_animal/bot/medbot/fire + skin = "ointment" + +/mob/living/simple_animal/bot/medbot/adv + skin = "adv" + +/mob/living/simple_animal/bot/medbot/fish + skin = "fish" + +/mob/living/simple_animal/bot/medbot/mysterious + name = "\improper Mysterious Medibot" + desc = "International Medibot of mystery." + skin = "bezerk" + treatment_oxy = "perfluorodecalin" + treatment_brute = "styptic_powder" + treatment_fire = "silver_sulfadiazine" + treatment_tox = "charcoal" + +/mob/living/simple_animal/bot/medbot/syndicate + name = "Suspicious Medibot" + desc = "You'd better have insurance!" + skin = "bezerk" + treatment_oxy = "perfluorodecalin" + treatment_brute = "styptic_powder" + treatment_fire = "silver_sulfadiazine" + treatment_tox = "charcoal" + bot_core_type = /obj/machinery/bot_core/medbot/syndicate + control_freq = BOT_FREQ + 1000 // make it not show up on lists + radio_channel = "Syndicate" + radio_config = list("Common" = 1, "Medical" = 1, "Syndicate" = 1) + +/mob/living/simple_animal/bot/medbot/syndicate/New() + ..() + Radio.syndie = 1 + +/mob/living/simple_animal/bot/medbot/update_icon() + if(!on) + icon_state = "medibot0" + return + if(mode == BOT_HEALING) + icon_state = "medibots[stationary_mode]" + return + else if(stationary_mode) //Bot has yellow light to indicate stationary mode. + icon_state = "medibot2" + else + icon_state = "medibot1" + +/mob/living/simple_animal/bot/medbot/New() + ..() + update_icon() + + spawn(4) + if(skin) + overlays += image('icons/obj/aibots.dmi', "medskin_[skin]") + + var/datum/job/doctor/J = new/datum/job/doctor + access_card.access += J.get_access() + prev_access = access_card.access + + var/datum/atom_hud/medsensor = huds[DATA_HUD_MEDICAL_ADVANCED] + medsensor.add_hud_to(src) + permanent_huds |= medsensor + +/mob/living/simple_animal/bot/medbot/bot_reset() + ..() + patient = null + oldpatient = null + oldloc = null + last_found = world.time + declare_cooldown = 0 + update_icon() + +/mob/living/simple_animal/bot/medbot/proc/soft_reset() //Allows the medibot to still actively perform its medical duties without being completely halted as a hard reset does. + path = list() + patient = null + mode = BOT_IDLE + last_found = world.time + update_icon() + +/mob/living/simple_animal/bot/medbot/set_custom_texts() + text_hack = "You corrupt [name]'s reagent processor circuits." + text_dehack = "You reset [name]'s reagent processor circuits." + text_dehack_fail = "[name] seems damaged and does not respond to reprogramming!" + +/mob/living/simple_animal/bot/medbot/get_controls(mob/user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += "Medical Unit Controls v1.1

                " + dat += "Status: [on ? "On" : "Off"]
                " + dat += "Maintenance panel panel is [open ? "opened" : "closed"]
                " + dat += "Beaker: " + if(reagent_glass) + dat += "Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]" + else + dat += "None Loaded" + dat += "
                Behaviour controls are [locked ? "locked" : "unlocked"]
                " + if(!locked || issilicon(user) || check_rights(R_ADMIN, 0, user)) + dat += "Healing Threshold: " + dat += "-- " + dat += "- " + dat += "[heal_threshold] " + dat += "+ " + dat += "++" + dat += "
                " + + dat += "Injection Level: " + dat += "- " + dat += "[injection_amount] " + dat += "+ " + dat += "
                " + + dat += "Reagent Source: " + dat += "[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]
                " + + dat += "Treat Viral Infections: [treat_virus ? "Yes" : "No"]
                " + dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle
                " + dat += "Critical Patient Alerts: [declare_crit ? "Yes" : "No"]
                " + dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
                " + dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
                " + + return dat + +/mob/living/simple_animal/bot/medbot/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["adj_threshold"]) + var/adjust_num = text2num(href_list["adj_threshold"]) + heal_threshold += adjust_num + if(heal_threshold < 5) + heal_threshold = 5 + if(heal_threshold > 75) + heal_threshold = 75 + + else if(href_list["adj_inject"]) + var/adjust_num = text2num(href_list["adj_inject"]) + injection_amount += adjust_num + if(injection_amount < 5) + injection_amount = 5 + if(injection_amount > 15) + injection_amount = 15 + + else if(href_list["use_beaker"]) + use_beaker = !use_beaker + + else if(href_list["eject"] && (!isnull(reagent_glass))) + reagent_glass.forceMove(get_turf(src)) + reagent_glass = null + + else if(href_list["togglevoice"]) + shut_up = !shut_up + + else if(href_list["critalerts"]) + declare_crit = !declare_crit + + else if(href_list["stationary"]) + stationary_mode = !stationary_mode + path = list() + update_icon() + + else if(href_list["virus"]) + treat_virus = !treat_virus + + update_controls() + return + +/mob/living/simple_animal/bot/medbot/attackby(obj/item/weapon/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/weapon/reagent_containers/glass)) + if(locked) + user << "You cannot insert a beaker because the panel is locked!" + return + if(!isnull(reagent_glass)) + user << "There is already a beaker loaded!" + return + if(!user.drop_item()) + return + + W.forceMove(src) + reagent_glass = W + user << "You insert [W]." + show_controls(user) + return + + else + var/current_health = health + ..() + if(health < current_health) //if medbot took some damage + step_to(src, (get_step_away(src,user))) + +/mob/living/simple_animal/bot/medbot/emag_act(mob/user) + ..() + if(emagged == 2) + declare_crit = 0 + if(user) + user << "You short out [src]'s reagent synthesis circuits." + audible_message("[src] buzzes oddly!") + flick("medibot_spark", src) + if(user) + oldpatient = user + +/mob/living/simple_animal/bot/medbot/process_scan(mob/living/carbon/human/H) + if(H.stat == 2) + return + + if((H == oldpatient) && (world.time < last_found + 200)) + return + + if(assess_patient(H)) + last_found = world.time + if((last_newpatient_speak + 300) < world.time) //Don't spam these messages! + var/message = pick("Hey, [H.name]! Hold on, I'm coming.","Wait [H.name]! I want to help!","[H.name], you appear to be injured!") + speak(message) + last_newpatient_speak = world.time + return H + else + return + +/mob/living/simple_animal/bot/medbot/handle_automated_action() + if(!..()) + return + + if(mode == BOT_HEALING) + return + + if(stunned) + icon_state = "medibota" + stunned-- + + oldpatient = patient + patient = null + mode = BOT_IDLE + + if(stunned <= 0) + update_icon() + stunned = 0 + return + + if(frustration > 8) + oldpatient = patient + soft_reset() + + if(!patient) + if(!shut_up && prob(1)) + var/message = pick("Radar, put a mask on!","There's always a catch, and it's the best there is.","I knew it, I should've been a plastic surgeon.","What kind of medbay is this? Everyone's dropping like dead flies.","Delicious!") + speak(message) + var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients. + patient = scan(/mob/living/carbon/human, oldpatient, scan_range) + oldpatient = patient + + if(patient && (get_dist(src,patient) <= 1)) //Patient is next to us, begin treatment! + if(mode != BOT_HEALING) + mode = BOT_HEALING + update_icon() + frustration = 0 + medicate_patient(patient) + return + + //Patient has moved away from us! + else if(patient && path.len && (get_dist(patient,path[path.len]) > 2)) + path = list() + mode = BOT_IDLE + last_found = world.time + + else if(stationary_mode && patient) //Since we cannot move in this mode, ignore the patient and wait for another. + soft_reset() + return + + if(patient && path.len == 0 && (get_dist(src,patient) > 1)) + path = get_path_to(src, get_turf(patient), /turf/proc/Distance_cardinal, 0, 30,id=access_card) + mode = BOT_MOVING + if(!path.len) //try to get closer if you can't reach the patient directly + path = get_path_to(src, get_turf(patient), /turf/proc/Distance_cardinal, 0, 30,1,id=access_card) + if(!path.len) //Do not chase a patient we cannot reach. + soft_reset() + + if(path.len > 0 && patient) + if(!bot_move(path[path.len])) + oldpatient = patient + soft_reset() + return + + if(path.len > 8 && patient) + frustration++ + + if(auto_patrol && !stationary_mode && !patient) + if(mode == BOT_IDLE || mode == BOT_START_PATROL) + start_patrol() + + if(mode == BOT_PATROL) + bot_patrol() + + return + +/mob/living/simple_animal/bot/medbot/proc/assess_patient(mob/living/carbon/C) + //Time to see if they need medical help! + if(C.stat == 2) + return 0 //welp too late for them! + + if(C.suiciding) + return 0 //Kevorkian school of robotic medical assistants. + + if(emagged == 2) //Everyone needs our medicine. (Our medicine is toxins) + return 1 + + if((skin == "bezerk") && (!("syndicate" in C.faction))) + return 0 + + if(declare_crit && C.health <= 0) //Critical condition! Call for help! + declare(C) + + //If they're injured, we're using a beaker, and don't have one of our WONDERCHEMS. + if((reagent_glass) && (use_beaker) && ((C.getBruteLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getOxyLoss() >= (heal_threshold + 15)))) + for(var/datum/reagent/R in reagent_glass.reagents.reagent_list) + if(!C.reagents.has_reagent(R.id)) + return 1 + + //They're injured enough for it! + if((C.getBruteLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_brute))) + return 1 //If they're already medicated don't bother! + + if((C.getOxyLoss() >= (15 + heal_threshold)) && (!C.reagents.has_reagent(treatment_oxy))) + return 1 + + if((C.getFireLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_fire))) + return 1 + + if((C.getToxLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_tox))) + return 1 + + if(treat_virus && C.virus2.len && !C.reagents.has_reagent(treatment_virus)) + for (var/ID in C.virus2) + if (ID in virusDB) // If the virus is known, the medbot is aware of it + return 1 + + return 0 + +/mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A) + if(iscarbon(A)) + var/mob/living/carbon/C = A + patient = C + mode = BOT_HEALING + update_icon() + medicate_patient(C) + update_icon() + else + ..() + +/mob/living/simple_animal/bot/medbot/examinate(atom/A as mob|obj|turf in view()) + ..() + if(!is_blind(src)) + chemscan(src, A) + +/mob/living/simple_animal/bot/medbot/proc/medicate_patient(mob/living/carbon/C) + if(!on) + return + + if(!istype(C)) + oldpatient = patient + soft_reset() + return + + if(C.stat == 2) + var/death_message = pick("No! NO!","Live, damnit! LIVE!","I...I've never lost a patient before. Not today, I mean.") + speak(death_message) + oldpatient = patient + soft_reset() + return + + var/reagent_id = null + + if(emagged == 2) //Emagged! Time to poison everybody. + reagent_id = "pancuronium" + + else + if(treat_virus) + var/virus = 0 + if(C.virus2.len) + for (var/ID in C.virus2) + if (ID in virusDB) // If the virus is known, the medbot is aware of it and will try to cure it + virus = 1 + + if (!reagent_id && (virus)) + if(!C.reagents.has_reagent(treatment_virus)) + reagent_id = treatment_virus + + if(!reagent_id && (C.getBruteLoss() >= heal_threshold)) + if(!C.reagents.has_reagent(treatment_brute)) + reagent_id = treatment_brute + + if(!reagent_id && (C.getOxyLoss() >= (15 + heal_threshold))) + if(!C.reagents.has_reagent(treatment_oxy)) + reagent_id = treatment_oxy + + if(!reagent_id && (C.getFireLoss() >= heal_threshold)) + if(!C.reagents.has_reagent(treatment_fire)) + reagent_id = treatment_fire + + if(!reagent_id && (C.getToxLoss() >= heal_threshold)) + if(!C.reagents.has_reagent(treatment_tox)) + reagent_id = treatment_tox + + //If the patient is injured but doesn't have our special reagent in them then we should give it to them first + if(reagent_id && use_beaker && reagent_glass && reagent_glass.reagents.total_volume) + for(var/datum/reagent/R in reagent_glass.reagents.reagent_list) + if(!C.reagents.has_reagent(R.id)) + reagent_id = "internal_beaker" + break + + if(!reagent_id) //If they don't need any of that they're probably cured! + var/message = pick("All patched up!","An apple a day keeps me away.","Feel better soon!") + speak(message) + bot_reset() + return + else + if(!emagged && check_overdose(patient,reagent_id,injection_amount)) + soft_reset() + return + C.visible_message("[src] is trying to inject [patient]!", \ + "[src] is trying to inject you!") + + spawn(30)//replace with do mob + if((get_dist(src, patient) <= 1) && on && assess_patient(patient)) + if(reagent_id == "internal_beaker") + if(use_beaker && reagent_glass && reagent_glass.reagents.total_volume) + var/fraction = min(injection_amount/reagent_glass.reagents.total_volume, 1) + reagent_glass.reagents.reaction(patient, 2, fraction) + reagent_glass.reagents.trans_to(patient, injection_amount) //Inject from beaker instead. + else + patient.reagents.add_reagent(reagent_id,injection_amount) + C.visible_message("[src] injects [patient] with its syringe!", \ + "[src] injects you with its syringe!") + else + visible_message("[src] retracts its syringe.") + update_icon() + soft_reset() + return + + reagent_id = null + return + +/mob/living/simple_animal/bot/medbot/proc/check_overdose(mob/living/carbon/patient,reagent_id,injection_amount) + var/datum/reagent/R = chemical_reagents_list[reagent_id] + if(!R.overdose_threshold) + return 0 + var/current_volume = patient.reagents.get_reagent_amount(reagent_id) + if(current_volume + injection_amount > R.overdose_threshold) + return 1 + return 0 + +/mob/living/simple_animal/bot/medbot/bullet_act(obj/item/projectile/Proj) + if(Proj.flag == "taser") + stunned = min(stunned+10,20) + ..() + +/mob/living/simple_animal/bot/medbot/explode() + on = 0 + visible_message("[src] blows apart!") + var/turf/Tsec = get_turf(src) + + switch(skin) + if("ointment") + new /obj/item/weapon/storage/firstaid/fire/empty(Tsec) + if("tox") + new /obj/item/weapon/storage/firstaid/toxin/empty(Tsec) + if("o2") + new /obj/item/weapon/storage/firstaid/o2/empty(Tsec) + if("brute") + new /obj/item/weapon/storage/firstaid/brute/empty(Tsec) + if("adv") + new /obj/item/weapon/storage/firstaid/adv/empty(Tsec) + if("bezerk") + new /obj/item/weapon/storage/firstaid/tactical/empty(Tsec) + if("fish") + new /obj/item/weapon/storage/firstaid/aquatic_kit(Tsec) + else + new /obj/item/weapon/storage/firstaid(Tsec) + + new /obj/item/device/assembly/prox_sensor(Tsec) + + new /obj/item/device/healthanalyzer(Tsec) + + if(reagent_glass) + reagent_glass.forceMove(Tsec) + reagent_glass = null + + if(prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + ..() + +/mob/living/simple_animal/bot/medbot/proc/declare(crit_patient) + if(declare_cooldown) + return + if((skin == "bezerk")) + return + var/area/location = get_area(src) + speak("Medical emergency! [crit_patient ? "[crit_patient]" : "A patient"] is in critical condition at [location]!", radio_channel) + declare_cooldown = 1 + spawn(200) //Twenty seconds + declare_cooldown = 0 + +/obj/machinery/bot_core/medbot + req_one_access = list(access_medical, access_robotics) + +/obj/machinery/bot_core/medbot/syndicate + req_one_access = list(access_syndicate) \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm new file mode 100644 index 00000000000..a16949b87a8 --- /dev/null +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -0,0 +1,869 @@ +//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 + +// Mulebot - carries crates around for Quartermaster +// Navigates via floor navbeacons +// Remote Controlled from QM's PDA + +#define SIGH 0 +#define ANNOYED 1 +#define DELIGHT 2 + +/mob/living/simple_animal/bot/mulebot + name = "\improper MULEbot" + desc = "A Multiple Utility Load Effector bot." + icon_state = "mulebot0" + density = 1 + anchored = 1 + animate_movement=1 + health = 50 + maxHealth = 50 + damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + a_intent = "harm" //No swapping + buckle_lying = 0 + + radio_channel = "Supply" + + bot_type = MULE_BOT + bot_filter = RADIO_MULEBOT + model = "MULE" + bot_purpose = "deliver crates and other packages between departments, as requested" + bot_core_type = /obj/machinery/bot_core/mulebot + + suffix = "" + + var/global/mulebot_count = 0 + var/atom/movable/load = null + var/mob/living/passenger = null + var/turf/target // this is turf to navigate to (location of beacon) + var/loaddir = 0 // this the direction to unload onto/load from + var/home_destination = "" // tag of home beacon + + var/reached_target = 1 //true if already reached the target + + var/auto_return = 1 // true if auto return to home beacon after unload + var/auto_pickup = 1 // true if auto-pickup at beacon + var/report_delivery = 1 // true if bot will announce an arrival to a location. + + var/obj/item/weapon/stock_parts/cell/cell + var/datum/wires/mulebot/wires = null + var/bloodiness = 0 + var/currentBloodColor = "#A10808" + var/currentDNA = null + +/mob/living/simple_animal/bot/mulebot/New() + ..() + wires = new /datum/wires/mulebot(src) + var/datum/job/cargo_tech/J = new/datum/job/cargo_tech + access_card.access = J.get_access() + prev_access = access_card.access + cell = new(src) + cell.charge = 2000 + cell.maxcharge = 2000 + + mulebot_count++ + set_suffix(suffix ? suffix : "#[mulebot_count]") + +/mob/living/simple_animal/bot/mulebot/Destroy() + unload(0) + if(wires) + qdel(wires) + wires = null + if(cell) + qdel(cell) + cell = null + return ..() + +/mob/living/simple_animal/bot/mulebot/proc/set_suffix(suffix) + src.suffix = suffix + if(paicard) + bot_name = "\improper MULEbot ([suffix])" + else + name = "\improper MULEbot ([suffix])" + +/mob/living/simple_animal/bot/mulebot/bot_reset() + ..() + reached_target = 0 + +/mob/living/simple_animal/bot/mulebot/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/weapon/screwdriver)) + ..() + if(open) + on = FALSE + update_controls() + else if(istype(I,/obj/item/weapon/stock_parts/cell) && open && !cell) + if(!user.drop_item()) + return + var/obj/item/weapon/stock_parts/cell/C = I + C.forceMove(src) + cell = C + visible_message("[user] inserts a cell into [src].", + "You insert the new cell into [src].") + update_controls() + else if(istype(I, /obj/item/weapon/crowbar) && open && cell) + cell.add_fingerprint(usr) + cell.forceMove(loc) + cell = null + visible_message("[user] crowbars out the power cell from [src].", + "You pry the powercell out of [src].") + update_controls() + else if (istype(I, /obj/item/weapon/wrench)) + if (health < maxHealth) + adjustBruteLoss(-25) + updatehealth() + user.visible_message( + "[user] repairs [src]!", + "You repair [src]!" + ) + else + user << "[src] does not need a repair!" + else if((istype(I, /obj/item/device/multitool) || istype(I, /obj/item/weapon/wirecutters)) && open) + return attack_hand(user) + else if(load && ismob(load)) // chance to knock off rider + if(prob(1 + I.force * 2)) + unload(0) + user.visible_message("[user] knocks [load] off [src] with \the [I]!", + "You knock [load] off [src] with \the [I]!") + else + user << "You hit [src] with \the [I] but to no effect!" + ..() + else + ..() + update_icon() + return + +/mob/living/simple_animal/bot/mulebot/emag_act(mob/user) + if(emagged < 1) + emagged = 1 + if(!open) + locked = !locked + user << "You [locked ? "lock" : "unlock"] the [src]'s controls!" + flick("mulebot-emagged", src) + playsound(loc, 'sound/effects/sparks1.ogg', 100, 0) + +/mob/living/simple_animal/bot/mulebot/update_icon() + if(open) + icon_state="mulebot-hatch" + else + icon_state = "mulebot[!wires.MobAvoid()]" + overlays.Cut() + if(load && !ismob(load))//buckling handles the mob offsets + load.pixel_y = initial(load.pixel_y) + 9 + if(load.layer < layer) + load.layer = layer + 0.1 + overlays += load + return + +/mob/living/simple_animal/bot/mulebot/ex_act(severity) + unload(0) + switch(severity) + if(1) + qdel(src) + if(2) + for(var/i = 1; i < 3; i++) + wires.RandomCut() + if(3) + wires.RandomCut() + return + +/mob/living/simple_animal/bot/mulebot/bullet_act(obj/item/projectile/Proj) + if(..()) + if(prob(50) && !isnull(load)) + unload(0) + if(prob(25)) + visible_message("Something shorts out inside [src]!") + wires.RandomCut() + +/mob/living/simple_animal/bot/mulebot/Topic(href, list/href_list) + if(..()) + return 1 + + switch(href_list["op"]) + if("lock") + toggle_lock(usr) + if("power") + if(on) + turn_off() + else if(cell && !open) + if(!turn_on()) + usr << "You can't switch on [src]!" + return + else + return + visible_message("[usr] switches [on ? "on" : "off"] [src].") + if("cellremove") + if(open && cell && !usr.get_active_hand()) + cell.updateicon() + usr.put_in_active_hand(cell) + cell.add_fingerprint(usr) + cell = null + + usr.visible_message("[usr] removes the power cell from [src].", "You remove the power cell from [src].") + if("cellinsert") + if(open && !cell) + var/obj/item/weapon/stock_parts/cell/C = usr.get_active_hand() + if(istype(C)) + usr.drop_item() + cell = C + C.forceMove(src) + C.add_fingerprint(usr) + + usr.visible_message("[usr] inserts a power cell into [src].", "You insert the power cell into [src].") + if("stop") + if(mode >= BOT_DELIVER) + bot_reset() + if("go") + if(mode == BOT_IDLE) + start() + if("home") + if(mode == BOT_IDLE || mode == BOT_DELIVER) + start_home() + if("destination") + var/new_dest = input(usr, "Enter Destination:", name, destination) as null|anything in deliverybeacontags + if(new_dest) + set_destination(new_dest) + if("setid") + var/new_id = stripped_input(usr, "Enter ID:", name, suffix, MAX_NAME_LEN) + if(new_id) + set_suffix(new_id) + if("sethome") + var/new_home = input(usr, "Enter Home:", name, home_destination) as null|anything in deliverybeacontags + if(new_home) + home_destination = new_home + if("unload") + if(load && mode != BOT_HUNT) + if(loc == target) + unload(loaddir) + else + unload(0) + if("autoret") + auto_return = !auto_return + if("autopick") + auto_pickup = !auto_pickup + if("report") + report_delivery = !report_delivery + update_controls() + +/mob/living/simple_animal/bot/mulebot/proc/toggle_lock(var/mob/user) + if(bot_core.allowed(user)) + locked = !locked + update_controls() + return 1 + else + user << "Access denied." + return 0 + +// TODO: remove this; PDAs currently depend on it +/mob/living/simple_animal/bot/mulebot/get_controls(mob/user) + var/ai = issilicon(user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += "

                Multiple Utility Load Effector Mk. V

                " + dat += "ID: [suffix]
                " + dat += "Power: [on ? "On" : "Off"]
                " + + if(!open) + dat += "

                Status

                " + dat += "
                " + switch(mode) + if(BOT_IDLE) + dat += "Ready" + if(BOT_DELIVER) + dat += "[mode_name[BOT_DELIVER]]" + if(BOT_GO_HOME) + dat += "[mode_name[BOT_GO_HOME]]" + if(BOT_BLOCKED) + dat += "[mode_name[BOT_BLOCKED]]" + if(BOT_NAV,BOT_WAIT_FOR_NAV) + dat += "[mode_name[BOT_NAV]]" + if(BOT_NO_ROUTE) + dat += "[mode_name[BOT_NO_ROUTE]]" + dat += "
                " + + dat += "Current Load: [load ? load.name : "none"]
                " + dat += "Destination: [!destination ? "none" : destination]
                " + dat += "Power level: [cell ? cell.percent() : 0]%" + + if(locked && !ai && !check_rights(R_ADMIN, 0, user)) + dat += " 
                Controls are locked
                Unlock Controls" + else + dat += " 
                Controls are unlocked
                Lock Controls

                " + + 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"])
                " + dat += "Toggle Delivery Reporting ([report_delivery ? "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 + dat += "Removed
                " + + dat += wires.GetInteractWindow() + else + dat += "
                The bot is in maintenance mode and cannot be controlled.

                " + + return dat + + +// returns true if the bot has power +/mob/living/simple_animal/bot/mulebot/proc/has_power() + return !open && cell && cell.charge > 0 && wires.HasPower() + +/mob/living/simple_animal/bot/mulebot/proc/buzz(type) + switch(type) + if(SIGH) + audible_message("[src] makes a sighing buzz.", "You hear an electronic buzzing sound.") + playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0) + if(ANNOYED) + audible_message("[src] makes an annoyed buzzing sound.", "You hear an electronic buzzing sound.") + playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0) + if(DELIGHT) + audible_message("[src] makes a delighted ping!", "You hear a ping.") + playsound(loc, 'sound/machines/ping.ogg', 50, 0) + + +// mousedrop a crate to load the bot +// can load anything if hacked +/mob/living/simple_animal/bot/mulebot/MouseDrop_T(atom/movable/AM, mob/user) + + if(user.incapacitated() || user.lying || get_dist(user, src) > 1) + return + + if(!istype(AM)) + return + + load(AM) + +// called to load a crate +/mob/living/simple_animal/bot/mulebot/proc/load(atom/movable/AM) + if(!on || load || AM.anchored || get_dist(src, AM) > 1) + return + + + //I'm sure someone will come along and ask why this is here... well people were dragging screen items onto the mule, and that was not cool. + //So this is a simple fix that only allows a selection of item types to be considered. Further narrowing-down is below. + if(!istype(AM, /obj/item) && !istype(AM, /obj/machinery) && !istype(AM, /obj/structure) && !ismob(AM)) + return + if(!isturf(AM.loc)) //To prevent the loading from stuff from someone's inventory or screen icons. + return + + var/obj/structure/closet/crate/CRATE + if(istype(AM,/obj/structure/closet/crate)) + CRATE = AM + else + if(wires.LoadCheck()) + buzz(SIGH) + return // if not hacked, only allow crates to be loaded + + if(CRATE) // if it's a crate, close before loading + CRATE.close() + + if(isobj(AM)) + var/obj/O = AM + if(O.buckled_mob || (locate(/mob) in AM)) //can't load non crates objects with mobs buckled to it or inside it. + buzz(SIGH) + return + + if(isliving(AM)) + if(!load_mob(AM)) + return + else + AM.forceMove(src) + + load = AM + mode = BOT_IDLE + update_icon() + +/mob/living/simple_animal/bot/mulebot/proc/load_mob(mob/living/M) + can_buckle = TRUE + if(buckle_mob(M)) + passenger = M + load = M + can_buckle = FALSE + if(M.client) + M.client.perspective = EYE_PERSPECTIVE + M.client.eye = src + return TRUE + return FALSE + +/mob/living/simple_animal/bot/mulebot/post_buckle_mob(mob/living/M) + if(M == buckled_mob) //post buckling + M.pixel_y = initial(M.pixel_y) + 9 + if(M.layer < layer) + M.layer = layer + 0.1 + + else //post unbuckling + load = null + M.layer = initial(M.layer) + M.pixel_y = initial(M.pixel_y) + +// called to unload the bot +// argument is optional direction to unload +// if zero, unload at bot's location +/mob/living/simple_animal/bot/mulebot/proc/unload(dirn) + if(!load) + return + + mode = BOT_IDLE + + overlays.Cut() + + if(ismob(load)) + var/mob/M = load + if(M.client) + M.client.perspective = MOB_PERSPECTIVE + M.client.eye = M + unbuckle_mob() + + if(load) + load.forceMove(loc) + load.pixel_y = initial(load.pixel_y) + load.layer = initial(load.layer) + if(dirn) + var/turf/T = loc + var/turf/newT = get_step(T,dirn) + if(load.CanPass(load,newT)) //Can't get off onto anything that wouldn't let you pass normally + 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 == access_card || AM == Radio || AM == bot_core || AM == paicard) + continue + + AM.forceMove(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 = M + +/mob/living/simple_animal/bot/mulebot/call_bot() + ..() + var/area/dest_area + if(path && path.len) + target = ai_waypoint //Target is the end point of the path, the waypoint set by the AI. + dest_area = get_area(target) + destination = format_text(dest_area.name) + pathset = 1 //Indicates the AI's custom path is initialized. + start() + +/mob/living/simple_animal/bot/mulebot/handle_automated_action() + diag_hud_set_botmode() + + if(!has_power()) + on = 0 + return + if(on) + var/speed = (wires.Motor1() ? 1 : 0) + (wires.Motor2() ? 2 : 0) + //world << "speed: [speed]" + var/num_steps = 0 + switch(speed) + if(0) + // do nothing + if(1) + num_steps = 10 + if(2) + num_steps = 5 + if(3) + num_steps = 3 + + if(num_steps) + process_bot() + num_steps-- + if(mode != BOT_IDLE) + spawn(0) + for(var/i=num_steps,i>0,i--) + sleep(2) + process_bot() + +/mob/living/simple_animal/bot/mulebot/proc/process_bot() + if(!on) + return + update_icon() + + switch(mode) + if(BOT_IDLE) // idle + return + + if(BOT_DELIVER, BOT_GO_HOME, BOT_BLOCKED) // 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])" + + var/oldloc = loc + var/moved = step_towards(src, next) // attempt to move + if(cell) cell.use(1) + if(moved && oldloc!=loc) // successful move + //world << "Successful move." + blockcount = 0 + path -= loc + + if(destination == home_destination) + mode = BOT_GO_HOME + else + mode = BOT_DELIVER + + else // failed to move + + //world << "Unable to move." + blockcount++ + mode = BOT_BLOCKED + if(blockcount == 3) + buzz(ANNOYED) + + if(blockcount > 10) // attempt 10 times before recomputing + // find new path excluding blocked turf + buzz(SIGH) + mode = BOT_WAIT_FOR_NAV + blockcount = 0 + spawn(20) + calc_path(avoid=next) + if(path.len > 0) + buzz(DELIGHT) + mode = BOT_BLOCKED + return + return + else + buzz(ANNOYED) + //world << "Bad turf." + mode = BOT_NAV + return + else + //world << "No path." + mode = BOT_NAV + return + + if(BOT_NAV) // calculate new path + //world << "Calc new path." + mode = BOT_WAIT_FOR_NAV + spawn(0) + calc_path() + + if(path.len > 0) + blockcount = 0 + mode = BOT_BLOCKED + buzz(DELIGHT) + + else + buzz(SIGH) + + mode = BOT_NO_ROUTE + +/mob/living/simple_animal/bot/mulebot/Move(turf/simulated/next) + var/turf/simulated/last = loc + + if(istype(last) && bloodiness) + last.AddTracks(/obj/effect/decal/cleanable/blood/tracks/wheels, currentDNA, 0, get_dir(last, next), currentBloodColor) + + . = ..() + + if(. && istype(next) && bloodiness) + next.AddTracks(/obj/effect/decal/cleanable/blood/tracks/wheels, currentDNA, get_dir(last, next), 0, currentBloodColor) + bloodiness-- + +// calculates a path to the current destination +// given an optional turf to avoid +/mob/living/simple_animal/bot/mulebot/calc_path(turf/avoid = null) + check_bot_access() + path = get_path_to(src, target, /turf/proc/Distance_cardinal, 0, 250, id=access_card, exclude=avoid) + +// sets the current destination +// signals all beacons matching the delivery code +// beacons will return a signal giving their locations +/mob/living/simple_animal/bot/mulebot/proc/set_destination(new_dest) + new_destination = new_dest + get_nav() + +// starts bot moving to current destination +/mob/living/simple_animal/bot/mulebot/proc/start() + if(!on) + return + if(destination == home_destination) + mode = BOT_GO_HOME + else + mode = BOT_DELIVER + update_icon() + get_nav() + +// starts bot moving to home +// sends a beacon query to find +/mob/living/simple_animal/bot/mulebot/proc/start_home() + if(!on) + return + spawn(0) + set_destination(home_destination) + mode = BOT_BLOCKED + update_icon() + +// called when bot reaches current target +/mob/living/simple_animal/bot/mulebot/proc/at_target() + if(!reached_target) + radio_channel = "Supply" //Supply channel + audible_message("[src] makes a chiming sound!", "You hear a chime.") + playsound(loc, 'sound/machines/chime.ogg', 50, 0) + reached_target = 1 + + if(pathset) //The AI called us here, so notify it of our arrival. + loaddir = dir //The MULE will attempt to load a crate in whatever direction the MULE is "facing". + if(calling_ai) + calling_ai << "\icon[src] [src] wirelessly plays a chiming sound!" + playsound(calling_ai, 'sound/machines/chime.ogg',40, 0) + calling_ai = null + radio_channel = "AI Private" //Report on AI Private instead if the AI is controlling us. + + if(load) // if loaded, unload at target + if(report_delivery) + speak("Destination [destination] reached. Unloading [load].", radio_channel) + if(istype(load, /obj/structure/closet/crate)) + var/obj/structure/closet/crate/C = load + C.notifyRecipient(destination) + unload(loaddir) + else + // not loaded + if(auto_pickup) // find a crate + var/atom/movable/AM + if(wires.LoadCheck()) // if hacked, 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/structure/closet/crate) in get_step(loc,loaddir) + if(AM && AM.Adjacent(src)) + load(AM) + if(report_delivery) + speak("Now loading [load] at [get_area(src)].", radio_channel) + // whatever happened, check to see if we return home + + if(auto_return && home_destination && destination != home_destination) + // auto return set and not at home already + start_home() + mode = BOT_BLOCKED + else + bot_reset() // otherwise go idle + + return + +// called when bot bumps into anything +/mob/living/simple_animal/bot/mulebot/Bump(atom/obs) + if(!wires.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)) + visible_message("[src] bumps into [M]!") + else + add_logs(src, M, "knocked down") + visible_message("[src] knocks over [M]!") + M.stop_pulling() + M.Stun(8) + M.Weaken(5) + return ..() + +// called from mob/living/carbon/human/Crossed() +// when mulebot is in the same loc +/mob/living/simple_animal/bot/mulebot/proc/RunOver(mob/living/carbon/human/H) + add_logs(src, H, "run over", null, "(DAMTYPE: [uppertext(BRUTE)])") + H.visible_message("[src] drives over [H]!", \ + "[src] drives over you!") + playsound(loc, 'sound/effects/splat.ogg', 50, 1) + + var/damage = rand(5,15) + H.apply_damage(2*damage, BRUTE, "head", run_armor_check("head", "melee")) + H.apply_damage(2*damage, BRUTE, "chest", run_armor_check("chest", "melee")) + H.apply_damage(0.5*damage, BRUTE, "l_leg", run_armor_check("l_leg", "melee")) + H.apply_damage(0.5*damage, BRUTE, "r_leg", run_armor_check("r_leg", "melee")) + H.apply_damage(0.5*damage, BRUTE, "l_arm", run_armor_check("l_arm", "melee")) + H.apply_damage(0.5*damage, BRUTE, "r_arm", run_armor_check("r_arm", "melee")) + + var/obj/effect/decal/cleanable/blood/B = new(loc) + B.blood_DNA = list() + B.blood_DNA[H.dna.unique_enzymes] = H.dna.b_type + currentDNA = B.blood_DNA + + bloodiness += 4 + + currentBloodColor = "#A10808" + if(istype(H)) + var/datum/reagent/blood/bld = H.get_blood(H.vessel) + if(bld.data["blood_colour"]) + currentBloodColor = bld.data["blood_colour"] + return + B.basecolor = currentBloodColor + B.update_icon() + +/mob/living/simple_animal/bot/mulebot/bot_control_message(command, mob/user, user_turf) + switch(command) + if("start") + if(load) + src << "DELIVER [load] TO [destination]" + else + src << "PICK UP DELIVERY AT [destination]" + if("unload") + if(load) + src << "UNLOAD" + else + src << "LOAD" + if("autoret", "autopick", "target") + else + ..() + +/mob/living/simple_animal/bot/mulebot/receive_signal(datum/signal/signal) + if(!wires.RemoteRX() || ..()) + return 1 + + var/recv = signal.data["command"] + + // process control input + switch(recv) + if("start") + start() + + if("target") + set_destination(signal.data["destination"]) + + if("unload") + if(client) + return 1 + if(loc == target) + unload(loaddir) + else + unload(0) + + if("home") + start_home() + + if("autoret") + auto_return = text2num(signal.data["value"]) + + if("autopick") + auto_pickup = text2num(signal.data["value"]) + + else + return 0 + return 1 + +// send a radio signal with multiple data key/values +/mob/living/simple_animal/bot/mulebot/post_signal_multiple(var/freq, var/list/keyval) + if(!wires.RemoteTX()) + return + + ..() + +// signals bot status etc. to controller +/mob/living/simple_animal/bot/mulebot/send_status() + var/list/kv = list( + "type" = MULE_BOT, + "name" = suffix, + "loca" = get_area(src), + "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) + +// player on mulebot attempted to move +/mob/living/simple_animal/bot/mulebot/relaymove(mob/user) + if(user.incapacitated()) + return + if(load == user) + unload(0) + + +//Update navigation data. Called when commanded to deliver, return home, or a route update is needed... +/mob/living/simple_animal/bot/mulebot/proc/get_nav() + if(!on || !wires.BeaconRX()) + return + + for(var/obj/machinery/navbeacon/NB in deliverybeacons) + if(NB.location == new_destination) // if the beacon location matches the set destination + destination = new_destination // the we will navigate there + target = NB.loc + var/direction = NB.dir // this will be the load/unload dir + loaddir = direction + update_icon() + calc_path() + return + +/mob/living/simple_animal/bot/mulebot/emp_act(severity) + if(cell) + cell.emp_act(severity) + if(load) + load.emp_act(severity) + ..() + + +/mob/living/simple_animal/bot/mulebot/explode() + visible_message("[src] blows apart!") + var/turf/Tsec = get_turf(src) + + new /obj/item/device/assembly/prox_sensor(Tsec) + new /obj/item/stack/rods(Tsec) + new /obj/item/stack/rods(Tsec) + new /obj/item/stack/cable_coil/cut(Tsec) + if(cell) + cell.forceMove(Tsec) + cell.update_icon() + cell = null + + var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + + new /obj/effect/decal/cleanable/blood/oil(loc) + ..() + +/mob/living/simple_animal/bot/mulebot/remove_air(amount) //To prevent riders suffocating + if(loc) + return loc.remove_air(amount) + else + return null + +/mob/living/simple_animal/bot/mulebot/resist() + ..() + if(load) + unload() + +/mob/living/simple_animal/bot/mulebot/UnarmedAttack(atom/A) + if(isturf(A) && isturf(loc) && loc.Adjacent(A) && load) + unload(get_dir(loc, A)) + else + ..() + +#undef SIGH +#undef ANNOYED +#undef DELIGHT + +/obj/machinery/bot_core/mulebot + req_access = list(access_cargo) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm new file mode 100644 index 00000000000..59c89580de5 --- /dev/null +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -0,0 +1,462 @@ +/mob/living/simple_animal/bot/secbot + name = "\improper Securitron" + desc = "A little security robot. He looks less than thrilled." + icon = 'icons/obj/aibots.dmi' + icon_state = "secbot0" + density = 0 + anchored = 0 + health = 25 + maxHealth = 25 + damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + pass_flags = PASSMOB + + radio_channel = "Security" //Security channel + bot_type = SEC_BOT + bot_filter = RADIO_SECBOT + model = "Securitron" + bot_purpose = "seek out criminals, handcuff them, and report their location to security" + bot_core_type = /obj/machinery/bot_core/secbot + window_id = "autosec" + window_name = "Automatic Security Unit v1.6" + + var/base_icon = "secbot" + var/mob/living/carbon/target + var/oldtarget_name + var/threatlevel = 0 + var/target_lastloc //Loc of target when arrested. + var/last_found //There's a delay + var/declare_arrests = 1 //When making an arrest, should it notify everyone on the security channel? + var/idcheck = 0 //If true, arrest people with no IDs + var/weaponscheck = 0 //If true, arrest people for weapons if they lack access + var/check_records = 1 //Does it check security records? + var/arrest_type = 0 //If true, don't handcuff + var/harmbaton = 0 //If true, beat instead of stun + var/flashing_lights = 0 //If true, flash lights + var/prev_flashing_lights = 0 + +/mob/living/simple_animal/bot/secbot/beepsky + name = "Officer Beep O'sky" + desc = "It's Officer Beep O'sky! Powered by a potato and a shot of whiskey." + idcheck = 0 + weaponscheck = 0 + auto_patrol = 1 + +/mob/living/simple_animal/bot/secbot/beepsky/explode() + var/turf/Tsec = get_turf(src) + new /obj/item/weapon/stock_parts/cell/potato(Tsec) + var/obj/item/weapon/reagent_containers/food/drinks/drinkingglass/S = new(Tsec) + S.reagents.add_reagent("whiskey", 15) + S.on_reagent_change() + ..() + +/mob/living/simple_animal/bot/secbot/pingsky + name = "Officer Pingsky" + desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment." + radio_channel = "AI Private" + +/mob/living/simple_animal/bot/secbot/ofitser + name = "Prison Ofitser" + desc = "It's Prison Ofitser! Powered by the tears and sweat of prisoners." + idcheck = 0 + weaponscheck = 1 + auto_patrol = 1 + +/mob/living/simple_animal/bot/secbot/buzzsky + name = "Officer Buzzsky" + desc = "It's Officer Buzzsky! Rusted and falling apart, he seems less than thrilled with the crew for leaving him in his current state." + base_icon = "rustbot" + icon_state = "rustbot0" + declare_arrests = 0 + arrest_type = 1 + harmbaton = 1 + emagged = 2 + +/mob/living/simple_animal/bot/secbot/New() + ..() + icon_state = "[base_icon][on]" + spawn(3) + var/datum/job/detective/J = new/datum/job/detective + access_card.access += J.get_access() + prev_access = access_card.access + + //SECHUD + var/datum/atom_hud/secsensor = huds[DATA_HUD_SECURITY_ADVANCED] + secsensor.add_hud_to(src) + permanent_huds |= secsensor + +/mob/living/simple_animal/bot/secbot/turn_on() + ..() + icon_state = "[base_icon][on]" + +/mob/living/simple_animal/bot/secbot/turn_off() + ..() + icon_state = "[base_icon][on]" + +/mob/living/simple_animal/bot/secbot/bot_reset() + ..() + target = null + oldtarget_name = null + anchored = 0 + walk_to(src,0) + last_found = world.time + +/mob/living/simple_animal/bot/secbot/set_custom_texts() + + text_hack = "You overload [name]'s target identification system." + text_dehack = "You reboot [name] and restore the target identification." + text_dehack_fail = "[name] refuses to accept your authority!" + +/mob/living/simple_animal/bot/secbot/get_controls(mob/user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += text({" +Securitron v1.6 controls

                +Status: []
                +Behaviour controls are [locked ? "locked" : "unlocked"]
                +Maintenance panel panel is [open ? "opened" : "closed"]"}, + +"[on ? "On" : "Off"]" ) + + if(!locked || issilicon(user) || check_rights(R_ADMIN, 0, user)) + dat += text({"
                +Arrest Unidentifiable Persons: []
                +Arrest for Unauthorized Weapons: []
                +Arrest for Warrant: []
                +Operating Mode: []
                +Report Arrests[]
                +Auto Patrol: []"}, + +"[idcheck ? "Yes" : "No"]", +"[weaponscheck ? "Yes" : "No"]", +"[check_records ? "Yes" : "No"]", +"[arrest_type ? "Detain" : "Arrest"]", +"[declare_arrests ? "Yes" : "No"]", +"[auto_patrol ? "On" : "Off"]" ) + + return dat + +/mob/living/simple_animal/bot/secbot/Topic(href, href_list) + if(..()) + return 1 + + switch(href_list["operation"]) + if("idcheck") + idcheck = !idcheck + update_controls() + if("weaponscheck") + weaponscheck = !weaponscheck + update_controls() + if("ignorerec") + check_records = !check_records + update_controls() + if("switchmode") + arrest_type = !arrest_type + update_controls() + if("declarearrests") + declare_arrests = !declare_arrests + update_controls() + +/mob/living/simple_animal/bot/secbot/proc/retaliate(mob/living/carbon/human/H) + threatlevel = H.assess_threat(src) + threatlevel += 6 + if(threatlevel >= 4) + target = H + mode = BOT_HUNT + +/mob/living/simple_animal/bot/secbot/attack_hand(mob/living/carbon/human/H) + if(H.a_intent == "harm") + retaliate(H) + return ..() + +/mob/living/simple_animal/bot/secbot/attackby(obj/item/weapon/W, mob/user, params) + ..() + if(istype(W, /obj/item/weapon/weldingtool) && user.a_intent != "harm") // Any intent but harm will heal, so we shouldn't get angry. + return + if(!istype(W, /obj/item/weapon/screwdriver) && (W.force) && (!target) && (W.damtype != STAMINA) ) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. + retaliate(user) + +/mob/living/simple_animal/bot/secbot/emag_act(mob/user) + ..() + if(emagged == 2) + if(user) + user << "You short out [src]'s target assessment circuits." + oldtarget_name = user.name + audible_message("[src] buzzes oddly!") + declare_arrests = 0 + icon_state = "[base_icon][on]" + +/mob/living/simple_animal/bot/secbot/bullet_act(obj/item/projectile/Proj) + if(istype(Proj ,/obj/item/projectile/beam)||istype(Proj,/obj/item/projectile/bullet)) + if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) + if(!Proj.nodamage && Proj.damage < src.health) + retaliate(Proj.firer) + ..() + + +/mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/A) + if(!on) + return + if(iscarbon(A)) + var/mob/living/carbon/C = A + if(!C.stunned || arrest_type) + stun_attack(A) + else if(C.canBeHandcuffed() && !C.handcuffed) + cuff(A) + else + ..() + + +/mob/living/simple_animal/bot/secbot/hitby(atom/movable/AM, skipcatch = 0, hitpush = 1, blocked = 0) + if(istype(AM, /obj/item)) + var/obj/item/I = AM + if(I.throwforce < src.health && I.thrower && (istype(I.thrower, /mob/living/carbon/human))) + var/mob/living/carbon/human/H = I.thrower + retaliate(H) + ..() + + +/mob/living/simple_animal/bot/secbot/proc/cuff(mob/living/carbon/C) + mode = BOT_ARREST + playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2) + C.visible_message("[src] is trying to put zipties on [C]!",\ + "[src] is trying to put zipties on you!") + spawn(60) + if( !Adjacent(C) || !isturf(C.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. + return + if(!C.handcuffed) + C.handcuffed = new /obj/item/weapon/restraints/handcuffs/cable/zipties/used(C) + C.update_inv_handcuffed(1) + playsound(loc, pick('sound/voice/bgod.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bsecureday.ogg', 'sound/voice/bradio.ogg', 'sound/voice/binsult.ogg', 'sound/voice/bcreep.ogg'), 50, 0) + back_to_idle() + +/mob/living/simple_animal/bot/secbot/proc/stun_attack(mob/living/carbon/C) + playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) + if(harmbaton) + playsound(loc, 'sound/weapons/genhit1.ogg', 50, 1, -1) + icon_state = "[base_icon]-c" + spawn(2) + icon_state = "[base_icon][on]" + var/threat = 5 + if(istype(C, /mob/living/carbon/human)) + C.stuttering = 5 + if(harmbaton) // Bots with harmbaton enabled become shitcurity. - Dave + C.apply_damage(10, BRUTE) + C.Stun(5) + C.Weaken(5) + var/mob/living/carbon/human/H = C + threat = H.assess_threat(src) + else + C.Weaken(5) + C.stuttering = 5 + C.Stun(5) + add_logs(src,C,"stunned") + if(declare_arrests) + var/area/location = get_area(src) + speak("[arrest_type ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel) + C.visible_message("[src] has [harmbaton ? "beaten" : "stunned"] [C]!",\ + "[src] has [harmbaton ? "beaten" : "stunned"] you!") + +/mob/living/simple_animal/bot/secbot/Life() + . = ..() + if(flashing_lights) + switch(light_color) + if(LIGHT_COLOR_PURE_RED) + light_color = LIGHT_COLOR_PURE_BLUE + if(LIGHT_COLOR_PURE_BLUE) + light_color = LIGHT_COLOR_PURE_RED + update_light() + else if(prev_flashing_lights) + light_color = LIGHT_COLOR_PURE_RED + update_light() + + prev_flashing_lights = flashing_lights + +/mob/living/simple_animal/bot/secbot/verb/toggle_flashing_lights() + set name = "Toggle Flashing Lights" + set category = "Object" + set src = usr + + flashing_lights = !flashing_lights + +/mob/living/simple_animal/bot/secbot/handle_automated_action() + if(!..()) + return + + flashing_lights = mode == BOT_HUNT + + switch(mode) + if(BOT_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 = BOT_START_PATROL // switch to patrol mode + + if(BOT_HUNT) // hunting for perp + // if can't reach perp for long enough, go idle + if(frustration >= 8) + walk_to(src,0) + back_to_idle() + return + + if(target) // make sure target exists + if(Adjacent(target) && isturf(target.loc)) // if right next to perp + stun_attack(target) + + mode = BOT_PREP_ARREST + anchored = 1 + target_lastloc = target.loc + return + + else // not next to perp + var/turf/olddist = get_dist(src, target) + walk_to(src, target,1,4) + if((get_dist(src, target)) >= (olddist)) + frustration++ + else + frustration = 0 + else + back_to_idle() + + if(BOT_PREP_ARREST) // preparing to arrest target + // see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again. + if( !Adjacent(target) || !isturf(target.loc) || target.weakened < 2 ) + back_to_hunt() + return + + if(iscarbon(target) && target.canBeHandcuffed()) + if(!arrest_type) + if(!target.handcuffed) //he's not cuffed? Try to cuff him! + cuff(target) + else + back_to_idle() + return + else + back_to_idle() + return + + if(BOT_ARREST) + if(!target) + anchored = 0 + mode = BOT_IDLE + last_found = world.time + frustration = 0 + return + + if(target.handcuffed) //no target or target cuffed? back to idle. + back_to_idle() + return + + if(!Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && target.weakened < 2)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. + back_to_hunt() + return + else //Try arresting again if the target escapes. + mode = BOT_PREP_ARREST + anchored = 0 + + if(BOT_START_PATROL) + look_for_perp() + start_patrol() + + if(BOT_PATROL) + look_for_perp() + bot_patrol() + + + return + +/mob/living/simple_animal/bot/secbot/proc/back_to_idle() + anchored = 0 + mode = BOT_IDLE + target = null + last_found = world.time + frustration = 0 + spawn(0) + handle_automated_action() //ensure bot quickly responds + +/mob/living/simple_animal/bot/secbot/proc/back_to_hunt() + anchored = 0 + frustration = 0 + mode = BOT_HUNT + spawn(0) + handle_automated_action() //ensure bot quickly responds +// look for a criminal in view of the bot + +/mob/living/simple_animal/bot/secbot/proc/look_for_perp() + 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 == oldtarget_name) && (world.time < last_found + 100)) + continue + + threatlevel = C.assess_threat(src) + + if(!threatlevel) + continue + + else if(threatlevel >= 4) + target = C + oldtarget_name = C.name + speak("Level [threatlevel] infraction alert!") + playsound(loc, pick('sound/voice/bcriminal.ogg', 'sound/voice/bjustice.ogg', 'sound/voice/bfreeze.ogg'), 50, 0) + visible_message("[src] points at [C.name]!") + mode = BOT_HUNT + spawn(0) + handle_automated_action() // ensure bot quickly responds to a perp + break + else + continue +/mob/living/simple_animal/bot/secbot/proc/check_for_weapons(var/obj/item/slot_item) + if(slot_item && slot_item.needs_permit) + return 1 + return 0 + +/mob/living/simple_animal/bot/secbot/explode() + + walk_to(src,0) + visible_message("[src] blows apart!") + 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 += "hs_hole" + Sa.created_name = name + new /obj/item/device/assembly/prox_sensor(Tsec) + new /obj/item/weapon/melee/baton(Tsec) + + if(prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + + new /obj/effect/decal/cleanable/blood/oil(loc) + ..() + +/mob/living/simple_animal/bot/secbot/attack_alien(var/mob/living/carbon/alien/user as mob) + ..() + if(!isalien(target)) + target = user + mode = BOT_HUNT + +/mob/living/simple_animal/bot/secbot/Crossed(atom/movable/AM) + if(ismob(AM) && target) + var/mob/living/carbon/C = AM + if(!istype(C) || !C || in_range(src, target)) + return + C.visible_message("[pick( \ + "[C] dives out of [src]'s way!", \ + "[C] stumbles over [src]!", \ + "[C] jumps out of [src]'s path!", \ + "[C] trips over [src] and falls!", \ + "[C] topples over [src]!", \ + "[C] leaps out of [src]'s way!")]") + C.Weaken(2) + return + ..() + +/obj/machinery/bot_core/secbot + req_access = list(access_security) diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index da3283355f3..989bbf09508 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -14,14 +14,7 @@ stop_automated_movement = 1 status_flags = CANPUSH attack_sound = 'sound/weapons/punch1.ogg' - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 faction = list("cult") flying = 1 @@ -110,7 +103,7 @@ /mob/living/simple_animal/construct/armoured/Life() weakened = 0 - ..() + return ..() /mob/living/simple_animal/construct/armoured/bullet_act(var/obj/item/projectile/P) if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) diff --git a/code/modules/mob/living/simple_animal/friendly/corgi.dm b/code/modules/mob/living/simple_animal/friendly/corgi.dm index 2b53e124796..ceb4779fcd8 100644 --- a/code/modules/mob/living/simple_animal/friendly/corgi.dm +++ b/code/modules/mob/living/simple_animal/friendly/corgi.dm @@ -399,11 +399,11 @@ response_disarm = "bops" response_harm = "kicks" -/mob/living/simple_animal/pet/corgi/Ian/Life() +/mob/living/simple_animal/pet/corgi/Ian/process_ai() ..() //Feeding, chasing food, FOOOOODDDD - if(!stat && !resting && !buckled && (ckey == null)) + if(!resting && !buckled) turns_since_scan++ if(turns_since_scan > 5) turns_since_scan = 0 @@ -450,10 +450,9 @@ if(prob(1)) custom_emote(1, pick("dances around.","chases its tail!")) spawn(0) - if (ckey == null) - for(var/i in list(1,2,4,8,4,2,1,2,4,8,4,2,1,2,4,8,4,2)) - dir = i - sleep(1) + for(var/i in list(1,2,4,8,4,2,1,2,4,8,4,2,1,2,4,8,4,2)) + dir = i + sleep(1) /obj/item/weapon/reagent_containers/food/snacks/meat/corgi name = "Corgi meat" @@ -563,20 +562,18 @@ return ..() -/mob/living/simple_animal/pet/corgi/Lisa/Life() +/mob/living/simple_animal/pet/corgi/Lisa/process_ai() ..() make_babies() - if(!stat && !resting && !buckled) + if(!resting && !buckled) if(prob(1)) - if (ckey == null) - custom_emote(1, pick("dances around.","chases her tail.")) - spawn(0) - for(var/i in list(1,2,4,8,4,2,1,2,4,8,4,2,1,2,4,8,4,2)) - dir = i - sleep(1) - + custom_emote(1, pick("dances around.","chases her tail.")) + spawn(0) + for(var/i in list(1,2,4,8,4,2,1,2,4,8,4,2,1,2,4,8,4,2)) + dir = i + sleep(1) /mob/living/simple_animal/pet/corgi/attack_hand(mob/living/carbon/human/M) . = ..() diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index 03fbb30ed06..de74b4a1af4 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -25,7 +25,7 @@ pass_flags = PASSTABLE | PASSGRILLE | PASSMOB var/mouse_color //brown, gray and white, leave blank for random layer = MOB_LAYER - min_oxy = 16 //Require atleast 16kPA oxygen + atmos_requirements = list("min_oxy" = 16, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) minbodytemp = 223 //Below -50 Degrees Celcius maxbodytemp = 323 //Above 50 Degrees Celcius universal_speak = 0 @@ -41,14 +41,7 @@ /mob/living/simple_animal/mouse/Life() . = ..() - - if(!ckey && stat == CONSCIOUS && prob(0.5)) - stat = UNCONSCIOUS - icon_state = "mouse_[mouse_color]_sleep" - wander = 0 - speak_chance = 0 - //snuffles - else if(stat == UNCONSCIOUS) + if(stat == UNCONSCIOUS) if(ckey || prob(1)) stat = CONSCIOUS icon_state = "mouse_[mouse_color]" @@ -56,6 +49,15 @@ else if(prob(5)) emote("snuffles") +/mob/living/simple_animal/mouse/process_ai() + ..() + + if(prob(0.5)) + stat = UNCONSCIOUS + icon_state = "mouse_[mouse_color]_sleep" + wander = 0 + speak_chance = 0 + /mob/living/simple_animal/mouse/New() ..() if(!mouse_color) diff --git a/code/modules/mob/living/simple_animal/friendly/pug.dm b/code/modules/mob/living/simple_animal/friendly/pug.dm index a819602719a..890a451e63a 100644 --- a/code/modules/mob/living/simple_animal/friendly/pug.dm +++ b/code/modules/mob/living/simple_animal/friendly/pug.dm @@ -19,10 +19,10 @@ response_harm = "kicks" see_in_dark = 5 -/mob/living/simple_animal/pet/pug/Life() +/mob/living/simple_animal/pet/pug/process_ai() ..() - if(!stat && !resting && !buckled) + if(!resting && !buckled) if(prob(1)) custom_emote(1, pick("chases its tail.")) spawn(0) diff --git a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm index 35f06c2b1c2..491f56f74a7 100644 --- a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm +++ b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm @@ -1,8 +1,6 @@ /mob/living/simple_animal/spiderbot - min_oxy = 0 - max_tox = 0 - max_co2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 maxbodytemp = 500 diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm index 336bc7f9906..b6309dd15ff 100644 --- a/code/modules/mob/living/simple_animal/hostile/alien.dm +++ b/code/modules/mob/living/simple_animal/hostile/alien.dm @@ -21,14 +21,7 @@ speak_emote = list("hisses") a_intent = I_HARM attack_sound = 'sound/weapons/bladeslice.ogg' - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) var/dead = 0 unsuitable_atmos_damage = 15 faction = list("alien") @@ -50,14 +43,16 @@ var/plant_cooldown = 30 var/plants_off = 0 -/mob/living/simple_animal/hostile/alien/drone/Life() - ..() - if(!stat) - plant_cooldown-- - if(stance==HOSTILE_STANCE_IDLE) - if(!plants_off && prob(10) && plant_cooldown<=0) - plant_cooldown = initial(plant_cooldown) - SpreadPlants() +/mob/living/simple_animal/hostile/alien/drone/process_ai() + . = ..() + if(!.) + return + + plant_cooldown-- + if(stance==HOSTILE_STANCE_IDLE) + if(!plants_off && prob(10) && plant_cooldown<=0) + plant_cooldown = initial(plant_cooldown) + SpreadPlants() /mob/living/simple_animal/hostile/alien/sentinel name = "alien sentinel" @@ -95,18 +90,18 @@ var/egg_cooldown = 30 var/plant_cooldown = 30 -/mob/living/simple_animal/hostile/alien/queen/Life() +/mob/living/simple_animal/hostile/alien/queen/process_ai() ..() - if(!stat) - egg_cooldown-- - plant_cooldown-- - if(stance==HOSTILE_STANCE_IDLE) - if(!plants_off && prob(10) && plant_cooldown<=0) - plant_cooldown = initial(plant_cooldown) - SpreadPlants() - if(!sterile && prob(10) && egg_cooldown<=0) - egg_cooldown = initial(egg_cooldown) - LayEggs() + + egg_cooldown-- + plant_cooldown-- + if(stance==HOSTILE_STANCE_IDLE) + if(!plants_off && prob(10) && plant_cooldown<=0) + plant_cooldown = initial(plant_cooldown) + SpreadPlants() + if(!sterile && prob(10) && egg_cooldown<=0) + egg_cooldown = initial(egg_cooldown) + LayEggs() /mob/living/simple_animal/hostile/alien/proc/SpreadPlants() if(!isturf(loc) || istype(loc, /turf/space)) diff --git a/code/modules/mob/living/simple_animal/hostile/bat.dm b/code/modules/mob/living/simple_animal/hostile/bat.dm index 0c2174d131e..ba8e79c582a 100644 --- a/code/modules/mob/living/simple_animal/hostile/bat.dm +++ b/code/modules/mob/living/simple_animal/hostile/bat.dm @@ -23,15 +23,7 @@ attacktext = "bites" attack_sound = 'sound/weapons/bite.ogg' - //Space carp aren't affected by atmos. - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 // break_stuff_probability = 2 diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm index 9c6f24bd4ac..0577834163a 100644 --- a/code/modules/mob/living/simple_animal/hostile/bear.dm +++ b/code/modules/mob/living/simple_animal/hostile/bear.dm @@ -27,14 +27,7 @@ attack_sound = 'sound/weapons/genhit3.ogg' //Space bears aren't affected by atmos. - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 var/stance_step = 0 @@ -56,12 +49,11 @@ else icon_state = "bearfloor" -/mob/living/simple_animal/hostile/bear/Life() +/mob/living/simple_animal/hostile/bear/process_ai() . = ..() if(!.) return - switch(stance) if(HOSTILE_STANCE_TIRED) diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm index 650f19cb758..2dbab5b6db5 100644 --- a/code/modules/mob/living/simple_animal/hostile/carp.dm +++ b/code/modules/mob/living/simple_animal/hostile/carp.dm @@ -26,14 +26,7 @@ speak_emote = list("gnashes") //Space carp aren't affected by atmos. - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 maxbodytemp = 1500 diff --git a/code/modules/mob/living/simple_animal/hostile/faithless.dm b/code/modules/mob/living/simple_animal/hostile/faithless.dm index bbcacd234fe..380b332a7e4 100644 --- a/code/modules/mob/living/simple_animal/hostile/faithless.dm +++ b/code/modules/mob/living/simple_animal/hostile/faithless.dm @@ -20,14 +20,7 @@ attack_sound = 'sound/hallucinations/growl1.ogg' speak_emote = list("growls") - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 speed = 4 diff --git a/code/modules/mob/living/simple_animal/hostile/hivebot.dm b/code/modules/mob/living/simple_animal/hostile/hivebot.dm index 68aeeb72616..44f5caa1609 100644 --- a/code/modules/mob/living/simple_animal/hostile/hivebot.dm +++ b/code/modules/mob/living/simple_animal/hostile/hivebot.dm @@ -18,14 +18,7 @@ projectilesound = 'sound/weapons/Gunshot.ogg' projectiletype = /obj/item/projectile/hivebotbullet faction = list("hivebot") - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 speak_emote = list("states") @@ -79,37 +72,36 @@ warpbots() - New() - ..() - var/datum/effect/system/harmless_smoke_spread/smoke = new /datum/effect/system/harmless_smoke_spread() - smoke.set_up(5, 0, src.loc) - smoke.start() - visible_message("\red The [src] warps in!") - playsound(src.loc, 'sound/effects/EMPulse.ogg', 25, 1) +/mob/living/simple_animal/hostile/hivebot/tele/New() + ..() + var/datum/effect/system/harmless_smoke_spread/smoke = new /datum/effect/system/harmless_smoke_spread() + smoke.set_up(5, 0, src.loc) + smoke.start() + visible_message("\red The [src] warps in!") + playsound(src.loc, 'sound/effects/EMPulse.ogg', 25, 1) - warpbots() - icon_state = "def_radar" - visible_message("\red The [src] turns on!") - while(bot_amt > 0) - bot_amt-- - switch(bot_type) - if("norm") - var/mob/living/simple_animal/hostile/hivebot/H = new /mob/living/simple_animal/hostile/hivebot(get_turf(src)) - H.faction = faction - if("range") - var/mob/living/simple_animal/hostile/hivebot/range/R = new /mob/living/simple_animal/hostile/hivebot/range(get_turf(src)) - R.faction = faction - if("rapid") - var/mob/living/simple_animal/hostile/hivebot/rapid/F = new /mob/living/simple_animal/hostile/hivebot/rapid(get_turf(src)) - F.faction = faction - spawn(100) - qdel(src) +/mob/living/simple_animal/hostile/hivebot/tele/warpbots() + icon_state = "def_radar" + visible_message("\red The [src] turns on!") + while(bot_amt > 0) + bot_amt-- + switch(bot_type) + if("norm") + var/mob/living/simple_animal/hostile/hivebot/H = new /mob/living/simple_animal/hostile/hivebot(get_turf(src)) + H.faction = faction + if("range") + var/mob/living/simple_animal/hostile/hivebot/range/R = new /mob/living/simple_animal/hostile/hivebot/range(get_turf(src)) + R.faction = faction + if("rapid") + var/mob/living/simple_animal/hostile/hivebot/rapid/F = new /mob/living/simple_animal/hostile/hivebot/rapid(get_turf(src)) + F.faction = faction + spawn(100) + qdel(src) + return + +/mob/living/simple_animal/hostile/hivebot/tele/process_ai() + . = ..() + if(!.) return - - - Life() - ..() - if(stat == 0) - if(prob(2))//Might be a bit low, will mess with it likely - warpbots() - + if(prob(2))//Might be a bit low, will mess with it likely + warpbots() \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index c5561a2f928..00ca1d65e82 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -32,33 +32,34 @@ var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_SLEEP (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever) /mob/living/simple_animal/hostile/Life() - . = ..() if(!.) walk(src, 0) return 0 if(ranged) ranged_cooldown-- - if(client) - return 0 + +/mob/living/simple_animal/hostile/process_ai() + ..() if(!AICanContinue()) return 0 - if(!stat) - switch(stance) - if(HOSTILE_STANCE_IDLE) - var/new_target = FindTarget() - GiveTarget(new_target) + switch(stance) + if(HOSTILE_STANCE_IDLE) + var/new_target = FindTarget() + GiveTarget(new_target) - if(HOSTILE_STANCE_ATTACK) - MoveToTarget() - DestroySurroundings() + if(HOSTILE_STANCE_ATTACK) + MoveToTarget() + DestroySurroundings() - if(HOSTILE_STANCE_ATTACKING) - AttackTarget() - DestroySurroundings() + if(HOSTILE_STANCE_ATTACKING) + AttackTarget() + DestroySurroundings() - if(AIShouldSleep()) - AIStatus = AI_SLEEP + if(AIShouldSleep()) + AIStatus = AI_SLEEP + + return 1 //////////////HOSTILE MOB TARGETTING AND AGGRESSION//////////// diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index 9d70385cf87..2f077249527 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -23,14 +23,7 @@ attack_sound = 'sound/weapons/bite.ogg' speak_emote = list("creaks") - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 faction = list("mimic") diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm index ab26322d958..bb5fcb8b23e 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm @@ -1,13 +1,6 @@ /mob/living/simple_animal/hostile/asteroid/ vision_range = 2 - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) unsuitable_atmos_damage = 15 faction = list("mining") environment_smash = 2 @@ -387,7 +380,7 @@ anchored = 1 //Stays anchored until death as to be unpullable var/pre_attack = 0 -/mob/living/simple_animal/hostile/asteroid/goliath/Life() +/mob/living/simple_animal/hostile/asteroid/goliath/process_ai() ..() handle_preattack() diff --git a/code/modules/mob/living/simple_animal/hostile/pirate.dm b/code/modules/mob/living/simple_animal/hostile/pirate.dm index 4f3cd78c093..fbbc0d0a5e3 100644 --- a/code/modules/mob/living/simple_animal/hostile/pirate.dm +++ b/code/modules/mob/living/simple_animal/hostile/pirate.dm @@ -19,14 +19,7 @@ attacktext = "slashes" attack_sound = 'sound/weapons/bladeslice.ogg' - min_oxy = 5 - max_oxy = 0 - min_tox = 0 - max_tox = 1 - min_co2 = 0 - max_co2 = 5 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) unsuitable_atmos_damage = 15 speak_emote = list("yarrs") var/corpse = /obj/effect/landmark/mobcorpse/pirate diff --git a/code/modules/mob/living/simple_animal/hostile/poison_bees.dm b/code/modules/mob/living/simple_animal/hostile/poison_bees.dm index dd5a7f35822..b05ba13ccab 100644 --- a/code/modules/mob/living/simple_animal/hostile/poison_bees.dm +++ b/code/modules/mob/living/simple_animal/hostile/poison_bees.dm @@ -22,14 +22,7 @@ flying = 1 //Spaceborn beings don't get hurt by space - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 /mob/living/simple_animal/hostile/poison/bees/Process_Spacemove(var/check_drift = 0) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm index f4f0db898bb..cce4b32235c 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm @@ -23,15 +23,6 @@ attacktext = "attacks" attack_sound = 'sound/items/bikehorn.ogg' environment_smash = 0 - - min_oxy = 5 - max_oxy = 0 - min_tox = 0 - max_tox = 1 - min_co2 = 0 - max_co2 = 5 - min_n2 = 0 - max_n2 = 0 minbodytemp = 270 maxbodytemp = 370 heat_damage_per_tick = 15 //amount of damage applied if animal's body temperature is higher than maxbodytemp diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm index 6c4986541a6..5ee52bec825 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm @@ -35,14 +35,7 @@ var/exploding = 0 //Drones aren't affected by atmos. - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 var/has_loot = 1 diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/pet.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/pet.dm index 17d764b9c1d..5a0798874e4 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/pet.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/pet.dm @@ -17,7 +17,4 @@ melee_damage_lower = 15 melee_damage_upper = 20 - min_oxy = 5 - max_tox = 2 - max_co2 = 5 - + atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 2, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm index abfcad0e403..44de372550f 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm @@ -41,14 +41,7 @@ attacktext = "grips" attack_sound = 'sound/hallucinations/growl1.ogg' - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 faction = list("undead") // did I mention ghost @@ -93,14 +86,7 @@ attacktext = "claws" attack_sound = 'sound/hallucinations/growl1.ogg' - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 faction = list("undead") @@ -131,14 +117,7 @@ attacktext = "claws" attack_sound = 'sound/hallucinations/growl1.ogg' - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 faction = list("undead") diff --git a/code/modules/mob/living/simple_animal/hostile/russian.dm b/code/modules/mob/living/simple_animal/hostile/russian.dm index 1c32975d5e8..35cdbb14dfb 100644 --- a/code/modules/mob/living/simple_animal/hostile/russian.dm +++ b/code/modules/mob/living/simple_animal/hostile/russian.dm @@ -21,14 +21,6 @@ a_intent = I_HARM var/corpse = /obj/effect/landmark/mobcorpse/russian var/weapon1 = /obj/item/weapon/kitchen/knife - min_oxy = 5 - max_oxy = 0 - min_tox = 0 - max_tox = 1 - min_co2 = 0 - max_co2 = 5 - min_n2 = 0 - max_n2 = 0 unsuitable_atmos_damage = 15 faction = list("russian") status_flags = CANPUSH diff --git a/code/modules/mob/living/simple_animal/hostile/spaceworms.dm b/code/modules/mob/living/simple_animal/hostile/spaceworms.dm index f07b2ed1794..b69e7c242db 100644 --- a/code/modules/mob/living/simple_animal/hostile/spaceworms.dm +++ b/code/modules/mob/living/simple_animal/hostile/spaceworms.dm @@ -26,9 +26,7 @@ minbodytemp = 0 maxbodytemp = 350 - min_oxy = 0 - max_co2 = 0 - max_tox = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) a_intent = I_HARM //so they don't get pushed around diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm index a466d62306b..8ca393bb2d1 100644 --- a/code/modules/mob/living/simple_animal/hostile/statue.dm +++ b/code/modules/mob/living/simple_animal/hostile/statue.dm @@ -23,14 +23,7 @@ attacktext = "claws" attack_sound = 'sound/hallucinations/growl1.ogg' - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 faction = list("statue") @@ -76,12 +69,12 @@ icon_state = "angel" return ..() -/mob/living/simple_animal/hostile/statue/Life() - if(..()) - if(!ckey) - spawn() //for saftey - checkTargets() - . = 1 +/mob/living/simple_animal/hostile/statue/process_ai() + . = ..() + if(!.) + return + spawn() //for saftey + checkTargets() /mob/living/simple_animal/hostile/statue/proc/checkTargets() if(target) @@ -195,6 +188,9 @@ . = ..() return . - creator +/mob/living/simple_animal/hostile/statue/sentience_act() + faction -= "neutral" + // Statue powers // Flicker lights diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm index b81a9161686..434d1739b9e 100644 --- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm +++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm @@ -22,14 +22,6 @@ var/corpse = /obj/effect/landmark/mobcorpse/syndicatesoldier var/weapon1 var/weapon2 - min_oxy = 5 - max_oxy = 0 - min_tox = 0 - max_tox = 1 - min_co2 = 0 - max_co2 = 5 - min_n2 = 0 - max_n2 = 0 unsuitable_atmos_damage = 15 faction = list("syndicate") status_flags = CANPUSH @@ -85,14 +77,7 @@ /mob/living/simple_animal/hostile/syndicate/melee/space - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 icon_state = "syndicatemeleespace" icon_living = "syndicatemeleespace" @@ -120,14 +105,7 @@ icon_state = "syndicaterangedpsace" icon_living = "syndicaterangedpsace" name = "Syndicate Commando" - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 corpse = /obj/effect/landmark/mobcorpse/syndicatecommando speed = 1 @@ -151,14 +129,7 @@ attacktext = "cuts" attack_sound = 'sound/weapons/bladeslice.ogg' faction = list("syndicate") - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 flying = 1 diff --git a/code/modules/mob/living/simple_animal/hostile/tree.dm b/code/modules/mob/living/simple_animal/hostile/tree.dm index 181ddaa144e..555386debcb 100644 --- a/code/modules/mob/living/simple_animal/hostile/tree.dm +++ b/code/modules/mob/living/simple_animal/hostile/tree.dm @@ -24,15 +24,7 @@ attack_sound = 'sound/weapons/bite.ogg' speak_emote = list("pines") - //Space carp aren't affected by atmos. - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 faction = list("hostile", "winter") diff --git a/code/modules/mob/living/simple_animal/hostile/winter_mobs.dm b/code/modules/mob/living/simple_animal/hostile/winter_mobs.dm index 8ca70430d44..82ca3994eb6 100644 --- a/code/modules/mob/living/simple_animal/hostile/winter_mobs.dm +++ b/code/modules/mob/living/simple_animal/hostile/winter_mobs.dm @@ -14,14 +14,7 @@ icon_living = "placeholder" icon_dead = "placeholder" - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 melee_damage_lower = 3 diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 1732caa55f3..92fdebf45dd 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -53,12 +53,12 @@ universal_speak = 1 var/parrot_state = PARROT_WANDER //Hunt for a perch when created - var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in life() being run every single tick. + var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in process_ai() being run every single tick. var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down var/parrot_dam_zone = list("chest", "head", "l_arm", "l_leg", "r_arm", "r_leg") //For humans, select a bodypart to attack var/parrot_speed = 5 //"Delay in world ticks between movement." according to byond. Yeah, that's BS but it does directly affect movement. Higher number = slower. - var/parrot_been_shot = 0 //Parrots get a speed bonus after being shot. This will deincrement every Life() and at 0 the parrot will return to regular speed. + var/parrot_been_shot = 0 //Parrots get a speed bonus after being shot. This will deincrement every process_ai() and at 0 the parrot will return to regular speed. var/list/speech_buffer = list() var/list/available_channels = list() @@ -273,14 +273,13 @@ //Sprite and AI update for when a parrot gets pulled if(pulledby && stat == CONSCIOUS) - icon_state = "parrot_fly" - if(!client) - parrot_state = PARROT_WANDER + icon_state = "parrot_fly" + +/mob/living/simple_animal/parrot/process_ai() + if(pulledby) + parrot_state = PARROT_WANDER return - - if(client || stat) - return //Lets not force players or dead/incap parrots to move - + if(!isturf(src.loc) || !canmove || buckled) return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove) diff --git a/code/modules/mob/living/simple_animal/pony.dm b/code/modules/mob/living/simple_animal/pony.dm index 85d8031f8a6..db3d06bc3f3 100644 --- a/code/modules/mob/living/simple_animal/pony.dm +++ b/code/modules/mob/living/simple_animal/pony.dm @@ -15,9 +15,7 @@ attacktext = "sends rainbows of power to" minbodytemp = 0 maxbodytemp = 4000 - min_oxy = 0 - max_co2 = 0 - max_tox = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) speed = 0 stop_automated_movement = 0 status_flags = 0 diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm index a006c94b11f..124cacc0a8f 100644 --- a/code/modules/mob/living/simple_animal/shade.dm +++ b/code/modules/mob/living/simple_animal/shade.dm @@ -18,9 +18,7 @@ attacktext = "drains the life from" minbodytemp = 0 maxbodytemp = 4000 - min_oxy = 0 - max_co2 = 0 - max_tox = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) speed = -1 stop_automated_movement = 1 status_flags = 0 diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 62a73504c40..91c7d3ef629 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -14,6 +14,7 @@ var/oxygen_alert = 0 var/toxins_alert = 0 var/fire_alert = 0 + var/healable = 1 var/list/speak = list() var/speak_chance = 0 @@ -43,14 +44,7 @@ var/cold_damage_per_tick = 2 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp //Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage - var/min_oxy = 5 - var/max_oxy = 0 //Leaving something at 0 means it's off - has no maximum - var/min_tox = 0 - var/max_tox = 1 - var/min_co2 = 0 - var/max_co2 = 5 - var/min_n2 = 0 - var/max_n2 = 0 + var/list/atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) //Leaving something at 0 means it's off - has no maximum var/unsuitable_atmos_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above @@ -58,7 +52,7 @@ var/melee_damage_lower = 0 var/melee_damage_upper = 0 var/melee_damage_type = BRUTE //Damage type of a simple mob's melee attack, should it do damage. - var/list/ignored_damage_types = list(BRUTE = 0, BURN = 0, TOX = 0, CLONE = 0, STAMINA = 1, OXY = 0) //Set 0 to receive that damage type, 1 to ignore + var/list/damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) // 1 for full damage , 0 for none , -1 for 1:1 heal from that source var/attacktext = "attacks" var/attack_sound = null var/friendly = "nuzzles" //If the mob does no damage with it's attack @@ -76,10 +70,12 @@ var/simplespecies //Sorry, no spider+corgi buttbabies. var/master_commander = null //holding var for determining who own/controls a sentient simple animal (for sentience potions). + var/sentience_type = SENTIENCE_ORGANIC // Sentience type, for slime potions /mob/living/simple_animal/New() ..() + simple_animal_list += src verbs -= /mob/verb/observe if(!can_hide) verbs -= /mob/living/simple_animal/verb/hide @@ -92,6 +88,7 @@ if(collar) collar.forceMove(loc) collar = null + simple_animal_list -= src return ..() /mob/living/simple_animal/Login() @@ -130,16 +127,14 @@ if(0) healths.icon_state = "health7" /mob/living/simple_animal/Life() - - if(..()) - if(!ckey) - handle_automated_movement() - handle_automated_action() - handle_automated_speech() - . = 1 - + . = ..() handle_state_icons() +/mob/living/simple_animal/proc/process_ai() + handle_automated_movement() + handle_automated_action() + handle_automated_speech() + /mob/living/simple_animal/proc/handle_state_icons() if(resting && icon_resting && stat != DEAD) icon_state = icon_resting @@ -218,32 +213,32 @@ var/n2 = environment.nitrogen var/co2 = environment.carbon_dioxide - if(min_oxy && oxy < min_oxy) + if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"]) atmos_suitable = 0 oxygen_alert = 1 - else if(max_oxy && oxy > max_oxy) + else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"]) atmos_suitable = 0 oxygen_alert = 1 else oxygen_alert = 0 - if(min_tox && tox < min_tox) + if(atmos_requirements["min_tox"] && tox < atmos_requirements["min_tox"]) atmos_suitable = 0 toxins_alert = 1 - else if(max_tox && tox > max_tox) + else if(atmos_requirements["max_tox"] && tox > atmos_requirements["max_tox"]) atmos_suitable = 0 toxins_alert = 1 else toxins_alert = 0 - if(min_n2 && n2 < min_n2) + if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"]) atmos_suitable = 0 - else if(max_n2 && n2 > max_n2) + else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"]) atmos_suitable = 0 - if(min_co2 && co2 < min_co2) + if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"]) atmos_suitable = 0 - else if(max_co2 && co2 > max_co2) + else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"]) atmos_suitable = 0 if(!atmos_suitable) @@ -414,7 +409,7 @@ return /mob/living/simple_animal/attackby(var/obj/item/O as obj, var/mob/living/user as mob) //Marker -Agouri - if(istype(O, /obj/item/stack/medical)) + if(istype(O, /obj/item/stack/medical) && healable) if(stat != DEAD) var/obj/item/stack/medical/MED = O if(health < maxHealth) @@ -510,21 +505,29 @@ if(3.0) adjustBruteLoss(30) -/mob/living/simple_animal/adjustBruteLoss(damage) - if(!ignored_damage_types[BRUTE]) - ..() +/mob/living/simple_animal/adjustBruteLoss(amount) + if(damage_coeff[BRUTE]) + return ..(amount * damage_coeff[BRUTE]) -/mob/living/simple_animal/adjustFireLoss(damage) - if(!ignored_damage_types[BURN]) - ..(damage) +/mob/living/simple_animal/adjustFireLoss(amount) + if(damage_coeff[BURN]) + return ..(amount * damage_coeff[BURN]) -/mob/living/simple_animal/adjustToxLoss(damage) - if(!ignored_damage_types[TOX]) - ..(damage) +/mob/living/simple_animal/adjustOxyLoss(amount) + if(damage_coeff[OXY]) + return ..(amount * damage_coeff[OXY]) -/mob/living/simple_animal/adjustCloneLoss(damage) - if(!ignored_damage_types[CLONE]) - ..(damage) +/mob/living/simple_animal/adjustToxLoss(amount) + if(damage_coeff[TOX]) + return ..(amount * damage_coeff[TOX]) + +/mob/living/simple_animal/adjustCloneLoss(amount) + if(damage_coeff[CLONE]) + return ..(amount * damage_coeff[CLONE]) + +/mob/living/simple_animal/adjustStaminaLoss(amount) + if(damage_coeff[STAMINA]) + return ..(amount*damage_coeff[STAMINA]) /mob/living/simple_animal/proc/CanAttack(var/atom/the_target) if(see_invisible < the_target.invisibility) @@ -544,11 +547,10 @@ return 1 /mob/living/simple_animal/proc/attack_threshold_check(damage, damagetype = BRUTE) - if(damage <= force_threshold || ignored_damage_types[damagetype]) + if(damage <= force_threshold || !damage_coeff[damagetype]) visible_message("[src] looks unharmed from the damage.") else - adjustBruteLoss(damage) - updatehealth() + apply_damage(damage, damagetype) /mob/living/simple_animal/update_fire() @@ -699,4 +701,7 @@ /mob/living/simple_animal/get_access() . = ..() if(collar) - . |= collar.GetAccess() \ No newline at end of file + . |= collar.GetAccess() + +/mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion + return \ No newline at end of file diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 3451da45d3f..c3c63fbe36b 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -749,16 +749,6 @@ var/list/slot_equipment_priority = list( \ namecounts[name] = 1 creatures[name] = O - if(istype(O, /obj/machinery/bot)) - var/name = "BOT: [O.name]" - if (names.Find(name)) - namecounts[name]++ - name = "[name] ([namecounts[name]])" - else - names.Add(name) - namecounts[name] = 1 - creatures[name] = O - for(var/mob/M in sortAtom(mob_list)) var/name = M.name @@ -861,9 +851,9 @@ var/list/slot_equipment_priority = list( \ ..() if(M != usr) return if(M.small) return // Stops pAI drones and small mobs (borers, parrots, crabs) from stripping people. --DZD + if(!M.can_strip) return if(usr == src) return if(!Adjacent(usr)) return - if(istype(M,/mob/living/silicon/ai)) return show_inv(usr) //this and stop_pulling really ought to be /mob/living procs diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 4f7327180c6..2ab7f27e041 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -87,6 +87,7 @@ var/lastpuke = 0 var/unacidable = 0 var/small = 0 + var/can_strip = 1 var/list/pinned = list() //List of things pinning this creature to walls (see living_defense.dm) var/list/embedded = list() //Embedded items, since simple mobs don't have organs. var/list/languages = list() // For speaking/listening. diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index cd574507d6f..834592eb9c5 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -94,6 +94,11 @@ proc/isembryo(A) return 1 return 0 +/proc/isbot(A) + if(istype(A, /mob/living/simple_animal/bot)) + return 1 + return 0 + /proc/isclown(A) if(istype(A, /mob/living/simple_animal/hostile/retaliate/clown)) return 1 diff --git a/code/modules/nano/interaction/default.dm b/code/modules/nano/interaction/default.dm index e0e7c3a98f6..ad70ec7f484 100644 --- a/code/modules/nano/interaction/default.dm +++ b/code/modules/nano/interaction/default.dm @@ -96,4 +96,10 @@ if(. != STATUS_CLOSE) . = min(., shared_living_nano_distance(src_object)) if(!IsAdvancedToolUser()) + . = STATUS_CLOSE + +/mob/living/simple_animal/bot/default_can_use_topic(var/src_object) + . = shared_nano_interaction(src_object) + if(. != STATUS_CLOSE) + if(!(src_object in contents)) . = STATUS_CLOSE \ No newline at end of file diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm index cddb25bd118..6b78745c97f 100644 --- a/code/modules/nano/nanoui.dm +++ b/code/modules/nano/nanoui.dm @@ -187,7 +187,7 @@ nanoui is used to open and update nano browser uis "showMap" = show_map, "mapZLevel" = map_z_level, "user" = list( - "name" = user.name, + "name" = sanitize(user.name), "fancy" = user.client.prefs.nanoui_fancy ), "window" = list( diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm index 58db368a489..9afb3e52764 100644 --- a/code/modules/pda/cart_apps.dm +++ b/code/modules/pda/cart_apps.dm @@ -96,7 +96,7 @@ /datum/data/pda/app/power/update_ui(mob/user as mob, list/data) update = PDA_APP_UPDATE_SLOW - + if (powmonitor && !isnull(powmonitor.powernet)) data["records"] = list( "powerconnected" = 1, @@ -226,7 +226,7 @@ beepskyData["botstatus"] = list("loca" = null, "mode" = -1) var/botsCount=0 if(SC.botlist && SC.botlist.len) - for(var/obj/machinery/bot/B in SC.botlist) + for(var/mob/living/simple_animal/bot/B in SC.botlist) botsCount++ if(B.loc) botsData[++botsData.len] = list("Name" = sanitize(B.name), "Location" = sanitize(B.loc.loc.name), "ref" = "\ref[B]") @@ -276,7 +276,7 @@ var/mulebotsCount=0 - for(var/obj/machinery/bot/B in QC.botlist) + for(var/mob/living/simple_animal/bot/B in QC.botlist) mulebotsCount++ if(B.loc) mulebotsData[++mulebotsData.len] = list("Name" = sanitize(B.name), "Location" = sanitize(B.loc.loc.name), "ref" = "\ref[B]") @@ -392,7 +392,7 @@ BucketData[++BucketData.len] = list("x" = 0, "y" = 0, dir=null, status = null) var/CbotData[0] - for(var/obj/machinery/bot/cleanbot/B in aibots) + for(var/mob/living/simple_animal/bot/cleanbot/B in simple_animal_list) var/turf/bl = get_turf(B) if(bl) if(bl.z != cl.z) diff --git a/code/modules/pda/radio.dm b/code/modules/pda/radio.dm index 02a5dc996e2..b343d6b2ce3 100644 --- a/code/modules/pda/radio.dm +++ b/code/modules/pda/radio.dm @@ -5,7 +5,7 @@ icon_state = "power_mod" var/obj/item/device/pda/hostpda = null var/list/botlist = null // list of bots - var/obj/machinery/bot/active // the active bot; if null, show bot list + var/mob/living/simple_animal/bot/active // the active bot; if null, show bot list var/list/botstatus // the status signal sent by the bot var/bot_type //The type of bot it is. var/bot_filter //Determines which radio filter to use. @@ -17,9 +17,9 @@ /obj/item/radio/integrated/New() ..() - if (istype(loc.loc, /obj/item/device/pda)) + if(istype(loc.loc, /obj/item/device/pda)) hostpda = loc.loc - if (bot_filter) + if(bot_filter) spawn(5) add_to_radio(bot_filter) @@ -51,31 +51,22 @@ frequency.post_signal(src, signal, filter = s_filter) /obj/item/radio/integrated/receive_signal(datum/signal/signal) - /*var/obj/item/device/pda/P = src.loc - - - world << "recvd:[P] : [signal.source]" - for(var/d in signal.data) - world << "- [d] = [signal.data[d]]" - */ - if (signal.data["type"] == bot_type) if(!botlist) botlist = new() - if(!(signal.source in botlist)) - botlist += signal.source + var/obj/machinery/bot_core/core = signal.source - if(active == signal.source) + if(istype(core) && !(core.owner in botlist)) + botlist += core.owner + + if(active == core.owner) var/list/b = signal.data botstatus = b.Copy() /obj/item/radio/integrated/Topic(href, href_list) ..() - var/obj/item/device/pda/PDA = src.hostpda - switch(href_list["op"]) - if("control") active = locate(href_list["bot"]) spawn(0) @@ -89,14 +80,14 @@ if("botlist") active = null - if("stop", "go") + if("stop", "go", "home") spawn(0) post_signal(control_freq, "command", href_list["op"], "active", active, s_filter = bot_filter) post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) if("summon") spawn(0) - post_signal(control_freq, "command", "summon", "active", active, "target", get_turf(PDA) , "useraccess", PDA.GetAccess(), s_filter = bot_filter) + post_signal(control_freq, "command", "summon", "active", active, "target", get_turf(hostpda), "useraccess", hostpda.GetAccess(), "user", usr, s_filter = bot_filter) post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) /obj/item/radio/integrated/proc/add_to_radio(bot_filter) //Master filter control for bots. Must be placed in the bot's local New() to support map spawned bots. @@ -121,101 +112,45 @@ bot_type = CLEAN_BOT /obj/item/radio/integrated/mule - //var/list/botlist = null // list of bots - //var/obj/machinery/bot/mulebot/active // the active bot; if null, show bot list - //var/list/botstatus // the status signal sent by the bot - var/list/beacons - - var/beacon_freq = 1400 - control_freq = 1447 - -// create a new QM cartridge, and register to receive bot control & beacon message -/obj/item/radio/integrated/mule/New() - ..() - spawn(5) - if(radio_controller) - 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", s_filter = RADIO_NAVBEACONS) - -/obj/item/radio/integrated/mule/Destroy() - if(radio_controller) - radio_controller.remove_object(src,beacon_freq) - return ..() - -// receive radio signals -// can detect bot status signals -// and beacon locations -// create/populate lists as they are recvd - -/obj/item/radio/integrated/mule/receive_signal(datum/signal/signal) - if(signal.data["type"] == MULE_BOT) - if(!botlist) - botlist = new() - - if(!(signal.source in botlist)) - botlist += signal.source - - if(active == signal.source) - var/list/b = signal.data - botstatus = b.Copy() - - else if(signal.data["beacon"]) - if(!beacons) - beacons = new() - - beacons[signal.data["beacon"] ] = signal.source + bot_filter = RADIO_MULEBOT + bot_type = MULE_BOT /obj/item/radio/integrated/mule/Topic(href, href_list) - //..() - //var/obj/item/device/pda/PDA = src.hostpda - var/cmd = "command" - if(active) - cmd = "command [active.suffix]" - + ..() switch(href_list["op"]) - if("control") - active = locate(href_list["bot"]) - - if("scanbots") // find all bots - botlist = null - - if("botlist") - active = null + if("start") + spawn(0) + post_signal(control_freq, "command", "start", "active", active, s_filter = RADIO_MULEBOT) if("unload") spawn(0) - post_signal(control_freq, cmd, "unload", s_filter = RADIO_MULEBOT) + post_signal(control_freq, "command", "unload", "active", active, s_filter = RADIO_MULEBOT) + if("setdest") - if(beacons) - var/dest = input("Select Bot Destination", "Mulebot [active.suffix] Interlink", active.destination) as null|anything in beacons + if(deliverybeacons) + var/dest = input("Select Bot Destination", "Mulebot [active.suffix] Interlink", active.destination) as null|anything in deliverybeacontags if(dest) spawn(0) - post_signal(control_freq, cmd, "target", "destination", dest, s_filter = RADIO_MULEBOT) + post_signal(control_freq, "command", "target", "active", active, "destination", dest, s_filter = RADIO_MULEBOT) if("retoff") spawn(0) - post_signal(control_freq, cmd, "autoret", "value", 0, s_filter = RADIO_MULEBOT) - + post_signal(control_freq, "command", "autoret", "active", active, "value", 0, s_filter = RADIO_MULEBOT) + if("reton") spawn(0) - post_signal(control_freq, cmd, "autoret", "value", 1, s_filter = RADIO_MULEBOT) + post_signal(control_freq, "command", "autoret", "active", active, "value", 1, s_filter = RADIO_MULEBOT) if("pickoff") spawn(0) - post_signal(control_freq, cmd, "autopick", "value", 0, s_filter = RADIO_MULEBOT) + post_signal(control_freq, "command", "autopick", "active", active, "value", 0, s_filter = RADIO_MULEBOT) if("pickon") spawn(0) - post_signal(control_freq, cmd, "autopick", "value", 1, s_filter = RADIO_MULEBOT) - - if("stop", "go", "home") - spawn(0) - post_signal(control_freq, cmd, href_list["op"], s_filter = RADIO_MULEBOT) + post_signal(control_freq, "command", "autopick", "active", active, "value", 1, s_filter = RADIO_MULEBOT) spawn(10) - post_signal(control_freq, cmd, "bot_status", s_filter = RADIO_MULEBOT) + post_signal(control_freq, "command", "bot_status", "active", active, s_filter = RADIO_MULEBOT) diff --git a/code/modules/reagents/reagent_containers/glass_containers.dm b/code/modules/reagents/reagent_containers/glass_containers.dm index b7c891370b0..536c50bc115 100644 --- a/code/modules/reagents/reagent_containers/glass_containers.dm +++ b/code/modules/reagents/reagent_containers/glass_containers.dm @@ -28,7 +28,7 @@ /obj/machinery/atmospherics/unary/cryo_cell, /obj/machinery/dna_scannernew, /obj/item/weapon/grenade/chem_grenade, - /obj/machinery/bot/medbot, + /mob/living/simple_animal/bot/medbot, /obj/item/weapon/storage/secure/safe, /obj/machinery/iv_drip, /obj/machinery/disease2/incubator, diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index ce990a7226b..aed517d98ce 100644 --- a/code/modules/shuttle/supply.dm +++ b/code/modules/shuttle/supply.dm @@ -780,7 +780,8 @@ var/list/mobs_can_pass = list( /mob/living/carbon/slime, /mob/living/simple_animal/mouse, - /mob/living/silicon/robot/drone + /mob/living/silicon/robot/drone, + /mob/living/simple_animal/bot/mulebot ) /obj/structure/plasticflaps/CanPass(atom/A, turf/T) @@ -814,6 +815,18 @@ return ..() + +/obj/structure/plasticflaps/CanAStarPass(ID, to_dir, caller) + if(istype(caller, /mob/living)) + for(var/mob_type in mobs_can_pass) + if(istype(caller, mob_type)) + return 1 + + var/mob/living/M = caller + if(!M.ventcrawler && !M.small) + return 0 + return 1 + /obj/structure/plasticflaps/ex_act(severity) switch(severity) if (1) diff --git a/icons/effects/fluidtracks.dmi b/icons/effects/fluidtracks.dmi index 0675d93d7f9b8abec04ba6190834fe96cacd3729..810ecc7f265d75424f316c34185f732ad87bb6f7 100644 GIT binary patch literal 3990 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 literal 3978 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 diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi index 9b2a74768d18a802ffdb263cdfaa4ffbb97b1f73..4f7829c3aacafc258efd5836c08891cc1e855d7b 100644 GIT binary patch literal 12564 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%0AsnoUpGL04lqKeG-b`X#bcS%Cj2C73cvY^PJPWX& z>Q=aCF43pataZ1kUZ;xsr1RpN7Vis)rs`E%lqn)?D5W7bgFPW`7k72Q(`&pETX4m;Qe-GcP1Uul4uDOg2|vz}K6;m!G#_o)Puw&`7a!d_lVV4w z5}9JBn=U|eA8)Nr=V-!#D&Z5o$J}L)NBCqr^r##x)sNXC*GH2nU;+R^KU)W2opO|} zM0rd>-_Bm__kX7{v?X7+t`sgt>d8Bl{gJeFccM%CljOe6#L3Q~W5_fYy7S_(j+Gk_ zW2WP)-gVymBID#2`NOn3kLv`YJ3-O(T+Ov$Dn*}K+dX;E<%yDjGtnWLWwLMdQil+# zGY!ieEaUrrnMpR%h5Kn0xDE%4!0KiK3ovs(CuX9B9HKF=;fGM_)-^)t4}EQ7(wfWk z;~V%f_33k=TnQr*R7?-(WC#&Xmx>mIHC+bQyGT=w}ZycJs z%}XI#YwyPF5uwRfb}?wzP}ClABXAOq{vlt2>Hqa>7XxZ~FCBF(`-X|jV&3(~%Z~_u zs6KTr16WR2U@PY+`H-y7mN>V94d)G^-) z(cBKvWKS-bYK!xcv0n+Qph!|Z;w>mc$pqd?@M|-FrLUs@Kfmhwg^!ES*c!0p2s502 z{NH9>l_zvUaevFoo?1eevnIz8&pL#On0y+uCMlgLWuKL}LVuKq8 z{do?h;2ybUWtC32fyaASR^yD)32geo5!673h5)AFU-b04;SL9A=Lf9qBe1epU|s~0 z+|zSy1kwK1Jc--4IXm>Z^(ZgRhqScsIWC=VB6?nyoOQMaA%!SWli;hA5^C@tcVA!} z+g~fV$ZWw;5nlv2y@`=Vl9E!u)Vt<0v#q*7>-#!b(>aOf)g_fzq z*x8PMq)TzS0j?kZw5vFUL^8F&SODdB-MYK{Id?dOH*VZG1-NFn1|s3})dvT3W_`B0 zmZb80Px3Gi12A@Xe_h&p;bw{pnkfV1hs>^A<#xxgC)C)idSUuT;B-o0-V4Flz@;qg z!NC|9@PJ@(eCJqf^wY7Se5nG^r5Z$iiwIb!m`yMJBYg1-qrM}|XSV*E@&T3G<#U8- jxzr6Xfo1W!>_jSylb>^i?sE_X0>BkRbAu{9r$_$>d}^wE diff --git a/icons/mob/screen1_bot.dmi b/icons/mob/screen1_bot.dmi new file mode 100644 index 0000000000000000000000000000000000000000..7522623f3273b8e65ada682370ddec9647474483 GIT binary patch literal 20085 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 diff --git a/nano/templates/pda_mule.tmpl b/nano/templates/pda_mule.tmpl index cf3c5eff6f6..369d0830aa5 100644 --- a/nano/templates/pda_mule.tmpl +++ b/nano/templates/pda_mule.tmpl @@ -110,7 +110,7 @@
                {{:helper.link('Stop', 'gear', {'radiomenu' : "1", 'op' : "stop"}, null, 'pdalink fixedLeft')}} - {{:helper.link('Proceed', 'gear', {'radiomenu' : "1", 'op' : "go"}, null, 'pdalink fixedLeft')}} + {{:helper.link('Proceed', 'gear', {'radiomenu' : "1", 'op' : "start"}, null, 'pdalink fixedLeft')}} {{:helper.link('Return Home', 'gear', {'radiomenu' : "1", 'op' : "home"}, null, 'pdalink fixedLeft')}}
                diff --git a/paradise.dme b/paradise.dme index 3b3535e57f6..c51ea9adbb0 100644 --- a/paradise.dme +++ b/paradise.dme @@ -17,6 +17,7 @@ #include "code\__DEFINES\_readme.dm" #include "code\__DEFINES\admin.dm" #include "code\__DEFINES\atmospherics.dm" +#include "code\__DEFINES\bots.dm" #include "code\__DEFINES\btime.dm" #include "code\__DEFINES\clothing.dm" #include "code\__DEFINES\combat.dm" @@ -99,6 +100,7 @@ #include "code\_onclick\hud\ai.dm" #include "code\_onclick\hud\alien.dm" #include "code\_onclick\hud\alien_larva.dm" +#include "code\_onclick\hud\bot.dm" #include "code\_onclick\hud\hud.dm" #include "code\_onclick\hud\human.dm" #include "code\_onclick\hud\monkey.dm" @@ -156,7 +158,7 @@ #include "code\controllers\voting.dm" #include "code\controllers\Processes\air.dm" #include "code\controllers\Processes\alarm.dm" -#include "code\controllers\Processes\bot.dm" +#include "code\controllers\Processes\animal.dm" #include "code\controllers\Processes\event.dm" #include "code\controllers\Processes\garbage.dm" #include "code\controllers\Processes\inactivity.dm" @@ -482,13 +484,6 @@ #include "code\game\machinery\atmoalter\pump.dm" #include "code\game\machinery\atmoalter\scrubber.dm" #include "code\game\machinery\atmoalter\zvent.dm" -#include "code\game\machinery\bots\bots.dm" -#include "code\game\machinery\bots\cleanbot.dm" -#include "code\game\machinery\bots\ed209bot.dm" -#include "code\game\machinery\bots\floorbot.dm" -#include "code\game\machinery\bots\medbot.dm" -#include "code\game\machinery\bots\mulebot.dm" -#include "code\game\machinery\bots\secbot.dm" #include "code\game\machinery\camera\camera.dm" #include "code\game\machinery\camera\camera_assembly.dm" #include "code\game\machinery\camera\motion.dm" @@ -1479,6 +1474,15 @@ #include "code\modules\mob\living\simple_animal\simple_animal.dm" #include "code\modules\mob\living\simple_animal\tribbles.dm" #include "code\modules\mob\living\simple_animal\vox.dm" +#include "code\modules\mob\living\simple_animal\bot\bot.dm" +#include "code\modules\mob\living\simple_animal\bot\cleanbot.dm" +#include "code\modules\mob\living\simple_animal\bot\construction.dm" +#include "code\modules\mob\living\simple_animal\bot\ed209bot.dm" +#include "code\modules\mob\living\simple_animal\bot\emote.dm" +#include "code\modules\mob\living\simple_animal\bot\floorbot.dm" +#include "code\modules\mob\living\simple_animal\bot\medbot.dm" +#include "code\modules\mob\living\simple_animal\bot\mulebot.dm" +#include "code\modules\mob\living\simple_animal\bot\secbot.dm" #include "code\modules\mob\living\simple_animal\friendly\butterfly.dm" #include "code\modules\mob\living\simple_animal\friendly\cat.dm" #include "code\modules\mob\living\simple_animal\friendly\corgi.dm" From 4ba5a6eefd1ca673a7dc629ea27781fec8d1355f Mon Sep 17 00:00:00 2001 From: Tastyfish Date: Mon, 28 Mar 2016 01:58:06 -0400 Subject: [PATCH 2/4] Moves MULEbot Runover code to mulebot/Move() --- code/modules/mob/living/carbon/human/human.dm | 4 ---- .../mob/living/simple_animal/bot/mulebot.dm | 14 ++++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 7aadabe066a..cbf10c31888 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -636,10 +636,6 @@ // called when something steps onto a human // this handles mulebots and vehicles /mob/living/carbon/human/Crossed(var/atom/movable/AM) - if(istype(AM, /mob/living/simple_animal/bot/mulebot)) - var/mob/living/simple_animal/bot/mulebot/MB = AM - MB.RunOver(src) - if(istype(AM, /obj/vehicle)) var/obj/vehicle/V = AM V.RunOver(src) diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index a16949b87a8..720eab73d5b 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -585,9 +585,13 @@ . = ..() - if(. && istype(next) && bloodiness) - next.AddTracks(/obj/effect/decal/cleanable/blood/tracks/wheels, currentDNA, get_dir(last, next), 0, currentBloodColor) - bloodiness-- + if(. && istype(next)) + if(bloodiness) + next.AddTracks(/obj/effect/decal/cleanable/blood/tracks/wheels, currentDNA, get_dir(last, next), 0, currentBloodColor) + bloodiness-- + + for(var/mob/living/carbon/human/H in next) + RunOver(H) // calculates a path to the current destination // given an optional turf to avoid @@ -687,8 +691,6 @@ M.Weaken(5) return ..() -// called from mob/living/carbon/human/Crossed() -// when mulebot is in the same loc /mob/living/simple_animal/bot/mulebot/proc/RunOver(mob/living/carbon/human/H) add_logs(src, H, "run over", null, "(DAMTYPE: [uppertext(BRUTE)])") H.visible_message("[src] drives over [H]!", \ @@ -765,7 +767,7 @@ if("autopick") auto_pickup = text2num(signal.data["value"]) - + else return 0 return 1 From 653e6738b8666dd0f8ed3d931566dcc9b6f8f1f7 Mon Sep 17 00:00:00 2001 From: Tastyfish Date: Tue, 29 Mar 2016 21:53:56 -0400 Subject: [PATCH 3/4] Removes pAI insertion from named secbots --- code/modules/mob/living/simple_animal/bot/secbot.dm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 59c89580de5..f60ad0ebb48 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -35,11 +35,12 @@ var/prev_flashing_lights = 0 /mob/living/simple_animal/bot/secbot/beepsky - name = "Officer Beep O'sky" - desc = "It's Officer Beep O'sky! Powered by a potato and a shot of whiskey." + name = "Officer Beepsky" + desc = "It's Officer Beepsky! Powered by a potato and a shot of whiskey." idcheck = 0 weaponscheck = 0 auto_patrol = 1 + allow_pai = 0 /mob/living/simple_animal/bot/secbot/beepsky/explode() var/turf/Tsec = get_turf(src) @@ -53,6 +54,7 @@ name = "Officer Pingsky" desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment." radio_channel = "AI Private" + allow_pai = 0 /mob/living/simple_animal/bot/secbot/ofitser name = "Prison Ofitser" @@ -60,6 +62,7 @@ idcheck = 0 weaponscheck = 1 auto_patrol = 1 + allow_pai = 0 /mob/living/simple_animal/bot/secbot/buzzsky name = "Officer Buzzsky" From fb9c883b90a4f4cfa30fae98cfd4b9774b8b0b20 Mon Sep 17 00:00:00 2001 From: Tastyfish Date: Wed, 30 Mar 2016 17:55:31 -0400 Subject: [PATCH 4/4] Makes medibots ignore IPC's --- code/modules/mob/living/simple_animal/bot/medbot.dm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index b1504fdb109..588588a1e3f 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -353,6 +353,12 @@ if(C.suiciding) return 0 //Kevorkian school of robotic medical assistants. + // is secretly a silicon + if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(H.species && H.species.reagent_tag == PROCESS_SYN) + return 0 + if(emagged == 2) //Everyone needs our medicine. (Our medicine is toxins) return 1

                Name

                Status

                Location

                Control

                [Bot.hacked ? "(!) [Bot.name]" : Bot.name] ([Bot.bot_type_name])
                [Bot.hacked ? "(!) [Bot.name]" : Bot.name] ([Bot.model])[Bot.on ? "[Bot.mode ? "[ Bot.mode_name[Bot.mode] ]": "Idle"]" : "Inactive"][bot_area.name]