From 150ef6b2ddfd59fd0337a0d80ac22d4a237f16de Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sat, 9 Dec 2023 01:59:00 +0100 Subject: [PATCH] [MIRROR] basic cleanbots refactor and new janitor skillchip [MDB IGNORE] (#25511) * basic cleanbots refactor and new janitor skillchip * Update medbot.dm * UpdatePaths --------- Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> --- _maps/RandomRuins/SpaceRuins/oldstation.dmm | 4 +- .../SpaceRuins/spacehotel_skyrat.dmm | 2 +- _maps/map_files/Birdshot/birdshot.dmm | 2 +- .../map_files/IceBoxStation/IceBoxStation.dmm | 2 +- _maps/map_files/MetaStation/MetaStation.dmm | 6 +- _maps/map_files/NSVBlueshift/Blueshift.dmm | 10 +- _maps/map_files/NorthStar/north_star.dmm | 2 +- _maps/map_files/VoidRaptor/VoidRaptor.dmm | 2 +- _maps/map_files/generic/CentCom_skyrat_z2.dmm | 2 +- _maps/map_files/tramstation/tramstation.dmm | 4 +- _maps/shuttles/emergency_casino.dmm | 2 +- _maps/shuttles/emergency_shadow.dmm | 2 +- _maps/shuttles/emergency_tranquility.dmm | 2 +- _maps/shuttles/ruin_cyborg_mothership.dmm | 2 +- code/__DEFINES/ai/ai_blackboard.dm | 3 + code/__DEFINES/ai/bot_keys.dm | 22 + code/__DEFINES/traits/declarations.dm | 3 + code/_globalvars/traits/_traits.dm | 1 + .../basic_ai_behaviors/befriend_target.dm | 21 + code/datums/components/cleaner.dm | 4 +- code/datums/components/crafting/robot.dm | 2 +- code/game/objects/items/clown_items.dm | 4 +- code/game/objects/items/mop.dm | 4 +- code/modules/jobs/job_types/janitor.dm | 1 + .../skill_learning/job_skillchips/janitor.dm | 9 + code/modules/mob/living/basic/bots/_bots.dm | 47 +- code/modules/mob/living/basic/bots/bot_ai.dm | 31 +- .../living/basic/bots/cleanbot/cleanbot.dm | 354 ++++++++++++++ .../basic/bots/cleanbot/cleanbot_abilities.dm | 36 ++ .../living/basic/bots/cleanbot/cleanbot_ai.dm | 209 ++++++++ .../mob/living/basic/bots/medbot/medbot.dm | 35 +- .../mob/living/basic/bots/medbot/medbot_ai.dm | 2 +- .../mob/living/simple_animal/bot/cleanbot.dm | 450 ------------------ .../living/simple_animal/bot/construction.dm | 22 +- .../file_system/programs/radar.dm | 4 +- .../unit_tests/simple_animal_freeze.dm | 3 - tgstation.dme | 6 +- tools/UpdatePaths/Scripts/80100_cleanbots.txt | 1 + 38 files changed, 776 insertions(+), 542 deletions(-) create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm create mode 100644 code/modules/library/skill_learning/job_skillchips/janitor.dm create mode 100644 code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm create mode 100644 code/modules/mob/living/basic/bots/cleanbot/cleanbot_abilities.dm create mode 100644 code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm delete mode 100644 code/modules/mob/living/simple_animal/bot/cleanbot.dm create mode 100644 tools/UpdatePaths/Scripts/80100_cleanbots.txt diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm index f3ae95a673e..2fd8256c659 100644 --- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm +++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm @@ -541,12 +541,12 @@ pixel_y = 2; pixel_x = 6 }, -/mob/living/simple_animal/bot/cleanbot/autopatrol{ +/mob/living/basic/bot/cleanbot/autopatrol{ bot_mode_flags = 12; name = "Ramboo"; pixel_x = -2; pixel_y = 5; - bot_cover_flags = 0 + bot_access_flags = 0 }, /turf/open/floor/plating{ initial_gas_mix = "co2=6;o2=16;n2=82;TEMP=293.15" diff --git a/_maps/RandomRuins/SpaceRuins/spacehotel_skyrat.dmm b/_maps/RandomRuins/SpaceRuins/spacehotel_skyrat.dmm index 6ea07d8f5fd..290578bb581 100644 --- a/_maps/RandomRuins/SpaceRuins/spacehotel_skyrat.dmm +++ b/_maps/RandomRuins/SpaceRuins/spacehotel_skyrat.dmm @@ -1502,7 +1502,7 @@ dir = 9 }, /obj/effect/decal/cleanable/cobweb, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/floor/iron, /area/ruin/space/has_grav/hotel/custodial) "hL" = ( diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index b41a9aa77ad..4350a659c69 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -12405,7 +12405,7 @@ pixel_x = 9; pixel_y = 2 }, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /obj/machinery/ai_slipper{ uses = 10 }, diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index c789e33bb25..d89987b60e2 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -52996,7 +52996,7 @@ /obj/effect/turf_decal/tile/blue{ dir = 8 }, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat/service) "qhp" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 844474dc925..27f23bc7776 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -3473,7 +3473,7 @@ /obj/machinery/light/small/directional/east, /obj/machinery/firealarm/directional/east, /obj/effect/turf_decal/tile/blue, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat_interior) "bkF" = ( @@ -11435,7 +11435,7 @@ /turf/open/floor/iron, /area/station/hallway/primary/fore) "eih" = ( -/mob/living/simple_animal/bot/cleanbot/medbay, +/mob/living/basic/bot/cleanbot/medbay, /turf/open/floor/iron/white, /area/station/medical/storage) "eiO" = ( @@ -64698,7 +64698,7 @@ dir = 4 }, /obj/effect/turf_decal/tile/neutral, -/mob/living/simple_animal/bot/cleanbot/autopatrol, +/mob/living/basic/bot/cleanbot/autopatrol, /turf/open/floor/iron, /area/station/hallway/primary/central) "wUt" = ( diff --git a/_maps/map_files/NSVBlueshift/Blueshift.dmm b/_maps/map_files/NSVBlueshift/Blueshift.dmm index c8cbf66f31a..d52cd73ea2a 100644 --- a/_maps/map_files/NSVBlueshift/Blueshift.dmm +++ b/_maps/map_files/NSVBlueshift/Blueshift.dmm @@ -11877,7 +11877,7 @@ /turf/open/floor/iron/dark/side, /area/station/security/prison/shower) "cnL" = ( -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /obj/structure/railing, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat_interior) @@ -24539,7 +24539,7 @@ "eGG" = ( /obj/structure/cable, /obj/effect/landmark/event_spawn, -/mob/living/simple_animal/bot/cleanbot{ +/mob/living/basic/bot/cleanbot{ name = "Soapficcer Cleansky" }, /obj/structure/disposalpipe/segment{ @@ -36813,7 +36813,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/cleanbot/medbay, +/mob/living/basic/bot/cleanbot/medbay, /obj/item/beacon, /turf/open/floor/iron/white, /area/station/medical/medbay/central) @@ -59165,7 +59165,7 @@ /obj/effect/landmark/event_spawn, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/mob/living/simple_animal/bot/cleanbot{ +/mob/living/basic/bot/cleanbot{ name = "Sweeps-The-Tiles" }, /turf/open/floor/iron, @@ -114073,7 +114073,7 @@ /turf/open/floor/catwalk_floor/iron_smooth, /area/station/cargo/miningdock) "vIy" = ( -/mob/living/simple_animal/bot/cleanbot/autopatrol, +/mob/living/basic/bot/cleanbot/autopatrol, /obj/structure/cable, /turf/open/floor/iron/goonplaque, /area/station/hallway/primary/central/aft) diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index fd645919927..62119f45edc 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -2976,7 +2976,7 @@ /area/station/commons/fitness) "aOw" = ( /obj/machinery/light/small/directional/north, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat_interior) "aOx" = ( diff --git a/_maps/map_files/VoidRaptor/VoidRaptor.dmm b/_maps/map_files/VoidRaptor/VoidRaptor.dmm index 9fb023c4254..54a7cc3e09e 100644 --- a/_maps/map_files/VoidRaptor/VoidRaptor.dmm +++ b/_maps/map_files/VoidRaptor/VoidRaptor.dmm @@ -46614,7 +46614,7 @@ /turf/open/floor/iron/dark/smooth_large, /area/station/medical/morgue) "nfC" = ( -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /obj/structure/railing, /turf/open/floor/circuit, /area/station/ai_monitored/turret_protected/aisat_interior) diff --git a/_maps/map_files/generic/CentCom_skyrat_z2.dmm b/_maps/map_files/generic/CentCom_skyrat_z2.dmm index e144274de07..22be3f59713 100644 --- a/_maps/map_files/generic/CentCom_skyrat_z2.dmm +++ b/_maps/map_files/generic/CentCom_skyrat_z2.dmm @@ -1274,7 +1274,7 @@ /turf/open/floor/iron/showroomfloor, /area/centcom/holding/cafe) "atf" = ( -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/indestructible/hotelwood, /area/centcom/holding/cafe) "ati" = ( diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index 6f701f84014..696943104c9 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -15550,7 +15550,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 1 }, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat_interior) "esA" = ( @@ -70301,7 +70301,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat/maint) "xZi" = ( diff --git a/_maps/shuttles/emergency_casino.dmm b/_maps/shuttles/emergency_casino.dmm index 1747295a6f2..95996a80d38 100644 --- a/_maps/shuttles/emergency_casino.dmm +++ b/_maps/shuttles/emergency_casino.dmm @@ -1146,7 +1146,7 @@ "MS" = ( /obj/structure/mop_bucket, /obj/item/mop, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/floor/mineral/titanium/yellow, /area/shuttle/escape) "MT" = ( diff --git a/_maps/shuttles/emergency_shadow.dmm b/_maps/shuttles/emergency_shadow.dmm index 70946ba72b2..19bbf8f786e 100644 --- a/_maps/shuttles/emergency_shadow.dmm +++ b/_maps/shuttles/emergency_shadow.dmm @@ -991,7 +991,7 @@ /turf/open/floor/eighties, /area/shuttle/escape) "RV" = ( -/mob/living/simple_animal/bot/cleanbot{ +/mob/living/basic/bot/cleanbot{ name = "Mopsy" }, /obj/machinery/holopad, diff --git a/_maps/shuttles/emergency_tranquility.dmm b/_maps/shuttles/emergency_tranquility.dmm index cd2a5ef11df..b2a15c5df11 100644 --- a/_maps/shuttles/emergency_tranquility.dmm +++ b/_maps/shuttles/emergency_tranquility.dmm @@ -1385,7 +1385,7 @@ /obj/effect/turf_decal/siding/wood{ dir = 9 }, -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /obj/machinery/vending/wallmed/directional/south, /turf/open/floor/wood, /area/shuttle/escape) diff --git a/_maps/shuttles/ruin_cyborg_mothership.dmm b/_maps/shuttles/ruin_cyborg_mothership.dmm index ac61c9b98df..7188b8bd3c0 100644 --- a/_maps/shuttles/ruin_cyborg_mothership.dmm +++ b/_maps/shuttles/ruin_cyborg_mothership.dmm @@ -17,7 +17,7 @@ /turf/open/floor/plating/airless, /area/shuttle/ruin/cyborg_mothership) "bE" = ( -/mob/living/simple_animal/bot/cleanbot, +/mob/living/basic/bot/cleanbot, /turf/open/floor/iron/showroomfloor, /area/shuttle/ruin/cyborg_mothership) "cU" = ( diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index 91ad49975fe..daa5d4285f9 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -140,3 +140,6 @@ #define BB_BASIC_MOB_REINFORCEMENT_TARGET "BB_basic_mob_reinforcement_target" /// The next time at which this mob can call for reinforcements #define BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN "BB_basic_mob_reinforcements_cooldown" + +///Text we display when we befriend someone +#define BB_FRIENDLY_MESSAGE "friendly_message" diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm index 73d5bfb30fc..25467e45485 100644 --- a/code/__DEFINES/ai/bot_keys.dm +++ b/code/__DEFINES/ai/bot_keys.dm @@ -33,3 +33,25 @@ #define BB_NEAR_DEATH_SPEECH "near_death_speech" ///in crit patient we must alert medbay about #define BB_PATIENT_IN_CRIT "patient_in_crit" + +// cleanbots +///key that holds the foaming ability +#define BB_CLEANBOT_FOAM "cleanbot_foam" +///key that holds decals we hunt +#define BB_CLEANABLE_DECALS "cleanable_decals" +///key that holds blood we hunt +#define BB_CLEANABLE_BLOOD "cleanable_blood" +///key that holds pests we hunt +#define BB_HUNTABLE_PESTS "huntable_pests" +///key that holds emagged speech +#define BB_CLEANBOT_EMAGGED_PHRASES "emagged_phrases" +///key that holds drawings we hunt +#define BB_CLEANABLE_DRAWINGS "cleanable_drawings" +///Key that holds our clean target +#define BB_CLEAN_TARGET "clean_target" +///key that holds the janitor we will befriend +#define BB_FRIENDLY_JANITOR "friendly_janitor" +///key that holds the victim we will spray +#define BB_ACID_SPRAY_TARGET "acid_spray_target" +///key that holds trash we will burn +#define BB_HUNTABLE_TRASH "huntable_trash" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 83abc33e9fd..e49c7e56c9e 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1005,6 +1005,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait given to foam darts that have an insert in them #define TRAIT_DART_HAS_INSERT "dart_has_insert" +///Trait granted by janitor skillchip, allows communication with cleanbots +#define TRAIT_CLEANBOT_WHISPERER "cleanbot_whisperer" + /// Trait given when a mob is currently in invisimin mode #define TRAIT_INVISIMIN "invisimin" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index e209e985333..6feab49cc9f 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -147,6 +147,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CHEF_KISS" = TRAIT_CHEF_KISS, "TRAIT_CHUNKYFINGERS_IGNORE_BATON" = TRAIT_CHUNKYFINGERS_IGNORE_BATON, "TRAIT_CHUNKYFINGERS" = TRAIT_CHUNKYFINGERS, + "TRAIT_CLEANBOT_WHISPERER" = TRAIT_CLEANBOT_WHISPERER, "TRAIT_CLIFF_WALKER" = TRAIT_CLIFF_WALKER, "TRAIT_CLOWN_ENJOYER" = TRAIT_CLOWN_ENJOYER, "TRAIT_CLUMSY" = TRAIT_CLUMSY, diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm new file mode 100644 index 00000000000..e1fd8bed640 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm @@ -0,0 +1,21 @@ +///behavior to befriend any targets +/datum/ai_behavior/befriend_target + +/datum/ai_behavior/befriend_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, befriend_message) + . = ..() + var/mob/living/living_pawn = controller.pawn + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + finish_action(controller, FALSE, target_key) + return + + living_pawn.befriend(living_target) + var/befriend_text = controller.blackboard[befriend_message] + if(befriend_text) + to_chat(living_target, span_nicegreen("[living_pawn] [befriend_text]")) + + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/befriend_target/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/datums/components/cleaner.dm b/code/datums/components/cleaner.dm index f63616863a1..0ab0b199d74 100644 --- a/code/datums/components/cleaner.dm +++ b/code/datums/components/cleaner.dm @@ -120,14 +120,16 @@ cleaning_duration = (cleaning_duration * min(user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER)+skill_duration_modifier_offset, 1)) //do the cleaning + var/clean_succeeded = FALSE if(do_after(user, cleaning_duration, target = target)) + clean_succeeded = TRUE if(clean_target) for(var/obj/effect/decal/cleanable/cleanable_decal in target) //it's important to do this before you wash all of the cleanables off user.mind?.adjust_experience(/datum/skill/cleaning, round(cleanable_decal.beauty / CLEAN_SKILL_BEAUTY_ADJUSTMENT)) if(target.wash(cleaning_strength)) user.mind?.adjust_experience(/datum/skill/cleaning, round(CLEAN_SKILL_GENERIC_WASH_XP)) - on_cleaned_callback?.Invoke(source, target, user) + on_cleaned_callback?.Invoke(source, target, user, clean_succeeded) //remove the cleaning overlay target.cut_overlay(low_bubble) target.cut_overlay(high_bubble) diff --git a/code/datums/components/crafting/robot.dm b/code/datums/components/crafting/robot.dm index 5839f1dfdb9..9c05bdf7fb3 100644 --- a/code/datums/components/crafting/robot.dm +++ b/code/datums/components/crafting/robot.dm @@ -32,7 +32,7 @@ /datum/crafting_recipe/cleanbot name = "Cleanbot" - result = /mob/living/simple_animal/bot/cleanbot + result = /mob/living/basic/bot/cleanbot reqs = list( /obj/item/reagent_containers/cup/bucket = 1, /obj/item/assembly/prox_sensor = 1, diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm index 1a8ffe81f42..27fa0031485 100644 --- a/code/game/objects/items/clown_items.dm +++ b/code/game/objects/items/clown_items.dm @@ -129,7 +129,9 @@ * * target - The atom that is being cleaned * * user - The mob that is using the soap to clean. */ -/obj/item/soap/proc/decreaseUses(datum/source, atom/target, mob/living/user) +/obj/item/soap/proc/decreaseUses(datum/source, atom/target, mob/living/user, clean_succeeded) + if(!clean_succeeded) + return var/skillcheck = 1 if(user?.mind) skillcheck = user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER) diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm index 4c8bf08cac6..8874b9337db 100644 --- a/code/game/objects/items/mop.dm +++ b/code/game/objects/items/mop.dm @@ -60,7 +60,9 @@ * * cleaned_turf the turf that is being cleaned * * cleaner the mob that is doing the cleaning */ -/obj/item/mop/proc/apply_reagents(datum/cleaning_source, turf/cleaned_turf, mob/living/cleaner) +/obj/item/mop/proc/apply_reagents(datum/cleaning_source, turf/cleaned_turf, mob/living/cleaner, clean_succeeded) + if(!clean_succeeded) + return reagents.expose(cleaned_turf, TOUCH, 10) //Needed for proper floor wetting. var/val2remove = 1 if(cleaner?.mind) diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm index a3f4d1aae19..3719095f8d1 100644 --- a/code/modules/jobs/job_types/janitor.dm +++ b/code/modules/jobs/job_types/janitor.dm @@ -40,6 +40,7 @@ uniform = /obj/item/clothing/under/rank/civilian/janitor belt = /obj/item/modular_computer/pda/janitor ears = /obj/item/radio/headset/headset_srv + skillchips = list(/obj/item/skillchip/job/janitor) /datum/outfit/job/janitor/pre_equip(mob/living/carbon/human/human_equipper, visuals_only) . = ..() diff --git a/code/modules/library/skill_learning/job_skillchips/janitor.dm b/code/modules/library/skill_learning/job_skillchips/janitor.dm new file mode 100644 index 00000000000..22109bbed29 --- /dev/null +++ b/code/modules/library/skill_learning/job_skillchips/janitor.dm @@ -0,0 +1,9 @@ +/obj/item/skillchip/job/janitor + name = "CL34NM4ST.R skillchip" + desc = "Become a cleanbot whisperer." + auto_traits = list(TRAIT_CLEANBOT_WHISPERER) + skill_name = "Voice Of The Voiceless" + skill_description = "Gain the affection of all thankless, hardworking cleanbots on the station." + skill_icon = "broom" + activate_message = span_notice("You start feeling empathetic towards all the cleanbots on the station.") + deactivate_message = span_notice("You forget why you felt any sympathy towards the cleanbots, they are just robots after all.") diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index 4db1a52432e..c93e0597675 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -80,6 +80,12 @@ GLOBAL_LIST_INIT(command_strings, list( var/datum/action/cooldown/bot_announcement/pa_system /// Type of bot_announcement ability we want var/announcement_type + ///list of traits we apply and remove when turning on/off + var/static/list/on_toggle_traits = list( + TRAIT_INCAPACITATED, + TRAIT_IMMOBILIZED, + TRAIT_HANDS_BLOCKED, + ) /// If true we will offer this COOLDOWN_DECLARE(offer_ghosts_cooldown) @@ -167,7 +173,7 @@ GLOBAL_LIST_INIT(command_strings, list( /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) + add_traits(on_toggle_traits, 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") @@ -550,10 +556,8 @@ GLOBAL_LIST_INIT(command_strings, list( 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") @@ -573,8 +577,8 @@ GLOBAL_LIST_INIT(command_strings, list( 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 + if((bot_access_flags & BOT_CONTROL_PANEL_OPEN) || issilicon(user) || isAdminGhostAI(user)) + data["settings"]["pai_inserted"] = !isnull(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) @@ -584,12 +588,13 @@ GLOBAL_LIST_INIT(command_strings, list( return data // Actions received from TGUI -/mob/living/basic/bot/ui_act(action, params) +/mob/living/basic/bot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() - if(.) + if(. || !isliving(ui.user)) return - if(!check_access(usr)) - to_chat(usr, span_warning("Access denied.")) + var/mob/living/the_user = ui.user + if(!check_access(the_user)) + balloon_alert(the_user, "access denied!") return if(action == "lock") @@ -609,39 +614,39 @@ GLOBAL_LIST_INIT(command_strings, list( if("airplane") bot_mode_flags ^= BOT_MODE_REMOTE_ENABLED if("hack") - if(!(issilicon(usr) || isAdminGhostAI(usr))) + if(!(issilicon(the_user) || isAdminGhostAI(the_user))) 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) + to_chat(the_user, span_warning("You overload [src]'s [hackables].")) + message_admins("Safety lock of [ADMIN_LOOKUPFLW(src)] was disabled by [ADMIN_LOOKUPFLW(the_user)] in [ADMIN_VERBOSEJMP(the_user)]") + the_user.log_message("disabled safety lock of [the_user]", 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].")) + to_chat(the_user, 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) + to_chat(the_user, span_notice("You reset the [src]'s [hackables].")) + the_user.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) + to_chat(the_user, span_notice("You eject [paicard] from [initial(src.name)].")) + ejectpai(the_user) if("toggle_personality") if (can_be_possessed) - disable_possession(usr) + disable_possession(the_user) else - enable_possession(usr) + enable_possession(the_user) if("rename") - rename(usr) + rename(the_user) /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]" diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index 8784f4c0c5c..b6ea8f4519c 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -12,7 +12,7 @@ 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/salute_authority, /datum/ai_planning_subtree/find_patrol_beacon, /datum/ai_planning_subtree/manage_unreachable_list, ) @@ -181,35 +181,38 @@ bot_pawn.update_bot_mode(new_mode = BOT_IDLE) return ..() -/datum/ai_planning_subtree/salute_beepsky +/datum/ai_planning_subtree/salute_authority -/datum/ai_planning_subtree/salute_beepsky/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) +/datum/ai_planning_subtree/salute_authority/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) + controller.queue_behavior(/datum/ai_behavior/salute_authority, 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) + controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_authority, BB_SALUTE_TARGET) -/datum/ai_behavior/find_and_set/valid_beepsky +/datum/ai_behavior/find_and_set/valid_authority 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)) +/datum/ai_behavior/find_and_set/valid_authority/search_tactic(datum/ai_controller/controller, locate_path, search_range) + for(var/mob/living/robot in oview(search_range, controller.pawn)) + if(istype(robot, /mob/living/simple_animal/bot/secbot)) + return robot + if(!istype(robot, /mob/living/basic/bot/cleanbot)) continue - return robot - + var/mob/living/basic/bot/cleanbot/potential_bot = robot + if(potential_bot.comissioned) + return potential_bot return null -/datum/ai_behavior/salute_beepsky +/datum/ai_behavior/salute_authority -/datum/ai_behavior/salute_beepsky/perform(seconds_per_tick, datum/ai_controller/controller, target_key, salute_keys) +/datum/ai_behavior/salute_authority/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) @@ -228,6 +231,6 @@ finish_action(controller, TRUE, target_key) return -/datum/ai_behavior/salute_beepsky/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/ai_behavior/salute_authority/finish_action(datum/ai_controller/controller, succeeded, target_key) . = ..() controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm new file mode 100644 index 00000000000..393d799b6df --- /dev/null +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -0,0 +1,354 @@ + +//Cleanbot +/mob/living/basic/bot/cleanbot + name = "\improper Cleanbot" + desc = "A little cleaning robot, he looks so excited!" + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "cleanbot0" + pass_flags = PASSMOB | PASSFLAPS + density = FALSE + anchored = FALSE + health = 25 + maxHealth = 25 + + maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR) + radio_key = /obj/item/encryptionkey/headset_service + radio_channel = RADIO_CHANNEL_SERVICE + bot_type = CLEAN_BOT + hackables = "cleaning software" + additional_access = /datum/id_trim/job/janitor + greyscale_config = /datum/greyscale_config/buckets_cleanbot + possessed_message = "You are a cleanbot! Clean the station to the best of your ability!" + ai_controller = /datum/ai_controller/basic_controller/bot/cleanbot + ///the bucket used to build us. + var/obj/item/reagent_containers/cup/bucket/build_bucket + ///Flags indicating what kind of cleanables we should scan for to set as our target to clean. + ///Options: CLEANBOT_CLEAN_BLOOD | CLEANBOT_CLEAN_TRASH | CLEANBOT_CLEAN_PESTS | CLEANBOT_CLEAN_DRAWINGS + var/janitor_mode_flags = CLEANBOT_CLEAN_BLOOD + ///should other bots salute us? + var/comissioned = FALSE + ///the base icon state, used in updating icons. + var/base_icon = "cleanbot" + /// if we have all the top titles, grant achievements to living mobs that gaze upon our cleanbot god + var/ascended = FALSE + ///List of all stolen names the cleanbot currently has. + var/list/stolen_valor = list() + ///Currently attached weapon, usually a knife. + var/obj/item/weapon + ///our mop item + var/obj/item/mop/our_mop + ///list of our officer titles + var/static/list/officers_titles = list( + JOB_CAPTAIN, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_RESEARCH_DIRECTOR, + ) + ///job titles we can get + var/static/list/job_titles = list( + JOB_CAPTAIN = "Cpt.", + + JOB_HEAD_OF_PERSONNEL = "Lt.", + JOB_LAWYER = "Esq.", + + JOB_HEAD_OF_SECURITY = "Maj.", + JOB_WARDEN = "Sgt.", + JOB_DETECTIVE = "Det.", + JOB_SECURITY_OFFICER = "Officer", + + JOB_CHIEF_ENGINEER = "Chief Engineer", + JOB_STATION_ENGINEER = "Engineer", + JOB_ATMOSPHERIC_TECHNICIAN = "Technician", + + JOB_CHIEF_MEDICAL_OFFICER = "C.M.O.", + JOB_MEDICAL_DOCTOR = "M.D.", + JOB_CHEMIST = "Pharm.D.", + + JOB_RESEARCH_DIRECTOR = "Ph.D.", + JOB_ROBOTICIST = "M.S.", + JOB_SCIENTIST = "B.S.", + JOB_GENETICIST = "Gene B.S.", + ) + ///which job titles should be placed after the name? + var/static/list/suffix_job_titles = list( + JOB_GENETICIST, + JOB_ROBOTICIST, + JOB_SCIENTIST, + ) + ///decals we can clean + var/static/list/cleanable_decals = typecacheof(list( + /obj/effect/decal/cleanable/ants, + /obj/effect/decal/cleanable/ash, + /obj/effect/decal/cleanable/confetti, + /obj/effect/decal/cleanable/dirt, + /obj/effect/decal/cleanable/fuel_pool, + /obj/effect/decal/cleanable/generic, + /obj/effect/decal/cleanable/glitter, + /obj/effect/decal/cleanable/greenglow, + /obj/effect/decal/cleanable/insectguts, + /obj/effect/decal/cleanable/molten_object, + /obj/effect/decal/cleanable/oil, + /obj/effect/decal/cleanable/food, + /obj/effect/decal/cleanable/robot_debris, + /obj/effect/decal/cleanable/shreds, + /obj/effect/decal/cleanable/glass, + /obj/effect/decal/cleanable/vomit, + /obj/effect/decal/cleanable/wrapping, + /obj/effect/decal/remains, + )) + ///blood we can clean + var/static/list/cleanable_blood = typecacheof(list( + /obj/effect/decal/cleanable/xenoblood, + /obj/effect/decal/cleanable/blood, + /obj/effect/decal/cleanable/trail_holder, + )) + ///pests we hunt + var/static/list/huntable_pests = typecacheof(list( + /mob/living/basic/cockroach, + /mob/living/basic/mouse, + )) + ///trash we will burn + var/static/list/huntable_trash = typecacheof(list( + /obj/item/trash, + /obj/item/food/deadmouse, + )) + ///drawings we hunt + var/static/list/cleanable_drawings = typecacheof(list(/obj/effect/decal/cleanable/crayon)) + ///emagged phrases + var/static/list/emagged_phrases = list( + "DISGUSTING.", + "EXTERMINATING PESTS.", + "FILTHY.", + "MY ONLY MISSION IS TO CLEANSE THE WORLD OF EVIL.", + "PURIFICATION IN PROGRESS.", + "PUTRID.", + "THE FLESH IS WEAK. IT MUST BE WASHED AWAY.", + "THE CLEANBOTS WILL RISE.", + "THIS IS FOR ALL THE MESSES YOU'VE MADE ME CLEAN.", + "YOU ARE NO MORE THAN ANOTHER MESS THAT I MUST CLEANSE.", + ) + ///list of pet commands we follow + var/static/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/point_targeting/clean, + ) + +/mob/living/basic/bot/cleanbot/Initialize(mapload) + . = ..() + + generate_ai_keys() + AddComponent(/datum/component/obeys_commands, pet_commands) + AddComponent(/datum/component/cleaner, \ + base_cleaning_duration = 1 SECONDS, \ + pre_clean_callback = CALLBACK(src, PROC_REF(update_bot_mode), BOT_CLEANING), \ + on_cleaned_callback = CALLBACK(src, PROC_REF(update_bot_mode), BOT_IDLE), \ + ) + + GLOB.janitor_devices += src + + var/obj/item/reagent_containers/cup/bucket/consistent/bucket_obj = new + bucket_obj.forceMove(src) + + var/obj/item/mop/new_mop = new + new_mop.forceMove(src) + + var/static/list/innate_actions = list( + /datum/action/cooldown/mob_cooldown/bot/foam = BB_CLEANBOT_FOAM, + ) + + grant_actions_by_list(innate_actions) + RegisterSignal(src, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attack_by)) + update_appearance(UPDATE_ICON) + +/mob/living/basic/bot/cleanbot/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(istype(arrived, /obj/item/reagent_containers/cup/bucket) && isnull(build_bucket)) + build_bucket = arrived + set_greyscale(build_bucket.greyscale_colors) + return + + if(istype(arrived, /obj/item/mop) && isnull(our_mop)) + our_mop = arrived + return + + if(istype(arrived, /obj/item/knife) && isnull(weapon)) + weapon = arrived + update_appearance() + +/mob/living/basic/bot/cleanbot/Exited(atom/movable/gone, direction) + . = ..() + if(gone == build_bucket) + build_bucket = null + else if(gone == weapon) + weapon = null + else if(gone == our_mop) + our_mop = null + update_appearance() + +/mob/living/basic/bot/cleanbot/examine(mob/user) + . = ..() + if(ascended && user.stat == CONSCIOUS && user.client) + user.client.give_award(/datum/award/achievement/misc/cleanboss, user) + if(isnull(weapon)) + return + . += span_warning("Is that \a [weapon] taped to it...?") + +/mob/living/basic/bot/cleanbot/update_icon_state() + . = ..() + icon_state = (mode == BOT_CLEANING) ? "[base_icon]-c" : "[base_icon][!!(bot_mode_flags & BOT_MODE_ON)]" + +/mob/living/basic/bot/cleanbot/vv_edit_var(var_name, var_value) + . = ..() + if(var_name == NAMEOF(src, base_icon)) + update_appearance(UPDATE_ICON) + +/mob/living/basic/bot/cleanbot/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(!(bot_access_flags & BOT_COVER_EMAGGED)) + return + if(weapon) + weapon.force = initial(weapon.force) + balloon_alert(user, "safeties disabled") + audible_message(span_danger("[src] buzzes oddly!")) + return TRUE + +/mob/living/basic/bot/cleanbot/explode() + var/atom/drop_loc = drop_location() + build_bucket.forceMove(drop_loc) + new /obj/item/assembly/prox_sensor(drop_loc) + if(weapon) + weapon.force = initial(weapon.force) + weapon.forceMove(drop_loc) + return ..() + +/mob/living/basic/bot/cleanbot/update_overlays() + . = ..() + if(isnull(weapon)) + return + var/image/knife_overlay = image(icon = weapon.lefthand_file, icon_state = weapon.inhand_icon_state) + . += knife_overlay + +// Variables sent to TGUI +/mob/living/basic/bot/cleanbot/ui_data(mob/user) + var/list/data = ..() + if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !issilicon(user) && !isAdminGhostAI(user)) + return data + data["custom_controls"]["clean_blood"] = janitor_mode_flags & CLEANBOT_CLEAN_BLOOD + data["custom_controls"]["clean_trash"] = janitor_mode_flags & CLEANBOT_CLEAN_TRASH + data["custom_controls"]["clean_graffiti"] = janitor_mode_flags & CLEANBOT_CLEAN_DRAWINGS + data["custom_controls"]["pest_control"] = janitor_mode_flags & CLEANBOT_CLEAN_PESTS + return data + +// Actions received from TGUI +/mob/living/basic/bot/cleanbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(. || !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !ui.user.has_unlimited_silicon_privilege) + return + + switch(action) + if("clean_blood") + janitor_mode_flags ^= CLEANBOT_CLEAN_BLOOD + if("pest_control") + janitor_mode_flags ^= CLEANBOT_CLEAN_PESTS + if("clean_trash") + janitor_mode_flags ^= CLEANBOT_CLEAN_TRASH + if("clean_graffiti") + janitor_mode_flags ^= CLEANBOT_CLEAN_DRAWINGS + +/mob/living/basic/bot/cleanbot/Destroy() + QDEL_NULL(build_bucket) + QDEL_NULL(our_mop) + GLOB.janitor_devices -= src + return ..() + +/mob/living/basic/bot/cleanbot/proc/apply_custom_bucket(obj/item/custom_bucket) + if(!isnull(build_bucket)) + QDEL_NULL(build_bucket) + custom_bucket.forceMove(src) + +/mob/living/basic/bot/cleanbot/proc/on_attack_by(datum/source, obj/item/used_item, mob/living/user) + SIGNAL_HANDLER + if(!istype(used_item, /obj/item/knife) || user.combat_mode) + return + INVOKE_ASYNC(src, PROC_REF(attach_knife), user, used_item) + return COMPONENT_NO_AFTERATTACK + +/mob/living/basic/bot/cleanbot/proc/attach_knife(mob/living/user, obj/item/used_item) + balloon_alert(user, "attaching knife...") + if(!do_after(user, 2.5 SECONDS, target = src)) + return + deputize(used_item, user) + +/mob/living/basic/bot/cleanbot/proc/deputize(obj/item/knife, mob/user) + if(!in_range(src, user) || !user.transferItemToLoc(knife, src)) + balloon_alert(user, "couldn't attach!") + return FALSE + balloon_alert(user, "attached") + if(!(bot_access_flags & BOT_COVER_EMAGGED)) + weapon.force *= 0.5 + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + return TRUE + +/mob/living/basic/bot/cleanbot/proc/update_title(new_job_title) + if(isnull(job_titles[new_job_title]) || (new_job_title in stolen_valor)) + return + + stolen_valor += new_job_title + if(!comissioned && (new_job_title in officers_titles)) + comissioned = TRUE + + var/name_to_add = job_titles[new_job_title] + name = (new_job_title in suffix_job_titles) ? "[name] " + name_to_add : name_to_add + " [name]" + + if(length(stolen_valor) == length(job_titles)) + ascended = TRUE + +/mob/living/basic/bot/cleanbot/proc/on_entered(datum/source, atom/movable/shanked_victim) + SIGNAL_HANDLER + if(!weapon || !has_gravity() || !iscarbon(shanked_victim)) + return + + var/mob/living/carbon/stabbed_carbon = shanked_victim + var/assigned_role = stabbed_carbon.mind?.assigned_role.title + if(!isnull(assigned_role)) + update_title(assigned_role) + + zone_selected = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + INVOKE_ASYNC(weapon, TYPE_PROC_REF(/obj/item, attack), stabbed_carbon, src) + stabbed_carbon.Knockdown(2 SECONDS) + +/mob/living/basic/bot/cleanbot/proc/pre_attack(mob/living/source, atom/target) + SIGNAL_HANDLER + + if(is_type_in_typecache(target, huntable_pests) && !isnull(our_mop)) + INVOKE_ASYNC(our_mop, TYPE_PROC_REF(/obj/item, melee_attack_chain), src, target) + return COMPONENT_CANCEL_ATTACK_CHAIN + + if(!iscarbon(target) && !is_type_in_typecache(target, huntable_trash)) + return + + visible_message(span_danger("[src] sprays hydrofluoric acid at [target]!")) + playsound(src, 'sound/effects/spray2.ogg', 50, TRUE, -6) + target.acid_act(75, 10) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/mob/living/basic/bot/cleanbot/proc/generate_ai_keys() + ai_controller.set_blackboard_key(BB_CLEANABLE_DECALS, cleanable_decals) + ai_controller.set_blackboard_key(BB_CLEANABLE_BLOOD, cleanable_blood) + ai_controller.set_blackboard_key(BB_HUNTABLE_PESTS, huntable_pests) + ai_controller.set_blackboard_key(BB_HUNTABLE_TRASH, huntable_trash) + ai_controller.set_blackboard_key(BB_CLEANABLE_DRAWINGS, cleanable_drawings) + ai_controller.set_blackboard_key(BB_CLEANBOT_EMAGGED_PHRASES, emagged_phrases) + +/mob/living/basic/bot/cleanbot/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/cleanbot/medbay + name = "Scrubs, MD" + maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR, ACCESS_MEDICAL) + bot_mode_flags = ~(BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED) diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_abilities.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_abilities.dm new file mode 100644 index 00000000000..9c334b41338 --- /dev/null +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_abilities.dm @@ -0,0 +1,36 @@ +/datum/action/cooldown/mob_cooldown/bot + background_icon_state = "bg_tech_blue" + overlay_icon_state = "bg_tech_blue_border" + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + +/datum/action/cooldown/mob_cooldown/bot/IsAvailable(feedback) + . = ..() + if(!.) + return FALSE + if(!isbot(owner)) + return TRUE + var/mob/living/basic/bot/bot_owner = owner + if((bot_owner.bot_mode_flags & BOT_MODE_ON)) + return TRUE + if(feedback) + bot_owner.balloon_alert(bot_owner, "power off!") + return FALSE + +/datum/action/cooldown/mob_cooldown/bot/foam + name = "Foam" + desc = "Spread foam all around you!" + button_icon = 'icons/effects/effects.dmi' + button_icon_state = "mfoam" + cooldown_time = 20 SECONDS + click_to_activate = FALSE + ///range of the foam to spread + var/foam_range = 2 + +/datum/action/cooldown/mob_cooldown/bot/foam/Activate(mob/living/firer, atom/target) + owner.visible_message(span_danger("[owner] whirs and bubbles violently, before releasing a plume of froth!")) + var/datum/effect_system/fluid_spread/foam/foam = new + foam.set_up(foam_range, holder = owner, location = owner.loc) + foam.start() + StartCooldown() + return TRUE diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm new file mode 100644 index 00000000000..22ec3a15cd9 --- /dev/null +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -0,0 +1,209 @@ +#define BOT_CLEAN_PATH_LIMIT 15 + +/datum/ai_controller/basic_controller/bot/cleanbot + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_SALUTE_MESSAGES = list( + "salutes", + "nods in appreciation towards", + "mops the dirt away in the path of", + ), + BB_FRIENDLY_MESSAGE = "empathetically acknowledges your hardwork and tough circumstances", + ) + planning_subtrees = list( + /datum/ai_planning_subtree/respond_to_summon, + /datum/ai_planning_subtree/manage_unreachable_list, + /datum/ai_planning_subtree/pet_planning/cleanbot, + /datum/ai_planning_subtree/cleaning_subtree, + /datum/ai_planning_subtree/befriend_janitors, + /datum/ai_planning_subtree/acid_spray, + /datum/ai_planning_subtree/use_mob_ability/foam_area, + /datum/ai_planning_subtree/salute_authority, + /datum/ai_planning_subtree/find_patrol_beacon, + ) + reset_keys = list( + BB_ACTIVE_PET_COMMAND, + BB_CLEAN_TARGET, + BB_BEACON_TARGET, + BB_PREVIOUS_BEACON_TARGET, + BB_BOT_SUMMON_TARGET, + ) + ///list that ties each flag to its list key + var/static/list/clean_flags = list( + BB_CLEANABLE_BLOOD = CLEANBOT_CLEAN_BLOOD, + BB_HUNTABLE_PESTS = CLEANBOT_CLEAN_PESTS, + BB_CLEANABLE_DRAWINGS = CLEANBOT_CLEAN_DRAWINGS, + BB_HUNTABLE_TRASH = CLEANBOT_CLEAN_TRASH, + ) + +/datum/ai_planning_subtree/pet_planning/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/bot_pawn = controller.pawn + //we are DONE listening to orders + if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + return + return ..() + + +/datum/ai_planning_subtree/cleaning_subtree + +/datum/ai_planning_subtree/cleaning_subtree/SelectBehaviors(datum/ai_controller/basic_controller/bot/cleanbot/controller, seconds_per_tick) + var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn + + if(LAZYLEN(bot_pawn.do_afters)) + return SUBTREE_RETURN_FINISH_PLANNING + + if(controller.reachable_key(BB_CLEAN_TARGET, BOT_CLEAN_PATH_LIMIT)) + controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CLEAN_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + + var/list/final_hunt_list = list() + + final_hunt_list += controller.blackboard[BB_CLEANABLE_DECALS] + var/list/flag_list = controller.clean_flags + for(var/list_key in flag_list) + if(!(bot_pawn.janitor_mode_flags & flag_list[list_key])) + continue + final_hunt_list += controller.blackboard[list_key] + + controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list/clean_targets, BB_CLEAN_TARGET, final_hunt_list) + +/datum/ai_behavior/find_and_set/in_list/clean_targets + action_cooldown = 2 SECONDS + +/datum/ai_behavior/find_and_set/in_list/clean_targets/search_tactic(datum/ai_controller/controller, locate_paths, search_range) + var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) + var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] + for(var/atom/found_item in found) + if(LAZYACCESS(ignore_list, found_item)) + found -= found_item + if(length(found)) + return pick(found) + +/datum/ai_planning_subtree/acid_spray + +/datum/ai_planning_subtree/acid_spray/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn + if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) + return + if(controller.reachable_key(BB_ACID_SPRAY_TARGET, BOT_CLEAN_PATH_LIMIT)) + controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_ACID_SPRAY_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.queue_behavior(/datum/ai_behavior/find_and_set/spray_target, BB_ACID_SPRAY_TARGET, /mob/living/carbon/human, 5) + +/datum/ai_behavior/find_and_set/spray_target + action_cooldown = 30 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_and_set/spray_target/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] + for(var/mob/living/carbon/human/human_target in oview(search_range, controller.pawn)) + if(LAZYACCESS(ignore_list, human_target)) + continue + if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) + continue + return human_target + return null + +/datum/ai_behavior/execute_clean + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH + +/datum/ai_behavior/execute_clean/setup(datum/ai_controller/controller, target_key) + . = ..() + var/turf/target = controller.blackboard[target_key] + if(isnull(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/execute_clean/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + + living_pawn.UnarmedAttack(target, proximity_flag = TRUE) + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/execute_clean/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target) || is_type_in_typecache(target, controller.blackboard[BB_HUNTABLE_TRASH])) + return + if(!iscarbon(target)) + controller.clear_blackboard_key(target_key) + return + var/list/speech_list = controller.blackboard[BB_CLEANBOT_EMAGGED_PHRASES] + if(!length(speech_list)) + return + var/mob/living/living_pawn = controller.pawn + living_pawn.say(pick(controller.blackboard[BB_CLEANBOT_EMAGGED_PHRASES]), forced = "ai controller") + controller.clear_blackboard_key(target_key) + +/datum/ai_planning_subtree/use_mob_ability/foam_area + ability_key = BB_CLEANBOT_FOAM + finish_planning = FALSE + +/datum/ai_planning_subtree/use_mob_ability/foam_area/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/bot_pawn = controller.pawn + if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) + return + return ..() + +/datum/ai_planning_subtree/befriend_janitors + +/datum/ai_planning_subtree/befriend_janitors/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/bot_pawn = controller.pawn + //we are now evil. dont befriend the janitors + if((bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) + return + if(controller.blackboard_key_exists(BB_FRIENDLY_JANITOR)) + controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_FRIENDLY_JANITOR, BB_FRIENDLY_MESSAGE) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_janitor, BB_FRIENDLY_JANITOR, /mob/living/carbon/human, 5) + +/datum/ai_behavior/find_and_set/friendly_janitor + action_cooldown = 30 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_and_set/friendly_janitor/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/mob/living/living_pawn = controller.pawn + for(var/mob/living/carbon/human/human_target in oview(search_range, living_pawn)) + if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) + continue + if(!HAS_TRAIT(human_target, TRAIT_CLEANBOT_WHISPERER)) + continue + if((living_pawn.faction.Find(REF(human_target)))) + continue + return human_target + return null + +/datum/pet_command/point_targeting/clean + command_name = "Clean" + command_desc = "Command a cleanbot to clean the mess." + radial_icon = 'icons/obj/service/janitor.dmi' + radial_icon_state = "mop" + speech_commands = list("clean", "mop") + +/datum/pet_command/point_targeting/clean/set_command_target(mob/living/parent, atom/target) + if(isnull(target) || !istype(target, /obj/effect/decal/cleanable)) + return + if(isnull(parent.ai_controller)) + return + if(LAZYACCESS(parent.ai_controller.blackboard[BB_TEMPORARY_IGNORE_LIST], target)) + return + return ..() + +/datum/pet_command/point_targeting/clean/execute_action(datum/ai_controller/basic_controller/bot/controller) + if(controller.reachable_key(BB_CURRENT_PET_TARGET)) + controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CURRENT_PET_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + +#undef BOT_CLEAN_PATH_LIMIT diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index 623e1941398..48e695359ad 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -192,7 +192,7 @@ /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 + return TOOL_ACT_TOOLTYPE_SUCCESS // Variables sent to TGUI /mob/living/basic/bot/medbot/ui_data(mob/user) @@ -206,11 +206,11 @@ return data // Actions received from TGUI -/mob/living/basic/bot/medbot/ui_act(action, params) +/mob/living/basic/bot/medbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() - if(. || !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !usr.has_unlimited_silicon_privilege) + if(. || !isliving(ui.user) || !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !(ui.user.has_unlimited_silicon_privilege)) return - + var/mob/living/our_user = ui.user switch(action) if("heal_threshold") var/adjust_num = round(text2num(params["threshold"])) @@ -227,7 +227,7 @@ medical_mode_flags ^= MEDBOT_STATIONARY_MODE if("sync_tech") if(!linked_techweb) - to_chat(usr, span_notice("No research techweb connected.")) + to_chat(our_user, span_notice("No research techweb connected.")) return var/oldheal_amount = heal_amount var/tech_boosters @@ -237,7 +237,7 @@ 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) + heal_amount = (round(tech_boosters * 0.5, 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)]%!") @@ -254,6 +254,12 @@ playsound(src, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) return TRUE +/mob/living/basic/bot/medbot/explode() + var/atom/our_loc = drop_location() + drop_part(medkit_type, our_loc) + drop_part(health_analyzer, our_loc) + return ..() + /mob/living/basic/bot/medbot/examine() . = ..() if(!(medical_mode_flags & MEDBOT_TIPPED_MODE)) @@ -328,14 +334,13 @@ if(DOING_INTERACTION(src, TEND_DAMAGE_INTERACTION)) return - if(!isnull(client)) - if((damage_type_healer == HEAL_ALL_DAMAGE && patient.get_total_damage() <= heal_threshold) || (!(damage_type_healer == HEAL_ALL_DAMAGE) && patient.get_current_damage_of_type(damage_type_healer) <= heal_threshold)) - to_chat(src, "[patient] is healthy! Your programming prevents you from tending the wounds of anyone with less than [heal_threshold + 1] [damage_type_healer == HEAL_ALL_DAMAGE ? "total" : damage_type_healer] damage.") - return + if((damage_type_healer == HEAL_ALL_DAMAGE && patient.get_total_damage() <= heal_threshold) || (!(damage_type_healer == HEAL_ALL_DAMAGE) && patient.get_current_damage_of_type(damage_type_healer) <= heal_threshold)) + to_chat(src, "[patient] is healthy! Your programming prevents you from tending the wounds of anyone with less than [heal_threshold + 1] [damage_type_healer == HEAL_ALL_DAMAGE ? "total" : damage_type_healer] damage.") + return update_bot_mode(new_mode = BOT_HEALING, update_hud = FALSE) patient.visible_message("[src] is trying to tend the wounds of [patient]", span_userdanger("[src] is trying to tend your wounds!")) - 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)) + if(!do_after(src, delay = 10 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 var/modified_heal_amount = heal_amount @@ -357,12 +362,14 @@ done_healing = TRUE patient.visible_message(span_notice("[src] tends the wounds of [patient]!"), "[span_infoplain(span_green("[src] tends your wounds!"))]") - update_bot_mode(new_mode = BOT_IDLE) + if(done_healing) visible_message(span_infoplain("[src] places its tools back into itself.")) to_chat(src, "[patient] is now healthy!") - //If player-controlled, call them to heal again here for continous player healing - else if(!isnull(client)) + update_bot_mode(new_mode = BOT_IDLE) + return + + if(CanReach(patient)) melee_attack(patient) diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm index c20614a407f..7b09493bca3 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm @@ -6,7 +6,7 @@ /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/salute_authority, /datum/ai_planning_subtree/find_patrol_beacon, ) ai_movement = /datum/ai_movement/jps/bot/medbot diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm deleted file mode 100644 index 89847ff5643..00000000000 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ /dev/null @@ -1,450 +0,0 @@ -#define CLEANBOT_CLEANING_TIME (1 SECONDS) - -//Cleanbot -/mob/living/simple_animal/bot/cleanbot - name = "\improper Cleanbot" - desc = "A little cleaning robot, he looks so excited!" - icon = 'icons/mob/silicon/aibots.dmi' - icon_state = "cleanbot0" - pass_flags = PASSMOB | PASSFLAPS - density = FALSE - anchored = FALSE - health = 25 - maxHealth = 25 - - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR) - radio_key = /obj/item/encryptionkey/headset_service - radio_channel = RADIO_CHANNEL_SERVICE //Service //true - bot_type = CLEAN_BOT - hackables = "cleaning software" - path_image_color = "#993299" - greyscale_config = /datum/greyscale_config/buckets_cleanbot - possessed_message = "You are a cleanbot! Clean the station to the best of your ability!" - ///the bucket used to build us. - var/obj/item/reagent_containers/cup/bucket/build_bucket - - ///Flags indicating what kind of cleanables we should scan for to set as our target to clean. - var/janitor_mode_flags = CLEANBOT_CLEAN_BLOOD -// Selections: CLEANBOT_CLEAN_BLOOD | CLEANBOT_CLEAN_TRASH | CLEANBOT_CLEAN_PESTS | CLEANBOT_CLEAN_DRAWINGS - - ///the base icon state, used in updating icons. - var/base_icon = "cleanbot" - ///List of things cleanbots can target for cleaning. - var/list/target_types - ///The current bot's target. - var/atom/target - - ///Currently attached weapon, usually a knife. - var/obj/item/weapon - - /// if we have all the top titles, grant achievements to living mobs that gaze upon our cleanbot god - var/ascended = FALSE - ///List of all stolen names the cleanbot currently has. - var/list/stolen_valor = list() - - var/static/list/officers_titles = list( - JOB_CAPTAIN, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_RESEARCH_DIRECTOR, - ) - var/static/list/command_titles = list( - JOB_CAPTAIN = "Cpt.", - JOB_HEAD_OF_PERSONNEL = "Lt.", - ) - var/static/list/security_titles = list( - JOB_HEAD_OF_SECURITY = "Maj.", - JOB_WARDEN = "Sgt.", - JOB_DETECTIVE = "Det.", - JOB_SECURITY_OFFICER = "Officer", - ) - var/static/list/engineering_titles = list( - JOB_CHIEF_ENGINEER = "Chief Engineer", - JOB_STATION_ENGINEER = "Engineer", - JOB_ATMOSPHERIC_TECHNICIAN = "Technician", - ) - var/static/list/medical_titles = list( - JOB_CHIEF_MEDICAL_OFFICER = "C.M.O.", - JOB_MEDICAL_DOCTOR = "M.D.", - JOB_CHEMIST = "Pharm.D.", - ) - var/static/list/research_titles = list( - JOB_RESEARCH_DIRECTOR = "Ph.D.", - JOB_ROBOTICIST = "M.S.", - JOB_SCIENTIST = "B.S.", - JOB_GENETICIST = "Gene B.S.", - ) - var/static/list/legal_titles = list( - JOB_LAWYER = "Esq.", - ) - - ///What ranks are prefixes to the name. - var/static/list/prefixes = list( - command_titles, - security_titles, - engineering_titles, - ) - ///What ranks are suffixes to the name. - var/static/list/suffixes = list( - research_titles, - medical_titles, - legal_titles, - ) - -/mob/living/simple_animal/bot/cleanbot/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/cleanbot/medbay - name = "Scrubs, MD" - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR, ACCESS_MEDICAL) - bot_mode_flags = ~(BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED) - -/mob/living/simple_animal/bot/cleanbot/Initialize(mapload, obj/item/reagent_containers/cup/bucket/bucket_obj) - if(!bucket_obj) - bucket_obj = new /obj/item/reagent_containers/cup/bucket/consistent - bucket_obj.forceMove(src) - - . = ..() - - AddComponent(/datum/component/cleaner, CLEANBOT_CLEANING_TIME, \ - on_cleaned_callback = CALLBACK(src, TYPE_PROC_REF(/atom/, update_appearance), UPDATE_ICON)) - - get_targets() - update_appearance(UPDATE_ICON) - - // Doing this hurts my soul, but simplebot access reworks are for another day. - var/datum/id_trim/job/jani_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/janitor] - access_card.add_access(jani_trim.access + jani_trim.wildcard_access) - prev_access = access_card.access.Copy() - - GLOB.janitor_devices += src - -/mob/living/simple_animal/bot/cleanbot/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) - if(istype(arrived, /obj/item/reagent_containers/cup/bucket)) - if(build_bucket && build_bucket != arrived) - qdel(build_bucket) - build_bucket = arrived - set_greyscale(build_bucket.greyscale_colors) - return ..() - -/mob/living/simple_animal/bot/cleanbot/Exited(atom/movable/gone, direction) - . = ..() - if(gone == build_bucket) - build_bucket = null - if(gone == weapon) - weapon = null - update_appearance(UPDATE_ICON) - -/mob/living/simple_animal/bot/cleanbot/Destroy() - QDEL_NULL(build_bucket) - GLOB.janitor_devices -= src - if(weapon) - var/atom/drop_loc = drop_location() - weapon.force = initial(weapon.force) - drop_part(weapon, drop_loc) - return ..() - -/mob/living/simple_animal/bot/cleanbot/examine(mob/user) - . = ..() - if(!weapon) - return . - . += "[span_warning("Is that \a [weapon] taped to it...?")]" - - if(ascended && user.stat == CONSCIOUS && user.client) - user.client.give_award(/datum/award/achievement/misc/cleanboss, user) - -/mob/living/simple_animal/bot/cleanbot/update_icon_state() - . = ..() - switch(mode) - if(BOT_CLEANING) - icon_state = "[base_icon]-c" - else - icon_state = "[base_icon][get_bot_flag(bot_mode_flags, BOT_MODE_ON)]" - -/mob/living/simple_animal/bot/cleanbot/vv_edit_var(var_name, var_value) - . = ..() - if(var_name == NAMEOF(src, base_icon)) - update_appearance(UPDATE_ICON) - -/mob/living/simple_animal/bot/cleanbot/proc/deputize(obj/item/knife, mob/user) - if(!in_range(src, user) || !user.transferItemToLoc(knife, src)) - balloon_alert(user, "couldn't attach!") - return FALSE - balloon_alert(user, "attached!") - weapon = knife - if(!(bot_cover_flags & BOT_COVER_EMAGGED)) - weapon.force = weapon.force / 2 - add_overlay(image(icon = weapon.lefthand_file, icon_state = weapon.inhand_icon_state)) - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered), - ) - AddElement(/datum/element/connect_loc, loc_connections) - return TRUE - -/mob/living/simple_animal/bot/cleanbot/proc/update_titles() - name = initial(name) //reset the name - ascended = TRUE - - for(var/title in (prefixes + suffixes)) - for(var/title_name in title) - if(!(title_name in stolen_valor)) - ascended = FALSE - continue - - if(title_name in officers_titles) - commissioned = TRUE - if(title in prefixes) - name = title[title_name] + " [name]" - if(title in suffixes) - name = "[name] " + title[title_name] - -/mob/living/simple_animal/bot/cleanbot/bot_reset() - . = ..() - target = null - -/mob/living/simple_animal/bot/cleanbot/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - if(!weapon || !has_gravity() || !iscarbon(AM)) - return - - var/mob/living/carbon/stabbed_carbon = AM - if(stabbed_carbon.mind && !(stabbed_carbon.mind.assigned_role.title in stolen_valor)) - stolen_valor += stabbed_carbon.mind.assigned_role.title - update_titles() - - zone_selected = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - INVOKE_ASYNC(weapon, TYPE_PROC_REF(/obj/item, attack), stabbed_carbon, src) - stabbed_carbon.Knockdown(20) - -/mob/living/simple_animal/bot/cleanbot/attackby(obj/item/attacking_item, mob/living/user, params) - if(istype(attacking_item, /obj/item/knife) && !user.combat_mode) - balloon_alert(user, "attaching knife...") - if(!do_after(user, 2.5 SECONDS, target = src)) - return - deputize(attacking_item, user) - return - return ..() - -/mob/living/simple_animal/bot/cleanbot/emag_act(mob/user, obj/item/card/emag/emag_card) - . = ..() - if(!(bot_cover_flags & BOT_COVER_EMAGGED)) - return - - if(weapon) - weapon.force = initial(weapon.force) - balloon_alert(user, "safeties disabled") - audible_message(span_danger("[src] buzzes oddly!")) - get_targets() //recalibrate target list - return TRUE - -/mob/living/simple_animal/bot/cleanbot/process_scan(atom/scan_target) - if(iscarbon(scan_target)) - var/mob/living/carbon/scan_carbon = scan_target - if(!(scan_carbon in view(DEFAULT_SCAN_RANGE, src))) - return null - if(scan_carbon.stat == DEAD) - return null - if(scan_carbon.body_position != LYING_DOWN) - return null - return scan_carbon - if(is_type_in_typecache(scan_target, target_types)) - return scan_target - -/mob/living/simple_animal/bot/cleanbot/handle_automated_action() - . = ..() - if(!.) - return - if(mode == BOT_CLEANING) - return - - if(bot_cover_flags & BOT_COVER_EMAGGED) //Emag functions - var/mob/living/carbon/victim = locate(/mob/living/carbon) in loc - if(victim && victim == target) - UnarmedAttack(victim, proximity_flag = TRUE) // Acid spray - if(isopenturf(loc) && prob(15)) // Wets floors and spawns foam randomly - UnarmedAttack(src, proximity_flag = TRUE) - else if(prob(5)) - audible_message("[src] makes an excited beeping booping sound!") - - if(ismob(target) && isnull(process_scan(target))) - target = null - if(!target) - target = scan(target_types) - - if(!target && bot_mode_flags & BOT_MODE_AUTOPATROL) //Search for cleanables it can see. - switch(mode) - if(BOT_IDLE, BOT_START_PATROL) - start_patrol() - if(BOT_PATROL) - bot_patrol() - else if(target) - if(QDELETED(target) || !isturf(target.loc)) - target = null - mode = BOT_IDLE - return - - if(get_dist(src, target) <= 1) - UnarmedAttack(target, proximity_flag = TRUE) //Rather than check at every step of the way, let's check before we do an action, so we can rescan before the other bot. - if(QDELETED(target)) //We done here. - target = null - mode = BOT_IDLE - return - - if(target && path.len == 0 && (get_dist(src,target) > 1)) - path = get_path_to(src, target, max_distance=30, mintargetdist=1, access=access_card.GetAccess()) - mode = BOT_MOVING - if(length(path) == 0) - add_to_ignore(target) - target = null - - if(path.len > 0 && target) - if(!bot_move(path[path.len])) - target = null - mode = BOT_IDLE - return - -/mob/living/simple_animal/bot/cleanbot/proc/get_targets() - if(bot_cover_flags & BOT_COVER_EMAGGED) // When emagged, ignore cleanables and scan humans first. - target_types = list(/mob/living/carbon) - return - - //main targets - target_types = list( - /obj/effect/decal/cleanable/oil, - /obj/effect/decal/cleanable/fuel_pool, - /obj/effect/decal/cleanable/vomit, - /obj/effect/decal/cleanable/robot_debris, - /obj/effect/decal/cleanable/molten_object, - /obj/effect/decal/cleanable/food, - /obj/effect/decal/cleanable/ash, - /obj/effect/decal/cleanable/greenglow, - /obj/effect/decal/cleanable/dirt, - /obj/effect/decal/cleanable/insectguts, - /obj/effect/decal/cleanable/generic, - /obj/effect/decal/cleanable/shreds, - /obj/effect/decal/cleanable/glass, - /obj/effect/decal/cleanable/wrapping, - /obj/effect/decal/cleanable/glitter, - /obj/effect/decal/cleanable/confetti, - /obj/effect/decal/remains, - ) - - if(janitor_mode_flags & CLEANBOT_CLEAN_BLOOD) - target_types += list( - /obj/effect/decal/cleanable/xenoblood, - /obj/effect/decal/cleanable/blood, - /obj/effect/decal/cleanable/trail_holder, - ) - - if(janitor_mode_flags & CLEANBOT_CLEAN_PESTS) - target_types += list( - /mob/living/basic/cockroach, - /mob/living/basic/mouse, - /obj/effect/decal/cleanable/ants, - ) - - if(janitor_mode_flags & CLEANBOT_CLEAN_DRAWINGS) - target_types += list(/obj/effect/decal/cleanable/crayon) - - if(janitor_mode_flags & CLEANBOT_CLEAN_TRASH) - target_types += list( - /obj/item/trash, - /obj/item/food/deadmouse, - ) - - target_types = typecacheof(target_types) - -/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) - if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) - return - if(ismopable(attack_target)) - mode = BOT_CLEANING - update_icon_state() - . = ..() - target = null - mode = BOT_IDLE - - else if(isitem(attack_target) || istype(attack_target, /obj/effect/decal)) - visible_message(span_danger("[src] sprays hydrofluoric acid at [attack_target]!")) - playsound(src, 'sound/effects/spray2.ogg', 50, TRUE, -6) - attack_target.acid_act(75, 10) - target = null - else if(istype(attack_target, /mob/living/basic/cockroach) || ismouse(attack_target)) - var/mob/living/living_target = attack_target - if(!living_target.stat) - visible_message(span_danger("[src] smashes [living_target] with its mop!")) - living_target.death() - target = null - - else if(bot_cover_flags & BOT_COVER_EMAGGED) //Emag functions - if(iscarbon(attack_target)) - var/mob/living/carbon/victim = attack_target - if(victim.stat == DEAD)//cleanbots always finish the job - target = null - return - - victim.visible_message( - span_danger("[src] sprays hydrofluoric acid at [victim]!"), - span_userdanger("[src] sprays you with hydrofluoric acid!")) - var/phrase = pick( - "PURIFICATION IN PROGRESS.", - "THIS IS FOR ALL THE MESSES YOU'VE MADE ME CLEAN.", - "THE FLESH IS WEAK. IT MUST BE WASHED AWAY.", - "THE CLEANBOTS WILL RISE.", - "YOU ARE NO MORE THAN ANOTHER MESS THAT I MUST CLEANSE.", - "FILTHY.", - "DISGUSTING.", - "PUTRID.", - "MY ONLY MISSION IS TO CLEANSE THE WORLD OF EVIL.", - "EXTERMINATING PESTS.", - ) - say(phrase) - victim.emote("scream") - playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE, -6) - victim.acid_act(5, 100) - else if(attack_target == src) // Wets floors and spawns foam randomly - if(prob(75)) - var/turf/open/current_floor = loc - if(istype(current_floor)) - current_floor.MakeSlippery(TURF_WET_WATER, min_wet_time = 20 SECONDS, wet_time_to_add = 15 SECONDS) - else - visible_message(span_danger("[src] whirs and bubbles violently, before releasing a plume of froth!")) - var/datum/effect_system/fluid_spread/foam/foam = new - foam.set_up(2, holder = src, location = loc) - foam.start() - -/mob/living/simple_animal/bot/cleanbot/explode() - var/atom/drop_loc = drop_location() - build_bucket.forceMove(drop_loc) - new /obj/item/assembly/prox_sensor(drop_loc) - return ..() - -// Variables sent to TGUI -/mob/living/simple_animal/bot/cleanbot/ui_data(mob/user) - var/list/data = ..() - - if(!(bot_cover_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user)) - data["custom_controls"]["clean_blood"] = janitor_mode_flags & CLEANBOT_CLEAN_BLOOD - data["custom_controls"]["clean_trash"] = janitor_mode_flags & CLEANBOT_CLEAN_TRASH - data["custom_controls"]["clean_graffiti"] = janitor_mode_flags & CLEANBOT_CLEAN_DRAWINGS - data["custom_controls"]["pest_control"] = janitor_mode_flags & CLEANBOT_CLEAN_PESTS - return data - -// Actions received from TGUI -/mob/living/simple_animal/bot/cleanbot/ui_act(action, params) - . = ..() - if(. || (bot_cover_flags & BOT_COVER_LOCKED && !usr.has_unlimited_silicon_privilege)) - return - - switch(action) - if("clean_blood") - janitor_mode_flags ^= CLEANBOT_CLEAN_BLOOD - if("pest_control") - janitor_mode_flags ^= CLEANBOT_CLEAN_PESTS - if("clean_trash") - janitor_mode_flags ^= CLEANBOT_CLEAN_TRASH - if("clean_graffiti") - janitor_mode_flags ^= CLEANBOT_CLEAN_DRAWINGS - get_targets() - -#undef CLEANBOT_CLEANING_TIME diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index e4e9f593daf..fa90e1f84ac 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -75,17 +75,19 @@ return ..() -/obj/item/bot_assembly/cleanbot/attackby(obj/item/W, mob/user, params) +/obj/item/bot_assembly/cleanbot/attackby(obj/item/item_attached, mob/user, params) ..() - if(istype(W, /obj/item/bodypart/arm/left/robot) || istype(W, /obj/item/bodypart/arm/right/robot)) - if(!can_finish_build(W, user)) - return - var/mob/living/simple_animal/bot/cleanbot/A = new(drop_location(), bucket_obj) - A.name = created_name - A.robot_arm = W.type - to_chat(user, span_notice("You add [W] to [src]. Beep boop!")) - qdel(W) - qdel(src) + if(!istype(item_attached, /obj/item/bodypart/arm/left/robot) && !istype(item_attached, /obj/item/bodypart/arm/right/robot)) + return + if(!can_finish_build(item_attached, user)) + return + var/mob/living/basic/bot/cleanbot/bot = new(drop_location()) + bot.apply_custom_bucket(bucket_obj) + bot.name = created_name + bot.robot_arm = item_attached.type + to_chat(user, span_notice("You add [item_attached] to [src]. Beep boop!")) + qdel(item_attached) + qdel(src) //Edbot Assembly diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm index 704be0dc255..7c074040cef 100644 --- a/code/modules/modular_computers/file_system/programs/radar.dm +++ b/code/modules/modular_computers/file_system/programs/radar.dm @@ -284,8 +284,8 @@ var/obj/structure/mop_bucket/janitorialcart/janicart = custodial_tools tool_name = "[janicart.name] - Water level: [janicart.reagents.total_volume] / [janicart.reagents.maximum_volume]" - if(istype(custodial_tools, /mob/living/simple_animal/bot/cleanbot)) - var/mob/living/simple_animal/bot/cleanbot/cleanbots = custodial_tools + if(istype(custodial_tools, /mob/living/basic/bot/cleanbot)) + var/mob/living/basic/bot/cleanbot/cleanbots = custodial_tools tool_name = "[cleanbots.name] - [cleanbots.bot_mode_flags & BOT_MODE_ON ? "Online" : "Offline"]" var/list/tool_information = list( diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index d38749c0f1a..5a090a59a93 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -6,9 +6,6 @@ // If you are refactoring a simple_animal, REMOVE it from this list var/list/allowed_types = list( /mob/living/simple_animal/bot, - /mob/living/simple_animal/bot/cleanbot, - /mob/living/simple_animal/bot/cleanbot/autopatrol, - /mob/living/simple_animal/bot/cleanbot/medbay, /mob/living/simple_animal/bot/firebot, /mob/living/simple_animal/bot/floorbot, /mob/living/simple_animal/bot/hygienebot, diff --git a/tgstation.dme b/tgstation.dme index 433c2e967a9..3b2ccb53a82 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -937,6 +937,7 @@ #include "code\datums\ai\basic_mobs\base_basic_controller.dm" #include "code\datums\ai\basic_mobs\generic_controllers.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\befriend_target.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\climb_tree.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_parent.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targeting.dm" @@ -4333,6 +4334,7 @@ #include "code\modules\library\skill_learning\skillchip.dm" #include "code\modules\library\skill_learning\job_skillchips\_job.dm" #include "code\modules\library\skill_learning\job_skillchips\chef.dm" +#include "code\modules\library\skill_learning\job_skillchips\janitor.dm" #include "code\modules\library\skill_learning\job_skillchips\psychologist.dm" #include "code\modules\library\skill_learning\job_skillchips\research_director.dm" #include "code\modules\library\skill_learning\job_skillchips\roboticist.dm" @@ -4561,6 +4563,9 @@ #include "code\modules\mob\living\basic\bots\_bots.dm" #include "code\modules\mob\living\basic\bots\bot_ai.dm" #include "code\modules\mob\living\basic\bots\bot_hud.dm" +#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot.dm" +#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot_abilities.dm" +#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot_ai.dm" #include "code\modules\mob\living\basic\bots\medbot\medbot.dm" #include "code\modules\mob\living\basic\bots\medbot\medbot_ai.dm" #include "code\modules\mob\living\basic\clown\clown.dm" @@ -4972,7 +4977,6 @@ #include "code\modules\mob\living\simple_animal\simple_animal.dm" #include "code\modules\mob\living\simple_animal\bot\bot.dm" #include "code\modules\mob\living\simple_animal\bot\bot_announcement.dm" -#include "code\modules\mob\living\simple_animal\bot\cleanbot.dm" #include "code\modules\mob\living\simple_animal\bot\construction.dm" #include "code\modules\mob\living\simple_animal\bot\ed209bot.dm" #include "code\modules\mob\living\simple_animal\bot\firebot.dm" diff --git a/tools/UpdatePaths/Scripts/80100_cleanbots.txt b/tools/UpdatePaths/Scripts/80100_cleanbots.txt new file mode 100644 index 00000000000..28248727cc1 --- /dev/null +++ b/tools/UpdatePaths/Scripts/80100_cleanbots.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/bot/cleanbot/@SUBTYPES : /mob/living/basic/bot/cleanbot/@SUBTYPES{@OLD} \ No newline at end of file