From af67bd7490f2f11c74671d9060bb6a0ee3f00414 Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Sat, 9 Dec 2023 01:26:10 +0200 Subject: [PATCH] basic cleanbots refactor and new janitor skillchip (#80128) ## About The Pull Request this pr refacotrs cleanbots into basic bots. also adds a new skillchip for janitrs. this skillchip will allow janitors to communicate with cleanbots and order them around, like pointing at something and telling them to clean it. also now the cleanbot has an inbuilt mop which it will use to smack mice and cockroaches ## Why It's Good For The Game refactors cleanbots into basic bots and fixes them getting stuck sometimes while patrolling. also janitors being able to order them around can make them a bit more useful as tools for the janitor ## Changelog :cl: refactor: cleanbots are refactored into basic bots. please report all bugs fix: fixes cleanbots getting stuck sometimes while patrolling add: janitors get a new skillchip which allow them to communicate with cleanbots /:cl: --- _maps/RandomRuins/SpaceRuins/oldstation.dmm | 4 +- _maps/map_files/Birdshot/birdshot.dmm | 2 +- .../map_files/IceBoxStation/IceBoxStation.dmm | 2 +- _maps/map_files/MetaStation/MetaStation.dmm | 6 +- _maps/map_files/NorthStar/north_star.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 + 34 files changed, 768 insertions(+), 534 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 37aafecdec3..71627ec3034 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/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 3cdca32faab..aedf70f3766 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -53003,7 +53003,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 52ee801a099..b2bf3ad02f9 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -3493,7 +3493,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" = ( @@ -11442,7 +11442,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" = ( @@ -64703,7 +64703,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/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index 760d0df6fea..987ff3c9858 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -2981,7 +2981,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/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index 9b9393c33bd..5b8bcf064b1 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -15564,7 +15564,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" = ( @@ -70294,7 +70294,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 e7808943d3d..1d44626a0d0 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 45b08e2fcb2..e5728565310 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1002,6 +1002,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 946924883c0..a45fe3f5b26 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 31feac017a7..8d425e5f1de 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 bf6c3423a01..7b562bca670 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 af040a7d110..5611ea804be 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)) @@ -322,14 +328,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 = 0.5 SECONDS, target = patient, interaction_key = TEND_DAMAGE_INTERACTION)) + if(!do_after(src, delay = 2 SECONDS, target = patient, interaction_key = TEND_DAMAGE_INTERACTION)) update_bot_mode(new_mode = BOT_IDLE) return var/modified_heal_amount = heal_amount @@ -351,12 +356,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 733fc3c0395..05a121d2a32 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 09376337251..d4d318e9820 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 239f702e245..4c4c0b553fc 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 7d90db878ad..991386ce086 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -847,6 +847,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" @@ -4235,6 +4236,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" @@ -4462,6 +4464,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" @@ -4874,7 +4879,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