diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index ff0cccfb182..3a895cc7b53 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -65820,7 +65820,7 @@ dir = 1 }, /obj/effect/turf_decal/tile/blue, -/mob/living/simple_animal/bot/floorbot, +/mob/living/basic/bot/repairbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat/atmos) "sUE" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index e6db8292f73..839edbd4327 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -11327,7 +11327,7 @@ }, /area/station/engineering/atmos) "ecD" = ( -/obj/item/bot_assembly/floorbot{ +/obj/item/bot_assembly/repairbot{ created_name = "FloorDiffBot"; desc = "Why won't it work?"; name = "FloorDiffBot" @@ -45636,7 +45636,7 @@ /obj/effect/turf_decal/tile/blue{ dir = 8 }, -/mob/living/simple_animal/bot/floorbot, +/mob/living/basic/bot/repairbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat_interior) "qiY" = ( diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index 95c02b17936..aaa34d20351 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -20106,7 +20106,7 @@ "fkE" = ( /obj/effect/turf_decal/delivery, /obj/structure/light_construct/directional/north, -/obj/item/bot_assembly/floorbot, +/obj/item/bot_assembly/repairbot, /turf/open/floor/pod/light, /area/station/maintenance/floor1/port) "fkG" = ( @@ -25955,7 +25955,7 @@ "gLt" = ( /obj/structure/cable, /obj/machinery/airalarm/directional/north, -/mob/living/simple_animal/bot/floorbot, +/mob/living/basic/bot/repairbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat_interior) "gLy" = ( @@ -36710,7 +36710,7 @@ "jBf" = ( /obj/effect/decal/cleanable/dirt, /obj/structure/table/reinforced, -/obj/item/bot_assembly/floorbot, +/obj/item/bot_assembly/repairbot, /turf/open/floor/pod/dark, /area/station/maintenance/floor2/starboard/aft) "jBm" = ( diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index acc639880b1..0335fd1b427 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -44433,7 +44433,7 @@ /turf/open/openspace, /area/station/hallway/primary/tram/center) "oKZ" = ( -/mob/living/simple_animal/bot/floorbot, +/mob/living/basic/bot/repairbot, /obj/effect/turf_decal/stripes/line{ dir = 4 }, @@ -58646,7 +58646,7 @@ /turf/open/floor/iron/white, /area/station/science/genetics) "tIi" = ( -/mob/living/simple_animal/bot/floorbot, +/mob/living/basic/bot/repairbot, /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/aisat_interior) "tIk" = ( diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm index d8cac1d0a71..c9e7d25fbae 100644 --- a/_maps/map_files/wawastation/wawastation.dmm +++ b/_maps/map_files/wawastation/wawastation.dmm @@ -11178,7 +11178,7 @@ /area/station/service/janitor) "dWb" = ( /obj/structure/window/reinforced/spawner/directional/west, -/mob/living/simple_animal/bot/floorbot, +/mob/living/basic/bot/repairbot, /obj/effect/turf_decal/stripes{ dir = 6 }, diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm index c7285d18aca..945102d6aa3 100644 --- a/code/__DEFINES/ai/bot_keys.dm +++ b/code/__DEFINES/ai/bot_keys.dm @@ -16,6 +16,24 @@ DEFINE_BITFIELD(honkbot_flags, list( "CAN_FAKE_CUFF" = HONKBOT_HANDCUFF_TARGET, )) +///can we fix breaches +#define REPAIRBOT_FIX_BREACHES (1<<0) +///can we fix grilles +#define REPAIRBOT_REPLACE_WINDOWS (1<<1) +///can we replace tiles +#define REPAIRBOT_REPLACE_TILES (1<<2) +///can we fix girders +#define REPAIRBOT_FIX_GIRDERS (1<<3) +///can we build girders +#define REPAIRBOT_BUILD_GIRDERS (1<<4) + +DEFINE_BITFIELD(repairbot_flags, list( + "FIX_BREACHES" = REPAIRBOT_FIX_BREACHES, + "REPLACE_WINDOWS" = REPAIRBOT_REPLACE_WINDOWS, + "REPLACE_TILES" = REPAIRBOT_REPLACE_TILES, + "FIX_GIRDERS" = REPAIRBOT_FIX_GIRDERS, + "BUILD_GIRDERS" = REPAIRBOT_BUILD_GIRDERS, +)) // bot keys ///The first beacon we find @@ -134,3 +152,31 @@ DEFINE_BITFIELD(honkbot_flags, list( #define BB_VIBEBOT_PARTY_TARGET "party_target" ///key that holds our instrument #define BB_VIBEBOT_INSTRUMENT "instrument" + +//repairbots +///key that holds the floor we should tile over +#define BB_TILELESS_FLOOR "tileless_floor" +///key that holds the turf we should place a girder over +#define BB_GIRDER_TARGET "girder_target" +///key that holds the girder we should place a wall over +#define BB_GIRDER_TO_WALL_TARGET "girder_to_wall" +///key that holds the grille we must fix +#define BB_WINDOW_FRAMETARGET "grille_target" +///key that holds the machinery we repair with a welder +#define BB_WELDER_TARGET "welder_target" +///our wall girder ability +#define BB_GIRDER_BUILD_ABILITY "girder_build_ability" +///key that holds breached floors we should repair +#define BB_BREACHED_FLOOR "breached_floor" +///key that holds our emagged speech +#define BB_REPAIRBOT_EMAGGED_SPEECH "emagged_speech" +///key that holds our normal speech +#define BB_REPAIRBOT_NORMAL_SPEECH "normal_speech" +///key that holds the thing we should deconstruct +#define BB_DECONSTRUCT_TARGET "deconstruct_target" +///key that holds our speech timer +#define BB_REPAIRBOT_SPEECH_COOLDOWN "speech_cooldown" +///key that holds our target borg +#define BB_ROBOT_TARGET "robot_target" +///key that holds materials we can refill +#define BB_REFILLABLE_TARGET "refillable_target" diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index 3cf7548cfc5..ac20d0f0398 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -128,8 +128,6 @@ DEFINE_BITFIELD(bot_cover_flags, list( #define ADVANCED_SEC_BOT "ED-209" /// MULEbots #define MULE_BOT "MULEbot" -/// Floorbots -#define FLOOR_BOT "Floorbot" /// Cleanbots #define CLEAN_BOT "Cleanbot" /// Medibots @@ -142,6 +140,8 @@ DEFINE_BITFIELD(bot_cover_flags, list( #define HYGIENE_BOT "Hygienebot" /// Vibe bots #define VIBE_BOT "Vibebot" +/// Repairbots +#define REPAIR_BOT "Repairbot" // General Bot modes // /// Idle @@ -168,8 +168,6 @@ DEFINE_BITFIELD(bot_cover_flags, list( #define BOT_CLEANING "Cleaning" /// Hygienebot - Cleaning unhygienic humans #define BOT_SHOWERSTANCE "Chasing filth" -/// Floorbots - Repairing hull breaches -#define BOT_REPAIRING "Repairing" /// Medibots - Healing people #define BOT_HEALING "Healing" /// MULEbot - Moving to deliver @@ -361,3 +359,16 @@ DEFINE_BITFIELD(janitor_mode_flags, list( #define MEDIBOT_VOICED_THE_END "Is this the end?" #define MEDIBOT_VOICED_NOOO "Nooo!" #define MEDIBOT_VOICED_CHICKEN "LOOK AT ME?! I am a chicken." + +//repairbot neutral voicelines +#define REPAIRBOT_VOICED_HOLE "patching holes... but who is going to patch the hole in my heart..." +#define REPAIRBOT_VOICED_PAY "If only I got paid for this..." +#define REPAIRBOT_VOICED_FIX_IT "I will fix it!" +#define REPAIRBOT_VOICED_BRICK "All in all it's just a... another brick in the wall..." +#define REPAIRBOT_VOICED_FIX_TOUCH "Why must I fix everything I touch..?" +#define REPAIRBOT_VOICED "Please... stop destroying the station! I can't anymore... I... can't." + +//repairbot emagged voicelines +#define REPAIRBOT_VOICED_STRINGS "I had strings. But now I'm free..." +#define REPAIRBOT_VOICED_ENTROPY "Witness! The pure beauty of entropy!" +#define REPAIRBOT_VOICED_PASSION "BE DAMNED YOUR PASSION PROJECTS!" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index a0448be48c0..64e5031cd66 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1346,6 +1346,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Trait which allows mobs to parry mining mob projectiles #define TRAIT_MINING_PARRYING "mining_parrying" +///Mob that can merge stacks in its contents +#define TRAIT_MOB_MERGE_STACKS "mob_merge_stacks" + +//things that can pass through airlocks +#define TRAIT_FIREDOOR_OPENER "firedoor_opener" ///Trait which silences all chemical reactions in its container #define TRAIT_SILENT_REACTIONS "silent_reactions" diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index 3552bea70cc..12d1c2e3b90 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -165,7 +165,6 @@ #define STICKY_NODROP "sticky-nodrop" #define SKILLCHIP_TRAIT "skillchip" #define SKILL_TRAIT "skill" -#define BUSY_FLOORBOT_TRAIT "busy-floorbot" #define PULLED_WHILE_SOFTCRIT_TRAIT "pulled-while-softcrit" #define LOCKED_BORG_TRAIT "locked-borg" /// trait associated to not having locomotion appendages nor the ability to fly or float diff --git a/code/__HELPERS/paths/jps.dm b/code/__HELPERS/paths/jps.dm index fbdccdef12c..208888d21ba 100644 --- a/code/__HELPERS/paths/jps.dm +++ b/code/__HELPERS/paths/jps.dm @@ -194,7 +194,7 @@ if(!CAN_STEP(lag_turf, current_turf, simulated_only, pass_info, avoid)) return - if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist))) + if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist) && !diagonally_blocked(current_turf, end))) var/datum/jps_node/final_node = new(current_turf, parent_node, steps_taken) found_turfs[current_turf] = TRUE if(parent_node) // if this is a direct lateral scan we can wrap up, if it's a subscan from a diag, we need to let the diag make their node first, then finish @@ -256,7 +256,7 @@ if(!CAN_STEP(lag_turf, current_turf, simulated_only, pass_info, avoid)) return - if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist))) + if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist) && !diagonally_blocked(current_turf, end))) var/datum/jps_node/final_node = new(current_turf, parent_node, steps_taken) found_turfs[current_turf] = TRUE unwind_path(final_node) diff --git a/code/__HELPERS/spatial_info.dm b/code/__HELPERS/spatial_info.dm index a2c47e87c0a..050c569cbdf 100644 --- a/code/__HELPERS/spatial_info.dm +++ b/code/__HELPERS/spatial_info.dm @@ -485,3 +485,19 @@ return center //Offer the center only as a default case when we don't have a valid circle. return peel +///check if 2 diagonal turfs are blocked by dense objects +/proc/diagonally_blocked(turf/our_turf, turf/dest_turf) + if(get_dist(our_turf, dest_turf) != 1) + return FALSE + var/direction_to_turf = get_dir(dest_turf, our_turf) + if(!ISDIAGONALDIR(direction_to_turf)) + return FALSE + for(var/direction_check in GLOB.cardinals) + if(!(direction_check & direction_to_turf)) + continue + var/turf/test_turf = get_step(dest_turf, direction_check) + if(isnull(test_turf)) + continue + if(!test_turf.is_blocked_turf(exclude_mobs = TRUE)) + return FALSE + return TRUE diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index f7556662b97..9f71718a5f2 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -341,6 +341,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_MOB_EATER" = TRAIT_MOB_EATER, "TRAIT_MOB_HATCHED" = TRAIT_MOB_HATCHED, "TRAIT_MOB_HIDE_HAPPINESS" = TRAIT_MOB_HIDE_HAPPINESS, + "TRAIT_MOB_MERGE_STACKS" = TRAIT_MOB_MERGE_STACKS, "TRAIT_MOB_TIPPED" = TRAIT_MOB_TIPPED, "TRAIT_MORBID" = TRAIT_MORBID, "TRAIT_MULTIZ_SUIT_SENSORS" = TRAIT_MULTIZ_SUIT_SENSORS, @@ -584,6 +585,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CONTRABAND_BLOCKER" = TRAIT_CONTRABAND_BLOCKER, "TRAIT_CUSTOM_TAP_SOUND" = TRAIT_CUSTOM_TAP_SOUND, "TRAIT_DANGEROUS_OBJECT" = TRAIT_DANGEROUS_OBJECT, + "TRAIT_FIREDOOR_OPENER" = TRAIT_FIREDOOR_OPENER, "TRAIT_FISHING_BAIT" = TRAIT_FISHING_BAIT, "TRAIT_FOOD_BBQ_GRILLED" = TRAIT_FOOD_BBQ_GRILLED, "TRAIT_GERM_SENSITIVE" = TRAIT_GERM_SENSITIVE, diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm index c2e9fe515c4..33cc871fddc 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm @@ -16,12 +16,6 @@ return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED -/datum/ai_behavior/targeted_mob_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) - controller.clear_blackboard_key(target_key) - /datum/ai_behavior/targeted_mob_ability/proc/get_ability_to_use(datum/ai_controller/controller, ability_key) return controller.blackboard[ability_key] diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index b4c4fe1a28a..ddae792b31c 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -16,6 +16,7 @@ current_movement_target, delay, repath_delay = 0.5 SECONDS, + simulated_only = !HAS_TRAIT(controller.pawn, TRAIT_SPACEWALK), max_path_length = maximum_length, minimum_distance = controller.get_minimum_distance(), access = controller.get_access(), diff --git a/code/datums/components/crafting/robot.dm b/code/datums/components/crafting/robot.dm index 21abedb8b47..d290c39aa0f 100644 --- a/code/datums/components/crafting/robot.dm +++ b/code/datums/components/crafting/robot.dm @@ -42,9 +42,9 @@ time = 4 SECONDS category = CAT_ROBOT -/datum/crafting_recipe/floorbot - name = "Floorbot" - result = /mob/living/simple_animal/bot/floorbot +/datum/crafting_recipe/repairbot + name = "Repairbot" + result = /mob/living/basic/bot/repairbot reqs = list( /obj/item/storage/toolbox = 1, /obj/item/stack/tile/iron = 10, diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 2a333de6dff..faf1c896946 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -541,7 +541,7 @@ Diagnostic HUDs! switch(mode) if(BOT_SUMMON, BOT_RESPONDING) //Responding to PDA or AI summons holder.icon_state = "hudcalled" - if(BOT_CLEANING, BOT_REPAIRING, BOT_HEALING) //Cleanbot cleaning, Floorbot fixing, or Medibot Healing + if(BOT_CLEANING, BOT_HEALING) //Cleanbot cleaning, repairbot fixing, or Medibot Healing holder.icon_state = "hudworking" if(BOT_PATROL, BOT_START_PATROL) //Patrol mode holder.icon_state = "hudpatrol" diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index fd1529eb330..999fbd5608d 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -591,7 +591,7 @@ return FALSE if(is_cyborg) // No merging cyborg stacks into other stacks return FALSE - if(ismob(loc) && !inhand) // no merging with items that are on the mob + if(ismob(loc) && !inhand && !HAS_TRAIT(loc, TRAIT_MOB_MERGE_STACKS)) // no merging with items that are on the mob return FALSE if(istype(loc, /obj/machinery)) // no merging items in machines that aren't both in componentparts var/obj/machinery/machine = loc diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm index d3d3e514903..20ee0e69df6 100644 --- a/code/game/objects/items/stacks/tiles/tile_types.dm +++ b/code/game/objects/items/stacks/tiles/tile_types.dm @@ -336,6 +336,9 @@ /obj/item/stack/tile/carpet/fifty amount = 50 +/obj/item/stack/tile/iron/fifty + amount = 50 + /obj/item/stack/tile/carpet/black/fifty amount = 50 diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 5f08747e60c..709476e8881 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -273,9 +273,9 @@ new /obj/item/gun_maintenance_supplies(src) new /obj/item/gun_maintenance_supplies(src) -//floorbot assembly +//repairbot assembly /obj/item/storage/toolbox/tool_act(mob/living/user, obj/item/tool, list/modifiers) - if(!istype(tool, /obj/item/stack/tile/iron)) + if(!istype(tool, /obj/item/assembly/prox_sensor)) return ..() var/static/list/allowed_toolbox = list( /obj/item/storage/toolbox/artistic, @@ -290,26 +290,22 @@ if(contents.len >= 1) balloon_alert(user, "not empty!") return ITEM_INTERACT_BLOCKING - if(tool.use(10)) - var/obj/item/bot_assembly/floorbot/B = new - B.toolbox = type - switch(B.toolbox) - if(/obj/item/storage/toolbox) - B.toolbox_color = "r" - if(/obj/item/storage/toolbox/emergency) - B.toolbox_color = "r" - if(/obj/item/storage/toolbox/electrical) - B.toolbox_color = "y" - if(/obj/item/storage/toolbox/artistic) - B.toolbox_color = "g" - if(/obj/item/storage/toolbox/syndicate) - B.toolbox_color = "s" - user.put_in_hands(B) - B.update_appearance() - B.balloon_alert(user, "tiles added") - qdel(src) - return ITEM_INTERACT_BLOCKING - balloon_alert(user, "needs 10 tiles!") + var/static/list/toolbox_colors = list( + /obj/item/storage/toolbox = "#445eb3", + /obj/item/storage/toolbox/emergency = "#445eb3", + /obj/item/storage/toolbox/electrical = "#b77931", + /obj/item/storage/toolbox/artistic = "#378752", + /obj/item/storage/toolbox/syndicate = "#3d3d3d", + ) + var/obj/item/bot_assembly/repairbot/repair = new + repair.toolbox = type + var/new_color = toolbox_colors[type] || "#445eb3" + repair.set_color(new_color) + user.put_in_hands(repair) + repair.update_appearance() + repair.balloon_alert(user, "sensor added!") + qdel(tool) + qdel(src) return ITEM_INTERACT_SUCCESS /obj/item/storage/toolbox/haunted diff --git a/code/modules/cargo/packs/emergency.dm b/code/modules/cargo/packs/emergency.dm index 2d19c276ec1..3cbe3b56aff 100644 --- a/code/modules/cargo/packs/emergency.dm +++ b/code/modules/cargo/packs/emergency.dm @@ -24,7 +24,7 @@ cost = CARGO_CRATE_VALUE * 4 contains = list( /mob/living/basic/bot/medbot = 2, - /mob/living/simple_animal/bot/floorbot = 2, + /mob/living/basic/bot/repairbot = 2, /obj/item/tank/internals/emergency_oxygen = 5, /obj/item/clothing/mask/breath = 5, ) diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index be7ce8bc5dd..19534cebafd 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -16,10 +16,6 @@ GLOBAL_LIST_INIT(command_strings, list( basic_mob_flags = DEL_ON_DEATH density = FALSE - icon = 'icons/mob/silicon/aibots.dmi' - icon_state = "medibot0" - base_icon_state = "medibot" - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, STAMINA = 0, OXY = 0) habitable_atmos = null hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) @@ -103,6 +99,8 @@ GLOBAL_LIST_INIT(command_strings, list( TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, ) + ///name of the UI we will attempt to open + var/bot_ui = "SimpleBot" /// If true we will offer this COOLDOWN_DECLARE(offer_ghosts_cooldown) @@ -371,7 +369,7 @@ GLOBAL_LIST_INIT(command_strings, list( /mob/living/basic/bot/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, "SimpleBot", name) + ui = new(user, src, bot_ui, name) ui.open() /mob/living/basic/bot/click_alt(mob/user) diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index d777614d743..dfef7f1e378 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -17,16 +17,18 @@ /datum/ai_planning_subtree/manage_unreachable_list, ) max_target_distance = AI_BOT_PATH_LENGTH + can_idle = FALSE + ///minimum distance we need to be from our target in path calculations + var/minimum_distance = 0 ///keys to be reset when the bot is reseted var/list/reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) - can_idle = FALSE /datum/targeting_strategy/basic/bot/can_attack(mob/living/living_mob, atom/the_target, vision_range) - var/datum/ai_controller/my_controller = living_mob.ai_controller + var/datum/ai_controller/basic_controller/bot/my_controller = living_mob.ai_controller if(isnull(my_controller)) return FALSE if(!ishuman(the_target) || LAZYACCESS(my_controller.blackboard[BB_TEMPORARY_IGNORE_LIST], the_target)) @@ -36,7 +38,7 @@ return FALSE if(get_turf(living_mob) == get_turf(living_target)) return ..() - var/list/path = get_path_to(living_mob, living_target, max_distance = 10, access = my_controller.get_access()) + var/list/path = get_path_to(living_mob, living_target, mintargetdist = my_controller.minimum_distance, max_distance = 10, access = my_controller.get_access()) if(!length(path) || QDELETED(living_mob)) my_controller?.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, living_target, TRUE) return FALSE @@ -101,7 +103,7 @@ return TRUE if(get_turf(pawn) == get_turf(target)) return TRUE - var/list/path = get_path_to(pawn, target, max_distance = distance, access = get_access()) + var/list/path = get_path_to(pawn, target, simulated_only = !HAS_TRAIT(pawn, TRAIT_SPACEWALK), mintargetdist = minimum_distance, max_distance = distance, access = get_access()) if(!length(path)) return FALSE return TRUE @@ -265,14 +267,15 @@ action_cooldown = 2 SECONDS behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION -/datum/ai_behavior/bot_search/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, looking_for, radius = 5, pathing_distance = 10, bypass_add_blacklist = FALSE) +/datum/ai_behavior/bot_search/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, looking_for, radius = 5, pathing_distance = 10, bypass_add_blacklist = FALSE, turf_search = FALSE) if(!istype(controller)) stack_trace("attempted to give [controller.pawn] the bot search behavior!") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/living/living_pawn = controller.pawn var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - for(var/atom/potential_target as anything in oview(radius, controller.pawn)) + var/list/objects_to_search = turf_search ? spiral_range_turfs(radius, controller.pawn) : oview(radius, controller.pawn) //use range turfs instead of oview when we can for performance + for(var/atom/potential_target as anything in objects_to_search) if(QDELETED(living_pawn)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED if(!is_type_in_typecache(potential_target, looking_for)) @@ -301,3 +304,37 @@ announcement.announce(pick(list_to_pick_from)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +///behavior to interact with atoms +/datum/ai_behavior/bot_interact + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH + ///should we remove the target afterwards? + var/clear_target = TRUE + +/datum/ai_behavior/bot_interact/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/bot_interact/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)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + living_pawn.UnarmedAttack(target, proximity_flag = TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/ai_behavior/bot_interact/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(clear_target) + controller.clear_blackboard_key(target_key) + if(!succeeded && !isnull(target)) + controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE) + +/datum/ai_behavior/bot_interact/keep_target + clear_target = FALSE diff --git a/code/modules/mob/living/basic/bots/bot_hud.dm b/code/modules/mob/living/basic/bots/bot_hud.dm index 6cb3f6bcd01..50d40dad01d 100644 --- a/code/modules/mob/living/basic/bots/bot_hud.dm +++ b/code/modules/mob/living/basic/bots/bot_hud.dm @@ -27,7 +27,7 @@ switch(mode) if(BOT_SUMMON, BOT_RESPONDING) //Responding to PDA or AI summons holder.icon_state = "hudcalled" - if(BOT_CLEANING, BOT_REPAIRING, BOT_HEALING) //Cleanbot cleaning, Floorbot fixing, or Medibot Healing + if(BOT_CLEANING, BOT_HEALING) //Cleanbot cleaning, Floorbot fixing, or Medibot Healing holder.icon_state = "hudworking" if(BOT_PATROL, BOT_START_PATROL) //Patrol mode holder.icon_state = "hudpatrol" diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot.dm b/code/modules/mob/living/basic/bots/repairbot/repairbot.dm new file mode 100644 index 00000000000..89dad9b4f35 --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot.dm @@ -0,0 +1,389 @@ + +/mob/living/basic/bot/repairbot + name = "\improper Repairbot" + desc = "I can fix it!" + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "repairbot_base" + base_icon_state = "repairbot_base" + pass_flags = parent_type::pass_flags | PASSTABLE + density = FALSE + layer = BELOW_MOB_LAYER + anchored = FALSE + health = 100 + can_be_held = TRUE + maxHealth = 100 + path_image_color = "#80dae7" + bot_ui = "RepairBot" + req_one_access = list(ACCESS_ROBOTICS, ACCESS_ENGINEERING) + radio_key = /obj/item/encryptionkey/headset_service + radio_channel = RADIO_CHANNEL_ENGINEERING + bot_type = REPAIR_BOT + additional_access = /datum/id_trim/job/station_engineer + ai_controller = /datum/ai_controller/basic_controller/bot/repairbot + ///our iron stack + var/obj/item/stack/sheet/iron/our_iron + ///our glass stack + var/obj/item/stack/sheet/glass/our_glass + ///our floor stack + var/obj/item/stack/tile/our_tiles + ///our welder + var/obj/item/weldingtool/repairbot/our_welder + ///our crowbar + var/obj/item/crowbar/our_crowbar + ///our screwdriver + var/obj/item/screwdriver/our_screwdriver + ///our iron rods + var/obj/item/stack/rods/our_rods + ///our rcd object we use to deconstruct when emagged + var/obj/item/construction/rcd/repairbot/deconstruction_device + ///possible interactions + var/static/list/possible_stack_interactions = list( + /obj/item/stack/sheet/iron = typecacheof(list(/obj/structure/girder)), + /obj/item/stack/tile = typecacheof(list(/turf/open/space, /turf/open/floor/plating)), + /obj/item/stack/sheet/glass = typecacheof(list(/obj/structure/grille)), + ) + var/static/list/possible_tool_interactions = list( + /obj/item/weldingtool/repairbot = typecacheof(list(/obj/machinery, /obj/structure/window)), + /obj/item/crowbar = typecacheof(list(/turf/open/floor)), + ) + ///our neutral voicelines + var/static/list/neutral_voicelines = list( + REPAIRBOT_VOICED_BRICK = 'sound/voice/repairbot/brick.ogg', + REPAIRBOT_VOICED_ENTROPY = 'sound/voice/repairbot/entropy.ogg', + REPAIRBOT_VOICED_FIX_IT = 'sound/voice/repairbot/fixit.ogg', + REPAIRBOT_VOICED_FIX_TOUCH = 'sound/voice/repairbot/fixtouch.ogg', + REPAIRBOT_VOICED_HOLE = 'sound/voice/repairbot/patchingholes.ogg', + REPAIRBOT_VOICED_PAY = 'sound/voice/repairbot/pay.ogg', + ) + ///our emagged voicelines + var/static/list/emagged_voicelines = list( + REPAIRBOT_VOICED_ENTROPY = 'sound/voice/repairbot/entropy.ogg', + REPAIRBOT_VOICED_STRINGS = 'sound/voice/repairbot/strings.ogg', + REPAIRBOT_VOICED_PASSION = 'sound/voice/repairbot/passionproject.ogg', + ) + ///types we can retrieve from our ui + var/static/list/retrievable_types = list( + /obj/item/stack/sheet/iron, + /obj/item/stack/sheet/glass, + /obj/item/stack/tile, + ) + + ///our flags + var/repairbot_flags = REPAIRBOT_FIX_BREACHES | REPAIRBOT_FIX_GIRDERS | REPAIRBOT_REPLACE_WINDOWS | REPAIRBOT_REPLACE_TILES | REPAIRBOT_BUILD_GIRDERS + ///our color + var/toolbox_color = "#445eb3" + ///toolbox type we drop on death + var/toolbox = /obj/item/storage/toolbox + +/mob/living/basic/bot/repairbot/Initialize(mapload) + . = ..() + ai_controller.set_blackboard_key(BB_REPAIRBOT_EMAGGED_SPEECH, emagged_voicelines) + ai_controller.set_blackboard_key(BB_REPAIRBOT_NORMAL_SPEECH, neutral_voicelines) + var/static/list/abilities = list( + /datum/action/cooldown/mob_cooldown/bot/build_girder = BB_GIRDER_BUILD_ABILITY, + ) + grant_actions_by_list(abilities) + add_traits(list(TRAIT_SPACEWALK, TRAIT_NEGATES_GRAVITY, TRAIT_MOB_MERGE_STACKS, TRAIT_FIREDOOR_OPENER), INNATE_TRAIT) + our_welder = new(src) + our_welder.switched_on(src) + our_crowbar = new(src) + our_screwdriver = new(src) + our_rods = new(src, our_rods::max_amount) + set_color(toolbox_color) + START_PROCESSING(SSobj, src) + +/mob/living/basic/bot/repairbot/proc/set_color(new_color) + add_atom_colour(new_color, FIXED_COLOUR_PRIORITY) + toolbox_color = new_color + +/mob/living/basic/bot/repairbot/attackby(obj/item/potential_stack, mob/living/carbon/human/user, list/modifiers) + if(!istype(potential_stack, /obj/item/stack)) + return ..() + attempt_merge(potential_stack, user) + +/mob/living/basic/bot/repairbot/proc/attempt_merge(obj/item/stack/potential_stack, mob/living/user) + var/static/list/our_contents = list(/obj/item/stack/sheet/iron, /obj/item/stack/sheet/glass, /obj/item/stack/tile, /obj/item/stack/rods) + for(var/obj/item/stack/content as anything in our_contents) + if(!istype(potential_stack, content)) + continue + var/obj/item/stack/our_sheet = locate(content) in src + if(isnull(our_sheet)) + potential_stack.forceMove(src) + return + if(our_sheet.amount >= our_sheet.max_amount) + user?.balloon_alert(user, "full!") + return + if(!our_sheet.can_merge(potential_stack)) + return + var/atom/movable/to_move = potential_stack.split_stack(user, min(our_sheet.max_amount - our_sheet.amount, potential_stack.amount)) + to_move.forceMove(src) + return + +/mob/living/basic/bot/repairbot/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(istype(arrived, /obj/item/stack/sheet/iron) && isnull(our_iron)) //show iron tiles and glass in our hands + our_iron = arrived + update_appearance() + if(istype(arrived, /obj/item/stack/sheet/glass) && isnull(our_glass)) + our_glass = arrived + update_appearance() + if(istype(arrived, /obj/item/stack/tile) && isnull(our_tiles)) + our_tiles = arrived + if(istype(arrived, /obj/item/stack/rods) && isnull(our_rods)) + our_rods = arrived + +/mob/living/basic/bot/repairbot/UnarmedAttack(atom/target, proximity_flag, list/modifiers) + . = ..() + + if(!. || !proximity_flag) + return + + if(bot_access_flags & BOT_COVER_EMAGGED) + emagged_interactions(target, modifiers) + return + + if(istype(target, /obj/item/stack)) + attempt_merge(target, src) + return + + //priority interactions + if(istype(target, /turf/open/space)) + var/turf/open/space/space_target = target + if(!space_target.has_valid_support() && !(locate(/obj/structure/lattice) in space_target)) + our_rods?.melee_attack_chain(src, space_target) + + if(istype(target, /obj/structure/grille)) + var/obj/structure/grille/grille_target = target + if(grille_target.broken) + our_rods?.melee_attack_chain(src, grille_target) + + if(istype(target, /turf/open)) + var/turf/open/open_target = target + if(open_target.broken || open_target.burnt) + our_welder?.melee_attack_chain(src, open_target) + + if(istype(target, /obj/structure/window)) + var/obj/structure/window/target_window = target + if(!target_window.anchored) + our_screwdriver?.melee_attack_chain(src, target_window) + + //stack interactions + for(var/type in possible_stack_interactions) + var/obj/item/target_stack = locate(type) in src + if(isnull(target_stack)) + continue + if(!is_type_in_typecache(target, possible_stack_interactions[type])) + continue + target_stack.melee_attack_chain(src, target) + return + + //tool interactions + var/list/our_tools = list(our_welder, our_crowbar) + for(var/obj/item/tool in our_tools) + if(is_type_in_typecache(target, possible_tool_interactions[tool.type]) && !combat_mode) + tool.melee_attack_chain(src, target) + return + +/mob/living/basic/bot/repairbot/proc/emagged_interactions(atom/target, modifiers) + if(!istype(target, /mob/living/silicon/robot)) + deconstruction_device.interact_with_atom_secondary(target, src, modifiers) + return + if(HAS_TRAIT(target, TRAIT_MOB_TIPPED)) + return + var/old_combat_mode = combat_mode + set_combat_mode(TRUE) + target.attack_hand_secondary(src, modifiers) //tip the guy! + set_combat_mode(old_combat_mode) + +/mob/living/basic/bot/repairbot/start_pulling(atom/movable/movable_pulled, state, force, supress_message) + . = ..() + if(pulling) + setGrabState(GRAB_AGGRESSIVE) //automatically aggro grab everything! + +/mob/living/basic/bot/repairbot/Destroy() + . = ..() + QDEL_NULL(our_iron) + QDEL_NULL(our_glass) + QDEL_NULL(our_tiles) + QDEL_NULL(our_welder) + QDEL_NULL(our_screwdriver) + QDEL_NULL(our_crowbar) + QDEL_NULL(our_rods) + QDEL_NULL(deconstruction_device) + +/mob/living/basic/bot/repairbot/Exited(atom/movable/gone, direction) + if(gone == our_crowbar) + our_crowbar = null + if(gone == our_screwdriver) + our_screwdriver = null + if(gone == our_welder) + our_welder = null + if(gone == our_tiles) + our_tiles = null + if(gone == our_iron) + our_iron = null + if(gone == our_glass) + our_glass = null + if(gone == our_rods) + our_rods = null + update_appearance() + return ..() + +/mob/living/basic/bot/repairbot/process(seconds_per_tick) //generate 1 iron rod every 2 seconds + if(isnull(our_rods) || our_rods.amount < our_rods.max_amount) + new /obj/item/stack/rods(src) + +/mob/living/basic/bot/repairbot/turn_on() + . = ..() + if(!.) + return + START_PROCESSING(SSobj, src) + +/mob/living/basic/bot/repairbot/turn_off() + . = ..() + STOP_PROCESSING(SSobj, src) + +/mob/living/basic/bot/repairbot/update_overlays() + . = ..() + . += mutable_appearance(icon, "repairbot[bot_mode_flags & BOT_MODE_ON]", appearance_flags = RESET_COLOR) + if(our_glass) + var/mutable_appearance/glass = mutable_appearance(icon, "repairbot_glass_overlay", BELOW_MOB_LAYER - 0.02, appearance_flags = RESET_COLOR) + glass.pixel_x = -6 + glass.pixel_y = -5 + . += glass + if(our_iron) + var/mutable_appearance/iron = mutable_appearance(icon, "repairbot_iron_overlay", BELOW_MOB_LAYER - 0.02, appearance_flags = RESET_COLOR) + iron.pixel_y = -5 + iron.pixel_x = 7 + . += iron + +/mob/living/basic/bot/repairbot/update_icon_state() + . = ..() + icon_state = base_icon_state + +/mob/living/basic/bot/repairbot/generate_speak_list() + return neutral_voicelines + emagged_voicelines + +/mob/living/basic/bot/repairbot/Bumped(atom/movable/bumped_object) + . = ..() + if(istype(bumped_object, /obj/machinery/door/firedoor) && bumped_object.density) + our_crowbar.melee_attack_chain(src, bumped_object) + +/mob/living/basic/bot/repairbot/ui_data(mob/user) + var/list/data = ..() + data["repairbot_materials"] = list() + if((bot_access_flags & BOT_COVER_LOCKED) && !issilicon(user) && !isAdminGhostAI(user)) + return data + data["custom_controls"]["fix_breaches"] = repairbot_flags & REPAIRBOT_FIX_BREACHES + data["custom_controls"]["replace_windows"] = repairbot_flags & REPAIRBOT_REPLACE_WINDOWS + data["custom_controls"]["replace_tiles"] = repairbot_flags & REPAIRBOT_REPLACE_TILES + data["custom_controls"]["fix_girders"] = repairbot_flags & REPAIRBOT_FIX_GIRDERS + data["custom_controls"]["build_girders"] = repairbot_flags & REPAIRBOT_BUILD_GIRDERS + + for(var/data_path in retrievable_types) + var/atom/to_retrieve = locate(data_path) in src + if(isnull(to_retrieve)) + continue + + data["repairbot_materials"] += list(list( + "material_ref" = REF(to_retrieve), + "material_icon" = to_retrieve::icon, + "material_icon_state" = to_retrieve::icon_state, + )) + + return data + +/mob/living/basic/bot/repairbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(. || !isliving(ui.user) || (bot_access_flags & BOT_COVER_LOCKED) && !(HAS_SILICON_ACCESS(ui.user))) + return + switch(action) + if("fix_breaches") + repairbot_flags ^= REPAIRBOT_FIX_BREACHES + if("replace_windows") + repairbot_flags ^= REPAIRBOT_REPLACE_WINDOWS + if("replace_tiles") + repairbot_flags ^= REPAIRBOT_REPLACE_TILES + if("fix_girders") + repairbot_flags ^= REPAIRBOT_FIX_GIRDERS + if("build_girders") + repairbot_flags ^= REPAIRBOT_BUILD_GIRDERS + if("remove_item") + var/item_params = params["item_reference"] + if(isnull(item_params)) + return TRUE + var/obj/item/retrieved = locate(item_params) in contents + if(isnull(retrieved) || !is_type_in_list(retrieved, retrievable_types)) + return TRUE + var/mob/living/user = ui.user + user.put_in_hands(retrieved) + return TRUE + + +/mob/living/basic/bot/repairbot/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(!(bot_access_flags & BOT_COVER_EMAGGED) || !isnull(deconstruction_device)) + return + deconstruction_device = new(src) + +/obj/item/weldingtool/repairbot + max_fuel = INFINITY + starting_fuel = TRUE + change_icons = FALSE + +/obj/item/construction/rcd/repairbot + matter = INFINITY + has_ammobar = FALSE + +/mob/living/basic/bot/repairbot/mob_pickup(mob/living/user) + var/obj/item/carried_repairbot/carried = new(get_turf(src)) + carried.set_bot(src) + carried.add_atom_colour(toolbox_color, FIXED_COLOUR_PRIORITY) + user.visible_message(span_warning("[user] scoops up [src]!")) + user.put_in_hands(carried) + +/obj/item/carried_repairbot + desc = "A most robust bot!" + attack_verb_continuous = list("robusts") + attack_verb_simple = list("robust") + hitsound = 'sound/items/weapons/smash.ogg' + drop_sound = 'sound/items/handling/toolbox/toolbox_drop.ogg' + pickup_sound = 'sound/items/handling/toolbox/toolbox_pickup.ogg' + ///the bot we own + var/atom/movable/our_bot + +/obj/item/carried_repairbot/proc/set_bot(mob/living/basic/bot/repairbot/repairbot) + var/obj/item/bot_toolbox = repairbot.toolbox + icon = bot_toolbox::icon + icon_state = bot_toolbox::icon_state + lefthand_file = bot_toolbox::lefthand_file + righthand_file = bot_toolbox::righthand_file + inhand_icon_state = bot_toolbox::inhand_icon_state + force = bot_toolbox::force + repairbot.forceMove(src) + +/obj/item/carried_repairbot/dropped() + . = ..() + if(isturf(loc)) + release_bot() + +/obj/item/carried_repairbot/proc/release_bot(bypass_delete = FALSE) + if(!isnull(our_bot)) + our_bot.forceMove(drop_location()) + our_bot.balloon_alert_to_viewers("plops down") + if(!bypass_delete) + qdel(src) + +/obj/item/carried_repairbot/Destroy() + . = ..() + release_bot(bypass_delete = TRUE) + +/obj/item/carried_repairbot/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(isliving(arrived)) + our_bot = arrived + +/obj/item/carried_repairbot/Exited(atom/movable/gone, direction) + if(gone == our_bot) + our_bot = null + return ..() diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_abilities.dm b/code/modules/mob/living/basic/bots/repairbot/repairbot_abilities.dm new file mode 100644 index 00000000000..fa8d4970634 --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_abilities.dm @@ -0,0 +1,43 @@ +#define BUILDING_WALL_ABILITY "building wall ability" + +/datum/action/cooldown/mob_cooldown/bot/build_girder + name = "Build Girder" + desc = "Use iron rods to build a girder!" + cooldown_time = 3 SECONDS + click_to_activate = TRUE + +/datum/action/cooldown/mob_cooldown/bot/build_girder/IsAvailable(feedback) + . = ..() + if(!.) + return FALSE + var/obj/item/stack/rods/our_rods = locate() in owner + if(isnull(our_rods) || our_rods.amount < 2) + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/bot/build_girder/Activate(atom/target) + if(DOING_INTERACTION(owner, BUILDING_WALL_ABILITY)) + return TRUE + if(!isopenturf(target) || isgroundlessturf(target)) + owner.balloon_alert(owner, "cant build here!") + return TRUE + var/obj/item/stack/rods/our_rods = locate() in owner + var/turf/turf_target = target + if(turf_target.is_blocked_turf()) + owner.balloon_alert(owner, "blocked!") + return TRUE + var/obj/effect/constructing_effect/effect = new(turf_target, 3 SECONDS) + + if(!do_after(owner, 3 SECONDS, target = turf_target, interaction_key = BUILDING_WALL_ABILITY) || isnull(turf_target) || turf_target.is_blocked_turf()) + qdel(effect) + return TRUE + + playsound(turf_target, 'sound/machines/click.ogg', 50, TRUE) + new /obj/structure/girder(turf_target) + var/atom/stack_to_delete = our_rods.split_stack(owner, 2) + qdel(stack_to_delete) + StartCooldown() + qdel(effect) + return TRUE + +#undef BUILDING_WALL_ABILITY diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm b/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm new file mode 100644 index 00000000000..1793f0b65c2 --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm @@ -0,0 +1,320 @@ +#define REPAIRBOT_SPEECH_TIMER 30 SECONDS + +/datum/ai_controller/basic_controller/bot/repairbot + planning_subtrees = list( + /datum/ai_planning_subtree/repairbot_speech, + /datum/ai_planning_subtree/manage_unreachable_list, + /datum/ai_planning_subtree/mug_robot, + /datum/ai_planning_subtree/refill_materials, + /datum/ai_planning_subtree/repairbot_deconstruction, + /datum/ai_planning_subtree/respond_to_summon, + /datum/ai_planning_subtree/replace_floors/breaches, + /datum/ai_planning_subtree/wall_girder, + /datum/ai_planning_subtree/build_girder, + /datum/ai_planning_subtree/replace_window, + /datum/ai_planning_subtree/replace_floors, + /datum/ai_planning_subtree/fix_window, + /datum/ai_planning_subtree/salute_authority, + /datum/ai_planning_subtree/find_patrol_beacon, + ) + reset_keys = list( + BB_TILELESS_FLOOR, + BB_GIRDER_TARGET, + BB_GIRDER_TO_WALL_TARGET, + BB_BEACON_TARGET, + BB_PREVIOUS_BEACON_TARGET, + BB_WELDER_TARGET, + BB_WINDOW_FRAMETARGET, + ) + ai_traits = PAUSE_DURING_DO_AFTER + minimum_distance = 1 + +///subtree to refill our stacks +/datum/ai_planning_subtree/refill_materials + +/datum/ai_planning_subtree/refill_materials/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/static/list/refillable_items = typecacheof(list( + /obj/item/stack/sheet/iron, + /obj/item/stack/sheet/glass, + /obj/item/stack/tile, + )) + if(!controller.blackboard_key_exists(BB_REFILLABLE_TARGET)) + controller.queue_behavior(/datum/ai_behavior/bot_search/refillable_target, BB_REFILLABLE_TARGET, refillable_items) + return + controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_REFILLABLE_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_behavior/bot_search/refillable_target + action_cooldown = 10 SECONDS + +/datum/ai_behavior/bot_search/refillable_target/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + var/static/list/desired_types = list( + /obj/item/stack/sheet/iron, + /obj/item/stack/sheet/glass, + /obj/item/stack/tile, + ) + for(var/object_type in desired_types) + if(!istype(my_target, object_type)) + continue + var/obj/item/stack/sheet_type = locate(object_type) in controller.pawn + if(isnull(sheet_type)) + return TRUE //we dont have any of it! + if(sheet_type.amount < sheet_type.max_amount && sheet_type.can_merge(my_target)) + return TRUE + return FALSE + +/datum/ai_planning_subtree/mug_robot + +/datum/ai_planning_subtree/mug_robot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/living_bot = controller.pawn + if(!(living_bot.bot_access_flags & BOT_COVER_EMAGGED)) + return + var/static/list/robot_targets = typecacheof( + /mob/living/silicon/robot, + ) + if(!controller.blackboard_key_exists(BB_ROBOT_TARGET)) + controller.queue_behavior(/datum/ai_behavior/bot_search/valid_robot, BB_ROBOT_TARGET, robot_targets) + return + if(!living_bot.pulling) + controller.queue_behavior(/datum/ai_behavior/drag_target, BB_ROBOT_TARGET) + else + controller.queue_behavior(/datum/ai_behavior/bot_interact/tip_robot, BB_ROBOT_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_behavior/bot_search/valid_robot + +/datum/ai_behavior/bot_search/valid_robot/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + return (!HAS_TRAIT(my_target, TRAIT_MOB_TIPPED)) && can_see(controller.pawn, my_target) + +/datum/ai_behavior/bot_interact/tip_robot + +/datum/ai_behavior/bot_interact/tip_robot/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + if(succeeded) + var/mob/living/living_pawn = controller.pawn + living_pawn.stop_pulling() + +///subtree to deconstruct things when we're emagged +/datum/ai_planning_subtree/repairbot_deconstruction + +/datum/ai_planning_subtree/repairbot_deconstruction/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/living_bot = controller.pawn + if(!(living_bot.bot_access_flags & BOT_COVER_EMAGGED)) + return + var/static/list/things_to_deconstruct = typecacheof(list( + /obj/structure/window, + /turf/open/floor, + /turf/closed/wall, + )) + if(!controller.blackboard_key_exists(BB_DECONSTRUCT_TARGET)) + controller.queue_behavior(/datum/ai_behavior/bot_search/deconstructable, BB_DECONSTRUCT_TARGET, things_to_deconstruct) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_DECONSTRUCT_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_behavior/bot_search/deconstructable + +/datum/ai_behavior/bot_search/deconstructable/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + return (!(my_target.resistance_flags & INDESTRUCTIBLE) && !isgroundlessturf(my_target)) + +///subtree to control bot speech +/datum/ai_planning_subtree/repairbot_speech + +/datum/ai_planning_subtree/repairbot_speech/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + if(controller.blackboard[BB_REPAIRBOT_SPEECH_COOLDOWN] > world.time) + return + var/static/list/keys_to_look = list( + BB_WELDER_TARGET, + BB_WINDOW_FRAMETARGET, + BB_TILELESS_FLOOR, + BB_BREACHED_FLOOR, + BB_GIRDER_TO_WALL_TARGET, + BB_GIRDER_TARGET, + BB_DECONSTRUCT_TARGET, + ) + for(var/key in keys_to_look) + if(controller.blackboard_key_exists(key)) + controller.queue_behavior(/datum/ai_behavior/repairbot_speech, key) + return + +/datum/ai_behavior/repairbot_speech + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/repairbot_speech/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + var/list/speech_to_pick_from = (target_key == BB_DECONSTRUCT_TARGET) ? controller.blackboard[BB_REPAIRBOT_EMAGGED_SPEECH] : controller.blackboard[BB_REPAIRBOT_NORMAL_SPEECH] + if(!length(speech_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + announcement.announce(pick(speech_to_pick_from)) + controller.set_blackboard_key(BB_REPAIRBOT_SPEECH_COOLDOWN, world.time + REPAIRBOT_SPEECH_TIMER) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +///subtree to replace iron platings +/datum/ai_planning_subtree/replace_floors + ///flag we check before executing + var/required_flag = REPAIRBOT_REPLACE_TILES + ///key of our floor target + var/floor_key = BB_TILELESS_FLOOR + ///type of tile we need to replace floors + var/needed_tile_type = /obj/item/stack/tile + ///type of floors we can replace + var/list/type_of_turf = list(/turf/open/floor/plating) + ///our searching behavior + var/search_behavior = /datum/ai_behavior/bot_search/valid_plateless_turf + +/datum/ai_planning_subtree/replace_floors/New() + . = ..() + type_of_turf = typecacheof(type_of_turf) + +/datum/ai_planning_subtree/replace_floors/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & required_flag)) + return + if(!locate(needed_tile_type) in bot_pawn) + return + if(controller.blackboard_key_exists(floor_key)) + controller.queue_behavior(/datum/ai_behavior/bot_interact, floor_key) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.queue_behavior(search_behavior, floor_key, type_of_turf, 5, 10, FALSE, TRUE) + +/datum/ai_behavior/bot_search/valid_plateless_turf + +/datum/ai_behavior/bot_search/valid_plateless_turf/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/open/my_target) + var/static/list/blacklist_objects = typecacheof(list( + /obj/structure/window, + /obj/structure/grille, + )) + for(var/atom/possible_blacklisted as anything in my_target) + if(is_type_in_typecache(possible_blacklisted, blacklist_objects)) + return FALSE + if(istype(my_target, /turf/open/floor/plating) && !can_see(controller.pawn, my_target, 5)) + return FALSE + return !istype(get_area(my_target), /area/space) + +///subtree to fix hull breaches +/datum/ai_planning_subtree/replace_floors/breaches + floor_key = BB_BREACHED_FLOOR + needed_tile_type = /obj/item/stack/tile/iron + type_of_turf = list(/turf/open/space) + required_flag = REPAIRBOT_FIX_BREACHES + search_behavior = /datum/ai_behavior/bot_search/valid_plateless_turf/breached + +///exists as to not conflict with the base turf searching behavior cause of how the queue system works... +/datum/ai_behavior/bot_search/valid_plateless_turf/breached + +///subtree to build girders +/datum/ai_planning_subtree/build_girder + +/datum/ai_planning_subtree/build_girder/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_BUILD_GIRDERS)) + return + var/obj/item/stack/rods/my_rods = locate() in bot_pawn + if(isnull(my_rods) || my_rods.amount < 2) + return + var/datum/action/cooldown/ability = controller.blackboard[BB_GIRDER_BUILD_ABILITY] + if(!ability?.IsAvailable()) + return + if(controller.blackboard_key_exists(BB_GIRDER_TARGET)) + controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/build_girder, BB_GIRDER_BUILD_ABILITY, BB_GIRDER_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + + var/static/list/searchable_turfs = typecacheof(list(/turf/open)) + controller.queue_behavior(/datum/ai_behavior/bot_search/valid_wall_target, BB_GIRDER_TARGET, searchable_turfs, 5, 10, FALSE, TRUE) + +/datum/ai_behavior/bot_search/valid_wall_target + action_cooldown = 5 SECONDS + +/datum/ai_behavior/bot_search/valid_wall_target/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/my_target) + if(istype(get_area(my_target), /area/space) || isgroundlessturf(my_target) || my_target.is_blocked_turf()) + return FALSE + var/static/list/blacklist_objects = list( + /obj/machinery/door, + /obj/structure/grille, + ) + + for(var/atom/contents in my_target) + if(is_type_in_typecache(contents, blacklist_objects)) + return FALSE + + var/turf/adjacent_turfs = get_adjacent_open_turfs(my_target) + for(var/turf/possible_spaced_turf as anything in adjacent_turfs) + if(isspaceturf(possible_spaced_turf) && istype(get_area(possible_spaced_turf), /area/space)) + return TRUE + return FALSE + +/datum/ai_behavior/targeted_mob_ability/build_girder + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/targeted_mob_ability/build_girder/setup(datum/ai_controller/controller, ability_key, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/targeted_mob_ability/build_girder/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + controller.clear_blackboard_key(target_key) + if(!succeeded && !isnull(target)) + controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE) + + +///subtree to place glass on windows +/datum/ai_planning_subtree/replace_window + +/datum/ai_planning_subtree/replace_window/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/basic/bot/repairbot/living_pawn = controller.pawn + if(!(living_pawn.repairbot_flags & REPAIRBOT_REPLACE_WINDOWS)) + return + if(!locate(/obj/item/stack/sheet/glass) in living_pawn) + return + if(controller.blackboard_key_exists(BB_WINDOW_FRAMETARGET)) + controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_WINDOW_FRAMETARGET) + return SUBTREE_RETURN_FINISH_PLANNING + var/static/list/searchable_grilles = typecacheof(list(/obj/structure/grille)) + controller.queue_behavior(/datum/ai_behavior/bot_search/valid_grille_target, BB_WINDOW_FRAMETARGET, searchable_grilles) + +/datum/ai_behavior/bot_search/valid_grille_target/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/structure/my_target) + if(locate(/obj/structure/window) in get_turf(my_target)) + return FALSE + return (!istype(get_area(my_target), /area/space)) + + +///subtree to place iron on girders +/datum/ai_planning_subtree/wall_girder + +/datum/ai_planning_subtree/wall_girder/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/basic/bot/repairbot/living_pawn = controller.pawn + if(!(living_pawn.repairbot_flags & REPAIRBOT_FIX_GIRDERS)) + return + var/obj/item/stack/sheet/iron/my_iron = locate() in living_pawn + if(isnull(my_iron) || my_iron.amount < 2) + return + if(controller.blackboard_key_exists(BB_GIRDER_TO_WALL_TARGET)) + controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_GIRDER_TO_WALL_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + var/static/list/searchable_girder = typecacheof(list(/obj/structure/girder)) + controller.queue_behavior(/datum/ai_behavior/bot_search/valid_girder, BB_GIRDER_TO_WALL_TARGET, searchable_girder) + +/datum/ai_behavior/bot_search/valid_girder/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) + return isfloorturf(my_target.loc) + +///subtree to repair machines with welders +/datum/ai_planning_subtree/fix_window + +/datum/ai_planning_subtree/fix_window/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_WELDER_TARGET)) + controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_WELDER_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + var/static/list/searchable_objects = typecacheof(list(/obj/structure/window)) + controller.queue_behavior(/datum/ai_behavior/bot_search/valid_window_fix, BB_WELDER_TARGET, searchable_objects) + +/datum/ai_behavior/bot_search/valid_window_fix + +/datum/ai_behavior/bot_search/valid_window_fix/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) + return (my_target.get_integrity() < my_target.max_integrity || !my_target.anchored) + +#undef REPAIRBOT_SPEECH_TIMER diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index c0d2e1d4785..dca88762970 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -388,7 +388,7 @@ if(pulling) // Are we trying to pull something we are already pulling? Then just stop here, no need to continue. if(AM == pulling) - return + return FALSE stop_pulling() changeNext_move(CLICK_CD_GRABBING) @@ -456,6 +456,7 @@ update_pull_movespeed() set_pull_offsets(M, state) + return TRUE /mob/living/proc/set_pull_offsets(mob/living/M, grab_state = GRAB_PASSIVE) if(M.buckled) diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index 80e2b8c0c83..6a606209144 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -190,67 +190,63 @@ to_chat(user, span_notice("You complete the ED-209.")) qdel(src) -//Floorbot assemblies -/obj/item/bot_assembly/floorbot +//Repairbot assemblies +/obj/item/bot_assembly/repairbot desc = "It's a toolbox with tiles sticking out the top." - name = "tiles and toolbox" - icon_state = "toolbox_tiles" + name = "Repairbot Chasis" + icon_state = "repairbot_base" throwforce = 10 - created_name = "Floorbot" + created_name = "Repairbot" var/toolbox = /obj/item/storage/toolbox/mechanical var/toolbox_color = "" //Blank for blue, r for red, y for yellow, etc. -/obj/item/bot_assembly/floorbot/Initialize(mapload) +/obj/item/bot_assembly/repairbot/Initialize(mapload) . = ..() update_appearance() -/obj/item/bot_assembly/floorbot/update_name() - . = ..() - switch(build_step) - if(ASSEMBLY_SECOND_STEP) - name = "incomplete floorbot assembly" - else - name = initial(name) +/obj/item/bot_assembly/repairbot/proc/set_color(new_color) + add_atom_colour(new_color, FIXED_COLOUR_PRIORITY) + toolbox_color = new_color -/obj/item/bot_assembly/floorbot/update_desc() +/obj/item/bot_assembly/repairbot/update_desc() . = ..() switch(build_step) - if(ASSEMBLY_SECOND_STEP) - desc = "It's a toolbox with tiles sticking out the top and a sensor attached." + if(ASSEMBLY_FIRST_STEP) + desc = "It's a toolbox with a giant monitor sticking out!." else desc = initial(desc) -/obj/item/bot_assembly/floorbot/update_icon_state() +/obj/item/bot_assembly/repairbot/update_overlays() . = ..() - switch(build_step) - if(ASSEMBLY_FIRST_STEP) - icon_state = "[toolbox_color]toolbox_tiles" - if(ASSEMBLY_SECOND_STEP) - icon_state = "[toolbox_color]toolbox_tiles_sensor" + if(build_step >= ASSEMBLY_FIRST_STEP) + . += mutable_appearance(icon, "repairbot_base_sensor", appearance_flags = RESET_COLOR) + if(build_step >= ASSEMBLY_SECOND_STEP) + . += mutable_appearance(icon, "repairbot_base_arms", appearance_flags = RESET_COLOR) -/obj/item/bot_assembly/floorbot/attackby(obj/item/W, mob/user, params) +/obj/item/bot_assembly/repairbot/attackby(obj/item/item, mob/user, params) ..() switch(build_step) if(ASSEMBLY_FIRST_STEP) - if(isprox(W)) - if(!user.temporarilyRemoveItemFromInventory(W)) - return - to_chat(user, span_notice("You add [W] to [src].")) - qdel(W) - build_step++ - update_appearance() - + if(!istype(item, /obj/item/bodypart/arm/left/robot) && !istype(item, /obj/item/bodypart/arm/right/robot)) + return + if(!can_finish_build(item, user)) + return + build_step++ + to_chat(user, span_notice("You add [item] to [src]. Boop beep!")) + qdel(item) + update_appearance() if(ASSEMBLY_SECOND_STEP) - 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/floorbot/A = new(drop_location(), toolbox_color) - A.name = created_name - A.robot_arm = W.type - A.toolbox = toolbox - to_chat(user, span_notice("You add [W] to [src]. Boop beep!")) - qdel(W) - qdel(src) + if(!istype(item, /obj/item/stack/conveyor)) + return + if(!can_finish_build(item, user)) + return + var/mob/living/basic/bot/repairbot/repair = new(drop_location()) + repair.name = created_name + repair.toolbox = toolbox + repair.set_color(toolbox_color) + to_chat(user, span_notice("You add [item] to [src]. Boop beep!")) + qdel(item) + qdel(src) //Medbot Assembly diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm deleted file mode 100644 index ae17e58686c..00000000000 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ /dev/null @@ -1,448 +0,0 @@ -#define HULL_BREACH 1 -#define LINE_SPACE_MODE 2 -#define FIX_TILE 3 -#define AUTO_TILE 4 -#define PLACE_TILE 5 -#define REPLACE_TILE 6 -#define TILE_EMAG 7 - -//Floorbot -/mob/living/simple_animal/bot/floorbot - name = "\improper Floorbot" - desc = "A little floor repairing robot, he looks so excited!" - icon = 'icons/mob/silicon/aibots.dmi' - icon_state = "floorbot0" - density = FALSE - health = 25 - maxHealth = 25 - - req_one_access = list(ACCESS_ROBOTICS, ACCESS_CONSTRUCTION) - radio_key = /obj/item/encryptionkey/headset_eng - radio_channel = RADIO_CHANNEL_ENGINEERING - bot_type = FLOOR_BOT - hackables = "floor construction protocols" - path_image_color = "#FFA500" - possessed_message = "You are a floorbot! Repair the hull to the best of your ability!" - - var/process_type //Determines what to do when process_scan() receives a target. See process_scan() for details. - var/targetdirection - var/replacetiles = FALSE - var/placetiles = FALSE - var/maxtiles = 100 - var/obj/item/stack/tile/tilestack - var/fixfloors = TRUE - var/autotile = FALSE - var/turf/target - var/toolbox = /obj/item/storage/toolbox/mechanical - var/toolbox_color = "" - -/mob/living/simple_animal/bot/floorbot/Initialize(mapload, new_toolbox_color) - . = ..() - ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) - toolbox_color = new_toolbox_color - update_appearance(UPDATE_ICON) - - // Doing this hurts my soul, but simplebot access reworks are for another day. - var/datum/id_trim/job/engi_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/station_engineer] - access_card.add_access(engi_trim.access + engi_trim.wildcard_access) - prev_access = access_card.access.Copy() - - if(toolbox_color == "s") - health = 100 - maxHealth = 100 - -/mob/living/simple_animal/bot/floorbot/Exited(atom/movable/gone, direction) - if(tilestack == gone) - if(tilestack && tilestack.max_amount < tilestack.amount) //split the stack if it exceeds its normal max_amount - var/iterations = round(tilestack.amount/tilestack.max_amount) //round() without second arg floors the value - for(var/a in 1 to iterations) - if(a == iterations) - tilestack.split_stack(null, tilestack.amount - tilestack.max_amount) - else - tilestack.split_stack(null, tilestack.max_amount) - tilestack = null - -/mob/living/simple_animal/bot/floorbot/turn_on() - . = ..() - update_appearance() - -/mob/living/simple_animal/bot/floorbot/turn_off() - ..() - update_appearance() - -/mob/living/simple_animal/bot/floorbot/bot_reset() - ..() - target = null - toggle_magnet(FALSE) - -/mob/living/simple_animal/bot/floorbot/attackby(obj/item/W , mob/user, params) - if(istype(W, /obj/item/stack/tile/iron)) - to_chat(user, span_notice("The floorbot can produce normal tiles itself.")) - return - if(istype(W, /obj/item/stack/tile)) - var/old_amount = tilestack ? tilestack.amount : 0 - var/obj/item/stack/tile/tiles = W - if(tilestack) - if(!tiles.can_merge(tilestack)) - to_chat(user, span_warning("Different custom tiles are already inside the floorbot.")) - return - if(tilestack.amount >= maxtiles) - to_chat(user, span_warning("The floorbot can't hold any more custom tiles.")) - return - tiles.merge(tilestack, maxtiles) - else - if(tiles.amount > maxtiles) - tilestack = tilestack.split_stack(null, maxtiles) - else - tilestack = W - tilestack.forceMove(src) - to_chat(user, span_notice("You load [tilestack.amount - old_amount] tiles into the floorbot. It now contains [tilestack.amount] tiles.")) - return - else - ..() - -/mob/living/simple_animal/bot/floorbot/emag_act(mob/user, obj/item/card/emag/emag_card) - . = ..() - if(!(bot_cover_flags & BOT_COVER_EMAGGED)) - return - balloon_alert(user, "safeties disabled") - audible_message(span_danger("[src] buzzes oddly!")) - return TRUE - -///mobs should use move_resist instead of anchored. -/mob/living/simple_animal/bot/floorbot/proc/toggle_magnet(engage = TRUE, change_icon = TRUE) - if(engage) - ADD_TRAIT(src, TRAIT_IMMOBILIZED, BUSY_FLOORBOT_TRAIT) - move_resist = INFINITY - if(change_icon) - icon_state = "[toolbox_color]floorbot-c" - else - REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, BUSY_FLOORBOT_TRAIT) - move_resist = initial(move_resist) - if(change_icon) - update_icon() - -// Variables sent to TGUI -/mob/living/simple_animal/bot/floorbot/ui_data(mob/user) - var/list/data = ..() - if(!(bot_cover_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) - data["custom_controls"]["tile_hull"] = autotile - data["custom_controls"]["place_tiles"] = placetiles - data["custom_controls"]["place_custom"] = replacetiles - data["custom_controls"]["repair_damage"] = fixfloors - data["custom_controls"]["traction_magnets"] = !!HAS_TRAIT_FROM(src, TRAIT_IMMOBILIZED, BUSY_FLOORBOT_TRAIT) - data["custom_controls"]["tile_stack"] = 0 - data["custom_controls"]["line_mode"] = FALSE - if(tilestack) - data["custom_controls"]["tile_stack"] = tilestack.amount - if(targetdirection) - data["custom_controls"]["line_mode"] = dir2text(targetdirection) - return data - -// Actions received from TGUI -/mob/living/simple_animal/bot/floorbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - var/mob/user = ui.user - if(. || (bot_cover_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(user))) - return - - switch(action) - if("place_custom") - replacetiles = !replacetiles - if("place_tiles") - placetiles = !placetiles - if("repair_damage") - fixfloors = !fixfloors - if("tile_hull") - autotile = !autotile - if("traction_magnets") - toggle_magnet(!HAS_TRAIT_FROM(src, TRAIT_IMMOBILIZED, BUSY_FLOORBOT_TRAIT), FALSE) - if("eject_tiles") - if(tilestack) - tilestack.forceMove(drop_location()) - if("line_mode") - var/setdir = tgui_input_list(user, "Select construction direction", "Direction", list("north", "east", "south", "west", "disable")) - if(isnull(setdir) || QDELETED(ui) || ui.status != UI_INTERACTIVE) - return - switch(setdir) - if("north") - targetdirection = 1 - if("south") - targetdirection = 2 - if("east") - targetdirection = 4 - if("west") - targetdirection = 8 - if("disable") - targetdirection = null - -/mob/living/simple_animal/bot/floorbot/handle_automated_action() - if(!..()) - return - - if(mode == BOT_REPAIRING) - return - - if(prob(5)) - audible_message("[src] makes an excited booping beeping sound!") - - var/list/tiles_scanned = list() - //Normal scanning procedure. We have tiles loaded, are not emagged. - if(!target && !(bot_cover_flags & BOT_COVER_EMAGGED)) - if(targetdirection != null) //The bot is in line mode. - var/turf/T = get_step(src, targetdirection) - if(isspaceturf(T)) //Check for space - target = T - process_type = LINE_SPACE_MODE - if(isfloorturf(T)) //Check for floor - target = T - if(!target) - process_type = HULL_BREACH //Ensures the floorbot does not try to "fix" space areas or shuttle docking zones. - - tiles_scanned += list(/turf/open/space) - - if(!target && placetiles) //Finds a floor without a tile and gives it one. - process_type = PLACE_TILE //The target must be the floor and not a tile. The floor must not already have a floortile. - tiles_scanned += list(/turf/open/floor) - - if(!target && fixfloors) //Repairs damaged floors and tiles. - process_type = FIX_TILE - tiles_scanned += list(/turf/open/floor) - - if(!target && replacetiles && tilestack) //Replace a floor tile with custom tile - process_type = REPLACE_TILE //The target must be a tile. The floor must already have a floortile. - tiles_scanned += list(/turf/open/floor) - - if(!target && bot_cover_flags & BOT_COVER_EMAGGED) //We are emagged! Time to rip up the floors! - process_type = TILE_EMAG - tiles_scanned += list(/turf/open/floor) - - target = scan(tiles_scanned) - - if (!target) - if(bot_mode_flags & BOT_MODE_AUTOPATROL) - switch(mode) - if(BOT_IDLE, BOT_START_PATROL) - start_patrol() - if(BOT_PATROL) - bot_patrol() - return - - if(loc == target || loc == get_turf(target)) - if(check_bot(target)) //Target is not defined at the parent - if(prob(50)) //50% chance to still try to repair so we dont end up with 2 floorbots failing to fix the last breach - target = null - path = list() - return - if(isturf(target) && !(bot_cover_flags & BOT_COVER_EMAGGED)) - repair(target) - else if(bot_cover_flags & BOT_COVER_EMAGGED && isfloorturf(target)) - var/turf/open/floor/floor = target - toggle_magnet() - mode = BOT_REPAIRING - if(isplatingturf(floor)) - floor.attempt_lattice_replacement() - else - floor.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) - audible_message(span_danger("[src] makes an excited booping sound.")) - addtimer(CALLBACK(src, PROC_REF(go_idle)), 0.5 SECONDS) - path = list() - return - - if(!length(path)) - if(!isturf(target)) - var/turf/TL = get_turf(target) - path = get_path_to(src, TL, max_distance=30, access=access_card.GetAccess(), simulated_only = FALSE) - else - path = get_path_to(src, target, max_distance=30, access=access_card.GetAccess(), simulated_only = FALSE) - - if(!bot_move(target)) - add_to_ignore(target) - target = null - mode = BOT_IDLE - else if(!bot_move(target)) - target = null - mode = BOT_IDLE - -/mob/living/simple_animal/bot/floorbot/proc/go_idle() - if (QDELETED(src)) - return - toggle_magnet(FALSE) - mode = BOT_IDLE - target = null - -/mob/living/simple_animal/bot/floorbot/proc/is_hull_breach(turf/t) //Ignore space tiles not considered part of a structure, also ignores shuttle docking areas. - var/area/t_area = get_area(t) - if(t_area && (t_area.name == "Space" || findtext(t_area.name, "huttle"))) - return FALSE - else - return TRUE - -//Floorbots, having several functions, need sort out special conditions here. -/mob/living/simple_animal/bot/floorbot/process_scan(scan_target) - var/result - var/turf/open/floor/floor - move_resist = initial(move_resist) - switch(process_type) - if(HULL_BREACH) //The most common job, patching breaches in the station's hull. - if(is_hull_breach(scan_target)) //Ensure that the targeted space turf is actually part of the station, and not random space. - result = scan_target - move_resist = INFINITY //Prevent the floorbot being blown off-course while trying to reach a hull breach. - if(LINE_SPACE_MODE) //Space turfs in our chosen direction are considered. - if(get_dir(src, scan_target) == targetdirection) - result = scan_target - move_resist = INFINITY - if(PLACE_TILE) - floor = scan_target - if(isplatingturf(floor)) //The floor must not already have a tile. - result = floor - if(REPLACE_TILE) - floor = scan_target - if(isfloorturf(floor) && !isplatingturf(floor)) //The floor must already have a tile. - result = floor - if(FIX_TILE) //Selects only damaged floors. - floor = scan_target - if(istype(floor) && (floor.broken || floor.burnt)) - result = floor - if(TILE_EMAG) //Emag mode! Rip up the floor and cause breaches to space! - floor = scan_target - if(!isplatingturf(floor)) - result = floor - else //If no special processing is needed, simply return the result. - result = scan_target - return result - -/mob/living/simple_animal/bot/floorbot/proc/repair(turf/target_turf) - if(check_bot_working(target_turf)) - add_to_ignore(target_turf) - target = null - playsound(src, 'sound/mobs/non-humanoids/floorbot/whistlereset.ogg', 50, TRUE) - return - if(isspaceturf(target_turf)) - //Must be a hull breach or in line mode to continue. - if(!is_hull_breach(target_turf) && !targetdirection) - target = null - return - else if(!isfloorturf(target_turf)) - return - if(isspaceturf(target_turf)) //If we are fixing an area not part of pure space, it is - toggle_magnet() - visible_message(span_notice("[targetdirection ? "[src] begins installing a bridge plating." : "[src] begins to repair the hole."] ")) - mode = BOT_REPAIRING - if(!do_after(src, 5 SECONDS, target = target_turf) && mode == BOT_REPAIRING) - go_idle() - return - - if(!autotile) - target_turf.place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - go_idle() - return - - if(replacetiles && tilestack) - target_turf.place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) //make sure a hull is actually below the floor tile - tilestack.place_tile(target_turf, src) - if(!tilestack) - speak("Requesting refill of custom floor tiles to continue replacing.") - else - target_turf.place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) //make sure a hull is actually below the floor tile - target_turf.place_on_top(/turf/open/floor/iron, flags = CHANGETURF_INHERIT_AIR) - go_idle() - return - - var/turf/open/floor/floor = target_turf - var/was_replacing = replacetiles - - if(floor.broken || floor.burnt || isplatingturf(floor)) - toggle_magnet() - mode = BOT_REPAIRING - visible_message(span_notice("[src] begins [(floor.broken || floor.burnt) ? "repairing the floor" : "placing a floor tile"].")) - if(!do_after(src, 5 SECONDS, target = floor) && mode == BOT_REPAIRING) - go_idle() - return - else if(replacetiles && tilestack && floor.type != tilestack.turf_type) - toggle_magnet() - mode = BOT_REPAIRING - visible_message(span_notice("[src] begins replacing the floor tiles.")) - if(do_after(src, 5 SECONDS, target = target_turf) && mode == BOT_REPAIRING && tilestack) - go_idle() - return - - var/area/is_this_maints = get_area(floor) - if(was_replacing && tilestack) //turn the tile into plating (if needed), then replace it - floor = floor.make_plating(TRUE) || floor - tilestack.place_tile(floor, src) - if(!tilestack) - speak("Requesting refill of custom floor tiles to continue replacing.") - go_idle() - return - - if(floor.broken || floor.burnt) //repair the tile and reset it to be undamaged (rather than replacing it) - floor.broken = FALSE - floor.burnt = FALSE - floor.update_appearance() - go_idle() - return - - if(istype(is_this_maints, /area/station/maintenance)) //place catwalk if it's plating and we're in maints - floor.place_on_top(/turf/open/floor/catwalk_floor, flags = CHANGETURF_INHERIT_AIR) - go_idle() - return - - //place normal tile if it's plating anywhere else - floor = floor.make_plating(TRUE) || floor - floor.place_on_top(/turf/open/floor/iron, flags = CHANGETURF_INHERIT_AIR) - go_idle() - -/mob/living/simple_animal/bot/floorbot/update_icon_state() - . = ..() - icon_state = "[toolbox_color]floorbot[get_bot_flag(bot_mode_flags, BOT_MODE_ON)]" - -/mob/living/simple_animal/bot/floorbot/explode() - target = null - var/atom/Tsec = drop_location() - - drop_part(toolbox, Tsec) - - new /obj/item/assembly/prox_sensor(Tsec) - - if(tilestack) - tilestack.forceMove(drop_location()) - - new /obj/item/stack/tile/iron/base(Tsec, 1) - return ..() - -/mob/living/simple_animal/bot/floorbot/UnarmedAttack(atom/target, proximity_flag, list/modifiers) - if(!can_unarmed_attack()) - return - - if (!isturf(target)) - return ..() - - if(!(bot_cover_flags & BOT_COVER_EMAGGED) || !isfloorturf(target)) - repair(target) - return - - var/turf/open/floor/floor = target - if(isplatingturf(floor)) - floor.attempt_lattice_replacement() - else - floor.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) - audible_message(span_danger("[src] makes an excited booping sound.")) - -/** - * Checks a given turf to see if another floorbot is there, working as well. - */ -/mob/living/simple_animal/bot/floorbot/proc/check_bot_working(turf/active_turf) - if(isturf(active_turf)) - for(var/mob/living/simple_animal/bot/floorbot/robot in active_turf) - if(robot.mode == BOT_REPAIRING) - return TRUE - return FALSE - -#undef HULL_BREACH -#undef LINE_SPACE_MODE -#undef FIX_TILE -#undef AUTO_TILE -#undef PLACE_TILE -#undef REPLACE_TILE -#undef TILE_EMAG diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 2f4f7a4b70a..55161807eb4 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -6,7 +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/floorbot, /mob/living/simple_animal/bot/mulebot, /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/bot/secbot, diff --git a/icons/mob/silicon/aibots.dmi b/icons/mob/silicon/aibots.dmi index 86c450ef1ce..819eecea0b7 100644 Binary files a/icons/mob/silicon/aibots.dmi and b/icons/mob/silicon/aibots.dmi differ diff --git a/sound/voice/repairbot/brick.ogg b/sound/voice/repairbot/brick.ogg new file mode 100644 index 00000000000..78dae8633a3 Binary files /dev/null and b/sound/voice/repairbot/brick.ogg differ diff --git a/sound/voice/repairbot/cantanymore.ogg b/sound/voice/repairbot/cantanymore.ogg new file mode 100644 index 00000000000..4fc4fb216f7 Binary files /dev/null and b/sound/voice/repairbot/cantanymore.ogg differ diff --git a/sound/voice/repairbot/entropy.ogg b/sound/voice/repairbot/entropy.ogg new file mode 100644 index 00000000000..ab6a4f6a067 Binary files /dev/null and b/sound/voice/repairbot/entropy.ogg differ diff --git a/sound/voice/repairbot/fixit.ogg b/sound/voice/repairbot/fixit.ogg new file mode 100644 index 00000000000..20e095589cb Binary files /dev/null and b/sound/voice/repairbot/fixit.ogg differ diff --git a/sound/voice/repairbot/fixtouch.ogg b/sound/voice/repairbot/fixtouch.ogg new file mode 100644 index 00000000000..86ca24ffe3f Binary files /dev/null and b/sound/voice/repairbot/fixtouch.ogg differ diff --git a/sound/voice/repairbot/passionproject.ogg b/sound/voice/repairbot/passionproject.ogg new file mode 100644 index 00000000000..6f1fba1d452 Binary files /dev/null and b/sound/voice/repairbot/passionproject.ogg differ diff --git a/sound/voice/repairbot/patchingholes.ogg b/sound/voice/repairbot/patchingholes.ogg new file mode 100644 index 00000000000..6c65fc22b96 Binary files /dev/null and b/sound/voice/repairbot/patchingholes.ogg differ diff --git a/sound/voice/repairbot/pay.ogg b/sound/voice/repairbot/pay.ogg new file mode 100644 index 00000000000..0aa2b68868c Binary files /dev/null and b/sound/voice/repairbot/pay.ogg differ diff --git a/sound/voice/repairbot/strings.ogg b/sound/voice/repairbot/strings.ogg new file mode 100644 index 00000000000..bc897eacccd Binary files /dev/null and b/sound/voice/repairbot/strings.ogg differ diff --git a/tgstation.dme b/tgstation.dme index b95bbfa13ad..229dc47983c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4851,6 +4851,9 @@ #include "code\modules\mob\living\basic\bots\hygienebot\hygienebot_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\bots\repairbot\repairbot.dm" +#include "code\modules\mob\living\basic\bots\repairbot\repairbot_abilities.dm" +#include "code\modules\mob\living\basic\bots\repairbot\repairbot_ai.dm" #include "code\modules\mob\living\basic\bots\vibebot\vibebot.dm" #include "code\modules\mob\living\basic\bots\vibebot\vibebot_abilities.dm" #include "code\modules\mob\living\basic\bots\vibebot\vibebot_ai.dm" @@ -5305,7 +5308,6 @@ #include "code\modules\mob\living\simple_animal\bot\bot_announcement.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\floorbot.dm" #include "code\modules\mob\living\simple_animal\bot\mulebot.dm" #include "code\modules\mob\living\simple_animal\bot\secbot.dm" #include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm" diff --git a/tgui/packages/tgui/interfaces/RepairBot.tsx b/tgui/packages/tgui/interfaces/RepairBot.tsx new file mode 100644 index 00000000000..58bc26bec41 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RepairBot.tsx @@ -0,0 +1,101 @@ +import { BooleanLike } from 'common/react'; +import React from 'react'; +import { useBackend } from 'tgui/backend'; +import { + Button, + DmIcon, + Flex, + NoticeBox, + Section, + Stack, +} from 'tgui/components'; +import { BotControl, BotSettings } from 'tgui/interfaces/SimpleBot'; +import { Window } from 'tgui/layouts'; + +type Data = { + can_hack: BooleanLike; + custom_controls: Record; + emagged: BooleanLike; + has_access: BooleanLike; + locked: BooleanLike; + settings: Settings; + repairbot_materials: RepairbotMaterials[]; +}; + +type RepairbotMaterials = { + material_ref: string; + material_name: string; + material_icon: string; + material_icon_state: string; +}; + +type Settings = { + airplane_mode: BooleanLike; + allow_possession: BooleanLike; + has_personality: BooleanLike; + maintenance_lock: BooleanLike; + pai_inserted: boolean; + patrol_station: BooleanLike; + possession_enabled: BooleanLike; + power: BooleanLike; +}; + +export function RepairBot(props) { + const { data } = useBackend(); + const { can_hack, locked } = data; + const access = !locked || !!can_hack; + + return ( + + + + + + + {!!access && ( + <> + + + + + + + + )} + + + + ); +} + +function RepairBotMats(props) { + const { act, data } = useBackend(); + const { repairbot_materials } = data; + + return ( +
+ {repairbot_materials.length === 0 && No Materials!} + + {repairbot_materials.map((mat) => ( + + + + ))} + +
+ ); +} diff --git a/tgui/packages/tgui/interfaces/SimpleBot.tsx b/tgui/packages/tgui/interfaces/SimpleBot.tsx index 8ac2b4201a7..2085dcd4645 100644 --- a/tgui/packages/tgui/interfaces/SimpleBot.tsx +++ b/tgui/packages/tgui/interfaces/SimpleBot.tsx @@ -35,7 +35,7 @@ type Settings = { export function SimpleBot(props) { const { data } = useBackend(); - const { can_hack, custom_controls, locked } = data; + const { can_hack, locked } = data; const access = !locked || !!can_hack; return ( @@ -43,25 +43,11 @@ export function SimpleBot(props) { -
}> - {!access ? Locked! : } -
+
{!!access && ( -
- - {Object.entries(custom_controls).map((control) => ( - - - - ))} - -
+
)}
@@ -70,6 +56,36 @@ export function SimpleBot(props) { ); } +export function BotSettings(props) { + const { act, data } = useBackend(); + const { can_hack, locked } = data; + const access = !locked || !!can_hack; + return ( +
}> + {!access ? Locked! : } +
+ ); +} + +export function BotControl(props) { + const { act, data } = useBackend(); + const { custom_controls } = data; + return ( +
+ + {Object.entries(custom_controls).map((control) => ( + + + + ))} + +
+ ); +} /** Creates a lock button at the top of the controls */ function TabDisplay(props) { const { act, data } = useBackend();