From 27854ad99edec0b6062ab95f08c5ea980fc9972a Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+Steals-The-PRs@users.noreply.github.com> Date: Mon, 4 Dec 2023 23:56:52 +0300 Subject: [PATCH] [MIRROR] Basic bots (medibots) (#946) * [MIRROR] Basic bots (medibots) [MDB IGNORE] (#25384) * Basic bots (medibots) * UpdatePaths, modular * update paths --------- Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: Iajret --- .../SpaceRuins/intactemptyship.dmm | 2 +- .../SpaceRuins/skyrat/interdynefob.dmm | 2 +- _maps/RandomRuins/SpaceRuins/spacehotel.dmm | 2 +- .../SpaceRuins/spacehotel_skyrat.dmm | 2 +- _maps/map_files/Birdshot/birdshot.dmm | 2 +- _maps/map_files/KiloStation2/KiloStation2.dmm | 2 +- _maps/map_files/MetaStation/MetaStation.dmm | 2 +- _maps/map_files/NSVBlueshift/Blueshift.dmm | 6 +- _maps/map_files/NorthStar/north_star.dmm | 4 +- _maps/map_files/VoidRaptor/VoidRaptor.dmm | 2 +- _maps/map_files/generic/CentCom_skyrat_z2.dmm | 2 +- _maps/map_files/tramstation/tramstation.dmm | 2 +- _maps/shuttles/emergency_casino.dmm | 2 +- _maps/shuttles/emergency_cere.dmm | 2 +- _maps/shuttles/emergency_delta.dmm | 2 +- _maps/shuttles/emergency_donut.dmm | 2 +- _maps/shuttles/emergency_kilo.dmm | 2 +- _maps/shuttles/emergency_lance.dmm | 2 +- _maps/shuttles/emergency_luxury.dmm | 2 +- _maps/shuttles/emergency_medisim.dmm | 2 +- _maps/shuttles/emergency_meta.dmm | 2 +- _maps/shuttles/emergency_nature.dmm | 2 +- _maps/shuttles/emergency_northstar.dmm | 2 +- _maps/shuttles/emergency_omega.dmm | 2 +- _maps/shuttles/emergency_raven.dmm | 2 +- _maps/shuttles/emergency_shadow.dmm | 2 +- _maps/shuttles/emergency_tranquility.dmm | 2 +- _maps/shuttles/infiltrator_advanced.dmm | 2 +- _maps/shuttles/infiltrator_basic.dmm | 2 +- _maps/shuttles/pirate_ex_interdyne.dmm | 2 +- _maps/shuttles/skyrat/goldeneye_cruiser.dmm | 2 +- _maps/templates/battlecruiser_starfury.dmm | 2 +- _maps/templates/lazy_templates/ninja_den.dmm | 6 +- _maps/templates/lazy_templates/wizard_den.dmm | 2 +- code/__DEFINES/ai/ai.dm | 1 + code/__DEFINES/ai/bot_keys.dm | 35 + .../dcs/signals/signals_mob/signals_mob_ai.dm | 2 + code/__DEFINES/is_helpers.dm | 4 +- code/__DEFINES/robots.dm | 25 +- code/__DEFINES/span.dm | 2 + code/_globalvars/lists/mobs.dm | 1 + code/_globalvars/phobias.dm | 2 +- code/datums/ai/movement/ai_movement_jps.dm | 7 +- code/datums/components/crafting/robot.dm | 8 +- code/datums/elements/hat_wearer.dm | 72 ++ code/game/data_huds.dm | 1 - code/game/machinery/botlaunchpad.dm | 55 +- .../crates_lockers/closets/secure/misc.dm | 4 +- .../antagonists/ninja/ninjaDrainAct.dm | 2 +- code/modules/cargo/packs/emergency.dm | 11 +- code/modules/mob/living/basic/bots/_bots.dm | 793 ++++++++++++++++++ code/modules/mob/living/basic/bots/bot_ai.dm | 233 +++++ code/modules/mob/living/basic/bots/bot_hud.dm | 39 + .../mob/living/basic/bots/medbot/medbot.dm | 375 +++++++++ .../mob/living/basic/bots/medbot/medbot_ai.dm | 209 +++++ code/modules/mob/living/silicon/ai/ai.dm | 20 +- .../mob/living/simple_animal/bot/bot.dm | 5 + .../simple_animal/bot/bot_announcement.dm | 9 + .../living/simple_animal/bot/construction.dm | 6 +- .../mob/living/simple_animal/bot/medbot.dm | 710 ---------------- .../unit_tests/simple_animal_freeze.dm | 6 - sound/voice/medbot/i_am_chicken.ogg | Bin 0 -> 108791 bytes tgstation.dme | 8 +- tools/UpdatePaths/Scripts/79955_medbots.txt | 6 + 64 files changed, 1918 insertions(+), 811 deletions(-) create mode 100644 code/__DEFINES/ai/bot_keys.dm create mode 100644 code/datums/elements/hat_wearer.dm create mode 100644 code/modules/mob/living/basic/bots/_bots.dm create mode 100644 code/modules/mob/living/basic/bots/bot_ai.dm create mode 100644 code/modules/mob/living/basic/bots/bot_hud.dm create mode 100644 code/modules/mob/living/basic/bots/medbot/medbot.dm create mode 100644 code/modules/mob/living/basic/bots/medbot/medbot_ai.dm delete mode 100644 code/modules/mob/living/simple_animal/bot/medbot.dm create mode 100644 sound/voice/medbot/i_am_chicken.ogg create mode 100644 tools/UpdatePaths/Scripts/79955_medbots.txt diff --git a/_maps/RandomRuins/SpaceRuins/intactemptyship.dmm b/_maps/RandomRuins/SpaceRuins/intactemptyship.dmm index f54460937b0..e0c166aee2c 100644 --- a/_maps/RandomRuins/SpaceRuins/intactemptyship.dmm +++ b/_maps/RandomRuins/SpaceRuins/intactemptyship.dmm @@ -84,7 +84,7 @@ /turf/open/floor/mineral/titanium/purple, /area/ruin/space/has_grav/powered/authorship) "y" = ( -/mob/living/simple_animal/bot/medbot/derelict, +/mob/living/basic/bot/medbot/derelict, /turf/open/floor/mineral/titanium/purple, /area/ruin/space/has_grav/powered/authorship) "z" = ( diff --git a/_maps/RandomRuins/SpaceRuins/skyrat/interdynefob.dmm b/_maps/RandomRuins/SpaceRuins/skyrat/interdynefob.dmm index c86038452d7..1ada129b23f 100644 --- a/_maps/RandomRuins/SpaceRuins/skyrat/interdynefob.dmm +++ b/_maps/RandomRuins/SpaceRuins/skyrat/interdynefob.dmm @@ -1252,7 +1252,7 @@ /turf/open/floor/plating, /area/ruin/space/has_grav/skyrat/interdynefob/cargo/hangar) "fd" = ( -/mob/living/simple_animal/bot/medbot/stationary{ +/mob/living/basic/bot/medbot/stationary{ faction = list("Syndicate"); maints_access_required = list("syndicate"); name = "Insurgent Care"; diff --git a/_maps/RandomRuins/SpaceRuins/spacehotel.dmm b/_maps/RandomRuins/SpaceRuins/spacehotel.dmm index 705b2654546..3a2d28ba1ee 100644 --- a/_maps/RandomRuins/SpaceRuins/spacehotel.dmm +++ b/_maps/RandomRuins/SpaceRuins/spacehotel.dmm @@ -862,7 +862,7 @@ /obj/effect/turf_decal/siding/yellow{ dir = 8 }, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "Accidents Happen" }, /obj/structure/cable, diff --git a/_maps/RandomRuins/SpaceRuins/spacehotel_skyrat.dmm b/_maps/RandomRuins/SpaceRuins/spacehotel_skyrat.dmm index 700323feee0..6ea07d8f5fd 100644 --- a/_maps/RandomRuins/SpaceRuins/spacehotel_skyrat.dmm +++ b/_maps/RandomRuins/SpaceRuins/spacehotel_skyrat.dmm @@ -8090,7 +8090,7 @@ /obj/effect/turf_decal/siding/dark_blue{ dir = 8 }, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "Accidents Happen" }, /turf/open/floor/iron/dark, diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index 50f2b3ed7df..d626c93f200 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -3077,7 +3077,7 @@ /area/station/hallway/secondary/construction) "bmB" = ( /obj/effect/turf_decal/tile/blue/fourcorners, -/mob/living/simple_animal/bot/medbot/autopatrol, +/mob/living/basic/bot/medbot/autopatrol, /turf/open/floor/iron/white, /area/station/medical/medbay/lobby) "bmM" = ( diff --git a/_maps/map_files/KiloStation2/KiloStation2.dmm b/_maps/map_files/KiloStation2/KiloStation2.dmm index 055328a080a..ef285398d16 100644 --- a/_maps/map_files/KiloStation2/KiloStation2.dmm +++ b/_maps/map_files/KiloStation2/KiloStation2.dmm @@ -60934,7 +60934,7 @@ /area/station/hallway/primary/aft) "tCd" = ( /obj/effect/landmark/event_spawn, -/mob/living/simple_animal/bot/medbot/autopatrol, +/mob/living/basic/bot/medbot/autopatrol, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/effect/turf_decal/tile/blue/opposingcorners, diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index fda76eb5ea1..24d16d68b86 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -10936,7 +10936,7 @@ /obj/effect/turf_decal/tile/blue/anticorner/contrasted{ dir = 4 }, -/mob/living/simple_animal/bot/medbot/autopatrol, +/mob/living/basic/bot/medbot/autopatrol, /turf/open/floor/iron/white/corner{ dir = 8 }, diff --git a/_maps/map_files/NSVBlueshift/Blueshift.dmm b/_maps/map_files/NSVBlueshift/Blueshift.dmm index 02a54339866..fe60f28e61e 100644 --- a/_maps/map_files/NSVBlueshift/Blueshift.dmm +++ b/_maps/map_files/NSVBlueshift/Blueshift.dmm @@ -41968,7 +41968,7 @@ /obj/effect/landmark/start/medical_doctor, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "Doctor Heals-The-Idiot" }, /obj/effect/turf_decal/tile/blue/fourcorners, @@ -60153,7 +60153,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/disposalpipe/segment, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "Healy Stabby" }, /turf/open/floor/iron, @@ -86152,7 +86152,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/mob/living/simple_animal/bot/medbot/autopatrol, +/mob/living/basic/bot/medbot/autopatrol, /turf/open/floor/iron, /area/station/hallway/primary/upper) "quF" = ( diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index e632d4e2006..ca2cebdeb76 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -20714,7 +20714,7 @@ codes_txt = "patrol;next_patrol=2-0"; location = "2-19" }, -/mob/living/simple_animal/bot/medbot/autopatrol, +/mob/living/basic/bot/medbot/autopatrol, /turf/open/floor/iron/dark, /area/station/hallway/floor2/aft) "fwV" = ( @@ -66224,7 +66224,7 @@ codes_txt = "patrol;next_patrol=4-1"; location = "4-0" }, -/mob/living/simple_animal/bot/medbot/autopatrol, +/mob/living/basic/bot/medbot/autopatrol, /turf/open/floor/iron/dark/side{ dir = 9 }, diff --git a/_maps/map_files/VoidRaptor/VoidRaptor.dmm b/_maps/map_files/VoidRaptor/VoidRaptor.dmm index 02c0a5b0fdb..83bbf9a4f17 100644 --- a/_maps/map_files/VoidRaptor/VoidRaptor.dmm +++ b/_maps/map_files/VoidRaptor/VoidRaptor.dmm @@ -16409,7 +16409,7 @@ "eJc" = ( /obj/machinery/power/apc/auto_name/directional/south, /obj/structure/cable, -/mob/living/simple_animal/bot/medbot, +/mob/living/basic/bot/medbot, /obj/structure/railing{ dir = 8 }, diff --git a/_maps/map_files/generic/CentCom_skyrat_z2.dmm b/_maps/map_files/generic/CentCom_skyrat_z2.dmm index 6eca212a6f4..e144274de07 100644 --- a/_maps/map_files/generic/CentCom_skyrat_z2.dmm +++ b/_maps/map_files/generic/CentCom_skyrat_z2.dmm @@ -7934,7 +7934,7 @@ /area/centcom/holding/cafe) "kpg" = ( /obj/structure/closet/cardboard, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ faction = list("Syndicate"); maints_access_required = list(150); name = "Kahn"; diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index b7e5199bbc9..e1c8ffd95b0 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -738,7 +738,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/effect/turf_decal/trimline/white/warning, -/mob/living/simple_animal/bot/medbot/autopatrol{ +/mob/living/basic/bot/medbot/autopatrol{ desc = "A Trauma Response Activation Medibot. It seems overwhelmed."; name = "T.R.A.M Unit" }, diff --git a/_maps/shuttles/emergency_casino.dmm b/_maps/shuttles/emergency_casino.dmm index 3992fd21348..1747295a6f2 100644 --- a/_maps/shuttles/emergency_casino.dmm +++ b/_maps/shuttles/emergency_casino.dmm @@ -1262,7 +1262,7 @@ /turf/open/floor/carpet/green, /area/shuttle/escape) "PP" = ( -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_cere.dmm b/_maps/shuttles/emergency_cere.dmm index 07e236344ab..dccdcc4b139 100644 --- a/_maps/shuttles/emergency_cere.dmm +++ b/_maps/shuttles/emergency_cere.dmm @@ -455,7 +455,7 @@ /turf/open/floor/iron, /area/shuttle/escape) "ch" = ( -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "Speedy* Recovery" }, /obj/effect/turf_decal/tile/blue/anticorner/contrasted{ diff --git a/_maps/shuttles/emergency_delta.dmm b/_maps/shuttles/emergency_delta.dmm index f2102147313..5cca48f0d8f 100644 --- a/_maps/shuttles/emergency_delta.dmm +++ b/_maps/shuttles/emergency_delta.dmm @@ -982,7 +982,7 @@ }, /obj/item/lazarus_injector, /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_donut.dmm b/_maps/shuttles/emergency_donut.dmm index 67dc9f62da7..f81c084e90b 100644 --- a/_maps/shuttles/emergency_donut.dmm +++ b/_maps/shuttles/emergency_donut.dmm @@ -377,7 +377,7 @@ }, /obj/item/lazarus_injector, /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_kilo.dmm b/_maps/shuttles/emergency_kilo.dmm index 39a1a99ea9a..b8710eb1164 100644 --- a/_maps/shuttles/emergency_kilo.dmm +++ b/_maps/shuttles/emergency_kilo.dmm @@ -426,7 +426,7 @@ }, /obj/item/lazarus_injector, /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_lance.dmm b/_maps/shuttles/emergency_lance.dmm index c6c0d9d9c18..05727cad322 100644 --- a/_maps/shuttles/emergency_lance.dmm +++ b/_maps/shuttles/emergency_lance.dmm @@ -1628,7 +1628,7 @@ /turf/open/floor/iron, /area/shuttle/escape) "ZZ" = ( -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_luxury.dmm b/_maps/shuttles/emergency_luxury.dmm index 1d6dfce2664..29744e3ec36 100644 --- a/_maps/shuttles/emergency_luxury.dmm +++ b/_maps/shuttles/emergency_luxury.dmm @@ -1114,7 +1114,7 @@ /turf/open/floor/plating, /area/shuttle/escape) "SE" = ( -/mob/living/simple_animal/bot/medbot, +/mob/living/basic/bot/medbot, /obj/structure/extinguisher_cabinet/directional/south, /turf/open/floor/mineral/titanium/blue, /area/shuttle/escape/luxury) diff --git a/_maps/shuttles/emergency_medisim.dmm b/_maps/shuttles/emergency_medisim.dmm index 7689bed7306..0780ea89d20 100644 --- a/_maps/shuttles/emergency_medisim.dmm +++ b/_maps/shuttles/emergency_medisim.dmm @@ -245,7 +245,7 @@ pixel_y = 3 }, /obj/item/lazarus_injector, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_meta.dmm b/_maps/shuttles/emergency_meta.dmm index 068ecf465e4..5a4ca2c4832 100644 --- a/_maps/shuttles/emergency_meta.dmm +++ b/_maps/shuttles/emergency_meta.dmm @@ -544,7 +544,7 @@ }, /obj/item/lazarus_injector, /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_nature.dmm b/_maps/shuttles/emergency_nature.dmm index 95eada06cfb..a1fa04c1c6a 100644 --- a/_maps/shuttles/emergency_nature.dmm +++ b/_maps/shuttles/emergency_nature.dmm @@ -787,7 +787,7 @@ dir = 9 }, /obj/structure/window/reinforced/spawner/directional/west, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_northstar.dmm b/_maps/shuttles/emergency_northstar.dmm index 20795c72317..85b807d8431 100644 --- a/_maps/shuttles/emergency_northstar.dmm +++ b/_maps/shuttles/emergency_northstar.dmm @@ -178,7 +178,7 @@ /turf/open/floor/iron/dark, /area/shuttle/escape) "oA" = ( -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_omega.dmm b/_maps/shuttles/emergency_omega.dmm index bb2976fc749..e3f867a6130 100644 --- a/_maps/shuttles/emergency_omega.dmm +++ b/_maps/shuttles/emergency_omega.dmm @@ -559,7 +559,7 @@ /obj/item/lazarus_injector, /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_raven.dmm b/_maps/shuttles/emergency_raven.dmm index 4b12d66baed..ce552c3764b 100644 --- a/_maps/shuttles/emergency_raven.dmm +++ b/_maps/shuttles/emergency_raven.dmm @@ -1135,7 +1135,7 @@ pixel_y = 3 }, /obj/item/lazarus_injector, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "\improper emergency medibot"; pixel_x = -3; pixel_y = 2 diff --git a/_maps/shuttles/emergency_shadow.dmm b/_maps/shuttles/emergency_shadow.dmm index 15593e7e956..70946ba72b2 100644 --- a/_maps/shuttles/emergency_shadow.dmm +++ b/_maps/shuttles/emergency_shadow.dmm @@ -330,7 +330,7 @@ /turf/open/floor/catwalk_floor, /area/shuttle/escape/engine) "oK" = ( -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ name = "Doctor Patches" }, /turf/open/floor/iron/dark/smooth_large, diff --git a/_maps/shuttles/emergency_tranquility.dmm b/_maps/shuttles/emergency_tranquility.dmm index 057c3b1adf7..cd2a5ef11df 100644 --- a/_maps/shuttles/emergency_tranquility.dmm +++ b/_maps/shuttles/emergency_tranquility.dmm @@ -2131,7 +2131,7 @@ /obj/effect/turf_decal/siding/thinplating_new{ dir = 4 }, -/mob/living/simple_animal/bot/medbot, +/mob/living/basic/bot/medbot, /turf/open/floor/iron/herringbone, /area/shuttle/escape) "NC" = ( diff --git a/_maps/shuttles/infiltrator_advanced.dmm b/_maps/shuttles/infiltrator_advanced.dmm index 83e13c63f35..fa8ab14f69c 100644 --- a/_maps/shuttles/infiltrator_advanced.dmm +++ b/_maps/shuttles/infiltrator_advanced.dmm @@ -600,7 +600,7 @@ dir = 6 }, /obj/structure/cable, -/mob/living/simple_animal/bot/medbot/nukie, +/mob/living/basic/bot/medbot/nukie, /turf/open/floor/mineral/plastitanium, /area/shuttle/syndicate/medical) "bA" = ( diff --git a/_maps/shuttles/infiltrator_basic.dmm b/_maps/shuttles/infiltrator_basic.dmm index ca3da37f63b..070fe4bdcee 100644 --- a/_maps/shuttles/infiltrator_basic.dmm +++ b/_maps/shuttles/infiltrator_basic.dmm @@ -676,7 +676,7 @@ /obj/effect/turf_decal/tile/blue{ dir = 1 }, -/mob/living/simple_animal/bot/medbot/nukie, +/mob/living/basic/bot/medbot/nukie, /turf/open/floor/iron/corner{ dir = 1 }, diff --git a/_maps/shuttles/pirate_ex_interdyne.dmm b/_maps/shuttles/pirate_ex_interdyne.dmm index ab11aa1dfdf..c84250b13d9 100644 --- a/_maps/shuttles/pirate_ex_interdyne.dmm +++ b/_maps/shuttles/pirate_ex_interdyne.dmm @@ -490,7 +490,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 4 }, -/mob/living/simple_animal/bot/medbot/nukie{ +/mob/living/basic/bot/medbot/nukie{ name = "Dr. Pax"; desc = "A twitchy medibot. It can't seem to hold still. Slightly concerning." }, diff --git a/_maps/shuttles/skyrat/goldeneye_cruiser.dmm b/_maps/shuttles/skyrat/goldeneye_cruiser.dmm index 3d886174ffe..474d5f97910 100644 --- a/_maps/shuttles/skyrat/goldeneye_cruiser.dmm +++ b/_maps/shuttles/skyrat/goldeneye_cruiser.dmm @@ -880,7 +880,7 @@ /area/shuttle/syndicate/cruiser/brig) "Ud" = ( /obj/structure/closet/crate/medical, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ faction = list("Syndicate"); maints_access_required = list(150); name = "James T. Kirk"; diff --git a/_maps/templates/battlecruiser_starfury.dmm b/_maps/templates/battlecruiser_starfury.dmm index 3e759257296..672cc444fbd 100644 --- a/_maps/templates/battlecruiser_starfury.dmm +++ b/_maps/templates/battlecruiser_starfury.dmm @@ -4780,7 +4780,7 @@ /area/shuttle/sbc_starfury) "Gz" = ( /obj/structure/cable, -/mob/living/simple_animal/bot/medbot{ +/mob/living/basic/bot/medbot{ desc = "A medical bot of syndicate origins. Probably plots about how to stab you full of toxins in its free time."; faction = list("neutral","silicon","turret","Syndicate"); name = "Syndicate Medibot"; diff --git a/_maps/templates/lazy_templates/ninja_den.dmm b/_maps/templates/lazy_templates/ninja_den.dmm index 40efeb77e7f..1f728350714 100644 --- a/_maps/templates/lazy_templates/ninja_den.dmm +++ b/_maps/templates/lazy_templates/ninja_den.dmm @@ -877,7 +877,7 @@ /turf/open/floor/iron/showroomfloor, /area/centcom/central_command_areas/holding) "uw" = ( -/mob/living/simple_animal/bot/medbot/stationary{ +/mob/living/basic/bot/medbot/stationary{ desc = "A little medical robot. You can make out the word \"sincerity\" on its chassis."; name = "Hijikata"; radio_key = null @@ -1476,7 +1476,7 @@ /turf/open/floor/iron/cafeteria, /area/centcom/central_command_areas/holding) "II" = ( -/mob/living/simple_animal/bot/medbot/stationary{ +/mob/living/basic/bot/medbot/stationary{ desc = "When engaged in combat, the vanquishing of thine enemy can be the warrior's only concern."; name = "Momo"; radio_key = null @@ -2052,7 +2052,7 @@ /turf/open/floor/carpet/black, /area/centcom/central_command_areas/holding) "Wm" = ( -/mob/living/simple_animal/bot/medbot/stationary{ +/mob/living/basic/bot/medbot/stationary{ desc = "When engaged in combat, the vanquishing of thine enemy can be the warrior's only concern."; name = "Hattori"; radio_key = null diff --git a/_maps/templates/lazy_templates/wizard_den.dmm b/_maps/templates/lazy_templates/wizard_den.dmm index ddec3df365c..8c2ad8d2153 100644 --- a/_maps/templates/lazy_templates/wizard_den.dmm +++ b/_maps/templates/lazy_templates/wizard_den.dmm @@ -362,7 +362,7 @@ /obj/effect/turf_decal/stripes{ dir = 1 }, -/mob/living/simple_animal/bot/medbot/mysterious{ +/mob/living/basic/bot/medbot/mysterious{ desc = "If you don't accidentally blow yourself up from time to time you're not really a wizard anyway."; faction = list("neutral","silicon","creature"); name = "Nobody's Perfect" diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index 5e0b979b2f8..65cc4214908 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -7,6 +7,7 @@ ///For JPS pathing, the maximum length of a path we'll try to generate. Should be modularized depending on what we're doing later on #define AI_MAX_PATH_LENGTH 30 // 30 is possibly overkill since by default we lose interest after 14 tiles of distance, but this gives wiggle room for weaving around obstacles +#define AI_BOT_PATH_LENGTH 150 ///Cooldown on planning if planning failed last time diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm new file mode 100644 index 00000000000..73d5bfb30fc --- /dev/null +++ b/code/__DEFINES/ai/bot_keys.dm @@ -0,0 +1,35 @@ +// bot keys +///The first beacon we find +#define BB_BEACON_TARGET "beacon_target" +///The last beacon we found, we will use its codes to find the next beacon +#define BB_PREVIOUS_BEACON_TARGET "previous_beacon_target" +///Location of whoever summoned us +#define BB_BOT_SUMMON_TARGET "bot_summon_target" +///salute messages to beepsky +#define BB_SALUTE_MESSAGES "salute_messages" +///the beepsky we will salute +#define BB_SALUTE_TARGET "salute_target" +///our announcement ability +#define BB_ANNOUNCE_ABILITY "announce_ability" +///list of our radio channels +#define BB_RADIO_CHANNEL "radio_channel" +///list of unreachable things we will temporarily ignore +#define BB_TEMPORARY_IGNORE_LIST "temporary_ignore_list" + +// medbot keys +///the patient we must heal +#define BB_PATIENT_TARGET "patient_target" +///list holding our wait dialogue +#define BB_WAIT_SPEECH "wait_speech" +///what we will say to our patient after we heal them +#define BB_AFTERHEAL_SPEECH "afterheal_speech" +///things we will say when we are bored +#define BB_IDLE_SPEECH "idle_speech" +///speech unlocked after being emagged +#define BB_EMAGGED_SPEECH "emagged_speech" +///speech when we are tipped +#define BB_WORRIED_ANNOUNCEMENTS "worried_announcements" +///speech when our patient is near death +#define BB_NEAR_DEATH_SPEECH "near_death_speech" +///in crit patient we must alert medbay about +#define BB_PATIENT_IN_CRIT "patient_in_crit" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm index a04b8e751a0..6cca71839a9 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm @@ -4,3 +4,5 @@ /// Signal sent when a blackboard key is cleared #define COMSIG_AI_BLACKBOARD_KEY_CLEARED(blackboard_key) "ai_blackboard_key_clear_[blackboard_key]" +///Signal sent when a bot is reset +#define COMSIG_BOT_RESET "bot_reset" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 8eec04ebe06..909399b3c3d 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -170,7 +170,9 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isrevenant(A) (istype(A, /mob/living/basic/revenant)) -#define isbot(A) (istype(A, /mob/living/simple_animal/bot)) +#define isbot(A) (istype(A, /mob/living/simple_animal/bot) || istype(A, /mob/living/basic/bot)) + +#define isbasicbot(A) (istype(A, /mob/living/basic/bot)) #define ismouse(A) (istype(A, /mob/living/basic/mouse)) diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index c1f1cee4ef6..42e6356d087 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -113,6 +113,22 @@ GLOBAL_LIST_EMPTY(cyborg_all_models_icon_list) ///The Bot has been hacked by a Silicon, emagging them, but revertable. #define BOT_COVER_HACKED (1<<3) + +//basic bots defines + +///is our maintenancle panel currently open +#define BOT_MAINTS_PANEL_OPEN (1<<0) +///is our control panel currently open +#define BOT_CONTROL_PANEL_OPEN (1<<1) + +///bitfield for our access flags +DEFINE_BITFIELD(bot_access_flags, list( + "MAINTS_OPEN" = BOT_MAINTS_PANEL_OPEN, + "CONTROL_OPEN" = BOT_CONTROL_PANEL_OPEN, + "COVER_EMAGGED" = BOT_COVER_EMAGGED, + "COVER_HACKED" = BOT_COVER_HACKED, +)) + //Bot types /// Secutritrons (Beepsky) #define SEC_BOT "Securitron" @@ -140,8 +156,6 @@ GLOBAL_LIST_EMPTY(cyborg_all_models_icon_list) #define BOT_IDLE "Idle" /// Found target, hunting #define BOT_HUNT "In Pursuit" -/// Currently tipped over. -#define BOT_TIPPED "Tipped" /// Start patrol #define BOT_START_PATROL "Beginning Patrol" /// Patrolling @@ -217,11 +231,17 @@ DEFINE_BITFIELD(security_mode_flags, list( #define MEDBOT_STATIONARY_MODE (1<<1) ///Whether the bot will randomly speak from time to time. This will not actually prevent all speech. #define MEDBOT_SPEAK_MODE (1<<2) +/// is the bot currently tipped over? +#define MEDBOT_TIPPED_MODE (1<<3) + +///can we heal all damage? +#define HEAL_ALL_DAMAGE "all_damage" DEFINE_BITFIELD(medical_mode_flags, list( "MEDBOT_DECLARE_CRIT" = MEDBOT_DECLARE_CRIT, "MEDBOT_STATIONARY_MODE" = MEDBOT_STATIONARY_MODE, "MEDBOT_SPEAK_MODE" = MEDBOT_SPEAK_MODE, + "MEDBOT_TIPPED_MODE" = MEDBOT_TIPPED_MODE, )) //cleanBOT defines on what to clean @@ -314,3 +334,4 @@ DEFINE_BITFIELD(janitor_mode_flags, list( #define MEDIBOT_VOICED_THIS_HURTS "This hurts, my pain is real!" #define MEDIBOT_VOICED_THE_END "Is this the end?" #define MEDIBOT_VOICED_NOOO "Nooo!" +#define MEDIBOT_VOICED_CHICKEN "LOOK AT ME?! i am a chicken." diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index ab8ff969e30..cbd29cd4218 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -13,6 +13,8 @@ #define span_alertwarning(str) ("" + str + "") #define span_alien(str) ("" + str + "") #define span_announce(str) ("" + str + "") +#define span_average(str) ("" + str + "") #define span_bigicon(str) ("" + str + "") #define span_binarysay(str) ("" + str + "") diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 1e775dd49ae..2de6a1c691f 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -9,6 +9,7 @@ GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for /// List of types of abstract mob which shouldn't usually exist in the world on its own if we're spawning random mobs GLOBAL_LIST_INIT(abstract_mob_types, list( /mob/living/basic/blob_minion, + /mob/living/basic/bot, /mob/living/basic/construct, /mob/living/basic/guardian, /mob/living/basic/heretic_summon, diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index 412902b0c56..1d11dceb05d 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -74,7 +74,7 @@ GLOBAL_LIST_INIT(phobia_mobs, list( /mob/living/basic/pet/penguin, /mob/living/simple_animal/bot/secbot, )), - "doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)), + "doctors" = typecacheof(list(/mob/living/basic/bot/medbot)), "heresy" = typecacheof(list( /mob/living/basic/heretic_summon, )), diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index 15083399133..31fd90ccf53 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -3,6 +3,7 @@ */ /datum/ai_movement/jps max_pathing_attempts = 20 + var/maximum_length = AI_MAX_PATH_LENGTH /datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) . = ..() @@ -13,7 +14,7 @@ current_movement_target, delay, repath_delay = 0.5 SECONDS, - max_path_length = AI_MAX_PATH_LENGTH, + max_path_length = maximum_length, minimum_distance = controller.get_minimum_distance(), access = controller.get_access(), subsystem = SSai_movement, @@ -30,3 +31,7 @@ source.access = controller.get_access() source.minimum_distance = controller.get_minimum_distance() + +/datum/ai_movement/jps/bot + max_pathing_attempts = 25 + maximum_length = AI_BOT_PATH_LENGTH diff --git a/code/datums/components/crafting/robot.dm b/code/datums/components/crafting/robot.dm index 12202cf17f8..5839f1dfdb9 100644 --- a/code/datums/components/crafting/robot.dm +++ b/code/datums/components/crafting/robot.dm @@ -56,7 +56,7 @@ /datum/crafting_recipe/medbot name = "Medbot" - result = /mob/living/simple_animal/bot/medbot + result = /mob/living/basic/bot/medbot reqs = list( /obj/item/healthanalyzer = 1, /obj/item/storage/medkit = 1, @@ -71,10 +71,10 @@ category = CAT_ROBOT /datum/crafting_recipe/medbot/on_craft_completion(mob/user, atom/result) - var/mob/living/simple_animal/bot/medbot/bot = result + var/mob/living/basic/bot/medbot/bot = result var/obj/item/storage/medkit/medkit = bot.contents[3] bot.medkit_type = medkit - bot.healthanalyzer = bot.contents[4] + bot.health_analyzer = bot.contents[4] ///if you add a new one don't forget to update /obj/item/storage/medkit/attackby() if (istype(medkit, /obj/item/storage/medkit/fire)) @@ -90,7 +90,7 @@ else if (istype(src, /obj/item/storage/medkit/tactical)) bot.skin = "bezerk" - bot.damagetype_healer = initial(medkit.damagetype_healed) ? initial(medkit.damagetype_healed) : BRUTE + bot.damage_type_healer = initial(medkit.damagetype_healed) ? initial(medkit.damagetype_healed) : BRUTE bot.update_appearance() /datum/crafting_recipe/honkbot diff --git a/code/datums/elements/hat_wearer.dm b/code/datums/elements/hat_wearer.dm new file mode 100644 index 00000000000..75fc6a6c677 --- /dev/null +++ b/code/datums/elements/hat_wearer.dm @@ -0,0 +1,72 @@ +/** + * # mobs that can wear hats! + */ +/datum/element/hat_wearer + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY|ELEMENT_BESPOKE + argument_hash_start_idx = 2 + ///offsets of hats we will wear + var/list/offsets + +/datum/element/hat_wearer/Attach(datum/target, offsets = list()) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + src.offsets = offsets + RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_overlays_updated)) + RegisterSignal(target, COMSIG_ATOM_EXITED, PROC_REF(exited)) + RegisterSignal(target, COMSIG_ATOM_ENTERED, PROC_REF(on_entered)) + RegisterSignal(target, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attack_by)) + +/datum/element/hat_wearer/Detach(datum/target) + var/obj/item/hat = (locate(/obj/item/clothing/head) in target) + if(hat) + hat.forceMove(get_turf(target)) + UnregisterSignal(target, list( + COMSIG_ATOM_UPDATE_OVERLAYS, + COMSIG_ATOM_EXITED, + COMSIG_ATOM_ENTERED, + COMSIG_ATOM_ATTACKBY, + )) + return ..() + +/datum/element/hat_wearer/proc/on_overlays_updated(atom/source, list/overlays) + SIGNAL_HANDLER + + var/obj/item/hat = (locate(/obj/item/clothing/head) in source) + if(isnull(hat)) + return + var/mutable_appearance/hat_overlay = mutable_appearance(hat.worn_icon, hat.icon_state) + hat_overlay.pixel_x = offsets[1] + hat_overlay.pixel_y = offsets[2] + overlays += hat_overlay + +/datum/element/hat_wearer/proc/exited(atom/movable/source, atom/movable/exited) + SIGNAL_HANDLER + + if(!istype(exited, /obj/item/clothing/head)) + return + source.update_appearance(UPDATE_OVERLAYS) + +/datum/element/hat_wearer/proc/on_entered(atom/movable/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) + SIGNAL_HANDLER + + if(!istype(arrived, /obj/item/clothing/head)) + return + for(var/obj/item/clothing/head/already_worn in source) + if(already_worn == arrived) + continue + already_worn.forceMove(get_turf(source)) + source.update_appearance(UPDATE_OVERLAYS) + +/datum/element/hat_wearer/proc/on_attack_by(atom/movable/source, obj/item/item, mob/living/attacker) + SIGNAL_HANDLER + if(!istype(item, /obj/item/clothing/head)) + return + INVOKE_ASYNC(src, PROC_REF(place_hat), source, item, attacker) + return COMPONENT_NO_AFTERATTACK + +/datum/element/hat_wearer/proc/place_hat(atom/movable/source, obj/item/item, mob/living/attacker) + if(!do_after(attacker, delay = 3 SECONDS, target = source)) + source.balloon_alert(attacker, "must stay still!") + return + item.forceMove(source) diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index dbc9a19a6c5..043d9453908 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -550,7 +550,6 @@ Diagnostic HUDs! holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]" else holder.icon_state = "hudnobatt" - /*~~~~~~~~~~~~ Airlocks! ~~~~~~~~~~~~~*/ diff --git a/code/game/machinery/botlaunchpad.dm b/code/game/machinery/botlaunchpad.dm index f77e11151e0..c5059f850eb 100644 --- a/code/game/machinery/botlaunchpad.dm +++ b/code/game/machinery/botlaunchpad.dm @@ -7,12 +7,13 @@ // ID of the console, used for linking up var/id = "botlauncher" var/obj/item/botpad_remote/connected_remote - var/mob/living/simple_animal/bot/launched_bot // we need this to recall the bot + var/datum/weakref/launched_bot // we need this to recall the bot /obj/machinery/botpad/Destroy() if(connected_remote) connected_remote.connected_botpad = null connected_remote = null + launched_bot = null return ..() /obj/machinery/botpad/screwdriver_act(mob/user, obj/item/tool) @@ -34,34 +35,36 @@ // Checks the turf for a bot and launches it if it's the only mob on the pad. /obj/machinery/botpad/proc/launch(mob/living/user) var/turf/reverse_turf = get_turf(user) - var/bot_count = 0 - var/possible_bot - for(var/atom/movable/ROI in get_turf(src)) - if(ismob(ROI)) - if(istype(ROI, /mob/living/simple_animal/bot)) - bot_count += 1 // this counts the number of bots so we don't launch if there multiple bots. - possible_bot = ROI // We don't change the launched_bot var here because we are not sure if there is another bot on the pad. - else - user?.balloon_alert(user, "unidentified life form on the pad!") - return - if(bot_count == 1) - launched_bot = possible_bot - podspawn(list( - "target" = get_turf(src), - "path" = /obj/structure/closet/supplypod/botpod, - "style" = STYLE_SEETHROUGH, - "reverse_dropoff_coords" = list(reverse_turf.x, reverse_turf.y, reverse_turf.z) - )) - use_power(active_power_usage) - else - user?.balloon_alert(user, "too many bots on the pad!") + var/atom/possible_bot + for(var/mob/living/robot in get_turf(src)) + if(!isbot(robot)) + user.balloon_alert(user, "unidentified life form on the pad!") + return + if(!isnull(possible_bot)) + user.balloon_alert(user, "too many bots on the pad!") + return + possible_bot = robot // We don't change the launched_bot var here because we are not sure if there is another bot on the pad. + launched_bot = WEAKREF(possible_bot) + podspawn(list( + "target" = get_turf(src), + "path" = /obj/structure/closet/supplypod/botpod, + "style" = STYLE_SEETHROUGH, + "reverse_dropoff_coords" = list(reverse_turf.x, reverse_turf.y, reverse_turf.z) + )) + use_power(active_power_usage) /obj/machinery/botpad/proc/recall(mob/living/user) - if(!launched_bot) - user?.balloon_alert(user, "no bots detected on the pad!") + var/atom/our_bot = launched_bot?.resolve() + if(isnull(our_bot)) + user.balloon_alert(user, "no bots detected on the pad!") return - user?.balloon_alert(user, "bot sent back to pad") - launched_bot.call_bot(src, get_turf(src)) + user.balloon_alert(user, "bot sent back to pad") + if(isbasicbot(our_bot)) + var/mob/living/basic/bot/basic_bot = our_bot + basic_bot.summon_bot(src) + return + var/mob/living/simple_animal/bot/simple_bot = our_bot + simple_bot.call_bot(src, get_turf(src)) /obj/structure/closet/supplypod/botpod style = STYLE_SEETHROUGH diff --git a/code/game/objects/structures/crates_lockers/closets/secure/misc.dm b/code/game/objects/structures/crates_lockers/closets/secure/misc.dm index 78faa05eaaf..a0a811fc6c8 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/misc.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/misc.dm @@ -45,14 +45,14 @@ icon_state = "cmo" /obj/structure/closet/secure_closet/ert_med/PopulateContents() - ..() + . = ..() + new /mob/living/basic/bot/medbot(src) new /obj/item/storage/medkit/o2(src) new /obj/item/storage/medkit/toxin(src) new /obj/item/storage/medkit/fire(src) new /obj/item/storage/medkit/brute(src) new /obj/item/storage/medkit/regular(src) new /obj/item/defibrillator/compact/combat/loaded/nanotrasen(src) - new /mob/living/simple_animal/bot/medbot(src) /obj/structure/closet/secure_closet/ert_engi name = "emergency response team engineer locker" diff --git a/code/modules/antagonists/ninja/ninjaDrainAct.dm b/code/modules/antagonists/ninja/ninjaDrainAct.dm index 4d21ab89185..ac5b960e3f0 100644 --- a/code/modules/antagonists/ninja/ninjaDrainAct.dm +++ b/code/modules/antagonists/ninja/ninjaDrainAct.dm @@ -365,7 +365,7 @@ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), src, 0, 1, 2, 3), 2.5 SECONDS) return COMPONENT_CANCEL_ATTACK_CHAIN -/mob/living/simple_animal/bot/medbot/ninjadrain_act(mob/living/carbon/human/ninja, obj/item/mod/module/hacker/hacking_module) +/mob/living/basic/bot/medbot/ninjadrain_act(mob/living/carbon/human/ninja, obj/item/mod/module/hacker/hacking_module) var/static/list/death_cry = list( MEDIBOT_VOICED_NO_SAD, MEDIBOT_VOICED_OH_FUCK, diff --git a/code/modules/cargo/packs/emergency.dm b/code/modules/cargo/packs/emergency.dm index 9969ea7d817..49e97271251 100644 --- a/code/modules/cargo/packs/emergency.dm +++ b/code/modules/cargo/packs/emergency.dm @@ -19,11 +19,12 @@ in stations and people alike! Comes with two floorbots, two medbots, five oxygen \ masks and five small oxygen tanks." cost = CARGO_CRATE_VALUE * 4 - contains = list(/mob/living/simple_animal/bot/floorbot = 2, - /mob/living/simple_animal/bot/medbot = 2, - /obj/item/tank/internals/emergency_oxygen = 5, - /obj/item/clothing/mask/breath = 5, - ) + contains = list( + /mob/living/basic/bot/medbot = 2, + /mob/living/simple_animal/bot/floorbot = 2, + /obj/item/tank/internals/emergency_oxygen = 5, + /obj/item/clothing/mask/breath = 5, + ) crate_name = "emergency crate" crate_type = /obj/structure/closet/crate/internals diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm new file mode 100644 index 00000000000..07ff6cf0328 --- /dev/null +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -0,0 +1,793 @@ +GLOBAL_LIST_INIT(command_strings, list( + "patroloff" = "STOP PATROL", + "patrolon" = "START PATROL", + "stop" = "STOP", + "go" = "GO", + "home" = "RETURN HOME", +)) + + +/mob/living/basic/bot + icon = 'icons/mob/silicon/aibots.dmi' + layer = MOB_LAYER + gender = NEUTER + mob_biotypes = MOB_ROBOTIC + basic_mob_flags = DEL_ON_DEATH + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "medibot0" + base_icon_state = "medibot" + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) + maximum_survivable_temperature = INFINITY + minimum_survivable_temperature = 0 + has_unlimited_silicon_privilege = TRUE + sentience_type = SENTIENCE_ARTIFICIAL + status_flags = NONE //no default canpush + faction = list(FACTION_MINING) + ai_controller = /datum/ai_controller/basic_controller/bot + pass_flags = PASSFLAPS + verb_say = "states" + verb_ask = "queries" + verb_exclaim = "declares" + verb_yell = "alarms" + initial_language_holder = /datum/language_holder/synthetic + bubble_icon = "machine" + speech_span = SPAN_ROBOT + faction = list(FACTION_NEUTRAL, FACTION_SILICON, FACTION_TURRET) + light_system = MOVABLE_LIGHT + light_range = 3 + light_power = 0.9 + speed = 3 + ///Access required to access this Bot's maintenance protocols + var/maints_access_required = list(ACCESS_ROBOTICS) + ///The Robot arm attached to this robot - has a 50% chance to drop on death. + var/robot_arm = /obj/item/bodypart/arm/right/robot + ///The inserted (if any) pAI in this bot. + var/obj/item/pai_card/paicard + ///The type of bot it is, for radio control. + var/bot_type = NONE + ///All initial access this bot started with. + var/list/initial_access = list() + ///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION + var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION + ///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_MAINTS_PANEL_OPEN | BOT_CONTROL_PANEL_OPEN | BOT_COVER_EMAGGED | BOT_COVER_HACKED + var/bot_access_flags = NONE + ///Small name of what the bot gets messed with when getting hacked/emagged. + var/hackables = "system circuits" + ///Standardizes the vars that indicate the bot is busy with its function. + var/mode = BOT_IDLE + ///Links a bot to the AI calling it. + var/datum/weakref/calling_ai_ref + ///The bot's radio, for speaking to people. + var/obj/item/radio/internal_radio + ///which channels can the bot listen to + var/radio_key = null + ///The bot's default radio channel + var/radio_channel = RADIO_CHANNEL_COMMON + ///our access card + var/obj/item/card/id/access_card + ///The trim type that will grant additional acces + var/datum/id_trim/additional_access + + ///The type of data HUD the bot uses. Diagnostic by default. + var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC + /// If true we will allow ghosts to control this mob + var/can_be_possessed = FALSE + /// Message to display upon possession + var/possessed_message = "You're a generic bot. How did one of these even get made?" + /// Action we use to say voice lines out loud, also we just pass anything we try to say through here just in case it plays a voice line + var/datum/action/cooldown/bot_announcement/pa_system + /// Type of bot_announcement ability we want + var/announcement_type + /// If true we will offer this + COOLDOWN_DECLARE(offer_ghosts_cooldown) + +/mob/living/basic/bot/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access)) + ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT) + GLOB.bots_list += src + + // Give bots a fancy new ID card that can hold any access. + access_card = new /obj/item/card/id/advanced/simple_bot(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.set_access(list(ACCESS_ROBOTICS)) + provide_additional_access() + + internal_radio = new /obj/item/radio(src) + if(radio_key) + internal_radio.keyslot = new radio_key + internal_radio.subspace_transmission = TRUE + internal_radio.canhear_range = 0 // anything greater will have the bot broadcast the channel as if it were saying it out loud. + internal_radio.recalculateChannels() + + //Adds bot to the diagnostic HUD system + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_atom_to_hud(src) + diag_hud_set_bothealth() + diag_hud_set_botstat() + diag_hud_set_botmode() + + //If a bot has its own HUD (for player bots), provide it. + if(!isnull(data_hud_type)) + var/datum/atom_hud/datahud = GLOB.huds[data_hud_type] + datahud.show_to(src) + + if(HAS_TRAIT(SSstation, STATION_TRAIT_BOTS_GLITCHED)) + randomize_language_if_on_station() + + if(mapload && is_station_level(z) && (bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT) && (bot_mode_flags & BOT_MODE_ROUNDSTART_POSSESSION)) + enable_possession(mapload = mapload) + + pa_system = (isnull(announcement_type)) ? new(src, automated_announcements = generate_speak_list()) : new announcement_type(src, automated_announcements = generate_speak_list()) + pa_system.Grant(src) + ai_controller.set_blackboard_key(BB_ANNOUNCE_ABILITY, pa_system) + ai_controller.set_blackboard_key(BB_RADIO_CHANNEL, radio_channel) + update_appearance() + +/mob/living/basic/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. + return span_bold("[paicard ? "pAI Controlled" : "Autonomous"]") + + if(!(bot_mode_flags & BOT_MODE_ON)) + return span_bad("Inactive") + + return span_average("[mode]") + +/** + * Returns a status string about the bot's current status, if it's moving, manually controlled, or idle. + */ +/mob/living/basic/bot/proc/get_mode_ui() + if(client) + return paicard ? "pAI Controlled" : "Autonomous" + + if(!(bot_mode_flags & BOT_MODE_ON)) + return "Inactive" + + return "[mode]" + +/** + * Returns a string of flavor text for emagged bots as defined by policy. + */ +/mob/living/basic/bot/proc/get_emagged_message() + return get_policy(ROLE_EMAGGED_BOT) || "You are a malfunctioning bot! Disrupt everyone and cause chaos!" + +/mob/living/basic/bot/proc/turn_on() + if(stat == DEAD) + return FALSE + bot_mode_flags |= BOT_MODE_ON + remove_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), POWER_LACK_TRAIT) + set_light_on(bot_mode_flags & BOT_MODE_ON ? TRUE : FALSE) + update_appearance() + balloon_alert(src, "turned on") + diag_hud_set_botstat() + return TRUE + +/mob/living/basic/bot/proc/turn_off() + bot_mode_flags &= ~BOT_MODE_ON + add_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), POWER_LACK_TRAIT) + set_light_on(bot_mode_flags & BOT_MODE_ON ? TRUE : FALSE) + bot_reset() //Resets an AI's call, should it exist. + balloon_alert(src, "turned off") + update_appearance() + +/mob/living/basic/bot/Destroy() + GLOB.bots_list -= src + QDEL_NULL(paicard) + QDEL_NULL(pa_system) + QDEL_NULL(internal_radio) + QDEL_NULL(access_card) + return ..() + +/// Allows this bot to be controlled by a ghost, who will become its mind +/mob/living/basic/bot/proc/enable_possession(user, mapload = FALSE) + if (paicard) + balloon_alert(user, "already sapient!") + return + can_be_possessed = TRUE + var/can_announce = !mapload && COOLDOWN_FINISHED(src, offer_ghosts_cooldown) + AddComponent( + /datum/component/ghost_direct_control, \ + ban_type = ROLE_BOT, \ + poll_candidates = can_announce, \ + poll_ignore_key = POLL_IGNORE_BOTS, \ + assumed_control_message = (bot_access_flags & BOT_COVER_EMAGGED) ? get_emagged_message() : possessed_message, \ + extra_control_checks = CALLBACK(src, PROC_REF(check_possession)), \ + after_assumed_control = CALLBACK(src, PROC_REF(post_possession)), \ + ) + if (can_announce) + COOLDOWN_START(src, offer_ghosts_cooldown, 30 SECONDS) + +/// Disables this bot from being possessed by ghosts +/mob/living/basic/bot/proc/disable_possession(mob/user) + can_be_possessed = FALSE + if(isnull(key)) + return + if (user) + log_combat(user, src, "ejected from [initial(src.name)] control.") + to_chat(src, span_warning("You feel yourself fade as your personality matrix is reset!")) + ghostize(can_reenter_corpse = FALSE) + playsound(src, 'sound/machines/ping.ogg', 30, TRUE) + speak("Personality matrix reset!") + key = null + +/// Returns true if this mob can be controlled +/mob/living/basic/bot/proc/check_possession(mob/potential_possessor) + if (!can_be_possessed) + to_chat(potential_possessor, span_warning("The bot's personality download has been disabled!")) + return can_be_possessed + +/// Fired after something takes control of this mob +/mob/living/basic/bot/proc/post_possession() + playsound(src, 'sound/machines/ping.ogg', 30, TRUE) + speak("New personality installed successfully!") + rename(src) + +/// Allows renaming the bot to something else +/mob/living/basic/bot/proc/rename(mob/user) + var/new_name = sanitize_name( + reject_bad_text(tgui_input_text( + user = user, + message = "This machine is designated [real_name]. Would you like to update its registration?", + title = "Name change", + default = real_name, + max_length = MAX_NAME_LEN, + )), + allow_numbers = TRUE, + ) + if (isnull(new_name) || QDELETED(src)) + return + if (key && user != src) + var/accepted = tgui_alert( + src, + message = "Do you wish to be renamed to [new_name]?", + title = "Name change", + buttons = list("Yes", "No"), + ) + if (accepted != "Yes" || QDELETED(src)) + return + fully_replace_character_name(real_name, new_name) + +/mob/living/basic/bot/proc/check_access(mob/living/user, obj/item/card/id) + if(!istype(user)) // Non-living mobs shouldn't be manipulating bots (like observes using the botkeeper UI). + return FALSE + if(user.has_unlimited_silicon_privilege || isAdminGhostAI(user)) // Silicon and Admins always have access. + return TRUE + if(!length(maints_access_required)) // No requirements to access it. + return TRUE + if(bot_access_flags & BOT_CONTROL_PANEL_OPEN) // Unlocked. + return TRUE + + var/obj/item/card/id/used_id = id || user.get_idcard(TRUE) + + if(!used_id || !used_id.access) + return FALSE + + for(var/requested_access in maints_access_required) + if(requested_access in used_id.access) + return TRUE + + return FALSE + +/mob/living/basic/bot/bee_friendly() + return TRUE + +/mob/living/basic/bot/death(gibbed) + if(paicard) + ejectpai() + explode() + return ..() + +/mob/living/basic/bot/proc/explode() + visible_message(span_boldnotice("[src] blows apart!")) + do_sparks(3, TRUE, src) + var/atom/location_destroyed = drop_location() + if(prob(50)) + drop_part(robot_arm, location_destroyed) + +/mob/living/basic/bot/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN)) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again. + bot_access_flags |= BOT_CONTROL_PANEL_OPEN + balloon_alert(user, "cover unlocked") + return TRUE + if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN) || !(bot_access_flags & BOT_MAINTS_PANEL_OPEN)) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. + balloon_alert(user, "open maintenance panel first!") + return FALSE + bot_access_flags |= BOT_COVER_EMAGGED + bot_access_flags &= ~BOT_CONTROL_PANEL_OPEN + bot_mode_flags &= ~BOT_MODE_REMOTE_ENABLED //Manually emagging the bot also locks the AI from controlling it. + bot_reset() + turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP. + to_chat(src, span_userdanger("(#$*#$^^( OVERRIDE DETECTED")) + to_chat(src, span_boldnotice(get_emagged_message())) + if(user) + log_combat(user, src, "emagged") + return TRUE + +/mob/living/basic/bot/examine(mob/user) + . = ..() + if(health < maxHealth) + if(health > (maxHealth * 0.3)) + . += "[src]'s parts look loose." + else + . += "[src]'s parts look very loose!" + else + . += "[src] is in pristine condition." + + . += span_notice("Its maintenance panel is [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "open" : "closed"].") + . += span_info("You can use a screwdriver to [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "close" : "open"] it.") + + if(bot_access_flags & BOT_MAINTS_PANEL_OPEN) + . += span_notice("Its control panel is [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "unlocked" : "locked"].") + if(!(bot_access_flags & BOT_COVER_EMAGGED) && (issilicon(user) || user.Adjacent(src))) + . += span_info("Alt-click [issilicon(user) ? "" : "or use your ID on "]it to [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "" : "un"]lock its control panel.") + if(isnull(paicard)) + return + . += span_notice("It has a pAI device installed.") + if(!(bot_access_flags & BOT_MAINTS_PANEL_OPEN)) + . += span_info("You can use a hemostat to remove it.") + +/mob/living/basic/bot/updatehealth() + . = ..() + diag_hud_set_bothealth() + +/mob/living/basic/bot/med_hud_set_health() + return //we use a different hud + +/mob/living/basic/bot/med_hud_set_status() + return //we use a different hud + +/mob/living/basic/bot/attack_hand(mob/living/carbon/human/user, list/modifiers) + if(!user.combat_mode) + ui_interact(user) + return + return ..() + +/mob/living/basic/bot/attack_ai(mob/user) + if(!topic_denied(user)) + ui_interact(user) + return + to_chat(user, span_warning("[src]'s interface is not responding!")) + +/mob/living/basic/bot/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SimpleBot", name) + ui.open() + +/mob/living/basic/bot/AltClick(mob/user) + . = ..() + if(!can_interact(user)) + return + if(!user.can_perform_action(src, ALLOW_SILICON_REACH)) + return + unlock_with_id(user) + +/mob/living/basic/bot/proc/unlock_with_id(mob/living/user) + if(bot_access_flags & BOT_COVER_EMAGGED) + balloon_alert(user, "error!") + return + if(bot_access_flags & BOT_MAINTS_PANEL_OPEN) + balloon_alert(user, "access panel must be closed!") + return + if(!check_access(user)) + balloon_alert(user, "no access") + return + bot_access_flags ^= BOT_CONTROL_PANEL_OPEN + to_chat(user, span_notice("Controls are now [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "unlocked" : "locked"].")) + return TRUE + +/mob/living/basic/bot/screwdriver_act(mob/living/user, obj/item/tool) + . = TOOL_ACT_TOOLTYPE_SUCCESS + if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN)) + to_chat(user, span_warning("The maintenance panel is locked!")) + return + + tool.play_tool_sound(src) + bot_access_flags ^= BOT_MAINTS_PANEL_OPEN + to_chat(user, span_notice("The maintenance panel is now [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "opened" : "closed"].")) + +/mob/living/basic/bot/welder_act(mob/living/user, obj/item/tool) + user.changeNext_move(CLICK_CD_MELEE) + if(user.combat_mode) + return FALSE + + . = TOOL_ACT_TOOLTYPE_SUCCESS + + if(health >= maxHealth) + user.balloon_alert(user, "no repairs needed!") + return + + if(!(bot_access_flags & BOT_MAINTS_PANEL_OPEN)) + user.balloon_alert(user, "maintenance panel closed!") + return + + if(!tool.use_tool(src, user, 0 SECONDS, volume=40)) + return + + heal_overall_damage(10) + user.visible_message(span_notice("[user] repairs [src]!"),span_notice("You repair [src].")) + +/mob/living/basic/bot/attackby(obj/item/attacking_item, mob/living/user, params) + if(attacking_item.GetID()) + unlock_with_id(user) + return + + if(istype(attacking_item, /obj/item/pai_card)) + insertpai(user, attacking_item) + return + + if(attacking_item.tool_behaviour != TOOL_HEMOSTAT || !paicard) + return ..() + + if(bot_access_flags & BOT_MAINTS_PANEL_OPEN) + balloon_alert(user, "open the access panel!") + return + + balloon_alert(user, "removing pAI...") + if(!do_after(user, 3 SECONDS, target = src) || !paicard) + return + + user.visible_message(span_notice("[user] uses [attacking_item] to pull [paicard] out of [initial(src.name)]!"), \ + span_notice("You pull [paicard] out of [initial(src.name)] with [attacking_item].")) + + ejectpai(user) + +/mob/living/basic/bot/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker) + if(damage_done > 0 && attacking_item.damtype != STAMINA && stat != DEAD) + do_sparks(5, TRUE, src) + . = TRUE + return ..() || . + +/mob/living/basic/bot/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + . = ..() + if(prob(25) || . != BULLET_ACT_HIT) + return + if(hitting_projectile.damage_type != BRUTE && hitting_projectile.damage_type != BURN) + return + if(!hitting_projectile.is_hostile_projectile() || hitting_projectile.damage <= 0) + return + do_sparks(5, TRUE, src) + +/mob/living/basic/bot/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + new /obj/effect/temp_visual/emp(loc) + if(paicard) + paicard.emp_act(severity) + src.visible_message(span_notice("[paicard] flies out of [initial(src.name)]!"), span_warning("You are forcefully ejected from [initial(src.name)]!")) + ejectpai() + + if (QDELETED(src)) + return + + if(bot_mode_flags & BOT_MODE_ON) + turn_off() + else + addtimer(CALLBACK(src, PROC_REF(turn_on)), severity * 30 SECONDS) + + if(!prob(70/severity) || !length(GLOB.uncommon_roundstart_languages)) + return + + remove_all_languages(source = LANGUAGE_EMP) + grant_random_uncommon_language(source = LANGUAGE_EMP) + +/** + * Pass a message to have the bot say() it, passing through our announcement action to potentially also play a sound. + * Optionally pass a frequency to say it on the radio. + */ +/mob/living/basic/bot/proc/speak(message, channel) + if(!message) + return + pa_system.announce(message, channel) + +/mob/living/basic/bot/radio(message, list/message_mods = list(), list/spans, language) + . = ..() + if(.) + return + + if(message_mods[MODE_HEADSET]) + internal_radio.talk_into(src, message, , spans, language, message_mods) + return REDUCE_RANGE + if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT) + internal_radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) + return REDUCE_RANGE + if(message_mods[RADIO_EXTENSION] in GLOB.radiochannels) + internal_radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) + return REDUCE_RANGE + +/mob/living/basic/bot/proc/drop_part(obj/item/drop_item, dropzone) + var/obj/item/item_to_drop + if(ispath(drop_item)) + item_to_drop = new drop_item(dropzone) + else + item_to_drop = drop_item + item_to_drop.forceMove(dropzone) + + if(istype(item_to_drop, /obj/item/stock_parts/cell)) + var/obj/item/stock_parts/cell/dropped_cell = item_to_drop + dropped_cell.charge = 0 + dropped_cell.update_appearance() + return + + if(istype(item_to_drop, /obj/item/storage)) + var/obj/item/storage/storage_to_drop = item_to_drop + storage_to_drop.emptyStorage() + return + + if(!istype(item_to_drop, /obj/item/gun/energy)) + return + var/obj/item/gun/energy/dropped_gun = item_to_drop + dropped_gun.cell.charge = 0 + dropped_gun.update_appearance() + +/mob/living/basic/bot/proc/bot_reset(bypass_ai_reset = FALSE) + SEND_SIGNAL(src, COMSIG_BOT_RESET) + if(length(initial_access)) + access_card.set_access(initial_access) + diag_hud_set_botstat() + diag_hud_set_botmode() + if(bypass_ai_reset || isnull(calling_ai_ref)) + return + var/mob/living/ai_caller = calling_ai_ref.resolve() + if(isnull(ai_caller)) + return + to_chat(ai_caller, span_danger("Call command to a bot has been reset.")) + calling_ai_ref = null + +//PDA control. Some bots, especially MULEs, may have more parameters. +/mob/living/basic/bot/proc/bot_control(command, mob/user, list/user_access = list()) + if(!(bot_mode_flags & BOT_MODE_ON) || bot_access_flags & BOT_COVER_EMAGGED || !(bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands. + return TRUE //ACCESS DENIED + if(client && command != "ejectpai") + bot_control_message(command, user) + // process control input + switch(command) + if("patroloff") + bot_reset() //HOLD IT!! //OBJECTION!! + bot_mode_flags &= ~BOT_MODE_AUTOPATROL + + if("patrolon") + bot_mode_flags |= BOT_MODE_AUTOPATROL + + if("summon") + summon_bot(user, user_access = user_access) + if("ejectpai") + eject_pai_remote(user) + + +/mob/living/basic/bot/proc/bot_control_message(command, user) + if(command == "summon") + return "PRIORITY ALERT:[user] in [get_area_name(user)]!" + return GLOB.command_strings[command] || "Unidentified control sequence received:[command]" + +/mob/living/basic/bot/ui_data(mob/user) + var/list/data = list() + data["can_hack"] = (issilicon(user) || isAdminGhostAI(user)) + data["custom_controls"] = list() + data["emagged"] = bot_access_flags & BOT_COVER_EMAGGED + data["has_access"] = check_access(user) + data["locked"] = !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) + data["settings"] = list() + if(bot_access_flags & BOT_CONTROL_PANEL_OPEN || issilicon(user) || isAdminGhostAI(user)) + data["settings"]["pai_inserted"] = !!paicard + data["settings"]["allow_possession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT + data["settings"]["possession_enabled"] = can_be_possessed + data["settings"]["airplane_mode"] = !(bot_mode_flags & BOT_MODE_REMOTE_ENABLED) + data["settings"]["maintenance_lock"] = !(bot_access_flags & BOT_MAINTS_PANEL_OPEN) + data["settings"]["power"] = bot_mode_flags & BOT_MODE_ON + data["settings"]["patrol_station"] = bot_mode_flags & BOT_MODE_AUTOPATROL + return data + +// Actions received from TGUI +/mob/living/basic/bot/ui_act(action, params) + . = ..() + if(.) + return + if(!check_access(usr)) + to_chat(usr, span_warning("Access denied.")) + return + + if(action == "lock") + bot_access_flags ^= BOT_CONTROL_PANEL_OPEN + + switch(action) + if("power") + if(bot_mode_flags & BOT_MODE_ON) + turn_off() + else + turn_on() + if("maintenance") + bot_access_flags ^= BOT_MAINTS_PANEL_OPEN + if("patrol") + bot_mode_flags ^= BOT_MODE_AUTOPATROL + bot_reset() + if("airplane") + bot_mode_flags ^= BOT_MODE_REMOTE_ENABLED + if("hack") + if(!(issilicon(usr) || isAdminGhostAI(usr))) + return + if(!(bot_access_flags & BOT_COVER_EMAGGED)) + bot_access_flags |= (BOT_COVER_EMAGGED|BOT_COVER_HACKED) + bot_access_flags &= ~BOT_CONTROL_PANEL_OPEN + to_chat(usr, span_warning("You overload [src]'s [hackables].")) + message_admins("Safety lock of [ADMIN_LOOKUPFLW(src)] was disabled by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]") + usr.log_message("disabled safety lock of [src]", LOG_GAME) + bot_reset() + to_chat(src, span_userdanger("(#$*#$^^( OVERRIDE DETECTED")) + to_chat(src, span_boldnotice(get_emagged_message())) + return + if(!(bot_access_flags & BOT_COVER_HACKED)) + to_chat(usr, span_boldannounce("You fail to repair [src]'s [hackables].")) + return + bot_access_flags &= ~(BOT_COVER_EMAGGED|BOT_COVER_HACKED) + to_chat(usr, span_notice("You reset the [src]'s [hackables].")) + usr.log_message("re-enabled safety lock of [src]", LOG_GAME) + bot_reset() + to_chat(src, span_userdanger("Software restored to standard.")) + to_chat(src, span_boldnotice(possessed_message)) + if("eject_pai") + if(!paicard) + return + to_chat(usr, span_notice("You eject [paicard] from [initial(src.name)].")) + ejectpai(usr) + if("toggle_personality") + if (can_be_possessed) + disable_possession(usr) + else + enable_possession(usr) + if("rename") + rename(usr) + +/mob/living/basic/bot/update_icon_state() + icon_state = "[isnull(base_icon_state) ? initial(icon_state) : base_icon_state][bot_mode_flags & BOT_MODE_ON]" + return ..() + +/// Access check proc for bot topics! Remember to place in a bot's individual Topic if desired. +/mob/living/basic/bot/proc/topic_denied(mob/user) + if(!user.can_perform_action(src, ALLOW_SILICON_REACH)) + return TRUE + // 0 for access, 1 for denied. + if(!(bot_access_flags & BOT_COVER_EMAGGED)) //An emagged bot cannot be controlled by humans, silicons can if one hacked it. + return FALSE + if(!(bot_access_flags & BOT_COVER_HACKED)) //Manually emagged by a human - access denied to all. + return TRUE + if(!issilicon(user) && !isAdminGhostAI(user)) //Bot is hacked, so only silicons and admins are allowed access. + return TRUE + + return FALSE + +/// Places a pAI in control of this mob +/mob/living/basic/bot/proc/insertpai(mob/user, obj/item/pai_card/card) + if(paicard) + balloon_alert(user, "slot occupied!") + return + if(key) + balloon_alert(user, "personality already present!") + return + if(!(bot_access_flags & BOT_COVER_OPEN)) + balloon_alert(user, "slot inaccessible!") + return + if(!(bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT)) + balloon_alert(user, "incompatible firmware!") + return + if(isnull(card.pai?.mind)) + balloon_alert(user, "pAI is inactive!") + return + if(!user.transferItemToLoc(card, src)) + return + paicard = card + disable_possession() + paicard.pai.fold_in() + copy_languages(paicard.pai, source_override = LANGUAGE_PAI) + set_active_language(paicard.pai.get_selected_language()) + user.visible_message(span_notice("[user] inserts [card] into [src]!"), span_notice("You insert [card] into [src].")) + paicard.pai.mind.transfer_to(src) + to_chat(src, span_notice("You sense your form change as you are uploaded into [src].")) + name = paicard.pai.name + faction = user.faction.Copy() + log_combat(user, paicard.pai, "uploaded to [initial(src.name)],") + return TRUE + +/mob/living/basic/bot/ghost() + if(stat != DEAD) // Only ghost if we're doing this while alive, the pAI probably isn't dead yet. + return ..() + if(paicard && (!client || stat == DEAD)) + ejectpai() + +/// Ejects a pAI from this bot +/mob/living/basic/bot/proc/ejectpai(mob/user = null, announce = TRUE) + if(isnull(paicard)) + return + + if(paicard.pai) + if(isnull(mind)) + mind.transfer_to(paicard.pai) + else + paicard.pai.key = key + else + ghostize(FALSE) // The pAI card that just got ejected was dead. + + key = null + paicard.forceMove(drop_location()) + var/to_log = user ? user : src + log_combat(to_log, paicard.pai, "ejected [user ? "from [initial(name)]" : ""].") + if(announce) + to_chat(paicard.pai, span_notice("You feel your control fade as [paicard] ejects from [initial(name)].")) + paicard = null + name = initial(name) + faction = initial(faction) + remove_all_languages(source = LANGUAGE_PAI) + get_selected_language() + +/// Ejects the pAI remotely. +/mob/living/basic/bot/proc/eject_pai_remote(mob/user) + if(!check_access(user) || !paicard) + return + speak("Ejecting personality chip.", radio_channel) + ejectpai(user) + +/mob/living/basic/bot/Login() + . = ..() + if(!. || isnull(client)) + return FALSE + diag_hud_set_botmode() + +/mob/living/basic/bot/Logout() + . = ..() + bot_reset() + +/mob/living/basic/bot/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE) + . = ..() + if(!.) + return + update_appearance() + +/mob/living/basic/bot/rust_heretic_act() + adjustBruteLoss(400) + +/mob/living/basic/bot/proc/attempt_access(mob/bot, obj/door_attempt) + SIGNAL_HANDLER + + if(door_attempt.check_access(access_card)) + return ACCESS_ALLOWED + return ACCESS_DISALLOWED + +/mob/living/basic/bot/proc/generate_speak_list() + return null + +/mob/living/basic/bot/proc/provide_additional_access() + var/datum/id_trim/additional_trim = SSid_access.trim_singletons_by_path[additional_access] + if(isnull(additional_trim)) + return + access_card.add_access(additional_trim.access + additional_trim.wildcard_access) + initial_access = access_card.access.Copy() + + +/mob/living/basic/bot/proc/summon_bot(atom/caller, user_access = list(), grant_all_access = FALSE) + if(isAI(caller) && !set_ai_caller(caller)) + return FALSE + bot_reset(bypass_ai_reset = isAI(caller)) + ai_controller?.set_blackboard_key(BB_BOT_SUMMON_TARGET, get_turf(caller)) + var/list/access_to_grant = grant_all_access ? REGION_ACCESS_ALL_STATION : user_access + initial_access + access_card.set_access(access_to_grant) + speak("Responding.", radio_channel) + update_bot_mode(new_mode = BOT_SUMMON) + return TRUE + +/mob/living/basic/bot/proc/set_ai_caller(mob/living/caller) + var/atom/calling_ai = calling_ai_ref?.resolve() + if(!isnull(calling_ai) && calling_ai != src) + return FALSE + calling_ai_ref = WEAKREF(caller) + return TRUE + +/mob/living/basic/bot/proc/update_bot_mode(new_mode, update_hud = TRUE) + mode = new_mode + update_appearance() + if(update_hud) + diag_hud_set_botmode() diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm new file mode 100644 index 00000000000..8784f4c0c5c --- /dev/null +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -0,0 +1,233 @@ +/datum/ai_controller/basic_controller/bot + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_SALUTE_MESSAGES = list( + "salutes", + "nods in appreciation towards", + "fist bumps", + ) + ) + + ai_movement = /datum/ai_movement/jps/bot + idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking + planning_subtrees = list( + /datum/ai_planning_subtree/respond_to_summon, + /datum/ai_planning_subtree/salute_beepsky, + /datum/ai_planning_subtree/find_patrol_beacon, + /datum/ai_planning_subtree/manage_unreachable_list, + ) + max_target_distance = AI_BOT_PATH_LENGTH + ///keys to be reset when the bot is reseted + var/list/reset_keys = list( + BB_BEACON_TARGET, + BB_PREVIOUS_BEACON_TARGET, + BB_BOT_SUMMON_TARGET, + ) + +/datum/ai_controller/basic_controller/bot/TryPossessPawn(atom/new_pawn) + . = ..() + if(. & AI_CONTROLLER_INCOMPATIBLE) + return + RegisterSignal(new_pawn, COMSIG_BOT_RESET, PROC_REF(reset_bot)) + +/datum/ai_controller/basic_controller/bot/able_to_run() + var/mob/living/basic/bot/bot_pawn = pawn + if(!(bot_pawn.bot_mode_flags & BOT_MODE_ON)) + return FALSE + return ..() + +/datum/ai_controller/basic_controller/bot/get_access() + var/mob/living/basic/bot/basic_bot = pawn + return basic_bot.access_card?.access + +/datum/ai_controller/basic_controller/bot/proc/reset_bot() + SIGNAL_HANDLER + + if(!length(reset_keys)) + return + for(var/key in reset_keys) + clear_blackboard_key(key) + +///set the target if we can reach them +/datum/ai_controller/basic_controller/bot/proc/set_if_can_reach(key, target, distance = 10) + if(can_reach_target(target, distance)) + set_blackboard_key(key, target) + return TRUE + return FALSE + +/datum/ai_controller/basic_controller/bot/proc/can_reach_target(target, distance = 10) + if(!isdatum(target)) //we dont need to check if its not a datum! + return TRUE + var/list/path = get_path_to(pawn, target, max_distance = distance, access = get_access()) + if(!length(path)) + return FALSE + return TRUE + +///check if the target is too far away, and delete them if so and add them to the unreachables list +/datum/ai_controller/basic_controller/bot/proc/reachable_key(key, distance = 10) + var/datum/target = blackboard[key] + if(QDELETED(target)) + return FALSE + if(!can_reach_target(target, distance)) + clear_blackboard_key(key) + set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE) + return FALSE + return TRUE + +/// subtree to manage our list of unreachables, we reset it every 15 seconds +/datum/ai_planning_subtree/manage_unreachable_list + +/datum/ai_planning_subtree/manage_unreachable_list/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + controller.queue_behavior(/datum/ai_behavior/manage_unreachable_list, BB_TEMPORARY_IGNORE_LIST) + +/datum/ai_behavior/manage_unreachable_list + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 15 SECONDS + +/datum/ai_behavior/manage_unreachable_list/perform(seconds_per_tick, datum/ai_controller/controller, list_key) + . = ..() + if(!isnull(controller.blackboard[list_key])) + controller.clear_blackboard_key(list_key) + finish_action(controller, TRUE) + + +/datum/ai_planning_subtree/find_patrol_beacon + +/datum/ai_planning_subtree/find_patrol_beacon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/basic/bot/bot_pawn = controller.pawn + if(!(bot_pawn.bot_mode_flags & BOT_MODE_AUTOPATROL) || bot_pawn.mode == BOT_SUMMON) + return + + if(controller.blackboard_key_exists(BB_BEACON_TARGET)) + bot_pawn.update_bot_mode(new_mode = BOT_PATROL) + controller.queue_behavior(/datum/ai_behavior/travel_towards/beacon, BB_BEACON_TARGET) + return + + if(controller.blackboard_key_exists(BB_PREVIOUS_BEACON_TARGET)) + controller.queue_behavior(/datum/ai_behavior/find_next_beacon_target, BB_BEACON_TARGET) + return + + controller.queue_behavior(/datum/ai_behavior/find_first_beacon_target, BB_BEACON_TARGET) + +/datum/ai_behavior/find_first_beacon_target + +/datum/ai_behavior/find_first_beacon_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/closest_distance = INFINITY + var/mob/living/basic/bot/bot_pawn = controller.pawn + var/atom/final_target + var/atom/previous_target = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) + if(beacon == previous_target) + continue + var/dist = get_dist(bot_pawn, beacon) + if(dist > closest_distance) + continue + closest_distance = dist + final_target = beacon + + if(isnull(final_target)) + finish_action(controller, FALSE) + return + controller.set_blackboard_key(BB_BEACON_TARGET, final_target) + finish_action(controller, TRUE) + +/datum/ai_behavior/find_next_beacon_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/bot/bot_pawn = controller.pawn + var/atom/final_target + var/obj/machinery/navbeacon/prev_beacon = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] + if(QDELETED(prev_beacon)) + finish_action(controller, FALSE) + return + + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) + if(beacon.location == prev_beacon.codes[NAVBEACON_PATROL_NEXT]) + final_target = beacon + break + + if(isnull(final_target)) + controller.clear_blackboard_key(BB_PREVIOUS_BEACON_TARGET) + finish_action(controller, FALSE) + + controller.set_blackboard_key(BB_BEACON_TARGET, final_target) + finish_action(controller, TRUE) + + +/datum/ai_behavior/travel_towards/beacon + clear_target = TRUE + +/datum/ai_behavior/travel_towards/beacon/finish_action(datum/ai_controller/controller, succeeded, target_key) + var/atom/target = controller.blackboard[target_key] + controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, target) + return ..() + +/datum/ai_planning_subtree/respond_to_summon + +/datum/ai_planning_subtree/respond_to_summon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_BOT_SUMMON_TARGET)) + return + controller.clear_blackboard_key(BB_PREVIOUS_BEACON_TARGET) + controller.clear_blackboard_key(BB_BEACON_TARGET) + controller.queue_behavior(/datum/ai_behavior/travel_towards/bot_summon, BB_BOT_SUMMON_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_behavior/travel_towards/bot_summon + clear_target = TRUE + +/datum/ai_behavior/travel_towards/bot_summon/finish_action(datum/ai_controller/controller, succeeded, target_key) + var/mob/living/basic/bot/bot_pawn = controller.pawn + bot_pawn.calling_ai_ref = null + bot_pawn.update_bot_mode(new_mode = BOT_IDLE) + return ..() + +/datum/ai_planning_subtree/salute_beepsky + +/datum/ai_planning_subtree/salute_beepsky/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/basic/bot/bot_pawn = controller.pawn + //we are criminals, dont salute the dirty pigs + if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + return + if(controller.blackboard_key_exists(BB_SALUTE_TARGET)) + controller.queue_behavior(/datum/ai_behavior/salute_beepsky, BB_SALUTE_TARGET, BB_SALUTE_MESSAGES) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_beepsky, BB_SALUTE_TARGET) + + +/datum/ai_behavior/find_and_set/valid_beepsky + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 30 SECONDS + +/datum/ai_behavior/find_and_set/valid_beepsky/search_tactic(datum/ai_controller/controller, locate_path, search_range) + for(var/mob/living/simple_animal/bot/secbot/robot in oview(search_range, controller.pawn)) + if(!(robot.bot_mode_flags & BOT_MODE_ON)) + continue + return robot + + return null + +/datum/ai_behavior/salute_beepsky + +/datum/ai_behavior/salute_beepsky/perform(seconds_per_tick, datum/ai_controller/controller, target_key, salute_keys) + . = ..() + if(!controller.blackboard_key_exists(target_key)) + finish_action(controller, FALSE, target_key) + return + var/list/salute_list = controller.blackboard[salute_keys] + if(!length(salute_list)) + finish_action(controller, FALSE, target_key) + return + var/mob/living/basic/bot/bot_pawn = controller.pawn + //special interaction if we are wearing a fedora + var/obj/item/our_hat = (locate(/obj/item/clothing/head) in bot_pawn) + if(our_hat) + salute_list += "tips [our_hat] at " + + bot_pawn.manual_emote(pick(salute_list) + " [controller.blackboard[target_key]]") + finish_action(controller, TRUE, target_key) + return + +/datum/ai_behavior/salute_beepsky/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/bots/bot_hud.dm b/code/modules/mob/living/basic/bots/bot_hud.dm new file mode 100644 index 00000000000..d9b429a32bf --- /dev/null +++ b/code/modules/mob/living/basic/bots/bot_hud.dm @@ -0,0 +1,39 @@ +/mob/living/basic/bot/proc/diag_hud_set_bothealth() + var/image/holder = hud_list[DIAG_HUD] + var/icon/icon_image = icon(icon, icon_state, dir) + holder.pixel_y = icon_image.Height() - world.icon_size + holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]" + +/mob/living/basic/bot/proc/diag_hud_set_botstat() //On (With wireless on or off), Off, EMP'ed + var/image/holder = hud_list[DIAG_STAT_HUD] + var/icon/our_icon = icon(icon, icon_state, dir) + holder.pixel_y = our_icon.Height() - world.icon_size + if(bot_mode_flags & BOT_MODE_ON) + holder.icon_state = "hudstat" + return + if(stat != CONSCIOUS) + holder.icon_state = "hudoffline" + return + holder.icon_state = "huddead2" + +/mob/living/basic/bot/proc/diag_hud_set_botmode() //Shows a bot's current operation + var/image/holder = hud_list[DIAG_BOT_HUD] + var/icon/icon_image = icon(icon, icon_state, dir) + holder.pixel_y = icon_image.Height() - world.icon_size + 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) //STOP RIGHT THERE, CRIMINAL SCUM! + holder.icon_state = "hudalert" + if(BOT_MOVING, BOT_DELIVER, BOT_GO_HOME, BOT_NAV) //Moving to target for normal bots, moving to deliver or go home for MULES. + holder.icon_state = "hudmove" + else + holder.icon_state = "" diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm new file mode 100644 index 00000000000..fd3b522ab23 --- /dev/null +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -0,0 +1,375 @@ +#define TEND_DAMAGE_INTERACTION "tend_damage_interaction" + +/mob/living/basic/bot/medbot + name = "\improper Medibot" + desc = "A little medical robot. He looks somewhat underwhelmed." + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "medibot0" + base_icon_state = "medibot" + density = FALSE + anchored = FALSE + health = 20 + maxHealth = 20 + pass_flags = PASSMOB | PASSFLAPS + status_flags = (CANPUSH | CANSTUN) + ai_controller = /datum/ai_controller/basic_controller/bot/medbot + + maints_access_required = list(ACCESS_ROBOTICS, ACCESS_MEDICAL) + radio_key = /obj/item/encryptionkey/headset_med + radio_channel = RADIO_CHANNEL_MEDICAL + bot_type = MED_BOT + data_hud_type = DATA_HUD_MEDICAL_ADVANCED + hackables = "health processor circuits" + possessed_message = "You are a medbot! Ensure good health among the crew to the best of your ability!" + + additional_access = /datum/id_trim/job/paramedic + announcement_type = /datum/action/cooldown/bot_announcement/medbot + + ///anouncements when we find a target to heal + var/static/list/wait_announcements = list( + MEDIBOT_VOICED_HOLD_ON = 'sound/voice/medbot/coming.ogg', + MEDIBOT_VOICED_WANT_TO_HELP = 'sound/voice/medbot/help.ogg', + MEDIBOT_VOICED_YOU_ARE_INJURED = 'sound/voice/medbot/injured.ogg', + ) + + ///announcements after we heal someone + var/static/list/afterheal_announcements = list( + MEDIBOT_VOICED_ALL_PATCHED_UP = 'sound/voice/medbot/patchedup.ogg', + MEDIBOT_VOICED_APPLE_A_DAY = 'sound/voice/medbot/apple.ogg', + MEDIBOT_VOICED_FEEL_BETTER = 'sound/voice/medbot/feelbetter.ogg', + ) + + ///announcements when we are healing someone near death + var/static/list/near_death_announcements = list( + MEDIBOT_VOICED_STAY_WITH_ME = 'sound/voice/medbot/no.ogg', + MEDIBOT_VOICED_LIVE = 'sound/voice/medbot/live.ogg', + MEDIBOT_VOICED_NEVER_LOST = 'sound/voice/medbot/lost.ogg', + ) + ///announcements when we are idle + var/static/list/idle_lines = list( + MEDIBOT_VOICED_DELICIOUS = 'sound/voice/medbot/delicious.ogg', + MEDIBOT_VOICED_PLASTIC_SURGEON = 'sound/voice/medbot/surgeon.ogg', + MEDIBOT_VOICED_MASK_ON = 'sound/voice/medbot/radar.ogg', + MEDIBOT_VOICED_ALWAYS_A_CATCH = 'sound/voice/medbot/catch.ogg', + MEDIBOT_VOICED_LIKE_FLIES = 'sound/voice/medbot/flies.ogg', + MEDIBOT_VOICED_SUFFER = 'sound/voice/medbot/why.ogg', + ) + ///announcements when we are emagged + var/static/list/emagged_announcements = list( + MEDIBOT_VOICED_FUCK_YOU = 'sound/voice/medbot/fuck_you.ogg', + MEDIBOT_VOICED_NOT_A_GAME = 'sound/voice/medbot/turn_off.ogg', + MEDIBOT_VOICED_IM_DIFFERENT = 'sound/voice/medbot/im_different.ogg', + MEDIBOT_VOICED_FOURTH_WALL = 'sound/voice/medbot/close.ogg', + MEDIBOT_VOICED_SHINDEMASHOU = 'sound/voice/medbot/shindemashou.ogg', + ) + ///announcements when we are being tipped + var/static/list/tipped_announcements = list( + MEDIBOT_VOICED_WAIT = 'sound/voice/medbot/hey_wait.ogg', + MEDIBOT_VOICED_DONT = 'sound/voice/medbot/please_dont.ogg', + MEDIBOT_VOICED_TRUSTED_YOU = 'sound/voice/medbot/i_trusted_you.ogg', + MEDIBOT_VOICED_NO_SAD = 'sound/voice/medbot/nooo.ogg', + MEDIBOT_VOICED_OH_FUCK = 'sound/voice/medbot/oh_fuck.ogg', + ) + ///announcements when we are being untipped + var/static/list/untipped_announcements = list( + MEDIBOT_VOICED_FORGIVE = 'sound/voice/medbot/forgive.ogg', + MEDIBOT_VOICED_THANKS = 'sound/voice/medbot/thank_you.ogg', + MEDIBOT_VOICED_GOOD_PERSON = 'sound/voice/medbot/youre_good.ogg', + ) + ///announcements when we are worried + var/static/list/worried_announcements = list( + MEDIBOT_VOICED_PUT_BACK = 'sound/voice/medbot/please_put_me_back.ogg', + MEDIBOT_VOICED_IM_SCARED = 'sound/voice/medbot/please_im_scared.ogg', + MEDIBOT_VOICED_NEED_HELP = 'sound/voice/medbot/dont_like.ogg', + MEDIBOT_VOICED_THIS_HURTS = 'sound/voice/medbot/pain_is_real.ogg', + MEDIBOT_VOICED_THE_END = 'sound/voice/medbot/is_this_the_end.ogg', + MEDIBOT_VOICED_NOOO = 'sound/voice/medbot/nooo.ogg', + ) + var/static/list/misc_announcements= list( + MEDIBOT_VOICED_CHICKEN = 'sound/voice/medbot/i_am_chicken.ogg', + ) + /// drop determining variable + var/health_analyzer = /obj/item/healthanalyzer + /// drop determining variable + var/medkit_type = /obj/item/storage/medkit + ///based off medkit_X skins in aibots.dmi for your selection; X goes here IE medskin_tox means skin var should be "tox" + var/skin + /// How much healing do we do at a time? + var/heal_amount = 2.5 + /// Start healing when they have this much damage in a category + var/heal_threshold = 10 + /// What damage type does this bot support. Because the default is brute, if the medkit is brute-oriented there is a slight bonus to healing. set to "all" for it to heal any of the 4 base damage types + var/damage_type_healer = BRUTE + + ///Flags Medbots use to decide how they should be acting. + var/medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_SPEAK_MODE + //Selections: MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE | MEDBOT_TIPPED_MODE + + /// techweb linked to the medbot + var/datum/techweb/linked_techweb + ///our tipper + var/datum/weakref/tipper + +/mob/living/basic/bot/medbot/proc/set_speech_keys() + if(isnull(ai_controller)) + return + ai_controller.set_blackboard_key(BB_NEAR_DEATH_SPEECH, near_death_announcements) + ai_controller.set_blackboard_key(BB_WAIT_SPEECH, wait_announcements) + ai_controller.set_blackboard_key(BB_AFTERHEAL_SPEECH, afterheal_announcements) + ai_controller.set_blackboard_key(BB_IDLE_SPEECH, idle_lines) + ai_controller.set_blackboard_key(BB_EMAGGED_SPEECH, emagged_announcements) + ai_controller.set_blackboard_key(BB_WORRIED_ANNOUNCEMENTS, worried_announcements) + +/mob/living/basic/bot/medbot/Initialize(mapload, new_skin) + . = ..() + set_speech_keys() + + if(!isnull(new_skin)) + skin = new_skin + update_appearance() + AddComponent(/datum/component/tippable, \ + tip_time = 3 SECONDS, \ + untip_time = 3 SECONDS, \ + self_right_time = 3.5 MINUTES, \ + pre_tipped_callback = CALLBACK(src, PROC_REF(pre_tip_over)), \ + post_tipped_callback = CALLBACK(src, PROC_REF(after_tip_over)), \ + post_untipped_callback = CALLBACK(src, PROC_REF(after_righted))) + var/static/list/hat_offsets = list(4,-9) + AddElement(/datum/element/hat_wearer, offsets = hat_offsets) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + + + if(!HAS_TRAIT(SSstation, STATION_TRAIT_MEDBOT_MANIA) || !mapload || !is_station_level(z)) + return + skin = "advanced" + update_appearance(UPDATE_OVERLAYS) + damage_type_healer = HEAL_ALL_DAMAGE + if(prob(50)) + name += ", PhD." + + return INITIALIZE_HINT_LATELOAD + +/mob/living/basic/bot/medbot/LateInitialize() + . = ..() + if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) + CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) + +/mob/living/basic/bot/medbot/update_icon_state() + . = ..() + if(!(bot_mode_flags & BOT_MODE_ON)) + icon_state = "[base_icon_state]0" + return + if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) + icon_state = "[base_icon_state]a" + return + if(mode == BOT_HEALING) + icon_state = "[base_icon_state]s[medical_mode_flags & MEDBOT_STATIONARY_MODE]" + return + icon_state = "[base_icon_state][medical_mode_flags & MEDBOT_STATIONARY_MODE ? 2 : 1]" //Bot has yellow light to indicate stationary mode. + +/mob/living/basic/bot/medbot/update_overlays() + . = ..() + if(skin) + . += "medskin_[skin]" + +//this is sin +/mob/living/basic/bot/medbot/generate_speak_list() + var/static/list/finalized_speak_list = (idle_lines + wait_announcements + afterheal_announcements + near_death_announcements + emagged_announcements + tipped_announcements + untipped_announcements + worried_announcements + misc_announcements) + return finalized_speak_list + + +/mob/living/basic/bot/medbot/attack_paw(mob/user, list/modifiers) + return attack_hand(user, modifiers) + +/mob/living/basic/bot/medbot/multitool_act(mob/living/user, obj/item/multitool/tool) + if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) + linked_techweb = tool.buffer + return TRUE + +// Variables sent to TGUI +/mob/living/basic/bot/medbot/ui_data(mob/user) + var/list/data = ..() + if((bot_access_flags & BOT_CONTROL_PANEL_OPEN) || issilicon(user) || isAdminGhostAI(user)) + data["custom_controls"]["heal_threshold"] = heal_threshold + data["custom_controls"]["speaker"] = medical_mode_flags & MEDBOT_SPEAK_MODE + data["custom_controls"]["crit_alerts"] = medical_mode_flags & MEDBOT_DECLARE_CRIT + data["custom_controls"]["stationary_mode"] = medical_mode_flags & MEDBOT_STATIONARY_MODE + data["custom_controls"]["sync_tech"] = TRUE + return data + +// Actions received from TGUI +/mob/living/basic/bot/medbot/ui_act(action, params) + . = ..() + if(. || !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !usr.has_unlimited_silicon_privilege) + return + + switch(action) + if("heal_threshold") + var/adjust_num = round(text2num(params["threshold"])) + heal_threshold = adjust_num + if(heal_threshold < 5) + heal_threshold = 5 + if(heal_threshold > 75) + heal_threshold = 75 + if("speaker") + medical_mode_flags ^= MEDBOT_SPEAK_MODE + if("crit_alerts") + medical_mode_flags ^= MEDBOT_DECLARE_CRIT + if("stationary_mode") + medical_mode_flags ^= MEDBOT_STATIONARY_MODE + if("sync_tech") + if(!linked_techweb) + to_chat(usr, span_notice("No research techweb connected.")) + return + var/oldheal_amount = heal_amount + var/tech_boosters + for(var/index in linked_techweb.researched_designs) + var/datum/design/surgery/healing/design = SSresearch.techweb_design_by_id(index) + if(!istype(design)) + continue + tech_boosters++ + if(tech_boosters) + heal_amount = (round(tech_boosters/2,0.1)*initial(heal_amount))+initial(heal_amount) //every 2 tend wounds tech gives you an extra 100% healing, adjusting for unique branches (combo is bonus) + if(oldheal_amount < heal_amount) + speak("New knowledge found! Surgical efficacy improved to [round(heal_amount/initial(heal_amount)*100)]%!") + + update_appearance() + +/mob/living/basic/bot/medbot/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(!(bot_access_flags & BOT_COVER_EMAGGED)) + return + medical_mode_flags &= ~MEDBOT_DECLARE_CRIT + balloon_alert(user, "reagent synthesis circuits shorted") + audible_message(span_danger("[src] buzzes oddly!")) + flick("medibot_spark", src) + playsound(src, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + return TRUE + +/* + * Proc used in a callback for before this medibot is tipped by the tippable component. + * + * user - the mob who is tipping us over + */ +/mob/living/basic/bot/medbot/proc/pre_tip_over(mob/user) + speak(pick(worried_announcements)) + +/* + * Proc used in a callback for after this medibot is tipped by the tippable component. + * + * user - the mob who tipped us over + */ +/mob/living/basic/bot/medbot/proc/after_tip_over(mob/user) + medical_mode_flags |= MEDBOT_TIPPED_MODE + tipper = WEAKREF(user) + playsound(src, 'sound/machines/warning-buzzer.ogg', 50) + +/* + * Proc used in a callback for after this medibot is righted, either by themselves or by a mob, by the tippable component. + * + * user - the mob who righted us. Can be null. + */ +/mob/living/basic/bot/medbot/proc/after_righted(mob/user) + var/mob/tipper_mob = isnull(user) ? null : tipper?.resolve() + tipper = null + medical_mode_flags &= ~MEDBOT_TIPPED_MODE + if(isnull(tipper_mob)) + return + if(tipper_mob == user) + speak(MEDIBOT_VOICED_FORGIVE) + return + speak(pick(untipped_announcements)) + +/mob/living/basic/bot/medbot/proc/pre_attack(mob/living/puncher, atom/target) + SIGNAL_HANDLER + + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + return + if(!iscarbon(target)) + return + // SKYRAT EDIT ADDITION START - Skip trying to heal synths + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(human_target.mob_biotypes & MOB_ROBOTIC) + return + // SKYRAT EDIT ADDITION END + INVOKE_ASYNC(src, PROC_REF(medicate_patient), target) + return COMPONENT_HOSTILE_NO_ATTACK + +/mob/living/basic/bot/medbot/proc/medicate_patient(mob/living/carbon/human/patient) + if(DOING_INTERACTION(src, TEND_DAMAGE_INTERACTION)) + return + + update_bot_mode(new_mode = BOT_HEALING, update_hud = FALSE) + if(!do_after(src, delay = 2.5 SECONDS, target = patient, interaction_key = TEND_DAMAGE_INTERACTION)) //SKYRAT EDIT CHANGE : Increased time as tradeoff for automated healing. ORIGINAL: if(!do_after(src, delay = 0.5 SECONDS, target = patient, interaction_key = TEND_DAMAGE_INTERACTION)) + update_bot_mode(new_mode = BOT_IDLE) + return + if(bot_access_flags & BOT_COVER_EMAGGED) + patient.reagents?.add_reagent(/datum/reagent/toxin/chloralhydrate, 5) + else if(damage_type_healer == HEAL_ALL_DAMAGE) + patient.heal_overall_damage(heal_amount) + else + patient.heal_damage_type(heal_amount = heal_amount, damagetype = damage_type_healer) + update_bot_mode(new_mode = BOT_IDLE) + +/mob/living/basic/bot/medbot/autopatrol + bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION + +/mob/living/basic/bot/medbot/stationary + medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE + +/mob/living/basic/bot/medbot/mysterious + name = "\improper Mysterious Medibot" + desc = "International Medibot of mystery." + skin = "bezerk" + damage_type_healer = HEAL_ALL_DAMAGE + heal_amount = 10 + +/mob/living/basic/bot/medbot/derelict + name = "\improper Old Medibot" + desc = "Looks like it hasn't been modified since the late 2080s." + skin = "bezerk" + damage_type_healer = HEAL_ALL_DAMAGE + medical_mode_flags = MEDBOT_SPEAK_MODE + heal_threshold = 0 + heal_amount = 5 + +/mob/living/basic/bot/medbot/nukie + name = "Oppenheimer" + desc = "A medibot stolen from a Nanotrasen station and upgraded by the Syndicate. Despite their best efforts at reprogramming, it still appears visibly upset near nuclear explosives." + skin = "bezerk" + health = 40 + maxHealth = 40 + maints_access_required = list(ACCESS_SYNDICATE) + radio_key = /obj/item/encryptionkey/syndicate + radio_channel = RADIO_CHANNEL_SYNDICATE + damage_type_healer = HEAL_ALL_DAMAGE + faction = list(ROLE_SYNDICATE) + heal_threshold = 0 + heal_amount = 5 + additional_access = /datum/id_trim/syndicom/crew + +/mob/living/basic/bot/medbot/nukie/Initialize(mapload, new_skin) + . = ..() + RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_DISARMED, PROC_REF(nuke_disarm)) + RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_ARMED, PROC_REF(nuke_arm)) + RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_DETONATING, PROC_REF(nuke_detonate)) + internal_radio.set_frequency(FREQ_SYNDICATE) + internal_radio.freqlock = RADIO_FREQENCY_LOCKED + +/mob/living/basic/bot/medbot/nukie/proc/nuke_disarm() + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(speak), pick(untipped_announcements)) + +/mob/living/basic/bot/medbot/nukie/proc/nuke_arm() + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(speak), pick(worried_announcements)) + +/mob/living/basic/bot/medbot/nukie/proc/nuke_detonate() + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(speak), pick(emagged_announcements)) + +#undef TEND_DAMAGE_INTERACTION diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm new file mode 100644 index 00000000000..c20614a407f --- /dev/null +++ b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm @@ -0,0 +1,209 @@ +#define BOT_PATIENT_PATH_LIMIT 20 +/datum/ai_controller/basic_controller/bot/medbot + planning_subtrees = list( + /datum/ai_planning_subtree/manage_unreachable_list, + /datum/ai_planning_subtree/respond_to_summon, + /datum/ai_planning_subtree/handle_medbot_speech, + /datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit, + /datum/ai_planning_subtree/treat_wounded_target, + /datum/ai_planning_subtree/salute_beepsky, + /datum/ai_planning_subtree/find_patrol_beacon, + ) + ai_movement = /datum/ai_movement/jps/bot/medbot + reset_keys = list( + BB_PATIENT_TARGET, + BB_BEACON_TARGET, + BB_PREVIOUS_BEACON_TARGET, + BB_BOT_SUMMON_TARGET, + ) + +/datum/ai_movement/jps/bot/medbot + +// only AI isnt allowed to move when this flag is set, sentient players can +/datum/ai_movement/jps/bot/medbot/allowed_to_move(datum/move_loop/source) + var/datum/ai_controller/controller = source.extra_info + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn + if(bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) + return FALSE + return ..() + + +/datum/ai_planning_subtree/treat_wounded_target + +/datum/ai_planning_subtree/treat_wounded_target/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn + if(bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE) + controller.clear_blackboard_key(BB_PATIENT_TARGET) + return + var/reach_distance = (bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) ? 1 : BOT_PATIENT_PATH_LIMIT + if(controller.reachable_key(BB_PATIENT_TARGET, reach_distance)) + controller.queue_behavior(/datum/ai_behavior/tend_to_patient, BB_PATIENT_TARGET, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.bot_access_flags) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.queue_behavior(/datum/ai_behavior/find_suitable_patient, BB_PATIENT_TARGET, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.medical_mode_flags, bot_pawn.bot_access_flags) + +/datum/ai_behavior/find_suitable_patient + var/search_range = 7 + action_cooldown = 2 SECONDS + +/datum/ai_behavior/find_suitable_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, threshold, heal_type, mode_flags, access_flags) + . = ..() + search_range = (mode_flags & MEDBOT_STATIONARY_MODE) ? 1 : initial(search_range) + var/list/ignore_keys = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] + for(var/mob/living/carbon/human/treatable_target in oview(search_range, controller.pawn)) + if(LAZYACCESS(ignore_keys, treatable_target) || treatable_target.stat == DEAD) + continue + if((access_flags & BOT_COVER_EMAGGED) && treatable_target.stat == CONSCIOUS) + controller.set_blackboard_key(BB_PATIENT_TARGET, treatable_target) + break + if((heal_type == HEAL_ALL_DAMAGE)) + if(treatable_target.get_total_damage() > threshold) + controller.set_blackboard_key(BB_PATIENT_TARGET, treatable_target) + break + continue + if(treatable_target.get_current_damage_of_type(damagetype = heal_type) > threshold) + controller.set_blackboard_key(BB_PATIENT_TARGET, treatable_target) + break + + finish_action(controller, controller.blackboard_key_exists(BB_PATIENT_TARGET)) + +/datum/ai_behavior/find_suitable_patient/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + if(!succeeded || get_dist(controller.pawn, controller.blackboard[target_key]) <= 1) + return + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + announcement?.announce(pick(controller.blackboard[BB_WAIT_SPEECH])) + +/datum/ai_behavior/tend_to_patient + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH + +/datum/ai_behavior/tend_to_patient/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/tend_to_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, threshold, damage_type_healer, access_flags) + . = ..() + var/mob/living/carbon/human/patient = controller.blackboard[target_key] + if(QDELETED(patient) || patient.stat == DEAD) + finish_action(controller, FALSE, target_key) + return + if(check_if_healed(patient, threshold, damage_type_healer, access_flags)) + finish_action(controller, TRUE, target_key, healed_target = TRUE) + return + + var/mob/living/basic/bot/bot_pawn = controller.pawn + if(patient.stat >= HARD_CRIT && prob(5)) + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + announcement?.announce(pick(controller.blackboard[BB_NEAR_DEATH_SPEECH])) + bot_pawn.melee_attack(patient) + finish_action(controller, TRUE, target_key) + +// only clear the target if they get healed +/datum/ai_behavior/tend_to_patient/finish_action(datum/ai_controller/controller, succeeded, target_key, healed_target = FALSE) + . = ..() + if(!succeeded) + return + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target) || !healed_target) + return + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + announcement?.announce(pick(controller.blackboard[BB_AFTERHEAL_SPEECH])) + controller.clear_blackboard_key(target_key) + +/datum/ai_behavior/tend_to_patient/proc/check_if_healed(mob/living/carbon/human/patient, threshold, damage_type_healer, access_flags) + if(access_flags & BOT_COVER_EMAGGED) + return (patient.stat > CONSCIOUS) + var/patient_damage = (damage_type_healer == HEAL_ALL_DAMAGE) ? patient.get_total_damage() : patient.get_current_damage_of_type(damagetype = damage_type_healer) + return (patient_damage <= threshold) + + +/datum/ai_planning_subtree/handle_medbot_speech + var/speech_chance = 5 + +/datum/ai_planning_subtree/handle_medbot_speech/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn + //we cant speak! + if(!(bot_pawn.medical_mode_flags & MEDBOT_SPEAK_MODE)) + return + + var/currently_tipped = bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE + speech_chance = ((bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) || currently_tipped) ? 15 : initial(speech_chance) + + if(!SPT_PROB(speech_chance, seconds_per_tick)) + return + + controller.queue_behavior(/datum/ai_behavior/handle_medbot_speech, BB_ANNOUNCE_ABILITY, bot_pawn.mode, bot_pawn.bot_access_flags, currently_tipped) + +/datum/ai_behavior/handle_medbot_speech + action_cooldown = 20 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/handle_medbot_speech/perform(seconds_per_tick, datum/ai_controller/controller, announce_key, mode, cover_flags, currently_tipped) + . = ..() + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[announce_key] + var/list/speech_to_pick_from + + if(currently_tipped) + speech_to_pick_from = controller.blackboard[BB_WORRIED_ANNOUNCEMENTS] + else if(cover_flags & BOT_COVER_EMAGGED) + speech_to_pick_from = controller.blackboard[BB_EMAGGED_SPEECH] + else if(mode == BOT_IDLE) + speech_to_pick_from = controller.blackboard[BB_IDLE_SPEECH] + var/mob/living/living_pawn = controller.pawn + + if(locate(/obj/item/clothing/head/costume/chicken) in living_pawn) + speech_to_pick_from += MEDIBOT_VOICED_CHICKEN + + if(!length(speech_to_pick_from)) + finish_action(controller, FALSE) + return + + announcement.announce(pick(speech_to_pick_from)) + finish_action(controller, TRUE) + +/datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit + target_key = BB_PATIENT_IN_CRIT + hunting_behavior = /datum/ai_behavior/announce_patient + finding_behavior = /datum/ai_behavior/find_hunt_target/patient_in_crit + hunt_targets = list(/mob/living/carbon/human) + finish_planning = FALSE + +/datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn + if(!(bot_pawn.medical_mode_flags & MEDBOT_DECLARE_CRIT)) + return + return ..() + +/datum/ai_behavior/find_hunt_target/patient_in_crit + +/datum/ai_behavior/find_hunt_target/patient_in_crit/valid_dinner(mob/living/source, mob/living/carbon/human/patient, radius) + if(patient.stat < UNCONSCIOUS || isnull(patient.mind)) + return FALSE + return can_see(source, patient, radius) + +/datum/ai_behavior/announce_patient + action_cooldown = 30 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/announce_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) + . = ..() + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + finish_action(controller, FALSE) + return + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + if(QDELETED(announcement)) + finish_action(controller, FALSE) + return + var/text_to_announce = "Medical emergency! [living_target] is in critical condition at [get_area(living_target)]!" + announcement.announce(text_to_announce, controller.blackboard[BB_RADIO_CHANNEL]) + finish_action(controller, TRUE) + +/datum/ai_behavior/announce_patient/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +#undef BOT_PATIENT_PATH_LIMIT diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index ab291da5a32..1264e7c884d 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -601,17 +601,21 @@ to_chat(src, span_danger("Selected location is not visible.")) /mob/living/silicon/ai/proc/call_bot(turf/waypoint) - var/mob/living/simple_animal/bot/bot = bot_ref?.resolve() + var/mob/living/bot = bot_ref?.resolve() if(!bot) return + var/summon_success + if(isbasicbot(bot)) + var/mob/living/basic/bot/basic_bot = bot + summon_success = basic_bot.summon_bot(src, grant_all_access = TRUE) + else + var/mob/living/simple_animal/bot/simple_bot = bot + call_bot_cooldown = world.time + CALL_BOT_COOLDOWN + summon_success = simple_bot.call_bot(src, waypoint) + call_bot_cooldown = 0 - if(bot.calling_ai && bot.calling_ai != src) //Prevents an override if another AI is controlling this bot. - to_chat(src, span_danger("Interface error. Unit is already in use.")) - return - to_chat(src, span_notice("Sending command to bot...")) - call_bot_cooldown = world.time + CALL_BOT_COOLDOWN - bot.call_bot(src, waypoint) - call_bot_cooldown = 0 + var/chat_message = summon_success ? "Sending command to bot..." : "Interface error. Unit is already in use." + to_chat(src, span_notice("[chat_message]")) /mob/living/silicon/ai/proc/alarm_triggered(datum/source, alarm_type, area/source_area) SIGNAL_HANDLER diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 9eca91ab1e6..408ca50c75c 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -709,6 +709,9 @@ Pass a positive integer as an argument to override a bot's default speed. access_card.set_access(prev_access) /mob/living/simple_animal/bot/proc/call_bot(caller, turf/waypoint, message = TRUE) + if(isAI(caller) && calling_ai && calling_ai != src) //Prevents an override if another AI is controlling this bot. + return FALSE + bot_reset() //Reset a bot before setting it to call mode. //For giving the bot temporary all-access. This method is bad and makes me feel bad. Refactoring access to a component is for another PR. @@ -737,6 +740,8 @@ Pass a positive integer as an argument to override a bot's default speed. calling_ai = null set_path(null) + return TRUE + /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) diff --git a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm index 32df0729c8c..30a30b81fd2 100644 --- a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm +++ b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm @@ -53,3 +53,12 @@ if (length(automated_announcements) && !isnull(automated_announcements[line])) playsound(bot_owner, automated_announcements[line], vol = 50, vary = FALSE) + + +/datum/action/cooldown/bot_announcement/medbot + +/datum/action/cooldown/bot_announcement/medbot/announce(line, channel) + var/mob/living/basic/bot/medbot/bot_owner = owner + if(!(bot_owner.medical_mode_flags & MEDBOT_SPEAK_MODE)) + return + return ..() diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index 12f33dec723..e4e9f593daf 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -291,14 +291,14 @@ if(!can_finish_build(W, user)) return qdel(W) - var/mob/living/simple_animal/bot/medbot/medbot = new(drop_location(), skin) + var/mob/living/basic/bot/medbot/medbot = new(drop_location(), skin) to_chat(user, span_notice("You complete the Medbot. Beep boop!")) medbot.name = created_name medbot.medkit_type = medkit_type medbot.robot_arm = robot_arm - medbot.healthanalyzer = healthanalyzer + medbot.health_analyzer = healthanalyzer var/obj/item/storage/medkit/medkit = medkit_type - medbot.damagetype_healer = initial(medkit.damagetype_healed) ? initial(medkit.damagetype_healed) : BRUTE + medbot.damage_type_healer = initial(medkit.damagetype_healed) ? initial(medkit.damagetype_healed) : BRUTE qdel(src) diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm deleted file mode 100644 index 33fa7baab7a..00000000000 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ /dev/null @@ -1,710 +0,0 @@ -//MEDBOT -//MEDBOT PATHFINDING -//MEDBOT ASSEMBLY -#define MEDBOT_PANIC_NONE 0 -#define MEDBOT_PANIC_LOW 15 -#define MEDBOT_PANIC_MED 35 -#define MEDBOT_PANIC_HIGH 55 -#define MEDBOT_PANIC_FUCK 70 -#define MEDBOT_PANIC_ENDING 90 -#define MEDBOT_PANIC_END 100 - -#define MEDBOT_NEW_PATIENTSPEAK_DELAY (30 SECONDS) -#define MEDBOT_PATIENTSPEAK_DELAY (20 SECONDS) -#define MEDBOT_FREAKOUT_DELAY (15 SECONDS) - -/mob/living/simple_animal/bot/medbot - name = "\improper Medibot" - desc = "A little medical robot. He looks somewhat underwhelmed." - icon = 'icons/mob/silicon/aibots.dmi' - icon_state = "medibot0" - base_icon_state = "medibot" - density = FALSE - anchored = FALSE - health = 20 - maxHealth = 20 - pass_flags = PASSMOB | PASSFLAPS - status_flags = (CANPUSH | CANSTUN) - - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_MEDICAL) - radio_key = /obj/item/encryptionkey/headset_med - radio_channel = RADIO_CHANNEL_MEDICAL - bot_type = MED_BOT - data_hud_type = DATA_HUD_MEDICAL_ADVANCED - hackables = "health processor circuits" - path_image_color = "#DDDDFF" - possessed_message = "You are a medbot! Ensure good health among the crew to the best of your ability!" - - automated_announcements = list( - MEDIBOT_VOICED_HOLD_ON = 'sound/voice/medbot/coming.ogg', - MEDIBOT_VOICED_WANT_TO_HELP = 'sound/voice/medbot/help.ogg', - MEDIBOT_VOICED_YOU_ARE_INJURED = 'sound/voice/medbot/injured.ogg', - MEDIBOT_VOICED_ALL_PATCHED_UP = 'sound/voice/medbot/patchedup.ogg', - MEDIBOT_VOICED_APPLE_A_DAY = 'sound/voice/medbot/apple.ogg', - MEDIBOT_VOICED_FEEL_BETTER = 'sound/voice/medbot/feelbetter.ogg', - MEDIBOT_VOICED_STAY_WITH_ME = 'sound/voice/medbot/no.ogg', - MEDIBOT_VOICED_LIVE = 'sound/voice/medbot/live.ogg', - MEDIBOT_VOICED_NEVER_LOST = 'sound/voice/medbot/lost.ogg', - MEDIBOT_VOICED_DELICIOUS = 'sound/voice/medbot/delicious.ogg', - MEDIBOT_VOICED_PLASTIC_SURGEON = 'sound/voice/medbot/surgeon.ogg', - MEDIBOT_VOICED_MASK_ON = 'sound/voice/medbot/radar.ogg', - MEDIBOT_VOICED_ALWAYS_A_CATCH = 'sound/voice/medbot/catch.ogg', - MEDIBOT_VOICED_LIKE_FLIES = 'sound/voice/medbot/flies.ogg', - MEDIBOT_VOICED_SUFFER = 'sound/voice/medbot/why.ogg', - MEDIBOT_VOICED_FUCK_YOU = 'sound/voice/medbot/fuck_you.ogg', - MEDIBOT_VOICED_NOT_A_GAME = 'sound/voice/medbot/turn_off.ogg', - MEDIBOT_VOICED_IM_DIFFERENT = 'sound/voice/medbot/im_different.ogg', - MEDIBOT_VOICED_FOURTH_WALL = 'sound/voice/medbot/close.ogg', - MEDIBOT_VOICED_SHINDEMASHOU = 'sound/voice/medbot/shindemashou.ogg', - MEDIBOT_VOICED_WAIT = 'sound/voice/medbot/hey_wait.ogg', - MEDIBOT_VOICED_DONT = 'sound/voice/medbot/please_dont.ogg', - MEDIBOT_VOICED_TRUSTED_YOU = 'sound/voice/medbot/i_trusted_you.ogg', - MEDIBOT_VOICED_NO_SAD = 'sound/voice/medbot/nooo.ogg', - MEDIBOT_VOICED_OH_FUCK = 'sound/voice/medbot/oh_fuck.ogg', - MEDIBOT_VOICED_FORGIVE = 'sound/voice/medbot/forgive.ogg', - MEDIBOT_VOICED_THANKS = 'sound/voice/medbot/thank_you.ogg', - MEDIBOT_VOICED_GOOD_PERSON = 'sound/voice/medbot/youre_good.ogg', - MEDIBOT_VOICED_BEHAVIOUR_REPORTED = 'sound/voice/medbot/reported.ogg', - MEDIBOT_VOICED_ASSISTANCE = 'sound/voice/medbot/i_require_asst.ogg', - MEDIBOT_VOICED_PUT_BACK = 'sound/voice/medbot/please_put_me_back.ogg', - MEDIBOT_VOICED_IM_SCARED = 'sound/voice/medbot/please_im_scared.ogg', - MEDIBOT_VOICED_NEED_HELP = 'sound/voice/medbot/dont_like.ogg', - MEDIBOT_VOICED_THIS_HURTS = 'sound/voice/medbot/pain_is_real.ogg', - MEDIBOT_VOICED_THE_END = 'sound/voice/medbot/is_this_the_end.ogg', - MEDIBOT_VOICED_NOOO = 'sound/voice/medbot/nooo.ogg', - ) - - /// drop determining variable - var/healthanalyzer = /obj/item/healthanalyzer - /// drop determining variable - var/medkit_type = /obj/item/storage/medkit - ///based off medkit_X skins in aibots.dmi for your selection; X goes here IE medskin_tox means skin var should be "tox" - var/skin - var/mob/living/carbon/patient - var/mob/living/carbon/oldpatient - var/last_found = 0 - /// How much healing do we do at a time? - var/heal_amount = 2.5 - /// Start healing when they have this much damage in a category - var/heal_threshold = 10 - /// What damage type does this bot support. Because the default is brute, if the medkit is brute-oriented there is a slight bonus to healing. set to "all" for it to heal any of the 4 base damage types - var/damagetype_healer = BRUTE - - ///Flags Medbots use to decide how they should be acting. - var/medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_SPEAK_MODE -// Selections: MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE - - /// techweb linked to the medbot - var/datum/techweb/linked_techweb - ///Is the medbot currently tending wounds - var/tending = FALSE - ///How panicked we are about being tipped over (why would you do this?) - var/tipped_status = MEDBOT_PANIC_NONE - ///The name we got when we were tipped - var/tipper_name - ///The trim type that will grant additional access to this medibot - var/datum/id_trim/additional_access = /datum/id_trim/job/paramedic - - ///Last announced healing a person in critical condition - COOLDOWN_DECLARE(last_patient_message) - ///Last announced trying to catch up to a new patient - COOLDOWN_DECLARE(last_newpatient_speak) - ///Last time we were tipped/righted and said a voice line - COOLDOWN_DECLARE(last_tipping_action_voice) - -/mob/living/simple_animal/bot/medbot/autopatrol - bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION - -/mob/living/simple_animal/bot/medbot/stationary - medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE - -/mob/living/simple_animal/bot/medbot/mysterious - name = "\improper Mysterious Medibot" - desc = "International Medibot of mystery." - skin = "bezerk" - damagetype_healer = "all" - heal_amount = 10 - -/mob/living/simple_animal/bot/medbot/derelict - name = "\improper Old Medibot" - desc = "Looks like it hasn't been modified since the late 2080s." - skin = "bezerk" - damagetype_healer = "all" - medical_mode_flags = MEDBOT_SPEAK_MODE - heal_threshold = 0 - heal_amount = 5 - -/mob/living/simple_animal/bot/medbot/nukie - name = "Oppenheimer" - desc = "A medibot stolen from a Nanotrasen station and upgraded by the Syndicate. Despite their best efforts at reprogramming, it still appears visibly upset near nuclear explosives." - skin = "bezerk" - health = 40 - maxHealth = 40 - maints_access_required = list(ACCESS_SYNDICATE) - radio_key = /obj/item/encryptionkey/syndicate - radio_channel = RADIO_CHANNEL_SYNDICATE - damagetype_healer = "all" - heal_threshold = 0 - heal_amount = 5 - additional_access = /datum/id_trim/syndicom/crew - -/mob/living/simple_animal/bot/medbot/nukie/Initialize(mapload, new_skin) - . = ..() - RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_DISARMED, PROC_REF(nuke_disarm)) - RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_ARMED, PROC_REF(nuke_arm)) - RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_DETONATING, PROC_REF(nuke_detonate)) - internal_radio.set_frequency(FREQ_SYNDICATE) - internal_radio.freqlock = RADIO_FREQENCY_LOCKED - faction += ROLE_SYNDICATE //one of us - -/mob/living/simple_animal/bot/medbot/nukie/proc/nuke_disarm() - SIGNAL_HANDLER - INVOKE_ASYNC(src, PROC_REF(speak), pick(MEDIBOT_VOICED_FORGIVE, MEDIBOT_VOICED_THANKS, MEDIBOT_VOICED_GOOD_PERSON)) - -/mob/living/simple_animal/bot/medbot/nukie/proc/nuke_arm() - SIGNAL_HANDLER - INVOKE_ASYNC(src, PROC_REF(speak), pick(MEDIBOT_VOICED_WAIT, MEDIBOT_VOICED_DONT, MEDIBOT_VOICED_IM_SCARED)) - -/mob/living/simple_animal/bot/medbot/nukie/proc/nuke_detonate() - SIGNAL_HANDLER - INVOKE_ASYNC(src, PROC_REF(speak), pick(MEDIBOT_VOICED_THE_END, MEDIBOT_VOICED_NOOO, MEDIBOT_VOICED_SUFFER)) - -/mob/living/simple_animal/bot/medbot/examine(mob/user) - . = ..() - if(tipped_status == MEDBOT_PANIC_NONE) - return - - switch(tipped_status) - if(MEDBOT_PANIC_NONE to MEDBOT_PANIC_LOW) - . += "It appears to be tipped over, and is quietly waiting for someone to set it right." - if(MEDBOT_PANIC_LOW to MEDBOT_PANIC_MED) - . += "It is tipped over and requesting help." - if(MEDBOT_PANIC_MED to MEDBOT_PANIC_HIGH) - . += "They are tipped over and appear visibly distressed." // now we humanize the medbot as a they, not an it - if(MEDBOT_PANIC_HIGH to MEDBOT_PANIC_FUCK) - . += span_warning("They are tipped over and visibly panicking!") - if(MEDBOT_PANIC_FUCK to INFINITY) - . += span_warning("They are freaking out from being tipped over!") - -/mob/living/simple_animal/bot/medbot/update_icon_state() - . = ..() - if(!(bot_mode_flags & BOT_MODE_ON)) - icon_state = "[base_icon_state]0" - return - if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) - icon_state = "[base_icon_state]a" - return - if(mode == BOT_HEALING) - icon_state = "[base_icon_state]s[get_bot_flag(medical_mode_flags, MEDBOT_STATIONARY_MODE)]" - return - icon_state = "[base_icon_state][get_bot_flag(medical_mode_flags, MEDBOT_STATIONARY_MODE) ? 2 : 1]" //Bot has yellow light to indicate stationary mode. - -/mob/living/simple_animal/bot/medbot/update_overlays() - . = ..() - if(skin) - . += "medskin_[skin]" - -/mob/living/simple_animal/bot/medbot/Initialize(mapload, new_skin) - . = ..() - - // Doing this hurts my soul, but simplebot access reworks are for another day. - var/datum/id_trim/additional_trim = SSid_access.trim_singletons_by_path[additional_access] - access_card.add_access(additional_trim.access + additional_trim.wildcard_access) - prev_access = access_card.access.Copy() - - if(!isnull(new_skin)) - skin = new_skin - update_appearance() - - if(HAS_TRAIT(SSstation, STATION_TRAIT_MEDBOT_MANIA) && mapload && is_station_level(z)) - skin = "advanced" - update_appearance(UPDATE_OVERLAYS) - damagetype_healer = "all" - if(prob(50)) - name += ", PhD." - - AddComponent(/datum/component/tippable, \ - tip_time = 3 SECONDS, \ - untip_time = 3 SECONDS, \ - self_right_time = 3.5 MINUTES, \ - pre_tipped_callback = CALLBACK(src, PROC_REF(pre_tip_over)), \ - post_tipped_callback = CALLBACK(src, PROC_REF(after_tip_over)), \ - post_untipped_callback = CALLBACK(src, PROC_REF(after_righted))) - return INITIALIZE_HINT_LATELOAD - -/mob/living/simple_animal/bot/medbot/LateInitialize() - . = ..() - if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb) - CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src) - -/mob/living/simple_animal/bot/medbot/bot_reset() - ..() - patient = null - oldpatient = null - last_found = world.time - update_appearance() - -/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_appearance() - -/mob/living/simple_animal/bot/medbot/attack_paw(mob/user, list/modifiers) - return attack_hand(user, modifiers) - -/mob/living/simple_animal/bot/medbot/multitool_act(mob/living/user, obj/item/multitool/tool) - if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) - linked_techweb = tool.buffer - return TRUE - -// Variables sent to TGUI -/mob/living/simple_animal/bot/medbot/ui_data(mob/user) - var/list/data = ..() - if(!(bot_cover_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user)) - data["custom_controls"]["heal_threshold"] = heal_threshold - data["custom_controls"]["speaker"] = medical_mode_flags & MEDBOT_SPEAK_MODE - data["custom_controls"]["crit_alerts"] = medical_mode_flags & MEDBOT_DECLARE_CRIT - data["custom_controls"]["stationary_mode"] = medical_mode_flags & MEDBOT_STATIONARY_MODE - data["custom_controls"]["sync_tech"] = TRUE - return data - -// Actions received from TGUI -/mob/living/simple_animal/bot/medbot/ui_act(action, params) - . = ..() - if(. || (bot_cover_flags & BOT_COVER_LOCKED && !usr.has_unlimited_silicon_privilege)) - return - - switch(action) - if("heal_threshold") - var/adjust_num = round(text2num(params["threshold"])) - heal_threshold = adjust_num - if(heal_threshold < 5) - heal_threshold = 5 - if(heal_threshold > 75) - heal_threshold = 75 - if("speaker") - medical_mode_flags ^= MEDBOT_SPEAK_MODE - if("crit_alerts") - medical_mode_flags ^= MEDBOT_DECLARE_CRIT - if("stationary_mode") - medical_mode_flags ^= MEDBOT_STATIONARY_MODE - path = list() - if("sync_tech") - if(!linked_techweb) - to_chat(usr, span_notice("No research techweb connected.")) - return - var/oldheal_amount = heal_amount - var/tech_boosters - for(var/index in linked_techweb.researched_designs) - var/datum/design/surgery/healing/design = SSresearch.techweb_design_by_id(index) - if(!istype(design)) - continue - tech_boosters++ - if(tech_boosters) - heal_amount = (round(tech_boosters/2,0.1)*initial(heal_amount))+initial(heal_amount) //every 2 tend wounds tech gives you an extra 100% healing, adjusting for unique branches (combo is bonus) - if(oldheal_amount < heal_amount) - speak("New knowledge found! Surgical efficacy improved to [round(heal_amount/initial(heal_amount)*100)]%!") - - update_appearance() - -/mob/living/simple_animal/bot/medbot/attackby(obj/item/W as obj, mob/user as mob, params) - 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, obj/item/card/emag/emag_card) - . = ..() - if(!(bot_cover_flags & BOT_COVER_EMAGGED)) - return - medical_mode_flags &= ~MEDBOT_DECLARE_CRIT - balloon_alert(user, "reagent synthesis circuits shorted") - audible_message(span_danger("[src] buzzes oddly!")) - flick("medibot_spark", src) - playsound(src, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) - if(user) - oldpatient = user - return TRUE - -/mob/living/simple_animal/bot/medbot/process_scan(mob/living/carbon/human/H) - if(H.stat == DEAD) - return null - if((H == oldpatient) && (world.time < last_found + 200)) - return null - if(!assess_patient(H)) - return null - - last_found = world.time - if(COOLDOWN_FINISHED(src, last_newpatient_speak)) - COOLDOWN_START(src, last_newpatient_speak, MEDBOT_NEW_PATIENTSPEAK_DELAY) - var/list/messagevoice = list( - "Hey, [H.name]! Hold on, I'm coming." = 'sound/voice/medbot/coming.ogg', - "Wait [H.name]! I want to help!" = 'sound/voice/medbot/help.ogg', - "[H.name], you appear to be injured!" = 'sound/voice/medbot/injured.ogg', - ) - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 50, FALSE) - return H - -/* - * Proc used in a callback for before this medibot is tipped by the tippable component. - * - * user - the mob who is tipping us over - */ -/mob/living/simple_animal/bot/medbot/proc/pre_tip_over(mob/user) - if(!COOLDOWN_FINISHED(src, last_tipping_action_voice)) - return - - COOLDOWN_START(src, last_tipping_action_voice, MEDBOT_FREAKOUT_DELAY) // message for tipping happens when we start interacting, message for righting comes after finishing - var/static/list/worried_line = list( - MEDIBOT_VOICED_WAIT, - MEDIBOT_VOICED_DONT, - MEDIBOT_VOICED_TRUSTED_YOU, - MEDIBOT_VOICED_NO_SAD, - MEDIBOT_VOICED_OH_FUCK, - ) - speak(pick(worried_line)) - -/* - * Proc used in a callback for after this medibot is tipped by the tippable component. - * - * user - the mob who tipped us over - */ -/mob/living/simple_animal/bot/medbot/proc/after_tip_over(mob/user) - mode = BOT_TIPPED - tipper_name = user.name - playsound(src, 'sound/machines/warning-buzzer.ogg', 50) - -/* - * Proc used in a callback for after this medibot is righted, either by themselves or by a mob, by the tippable component. - * - * user - the mob who righted us. Can be null. - */ -/mob/living/simple_animal/bot/medbot/proc/after_righted(mob/user) - var/list/messagevoice - if(user) - if(user.name == tipper_name) - messagevoice = list(MEDIBOT_VOICED_FORGIVE) - else - messagevoice = list(MEDIBOT_VOICED_THANKS, MEDIBOT_VOICED_GOOD_PERSON) - else - messagevoice = list(MEDIBOT_VOICED_FUCK_YOU, MEDIBOT_VOICED_BEHAVIOUR_REPORTED) - tipper_name = null - - if(COOLDOWN_FINISHED(src, last_tipping_action_voice)) - COOLDOWN_START(src, last_tipping_action_voice, MEDBOT_FREAKOUT_DELAY) - speak(pick(messagevoice)) - tipped_status = MEDBOT_PANIC_NONE - mode = BOT_IDLE - -/// if someone tipped us over, check whether we should ask for help or just right ourselves eventually -/mob/living/simple_animal/bot/medbot/proc/handle_panic() - tipped_status++ - var/list/messagevoice - - switch(tipped_status) - if(MEDBOT_PANIC_LOW) - messagevoice = list(MEDIBOT_VOICED_ASSISTANCE) - if(MEDBOT_PANIC_MED) - messagevoice = list(MEDIBOT_VOICED_PUT_BACK) - if(MEDBOT_PANIC_HIGH) - messagevoice = list(MEDIBOT_VOICED_IM_SCARED) - if(MEDBOT_PANIC_FUCK) - messagevoice = list(MEDIBOT_VOICED_NEED_HELP, MEDIBOT_VOICED_THIS_HURTS) - if(MEDBOT_PANIC_ENDING) - messagevoice = list(MEDIBOT_VOICED_THE_END, MEDIBOT_VOICED_NOOO) - if(MEDBOT_PANIC_END) - speak("PSYCH ALERT: Crewmember [tipper_name] recorded displaying antisocial tendencies torturing bots in [get_area(src)]. Please schedule psych evaluation.", radio_channel) - - if(prob(tipped_status)) - do_jitter_animation(tipped_status * 0.1) - - if(messagevoice) - speak(pick(messagevoice)) - else if(prob(tipped_status * 0.2)) - playsound(src, 'sound/machines/warning-buzzer.ogg', 30, extrarange=-2) - -/mob/living/simple_animal/bot/medbot/handle_automated_action() - . = ..() - if(!.) - return - - switch(mode) - if(BOT_TIPPED) - handle_panic() - return - if(BOT_HEALING) - return - - if(IsStun() || IsParalyzed()) - oldpatient = patient - patient = null - mode = BOT_IDLE - return - - if(frustration > 8) - oldpatient = patient - soft_reset() - - if(QDELETED(patient)) - if(medical_mode_flags & MEDBOT_SPEAK_MODE && prob(1)) - if(bot_cover_flags & BOT_COVER_EMAGGED && prob(30)) - var/static/list/i_need_scissors = list( - MEDIBOT_VOICED_FUCK_YOU, - MEDIBOT_VOICED_NOT_A_GAME, - MEDIBOT_VOICED_IM_DIFFERENT, - MEDIBOT_VOICED_FOURTH_WALL, - MEDIBOT_VOICED_SHINDEMASHOU, - ) - speak(pick(i_need_scissors)) - else - var/static/list/idle_lines = list( - MEDIBOT_VOICED_DELICIOUS, - MEDIBOT_VOICED_PLASTIC_SURGEON, - MEDIBOT_VOICED_MASK_ON, - MEDIBOT_VOICED_ALWAYS_A_CATCH, - MEDIBOT_VOICED_LIKE_FLIES, - MEDIBOT_VOICED_SUFFER, - ) - speak(pick(idle_lines)) - var/scan_range = (medical_mode_flags & MEDBOT_STATIONARY_MODE ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients. - patient = scan(list(/mob/living/carbon/human), oldpatient, scan_range) - oldpatient = patient - - if(patient && (get_dist(src,patient) <= 1) && !tending) //Patient is next to us, begin treatment! - if(mode != BOT_HEALING) - mode = BOT_HEALING - update_appearance() - 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(medical_mode_flags & MEDBOT_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, patient, max_distance=30, access=access_card.GetAccess()) - mode = BOT_MOVING - if(!path.len) //try to get closer if you can't reach the patient directly - path = get_path_to(src, patient, max_distance=30, mintargetdist=1, access=access_card.GetAccess()) - 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(bot_mode_flags & BOT_MODE_AUTOPATROL && !(medical_mode_flags & MEDBOT_STATIONARY_MODE) && !patient) - switch(mode) - if(BOT_IDLE, BOT_START_PATROL) - start_patrol() - if(BOT_PATROL) - bot_patrol() - -/mob/living/simple_animal/bot/medbot/proc/assess_patient(mob/living/carbon/C) - . = FALSE - //Time to see if they need medical help! - if(medical_mode_flags & MEDBOT_STATIONARY_MODE && !Adjacent(C)) //YOU come to ME, BRO - return FALSE - - if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) - return FALSE //welp too late for them! - - if(!(loc == C.loc) && !(isturf(C.loc) && isturf(loc))) - return FALSE - - if(HAS_TRAIT_FROM_ONLY(C, TRAIT_SUICIDED, REF(C))) - return FALSE //Kevorkian school of robotic medical assistants. - - if(bot_cover_flags & BOT_COVER_EMAGGED) //Everyone needs our medicine. (Our medicine is toxins) - return TRUE - - if(HAS_TRAIT(C, TRAIT_MEDIBOTCOMINGTHROUGH) && !HAS_TRAIT_FROM(C, TRAIT_MEDIBOTCOMINGTHROUGH, tag)) //the early medbot gets the worm (or in this case the patient) - return FALSE - - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if (H.wear_suit && H.head && istype(H.wear_suit, /obj/item/clothing) && istype(H.head, /obj/item/clothing)) - var/obj/item/clothing/CS = H.wear_suit - var/obj/item/clothing/CH = H.head - if (CS.clothing_flags & CH.clothing_flags & THICKMATERIAL) - return FALSE // Skip over them if they have no exposed flesh. - // SKYRAT EDIT ADDITION START - if(H.mob_biotypes & MOB_ROBOTIC) - return FALSE - // SKYRAT EDIT ADDITION END - - if(medical_mode_flags & MEDBOT_DECLARE_CRIT && C.health <= 0) //Critical condition! Call for help! - declare(C) - - //They're injured enough for it! - var/list/treat_me_for = list() - if(C.getBruteLoss() > heal_threshold) - treat_me_for += BRUTE - - if(C.getOxyLoss() > (5 + heal_threshold)) - treat_me_for += OXY - - if(C.getFireLoss() > heal_threshold) - treat_me_for += BURN - - if(C.getToxLoss() > heal_threshold) - treat_me_for += TOX - - if(damagetype_healer in treat_me_for) - return TRUE - if(damagetype_healer == "all" && treat_me_for.len) - return TRUE - -/mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A, proximity_flag, list/modifiers) - if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) - return - if(iscarbon(A) && !tending) - var/mob/living/carbon/C = A - patient = C - mode = BOT_HEALING - update_appearance() - medicate_patient(C) - update_appearance() - return - ..() - -/mob/living/simple_animal/bot/medbot/examinate(atom/A as mob|obj|turf in view()) - ..() - if(!is_blind()) - chemscan(src, A) - -/mob/living/simple_animal/bot/medbot/proc/medicate_patient(mob/living/carbon/C) - if(!(bot_mode_flags & BOT_MODE_ON)) - return - - if(!istype(C)) - oldpatient = patient - soft_reset() - return - - if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) - var/static/list/grief_lines = list( - MEDIBOT_VOICED_STAY_WITH_ME, - MEDIBOT_VOICED_LIVE, - MEDIBOT_VOICED_NEVER_LOST, - ) - speak(pick(grief_lines)) - oldpatient = patient - soft_reset() - return - - tending = TRUE - while(tending) - var/treatment_method - var/list/potential_methods = list() - - if(C.getBruteLoss() > heal_threshold) - potential_methods += BRUTE - - if(C.getFireLoss() > heal_threshold) - potential_methods += BURN - - if(C.getOxyLoss() > (5 + heal_threshold)) - potential_methods += OXY - - if(C.getToxLoss() > heal_threshold) - potential_methods += TOX - - for(var/i in potential_methods) - if(i != damagetype_healer) - continue - treatment_method = i - - if(damagetype_healer == "all" && potential_methods.len) - treatment_method = pick(potential_methods) - - if(!treatment_method && !(bot_cover_flags & BOT_COVER_EMAGGED)) //If they don't need any of that they're probably cured! - if(C.maxHealth - C.get_organic_health() < heal_threshold) - to_chat(src, span_notice("[C] is healthy! Your programming prevents you from tending the wounds of anyone without at least [heal_threshold] damage of any one type ([heal_threshold + 5] for oxygen damage.)")) - - var/static/list/success_lines = list( - MEDIBOT_VOICED_ALL_PATCHED_UP, - MEDIBOT_VOICED_APPLE_A_DAY, - MEDIBOT_VOICED_FEEL_BETTER, - ) - speak(pick(success_lines)) - bot_reset() - tending = FALSE - else if(patient) - C.visible_message(span_danger("[src] is trying to tend the wounds of [patient]!"), \ - span_userdanger("[src] is trying to tend your wounds!")) - - if(do_after(src, 10 SECONDS, patient)) //SKYRAT EDIT: Increased time as tradeoff for automated healing. ORIGINAL: if(do_after(src, 2 SECONDS, patient)) - if((get_dist(src, patient) <= 1) && (bot_mode_flags & BOT_MODE_ON) && assess_patient(patient)) - var/healies = heal_amount - var/obj/item/storage/medkit/medkit = medkit_type - if(treatment_method == BRUTE && initial(medkit.damagetype_healed) == BRUTE) //specialized brute gets a bit of bonus, as a snack. - healies *= 1.1 - if(bot_cover_flags & BOT_COVER_EMAGGED) - patient.reagents.add_reagent(/datum/reagent/toxin/chloralhydrate, 5) - patient.apply_damage((healies * 1), treatment_method, spread_damage = TRUE) - log_combat(src, patient, "pretended to tend wounds on", "internal tools", "([uppertext(treatment_method)]) (EMAGGED)") - else - patient.heal_damage_type((healies * 1), treatment_method) //don't need to check treatment_method since we know by this point that they were actually damaged. - log_combat(src, patient, "tended the wounds of", "internal tools", "([uppertext(treatment_method)])") - C.visible_message(span_notice("[src] tends the wounds of [patient]!"), \ - "[span_green("[src] tends your wounds!")]") - ADD_TRAIT(patient,TRAIT_MEDIBOTCOMINGTHROUGH,tag) - addtimer(TRAIT_CALLBACK_REMOVE(patient, TRAIT_MEDIBOTCOMINGTHROUGH, tag), (30 SECONDS)) - else - tending = FALSE - else - tending = FALSE - - update_appearance() - if(!tending) - visible_message("[src] places its tools back into itself.") - soft_reset() - else - tending = FALSE - -/mob/living/simple_animal/bot/medbot/explode() - var/atom/Tsec = drop_location() - - drop_part(medkit_type, Tsec) - new /obj/item/assembly/prox_sensor(Tsec) - drop_part(healthanalyzer, Tsec) - - if(bot_cover_flags & BOT_COVER_EMAGGED && prob(25)) - playsound(src, 'sound/voice/medbot/insult.ogg', 50) - return ..() - -/mob/living/simple_animal/bot/medbot/proc/declare(crit_patient) - if(!COOLDOWN_FINISHED(src, last_patient_message)) - return - COOLDOWN_START(src, last_patient_message, MEDBOT_PATIENTSPEAK_DELAY) - var/area/location = get_area(src) - speak("Medical emergency! [crit_patient || "A patient"] is in critical condition at [location]!", radio_channel) - -#undef MEDBOT_NEW_PATIENTSPEAK_DELAY -#undef MEDBOT_PATIENTSPEAK_DELAY -#undef MEDBOT_FREAKOUT_DELAY - -#undef MEDBOT_PANIC_NONE -#undef MEDBOT_PANIC_LOW -#undef MEDBOT_PANIC_MED -#undef MEDBOT_PANIC_HIGH -#undef MEDBOT_PANIC_FUCK -#undef MEDBOT_PANIC_ENDING -#undef MEDBOT_PANIC_END diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index fba922380d9..d38749c0f1a 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -12,12 +12,6 @@ /mob/living/simple_animal/bot/firebot, /mob/living/simple_animal/bot/floorbot, /mob/living/simple_animal/bot/hygienebot, - /mob/living/simple_animal/bot/medbot, - /mob/living/simple_animal/bot/medbot/autopatrol, - /mob/living/simple_animal/bot/medbot/derelict, - /mob/living/simple_animal/bot/medbot/mysterious, - /mob/living/simple_animal/bot/medbot/nukie, - /mob/living/simple_animal/bot/medbot/stationary, /mob/living/simple_animal/bot/mulebot, /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/bot/secbot, diff --git a/sound/voice/medbot/i_am_chicken.ogg b/sound/voice/medbot/i_am_chicken.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d1c4465505f39e3ea7ca369df7ed9b5462950f73 GIT binary patch literal 108791 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

KGyZt+ppFyf9D(dRvzQz9VqLVH^Glo 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