diff --git a/_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm b/_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm index d8ca28b7b00..2b43ae43fb3 100644 --- a/_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm +++ b/_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm @@ -21404,7 +21404,7 @@ codes_txt = "delivery;dir=4"; location = "Cargo Storage4" }, -/mob/living/simple_animal/bot/mulebot{ +/mob/living/basic/bot/mulebot{ name = "Old Yeller" }, /turf/open/floor/iron/recharge_floor, @@ -33958,7 +33958,7 @@ codes_txt = "delivery;dir=4"; location = "Cargo Storage-2" }, -/mob/living/simple_animal/bot/mulebot{ +/mob/living/basic/bot/mulebot{ name = "Parcel Pete" }, /obj/item/radio/intercom/directional/west, diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 1cb51bb30c0..6b9115f4b99 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -22766,7 +22766,7 @@ name = "warehouse shutters control" }, /obj/structure/window/reinforced/spawner/directional/north, -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/machinery/navbeacon{ codes_txt = "delivery;dir=4"; location = "QM #1" @@ -48594,7 +48594,7 @@ /turf/open/floor/iron, /area/station/maintenance/port/aft) "lUm" = ( -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/machinery/navbeacon{ codes_txt = "delivery;dir=4"; location = "QM #4" diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index 51b0e5c781c..a31a47131c1 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -63657,7 +63657,7 @@ }, /obj/effect/turf_decal/bot, /obj/machinery/light/small/directional/east, -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/machinery/status_display/supply{ pixel_x = 32 }, @@ -81435,7 +81435,7 @@ location = "QM #1" }, /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /turf/open/floor/iron, /area/station/cargo/storage) "xaH" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index d8b9a3fbce1..316f1b0f0f2 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -20077,7 +20077,7 @@ /turf/open/floor/engine, /area/station/engineering/supermatter/room) "gWz" = ( -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/structure/cable, /obj/machinery/navbeacon{ codes_txt = "delivery;dir=1"; @@ -21868,7 +21868,7 @@ /turf/open/floor/iron/dark, /area/station/ai/satellite/exterior) "hBo" = ( -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/structure/cable, /obj/machinery/navbeacon{ codes_txt = "delivery;dir=2"; @@ -30607,7 +30607,7 @@ /obj/effect/decal/cleanable/dirt, /obj/effect/decal/cleanable/blood/oil/slippery, /obj/effect/decal/cleanable/blood/gibs/down, -/mob/living/simple_animal/bot/mulebot{ +/mob/living/basic/bot/mulebot{ name = "Leaping Rabbit" }, /obj/structure/disposalpipe/segment, diff --git a/_maps/map_files/NebulaStation/NebulaStation.dmm b/_maps/map_files/NebulaStation/NebulaStation.dmm index bd3547f33d8..a29aefe5dd3 100644 --- a/_maps/map_files/NebulaStation/NebulaStation.dmm +++ b/_maps/map_files/NebulaStation/NebulaStation.dmm @@ -18050,7 +18050,7 @@ /area/station/engineering/storage) "cII" = ( /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/mulebot{ +/mob/living/basic/bot/mulebot{ home_destination = "QM #2"; suffix = "#2" }, @@ -75322,7 +75322,7 @@ /area/station/engineering/supermatter/room) "lcm" = ( /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/mulebot{ +/mob/living/basic/bot/mulebot{ home_destination = "QM #2"; suffix = "#2" }, @@ -90689,7 +90689,7 @@ /area/station/hallway/primary/fore) "nvZ" = ( /obj/effect/turf_decal/bot, -/mob/living/simple_animal/bot/mulebot{ +/mob/living/basic/bot/mulebot{ home_destination = "QM #2"; suffix = "#2" }, diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index c6f10ec5116..7e9d064585a 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -37251,7 +37251,7 @@ /turf/open/floor/iron/dark, /area/station/security/prison/garden) "lQz" = ( -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/effect/turf_decal/delivery, /obj/machinery/navbeacon{ location = "QM #2"; @@ -42305,7 +42305,7 @@ /turf/open/floor/iron/dark, /area/station/medical/virology) "nIC" = ( -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/effect/turf_decal/delivery, /obj/machinery/navbeacon{ location = "QM #6"; @@ -48524,7 +48524,7 @@ /turf/open/floor/iron/dark, /area/station/ai/satellite/interior) "pXq" = ( -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/effect/turf_decal/delivery, /obj/machinery/navbeacon{ location = "QM #3"; @@ -53129,7 +53129,7 @@ /turf/closed/wall, /area/station/engineering/atmospherics_engine) "rCs" = ( -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/effect/turf_decal/delivery, /obj/machinery/navbeacon{ location = "QM #1"; diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm index cf1b7a136a7..a723bb3f2a8 100644 --- a/_maps/map_files/wawastation/wawastation.dmm +++ b/_maps/map_files/wawastation/wawastation.dmm @@ -3278,7 +3278,7 @@ codes_txt = "delivery;dir=8"; location = "QM #2" }, -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /turf/open/floor/iron, /area/station/cargo/warehouse) "beO" = ( @@ -16365,7 +16365,7 @@ codes_txt = "delivery;dir=8"; location = "QM #1" }, -/mob/living/simple_animal/bot/mulebot, +/mob/living/basic/bot/mulebot, /obj/machinery/light/small/directional/east, /turf/open/floor/iron, /area/station/cargo/warehouse) diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index f8285b075e4..a6724aeb4aa 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -21,6 +21,7 @@ ///For JPS pathing, the maximum length of a path we'll try to generate. Should be modularized depending on what we're doing later on #define AI_MAX_PATH_LENGTH 30 // 30 is possibly overkill since by default we lose interest after 14 tiles of distance, but this gives wiggle room for weaving around obstacles #define AI_BOT_PATH_LENGTH 60 +#define AI_MULEBOT_PATH_LENGTH 150 //we making a pilgramage sometimes... // How far should we, by default, be looking for interesting things to de-idle? #define AI_DEFAULT_INTERESTING_DIST 10 diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm index 0c7758f6134..653bace09d8 100644 --- a/code/__DEFINES/ai/bot_keys.dm +++ b/code/__DEFINES/ai/bot_keys.dm @@ -149,3 +149,12 @@ #define BB_ROBOT_TARGET "robot_target" ///key that holds materials we can refill #define BB_REFILLABLE_TARGET "refillable_target" + + +//mulebots +///key that holds our delivery destination's name +#define BB_MULEBOT_DESTINATION_BEACON "mulebot_destination" +///key that holds our home port's name +#define BB_MULEBOT_HOME_BEACON "mulebot_home_beacon" +///key that holds our current delivery target atom +#define BB_MULEBOT_TRAVEL_TARGET "mulebot_travel_target" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index cf26bbbad24..7fe071ff8a3 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -23,11 +23,11 @@ #define COMSIG_MOB_CANCEL_CLICKON (1<<0) ///from base of mob/alt_click_on_secodary(): (atom/A) #define COMSIG_MOB_ALTCLICKON_SECONDARY "mob_altclickon_secondary" -/// From base of /mob/living/simple_animal/bot/proc/bot_step() +/// From base of /mob/living/basic/bot/proc/bot_step() #define COMSIG_MOB_BOT_PRE_STEP "mob_bot_pre_step" /// Should always match COMPONENT_MOVABLE_BLOCK_PRE_MOVE as these are interchangeable and used to block movement. #define COMPONENT_MOB_BOT_BLOCK_PRE_STEP COMPONENT_MOVABLE_BLOCK_PRE_MOVE -/// From base of /mob/living/simple_animal/bot/proc/bot_step() +/// From base of /mob/living/basic/bot/proc/bot_step() #define COMSIG_MOB_BOT_STEP "mob_bot_step" /// From base of /mob/proc/update_held_items diff --git a/code/__DEFINES/dcs/signals/signals_moveloop.dm b/code/__DEFINES/dcs/signals/signals_moveloop.dm index 8a354f8bfbb..95208dd1e81 100644 --- a/code/__DEFINES/dcs/signals/signals_moveloop.dm +++ b/code/__DEFINES/dcs/signals/signals_moveloop.dm @@ -11,3 +11,6 @@ #define COMSIG_MOVELOOP_JPS_REPATH "moveloop_jps_repath" ///from [/datum/move_loop/has_target/jps/on_finish_pathing] #define COMSIG_MOVELOOP_JPS_FINISHED_PATHING "moveloop_jps_finished_pathing" + +///from /datum/move_loop/has_target/jps/frustrations/handle_move_attempt_failure +#define COMSIG_MOVELOOP_JPS_FRUSTRATION_INCREMENTED "moveloop_jps_frustration_incremented" diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 1b71b003872..4e506977dc3 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -158,6 +158,8 @@ #define HUD_WIZARD_COMPACT_PERKS "wizard_compact_perks" #define HUD_WIZARD_PERK(slot) "wizard_perk:[slot]" +#define HUD_MULEBOT_CHARGE "mulebot_charge" + /// Converts item slots to hud keys #define HUD_KEY_ITEM_SLOT(slot) "item_slot:[slot]" /// Converts item slots to hud keys as a compiler constant diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 28300319502..d314074dc32 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -192,9 +192,7 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isrevenant(A) (istype(A, /mob/living/basic/revenant)) -#define isbot(A) (istype(A, /mob/living/simple_animal/bot) || istype(A, /mob/living/basic/bot)) - -#define isbasicbot(A) (istype(A, /mob/living/basic/bot)) +#define isbot(A) istype(A, /mob/living/basic/bot) #define ismouse(A) (istype(A, /mob/living/basic/mouse)) diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index 2c76b6f3b91..60f6cbbd762 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -399,3 +399,10 @@ DEFINE_BITFIELD(janitor_mode_flags, list( /// Default offsets for riding a cyborg #define DEFAULT_ROBOT_RIDING_OFFSETS list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list(6, 3)) + + +//mulebots +#define MULEBOT_MOOD_ANNOYED "ANNOYED" +#define MULEBOT_MOOD_CHIME "CHIME" +#define MULEBOT_MOOD_DELIGHT "DELIGHT" +#define MULEBOT_MOOD_SIGH "SIGH" diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index acdb1f02133..f3af1b68531 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -68,9 +68,6 @@ /mob/living/basic/bot/generate_random_mob_name(unique) return generate_random_name(gender, unique, list(/datum/language/machine = 1)) -/mob/living/simple_animal/bot/generate_random_mob_name(unique) - return generate_random_name(gender, unique, list(/datum/language/machine = 1)) - GLOBAL_VAR(command_name) /proc/command_name() if (GLOB.command_name) diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index 186385debfe..9d8ea11ce86 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -98,7 +98,6 @@ GLOBAL_LIST_INIT(phobia_mobs, list( /mob/living/basic/drone, /mob/living/silicon/ai, /mob/living/silicon/robot, - /mob/living/simple_animal/bot, )), "security" = typecacheof(list(/mob/living/basic/bot/secbot)), "spiders" = typecacheof(list( @@ -123,7 +122,7 @@ GLOBAL_LIST_INIT(phobia_mobs, list( /mob/living/basic/skeleton, /mob/living/basic/wizard, /mob/living/basic/zombie, - /mob/living/simple_animal/bot/mulebot/paranormal, + /mob/living/basic/bot/mulebot/paranormal, /mob/living/basic/voidwalker, )), )) diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm index e83f68d22db..ea1e7f90cda 100644 --- a/code/controllers/subsystem/movement/movement_types.dm +++ b/code/controllers/subsystem/movement/movement_types.dm @@ -461,6 +461,94 @@ if(length(movement_path)) movement_path.Cut(1,2) else + return handle_move_attempt_failure() + + +/datum/move_loop/has_target/jps/proc/handle_move_attempt_failure() + EVLOG_TEXT(moving, EVLOG_CATEGORY_MOVELOOPS, "Path recalculating due to obstruction") + INVOKE_ASYNC(src, PROC_REF(recalculate_path)) + return MOVELOOP_FAILURE + +/datum/move_loop/has_target/jps/frustrations + ///maximum amount of frustrations before we recalculate path + var/maximum_frustrations + ///what is our current frustration? + var/current_frustrations = 0 + ///how long before we're able to increment frustration? + var/frustration_delay + ///have we drawn our initial path? + var/initial_path_drawn = FALSE + ///cooldown between frustration increments + COOLDOWN_DECLARE(frustration_cooldown) + + +/datum/move_manager/proc/frustrations_move(moving, + chasing, + delay, + timeout, + repath_delay, + max_path_length, + minimum_distance, + list/access, + simulated_only, + turf/avoid, + skip_first, + subsystem, + diagonal_handling, + priority, + flags, + datum/extra_info, + initial_path) + return add_to_loop(moving, + subsystem, + /datum/move_loop/has_target/jps/frustrations, + priority, + flags, + extra_info, + delay, + timeout, + chasing, + repath_delay, + max_path_length, + minimum_distance, + access, + simulated_only, + avoid, + skip_first, + diagonal_handling, + initial_path) + +/datum/move_loop/has_target/jps/frustrations/setup(delay, timeout, atom/chasing, maximum_frustrations = 10, frustration_delay = 2 SECONDS) + . = ..() + if(!.) + return + src.maximum_frustrations = maximum_frustrations + src.frustration_delay = frustration_delay + +/datum/move_loop/has_target/jps/frustrations/recalculate_path() + if(initial_path_drawn && current_frustrations < maximum_frustrations) + return + return ..() + +/datum/move_loop/has_target/jps/frustrations/loop_stopped() + . = ..() + +/datum/move_loop/has_target/jps/frustrations/on_finish_pathing(list/path) + . = ..() + if(movement_path) + initial_path_drawn = TRUE + +/datum/move_loop/has_target/jps/frustrations/handle_move_attempt_failure() + if(!initial_path_drawn) + INVOKE_ASYNC(src, PROC_REF(recalculate_path)) + return MOVELOOP_FAILURE + if(!COOLDOWN_FINISHED(src, frustration_cooldown)) + return NONE + COOLDOWN_START(src, frustration_cooldown, frustration_delay) + current_frustrations++ + SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_FRUSTRATION_INCREMENTED, current_frustrations) + if(current_frustrations >= maximum_frustrations) + current_frustrations = 0 EVLOG_TEXT(moving, EVLOG_CATEGORY_MOVELOOPS, "Path recalculating due to obstruction") INVOKE_ASYNC(src, PROC_REF(recalculate_path)) return MOVELOOP_FAILURE diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index 1153833a31e..a8980d15b85 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -12,6 +12,16 @@ var/atom/movable/moving = controller.pawn var/delay = controller.movement_delay + var/datum/move_loop/loop = setup_moveloop(controller, current_movement_target, moving, delay) + + RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) + RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) + RegisterSignal(loop, COMSIG_MOVELOOP_JPS_REPATH, PROC_REF(repath_incoming)) + + return loop + + +/datum/ai_movement/jps/proc/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay) var/datum/move_loop/has_target/jps/loop = GLOB.move_manager.jps_move(moving, current_movement_target, delay, @@ -24,11 +34,6 @@ diagonal_handling = diagonal_flags, extra_info = controller, ) - - RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) - RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) - RegisterSignal(loop, COMSIG_MOVELOOP_JPS_REPATH, PROC_REF(repath_incoming)) - return loop /datum/ai_movement/jps/proc/repath_incoming(datum/move_loop/has_target/jps/source) @@ -47,9 +52,35 @@ var/datum/move_loop/loop = ..() var/atom/our_pawn = controller.pawn if(isnull(our_pawn)) - return + return null our_pawn.RegisterSignal(loop, COMSIG_MOVELOOP_JPS_FINISHED_PATHING, TYPE_PROC_REF(/mob/living/basic/bot, generate_bot_path)) + return loop /datum/ai_movement/jps/bot/travel_to_beacon maximum_length = AI_BOT_PATH_LENGTH max_pathing_attempts = 10 + + +/datum/ai_movement/jps/bot/mulebot + max_pathing_attempts = 10 + maximum_length = AI_MULEBOT_PATH_LENGTH + +/datum/ai_movement/jps/bot/mulebot/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay) + var/datum/move_loop/has_target/jps/frustrations/loop = GLOB.move_manager.frustrations_move(moving, + 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(), + subsystem = SSai_movement, + diagonal_handling = diagonal_flags, + extra_info = controller, + ) + return loop + +/datum/ai_movement/jps/bot/mulebot/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) + var/datum/move_loop/loop = ..() + var/atom/our_pawn = controller.pawn + our_pawn.RegisterSignal(loop, COMSIG_MOVELOOP_JPS_FRUSTRATION_INCREMENTED, TYPE_PROC_REF(/mob/living/basic/bot/mulebot, handle_buzzing)) diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index c3b4efe3460..fa22771945d 100644 --- a/code/datums/wires/mulebot.dm +++ b/code/datums/wires/mulebot.dm @@ -3,7 +3,7 @@ #define SLOW_MOTOR_SPEED 3 /datum/wires/mulebot - holder_type = /mob/living/simple_animal/bot/mulebot + holder_type = /mob/living/basic/bot/mulebot proper_name = "Mulebot" randomize = TRUE @@ -19,12 +19,12 @@ /datum/wires/mulebot/interactable(mob/user) if(!..()) return FALSE - var/mob/living/simple_animal/bot/mulebot/mule = holder - if(mule.bot_cover_flags & BOT_COVER_MAINTS_OPEN) + var/mob/living/basic/bot/mulebot/mule = holder + if(mule.bot_access_flags & BOT_COVER_MAINTS_OPEN) return TRUE /datum/wires/mulebot/on_cut(wire, mend, source) - var/mob/living/simple_animal/bot/mulebot/mule = holder + var/mob/living/basic/bot/mulebot/mule = holder switch(wire) if(WIRE_MOTOR1, WIRE_MOTOR2) if(is_cut(WIRE_MOTOR1) && is_cut(WIRE_MOTOR2)) @@ -49,7 +49,7 @@ holder.audible_message(span_hear("Something inside [mule] clicks ominously!"), null, 1) /datum/wires/mulebot/on_pulse(wire) - var/mob/living/simple_animal/bot/mulebot/mule = holder + var/mob/living/basic/bot/mulebot/mule = holder if(!mule.has_power(TRUE)) return //logically mulebots can't flash and beep if they don't have power. switch(wire) @@ -57,7 +57,7 @@ holder.visible_message(span_notice("[icon2html(mule, viewers(holder))] The charge light flickers.")) if(WIRE_AVOIDANCE) holder.visible_message(span_notice("[icon2html(mule, viewers(holder))] The external warning lights flash briefly.")) - flick("[mule.base_icon]1", mule) + flick("[mule.base_icon_state]1", mule) if(WIRE_LOADCHECK) holder.visible_message(span_notice("[icon2html(mule, viewers(holder))] The load platform clunks.")) if(WIRE_MOTOR1, WIRE_MOTOR2) @@ -65,6 +65,7 @@ else holder.visible_message(span_notice("[icon2html(mule, viewers(holder))] You hear a radio crackle.")) + #undef FAST_MOTOR_SPEED #undef AVERAGE_MOTOR_SPEED #undef SLOW_MOTOR_SPEED diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index f56633befce..a38ba684758 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -455,46 +455,6 @@ Diagnostic HUDs! else set_hud_image_state(DIAG_CAMERA_HUD, "hudcamera") -/*~~~~~~~~~ - Bots! -~~~~~~~~~~*/ -/mob/living/simple_animal/bot/proc/diag_hud_set_bothealth() - set_hud_image_state(DIAG_HUD, "huddiag[RoundDiagBar(health/maxHealth)]") - -/mob/living/simple_animal/bot/proc/diag_hud_set_botstat() //On (With wireless on or off), Off, EMP'ed - if(bot_mode_flags & BOT_MODE_ON) - set_hud_image_state(DIAG_STAT_HUD, "hudstat") - else if(stat) //Generally EMP causes this - set_hud_image_state(DIAG_STAT_HUD, "hudoffline") - else //Bot is off - set_hud_image_state(DIAG_STAT_HUD, "huddead2") - -/mob/living/simple_animal/bot/proc/diag_hud_set_botmode() //Shows a bot's current operation - if(client) //If the bot is player controlled, it will not be following mode logic! - set_hud_image_state(DIAG_BOT_HUD, "hudsentient") - return - - switch(mode) - if(BOT_SUMMON, BOT_RESPONDING) //Responding to PDA or AI summons - set_hud_image_state(DIAG_BOT_HUD, "hudcalled") - if(BOT_CLEANING, BOT_HEALING) //Cleanbot cleaning, repairbot fixing, or Medibot Healing - set_hud_image_state(DIAG_BOT_HUD, "hudworking") - if(BOT_PATROL, BOT_START_PATROL) //Patrol mode - set_hud_image_state(DIAG_BOT_HUD, "hudpatrol") - if(BOT_PREP_ARREST, BOT_ARREST, BOT_HUNT) //STOP RIGHT THERE, CRIMINAL SCUM! - set_hud_image_state(DIAG_BOT_HUD, "hudalert") - if(BOT_MOVING, BOT_DELIVER, BOT_GO_HOME, BOT_NAV) //Moving to target for normal bots, moving to deliver or go home for MULES. - set_hud_image_state(DIAG_BOT_HUD, "hudmove") - else - set_hud_image_state(DIAG_BOT_HUD, "") - -/mob/living/simple_animal/bot/mulebot/proc/diag_hud_set_mulebotcell() - if(QDELETED(cell) || (cell.maxcharge == 0)) - set_hud_image_state(DIAG_BATT_HUD, "hudnobatt") - else - var/chargelvl = (cell.charge/cell.maxcharge) - set_hud_image_state(DIAG_BATT_HUD, "hudbatt[RoundDiagBar(chargelvl)]") - /*~~~~~~~~~~~~ Airlocks! ~~~~~~~~~~~~~*/ diff --git a/code/game/machinery/botlaunchpad.dm b/code/game/machinery/botlaunchpad.dm index 857e9278af0..ab949fb4358 100644 --- a/code/game/machinery/botlaunchpad.dm +++ b/code/game/machinery/botlaunchpad.dm @@ -65,12 +65,8 @@ user.balloon_alert(user, "no bots sent from the pad!") return user.balloon_alert(user, "bot sent back to pad") - if(isbasicbot(our_bot)) - var/mob/living/basic/bot/basic_bot = our_bot - basic_bot.summon_bot(src) - return - var/mob/living/simple_animal/bot/simple_bot = our_bot - simple_bot.call_bot(src, get_turf(src)) + var/mob/living/basic/bot/basic_bot = our_bot + basic_bot.summon_bot(src) /obj/structure/closet/supplypod/transport/botpod reverse_option_list = list("Mobs"=TRUE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE,"Mecha"=FALSE) diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm index 0c153c59128..81641800968 100644 --- a/code/game/objects/structures/plasticflaps.dm +++ b/code/game/objects/structures/plasticflaps.dm @@ -224,7 +224,7 @@ else if(isliving(mover)) // You Shall Not Pass! var/mob/living/living_mover = mover - if(istype(living_mover.buckled, /mob/living/simple_animal/bot/mulebot)) // mulebot passenger gets a free pass. + if(istype(living_mover.buckled, /mob/living/basic/bot/mulebot)) // mulebot passenger gets a free pass. return TRUE if(living_mover.body_position == STANDING_UP && living_mover.mob_size != MOB_SIZE_TINY && !(HAS_TRAIT(living_mover, TRAIT_VENTCRAWLER_ALWAYS) || HAS_TRAIT(living_mover, TRAIT_VENTCRAWLER_NUDE))) diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm index 0fdb4251f5f..5f810a1e37c 100644 --- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm +++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm @@ -1097,7 +1097,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf)) /obj/structure, /obj/item/radio/intercom, /obj/item/modular_computer, - /mob/living/simple_animal/bot, + /mob/living/basic/bot, /mob/living/silicon, ) diff --git a/code/modules/antagonists/spy/spy_bounty.dm b/code/modules/antagonists/spy/spy_bounty.dm index 5344f86be2e..5c65e544dbc 100644 --- a/code/modules/antagonists/spy/spy_bounty.dm +++ b/code/modules/antagonists/spy/spy_bounty.dm @@ -679,7 +679,7 @@ /datum/spy_bounty/some_bot/get_dupe_protection_key(atom/movable/stealing) return bot_type -/datum/spy_bounty/some_bot/finish_cleanup(mob/living/simple_animal/bot/stealing) +/datum/spy_bounty/some_bot/finish_cleanup(mob/living/basic/bot/stealing) if(stealing.client) to_chat(stealing, span_deadsay("You've been stolen! You are shipped off to the black market and taken apart for spare parts...")) stealing.investigate_log("stole by a spy (and deleted)", INVESTIGATE_DEATHS) diff --git a/code/modules/cargo/packs/service.dm b/code/modules/cargo/packs/service.dm index 4092fcfe25d..3a97e99ee54 100644 --- a/code/modules/cargo/packs/service.dm +++ b/code/modules/cargo/packs/service.dm @@ -68,7 +68,7 @@ desc = "Pink-haired Quartermaster not doing her job? Replace her with this tireless worker, today! \ Contains one MULEbot." cost = CARGO_CRATE_VALUE * 4 - contains = list(/mob/living/simple_animal/bot/mulebot) + contains = list(/mob/living/basic/bot/mulebot) crate_name = "\improper MULEbot Crate" crate_type = /obj/structure/closet/crate/large diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm index 555a4b03c14..59caf2b1560 100644 --- a/code/modules/events/ion_storm.dm +++ b/code/modules/events/ion_storm.dm @@ -60,7 +60,7 @@ M.post_lawchange() if(botEmagChance) - for(var/mob/living/simple_animal/bot/bot in GLOB.alive_mob_list) + for(var/mob/living/basic/bot/bot in GLOB.alive_mob_list) if(prob(botEmagChance)) bot.emag_act() diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index b600c291214..5c643461b15 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -302,7 +302,7 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) /mob/eye, /mob/living/basic/boss, /mob/living/basic/regal_rat, - /mob/living/simple_animal/bot, + /mob/living/basic/bot, /mob/living/simple_animal/hostile/megafauna, )) if(!is_type_in_typecache(potential_mob_poi, mob_allowed_typecache) && !potential_mob_poi.GetComponent(/datum/component/deadchat_control)) diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index 15d983b8745..f7962d9b868 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -196,7 +196,7 @@ GLOBAL_LIST_INIT(command_strings, list( /mob/living/basic/bot/proc/get_emagged_message() return get_policy(ROLE_EMAGGED_BOT) || "You are a malfunctioning bot! Disrupt everyone and cause chaos!" -/mob/living/basic/bot/proc/turn_on() +/mob/living/basic/bot/proc/turn_on(mob/user) if(stat == DEAD) return FALSE set_mode_flags(bot_mode_flags | BOT_MODE_ON) diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot.dm new file mode 100644 index 00000000000..55d5632e5e1 --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot.dm @@ -0,0 +1,193 @@ +/mob/living/basic/bot/mulebot + name = "\improper MULEbot" + desc = "A Multiple Utility Load Effector bot." + icon = 'icons/mob/silicon/aibots.dmi' + icon_state = "mulebot0" + base_icon_state = "mulebot" + + light_color = "#ffcc99" + light_power = 0.8 + + health = 50 + maxHealth = 50 + + damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, STAMINA = 0, OXY = 0) + density = TRUE + mob_size = MOB_SIZE_LARGE + move_resist = MOVE_FORCE_STRONG + animate_movement = SLIDE_STEPS + speed = 3 + + combat_mode = TRUE + + buckle_lying = 0 + buckle_prevents_pull = TRUE // No pulling loaded shit + + bot_mode_flags = ~BOT_MODE_ROUNDSTART_POSSESSION + req_one_access = list(ACCESS_ROBOTICS, ACCESS_CARGO) + radio_key = /obj/item/encryptionkey/headset_cargo + radio_channel = RADIO_CHANNEL_SUPPLY + pass_flags = PASSFLAPS + bot_type = MULE_BOT + + additional_access = /datum/id_trim/job/cargo_technician + path_image_color = "#7F5200" + hud_type = /datum/hud/living/mulebot + + hackables = "obstacle detection circuits" + possessed_message = "You are a MULEbot! Do your best to make sure that packages get to their destination!" + ai_controller = /datum/ai_controller/basic_controller/bot/mulebot + + /// unique identifier in case there are multiple mulebots. + var/id + + /// what we're transporting + var/atom/movable/load + /// who's riding us + var/mob/living/passenger + + ///flags of mulebot mode + var/mulebot_delivery_flags = MULEBOT_RETURN_MODE | MULEBOT_AUTO_PICKUP_MODE | MULEBOT_REPORT_DELIVERY_MODE + + ///Internal Powercell + var/obj/item/stock_parts/power_store/cell + ///How much power we use when we move. + var/cell_move_power_usage = 0.0005 * STANDARD_CELL_CHARGE + ///The amount of steps we should take until we rest for a time. + var/num_steps = 0 + + ///The chance to be deleted and replaced by a different mule + var/replacement_chance = 0.666 + ///home destination, only used by mappers. + var/home_destination = "" + +/mob/living/basic/bot/mulebot/Initialize(mapload) + . = ..() + + if(prob(replacement_chance) && mapload) + new /mob/living/basic/bot/mulebot/paranormal(loc) + return INITIALIZE_HINT_QDEL + + set_wires(new /datum/wires/mulebot(src)) + var/obj/item/stock_parts/power_store/cell/upgraded/new_cell = new(src) + assign_cell(new_cell) + ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, "") + AddElement(/datum/element/ridable, /datum/component/riding/creature/mulebot) + ADD_TRAIT(src, TRAIT_NOMOBSWAP, INNATE_TRAIT) + add_traits(list(TRAIT_NOMOBSWAP, TRAIT_COMBAT_MODE_LOCK), INNATE_TRAIT) + RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_pre_move)) + + set_id(suffix || assign_random_name()) + suffix = null + if(name == "\improper MULEbot") + name = "\improper MULEbot [id]" + set_home(get_turf(src)) + ai_controller.update_able_to_run() + update_appearance() + +/mob/living/basic/bot/mulebot/Destroy() + UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE) + unload() + QDEL_NULL(cell) + return ..() + +/mob/living/basic/bot/mulebot/proc/assign_cell(atom/new_cell) + cell = new_cell + var/atom/movable/screen/mob_charge/charge_hud = hud_used?.screen_objects[HUD_MULEBOT_CHARGE] + charge_hud?.update_battery_overlay(new_cell) + charge_hud?.calculate_charge() + + +/mob/living/basic/bot/mulebot/attack_hand(mob/living/carbon/human/user, list/modifiers) + if(bot_access_flags & BOT_COVER_MAINTS_OPEN && !HAS_AI_ACCESS(user)) + wires.interact(user) + return + if(wires.is_cut(WIRE_RX) && HAS_AI_ACCESS(user)) + return + + return ..() + +/mob/living/basic/bot/mulebot/examine(mob/user) + . = ..() + if(bot_access_flags & BOT_COVER_MAINTS_OPEN) + if(cell) + . += span_notice("It has \a [cell] installed.") + . += span_info("You can use a crowbar to remove it.") + else + . += span_notice("It has an empty compartment where a power cell can be installed.") + if(load) //observer check is so we don't show the name of the ghost that's sitting on it to prevent metagaming who's ded. + . += span_notice("\A [isobserver(load) ? "ghostly figure" : load] is on its load platform.") + +/mob/living/basic/bot/mulebot/get_cell() + return cell + +/mob/living/basic/bot/mulebot/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE) + if(!can_unarmed_attack()) + return + if(isturf(target) && isturf(loc) && loc.Adjacent(target) && load) + unload(get_dir(loc, target)) + else + return ..() + +/mob/living/basic/bot/mulebot/turn_on(mob/user) + if(bot_access_flags & BOT_COVER_MAINTS_OPEN) + if(user) + to_chat(user, span_warning("[src]'s maintenance panel is open!")) + return FALSE + if(!has_power()) + if(user) + to_chat(user, span_warning("[src] has no power!")) + return FALSE + return ..() + +/mob/living/basic/bot/mulebot/update_icon_state() //if you change the icon_state names, please make sure to update /datum/wires/mulebot/on_pulse() as well. <3 + . = ..() + icon_state = "[base_icon_state][(bot_mode_flags & BOT_MODE_ON) ? wires?.is_cut(WIRE_AVOIDANCE) : "0"]" + +/mob/living/basic/bot/mulebot/update_overlays() + . = ..() + if(bot_access_flags & BOT_COVER_MAINTS_OPEN) + . += "[base_icon_state]-hatch" + if(isnull(load) || ismob(load)) //mob offsets and such are handled by the riding component / buckling + return + var/mutable_appearance/load_overlay = mutable_appearance(load.icon, load.icon_state, layer + 0.01) + load_overlay.pixel_y = initial(load.pixel_y) + 11 + . += load_overlay + +/mob/living/basic/bot/mulebot/proc/handle_buzzing(datum/move_loop/has_target/jps/frustrations/source, frustration_counter) + SIGNAL_HANDLER + + update_bot_mode(new_mode = BOT_BLOCKED) + var/buzz_mode = frustration_counter >= source.maximum_frustrations ? MULEBOT_MOOD_ANNOYED : MULEBOT_MOOD_SIGH + buzz(buzz_mode) + +/mob/living/basic/bot/mulebot/handle_loop_movement(atom/movable/source, atom/oldloc, dir, forced) //incase we start moving again after being previously blocked, update our mode + . = ..() + if(mode != BOT_BLOCKED) + return + var/obj/machinery/navbeacon/beacon = ai_controller.current_movement_target + if(!istype(beacon)) + return + var/intended_mode = beacon.location == ai_controller.blackboard[BB_MULEBOT_HOME_BEACON] ? BOT_GO_HOME : BOT_DELIVER + update_bot_mode(new_mode = intended_mode) + +///Noises that mulebots make +/mob/living/basic/bot/mulebot/proc/buzz(type) + switch(type) + if(MULEBOT_MOOD_SIGH) + audible_message(span_hear("[src] makes a sighing buzz.")) + playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 50, FALSE) + if(MULEBOT_MOOD_ANNOYED) + audible_message(span_hear("[src] makes an annoyed buzzing sound.")) + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, FALSE) + if(MULEBOT_MOOD_DELIGHT) + audible_message(span_hear("[src] makes a delighted ping!")) + playsound(src, 'sound/machines/ping.ogg', 50, FALSE) + if(MULEBOT_MOOD_CHIME) + audible_message(span_hear("[src] makes a chiming sound!")) + playsound(src, 'sound/machines/chime.ogg', 50, FALSE) + flick("[base_icon_state]1", src) + +/// returns true if the bot is fully powered. +/mob/living/basic/bot/mulebot/proc/has_power() + return cell && cell.charge > 0 && (!wires.is_cut(WIRE_POWER1) && !wires.is_cut(WIRE_POWER2)) diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm new file mode 100644 index 00000000000..7898ced148c --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm @@ -0,0 +1,145 @@ +/datum/ai_controller/basic_controller/bot/mulebot + blackboard = list( + BB_SALUTE_MESSAGES = list( + "blinks its light in appreciation towards", + ) + ) + ai_movement = /datum/ai_movement/jps/bot/mulebot + max_target_distance = AI_MULEBOT_PATH_LENGTH + planning_subtrees = list( + /datum/ai_planning_subtree/respond_to_summon, + /datum/ai_planning_subtree/salute_authority, + /datum/ai_planning_subtree/attempt_delivery, + /datum/ai_planning_subtree/find_delivery_beacon, + ) + reset_keys = list( + BB_BOT_SUMMON_TARGET, + BB_MULEBOT_DESTINATION_BEACON, + BB_MULEBOT_TRAVEL_TARGET, + ) + +/datum/ai_controller/basic_controller/bot/mulebot/get_able_to_run() + var/mob/living/basic/bot/mulebot/bot_pawn = pawn + if(!bot_pawn.has_power()) + return AI_UNABLE_TO_RUN + return ..() + +/datum/ai_controller/basic_controller/bot/mulebot/setup_able_to_run() + . = ..() + var/mob/living/basic/bot/my_bot = pawn + var/static/list/wire_signals = list( + COMSIG_MEND_WIRE(WIRE_POWER1), //this framework is insane + COMSIG_MEND_WIRE(WIRE_POWER2), + COMSIG_CUT_WIRE(WIRE_POWER1), + COMSIG_CUT_WIRE(WIRE_POWER2), + ) + RegisterSignals(my_bot.wires, wire_signals, PROC_REF(update_able_to_run)) + var/static/list/content_signals = list( + COMSIG_ATOM_ENTERED, + COMSIG_ATOM_EXITED, + ) + RegisterSignals(my_bot, content_signals, PROC_REF(update_able_to_run)) + +/datum/ai_planning_subtree/find_delivery_beacon + ///what behavior do we use to seek beacons + var/find_beacon_behaviour = /datum/ai_behavior/find_delivery_beacon + +/datum/ai_planning_subtree/find_delivery_beacon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn + if(bot_pawn.wires.is_cut(WIRE_BEACON)) + return + + if(!controller.blackboard_key_exists(BB_MULEBOT_TRAVEL_TARGET)) + controller.queue_behavior(find_beacon_behaviour, BB_MULEBOT_TRAVEL_TARGET) + +/datum/ai_behavior/find_delivery_beacon + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_delivery_beacon/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn + var/atom/delivery_beacon + + var/beacon_tag = null + + switch(bot_pawn.mode) + if(BOT_DELIVER) + beacon_tag = controller.blackboard[BB_MULEBOT_DESTINATION_BEACON] + if(BOT_GO_HOME) + beacon_tag = controller.blackboard[BB_MULEBOT_HOME_BEACON] + else + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.deliverybeacons) + if(beacon.location == beacon_tag) + delivery_beacon = beacon + break + + if(isnull(delivery_beacon)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(BB_MULEBOT_TRAVEL_TARGET, delivery_beacon) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/ai_behavior/travel_towards/delivery_beacon + new_movement_type = /datum/ai_movement/jps/bot/mulebot + +/datum/ai_planning_subtree/attempt_delivery + ///behavior we use to unload crates + var/delivery_behaviour = /datum/ai_behavior/handle_delivery + +/datum/ai_planning_subtree/attempt_delivery/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_MULEBOT_TRAVEL_TARGET)) + return + + controller.queue_behavior(delivery_behaviour, BB_MULEBOT_TRAVEL_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_behavior/handle_delivery + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH + +/datum/ai_behavior/handle_delivery/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/handle_delivery/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/obj/machinery/navbeacon/beacon = controller.blackboard[target_key] + var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn + + var/load_direction = beacon.codes[NAVBEACON_DELIVERY_DIRECTION] // this will be the load/unload dir + if(!load_direction) + load_direction = beacon.dir // fallback + + load_direction = text2num(load_direction) + + if(bot_pawn.load) + if(bot_pawn.mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE) + bot_pawn.radio_channel = RADIO_CHANNEL_SUPPLY //Supply channel + bot_pawn.buzz(MULEBOT_MOOD_CHIME) + bot_pawn.speak("Destination [RUNECHAT_BOLD("[beacon.location]")] reached. Unloading [bot_pawn.load].", bot_pawn.radio_channel) + bot_pawn.unload(load_direction) + + else + if(bot_pawn.mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE) // find a crate + var/atom/movable/atom_to_pick_up + if(bot_pawn.wires.is_cut(WIRE_LOADCHECK)) // if hacked, load first unanchored thing we find + for(var/atom/movable/target_atom in get_step(bot_pawn.loc, load_direction)) + if(!target_atom.anchored) + atom_to_pick_up = target_atom + break + else // otherwise, look for crates only + atom_to_pick_up = locate(/obj/structure/closet/crate) in get_step(bot_pawn.loc, load_direction) + if(atom_to_pick_up?.Adjacent(bot_pawn)) + bot_pawn.load(atom_to_pick_up) + if(bot_pawn.mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE) + bot_pawn.speak("Now loading [bot_pawn.load] at [RUNECHAT_BOLD("[get_area_name(bot_pawn)]")].", bot_pawn.radio_channel) + + if((bot_pawn.mulebot_delivery_flags & MULEBOT_RETURN_MODE) && controller.blackboard[BB_MULEBOT_HOME_BEACON] && controller.blackboard[BB_MULEBOT_HOME_BEACON] != beacon.location) + bot_pawn.update_bot_mode(new_mode = BOT_GO_HOME) + controller.clear_blackboard_key(BB_MULEBOT_TRAVEL_TARGET) + else + bot_pawn.bot_reset() // otherwise go idle + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_control.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_control.dm new file mode 100644 index 00000000000..d7bd93be22c --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_control.dm @@ -0,0 +1,116 @@ +/mob/living/basic/bot/mulebot/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Mule", name) + ui.open() + +/mob/living/basic/bot/mulebot/ui_data(mob/user) + var/list/data = list() + data["powerStatus"] = bot_mode_flags & BOT_MODE_ON + data["locked"] = bot_access_flags & BOT_COVER_LOCKED + data["siliconUser"] = HAS_SILICON_ACCESS(user) + data["mode"] = mode ? "[mode]" : "Ready" + data["modeStatus"] = "" + switch(mode) + if(BOT_IDLE, BOT_DELIVER, BOT_GO_HOME) + data["modeStatus"] = "good" + if(BOT_BLOCKED, BOT_NAV, BOT_WAIT_FOR_NAV) + data["modeStatus"] = "average" + if(BOT_NO_ROUTE) + data["modeStatus"] = "bad" + data["load"] = get_load_name() + data["destination"] = ai_controller.blackboard[BB_MULEBOT_DESTINATION_BEACON] + data["homeDestination"] = ai_controller.blackboard[BB_MULEBOT_HOME_BEACON] + data["destinationsList"] = GLOB.deliverybeacontags + data["cellPercent"] = cell?.percent() + data["autoReturn"] = mulebot_delivery_flags & MULEBOT_RETURN_MODE + data["autoPickup"] = mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE + data["reportDelivery"] = mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE + data["botId"] = id + data["allowPossession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT + data["possessionEnabled"] = can_be_possessed + data["paiInserted"] = !!paicard + return data + +/mob/living/basic/bot/mulebot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + var/mob/user = ui.user + if(. || (bot_access_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(user))) + return + + bot_control(action, user, params) + return TRUE + +/mob/living/basic/bot/mulebot/bot_control(command, mob/user, list/params = list(), pda = FALSE) + if(pda && wires.is_cut(WIRE_RX)) // MULE wireless is controlled by wires. + return + + switch(command) + if("stop") + if(mode != BOT_IDLE) + bot_reset() + if("go") + if(mode == BOT_IDLE) + start() + if("home") + if(mode == BOT_IDLE || mode == BOT_DELIVER) + start_home() + if("destination") + var/new_dest + if(pda) + new_dest = tgui_input_list(user, "Enter Destination", "Mulebot Settings", GLOB.deliverybeacontags, ai_controller.blackboard[BB_MULEBOT_DESTINATION_BEACON]) + else + new_dest = params["value"] + if(new_dest) + set_destination(new_dest) + if("setid") + var/new_id = tgui_input_text(user, "Enter ID", "ID Assignment", id, max_length = MAX_NAME_LEN) + if(new_id) + set_id(new_id) + name = "\improper MULEbot [new_id]" + if("sethome") + var/new_home = tgui_input_list(user, "Enter Home", "Mulebot Settings", GLOB.deliverybeacontags, ai_controller.blackboard[BB_MULEBOT_HOME_BEACON]) + if(new_home) + ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, new_home) + if("unload") + if(load && mode != BOT_HUNT) + unload() + if("autoret") + mulebot_delivery_flags ^= MULEBOT_RETURN_MODE + if("autopick") + mulebot_delivery_flags ^= MULEBOT_AUTO_PICKUP_MODE + if("report") + mulebot_delivery_flags ^= MULEBOT_REPORT_DELIVERY_MODE + +/mob/living/basic/bot/mulebot/proc/start() + if(!(bot_mode_flags & BOT_MODE_ON)) + return + if(ai_controller.blackboard[BB_MULEBOT_DESTINATION_BEACON] == ai_controller.blackboard[BB_MULEBOT_HOME_BEACON]) + update_bot_mode(new_mode = BOT_GO_HOME) + else + update_bot_mode(new_mode = BOT_DELIVER) + +/mob/living/basic/bot/mulebot/proc/start_home() + set_destination(ai_controller.blackboard[BB_MULEBOT_HOME_BEACON]) + update_bot_mode(new_mode = BOT_GO_HOME) + +/mob/living/basic/bot/mulebot/proc/set_destination(new_destination) + ai_controller.set_blackboard_key(BB_MULEBOT_DESTINATION_BEACON, new_destination) + +/mob/living/basic/bot/mulebot/proc/set_home(turf/home_loc) + if(home_destination) + ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, home_destination) + home_destination = null + if(!istype(home_loc)) + CRASH("MULEbot [id] was requested to set a home location to [home_loc ? "an invalid home loc ([home_loc.type])" : "null"]") + + var/obj/machinery/navbeacon/home_beacon = locate() in home_loc + if(isnull(home_beacon)) + ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, "") + return + ai_controller.set_blackboard_key(BB_MULEBOT_HOME_BEACON, home_beacon.location) + log_transport("[id]: MULEbot successfuly set home location to ID [home_beacon.location] at [home_beacon.x], [home_beacon.y], [home_beacon.z]") + +///Sets the new ID of the mulebot +/mob/living/basic/bot/mulebot/proc/set_id(new_id) + id = new_id diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_delivery.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_delivery.dm new file mode 100644 index 00000000000..5fec23e9bbf --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_delivery.dm @@ -0,0 +1,117 @@ +/mob/living/basic/bot/mulebot/execute_resist() + . = ..() + if(load) + unload() + +/mob/living/basic/bot/mulebot/Exited(atom/movable/gone, direction) + . = ..() + if(gone == load) + unload() + if(gone == cell) + turn_off() + assign_cell() + cell = null + set_cell_hud() + +/mob/living/basic/bot/mulebot/Entered(obj/item/stock_parts/power_store/cell/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(istype(arrived) && isnull(cell)) + assign_cell(arrived) + +// mousedrop a crate to load the bot +// can load anything if hacked +/mob/living/basic/bot/mulebot/mouse_drop_receive(atom/movable/atom_to_load, mob/user, params) + if(!isliving(user)) + return + + if(!istype(atom_to_load) || isdead(atom_to_load) || iseyemob(atom_to_load) || istype(atom_to_load, /obj/effect/dummy/phased_mob)) + return + + load(atom_to_load) + +/mob/living/basic/bot/mulebot/post_unbuckle_mob(mob/living/M) + load = null + return ..() + +/mob/living/basic/bot/mulebot/relaymove(mob/living/user, direction) + if(user.incapacitated) + return + if(load == user) + unload() + +/mob/living/basic/bot/mulebot/remove_air(amount) //To prevent riders suffocating + return loc ? loc.remove_air(amount) : null + +/// Called to load an atom on the mulebot, which is usually a crate, unless if hacked +/mob/living/basic/bot/mulebot/proc/load(atom/movable/atom_to_load) + if(load || atom_to_load.anchored) + return + + if(!isturf(atom_to_load.loc)) //To prevent the loading from stuff from someone's inventory or screen icons. + return + + var/obj/structure/closet/crate/crate = atom_to_load + if(!istype(crate)) + if(!wires.is_cut(WIRE_LOADCHECK)) + buzz(MULEBOT_MOOD_SIGH) + return // if not hacked, only allow crates to be loaded + crate = null + + if(crate || isobj(atom_to_load)) + var/obj/object_to_load = atom_to_load + if(object_to_load.has_buckled_mobs() || (locate(/mob) in atom_to_load)) //can't load non crates objects with mobs buckled to it or inside it. + buzz(MULEBOT_MOOD_SIGH) + return + + if(crate) + crate.close() //make sure the crate is closed + + object_to_load.forceMove(src) + + else if(isliving(atom_to_load)) + if(!load_mob(atom_to_load)) //forceMove() is handled in buckling + return + + load = atom_to_load + update_appearance() + +///resolves the name to display for the loaded mob. primarily needed for the paranormal subtype since we don't want to show the name of ghosts riding it. +/mob/living/basic/bot/mulebot/proc/get_load_name() + return load ? load.name : null + +///Loads a mob onto the mulebot +/mob/living/basic/bot/mulebot/proc/load_mob(mob/living/mob_to_load) + can_buckle = TRUE + if(buckle_mob(mob_to_load)) + passenger = mob_to_load + load = mob_to_load + can_buckle = FALSE + return TRUE + +// called to unload the bot +// argument is optional direction to unload +// if zero or null, unload at bot's location +/mob/living/basic/bot/mulebot/proc/unload(dirn) + if(QDELETED(load)) + if(load) //if our thing was qdel'd, there's likely a leftover reference. just clear it and remove the overlay. we'll let the bot keep moving around to prevent it abruptly stopping somewhere. + load = null + update_appearance() + return + + update_bot_mode(new_mode = BOT_IDLE) + + var/atom/movable/cached_load = load //cache the load since unbuckling mobs clears the var. + + unbuckle_all_mobs() + + if(load) //don't have to do any of this for mobs. + cached_load.forceMove(loc) + cached_load.pixel_y = initial(cached_load.pixel_y) + cached_load.layer = initial(cached_load.layer) + SET_PLANE_EXPLICIT(cached_load, initial(cached_load.plane), src) + load = null + + if(dirn) //move the thing to the delivery point. + cached_load.Move(get_step(loc,dirn), dirn) + + update_appearance() diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_hud.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_hud.dm new file mode 100644 index 00000000000..3e1f26c8584 --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_hud.dm @@ -0,0 +1,74 @@ +/mob/living/basic/bot/mulebot/proc/set_cell_hud() + if(!has_power()) + set_hud_image_state(DIAG_BATT_HUD, "hudnobatt") + return + + var/atom/movable/screen/mob_charge/charge_hud = hud_used?.screen_objects[HUD_MULEBOT_CHARGE] + charge_hud?.calculate_charge() + set_hud_image_state(DIAG_BATT_HUD, "hudbatt[RoundDiagBar(cell.charge/cell.maxcharge)]") + +/atom/movable/screen/mob_charge + icon = 'icons/obj/machines/cell_charger.dmi' + icon_state = "ccharger" + screen_loc = ui_stamina + ///used to find the overlay for charger icon + var/current_charge_level = 4 + ///dynamic, based on what cell our nulebot's using + var/image/battery_overlay + ///maptext that displays charge in numbers + var/image/charge_overlay + ///is there a mouse on us + var/hovering = FALSE + +/atom/movable/screen/mob_charge/proc/update_battery_overlay(atom/target_battery) + var/obj/item/stock_parts/power_store/cell/my_cell = target_battery || (locate() in get_mob()) + if(isnull(my_cell)) + battery_overlay = null + else + battery_overlay = image(icon = my_cell.icon, icon_state = my_cell.icon_state, loc = src, layer = src.layer + 0.1) + update_appearance(UPDATE_ICON) + +/atom/movable/screen/mob_charge/proc/calculate_charge() + var/obj/item/stock_parts/power_store/cell/my_battery = locate() in get_mob() + var/charge_value = isnull(my_battery) ? 0 : round(my_battery.charge/my_battery.maxcharge * 100 , 1) + current_charge_level = round(charge_value * 4 / 100) + charge_overlay.maptext = MAPTEXT("
[charge_value]%
") + update_appearance(UPDATE_ICON) + +/atom/movable/screen/mob_charge/New(loc, ...) + . = ..() + charge_overlay = image(loc = src, layer = src.layer+0.2, pixel_y = -5) + update_battery_overlay() + +/atom/movable/screen/mob_charge/Destroy() + charge_overlay = null + battery_overlay = null + return ..() + +/atom/movable/screen/mob_charge/update_overlays() + . = ..() + . += mutable_appearance(icon, "ccharger-o[current_charge_level]") + if(battery_overlay) + . |= battery_overlay + if(hovering) + . |= charge_overlay + +/atom/movable/screen/mob_charge/MouseEntered(location,control,params) + if(usr != get_mob()) + return + . = ..() + hovering = TRUE + calculate_charge() + +/atom/movable/screen/mob_charge/MouseExited(location, control, params) + if(usr != get_mob()) + return + . = ..() + hovering = FALSE + update_appearance(UPDATE_ICON) + +/datum/hud/living/mulebot + +/datum/hud/living/mulebot/initialize_screen_objects() + . = ..() + add_screen_object(/atom/movable/screen/mob_charge, HUD_MULEBOT_CHARGE, HUD_GROUP_INFO) diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_movement.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_movement.dm new file mode 100644 index 00000000000..0654477a096 --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_movement.dm @@ -0,0 +1,72 @@ +/mob/living/basic/bot/mulebot/MobBump(mob/bumped_mob) // called when the bot bumps into a mob + if(mind || !isliving(bumped_mob)) //if there's a sentience controlling the bot, they aren't allowed to harm folks. + return ..() + var/mob/living/bumped_living = bumped_mob + if(wires.is_cut(WIRE_AVOIDANCE)) // usually just bumps, but if the avoidance wire is cut, knocks them over. + if(iscyborg(bumped_living)) + visible_message(span_danger("[src] bumps into [bumped_living]!")) + else if(bumped_living.Knockdown(8 SECONDS)) + log_combat(src, bumped_living, "knocked down") + visible_message(span_danger("[src] knocks over [bumped_living]!")) + return ..() + +/mob/living/basic/bot/mulebot/on_bot_movement(atom/movable/source, atom/oldloc, dir, forced) + cell?.use(cell_move_power_usage) + set_cell_hud() + + if(has_gravity()) + for(var/mob/living/carbon/human/future_pancake in loc) + if(future_pancake.body_position == LYING_DOWN) + run_over(future_pancake) + + return ..() + +///Checks if the bot is on or if it has charge +/mob/living/basic/bot/mulebot/proc/on_pre_move() + SIGNAL_HANDLER + + if(!(bot_mode_flags & BOT_MODE_ON)) + return COMPONENT_MOB_BOT_BLOCK_PRE_STEP + + if((cell && (cell.charge < cell_move_power_usage)) || !has_power()) + turn_off() + return COMPONENT_MOB_BOT_BLOCK_PRE_STEP + +// when mulebot is in the same loc +/mob/living/basic/bot/mulebot/proc/run_over(mob/living/carbon/human/crushed) + if (!(bot_access_flags & BOT_COVER_EMAGGED) && !wires.is_cut(WIRE_AVOIDANCE)) + if (!has_status_effect(/datum/status_effect/careful_driving)) + crushed.visible_message(span_notice("[src] slows down to avoid crushing [crushed].")) + apply_status_effect(/datum/status_effect/careful_driving) + return // Player mules must be emagged before they can trample + + log_combat(src, crushed, "run over", addition = "(DAMTYPE: [uppertext(BRUTE)])") + crushed.visible_message( + span_danger("[src] drives over [crushed]!"), + span_userdanger("[src] drives over you!"), + ) + + playsound(src, 'sound/effects/splat.ogg', 50, TRUE) + + var/damage = rand(5, 15) + var/static/list/zone_damages = list( + BODY_ZONE_HEAD = 2, + BODY_ZONE_CHEST = 2, + BODY_ZONE_L_LEG = 0.5, + BODY_ZONE_R_LEG = 0.5, + BODY_ZONE_L_ARM = 0.5, + BODY_ZONE_R_ARM = 0.5, + ) + for(var/body_zone in zone_damages) + crushed.apply_damage(zone_damages[body_zone] * damage, BRUTE, body_zone, run_armor_check(body_zone, MELEE)) + + add_mob_blood(crushed) + + var/turf/below_us = get_turf(src) + below_us.add_mob_blood(crushed) + + AddComponent(/datum/component/blood_walk, \ + blood_type = /obj/effect/decal/cleanable/blood/tracks, \ + target_dir_change = TRUE, \ + transfer_blood_dna = TRUE, \ + max_blood = 4) diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_paranormal.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_paranormal.dm new file mode 100644 index 00000000000..9a4b20b9e5a --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_paranormal.dm @@ -0,0 +1,61 @@ +/mob/living/basic/bot/mulebot/paranormal + name = "\improper GHOULbot" + desc = "A rather ghastly looking... Multiple Utility Load Effector bot? It only seems to accept paranormal forces, and for this reason is fucking useless." + icon_state = "paranormalmulebot0" + base_icon_state = "paranormalmulebot" + ///avoid the utterly miniscule chance of infinite looping + replacement_chance = 0 + +/mob/living/basic/bot/mulebot/paranormal/update_overlays() + . = ..() + if(!isobserver(load)) + return + var/mutable_appearance/ghost_overlay = mutable_appearance('icons/mob/simple/mob.dmi', "ghost", layer + 0.01) //use a generic ghost icon, otherwise you can metagame who's dead if they have a custom ghost set + ghost_overlay.pixel_y = 12 + . += ghost_overlay + +/mob/living/basic/bot/mulebot/paranormal/get_load_name() //Don't reveal the name of ghosts so we can't metagame who died and all that. + . = ..() + if(. && isobserver(load)) + return "Unknown" + +/mob/living/basic/bot/mulebot/paranormal/load(atom/movable/movable_atom) + if(load || movable_atom.anchored) + return + + if(!isturf(movable_atom.loc)) //To prevent the loading from stuff from someone's inventory or screen icons. + return + + if(isobserver(movable_atom)) + visible_message(span_warning("A ghostly figure appears on [src]!")) + movable_atom.forceMove(src) + RegisterSignal(movable_atom, COMSIG_MOVABLE_MOVED, PROC_REF(ghost_moved)) + + else if(!wires.is_cut(WIRE_LOADCHECK)) + buzz(MULEBOT_MOOD_SIGH) + return // if not hacked, only allow ghosts to be loaded + + else if(isobj(movable_atom)) + if(movable_atom.has_buckled_mobs() || (locate(/mob) in movable_atom)) //can't load non crates objects with mobs buckled to it or inside it. + buzz(MULEBOT_MOOD_SIGH) + return + + if(istype(movable_atom, /obj/structure/closet/crate)) + var/obj/structure/closet/crate/crate = movable_atom + crate.close() //make sure it's closed + + movable_atom.forceMove(src) + + else if(isliving(movable_atom) && !load_mob(movable_atom)) + return + + load = movable_atom + update_bot_mode(new_mode = BOT_IDLE) + update_appearance() + +///Handles the ghosts moving out from the mule +/mob/living/basic/bot/mulebot/paranormal/proc/ghost_moved() + SIGNAL_HANDLER + visible_message(span_notice("The ghostly figure vanishes...")) + UnregisterSignal(load, COMSIG_MOVABLE_MOVED) + unload() diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_tool_interactions.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_tool_interactions.dm new file mode 100644 index 00000000000..d80cc520bbb --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_tool_interactions.dm @@ -0,0 +1,46 @@ +/mob/living/basic/bot/mulebot/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + update_appearance() + +/mob/living/basic/bot/mulebot/crowbar_act(mob/living/user, obj/item/tool) + if(!(bot_access_flags & BOT_COVER_MAINTS_OPEN) || user.combat_mode) + return + if(!cell) + to_chat(user, span_warning("[src] doesn't have a power cell!")) + return ITEM_INTERACT_BLOCKING + cell.add_fingerprint(user) + user.visible_message( + span_notice("[user] crowbars [cell] out from [src]."), + span_notice("You pry [cell] out of [src]."), + ) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(cell) + else + cell.forceMove(drop_location()) + return ITEM_INTERACT_SUCCESS + +/mob/living/basic/bot/mulebot/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/stock_parts/power_store/cell) && (bot_access_flags & BOT_COVER_MAINTS_OPEN)) + if(cell) + to_chat(user, span_warning("[src] already has a power cell!")) + return ITEM_INTERACT_BLOCKING + if(!user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING + user.visible_message( + span_notice("[user] inserts \a [cell] into [src]."), + span_notice("You insert [cell] into [src]."), + ) + return ITEM_INTERACT_SUCCESS + if(is_wire_tool(tool) && (bot_access_flags & BOT_COVER_MAINTS_OPEN)) + attack_hand(user) + return ITEM_INTERACT_SUCCESS + return ..() + + +/mob/living/basic/bot/mulebot/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(!(bot_access_flags & BOT_COVER_EMAGGED)) + return + flick("[base_icon_state]-emagged", src) + playsound(src, SFX_SPARKS, 100, FALSE, SHORT_RANGE_SOUND_EXTRARANGE) + return TRUE diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot.dm b/code/modules/mob/living/basic/bots/repairbot/repairbot.dm index 8cc3a90a336..0e3647adb19 100644 --- a/code/modules/mob/living/basic/bots/repairbot/repairbot.dm +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot.dm @@ -250,7 +250,7 @@ var/obj/item/stack/rods/new_rods = new() new_rods.forceMove(src) -/mob/living/basic/bot/repairbot/turn_on() +/mob/living/basic/bot/repairbot/turn_on(mob/user) . = ..() if(!.) return diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm index 86849f1ffd9..5202159878b 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm @@ -175,12 +175,6 @@ // A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you. /datum/action/cooldown/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/basic/revenant/caster) - for(var/mob/living/simple_animal/bot/bot in victim) - if(!(bot.bot_cover_flags & BOT_COVER_EMAGGED)) - new /obj/effect/temp_visual/revenant(bot.loc) - bot.bot_cover_flags &= ~BOT_COVER_LOCKED - bot.bot_cover_flags |= BOT_COVER_MAINTS_OPEN - bot.emag_act(caster) for(var/mob/living/basic/bot/bot in victim) if(!(bot.bot_access_flags & BOT_COVER_EMAGGED)) new /obj/effect/temp_visual/revenant(bot.loc) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index aa960b3444a..0849b20eed9 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -541,13 +541,8 @@ var/mob/living/bot = bot_ref?.resolve() if(!bot) return - var/summon_success - if(isbasicbot(bot)) - var/mob/living/basic/bot/basic_bot = bot - summon_success = basic_bot.summon_bot(src, waypoint, grant_all_access = TRUE) - else - var/mob/living/simple_animal/bot/simple_bot = bot - summon_success = simple_bot.call_bot(src, waypoint) + var/mob/living/basic/bot/basic_bot = bot + var/summon_success = basic_bot.summon_bot(src, waypoint, grant_all_access = TRUE) var/chat_message = summon_success ? "Sending command to bot..." : "Interface error. Unit is already in use." to_chat(src, span_notice("[chat_message]")) diff --git a/code/modules/mob/living/silicon/ai/robot_control.dm b/code/modules/mob/living/silicon/ai/robot_control.dm index 94c99369b6c..1884804afe4 100644 --- a/code/modules/mob/living/silicon/ai/robot_control.dm +++ b/code/modules/mob/living/silicon/ai/robot_control.dm @@ -35,37 +35,22 @@ var/turf/ai_current_turf = get_turf(owner) data["robots"] = list() - for(var/mob/living/our_bot as anything in GLOB.bots_list) + for(var/mob/living/basic/bot/our_bot as anything in GLOB.bots_list) if(!isbot(our_bot) || !is_valid_z_level(ai_current_turf, get_turf(our_bot))) continue - if(isbasicbot(our_bot)) - var/mob/living/basic/bot/basic_bot = our_bot - if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) - continue - var/list/basic_bot_data = list( - name = basic_bot.name, - model = basic_bot.bot_type, - mode = basic_bot.mode, - hacked = !!(basic_bot.bot_access_flags & BOT_COVER_HACKED), - location = get_area_name(basic_bot, TRUE), - ref = REF(basic_bot), - ) - data["robots"] += list(basic_bot_data) + var/mob/living/basic/bot/basic_bot = our_bot + if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) continue - - var/mob/living/simple_animal/bot/simple_bot = our_bot - if(!(simple_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) - continue - var/list/simple_bot_data = list( - name = simple_bot.name, - model = simple_bot.bot_type, - mode = simple_bot.get_mode(), - hacked = !!(simple_bot.bot_cover_flags & BOT_COVER_HACKED), - location = get_area_name(simple_bot, TRUE), - ref = REF(simple_bot), + var/list/basic_bot_data = list( + name = basic_bot.name, + model = basic_bot.bot_type, + mode = basic_bot.mode, + hacked = !!(basic_bot.bot_access_flags & BOT_COVER_HACKED), + location = get_area_name(basic_bot, TRUE), + ref = REF(basic_bot), ) - data["robots"] += list(simple_bot_data) + data["robots"] += list(basic_bot_data) return data @@ -84,28 +69,19 @@ switch(action) if("callbot") //Command a bot to move to a selected location. - if(isbasicbot(bot)) - var/mob/living/basic/bot/basic_bot = bot - if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) - return - else - var/mob/living/simple_animal/bot/simple_bot = bot - if(!(simple_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) - return + var/mob/living/basic/bot/basic_bot = bot + if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) + return owner.bot_ref = WEAKREF(bot) owner.setting_waypoint = TRUE to_chat(our_user, span_notice("Set your waypoint by clicking on a valid location free of obstructions.")) if("interface") //Remotely connect to a bot! owner.bot_ref = WEAKREF(bot) - if(isbasicbot(bot)) - var/mob/living/basic/bot/basic_bot = bot - if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) - return - else - var/mob/living/basic/bot/simple_bot = bot - if(!(simple_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) - return + var/mob/living/basic/bot/basic_bot = bot + if(!(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) + return + bot.attack_ai(our_user) return TRUE diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm deleted file mode 100644 index 86f50e6246a..00000000000 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ /dev/null @@ -1,1231 +0,0 @@ -// AI (i.e. game AI, not the AI player) controlled bots -/mob/living/simple_animal/bot - abstract_type = /mob/living/simple_animal/bot - icon = 'icons/mob/silicon/aibots.dmi' - layer = MOB_LAYER - gender = NEUTER - mob_biotypes = MOB_ROBOTIC - stop_automated_movement = TRUE - wander = FALSE - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, STAMINA = 0, OXY = 0) - atmos_requirements = null - unsuitable_atmos_damage = 0 - hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) - maxbodytemp = INFINITY - minbodytemp = 0 - sentience_type = SENTIENCE_ARTIFICIAL - status_flags = NONE //no default canpush - pass_flags = PASSFLAPS - verb_say = "states" - verb_ask = "queries" - verb_exclaim = "declares" - verb_yell = "alarms" - initial_language_holder = /datum/language_holder/synthetic - bubble_icon = "machine" - speech_span = SPAN_ROBOT - faction = list(FACTION_NEUTRAL, FACTION_SILICON, FACTION_TURRET) - light_system = OVERLAY_LIGHT - light_range = 3 - light_power = 0.6 - del_on_death = TRUE - req_one_access = list(ACCESS_ROBOTICS) - interaction_flags_click = ALLOW_SILICON_REACH - - ///Cooldown between salutations for commissioned bots - COOLDOWN_DECLARE(next_salute_check) - - ///The Robot arm attached to this robot - has a 50% chance to drop on death. - var/robot_arm = /obj/item/bodypart/arm/right/robot - ///The inserted (if any) pAI in this bot. - var/obj/item/pai_card/paicard - ///The type of bot it is, for radio control. - var/bot_type = NONE - - ///Additonal access given to player-controlled bots. - var/list/player_access = list() - ///All initial access this bot started with. - var/list/prev_access = list() - - ///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION - var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION - - ///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_COVER_MAINTS_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED - var/bot_cover_flags = BOT_COVER_LOCKED - - ///Small name of what the bot gets messed with when getting hacked/emagged. - var/hackables = "system circuits" - ///Used by some bots for tracking failures to reach their target. - var/frustration = 0 - ///The speed at which the bot moves, or the number of times it moves per process() tick. - var/base_speed = 2 - ///The end point of a bot's path, or the target location. - var/turf/ai_waypoint - ///The bot is on a custom set path. - var/pathset = FALSE - ///List of turfs through which a bot 'steps' to reach the waypoint, associated with the path image, if there is one. - var/list/path = list() - ///List of unreachable targets for an ignore-list enabled bot to ignore. - var/list/ignore_list = list() - ///Standardizes the vars that indicate the bot is busy with its function. - var/mode = BOT_IDLE - ///Number of times the bot tried and failed to move. - var/tries = 0 - ///Links a bot to the AI calling it. - var/mob/living/silicon/ai/calling_ai - ///The bot's radio, for speaking to people. - var/obj/item/radio/internal_radio - ///which channels can the bot listen to - var/radio_key = null - ///The bot's default radio channel - var/radio_channel = RADIO_CHANNEL_COMMON - ///Turf a bot is summoned to navitage towards. - var/turf/patrol_target - ///Turf of a user summoning a bot towards their location. - var/turf/summon_target - ///Pending new destination (waiting for beacon response) - var/new_destination - ///Destination description tag - var/destination - ///The next destination in the patrol route - var/next_destination - - /// the nearest beacon's tag - var/nearest_beacon - ///The nearest beacon's location - var/turf/nearest_beacon_loc - - ///The type of data HUD the bot uses. Diagnostic by default. - var/data_hud_type = TRAIT_DIAGNOSTIC_HUD - var/datum/atom_hud/data/bot_path/private/path_hud - var/path_image_icon = 'icons/mob/silicon/aibots.dmi' - var/path_image_icon_state = "path_indicator" - var/path_image_color = COLOR_WHITE - var/reset_access_timer_id - var/ignorelistcleanuptimer = 1 // This ticks up every automated action, at 300 we clean the ignore list - - /// If true we will allow ghosts to control this mob - var/can_be_possessed = FALSE - /// If true we will offer this - COOLDOWN_DECLARE(offer_ghosts_cooldown) - /// Message to display upon possession - var/possessed_message = "You're a generic bot. How did one of these even get made?" - /// List of strings to sound effects corresponding to automated messages the bot can play - var/list/automated_announcements - /// Action we use to say voice lines out loud, also we just pass anything we try to say through here just in case it plays a voice line - var/datum/action/cooldown/bot_announcement/pa_system - // The faction of the bot before it inherited the pai's faction - var/list/original_faction - // The allies of the bot before it inherited the pai's faction - var/list/original_allies - - ///Innate access uses an internal ID card. - var/obj/item/card/id/access_card = null - -/mob/living/simple_animal/bot/proc/get_mode() - if(client) //Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player. - return paicard ? "pAI Controlled" : "Autonomous" - if(!(bot_mode_flags & BOT_MODE_ON)) - return span_bad("Inactive") - return span_average("[mode]") - -/** - * Returns a status string about the bot's current status, if it's moving, manually controlled, or idle. - */ -/mob/living/simple_animal/bot/proc/get_mode_ui() - if(client) //Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player. - return paicard ? "pAI Controlled" : "Autonomous" - if(!(bot_mode_flags & BOT_MODE_ON)) - return "Inactive" - return "[mode]" - -/** - * Returns a string of flavor text for emagged bots as defined by policy. - */ -/mob/living/simple_animal/bot/proc/get_emagged_message() - return get_policy(ROLE_EMAGGED_BOT) || "You are a malfunctioning bot! Disrupt everyone and cause chaos!" - -/mob/living/simple_animal/bot/proc/turn_on() - if(stat) - return FALSE - bot_mode_flags |= BOT_MODE_ON - remove_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), POWER_LACK_TRAIT) - set_light_on(bot_mode_flags & BOT_MODE_ON ? TRUE : FALSE) - update_appearance() - balloon_alert(src, "turned on") - diag_hud_set_botstat() - return TRUE - -/mob/living/simple_animal/bot/proc/turn_off() - bot_mode_flags &= ~BOT_MODE_ON - add_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), POWER_LACK_TRAIT) - set_light_on(bot_mode_flags & BOT_MODE_ON ? TRUE : FALSE) - bot_reset() //Resets an AI's call, should it exist. - balloon_alert(src, "turned off") - update_appearance() - -/mob/living/simple_animal/bot/proc/get_bot_flag(checked_mode, checked_flag) - if(checked_mode & checked_flag) - return TRUE - return FALSE - -/mob/living/simple_animal/bot/Initialize(mapload) - . = ..() - GLOB.bots_list += src - add_traits(list(TRAIT_SILICON_ACCESS, TRAIT_REAGENT_SCANNER, TRAIT_UNOBSERVANT), INNATE_TRAIT) - LoadComponent(/datum/component/bloodysoles/bot) - - path_hud = new /datum/atom_hud/data/bot_path/private() - for(var/hud in path_hud.hud_icons) // You get to see your own path - set_hud_image_active(hud, exclusive_hud = path_hud) - - // Give bots a fancy new ID card that can hold any access. - access_card = new /obj/item/card/id/advanced/simple_bot(src) - // This access is so bots can be immediately set to patrol and leave Robotics, instead of having to be let out first. - access_card.set_access(list(ACCESS_ROBOTICS)) - internal_radio = new /obj/item/radio(src) - if(radio_key) - internal_radio.keyslot = new radio_key - internal_radio.subspace_transmission = TRUE - internal_radio.canhear_range = 0 // anything greater will have the bot broadcast the channel as if it were saying it out loud. - internal_radio.recalculateChannels() - - //Adds bot to the diagnostic HUD system - prepare_huds() - var/datum/atom_hud/data/diagnostic/diag_hud = GLOB.huds[DATA_HUD_DIAGNOSTIC] - diag_hud.add_atom_to_hud(src) - diag_hud_set_bothealth() - diag_hud_set_botstat() - diag_hud_set_botmode() - - //If a bot has its own HUD (for player bots), provide it. - if(!isnull(data_hud_type)) - ADD_TRAIT(src, data_hud_type, INNATE_TRAIT) - if(path_hud) - path_hud.add_atom_to_hud(src) - path_hud.show_to(src) - - if(mapload && is_station_level(z) && bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT && bot_mode_flags & BOT_MODE_ROUNDSTART_POSSESSION) - enable_possession(mapload = mapload) - - pa_system = new(src, automated_announcements = automated_announcements) - pa_system.Grant(src) - RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access)) - ADD_TRAIT(src, TRAIT_SILICON_EMOTES_ALLOWED, INNATE_TRAIT) - -/mob/living/simple_animal/bot/Destroy() - GLOB.bots_list -= src - QDEL_NULL(paicard) - QDEL_NULL(pa_system) - QDEL_NULL(internal_radio) - QDEL_NULL(access_card) - QDEL_NULL(path_hud) - return ..() - -/// Allows this bot to be controlled by a ghost, who will become its mind -/mob/living/simple_animal/bot/proc/enable_possession(user, mapload = FALSE) - if (paicard) - balloon_alert(user, "already sapient!") - return - can_be_possessed = TRUE - var/can_announce = !mapload && COOLDOWN_FINISHED(src, offer_ghosts_cooldown) - AddComponent( - /datum/component/ghost_direct_control, \ - ban_type = ROLE_BOT, \ - poll_candidates = can_announce, \ - poll_ignore_key = POLL_IGNORE_BOTS, \ - assumed_control_message = (bot_cover_flags & BOT_COVER_EMAGGED) ? get_emagged_message() : possessed_message, \ - extra_control_checks = CALLBACK(src, PROC_REF(check_possession)), \ - after_assumed_control = CALLBACK(src, PROC_REF(post_possession)), \ - ) - if (can_announce) - COOLDOWN_START(src, offer_ghosts_cooldown, 30 SECONDS) - - if (user) - log_silicon("[key_name(user)] enabled sapience for [src] ([initial(src.name)])") // Not technically a silicon but who is counting - -/// Disables this bot from being possessed by ghosts -/mob/living/simple_animal/bot/proc/disable_possession(mob/user) - if (user) - log_silicon("[key_name(user)] disabled sapience for [src] ([initial(src.name)])") - can_be_possessed = FALSE - qdel(GetComponent(/datum/component/ghost_direct_control)) - if (isnull(key)) - return - if (user) - log_combat(user, src, "ejected [key_name(src)] from control of [src] ([initial(src.name)]).") - to_chat(src, span_warning("You feel yourself fade as your personality matrix is reset!")) - ghostize(can_reenter_corpse = FALSE) - playsound(src, 'sound/machines/ping.ogg', 30, TRUE) - speak("Personality matrix reset!") - key = null - -/// Returns true if this mob can be controlled -/mob/living/simple_animal/bot/proc/check_possession(mob/potential_possessor) - if (!can_be_possessed) - to_chat(potential_possessor, span_warning("The bot's personality download has been disabled!")) - return can_be_possessed - -/// Fired after something takes control of this mob -/mob/living/simple_animal/bot/proc/post_possession() - playsound(src, 'sound/machines/ping.ogg', 30, TRUE) - speak("New personality installed successfully!") - rename(src) - -/// Allows renaming the bot to something else -/mob/living/simple_animal/bot/proc/rename(mob/user) - var/new_name = sanitize_name( - reject_bad_text(tgui_input_text( - user = user, - message = "This machine is designated [real_name]. Would you like to update its registration?", - title = "Name change", - default = real_name, - max_length = MAX_NAME_LEN, - )), - allow_numbers = TRUE - ) - if (isnull(new_name) || QDELETED(src)) - return - if (key && user != src) - var/accepted = tgui_alert( - src, - message = "Do you wish to be renamed to [new_name]?", - title = "Name change", - buttons = list("Yes", "No"), - ) - if (accepted != "Yes" || QDELETED(src)) - return - fully_replace_character_name(real_name, new_name) - -/mob/living/simple_animal/bot/allowed(mob/living/user) - if(!(bot_cover_flags & BOT_COVER_LOCKED)) // Unlocked. - return TRUE - return ..() - -/mob/living/simple_animal/bot/bee_friendly() - return TRUE - -/mob/living/simple_animal/bot/death(gibbed) - if(paicard) - ejectpai() - explode() - return ..() - -/mob/living/simple_animal/bot/proc/explode() - visible_message(span_boldnotice("[src] blows apart!")) - do_sparks(3, TRUE, src) - var/atom/location_destroyed = drop_location() - if(prob(50)) - drop_part(robot_arm, location_destroyed) - -/mob/living/simple_animal/bot/emag_act(mob/user, obj/item/card/emag/emag_card) - . = ..() - if(bot_cover_flags & BOT_COVER_LOCKED) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again. - bot_cover_flags &= ~BOT_COVER_LOCKED - balloon_alert(user, "cover unlocked") - return TRUE - if(!(bot_cover_flags & BOT_COVER_LOCKED) && bot_cover_flags & BOT_COVER_MAINTS_OPEN) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. - bot_cover_flags |= BOT_COVER_EMAGGED - bot_cover_flags &= ~BOT_COVER_LOCKED //Manually emagging the bot locks out the panel. - bot_mode_flags &= ~BOT_MODE_REMOTE_ENABLED //Manually emagging the bot also locks the AI from controlling it. - bot_reset() - turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP. - to_chat(src, span_userdanger("(#$*#$^^( OVERRIDE DETECTED")) - to_chat(src, span_boldnotice(get_emagged_message())) - if(user) - log_combat(user, src, "emagged") - return TRUE - else //Bot is unlocked, but the maint panel has not been opened with a screwdriver (or through the UI) yet. - balloon_alert(user, "open maintenance panel first!") - return FALSE - -/mob/living/simple_animal/bot/examine(mob/user) - . = ..() - if(health < maxHealth) - if(health > maxHealth/3) - . += "[src]'s parts look loose." - else - . += "[src]'s parts look very loose!" - else - . += "[src] is in pristine condition." - . += span_notice("[p_Their()] maintenance panel is [bot_cover_flags & BOT_COVER_MAINTS_OPEN ? "open" : "closed"].") - . += span_info("You can use a screwdriver to [bot_cover_flags & BOT_COVER_MAINTS_OPEN ? "close" : "open"] it.") - if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) - . += span_notice("[p_Their()] control panel is [bot_cover_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"].") - var/is_sillycone = HAS_SILICON_ACCESS(user) - if(!(bot_cover_flags & BOT_COVER_EMAGGED) && (is_sillycone || user.Adjacent(src))) - . += span_info("Alt-click [is_sillycone ? "" : "or use your ID on "]it to [bot_cover_flags & BOT_COVER_LOCKED ? "un" : ""]lock [p_their()] control panel.") - if(paicard) - . += span_notice("[p_They()] has a pAI device installed.") - if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN)) - . += span_info("You can use a hemostat to remove it.") - if(access_card) - . += "There appears to be [icon2html(access_card, user)] \a [access_card] pinned to [p_them()]." - -/mob/living/simple_animal/bot/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - if(amount > 0 && prob(10)) - new /obj/effect/decal/cleanable/blood/oil(loc) - return ..() - -/mob/living/simple_animal/bot/updatehealth() - ..() - diag_hud_set_bothealth() - -/mob/living/simple_animal/bot/med_hud_set_health() - return //we use a different hud - -/mob/living/simple_animal/bot/med_hud_set_status() - return //we use a different hud - -/mob/living/simple_animal/bot/handle_automated_action() //Master process which handles code common across most bots. - diag_hud_set_botmode() - - if (ignorelistcleanuptimer % 300 == 0) // Every 300 actions, clean up the ignore list from old junk - for(var/ref in ignore_list) - var/atom/referredatom = locate(ref) - if (!referredatom || !istype(referredatom) || QDELETED(referredatom)) - ignore_list -= ref - ignorelistcleanuptimer = 1 - else - ignorelistcleanuptimer++ - - if(!(bot_mode_flags & BOT_MODE_ON) || client) - return FALSE - - if(HAS_TRAIT(src, TRAIT_COMMISSIONED) && COOLDOWN_FINISHED(src, next_salute_check)) - COOLDOWN_START(src, next_salute_check, BOT_COMMISSIONED_SALUTE_DELAY) - for(var/mob/living/simple_animal/bot/nearby_bot in view(5, src)) - if(!HAS_TRAIT(nearby_bot, TRAIT_COMMISSIONED) && nearby_bot.bot_mode_flags & BOT_MODE_ON) - manual_emote("performs an elaborate salute for [nearby_bot]!") - break - - switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command. - if(BOT_RESPONDING) //Called by the AI. - call_mode() - return FALSE - if(BOT_SUMMON) //Called to a location - summon_step() - return FALSE - return TRUE //Successful completion. Used to prevent child process() continuing if this one is ended early. - - -/mob/living/simple_animal/bot/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(!user.combat_mode) - ui_interact(user) - else - return ..() - -/mob/living/simple_animal/bot/attack_ai(mob/user) - if(!topic_denied(user)) - ui_interact(user) - else - to_chat(user, span_warning("[src]'s interface is not responding!")) - -/mob/living/simple_animal/bot/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "SimpleBot", name) - ui.open() - -/mob/living/simple_animal/bot/click_alt(mob/user) - unlock_with_id(user) - return CLICK_ACTION_SUCCESS - -/mob/living/simple_animal/bot/proc/unlock_with_id(mob/user) - if(bot_cover_flags & BOT_COVER_EMAGGED) - to_chat(user, span_danger("ERROR")) - return - if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) - to_chat(user, span_warning("Please close the access panel before [bot_cover_flags & BOT_COVER_LOCKED ? "un" : ""]locking it.")) - return - if(!allowed(user)) - to_chat(user, span_warning("Access denied.")) - return - bot_cover_flags ^= BOT_COVER_LOCKED - to_chat(user, span_notice("Controls are now [bot_cover_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"].")) - return TRUE - -/mob/living/simple_animal/bot/screwdriver_act(mob/living/user, obj/item/tool) - if(bot_cover_flags & BOT_COVER_LOCKED) - to_chat(user, span_warning("The maintenance panel is locked!")) - return ITEM_INTERACT_SUCCESS - - tool.play_tool_sound(src) - bot_cover_flags ^= BOT_COVER_MAINTS_OPEN - to_chat(user, span_notice("The maintenance panel is now [bot_cover_flags & BOT_COVER_MAINTS_OPEN ? "opened" : "closed"].")) - return ITEM_INTERACT_SUCCESS - -/mob/living/simple_animal/bot/welder_act(mob/living/user, obj/item/tool) - user.changeNext_move(CLICK_CD_MELEE) - if(user.combat_mode) - return FALSE - - if(health >= maxHealth) - to_chat(user, span_warning("[src] does not need a repair!")) - return ITEM_INTERACT_SUCCESS - if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN)) - to_chat(user, span_warning("Unable to repair with the maintenance panel closed!")) - return ITEM_INTERACT_SUCCESS - - if(tool.use_tool(src, user, 0 SECONDS, volume=40)) - adjustHealth(-10) - user.visible_message(span_notice("[user] repairs [src]!"),span_notice("You repair [src].")) - return ITEM_INTERACT_SUCCESS - -/mob/living/simple_animal/bot/attackby(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers) - if(attacking_item.GetID()) - unlock_with_id(user) - return - if(istype(attacking_item, /obj/item/pai_card)) - insertpai(user, attacking_item) - return - if(attacking_item.tool_behaviour == TOOL_HEMOSTAT && paicard) - if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) - balloon_alert(user, "open the access panel!") - else - balloon_alert(user, "removing pAI...") - if(!do_after(user, 3 SECONDS, target = src) || !paicard) - return - user.visible_message(span_notice("[user] uses [attacking_item] to pull [paicard] out of [initial(src.name)]!"),span_notice("You pull [paicard] out of [initial(src.name)] with [attacking_item].")) - ejectpai(user) - return - return ..() - -/mob/living/simple_animal/bot/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker) - if(damage_done > 0 && attacking_item.damtype != STAMINA && stat != DEAD) - do_sparks(5, TRUE, src) - . = TRUE - return ..() || . - -/mob/living/simple_animal/bot/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) - . = ..() - if(prob(25) || . != BULLET_ACT_HIT) - return - if(hitting_projectile.damage_type != BRUTE && hitting_projectile.damage_type != BURN) - return - if(!hitting_projectile.is_hostile_projectile() || hitting_projectile.damage <= 0) - return - do_sparks(5, TRUE, src) - -/mob/living/simple_animal/bot/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - var/was_on = bot_mode_flags & BOT_MODE_ON ? TRUE : FALSE - stat |= EMPED - new /obj/effect/temp_visual/emp(loc) - if(paicard) - paicard.emp_act(severity) - src.visible_message(span_notice("[paicard] is flies out of [initial(src.name)]!"), span_warning("You are forcefully ejected from [initial(src.name)]!")) - ejectpai() - - if (QDELETED(src)) - return - - if(bot_mode_flags & BOT_MODE_ON) - turn_off() - addtimer(CALLBACK(src, PROC_REF(emp_reset), was_on), severity * 30 SECONDS) - if(!prob(70/severity)) - return - if (!length(GLOB.uncommon_roundstart_languages)) - return - remove_all_languages(source = LANGUAGE_EMP) - grant_random_uncommon_language(source = LANGUAGE_EMP) - -/mob/living/simple_animal/bot/proc/emp_reset(was_on) - stat &= ~EMPED - if(was_on) - turn_on() - -/** - * Pass a message to have the bot say() it, passing through our announcement action to potentially also play a sound. - * Optionally pass a frequency to say it on the radio. - */ -/mob/living/simple_animal/bot/proc/speak(message, channel) - if(!message) - return - pa_system.announce(message, channel) - -/mob/living/simple_animal/bot/radio(message, list/message_mods = list(), list/spans, language) - . = ..() - if(.) - return - - if(message_mods[MODE_HEADSET]) - internal_radio.talk_into(src, message, , spans, language, message_mods) - return REDUCE_RANGE - else if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT) - internal_radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) - return REDUCE_RANGE - else if(message_mods[RADIO_EXTENSION] in GLOB.default_radio_channels) - internal_radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods) - return REDUCE_RANGE - -/mob/living/simple_animal/bot/proc/drop_part(obj/item/drop_item, dropzone) - var/obj/item/item_to_drop - if(ispath(drop_item)) - item_to_drop = new drop_item(dropzone) - else - item_to_drop = drop_item - item_to_drop.forceMove(dropzone) - - if(istype(item_to_drop, /obj/item/stock_parts/power_store/cell)) - var/obj/item/stock_parts/power_store/cell/dropped_cell = item_to_drop - dropped_cell.charge = 0 - - else if(istype(item_to_drop, /obj/item/storage)) - var/obj/item/storage/storage_to_drop = item_to_drop - storage_to_drop.contents = list() - - else if(istype(item_to_drop, /obj/item/gun/energy)) - var/obj/item/gun/energy/dropped_gun = item_to_drop - dropped_gun.cell.charge = 0 - dropped_gun.update_appearance() - -//Generalized behavior code, override where needed! - -GLOBAL_LIST_EMPTY(scan_typecaches) -/** - * Attempt to scan tiles near [src], first by checking adjacent, then if a target is still not found, nearby. - * - * scan_types - list (of typepaths) that nearby tiles are being scanned for. - * old_target - what has already been scanned, and will early return at checkscan. - * scan_range - how far away from [src] will be scanned, if nothing is found directly adjacent. - */ -/mob/living/simple_animal/bot/proc/scan(list/scan_types, old_target, scan_range = DEFAULT_SCAN_RANGE) - var/key = scan_types.Join(",") - var/list/scan_cache = GLOB.scan_typecaches[key] - if(!scan_cache) - scan_cache = typecacheof(scan_types) - GLOB.scan_typecaches[key] = scan_cache - if(!get_turf(src)) - return - // Nicer behavior, ensures we don't conflict with other bots quite so often - var/list/adjacent = list() - for(var/turf/to_walk in view(1, src)) - adjacent += to_walk - - adjacent = shuffle(adjacent) - - var/list/turfs_to_walk = list() - for(var/turf/victim in view(scan_range, src)) - turfs_to_walk += victim - - turfs_to_walk = turfs_to_walk - adjacent - // Now we prepend adjacent since we want to run those first - turfs_to_walk = adjacent + turfs_to_walk - - for(var/turf/scanned as anything in turfs_to_walk) - // Check bot is inlined here to save cpu time - //Is there another bot there? Then let's just skip it so we dont all atack on top of eachother. - var/bot_found = FALSE - for(var/mob/living/simple_animal/bot/buddy in scanned.contents) - if(istype(buddy, type) && (buddy != src)) - bot_found = TRUE - break - if(bot_found) - continue - - for(var/atom/thing as anything in scanned) - if(!scan_cache[thing.type]) //Check that the thing we found is the type we want! - continue //If not, keep searching! - if(thing == old_target || (REF(thing) in ignore_list)) //Filter for blacklisted elements, usually unreachable or previously processed oness - continue - - var/scan_result = process_scan(thing) //Some bots may require additional processing when a result is selected. - if(!isnull(scan_result)) - return scan_result - -//When the scan finds a target, run bot specific processing to select it for the next step. Empty by default. -/mob/living/simple_animal/bot/proc/process_scan(scan_target) - return scan_target - -/mob/living/simple_animal/bot/proc/check_bot(targ) - var/turf/target_turf = get_turf(targ) - if(!target_turf) - return FALSE - for(var/mob/living/simple_animal/bot/buddy in target_turf.contents) - if(istype(buddy, type) && (buddy != src)) - return TRUE - return FALSE - -/mob/living/simple_animal/bot/proc/add_to_ignore(subject) - if(ignore_list.len < 50) //This will help keep track of them, so the bot is always trying to reach a blocked spot. - ignore_list += REF(subject) - else //If the list is full, insert newest, delete oldest. - ignore_list.Cut(1,2) - ignore_list += REF(subject) - -/* -Movement proc for stepping a bot through a path generated through A-star. -Pass a positive integer as an argument to override a bot's default speed. -*/ -/mob/living/simple_animal/bot/proc/bot_move(dest, move_speed) - if(!dest || !path || path.len == 0) //A-star failed or a path/destination was not set. - set_path(null) - return FALSE - dest = get_turf(dest) //We must always compare turfs, so get the turf of the dest var if dest was originally something else. - var/turf/last_node = get_turf(path[path.len]) //This is the turf at the end of the path, it should be equal to dest. - if(get_turf(src) == dest) //We have arrived, no need to move again. - return TRUE - else if(dest != last_node) //The path should lead us to our given destination. If this is not true, we must stop. - set_path(null) - return FALSE - var/step_count = move_speed ? move_speed : base_speed //If a value is passed into move_speed, use that instead of the default speed var. - - if(step_count >= 1 && tries < BOT_STEP_MAX_RETRIES) - for(var/step_number in 1 to step_count) - addtimer(CALLBACK(src, PROC_REF(bot_step)), BOT_STEP_DELAY*(step_number-1)) - else - return FALSE - return TRUE - -/// Performs a step_towards and increments the path if successful. Returns TRUE if the bot moved and FALSE otherwise. -/mob/living/simple_animal/bot/proc/bot_step() - if(!length(path)) - return FALSE - - if(SEND_SIGNAL(src, COMSIG_MOB_BOT_PRE_STEP) & COMPONENT_MOB_BOT_BLOCK_PRE_STEP) - return FALSE - - if(!step_towards(src, path[1])) - tries++ - return FALSE - - increment_path() - tries = 0 - SEND_SIGNAL(src, COMSIG_MOB_BOT_STEP) - return TRUE - - -/mob/living/simple_animal/bot/proc/check_bot_access() - if(mode != BOT_SUMMON && mode != BOT_RESPONDING) - access_card.set_access(prev_access) - -/mob/living/simple_animal/bot/proc/call_bot(summoner, turf/waypoint, message = TRUE) - if(isAI(summoner) && calling_ai && calling_ai != src) //Prevents an override if another AI is controlling this bot. - return FALSE - - bot_reset() //Reset a bot before setting it to call mode. - - set_path(get_path_to(src, waypoint, max_distance=200, access = REGION_ACCESS_ALL_STATION)) - calling_ai = summoner //Link the AI to the bot! - ai_waypoint = waypoint - - if(path?.len) //Ensures that a valid path is calculated! - var/end_area = get_area_name(waypoint) - if(!(bot_mode_flags & BOT_MODE_ON)) - turn_on() //Saves the AI the hassle of having to activate a bot manually. - access_card.set_access(REGION_ACCESS_ALL_STATION) //Give the bot all-access while under the AI's command. - if(client) - reset_access_timer_id = addtimer(CALLBACK (src, PROC_REF(bot_reset)), 60 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time - to_chat(src, span_notice("[span_big("Priority waypoint set by [icon2html(calling_ai, src)] [summoner]. Proceed to [end_area].")]
[path.len-1] meters to destination. You have been granted additional door access for 60 seconds.")) - if(message) - to_chat(calling_ai, span_notice("[icon2html(src, calling_ai)] [name] called to [end_area]. [path.len-1] meters to destination.")) - pathset = TRUE - mode = BOT_RESPONDING - tries = 0 - else - if(message) - to_chat(calling_ai, span_danger("Failed to calculate a valid route. Ensure destination is clear of obstructions and within range.")) - calling_ai = null - set_path(null) - - return TRUE - -/mob/living/simple_animal/bot/proc/call_mode() //Handles preparing a bot for a call, as well as calling the move proc. -//Handles the bot's movement during a call. - var/success = bot_move(ai_waypoint, 3) - if(!success) - if(calling_ai) - to_chat(calling_ai, "[icon2html(src, calling_ai)] [get_turf(src) == ai_waypoint ? span_notice("[src] successfully arrived to waypoint.") : span_danger("[src] failed to reach waypoint.")]") - calling_ai = null - bot_reset() - -/mob/living/simple_animal/bot/proc/bot_reset() - if(calling_ai) //Simple notification to the AI if it called a bot. It will not know the cause or identity of the bot. - to_chat(calling_ai, span_danger("Call command to a bot has been reset.")) - calling_ai = null - if(reset_access_timer_id) - deltimer(reset_access_timer_id) - reset_access_timer_id = null - set_path(null) - summon_target = null - pathset = FALSE - access_card.set_access(prev_access) - tries = 0 - mode = BOT_IDLE - ignore_list = list() - diag_hud_set_botstat() - diag_hud_set_botmode() - - - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -//Patrol and summon code! -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -/mob/living/simple_animal/bot/proc/bot_patrol() - patrol_step() - addtimer(CALLBACK(src, PROC_REF(do_patrol)), 0.5 SECONDS) - -/mob/living/simple_animal/bot/proc/do_patrol() - if(mode == BOT_PATROL) - patrol_step() - -/mob/living/simple_animal/bot/proc/start_patrol() - - if(tries >= BOT_STEP_MAX_RETRIES) //Bot is trapped, so stop trying to patrol. - bot_mode_flags &= ~BOT_MODE_AUTOPATROL - tries = 0 - speak("Unable to start patrol.") - - return - - if(!(bot_mode_flags & BOT_MODE_AUTOPATROL)) //A bot not set to patrol should not be patrolling. - mode = BOT_IDLE - return - - if(patrol_target) // has patrol target - INVOKE_ASYNC(src, PROC_REF(target_patrol)) - else // no patrol target, so need a new one - speak("Engaging patrol mode.") - find_patrol_target() - tries++ - return - -/mob/living/simple_animal/bot/proc/target_patrol() - calc_path() // Find a route to it - if(!path.len) - patrol_target = null - return - mode = BOT_PATROL -// perform a single patrol step - -/mob/living/simple_animal/bot/proc/patrol_step() - - if(client) // In use by player, don't actually move. - return - - if(loc == patrol_target) // reached target - //Find the next beacon matching the target. - if(!get_next_patrol_target()) - find_patrol_target() //If it fails, look for the nearest one instead. - return - - else if(path.len > 0 && patrol_target) // valid path - if(path[1] == loc) - increment_path() - return - - - var/moved = bot_move(patrol_target)//step_towards(src, next) // attempt to move - if(!moved) //Couldn't proceed the next step of the path BOT_STEP_MAX_RETRIES times - addtimer(CALLBACK(src, PROC_REF(patrol_step_not_moved)), 0.2 SECONDS) - - else // no path, so calculate new one - mode = BOT_START_PATROL - -/mob/living/simple_animal/bot/proc/patrol_step_not_moved() - calc_path() - if(!length(path)) - find_patrol_target() - tries = 0 - -// finds the nearest beacon to self -/mob/living/simple_animal/bot/proc/find_patrol_target() - nearest_beacon = null - new_destination = null - find_nearest_beacon() - if(nearest_beacon) - patrol_target = nearest_beacon_loc - destination = next_destination - else - bot_mode_flags &= ~BOT_MODE_AUTOPATROL - mode = BOT_IDLE - speak("Disengaging patrol mode.") - -/mob/living/simple_animal/bot/proc/get_next_patrol_target() - // search the beacon list for the next target in the list. - for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"]) - if(NB.location == next_destination) //Does the Beacon location text match the destination? - destination = new_destination //We now know the name of where we want to go. - patrol_target = NB.loc //Get its location and set it as the target. - next_destination = NB.codes[NAVBEACON_PATROL_NEXT] //Also get the name of the next beacon in line. - return TRUE - -/mob/living/simple_animal/bot/proc/find_nearest_beacon() - for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"]) - var/dist = get_dist(src, NB) - if(nearest_beacon) //Loop though the beacon net to find the true closest beacon. - //Ignore the beacon if were are located on it. - if(dist>1 && dist 1) //Begin the search, save this one for comparison on the next loop. - nearest_beacon = NB.location - nearest_beacon_loc = NB.loc - patrol_target = nearest_beacon_loc - destination = nearest_beacon - -//PDA control. Some bots, especially MULEs, may have more parameters. -/mob/living/simple_animal/bot/proc/bot_control(command, mob/user, list/user_access = list()) - if(!(bot_mode_flags & BOT_MODE_ON) || bot_cover_flags & BOT_COVER_EMAGGED || !(bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands. - return TRUE //ACCESS DENIED - if(client) - bot_control_message(command, user) - // process control input - switch(command) - if("patroloff") - bot_reset() //HOLD IT!! //OBJECTION!! - bot_mode_flags &= ~BOT_MODE_AUTOPATROL - - if("patrolon") - bot_mode_flags |= BOT_MODE_AUTOPATROL - - if("summon") - bot_reset() - summon_target = get_turf(user) - if(user_access.len != 0) - access_card.set_access(user_access + prev_access) //Adds the user's access, if any. - mode = BOT_SUMMON - speak("Responding.", radio_channel) - if("ejectpai") - ejectpairemote(user) - return - - -/mob/living/simple_animal/bot/proc/bot_control_message(command, user) - switch(command) - if("patroloff") - to_chat(src, "STOP PATROL") - if("patrolon") - to_chat(src, "START PATROL") - if("summon") - to_chat(src, "PRIORITY ALERT:[user] in [get_area_name(user)]!") - if("stop") - to_chat(src, "STOP!") - - if("go") - to_chat(src, "GO!") - - if("home") - to_chat(src, "RETURN HOME!") - if("ejectpai") - return - else - to_chat(src, span_warning("Unidentified control sequence received:[command]")) - -// calculates a path to the current destination -// given an optional turf to avoid -/mob/living/simple_animal/bot/proc/calc_path(turf/avoid) - check_bot_access() - set_path(get_path_to(src, patrol_target, max_distance=120, access=access_card.GetAccess(), exclude=avoid, diagonal_handling=DIAGONAL_REMOVE_ALL)) - -/mob/living/simple_animal/bot/proc/calc_summon_path(turf/avoid) - check_bot_access() - var/datum/callback/path_complete = CALLBACK(src, PROC_REF(on_summon_path_finish)) - SSpathfinder.pathfind(src, summon_target, max_distance=150, access=access_card.GetAccess(), exclude=avoid, diagonal_handling=DIAGONAL_REMOVE_ALL, on_finish=list(path_complete)) - -/mob/living/simple_animal/bot/proc/on_summon_path_finish(list/path) - set_path(path) - if(!length(path)) //Cannot reach target. Give up and announce the issue. - speak("Summon command failed, destination unreachable.",radio_channel) - bot_reset() - -/mob/living/simple_animal/bot/proc/summon_step() - - if(client) // In use by player, don't actually move. - return - - if(loc == summon_target) // Arrived to summon location. - bot_reset() - return - - else if(path.len > 0 && summon_target) //Proper path acquired! - if(path[1] == loc) - increment_path() - return - - var/moved = bot_move(summon_target, 3) // Move attempt - if(!moved) - addtimer(CALLBACK(src, PROC_REF(summon_step_not_moved)), 0.2 SECONDS) - - else // no path, so calculate new one - calc_summon_path() - -/mob/living/simple_animal/bot/proc/summon_step_not_moved() - calc_summon_path() - tries = 0 - -/mob/living/simple_animal/bot/proc/attempt_access(mob/bot, obj/door_attempt) - SIGNAL_HANDLER - - if(door_attempt.check_access(access_card)) - frustration = 0 - return ACCESS_ALLOWED - return ACCESS_DISALLOWED - -/mob/living/simple_animal/bot/ui_data(mob/user) - var/list/data = list() - data["can_hack"] = HAS_SILICON_ACCESS(user) - data["custom_controls"] = list() - data["emagged"] = bot_cover_flags & BOT_COVER_EMAGGED - data["has_access"] = allowed(user) - data["locked"] = bot_cover_flags & BOT_COVER_LOCKED - data["settings"] = list() - if(!(bot_cover_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) - data["settings"]["pai_inserted"] = !!paicard - data["settings"]["allow_possession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT - data["settings"]["possession_enabled"] = can_be_possessed - data["settings"]["airplane_mode"] = !(bot_mode_flags & BOT_MODE_REMOTE_ENABLED) - data["settings"]["maintenance_lock"] = !(bot_cover_flags & BOT_COVER_MAINTS_OPEN) - data["settings"]["power"] = bot_mode_flags & BOT_MODE_ON - data["settings"]["patrol_station"] = bot_mode_flags & BOT_MODE_AUTOPATROL - return data - -// Actions received from TGUI -/mob/living/simple_animal/bot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if(.) - return - var/mob/user = ui.user - if(!allowed(user)) - to_chat(user, span_warning("Access denied.")) - return - - if(action == "lock") - bot_cover_flags ^= BOT_COVER_LOCKED - - switch(action) - if("power") - if(bot_mode_flags & BOT_MODE_ON) - turn_off() - else - turn_on() - if("maintenance") - bot_cover_flags ^= BOT_COVER_MAINTS_OPEN - if("patrol") - bot_mode_flags ^= BOT_MODE_AUTOPATROL - bot_reset() - if("airplane") - bot_mode_flags ^= BOT_MODE_REMOTE_ENABLED - if("hack") - if(!HAS_SILICON_ACCESS(user)) - return - if(!(bot_cover_flags & BOT_COVER_EMAGGED)) - bot_cover_flags |= (BOT_COVER_EMAGGED|BOT_COVER_HACKED|BOT_COVER_LOCKED) - to_chat(user, span_warning("You overload [src]'s [hackables].")) - message_admins("Safety lock of [ADMIN_LOOKUPFLW(src)] was disabled by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(src)]") - user.log_message("disabled safety lock of [src]", LOG_GAME) - bot_reset() - to_chat(src, span_userdanger("(#$*#$^^( OVERRIDE DETECTED")) - to_chat(src, span_boldnotice(get_emagged_message())) - return - if(!(bot_cover_flags & BOT_COVER_HACKED)) - to_chat(user, span_bolddanger("You fail to repair [src]'s [hackables].")) - return - bot_cover_flags &= ~(BOT_COVER_EMAGGED|BOT_COVER_HACKED) - to_chat(user, span_notice("You reset the [src]'s [hackables].")) - 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(user, span_notice("You eject [paicard] from [initial(src.name)].")) - ejectpai(user) - if("toggle_personality") - if (can_be_possessed) - disable_possession(user) - else - enable_possession(user) - if("rename") - rename(user) - -/mob/living/simple_animal/bot/update_icon_state() - icon_state = "[isnull(base_icon_state) ? initial(icon_state) : base_icon_state][get_bot_flag(bot_mode_flags, BOT_MODE_ON)]" - return ..() - -/// Access check proc for bot topics! Remember to place in a bot's individual Topic if desired. -/mob/living/simple_animal/bot/proc/topic_denied(mob/user) - if(!user.can_perform_action(src, ALLOW_SILICON_REACH)) - return TRUE - // 0 for access, 1 for denied. - if(bot_cover_flags & BOT_COVER_EMAGGED) //An emagged bot cannot be controlled by humans, silicons can if one hacked it. - if(!(bot_cover_flags & BOT_COVER_HACKED)) //Manually emagged by a human - access denied to all. - return TRUE - else if(!HAS_SILICON_ACCESS(user)) //Bot is hacked, so only silicons and admins are allowed access. - return TRUE - return FALSE - -/// Places a pAI in control of this mob -/mob/living/simple_animal/bot/proc/insertpai(mob/user, obj/item/pai_card/card) - if(paicard) - balloon_alert(user, "slot occupied!") - return - if(key) - balloon_alert(user, "personality already present!") - return - if(bot_cover_flags & BOT_COVER_LOCKED || !(bot_cover_flags & BOT_COVER_MAINTS_OPEN)) - balloon_alert(user, "slot inaccessible!") - return - if(!(bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT)) - balloon_alert(user, "incompatible firmware!") - return - if(!card.pai || !card.pai.mind) - balloon_alert(user, "pAI is inactive!") - return - if(!user.transferItemToLoc(card, src)) - return - paicard = card - disable_possession() - if(paicard.pai.holoform) - paicard.pai.fold_in() - copy_languages(paicard.pai, source_override = LANGUAGE_PAI) - set_active_language(paicard.pai.get_selected_language()) - user.visible_message(span_notice("[user] inserts [card] into [src]!"), span_notice("You insert [card] into [src].")) - paicard.pai.mind.transfer_to(src) - to_chat(src, span_notice("You sense your form change as you are uploaded into [src].")) - name = paicard.pai.name - original_faction = get_faction() - original_allies = allies - SET_FACTION_AND_ALLIES_FROM(src, user) - log_combat(user, paicard.pai, "uploaded to [initial(src.name)],") - return TRUE - -/mob/living/simple_animal/bot/ghost() - if(stat != DEAD) // Only ghost if we're doing this while alive, the pAI probably isn't dead yet. - return ..() - if(paicard && (!client || stat == DEAD)) - ejectpai() - -/// Ejects a pAI from this bot -/mob/living/simple_animal/bot/proc/ejectpai(mob/user = null, announce = TRUE) - if(!paicard) - return - if(mind && paicard.pai) - mind.transfer_to(paicard.pai) - else if(paicard.pai) - paicard.pai.PossessByPlayer(key) - else - ghostize(FALSE) // The pAI card that just got ejected was dead. - key = null - paicard.forceMove(loc) - if(user) - log_combat(user, paicard.pai, "ejected from [initial(src.name)],") - else - log_combat(src, paicard.pai, "ejected") - if(announce) - to_chat(paicard.pai, span_notice("You feel your control fade as [paicard] ejects from [initial(src.name)].")) - paicard = null - name = initial(src.name) - set_faction(original_faction) - set_allies(original_allies) - remove_all_languages(source = LANGUAGE_PAI) - get_selected_language() - -/// Ejects the pAI remotely. -/mob/living/simple_animal/bot/proc/ejectpairemote(mob/user) - if(!allowed(user) || !paicard) - return - speak("Ejecting personality chip.", radio_channel) - ejectpai(user) - -/mob/living/simple_animal/bot/Login() - . = ..() - if(!. || !client) - return FALSE - // If we have any bonus player accesses, add them to our internal ID card. - if(length(player_access)) - access_card.add_access(player_access) - diag_hud_set_botmode() - -/mob/living/simple_animal/bot/Logout() - . = ..() - bot_reset() - -/mob/living/simple_animal/bot/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE) - . = ..() - if(!.) - return - update_appearance() - -/mob/living/simple_animal/bot/sentience_act() - remove_faction(FACTION_SILICON) - -/mob/living/simple_animal/bot/proc/set_path(list/newpath) - path = newpath ? newpath : list() - if(!path_hud) - return - var/list/path_huds_watching_me = list(GLOB.huds[DATA_HUD_DIAGNOSTIC], GLOB.huds[DATA_HUD_BOT_PATH]) - if(path_hud) - path_huds_watching_me += path_hud - for(var/datum/atom_hud/hud as anything in path_huds_watching_me) - hud.remove_atom_from_hud(src) - - var/list/path_images = active_hud_list[DIAG_PATH_HUD] - LAZYCLEARLIST(path_images) - if(length(newpath)) - var/mutable_appearance/path_image = mutable_appearance(path_image_icon, path_image_icon_state, BOT_PATH_LAYER, appearance_flags = RESET_COLOR|RESET_TRANSFORM|KEEP_APART) - path_image.color = path_image_color - for(var/i in 1 to newpath.len) - var/turf/T = newpath[i] - if(T == loc) //don't bother putting an image if it's where we already exist. - continue - var/direction = get_dir(src, T) - if(i > 1) - var/turf/prevT = path[i - 1] - var/image/prevI = path[prevT] - direction = get_dir(prevT, T) - if(i > 2 && prevI) // make sure we actually have an image to manipulate at index > 2 - var/turf/prevprevT = path[i - 2] - var/prevDir = get_dir(prevprevT, prevT) - var/mixDir = direction|prevDir - if(ISDIAGONALDIR(mixDir)) - prevI.dir = mixDir - if(prevDir & (NORTH|SOUTH)) - var/matrix/ntransform = matrix() - ntransform.Turn(90) - if((mixDir == NORTHWEST) || (mixDir == SOUTHEAST)) - ntransform.Scale(-1, 1) - else - ntransform.Scale(1, -1) - prevI.transform = ntransform - - SET_PLANE(path_image, GAME_PLANE, T) - path_image.dir = direction - var/image/I = image(loc = T) - I.appearance = path_image - path[T] = I - path_images += I - - for(var/datum/atom_hud/hud as anything in path_huds_watching_me) - hud.add_atom_to_hud(src) - -/mob/living/simple_animal/bot/proc/increment_path() - if(!length(path)) - return - var/image/I = path[path[1]] - if(I) - animate(I, alpha = 0, time = 3) - path.Cut(1, 2) - - if(!length(path)) - addtimer(CALLBACK(src, PROC_REF(set_path), null), 0.6 SECONDS) // Enough time for the animate to finish - -/mob/living/simple_animal/bot/rust_heretic_act() - adjust_brute_loss(400) - -/mob/living/simple_animal/bot/get_hit_area_message(input_area) - // we just get hit, there's no complexity for hitting an arm (if it exists) or anything. - // we also need to return an empty string as otherwise it would falsely say that we get hit in the chest or something strange like that (bots don't have "chests") - return "" - -//Will always check hands first, because access_card is internal to the mob and can't be removed or swapped. -/mob/living/simple_animal/bot/get_idcard(hand_first) - return (..() || access_card) diff --git a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm index 0a9f8d8d59c..10161dddfd9 100644 --- a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm +++ b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm @@ -51,7 +51,7 @@ /datum/action/cooldown/bot_announcement/ui_data(mob/user) var/list/data = list() - var/mob/living/simple_animal/bot/bot_owner = owner + var/mob/living/basic/bot/bot_owner = owner if(istype(bot_owner)) var/list/channels = list() for(var/channel in bot_owner.internal_radio.channels) @@ -149,7 +149,7 @@ /// Speak the provided line on the provided radio channel /datum/action/cooldown/bot_announcement/proc/announce(line, channel) - var/mob/living/simple_animal/bot/bot_owner = owner + var/mob/living/basic/bot/bot_owner = owner if (!(bot_owner.bot_mode_flags & BOT_MODE_ON)) return diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm deleted file mode 100644 index 5095a1e4baa..00000000000 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ /dev/null @@ -1,873 +0,0 @@ - - -// Mulebot - carries crates around for Quartermaster -// Navigates via floor navbeacons -// Remote Controlled from QM's PDA - -#define SIGH 0 -#define ANNOYED 1 -#define DELIGHT 2 -#define CHIME 3 - -/mob/living/simple_animal/bot/mulebot - name = "\improper MULEbot" - desc = "A Multiple Utility Load Effector bot." - icon_state = "mulebot0" - light_color = "#ffcc99" - light_power = 0.8 - density = TRUE - move_resist = MOVE_FORCE_STRONG - animate_movement = SLIDE_STEPS - health = 50 - maxHealth = 50 - speed = 3 - damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, STAMINA = 0, OXY = 0) - combat_mode = TRUE //No swapping - buckle_lying = 0 - mob_size = MOB_SIZE_LARGE - buckle_prevents_pull = TRUE // No pulling loaded shit - bot_mode_flags = ~BOT_MODE_ROUNDSTART_POSSESSION - req_one_access = list(ACCESS_ROBOTICS, ACCESS_CARGO) - radio_key = /obj/item/encryptionkey/headset_cargo - radio_channel = RADIO_CHANNEL_SUPPLY - bot_type = MULE_BOT - path_image_color = "#7F5200" - possessed_message = "You are a MULEbot! Do your best to make sure that packages get to their destination!" - - /// unique identifier in case there are multiple mulebots. - var/id - - var/base_icon = "mulebot" /// icon_state to use in update_icon_state - var/atom/movable/load /// what we're transporting - var/mob/living/passenger /// who's riding us - var/turf/target /// this is turf to navigate to (location of beacon) - var/loaddir = 0 /// this the direction to unload onto/load from - var/home_destination = "" /// tag of home delivery beacon - - var/reached_target = TRUE ///true if already reached the target - ///Number of times retried a blocked path - var/blockcount = 0 - - ///flags of mulebot mode - var/mulebot_delivery_flags = MULEBOT_RETURN_MODE | MULEBOT_AUTO_PICKUP_MODE | MULEBOT_REPORT_DELIVERY_MODE - - var/obj/item/stock_parts/power_store/cell /// Internal Powercell - var/cell_move_power_usage = 1///How much power we use when we move. - var/num_steps = 0 ///The amount of steps we should take until we rest for a time. - - - -/mob/living/simple_animal/bot/mulebot/Initialize(mapload) - . = ..() - - RegisterSignal(src, COMSIG_MOB_BOT_PRE_STEP, PROC_REF(check_pre_step)) - RegisterSignal(src, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(check_pre_step)) - RegisterSignal(src, COMSIG_MOB_BOT_STEP, PROC_REF(on_bot_step)) - RegisterSignal(src, COMSIG_MOB_CLIENT_MOVED, PROC_REF(on_bot_step)) - - ADD_TRAIT(src, TRAIT_NOMOBSWAP, INNATE_TRAIT) - - if(prob(0.666) && mapload) - new /mob/living/simple_animal/bot/mulebot/paranormal(loc) - return INITIALIZE_HINT_QDEL - set_wires(new /datum/wires/mulebot(src)) - - // Doing this hurts my soul, but simplebot access reworks are for another day. - var/datum/id_trim/job/cargo_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/cargo_technician] - access_card.add_access(cargo_trim.access + cargo_trim.wildcard_access) - prev_access = access_card.access.Copy() - - cell = new /obj/item/stock_parts/power_store/cell/upgraded(src, 2000) - - AddElement(/datum/element/ridable, /datum/component/riding/creature/mulebot) - diag_hud_set_mulebotcell() - - set_id(suffix || assign_random_name()) - suffix = null - if(name == "\improper MULEbot") - name = "\improper MULEbot [id]" - set_home(get_turf(src)) - -/mob/living/simple_animal/bot/mulebot/Exited(atom/movable/gone, direction) - . = ..() - if(gone == load) - unload(0) - if(gone == cell) - turn_off() - cell = null - diag_hud_set_mulebotcell() - -/mob/living/simple_animal/bot/mulebot/examine(mob/user) - . = ..() - if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) - if(cell) - . += span_notice("[p_They()] [p_have()] \a [cell] installed.") - . += span_info("You can use a crowbar to remove it.") - else - . += span_notice("[p_They()] [p_have()] an empty compartment where a power cell can be installed.") - if(load) //observer check is so we don't show the name of the ghost that's sitting on it to prevent metagaming who's ded. - . += span_notice("\A [isobserver(load) ? "ghostly figure" : load] is on [p_their()] load platform.") - - -/mob/living/simple_animal/bot/mulebot/Destroy() - UnregisterSignal(src, list(COMSIG_MOB_BOT_PRE_STEP, COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_MOB_BOT_STEP, COMSIG_MOB_CLIENT_MOVED)) - unload(0) - QDEL_NULL(cell) - return ..() - -/mob/living/simple_animal/bot/mulebot/get_cell() - return cell - -/mob/living/simple_animal/bot/mulebot/turn_on() - if(!has_power()) - return - return ..() - -/// returns true if the bot is fully powered. -/mob/living/simple_animal/bot/mulebot/proc/has_power() - return cell && cell.charge > 0 && (!wires.is_cut(WIRE_POWER1) && !wires.is_cut(WIRE_POWER2)) - -/mob/living/simple_animal/bot/mulebot/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(bot_cover_flags & BOT_COVER_MAINTS_OPEN && !HAS_AI_ACCESS(user)) - wires.interact(user) - return - if(wires.is_cut(WIRE_RX) && HAS_AI_ACCESS(user)) - return - - return ..() - -/mob/living/simple_animal/bot/mulebot/proc/set_id(new_id) - id = new_id - -/mob/living/simple_animal/bot/mulebot/proc/set_home(turf/home_loc) - if(!istype(home_loc)) - CRASH("MULEbot [id] was requested to set a home location to [home_loc ? "an invalid home loc ([home_loc.type])" : "null"]") - - var/obj/machinery/navbeacon/home_beacon = locate() in home_loc - if(!isnull(home_beacon)) - home_destination = home_beacon.location - log_transport("[id]: MULEbot successfuly set home location to ID [home_destination] at [home_beacon.x], [home_beacon.y], [home_beacon.z]") - return - - log_transport("[id]: MULEbot failed to set home at [home_loc.x], [home_loc.y], [home_loc.z]") - -/mob/living/simple_animal/bot/mulebot/bot_reset() - ..() - reached_target = FALSE - -/mob/living/simple_animal/bot/mulebot/screwdriver_act(mob/living/user, obj/item/tool) - . = ..() - update_appearance() - -/mob/living/simple_animal/bot/mulebot/crowbar_act(mob/living/user, obj/item/tool) - if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN) || user.combat_mode) - return - if(!cell) - to_chat(user, span_warning("[src] doesn't have a power cell!")) - return ITEM_INTERACT_BLOCKING - cell.add_fingerprint(user) - user.visible_message( - span_notice("[user] crowbars [cell] out from [src]."), - span_notice("You pry [cell] out of [src]."), - ) - if(Adjacent(user) && !issilicon(user)) - user.put_in_hands(cell) - else - cell.forceMove(drop_location()) - return ITEM_INTERACT_SUCCESS - -/mob/living/simple_animal/bot/mulebot/item_interaction(mob/living/user, obj/item/tool, list/modifiers) - if(istype(tool, /obj/item/stock_parts/power_store/cell) && (bot_cover_flags & BOT_COVER_MAINTS_OPEN)) - if(cell) - to_chat(user, span_warning("[src] already has a power cell!")) - return ITEM_INTERACT_BLOCKING - if(!user.transferItemToLoc(tool, src)) - return ITEM_INTERACT_BLOCKING - cell = tool - diag_hud_set_mulebotcell() - user.visible_message( - span_notice("[user] inserts \a [cell] into [src]."), - span_notice("You insert [cell] into [src]."), - ) - return ITEM_INTERACT_SUCCESS - if(is_wire_tool(tool) && (bot_cover_flags & BOT_COVER_MAINTS_OPEN)) - attack_hand(user) - return ITEM_INTERACT_SUCCESS - return ..() - -/mob/living/simple_animal/bot/mulebot/attackby(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers) - . = ..() - if(ismob(load) && prob(1 + attacking_item.force * 2)) - user.visible_message( - span_danger("[user] knocks [load] off [src] with \the [attacking_item]!"), - span_danger("You knock [load] off [src] with \the [attacking_item]!"), - ) - unload(0) - return TRUE - -/mob/living/simple_animal/bot/mulebot/emag_act(mob/user, obj/item/card/emag/emag_card) - if(!(bot_cover_flags & BOT_COVER_EMAGGED)) - bot_cover_flags |= BOT_COVER_EMAGGED - if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN)) - bot_cover_flags ^= BOT_COVER_LOCKED - balloon_alert(user, "controls [bot_cover_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"]") - flick("[base_icon]-emagged", src) - playsound(src, SFX_SPARKS, 100, FALSE, SHORT_RANGE_SOUND_EXTRARANGE) - return TRUE - -/mob/living/simple_animal/bot/mulebot/update_icon_state() //if you change the icon_state names, please make sure to update /datum/wires/mulebot/on_pulse() as well. <3 - . = ..() - icon_state = "[base_icon][(bot_mode_flags & BOT_MODE_ON) ? wires.is_cut(WIRE_AVOIDANCE) : 0]" - -/mob/living/simple_animal/bot/mulebot/update_overlays() - . = ..() - if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) - . += "[base_icon]-hatch" - if(!load || ismob(load)) //mob offsets and such are handled by the riding component / buckling - return - var/mutable_appearance/load_overlay = mutable_appearance(load.icon, load.icon_state, layer + 0.01) - load_overlay.pixel_z = initial(load.pixel_z) + 11 - . += load_overlay - -/mob/living/simple_animal/bot/mulebot/ex_act(severity) - unload(0) - switch(severity) - if(EXPLODE_DEVASTATE) - qdel(src) - if(EXPLODE_HEAVY) - wires.cut_random() - wires.cut_random() - if(EXPLODE_LIGHT) - wires.cut_random() - - return TRUE - - -/mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/proj) - . = ..() - if(. && !QDELETED(src)) //Got hit and not blown up yet. - if(prob(50) && !isnull(load)) - unload(0) - if(prob(25)) - visible_message(span_danger("Something shorts out inside [src]!")) - wires.cut_random(source = proj.firer) - -/mob/living/simple_animal/bot/mulebot/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "Mule", name) - ui.open() - -/mob/living/simple_animal/bot/mulebot/ui_data(mob/user) - var/list/data = list() - data["powerStatus"] = bot_mode_flags & BOT_MODE_ON - data["locked"] = bot_cover_flags & BOT_COVER_LOCKED - data["siliconUser"] = HAS_SILICON_ACCESS(user) - data["mode"] = mode ? "[mode]" : "Ready" - data["modeStatus"] = "" - switch(mode) - if(BOT_IDLE, BOT_DELIVER, BOT_GO_HOME) - data["modeStatus"] = "good" - if(BOT_BLOCKED, BOT_NAV, BOT_WAIT_FOR_NAV) - data["modeStatus"] = "average" - if(BOT_NO_ROUTE) - data["modeStatus"] = "bad" - data["load"] = get_load_name() - data["destination"] = destination - data["homeDestination"] = home_destination - data["destinationsList"] = GLOB.deliverybeacontags - data["cellPercent"] = cell?.percent() - data["autoReturn"] = mulebot_delivery_flags & MULEBOT_RETURN_MODE - data["autoPickup"] = mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE - data["reportDelivery"] = mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE - data["botId"] = id - data["allowPossession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT - data["possessionEnabled"] = can_be_possessed - data["paiInserted"] = !!paicard - return data - -/mob/living/simple_animal/bot/mulebot/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("lock") - if(HAS_SILICON_ACCESS(user)) - bot_cover_flags ^= BOT_COVER_LOCKED - return TRUE - if("on") - if(bot_mode_flags & BOT_MODE_ON) - turn_off() - else if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) - to_chat(user, span_warning("[name]'s maintenance panel is open!")) - return - else if(cell) - if(!turn_on()) - to_chat(user, span_warning("You can't switch on [src]!")) - return - return TRUE - else - bot_control(action, user, params) // Kill this later. // Kill PDAs in general please - return TRUE - -/mob/living/simple_animal/bot/mulebot/bot_control(command, mob/user, list/params = list(), pda = FALSE) - if(pda && wires.is_cut(WIRE_RX)) // MULE wireless is controlled by wires. - return - - switch(command) - if("stop") - if(mode != BOT_IDLE) - bot_reset() - if("go") - if(mode == BOT_IDLE) - start() - if("home") - if(mode == BOT_IDLE || mode == BOT_DELIVER) - start_home() - if("destination") - var/new_dest - if(pda) - new_dest = tgui_input_list(user, "Enter Destination", "Mulebot Settings", GLOB.deliverybeacontags, destination) - else - new_dest = params["value"] - if(new_dest) - set_destination(new_dest) - if("setid") - var/new_id = tgui_input_text(user, "Enter ID", "ID Assignment", id, max_length = MAX_NAME_LEN) - if(new_id) - set_id(new_id) - name = "\improper MULEbot [new_id]" - if("sethome") - var/new_home = tgui_input_list(user, "Enter Home", "Mulebot Settings", GLOB.deliverybeacontags, home_destination) - if(new_home) - home_destination = new_home - if("unload") - if(load && mode != BOT_HUNT) - if(loc == target) - unload(loaddir) - else - unload(0) - if("autoret") - mulebot_delivery_flags ^= MULEBOT_RETURN_MODE - if("autopick") - mulebot_delivery_flags ^= MULEBOT_AUTO_PICKUP_MODE - if("report") - mulebot_delivery_flags ^= MULEBOT_REPORT_DELIVERY_MODE - -/mob/living/simple_animal/bot/mulebot/proc/buzz(type) - switch(type) - if(SIGH) - audible_message(span_hear("[src] makes a sighing buzz.")) - playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 50, FALSE) - if(ANNOYED) - audible_message(span_hear("[src] makes an annoyed buzzing sound.")) - playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, FALSE) - if(DELIGHT) - audible_message(span_hear("[src] makes a delighted ping!")) - playsound(src, 'sound/machines/ping.ogg', 50, FALSE) - if(CHIME) - audible_message(span_hear("[src] makes a chiming sound!")) - playsound(src, 'sound/machines/chime.ogg', 50, FALSE) - flick("[base_icon]1", src) - - -// mousedrop a crate to load the bot -// can load anything if hacked -/mob/living/simple_animal/bot/mulebot/mouse_drop_receive(atom/movable/AM, mob/user, params) - if(!isliving(user)) - return - - if(!istype(AM) || isdead(AM) || iseyemob(AM) || istype(AM, /obj/effect/dummy/phased_mob)) - return - - load(AM) - -// called to load a crate -/mob/living/simple_animal/bot/mulebot/proc/load(atom/movable/AM) - if(load || AM.anchored) - return - - if(!isturf(AM.loc)) //To prevent the loading from stuff from someone's inventory or screen icons. - return - - var/obj/structure/closet/crate/crate = AM - if(!istype(crate)) - if(!wires.is_cut(WIRE_LOADCHECK)) - buzz(SIGH) - return // if not hacked, only allow crates to be loaded - crate = null - - if(crate || isobj(AM)) - var/obj/O = AM - if(O.has_buckled_mobs() || (locate(/mob) in AM)) //can't load non crates objects with mobs buckled to it or inside it. - buzz(SIGH) - return - - if(crate) - crate.close() //make sure the crate is closed - - O.forceMove(src) - - else if(isliving(AM)) - if(!load_mob(AM)) //forceMove() is handled in buckling - return - - load = AM - mode = BOT_IDLE - update_appearance() - -///resolves the name to display for the loaded mob. primarily needed for the paranormal subtype since we don't want to show the name of ghosts riding it. -/mob/living/simple_animal/bot/mulebot/proc/get_load_name() - return load ? load.name : null - -/mob/living/simple_animal/bot/mulebot/proc/load_mob(mob/living/M) - can_buckle = TRUE - if(buckle_mob(M)) - passenger = M - load = M - can_buckle = FALSE - return TRUE - -/mob/living/simple_animal/bot/mulebot/post_unbuckle_mob(mob/living/M) - load = null - return ..() - -// called to unload the bot -// argument is optional direction to unload -// if zero, unload at bot's location -/mob/living/simple_animal/bot/mulebot/proc/unload(dirn) - if(QDELETED(load)) - if(load) //if our thing was qdel'd, there's likely a leftover reference. just clear it and remove the overlay. we'll let the bot keep moving around to prevent it abruptly stopping somewhere. - load = null - update_appearance() - return - - mode = BOT_IDLE - - var/atom/movable/cached_load = load //cache the load since unbuckling mobs clears the var. - - unbuckle_all_mobs() - - if(load) //don't have to do any of this for mobs. - load = null - cached_load.forceMove(loc) - cached_load.pixel_y = initial(cached_load.pixel_y) - cached_load.layer = initial(cached_load.layer) - SET_PLANE_EXPLICIT(cached_load, initial(cached_load.plane), src) - - if(dirn) //move the thing to the delivery point. - cached_load.Move(get_step(loc,dirn), dirn) - - update_appearance() - -/mob/living/simple_animal/bot/mulebot/get_status_tab_items() - . = ..() - if(cell) - . += "Charge Left: [cell.charge]/[cell.maxcharge]" - else - . += "No Cell Inserted!" - if(load) - . += "Current Load: [get_load_name()]" - - -/mob/living/simple_animal/bot/mulebot/call_bot() - ..() - if(path && length(path)) - target = ai_waypoint //Target is the end point of the path, the waypoint set by the AI. - destination = get_area_name(target, TRUE) - pathset = TRUE //Indicates the AI's custom path is initialized. - start() - -/mob/living/simple_animal/bot/mulebot/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - if(has_gravity()) - for(var/mob/living/carbon/human/future_pancake in loc) - if(future_pancake.body_position == LYING_DOWN) - run_over(future_pancake) - - diag_hud_set_mulebotcell() - -/mob/living/simple_animal/bot/mulebot/handle_automated_action() - if(!(bot_mode_flags & BOT_MODE_ON)) - return - if(!has_power()) - turn_off() - return - if(mode == BOT_IDLE) - return - if(HAS_TRAIT(src, TRAIT_IMMOBILIZED)) - return - - var/speed = (wires.is_cut(WIRE_MOTOR1) ? 0 : 2) + (wires.is_cut(WIRE_MOTOR2) ? 0 : 1) - if(!speed)//Devide by zero man bad - return - num_steps = round(10/speed) //10, 5, or 3 steps, depending on how many wires we have cut - START_PROCESSING(SSfastprocess, src) - -/mob/living/simple_animal/bot/mulebot/process() - if(!(bot_mode_flags & BOT_MODE_ON) || client || (num_steps <= 0) || !has_power()) - return PROCESS_KILL - num_steps-- - - switch(mode) - if(BOT_IDLE) // idle - return - - if(BOT_DELIVER, BOT_GO_HOME, BOT_BLOCKED) // navigating to deliver,home, or blocked - if(loc == target) // reached target - at_target() - return - - else if(length(path) && target) // valid path - var/turf/next = path[1] - reached_target = FALSE - if(next == loc) - path -= next - return - if(isturf(next)) - if(SEND_SIGNAL(src, COMSIG_MOB_BOT_PRE_STEP) & COMPONENT_MOB_BOT_BLOCK_PRE_STEP) - return - var/oldloc = loc - var/moved = step_towards(src, next) // attempt to move - if(moved && oldloc != loc) // successful move - SEND_SIGNAL(src, COMSIG_MOB_BOT_STEP) - blockcount = 0 - path -= loc - if(destination == home_destination) - mode = BOT_GO_HOME - else - mode = BOT_DELIVER - - else // failed to move - - blockcount++ - mode = BOT_BLOCKED - if(blockcount == 3) - buzz(ANNOYED) - - if(blockcount > 10) // attempt 10 times before recomputing - // find new path excluding blocked turf - buzz(SIGH) - mode = BOT_WAIT_FOR_NAV - blockcount = 0 - addtimer(CALLBACK(src, PROC_REF(process_blocked), next), 2 SECONDS) - return - return - else - buzz(ANNOYED) - mode = BOT_NAV - return - else - mode = BOT_NAV - return - - if(BOT_NAV) // calculate new path - mode = BOT_WAIT_FOR_NAV - INVOKE_ASYNC(src, PROC_REF(process_nav)) - -/mob/living/simple_animal/bot/mulebot/proc/process_blocked(turf/next) - calc_path(avoid=next) - if(length(path)) - buzz(DELIGHT) - mode = BOT_BLOCKED - -/mob/living/simple_animal/bot/mulebot/proc/process_nav() - calc_path() - - if(length(path)) - blockcount = 0 - mode = BOT_BLOCKED - buzz(DELIGHT) - - else - buzz(SIGH) - - mode = BOT_NO_ROUTE - -// calculates a path to the current destination -// given an optional turf to avoid -/mob/living/simple_animal/bot/mulebot/calc_path(turf/avoid = null) - path = get_path_to(src, target, max_distance=250, access=access_card.GetAccess(), exclude=avoid, diagonal_handling=DIAGONAL_REMOVE_ALL) - -// sets the current destination -// signals all beacons matching the delivery code -// beacons will return a signal giving their locations -/mob/living/simple_animal/bot/mulebot/proc/set_destination(new_dest) - new_destination = new_dest - get_nav() - -// starts bot moving to current destination -/mob/living/simple_animal/bot/mulebot/proc/start() - if(!(bot_mode_flags & BOT_MODE_ON)) - return - if(destination == home_destination) - mode = BOT_GO_HOME - else - mode = BOT_DELIVER - get_nav() - -// starts bot moving to home -// sends a beacon query to find -/mob/living/simple_animal/bot/mulebot/proc/start_home() - if(!(bot_mode_flags & BOT_MODE_ON)) - return - INVOKE_ASYNC(src, PROC_REF(do_start_home)) - -/mob/living/simple_animal/bot/mulebot/proc/do_start_home() - set_destination(home_destination) - mode = BOT_BLOCKED - -// called when bot reaches current target -/mob/living/simple_animal/bot/mulebot/proc/at_target() - if(!reached_target) - radio_channel = RADIO_CHANNEL_SUPPLY //Supply channel - buzz(CHIME) - reached_target = TRUE - - if(pathset) //The AI called us here, so notify it of our arrival. - loaddir = dir //The MULE will attempt to load a crate in whatever direction the MULE is "facing". - if(calling_ai) - to_chat(calling_ai, span_notice("[icon2html(src, calling_ai)] [src] wirelessly plays a chiming sound!")) - calling_ai.playsound_local(calling_ai, 'sound/machines/chime.ogg', 40, FALSE) - calling_ai = null - radio_channel = RADIO_CHANNEL_AI_PRIVATE //Report on AI Private instead if the AI is controlling us. - - if(load) // if loaded, unload at target - if(mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE) - speak("Destination [RUNECHAT_BOLD("[destination]")] reached. Unloading [load].",radio_channel) - unload(loaddir) - else - // not loaded - if(mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE) // find a crate - var/atom/movable/AM - if(wires.is_cut(WIRE_LOADCHECK)) // if hacked, load first unanchored thing we find - for(var/atom/movable/A in get_step(loc, loaddir)) - if(!A.anchored) - AM = A - break - else // otherwise, look for crates only - AM = locate(/obj/structure/closet/crate) in get_step(loc,loaddir) - if(AM?.Adjacent(src)) - load(AM) - if(mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE) - speak("Now loading [load] at [RUNECHAT_BOLD("[get_area_name(src)]")].", radio_channel) - // whatever happened, check to see if we return home - - if((mulebot_delivery_flags & MULEBOT_RETURN_MODE) && home_destination && destination != home_destination) - // auto return set and not at home already - start_home() - mode = BOT_BLOCKED - else - bot_reset() // otherwise go idle - - -/mob/living/simple_animal/bot/mulebot/MobBump(mob/M) // called when the bot bumps into a mob - if(mind || !isliving(M)) //if there's a sentience controlling the bot, they aren't allowed to harm folks. - return ..() - var/mob/living/L = M - if(wires.is_cut(WIRE_AVOIDANCE)) // usually just bumps, but if the avoidance wire is cut, knocks them over. - if(iscyborg(L)) - visible_message(span_danger("[src] bumps into [L]!")) - else if(L.Knockdown(8 SECONDS)) - log_combat(src, L, "knocked down") - visible_message(span_danger("[src] knocks over [L]!")) - return ..() - -// when mulebot is in the same loc -/mob/living/simple_animal/bot/mulebot/proc/run_over(mob/living/carbon/human/crushed) - if (!(bot_cover_flags & BOT_COVER_EMAGGED) && !wires.is_cut(WIRE_AVOIDANCE)) - if (!has_status_effect(/datum/status_effect/careful_driving)) - crushed.visible_message(span_notice("[src] slows down to avoid crushing [crushed].")) - apply_status_effect(/datum/status_effect/careful_driving) - return // Player mules must be emagged before they can trample - - log_combat(src, crushed, "run over", addition = "(DAMTYPE: [uppertext(BRUTE)])") - crushed.visible_message( - span_danger("[src] drives over [crushed]!"), - span_userdanger("[src] drives over you!"), - ) - - playsound(src, 'sound/effects/splat.ogg', 50, TRUE) - - var/damage = rand(5, 15) - crushed.apply_damage(2 * damage, BRUTE, BODY_ZONE_HEAD, run_armor_check(BODY_ZONE_HEAD, MELEE)) - crushed.apply_damage(2 * damage, BRUTE, BODY_ZONE_CHEST, run_armor_check(BODY_ZONE_CHEST, MELEE)) - crushed.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_LEG, run_armor_check(BODY_ZONE_L_LEG, MELEE)) - crushed.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_LEG, run_armor_check(BODY_ZONE_R_LEG, MELEE)) - crushed.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_ARM, run_armor_check(BODY_ZONE_L_ARM, MELEE)) - crushed.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_ARM, run_armor_check(BODY_ZONE_R_ARM, MELEE)) - - add_mob_blood(crushed) - - var/turf/below_us = get_turf(src) - below_us.add_mob_blood(crushed) - - AddComponent(/datum/component/blood_walk, \ - blood_type = /obj/effect/decal/cleanable/blood/tracks, \ - target_dir_change = TRUE, \ - transfer_blood_dna = TRUE, \ - max_blood = 4) - -// player on mulebot attempted to move -/mob/living/simple_animal/bot/mulebot/relaymove(mob/living/user, direction) - if(user.incapacitated) - return - if(load == user) - unload(0) - - -//Update navigation data. Called when commanded to deliver, return home, or a route update is needed... -/mob/living/simple_animal/bot/mulebot/proc/get_nav() - if(!(bot_mode_flags & BOT_MODE_ON) || wires.is_cut(WIRE_BEACON)) - return - - for(var/obj/machinery/navbeacon/NB in GLOB.deliverybeacons) - if(NB.location == new_destination) // if the beacon location matches the set destination - // the we will navigate there - destination = new_destination - target = NB.loc - var/direction = NB.codes[NAVBEACON_DELIVERY_DIRECTION] // this will be the load/unload dir - if(!direction) - direction = NB.dir // fallback - if(direction) - loaddir = text2num(direction) - else - loaddir = 0 - if(destination) // No need to calculate a path if you do not have a destination set! - calc_path() - -/mob/living/simple_animal/bot/mulebot/emp_act(severity) - . = ..() - if(cell && !(. & EMP_PROTECT_CONTENTS)) - cell.emp_act(severity) - if(load) - load.emp_act(severity) - - -/mob/living/simple_animal/bot/mulebot/explode() - var/atom/Tsec = drop_location() - - new /obj/item/assembly/prox_sensor(Tsec) - new /obj/item/stack/rods(Tsec) - new /obj/item/stack/rods(Tsec) - new /obj/item/stack/cable_coil/cut(Tsec) - if(cell) - cell.forceMove(Tsec) - cell = null - - new /obj/effect/decal/cleanable/blood/oil(loc) - return ..() - -/mob/living/simple_animal/bot/mulebot/remove_air(amount) //To prevent riders suffocating - return loc ? loc.remove_air(amount) : null - -/mob/living/simple_animal/bot/mulebot/execute_resist() - . = ..() - if(load) - unload() - -/mob/living/simple_animal/bot/mulebot/UnarmedAttack(atom/A, proximity_flag, list/modifiers) - if(!can_unarmed_attack()) - return - if(isturf(A) && isturf(loc) && loc.Adjacent(A) && load) - unload(get_dir(loc, A)) - else - return ..() - -/// Checks whether the bot can complete a step_towards, checking whether the bot is on and has the charge to do the move. Returns COMPONENT_MOB_BOT_CANCELSTEP if the bot should not step. -/mob/living/simple_animal/bot/mulebot/proc/check_pre_step(datum/source) - SIGNAL_HANDLER - - if(!(bot_mode_flags & BOT_MODE_ON)) - return COMPONENT_MOB_BOT_BLOCK_PRE_STEP - - if((cell && (cell.charge < cell_move_power_usage)) || !has_power()) - turn_off() - return COMPONENT_MOB_BOT_BLOCK_PRE_STEP - -/// Uses power from the cell when the bot steps. -/mob/living/simple_animal/bot/mulebot/proc/on_bot_step(datum/source) - SIGNAL_HANDLER - - cell?.use(cell_move_power_usage) - -/mob/living/simple_animal/bot/mulebot/post_possession() - . = ..() - visible_message(span_notice("[src]'s safeties are locked on.")) - -/mob/living/simple_animal/bot/mulebot/paranormal//allows ghosts only unless hacked to actually be useful - name = "\improper GHOULbot" - desc = "A rather ghastly looking... Multiple Utility Load Effector bot? It only seems to accept paranormal forces, and for this reason is fucking useless." - icon_state = "paranormalmulebot0" - base_icon = "paranormalmulebot" - -/mob/living/simple_animal/bot/mulebot/paranormal/mouse_drop_receive(atom/movable/AM, mob/user, params) - var/mob/living/L = user - - if(user.incapacitated || (istype(L) && L.body_position == LYING_DOWN)) - return - - if(!istype(AM) || iseyemob(AM) || istype(AM, /obj/effect/dummy/phased_mob)) //allows ghosts! - return - - load(AM) - -/mob/living/simple_animal/bot/mulebot/paranormal/load(atom/movable/movable_atom) - if(load || movable_atom.anchored) - return - - if(!isturf(movable_atom.loc)) //To prevent the loading from stuff from someone's inventory or screen icons. - return - - if(isobserver(movable_atom)) - visible_message(span_warning("A ghostly figure appears on [src]!")) - movable_atom.forceMove(src) - RegisterSignal(movable_atom, COMSIG_MOVABLE_MOVED, PROC_REF(ghostmoved)) - - else if(!wires.is_cut(WIRE_LOADCHECK)) - buzz(SIGH) - return // if not hacked, only allow ghosts to be loaded - - else if(isobj(movable_atom)) - if(movable_atom.has_buckled_mobs() || (locate(/mob) in movable_atom)) //can't load non crates objects with mobs buckled to it or inside it. - buzz(SIGH) - return - - if(istype(movable_atom, /obj/structure/closet/crate)) - var/obj/structure/closet/crate/crate = movable_atom - crate.close() //make sure it's closed - - movable_atom.forceMove(src) - - else if(isliving(movable_atom) && !load_mob(movable_atom)) - return - - load = movable_atom - mode = BOT_IDLE - update_appearance() - -/mob/living/simple_animal/bot/mulebot/paranormal/update_overlays() - . = ..() - if(!isobserver(load)) - return - var/mutable_appearance/ghost_overlay = mutable_appearance('icons/mob/simple/mob.dmi', "ghost", layer + 0.01) //use a generic ghost icon, otherwise you can metagame who's dead if they have a custom ghost set - ghost_overlay.pixel_z = 12 - . += ghost_overlay - -/mob/living/simple_animal/bot/mulebot/paranormal/get_load_name() //Don't reveal the name of ghosts so we can't metagame who died and all that. - . = ..() - if(. && isobserver(load)) - return "Unknown" - -/mob/living/simple_animal/bot/mulebot/paranormal/proc/ghostmoved() - SIGNAL_HANDLER - visible_message(span_notice("The ghostly figure vanishes...")) - UnregisterSignal(load, COMSIG_MOVABLE_MOVED) - unload(0) - -#undef SIGH -#undef ANNOYED -#undef DELIGHT -#undef CHIME diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm index 04df80eb540..1f530e8904d 100644 --- a/code/modules/mod/modules/modules_ninja.dm +++ b/code/modules/mod/modules/modules_ninja.dm @@ -547,7 +547,7 @@ return COMPONENT_CANCEL_ATTACK_CHAIN //BOTS, overloads them and causes a explosion -/mob/living/simple_animal/bot/ninjadrain_act(mob/living/carbon/human/ninja, obj/item/mod/module/hacker/hacking_module) +/mob/living/basic/bot/ninjadrain_act(mob/living/carbon/human/ninja, obj/item/mod/module/hacker/hacking_module) to_chat(src, span_boldwarning("Your circutry suddenly begins heating up!")) if(!do_after(ninja, 1.5 SECONDS, target = src, hidden = TRUE)) return COMPONENT_CANCEL_ATTACK_CHAIN diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm index 1176ce92114..f7278fa3008 100644 --- a/code/modules/modular_computers/file_system/programs/robocontrol.dm +++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm @@ -32,32 +32,32 @@ botcount = 0 - for(var/mob/living/simple_animal/bot/simple_bot as anything in GLOB.bots_list) - if(!is_valid_z_level(current_turf, get_turf(simple_bot)) || !(simple_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) //Only non-emagged bots on the same Z-level are detected! + for(var/mob/living/basic/bot/basic_bot as anything in GLOB.bots_list) + if(!is_valid_z_level(current_turf, get_turf(basic_bot)) || !(basic_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) //Only non-emagged bots on the same Z-level are detected! continue - if(!simple_bot.allowed(user) && !simple_bot.check_access(computer.stored_id)) // Only check Bots we can access + if(!basic_bot.allowed(user) && !basic_bot.check_access(computer.stored_id)) // Only check Bots we can access continue var/list/newbot = list( - "name" = simple_bot.name, - "mode" = simple_bot.get_mode_ui(), - "model" = simple_bot.bot_type, - "locat" = get_area(simple_bot), - "bot_ref" = REF(simple_bot), + "name" = basic_bot.name, + "mode" = basic_bot.get_mode_ui(), + "model" = basic_bot.bot_type, + "locat" = get_area(basic_bot), + "bot_ref" = REF(basic_bot), "mule_check" = FALSE, ) - if(simple_bot.bot_type == MULE_BOT) - var/mob/living/simple_animal/bot/mulebot/simple_mulebot = simple_bot + if(basic_bot.bot_type == MULE_BOT) + var/mob/living/basic/bot/mulebot/basic_mulebot = basic_bot mulelist += list(list( - "name" = simple_mulebot.name, - "id" = simple_mulebot.id, - "dest" = simple_mulebot.destination, - "power" = simple_mulebot.cell ? simple_mulebot.cell.percent() : 0, - "home" = simple_mulebot.home_destination, - "autoReturn" = simple_mulebot.mulebot_delivery_flags & MULEBOT_RETURN_MODE, - "autoPickup" = simple_mulebot.mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE, - "reportDelivery" = simple_mulebot.mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE, - "mule_ref" = REF(simple_mulebot), - "load" = simple_mulebot.get_load_name(), + "name" = basic_mulebot.name, + "id" = basic_mulebot.id, + "dest" = basic_mulebot.ai_controller.blackboard[BB_MULEBOT_DESTINATION_BEACON], + "power" = basic_mulebot.cell ? basic_mulebot.cell.percent() : 0, + "home" = basic_mulebot.ai_controller.blackboard[BB_MULEBOT_HOME_BEACON], + "autoReturn" = basic_mulebot.mulebot_delivery_flags & MULEBOT_RETURN_MODE, + "autoPickup" = basic_mulebot.mulebot_delivery_flags & MULEBOT_AUTO_PICKUP_MODE, + "reportDelivery" = basic_mulebot.mulebot_delivery_flags & MULEBOT_REPORT_DELIVERY_MODE, + "mule_ref" = REF(basic_mulebot), + "load" = basic_mulebot.get_load_name(), )) newbot["mule_check"] = TRUE botlist += list(newbot) @@ -106,15 +106,15 @@ "report", "ejectpai", ) - var/mob/living/simple_animal/bot/simple_bot = locate(params["robot"]) in GLOB.bots_list + var/mob/living/basic/bot/basic_bot = locate(params["robot"]) in GLOB.bots_list if (action in standard_actions) - simple_bot.bot_control(action, current_user, id_card?.GetAccess()) + basic_bot.bot_control(action, current_user, id_card?.GetAccess()) if (action in MULE_actions) - simple_bot.bot_control(action, current_user, id_card?.GetAccess(), TRUE) + basic_bot.bot_control(action, current_user, id_card?.GetAccess(), TRUE) switch(action) if("summon") - simple_bot.bot_control(action, current_user, id_card ? id_card.access : id_card?.GetAccess()) + basic_bot.bot_control(action, current_user, id_card ? id_card.access : id_card?.GetAccess()) if("ejectcard") if(!computer || !computer.stored_id) return diff --git a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm index 7ecd0e6b852..c9b84527ebf 100644 --- a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -165,9 +165,9 @@ new_shape.add_traits(list(TRAIT_DONT_WRITE_MEMORY, TRAIT_SHAPESHIFTED), SHAPESHIFT_TRAIT) // Make sure that if you shapechanged into a bot, the AI can't just turn you off. - var/mob/living/simple_animal/bot/polymorph_bot = new_shape + var/mob/living/basic/bot/polymorph_bot = new_shape if (istype(polymorph_bot)) - polymorph_bot.bot_cover_flags |= BOT_COVER_EMAGGED + polymorph_bot.bot_access_flags |= BOT_COVER_EMAGGED polymorph_bot.bot_mode_flags &= ~BOT_MODE_REMOTE_ENABLED return new_shape diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 5b1794ef33b..dae3132b706 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -5,9 +5,6 @@ // Use the new basic mobs system instead. // 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/mulebot, - /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/hostile, /mob/living/simple_animal/hostile/asteroid, /mob/living/simple_animal/hostile/asteroid/elite, diff --git a/tgstation.dme b/tgstation.dme index afa90adb47e..e17c81ab8be 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5260,6 +5260,14 @@ #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\mulebot\mulebot.dm" +#include "code\modules\mob\living\basic\bots\mulebot\mulebot_ai.dm" +#include "code\modules\mob\living\basic\bots\mulebot\mulebot_control.dm" +#include "code\modules\mob\living\basic\bots\mulebot\mulebot_delivery.dm" +#include "code\modules\mob\living\basic\bots\mulebot\mulebot_hud.dm" +#include "code\modules\mob\living\basic\bots\mulebot\mulebot_movement.dm" +#include "code\modules\mob\living\basic\bots\mulebot\mulebot_paranormal.dm" +#include "code\modules\mob\living\basic\bots\mulebot\mulebot_tool_interactions.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" @@ -5746,10 +5754,8 @@ #include "code\modules\mob\living\simple_animal\animal_defense.dm" #include "code\modules\mob\living\simple_animal\damage_procs.dm" #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\construction.dm" -#include "code\modules\mob\living\simple_animal\bot\mulebot.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" #include "code\modules\mob\living\simple_animal\hostile\ooze.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\_megafauna.dm" diff --git a/tools/UpdatePaths/Scripts/96500_mulebots.txt b/tools/UpdatePaths/Scripts/96500_mulebots.txt new file mode 100644 index 00000000000..1393f74f32a --- /dev/null +++ b/tools/UpdatePaths/Scripts/96500_mulebots.txt @@ -0,0 +1,2 @@ +/mob/living/simple_animal/bot/mulebot : /mob/living/basic/bot/mulebot{@OLD} +/mob/living/simple_animal/bot/mulebot/paranormal : /mob/living/basic/bot/mulebot/paranormal{@OLD} \ No newline at end of file