mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 13:10:02 +01:00
Bots no longer require PAIs to become sapient (#76691)
## About The Pull Request We were talking in the coder channel about what the role of a pAI is, with a general conclusion that as the name would suggest they should be _personal assistants_. This means they should be sticking around their owner, not wandering away as a holochassis or in the body of a bot. The former is a matter for a future PR, the latter I am addressing here. What we also discussed is that clearly some people _want_ to respawn as a weird quasi-useless mob which wanders aimlessly around the station. That seems like a fine thing to exist, but it shouldn't be a pAI. Resultingly: pAI cards can no longer be placed inside bots. However, you also no longer need to place pAI cards inside bots in order for them to become sapient, it's a simple toggle on the bot control menu. Enabling this option will poll ghosts Toggling the "personality matrix" off while a bot is being controlled by a ghost will ghost them again, so if they're annoying they're not that hard to get rid of.  Mobs which couldn't have a pAI inserted don't have this option. Specifically securitrons, ED-209, and Hygienebots (for some reason). Perhaps most controversially, any bots which are present on the station when the map loads will have this setting enabled by default. We will see if players abuse this too much and need their toys taken away, I am hoping they can be trusted. Additionally, as part of this change, mobs you can possess now appear in the spawners menu.  Here is an unusually populated example. Oh also in the process of doing this I turned the regal rat "click this to become it" behaviour into a component because it seems generally useful. ## Why It's Good For The Game Minor stuff for dead players to do if they want to interact with living players instead of observe. Shift pAI back into a more intended role as a personal assistant who hangs around with their owner, rather than just a generic respawn role. ## Changelog 🆑 add: PAIs can no longer be inserted into Bots add: Bots can now have their sapience toggled by anyone with access to their settings panel add: Bots which exist on the map at the start of the round automatically have this setting enabled qol: Bots, Regal Rats, and Cargorilla now appear in the Spawners menu if you are dead qol: Bots can be renamed from their maintenance panel /🆑
This commit is contained in:
@@ -86,8 +86,8 @@
|
||||
#define BOT_MODE_AUTOPATROL (1<<1)
|
||||
///The Bot is currently allowed to be remote controlled by Silicon.
|
||||
#define BOT_MODE_REMOTE_ENABLED (1<<2)
|
||||
///The Bot is allowed to have a pAI placed in control of it.
|
||||
#define BOT_MODE_PAI_CONTROLLABLE (1<<3)
|
||||
///The Bot is allowed to have a ghost placed in control of it.
|
||||
#define BOT_MODE_GHOST_CONTROLLABLE (1<<3)
|
||||
|
||||
//Bot cover defines indicating the Bot's status
|
||||
///The Bot's cover is open and can be modified/emagged by anyone.
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
#define ROLE_BATTLECRUISER_CREW "Battlecruiser Crew"
|
||||
#define ROLE_BATTLECRUISER_CAPTAIN "Battlecruiser Captain"
|
||||
#define ROLE_VENUSHUMANTRAP "Venus Human Trap"
|
||||
#define ROLE_BOT "Bot"
|
||||
|
||||
|
||||
/// This defines the antagonists you can operate with in the settings.
|
||||
|
||||
@@ -1,31 +1,54 @@
|
||||
GLOBAL_LIST_EMPTY(cable_list) //Index for all cables, so that powernets don't have to look through the entire world all the time
|
||||
GLOBAL_LIST_EMPTY(portals) //list of all /obj/effect/portal
|
||||
GLOBAL_LIST_EMPTY(airlocks) //list of all airlocks
|
||||
GLOBAL_LIST_EMPTY(curtains) //list of all curtains
|
||||
GLOBAL_LIST_EMPTY(mechas_list) //list of all mechs. Used by hostile mobs target tracking.
|
||||
GLOBAL_LIST_EMPTY(shuttle_caller_list) //list of all communication consoles and AIs, for automatic shuttle calls when there are none.
|
||||
GLOBAL_LIST_EMPTY(machines) //NOTE: this is a list of ALL machines now. The processing machines list is SSmachine.processing !
|
||||
GLOBAL_LIST_EMPTY(navigation_computers) //list of all /obj/machinery/computer/camera_advanced/shuttle_docker
|
||||
GLOBAL_LIST_EMPTY(syndicate_shuttle_boards) //important to keep track of for managing nukeops war declarations.
|
||||
GLOBAL_LIST_EMPTY(navbeacons) //list of all bot nagivation beacons, used for patrolling.
|
||||
GLOBAL_LIST_EMPTY(teleportbeacons) //list of all tracking beacons used by teleporters
|
||||
GLOBAL_LIST_EMPTY(deliverybeacons) //list of all MULEbot delivery beacons.
|
||||
GLOBAL_LIST_EMPTY(deliverybeacontags) //list of all tags associated with delivery beacons.
|
||||
/// Index for all cables, so that powernets don't have to look through the entire world all the time
|
||||
GLOBAL_LIST_EMPTY(cable_list)
|
||||
/// list of all /obj/effect/portal
|
||||
GLOBAL_LIST_EMPTY(portals)
|
||||
/// list of all airlocks
|
||||
GLOBAL_LIST_EMPTY(airlocks)
|
||||
/// list of all curtains
|
||||
GLOBAL_LIST_EMPTY(curtains)
|
||||
/// list of all mechs. Used by hostile mobs target tracking.
|
||||
GLOBAL_LIST_EMPTY(mechas_list)
|
||||
/// list of all communication consoles and AIs, for automatic shuttle calls when there are none.
|
||||
GLOBAL_LIST_EMPTY(shuttle_caller_list)
|
||||
/// list of ALL machines
|
||||
GLOBAL_LIST_EMPTY(machines)
|
||||
/// list of all /obj/machinery/computer/camera_advanced/shuttle_docker
|
||||
GLOBAL_LIST_EMPTY(navigation_computers)
|
||||
/// important to keep track of for managing nukeops war declarations.
|
||||
GLOBAL_LIST_EMPTY(syndicate_shuttle_boards)
|
||||
/// list of all bot nagivation beacons, used for patrolling.
|
||||
GLOBAL_LIST_EMPTY(navbeacons)
|
||||
/// list of all tracking beacons used by teleporters
|
||||
GLOBAL_LIST_EMPTY(teleportbeacons)
|
||||
/// list of all MULEbot delivery beacons.
|
||||
GLOBAL_LIST_EMPTY(deliverybeacons)
|
||||
/// list of all tags associated with delivery beacons.
|
||||
GLOBAL_LIST_EMPTY(deliverybeacontags)
|
||||
GLOBAL_LIST_EMPTY(nuke_list)
|
||||
GLOBAL_LIST_EMPTY(alarmdisplay) //list of all machines or programs that can display station alerts
|
||||
GLOBAL_LIST_EMPTY_TYPED(singularities, /datum/component/singularity) //list of all singularities on the station
|
||||
GLOBAL_LIST_EMPTY(mechpad_list) //list of all /obj/machinery/mechpad
|
||||
/// list of all machines or programs that can display station alerts
|
||||
GLOBAL_LIST_EMPTY(alarmdisplay)
|
||||
/// list of all singularities on the station
|
||||
GLOBAL_LIST_EMPTY_TYPED(singularities, /datum/component/singularity)
|
||||
/// list of all /obj/machinery/mechpad
|
||||
GLOBAL_LIST_EMPTY(mechpad_list)
|
||||
|
||||
GLOBAL_LIST(chemical_reactions_list) //list of all /datum/chemical_reaction datums indexed by their typepath. Use this for general lookup stuff
|
||||
GLOBAL_LIST(chemical_reactions_list_reactant_index) //list of all /datum/chemical_reaction datums. Used during chemical reactions. Indexed by REACTANT types
|
||||
GLOBAL_LIST(chemical_reactions_list_product_index) //list of all /datum/chemical_reaction datums. Used for the reaction lookup UI. Indexed by PRODUCT type
|
||||
GLOBAL_LIST_INIT(chemical_reagents_list, init_chemical_reagent_list()) //list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff
|
||||
/// list of all /datum/chemical_reaction datums indexed by their typepath. Use this for general lookup stuff
|
||||
GLOBAL_LIST(chemical_reactions_list)
|
||||
/// list of all /datum/chemical_reaction datums. Used during chemical reactions. Indexed by REACTANT types
|
||||
GLOBAL_LIST(chemical_reactions_list_reactant_index)
|
||||
/// list of all /datum/chemical_reaction datums. Used for the reaction lookup UI. Indexed by PRODUCT type
|
||||
GLOBAL_LIST(chemical_reactions_list_product_index) /// list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff
|
||||
GLOBAL_LIST_INIT(chemical_reagents_list, init_chemical_reagent_list())
|
||||
/// names of reagents used by plumbing UI.
|
||||
GLOBAL_LIST_INIT(chemical_name_list, init_chemical_name_list())
|
||||
GLOBAL_LIST(chemical_reactions_results_lookup_list) //List of all reactions with their associated product and result ids. Used for reaction lookups
|
||||
GLOBAL_LIST(fake_reagent_blacklist) //List of all reagents that are parent types used to define a bunch of children - but aren't used themselves as anything.
|
||||
GLOBAL_LIST_EMPTY(tech_list) //list of all /datum/tech datums indexed by id.
|
||||
GLOBAL_LIST_INIT(surgeries_list, init_surgeries()) //list of all surgeries by name, associated with their path.
|
||||
/// List of all reactions with their associated product and result ids. Used for reaction lookups
|
||||
GLOBAL_LIST(chemical_reactions_results_lookup_list)
|
||||
/// List of all reagents that are parent types used to define a bunch of children - but aren't used themselves as anything.
|
||||
GLOBAL_LIST(fake_reagent_blacklist)
|
||||
/// list of all /datum/tech datums indexed by id.
|
||||
GLOBAL_LIST_EMPTY(tech_list)
|
||||
/// list of all surgeries by name, associated with their path.
|
||||
GLOBAL_LIST_INIT(surgeries_list, init_surgeries())
|
||||
|
||||
/// Global list of all non-cooking related crafting recipes.
|
||||
GLOBAL_LIST_EMPTY(crafting_recipes)
|
||||
@@ -37,16 +60,24 @@ GLOBAL_LIST_EMPTY(cooking_recipes)
|
||||
/// This is a global list of typepaths, these typepaths are atoms or reagents that are associated with cooking recipes.
|
||||
/// This includes stuff like recipe components and results.
|
||||
GLOBAL_LIST_EMPTY(cooking_recipes_atoms)
|
||||
|
||||
GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices.
|
||||
GLOBAL_LIST_EMPTY(intercoms_list) //list of wallmounted intercom radios.
|
||||
GLOBAL_LIST_EMPTY(apcs_list) //list of all Area Power Controller machines, separate from machines for powernet speeeeeeed.
|
||||
GLOBAL_LIST_EMPTY(tracked_implants) //list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented...
|
||||
GLOBAL_LIST_EMPTY(tracked_chem_implants) //list of implants the prisoner console can track and send inject commands too
|
||||
GLOBAL_LIST_EMPTY(pinpointer_list) //list of all pinpointers. Used to change stuff they are pointing to all at once.
|
||||
GLOBAL_LIST_EMPTY(zombie_infection_list) // A list of all zombie_infection organs, for any mass "animation"
|
||||
GLOBAL_LIST_EMPTY(meteor_list) // List of all meteors.
|
||||
GLOBAL_LIST_EMPTY(active_jammers) // List of active radio jammers
|
||||
/// list of Rapid Construction Devices.
|
||||
GLOBAL_LIST_EMPTY(rcd_list)
|
||||
/// list of wallmounted intercom radios.
|
||||
GLOBAL_LIST_EMPTY(intercoms_list)
|
||||
/// list of all Area Power Controller machines, separate from machines for powernet speeeeeeed.
|
||||
GLOBAL_LIST_EMPTY(apcs_list)
|
||||
/// list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented...
|
||||
GLOBAL_LIST_EMPTY(tracked_implants)
|
||||
/// list of implants the prisoner console can track and send inject commands too
|
||||
GLOBAL_LIST_EMPTY(tracked_chem_implants)
|
||||
/// list of all pinpointers. Used to change stuff they are pointing to all at once.
|
||||
GLOBAL_LIST_EMPTY(pinpointer_list)
|
||||
/// A list of all zombie_infection organs, for any mass "animation"
|
||||
GLOBAL_LIST_EMPTY(zombie_infection_list)
|
||||
/// List of all meteors.
|
||||
GLOBAL_LIST_EMPTY(meteor_list)
|
||||
/// List of active radio jammers
|
||||
GLOBAL_LIST_EMPTY(active_jammers)
|
||||
GLOBAL_LIST_EMPTY(ladders)
|
||||
GLOBAL_LIST_EMPTY(stairs)
|
||||
GLOBAL_LIST_EMPTY(janitor_devices)
|
||||
@@ -60,11 +91,17 @@ GLOBAL_LIST_EMPTY(wire_name_directory)
|
||||
|
||||
GLOBAL_LIST_EMPTY(ai_status_displays)
|
||||
|
||||
GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects
|
||||
GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert
|
||||
/// List of all instances of /obj/effect/mob_spawn/ghost_role in the game world
|
||||
GLOBAL_LIST_EMPTY(mob_spawners)
|
||||
/// List of all mobs with the "ghost_direct_control" component
|
||||
GLOBAL_LIST_EMPTY(joinable_mobs)
|
||||
/// List of all station alert consoles, /obj/machinery/computer/station_alert
|
||||
GLOBAL_LIST_EMPTY(alert_consoles)
|
||||
|
||||
GLOBAL_LIST_EMPTY(roundstart_station_borgcharger_areas) // List of area names of roundstart station cyborg rechargers, for the low charge/no charge cyborg screen alert tooltips.
|
||||
GLOBAL_LIST_EMPTY(roundstart_station_mechcharger_areas) // List of area names of roundstart station mech rechargers, for the low charge/no charge mech screen alert tooltips.
|
||||
/// List of area names of roundstart station cyborg rechargers, for the low charge/no charge cyborg screen alert tooltips.
|
||||
GLOBAL_LIST_EMPTY(roundstart_station_borgcharger_areas)
|
||||
/// List of area names of roundstart station mech rechargers, for the low charge/no charge mech screen alert tooltips.
|
||||
GLOBAL_LIST_EMPTY(roundstart_station_mechcharger_areas)
|
||||
|
||||
/// Associative list of alcoholic container typepath to instances, currently used by the alcoholic quirk
|
||||
GLOBAL_LIST_INIT(alcohol_containers, init_alcohol_containers())
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define POLL_IGNORE_MONKEY_HELMET "mind_magnified_monkey"
|
||||
#define POLL_IGNORE_LAVALAND_ELITE "lavaland_elite"
|
||||
#define POLL_IGNORE_SHUTTLE_DENIZENS "shuttle_denizens"
|
||||
#define POLL_IGNORE_BOTS "bots"
|
||||
|
||||
|
||||
GLOBAL_LIST_INIT(poll_ignore_desc, list(
|
||||
@@ -55,6 +56,7 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
|
||||
POLL_IGNORE_MONKEY_HELMET = "Mind magnified monkey",
|
||||
POLL_IGNORE_LAVALAND_ELITE = "Lavaland elite",
|
||||
POLL_IGNORE_SHUTTLE_DENIZENS = "Shuttle denizens",
|
||||
POLL_IGNORE_BOTS = "Bots",
|
||||
))
|
||||
GLOBAL_LIST_INIT(poll_ignore, init_poll_ignore())
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Component which lets ghosts click on a mob to take control of it
|
||||
*/
|
||||
/datum/component/ghost_direct_control
|
||||
/// Message to display upon successful possession
|
||||
var/assumed_control_message
|
||||
/// Type of ban you can get to prevent you from accepting this role
|
||||
var/ban_type
|
||||
/// Any extra checks which need to run before we take over
|
||||
var/datum/callback/extra_control_checks
|
||||
/// Callback run after someone successfully takes over the body
|
||||
var/datum/callback/after_assumed_control
|
||||
/// If we're currently awaiting the results of a ghost poll
|
||||
var/awaiting_ghosts = FALSE
|
||||
|
||||
/datum/component/ghost_direct_control/Initialize(
|
||||
ban_type = ROLE_SENTIENCE,
|
||||
role_name = null,
|
||||
poll_candidates = TRUE,
|
||||
poll_length = 10 SECONDS,
|
||||
poll_ignore_key = POLL_IGNORE_SENTIENCE_POTION,
|
||||
assumed_control_message = null,
|
||||
datum/callback/extra_control_checks,
|
||||
datum/callback/after_assumed_control,
|
||||
)
|
||||
. = ..()
|
||||
if (!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.ban_type = ban_type
|
||||
src.assumed_control_message = assumed_control_message || "You are [parent]!"
|
||||
src.extra_control_checks = extra_control_checks
|
||||
src.after_assumed_control= after_assumed_control
|
||||
|
||||
var/mob/mob_parent = parent
|
||||
LAZYADD(GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")], mob_parent)
|
||||
|
||||
if (poll_candidates)
|
||||
INVOKE_ASYNC(src, PROC_REF(request_ghost_control), role_name || "[parent]", poll_length, poll_ignore_key)
|
||||
|
||||
/datum/component/ghost_direct_control/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, PROC_REF(on_ghost_clicked))
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined))
|
||||
|
||||
/datum/component/ghost_direct_control/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_ATOM_ATTACK_GHOST, COMSIG_ATOM_EXAMINE))
|
||||
return ..()
|
||||
|
||||
/datum/component/ghost_direct_control/Destroy(force, silent)
|
||||
QDEL_NULL(extra_control_checks)
|
||||
QDEL_NULL(after_assumed_control)
|
||||
|
||||
var/mob/mob_parent = parent
|
||||
var/list/spawners = GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")]
|
||||
LAZYREMOVE(spawners, mob_parent)
|
||||
if(!LAZYLEN(spawners))
|
||||
GLOB.joinable_mobs -= format_text("[initial(mob_parent.name)]")
|
||||
return ..()
|
||||
|
||||
/// Inform ghosts that they can possess this
|
||||
/datum/component/ghost_direct_control/proc/on_examined(datum/source, mob/user, list/examine_text)
|
||||
SIGNAL_HANDLER
|
||||
if (!isobserver(user))
|
||||
return
|
||||
var/mob/living/our_mob = parent
|
||||
if (our_mob.stat == DEAD || our_mob.key || awaiting_ghosts)
|
||||
return
|
||||
examine_text += span_boldnotice("You could take control of this mob by clicking on it.")
|
||||
|
||||
/// Send out a request for a brain
|
||||
/datum/component/ghost_direct_control/proc/request_ghost_control(role_name, poll_length, poll_ignore_key)
|
||||
if (!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER))
|
||||
return
|
||||
awaiting_ghosts = TRUE
|
||||
var/list/mob/dead/observer/candidates = poll_ghost_candidates(
|
||||
question = "Do you want to play as [role_name]?",
|
||||
jobban_type = ban_type,
|
||||
be_special_flag = ban_type,
|
||||
poll_time = poll_length,
|
||||
ignore_category = poll_ignore_key,
|
||||
)
|
||||
awaiting_ghosts = FALSE
|
||||
if (!LAZYLEN(candidates))
|
||||
return
|
||||
assume_direct_control(pick(candidates))
|
||||
|
||||
/// A ghost clicked on us, they want to get in this body
|
||||
/datum/component/ghost_direct_control/proc/on_ghost_clicked(mob/our_mob, mob/dead/observer/hopeful_ghost)
|
||||
SIGNAL_HANDLER
|
||||
if (our_mob.key)
|
||||
qdel(src)
|
||||
return
|
||||
if (!hopeful_ghost.client)
|
||||
return
|
||||
if (!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER))
|
||||
to_chat(hopeful_ghost, span_warning("Ghost roles have been temporarily disabled!"))
|
||||
return
|
||||
if (awaiting_ghosts)
|
||||
to_chat(hopeful_ghost, span_warning("Ghost candidate selection currently in progress!"))
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
if (!SSticker.HasRoundStarted())
|
||||
to_chat(hopeful_ghost, span_warning("You cannot assume control of this until after the round has started!"))
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
INVOKE_ASYNC(src, PROC_REF(attempt_possession), our_mob, hopeful_ghost)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/// We got far enough to establish that this mob is a valid target, let's try to posssess it
|
||||
/datum/component/ghost_direct_control/proc/attempt_possession(mob/our_mob, mob/dead/observer/hopeful_ghost)
|
||||
var/ghost_asked = tgui_alert(usr, "Become [our_mob]?", "Are you sure?", list("Yes", "No"))
|
||||
if (ghost_asked != "Yes" || QDELETED(our_mob))
|
||||
return
|
||||
assume_direct_control(hopeful_ghost)
|
||||
|
||||
/// Grant possession of our mob, component is now no longer required
|
||||
/datum/component/ghost_direct_control/proc/assume_direct_control(mob/harbinger)
|
||||
if (QDELETED(src))
|
||||
to_chat(harbinger, span_warning("Offer to possess creature has expired!"))
|
||||
return
|
||||
if (is_banned_from(harbinger.ckey, list(ban_type)))
|
||||
to_chat(harbinger, span_warning("You are banned from playing as this role!"))
|
||||
return
|
||||
if (!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER))
|
||||
to_chat(harbinger, span_warning("Ghost roles have been temporarily disabled!"))
|
||||
return
|
||||
var/mob/living/new_body = parent
|
||||
if (new_body.stat == DEAD)
|
||||
to_chat(harbinger, span_warning("This body has passed away, it is of no use!"))
|
||||
return
|
||||
if (new_body.key)
|
||||
to_chat(harbinger, span_warning("[parent] has already become sapient!"))
|
||||
qdel(src)
|
||||
return
|
||||
if (extra_control_checks && !extra_control_checks.Invoke(harbinger))
|
||||
return
|
||||
harbinger.log_message("took control of [new_body].", LOG_GAME)
|
||||
new_body.key = harbinger.key
|
||||
to_chat(new_body, span_boldnotice(assumed_control_message))
|
||||
after_assumed_control?.Invoke(harbinger)
|
||||
qdel(src)
|
||||
@@ -44,6 +44,16 @@
|
||||
this["amount_left"] += 1
|
||||
if(this["amount_left"] > 0)
|
||||
data["spawners"] += list(this)
|
||||
for(var/mob_type in GLOB.joinable_mobs)
|
||||
var/list/this = list()
|
||||
this["name"] = mob_type
|
||||
this["amount_left"] = 0
|
||||
for(var/mob/joinable_mob as anything in GLOB.joinable_mobs[mob_type])
|
||||
this["amount_left"] += 1
|
||||
if(!this["desc"])
|
||||
this["desc"] = initial(joinable_mob.desc)
|
||||
if(this["amount_left"] > 0)
|
||||
data["spawners"] += list(this)
|
||||
return data
|
||||
|
||||
/datum/spawners_menu/ui_act(action, params, datum/tgui/ui)
|
||||
@@ -52,16 +62,25 @@
|
||||
return
|
||||
|
||||
var/group_name = params["name"]
|
||||
if(!group_name || !(group_name in GLOB.mob_spawners))
|
||||
if(!group_name)
|
||||
return
|
||||
var/list/spawnerlist = GLOB.mob_spawners[group_name]
|
||||
for(var/obj/effect/mob_spawn/ghost_role/current_spawner as anything in spawnerlist)
|
||||
if(!current_spawner.allow_spawn(usr, silent = TRUE))
|
||||
spawnerlist -= current_spawner
|
||||
if(!spawnerlist.len)
|
||||
|
||||
var/list/spawnerlist = list()
|
||||
|
||||
if (group_name in GLOB.mob_spawners)
|
||||
spawnerlist = GLOB.mob_spawners[group_name]
|
||||
if(!length(spawnerlist))
|
||||
return
|
||||
for(var/obj/effect/mob_spawn/ghost_role/current_spawner as anything in spawnerlist)
|
||||
if(!current_spawner.allow_spawn(usr, silent = TRUE))
|
||||
spawnerlist -= current_spawner
|
||||
else if (group_name in GLOB.joinable_mobs)
|
||||
spawnerlist = GLOB.joinable_mobs[group_name]
|
||||
|
||||
if(!length(spawnerlist))
|
||||
return
|
||||
var/obj/effect/mob_spawn/mob_spawner = pick(spawnerlist)
|
||||
if(!istype(mob_spawner) || !SSpoints_of_interest.is_valid_poi(mob_spawner))
|
||||
var/atom/mob_spawner = pick(spawnerlist)
|
||||
if(!SSpoints_of_interest.is_valid_poi(mob_spawner))
|
||||
return
|
||||
|
||||
switch(action)
|
||||
|
||||
@@ -165,9 +165,7 @@
|
||||
cargorilla = new(cargo_sloth.loc)
|
||||
cargorilla.name = cargo_sloth.name
|
||||
// We do a poll on roundstart, don't let ghosts in early
|
||||
cargorilla.being_polled_for = TRUE
|
||||
INVOKE_ASYNC(src, PROC_REF(make_id_for_gorilla))
|
||||
|
||||
// hm our sloth looks funny today
|
||||
qdel(cargo_sloth)
|
||||
|
||||
|
||||
@@ -332,6 +332,7 @@
|
||||
var/list/long_job_lists = list(
|
||||
"Ghost and Other Roles" = list(
|
||||
ROLE_PAI,
|
||||
ROLE_BOT,
|
||||
ROLE_BRAINWASHED,
|
||||
ROLE_DEATHSQUAD,
|
||||
ROLE_DRONE,
|
||||
|
||||
@@ -11,7 +11,6 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list(
|
||||
/mob/living/basic/pig,
|
||||
/mob/living/basic/rabbit,
|
||||
/mob/living/basic/sheep,
|
||||
/mob/living/simple_animal/bot/mulebot,
|
||||
/mob/living/simple_animal/bot/secbot/beepsky,
|
||||
/mob/living/simple_animal/hostile/retaliate/goat,
|
||||
/mob/living/simple_animal/hostile/retaliate/goose/vomit,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/datum/emote/silicon
|
||||
mob_type_allowed_typecache = list(/mob/living/silicon)
|
||||
mob_type_allowed_typecache = list(/mob/living/silicon, /mob/living/simple_animal/bot)
|
||||
emote_type = EMOTE_AUDIBLE
|
||||
|
||||
/datum/emote/silicon/boop
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
var/maints_access_required = list(ACCESS_ROBOTICS)
|
||||
///The Robot arm attached to this robot - has a 50% chance to drop on death.
|
||||
var/robot_arm = /obj/item/bodypart/arm/right/robot
|
||||
///The inserted (if any) pAI in this bot.
|
||||
var/obj/item/pai_card/paicard
|
||||
///The type of bot it is, for radio control.
|
||||
var/bot_type = NONE
|
||||
|
||||
@@ -47,13 +45,11 @@
|
||||
///All initial access this bot started with.
|
||||
var/list/prev_access = list()
|
||||
|
||||
///Bot-related mode flags on the Bot indicating how they will act.
|
||||
var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_PAI_CONTROLLABLE
|
||||
// Selections: BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_PAI_CONTROLLABLE
|
||||
///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE
|
||||
var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE
|
||||
|
||||
///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging.
|
||||
///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_COVER_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED
|
||||
var/bot_cover_flags = BOT_COVER_LOCKED
|
||||
// Selections: BOT_COVER_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED
|
||||
|
||||
///Small name of what the bot gets messed with when getting hacked/emagged.
|
||||
var/hackables = "system circuits"
|
||||
@@ -106,27 +102,31 @@
|
||||
var/reset_access_timer_id
|
||||
var/ignorelistcleanuptimer = 1 // This ticks up every automated action, at 300 we clean the ignore list
|
||||
|
||||
/// Component which allows ghosts to take over this bot
|
||||
var/datum/component/ghost_direct_control/personality_download
|
||||
/// 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?"
|
||||
|
||||
/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.
|
||||
if(paicard)
|
||||
return "<b>pAI Controlled</b>"
|
||||
else
|
||||
return "<b>Autonomous</b>"
|
||||
else if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return "<b>Autonomous</b>"
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return "<span class='bad'>Inactive</span>"
|
||||
else
|
||||
return "<span class='average'>[mode]</span>"
|
||||
return "<span class='average'>[mode]</span>"
|
||||
|
||||
/**
|
||||
* 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"
|
||||
else if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return "Autonomous"
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return "Inactive"
|
||||
else
|
||||
return "[mode]"
|
||||
return "[mode]"
|
||||
|
||||
/mob/living/simple_animal/bot/proc/turn_on()
|
||||
if(stat)
|
||||
@@ -190,17 +190,80 @@
|
||||
if(HAS_TRAIT(SSstation, STATION_TRAIT_BOTS_GLITCHED))
|
||||
randomize_language_if_on_station()
|
||||
|
||||
if(mapload && is_station_level(z) && (bot_mode_flags & BOT_MODE_GHOST_CONTROLLABLE))
|
||||
enable_possession(mapload)
|
||||
|
||||
/mob/living/simple_animal/bot/Destroy()
|
||||
if(path_hud)
|
||||
QDEL_NULL(path_hud)
|
||||
path_hud = null
|
||||
GLOB.bots_list -= src
|
||||
if(paicard)
|
||||
ejectpai()
|
||||
QDEL_NULL(personality_download)
|
||||
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(mapload = FALSE)
|
||||
can_be_possessed = TRUE
|
||||
var/can_announce = !mapload && COOLDOWN_FINISHED(src, offer_ghosts_cooldown)
|
||||
personality_download = AddComponent(\
|
||||
/datum/component/ghost_direct_control,\
|
||||
ban_type = ROLE_BOT,\
|
||||
poll_candidates = can_announce,\
|
||||
poll_ignore_key = POLL_IGNORE_BOTS,\
|
||||
assumed_control_message = possessed_message,\
|
||||
extra_control_checks = CALLBACK(src, PROC_REF(check_possession)),\
|
||||
after_assumed_control = CALLBACK(src, PROC_REF(post_possession)),\
|
||||
)
|
||||
if (can_announce)
|
||||
COOLDOWN_START(src, offer_ghosts_cooldown, 30 SECONDS)
|
||||
|
||||
/// Disables this bot from being possessed by ghosts
|
||||
/mob/living/simple_animal/bot/proc/disable_possession(mob/user)
|
||||
can_be_possessed = FALSE
|
||||
QDEL_NULL(personality_download)
|
||||
if (mind)
|
||||
if (user)
|
||||
log_combat(user, src, "ejected from [initial(src.name)] control.")
|
||||
to_chat(src, span_warning("You feel yourself fade as your personality matrix is reset!"))
|
||||
ghostize(can_reenter_corpse = FALSE)
|
||||
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
|
||||
say("Personally matrix reset!", forced = "bot")
|
||||
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)
|
||||
say("New personality installed successfully!", forced = "bot")
|
||||
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,
|
||||
)))
|
||||
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/proc/check_access(mob/living/user, obj/item/card/id)
|
||||
if(user.has_unlimited_silicon_privilege || isAdminGhostAI(user)) // Silicon and Admins always have access.
|
||||
return TRUE
|
||||
@@ -271,10 +334,6 @@
|
||||
var/is_sillycone = issilicon(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 its control panel.")
|
||||
if(paicard)
|
||||
. += span_notice("It has a pAI device installed.")
|
||||
if(!(bot_cover_flags & BOT_COVER_OPEN))
|
||||
. += span_info("You can use a <b>hemostat</b> to remove it.")
|
||||
|
||||
/mob/living/simple_animal/bot/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
|
||||
if(amount > 0 && prob(10))
|
||||
@@ -393,21 +452,14 @@
|
||||
/mob/living/simple_animal/bot/attackby(obj/item/attacking_item, mob/living/user, params)
|
||||
if(attacking_item.GetID())
|
||||
unlock_with_id(user)
|
||||
else if(istype(attacking_item, /obj/item/pai_card))
|
||||
insertpai(user, attacking_item)
|
||||
else if(attacking_item.tool_behaviour == TOOL_HEMOSTAT && paicard)
|
||||
if(bot_cover_flags & BOT_COVER_OPEN)
|
||||
to_chat(user, span_warning("Close the access panel before manipulating the personality slot!"))
|
||||
else
|
||||
to_chat(user, span_notice("You attempt to pull [paicard] free..."))
|
||||
if(do_after(user, 30, target = src))
|
||||
if (paicard)
|
||||
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)
|
||||
else
|
||||
if(attacking_item.force) //if force is non-zero
|
||||
do_sparks(5, TRUE, src)
|
||||
..()
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/attacked_by(obj/item/I, mob/living/user)
|
||||
. = ..()
|
||||
if (!.)
|
||||
return
|
||||
do_sparks(5, TRUE, src)
|
||||
|
||||
/mob/living/simple_animal/bot/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE)
|
||||
if(Proj && (Proj.damage_type == BRUTE || Proj.damage_type == BURN))
|
||||
@@ -422,10 +474,6 @@
|
||||
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(0)
|
||||
|
||||
if(prob(70/severity))
|
||||
set_active_language(get_random_spoken_language())
|
||||
@@ -794,9 +842,6 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
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
|
||||
|
||||
|
||||
@@ -878,11 +923,10 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
data["emagged"] = bot_cover_flags & BOT_COVER_EMAGGED
|
||||
data["has_access"] = check_access(user)
|
||||
data["locked"] = bot_cover_flags & BOT_COVER_LOCKED
|
||||
data["pai"] = list()
|
||||
data["settings"] = list()
|
||||
if(!(bot_cover_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user))
|
||||
data["pai"]["allow_pai"] = bot_mode_flags & BOT_MODE_PAI_CONTROLLABLE
|
||||
data["pai"]["card_inserted"] = paicard
|
||||
data["settings"]["allow_possession"] = bot_mode_flags & BOT_MODE_GHOST_CONTROLLABLE
|
||||
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_OPEN)
|
||||
data["settings"]["power"] = bot_mode_flags & BOT_MODE_ON
|
||||
@@ -930,10 +974,13 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
to_chat(usr, span_notice("You reset the [src]'s [hackables]."))
|
||||
usr.log_message("re-enabled safety lock of [src]", LOG_GAME)
|
||||
bot_reset()
|
||||
if("eject_pai")
|
||||
if(paicard)
|
||||
to_chat(usr, span_notice("You eject [paicard] from [initial(src.name)]."))
|
||||
ejectpai(usr)
|
||||
if("toggle_personality")
|
||||
if (can_be_possessed)
|
||||
disable_possession(usr)
|
||||
else
|
||||
enable_possession()
|
||||
if("rename")
|
||||
rename(usr)
|
||||
|
||||
/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)]"
|
||||
@@ -950,55 +997,6 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/living/simple_animal/bot/proc/insertpai(mob/user, obj/item/pai_card/card)
|
||||
if(paicard)
|
||||
to_chat(user, span_warning("A [paicard] is already inserted!"))
|
||||
return
|
||||
if(bot_cover_flags & BOT_COVER_LOCKED || !(bot_cover_flags & BOT_COVER_OPEN))
|
||||
to_chat(user, span_warning("The personality slot is locked."))
|
||||
return
|
||||
if(!(bot_mode_flags & BOT_MODE_PAI_CONTROLLABLE) || key) //Not pAI controllable or is already player controlled.
|
||||
to_chat(user, span_warning("[src] is not compatible with [card]!"))
|
||||
return
|
||||
if(!card.pai || !card.pai.mind)
|
||||
to_chat(user, span_warning("[card] is inactive."))
|
||||
return
|
||||
if(!user.transferItemToLoc(card, src))
|
||||
return
|
||||
paicard = card
|
||||
user.visible_message(span_notice("[user] inserts [card] into [src]!"), span_notice("You insert [card] into [src]."))
|
||||
paicard.pai.mind.transfer_to(src)
|
||||
to_chat(src, span_notice("You sense your form change as you are uploaded into [src]."))
|
||||
name = paicard.pai.name
|
||||
faction = user.faction.Copy()
|
||||
log_combat(user, paicard.pai, "uploaded to [initial(src.name)],")
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/bot/proc/ejectpai(mob/user = null, announce = TRUE)
|
||||
if(paicard)
|
||||
if(mind && paicard.pai)
|
||||
mind.transfer_to(paicard.pai)
|
||||
else if(paicard.pai)
|
||||
paicard.pai.key = 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)
|
||||
faction = initial(faction)
|
||||
|
||||
/mob/living/simple_animal/bot/proc/ejectpairemote(mob/user)
|
||||
if(check_access(user) && paicard)
|
||||
speak("Ejecting personality chip.", radio_channel)
|
||||
ejectpai(user)
|
||||
|
||||
/mob/living/simple_animal/bot/Login()
|
||||
. = ..()
|
||||
if(!. || !client)
|
||||
@@ -1019,12 +1017,6 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
|
||||
update_appearance()
|
||||
|
||||
/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(0)
|
||||
|
||||
/mob/living/simple_animal/bot/sentience_act()
|
||||
faction -= FACTION_SILICON
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
hackables = "cleaning software"
|
||||
path_image_color = "#993299"
|
||||
greyscale_config = /datum/greyscale_config/buckets_cleanbot
|
||||
possessed_message = "You are a cleanbot! Clean the station to the best of your ability!"
|
||||
///the bucket used to build us.
|
||||
var/obj/item/reagent_containers/cup/bucket/build_bucket
|
||||
|
||||
@@ -91,7 +92,7 @@
|
||||
)
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/autopatrol
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_PAI_CONTROLLABLE
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/medbay
|
||||
name = "Scrubs, MD"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
bot_type = FIRE_BOT
|
||||
hackables = "fire safety protocols"
|
||||
path_image_color = "#FFA500"
|
||||
possessed_message = "You are a firebot! Protect the station from fires to the best of your ability!"
|
||||
|
||||
var/atom/target_fire
|
||||
var/atom/old_target_fire
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
bot_type = FLOOR_BOT
|
||||
hackables = "floor construction protocols"
|
||||
path_image_color = "#FFA500"
|
||||
possessed_message = "You are a floorbot! Repair the hull to the best of your ability!"
|
||||
|
||||
var/process_type //Determines what to do when process_scan() receives a target. See process_scan() for details.
|
||||
var/targetdirection
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
radio_key = /obj/item/encryptionkey/headset_service //doesn't have security key
|
||||
radio_channel = RADIO_CHANNEL_SERVICE //Doesn't even use the radio anyway.
|
||||
bot_type = HONK_BOT
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_PAI_CONTROLLABLE | BOT_MODE_AUTOPATROL
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE | BOT_MODE_AUTOPATROL
|
||||
hackables = "sound control systems"
|
||||
path_image_color = "#FF69B4"
|
||||
data_hud_type = DATA_HUD_SECURITY_BASIC //show jobs
|
||||
@@ -17,6 +17,7 @@
|
||||
baton_type = /obj/item/bikehorn
|
||||
cuff_type = /obj/item/restraints/handcuffs/cable/zipties/fake/used
|
||||
security_mode_flags = SECBOT_CHECK_WEAPONS | SECBOT_HANDCUFF_TARGET
|
||||
possessed_message = "You are a honkbot! Make sure the crew are having a great time!"
|
||||
|
||||
///Keeping track of how much we honk to prevent spamming it
|
||||
var/limiting_spam = FALSE
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR)
|
||||
radio_key = /obj/item/encryptionkey/headset_service
|
||||
radio_channel = RADIO_CHANNEL_SERVICE //Service
|
||||
bot_mode_flags = ~BOT_MODE_PAI_CONTROLLABLE
|
||||
bot_mode_flags = ~BOT_MODE_GHOST_CONTROLLABLE
|
||||
bot_type = HYGIENE_BOT
|
||||
hackables = "cleaning service protocols"
|
||||
path_image_color = "#993299"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
data_hud_type = DATA_HUD_MEDICAL_ADVANCED
|
||||
hackables = "health processor circuits"
|
||||
path_image_color = "#DDDDFF"
|
||||
possessed_message = "You are a medbot! Ensure good health among the crew to the best of your ability!"
|
||||
|
||||
/// drop determining variable
|
||||
var/healthanalyzer = /obj/item/healthanalyzer
|
||||
@@ -71,7 +72,7 @@
|
||||
COOLDOWN_DECLARE(last_tipping_action_voice)
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/autopatrol
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_PAI_CONTROLLABLE
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/stationary
|
||||
medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
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
|
||||
@@ -135,8 +136,6 @@
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/set_id(new_id)
|
||||
id = new_id
|
||||
if(!paicard)
|
||||
name = "[initial(name)] ([new_id])"
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/bot_reset()
|
||||
..()
|
||||
@@ -263,8 +262,9 @@
|
||||
data["autoReturn"] = auto_return
|
||||
data["autoPickup"] = auto_pickup
|
||||
data["reportDelivery"] = report_delivery
|
||||
data["haspai"] = paicard ? TRUE : FALSE
|
||||
data["id"] = id
|
||||
data["allow_possession"] = bot_mode_flags & BOT_MODE_GHOST_CONTROLLABLE
|
||||
data["possession_enabled"] = can_be_possessed
|
||||
return data
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/ui_act(action, params)
|
||||
@@ -276,7 +276,7 @@
|
||||
if("lock")
|
||||
if(usr.has_unlimited_silicon_privilege)
|
||||
bot_cover_flags ^= BOT_COVER_LOCKED
|
||||
. = TRUE
|
||||
return TRUE
|
||||
if("on")
|
||||
if(bot_mode_flags & BOT_MODE_ON)
|
||||
turn_off()
|
||||
@@ -287,10 +287,10 @@
|
||||
if(!turn_on())
|
||||
to_chat(usr, span_warning("You can't switch on [src]!"))
|
||||
return
|
||||
. = TRUE
|
||||
return TRUE
|
||||
else
|
||||
bot_control(action, usr, params) // Kill this later. // Kill PDAs in general please
|
||||
. = TRUE
|
||||
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.
|
||||
@@ -342,8 +342,6 @@
|
||||
auto_pickup = !auto_pickup
|
||||
if("report")
|
||||
report_delivery = !report_delivery
|
||||
if("ejectpai")
|
||||
ejectpairemote(user)
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/proc/buzz(type)
|
||||
switch(type)
|
||||
@@ -658,7 +656,7 @@
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/MobBump(mob/M) // called when the bot bumps into a mob
|
||||
if(paicard || !isliving(M)) //if there's a PAIcard controlling the bot, they aren't allowed to harm folks.
|
||||
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.
|
||||
@@ -765,11 +763,6 @@
|
||||
else
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/insertpai(mob/user, obj/item/pai_card/card)
|
||||
. = ..()
|
||||
if(.)
|
||||
visible_message(span_notice("[src]'s safeties are locked on."))
|
||||
|
||||
/// 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
|
||||
@@ -787,6 +780,10 @@
|
||||
|
||||
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."
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
radio_key = /obj/item/encryptionkey/secbot //AI Priv + Security
|
||||
radio_channel = RADIO_CHANNEL_SECURITY //Security channel
|
||||
bot_type = SEC_BOT
|
||||
bot_mode_flags = ~BOT_MODE_PAI_CONTROLLABLE
|
||||
bot_mode_flags = ~BOT_MODE_GHOST_CONTROLLABLE
|
||||
data_hud_type = DATA_HUD_SECURITY_ADVANCED
|
||||
hackables = "target identification systems"
|
||||
path_image_color = "#FF0000"
|
||||
possessed_message = "You are a securitron! Guard the station to the best of your ability!"
|
||||
|
||||
///The type of baton this Secbot will use
|
||||
var/baton_type = /obj/item/melee/baton/security
|
||||
@@ -64,13 +65,13 @@
|
||||
/mob/living/simple_animal/bot/secbot/beepsky/ofitser
|
||||
name = "Prison Ofitser"
|
||||
desc = "Powered by the tears and sweat of laborers."
|
||||
bot_mode_flags = ~(BOT_MODE_PAI_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
bot_mode_flags = ~(BOT_MODE_GHOST_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/beepsky/armsky
|
||||
name = "Sergeant-At-Armsky"
|
||||
desc = "It's Sergeant-At-Armsky! He's a disgruntled assistant to the warden that would probably shoot you if he had hands."
|
||||
health = 45
|
||||
bot_mode_flags = ~(BOT_MODE_PAI_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
bot_mode_flags = ~(BOT_MODE_GHOST_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
security_mode_flags = SECBOT_DECLARE_ARRESTS | SECBOT_CHECK_IDS | SECBOT_CHECK_RECORDS
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/beepsky/jr
|
||||
@@ -86,7 +87,7 @@
|
||||
name = "Officer Pingsky"
|
||||
desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment."
|
||||
radio_channel = RADIO_CHANNEL_AI_PRIVATE
|
||||
bot_mode_flags = ~(BOT_MODE_PAI_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
bot_mode_flags = ~(BOT_MODE_GHOST_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
security_mode_flags = SECBOT_DECLARE_ARRESTS | SECBOT_CHECK_IDS | SECBOT_CHECK_RECORDS
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/genesky
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
bot_type = VIBE_BOT
|
||||
data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC
|
||||
path_image_color = "#2cac12"
|
||||
possessed_message = "You are a vibebot! Maintain the station's vibes to the best of your ability!"
|
||||
|
||||
///The vibe ability given to vibebots, so sentient ones can still change their color.
|
||||
var/datum/action/innate/vibe/vibe_ability
|
||||
|
||||
@@ -124,58 +124,28 @@
|
||||
faction = list(FACTION_NEUTRAL, FACTION_MONKEY, FACTION_JUNGLE)
|
||||
gold_core_spawnable = NO_SPAWN
|
||||
unique_name = FALSE
|
||||
/// Whether we're currently being polled over
|
||||
var/being_polled_for = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/gorilla/cargo_domestic/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_PACIFISM, INNATE_TRAIT)
|
||||
AddComponent(/datum/component/crate_carrier)
|
||||
|
||||
/mob/living/simple_animal/hostile/gorilla/cargo_domestic/attack_ghost(mob/user)
|
||||
if(being_polled_for || mind || client || (flags_1 & ADMIN_SPAWNED_1))
|
||||
return ..()
|
||||
|
||||
if(is_banned_from(user.ckey, list(ROLE_SENTIENCE, ROLE_SYNDICATE)))
|
||||
return ..()
|
||||
|
||||
if(!SSticker.HasRoundStarted())
|
||||
return ..()
|
||||
|
||||
var/become_gorilla = tgui_alert(user, "Become a Cargorilla?", "Confirm", list("Yes", "No"))
|
||||
if(become_gorilla != "Yes" || QDELETED(src) || QDELETED(user) || being_polled_for || mind || client)
|
||||
return
|
||||
|
||||
enter_ghost(user)
|
||||
|
||||
/// Poll ghosts for control of the gorilla.
|
||||
/mob/living/simple_animal/hostile/gorilla/cargo_domestic/proc/poll_for_gorilla()
|
||||
being_polled_for = TRUE
|
||||
var/list/mob/dead/candidates = poll_candidates_for_mob(
|
||||
"Do you want to play as a Cargorilla?",
|
||||
ROLE_SENTIENCE,
|
||||
ROLE_SENTIENCE,
|
||||
30 SECONDS,
|
||||
src,
|
||||
POLL_IGNORE_CARGORILLA
|
||||
AddComponent(\
|
||||
/datum/component/ghost_direct_control,\
|
||||
poll_candidates = TRUE,\
|
||||
poll_length = 30 SECONDS,\
|
||||
role_name = "Cargorilla",\
|
||||
assumed_control_message = "You are Cargorilla, a pacifistic friend of the station and carrier of freight.",\
|
||||
poll_ignore_key = POLL_IGNORE_CARGORILLA,\
|
||||
after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\
|
||||
)
|
||||
|
||||
being_polled_for = FALSE
|
||||
if(QDELETED(src) || mind || client)
|
||||
return
|
||||
|
||||
if(LAZYLEN(candidates))
|
||||
enter_ghost(pick(candidates))
|
||||
|
||||
/// Brings in the a ghost to take control of the gorilla.
|
||||
/mob/living/simple_animal/hostile/gorilla/cargo_domestic/proc/enter_ghost(mob/dead/user)
|
||||
key = user.key
|
||||
if(!mind)
|
||||
CRASH("[type] - enter_ghost didn't end up with a mind.")
|
||||
|
||||
/// Called once a ghost assumes control
|
||||
/mob/living/simple_animal/hostile/gorilla/cargo_domestic/proc/became_player_controlled()
|
||||
mind.set_assigned_role(SSjob.GetJobType(/datum/job/cargo_technician))
|
||||
mind.special_role = "Cargorilla"
|
||||
to_chat(src, span_boldnotice("You are a Cargorilla, a pacifistic friend of the station and carrier of freight."))
|
||||
to_chat(src, span_notice("You can pick up crates by clicking on them, and drop them by clicking on the ground."))
|
||||
|
||||
/obj/item/card/id/advanced/cargo_gorilla
|
||||
|
||||
@@ -35,50 +35,39 @@
|
||||
var/datum/action/cooldown/domain/domain
|
||||
///The Spell that the rat uses to recruit/convert more rats.
|
||||
var/datum/action/cooldown/riot/riot
|
||||
///Should we request a mind immediately upon spawning?
|
||||
var/poll_ghosts = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/regalrat/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
|
||||
AddElement(/datum/element/waddling)
|
||||
AddComponent(\
|
||||
/datum/component/ghost_direct_control,\
|
||||
poll_candidates = poll_ghosts,\
|
||||
role_name = "the Regal Rat, cheesy be their crown",\
|
||||
poll_ignore_key = POLL_IGNORE_REGAL_RAT,\
|
||||
assumed_control_message = "You are an independent, invasive force on the station! Hoard coins, trash, cheese, and the like from the safety of darkness!",\
|
||||
after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\
|
||||
)
|
||||
domain = new(src)
|
||||
riot = new(src)
|
||||
domain.Grant(src)
|
||||
riot.Grant(src)
|
||||
AddElement(/datum/element/waddling)
|
||||
|
||||
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
|
||||
|
||||
/mob/living/simple_animal/hostile/regalrat/Destroy()
|
||||
QDEL_NULL(domain)
|
||||
QDEL_NULL(riot)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/regalrat/proc/become_player_controlled(mob/user)
|
||||
log_message("took control of [name].", LOG_GAME)
|
||||
key = user.key
|
||||
notify_ghosts("All rise for the rat king, ascendant to the throne in \the [get_area(src)].", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "Sentient Rat Created")
|
||||
to_chat(src, span_notice("You are an independent, invasive force on the station! Horde coins, trash, cheese, and the like from the safety of darkness!"))
|
||||
|
||||
/mob/living/simple_animal/hostile/regalrat/proc/get_player()
|
||||
var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as the Regal Rat, cheesey be their crown?", ROLE_SENTIENCE, ROLE_SENTIENCE, 100, POLL_IGNORE_REGAL_RAT)
|
||||
if(LAZYLEN(candidates) && !mind)
|
||||
var/mob/dead/observer/candidate = pick(candidates)
|
||||
become_player_controlled(candidate)
|
||||
|
||||
/mob/living/simple_animal/hostile/regalrat/attack_ghost(mob/user)
|
||||
. = ..()
|
||||
if(. || !(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER))
|
||||
return
|
||||
if(key || stat)
|
||||
return
|
||||
if(!SSticker.HasRoundStarted())
|
||||
to_chat(user, span_warning("You cannot assume control of this until after the round has started!"))
|
||||
return
|
||||
var/rat_ask = tgui_alert(usr, "Become the Royal Rat?", "Are you sure?", list("Yes", "No"))
|
||||
if(rat_ask != "Yes" || QDELETED(src))
|
||||
return
|
||||
if(key)
|
||||
to_chat(user, span_warning("Someone else already took the rat!"))
|
||||
return
|
||||
become_player_controlled(user)
|
||||
/mob/living/simple_animal/hostile/regalrat/proc/became_player_controlled()
|
||||
notify_ghosts(
|
||||
"All rise for the rat king, ascendant to the throne in \the [get_area(src)].",
|
||||
source = src,
|
||||
action = NOTIFY_ORBIT,
|
||||
flashwindow = FALSE,
|
||||
header = "Sentient Rat Created",
|
||||
)
|
||||
|
||||
/mob/living/simple_animal/hostile/regalrat/handle_automated_action()
|
||||
if(prob(20))
|
||||
@@ -181,9 +170,11 @@
|
||||
return FALSE
|
||||
opening_airlock = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/regalrat/controlled
|
||||
poll_ghosts = TRUE
|
||||
|
||||
/mob/living/simple_animal/hostile/regalrat/controlled/Initialize(mapload)
|
||||
. = ..()
|
||||
INVOKE_ASYNC(src, PROC_REF(get_player))
|
||||
var/kingdom = pick("Plague","Miasma","Maintenance","Trash","Garbage","Rat","Vermin","Cheese")
|
||||
var/title = pick("King","Lord","Prince","Emperor","Supreme","Overlord","Master","Shogun","Bojar","Tsar")
|
||||
name = "[kingdom] [title]"
|
||||
|
||||
@@ -929,6 +929,7 @@
|
||||
#include "code\datums\components\gags_recolorable.dm"
|
||||
#include "code\datums\components\gas_leaker.dm"
|
||||
#include "code\datums\components\geiger_sound.dm"
|
||||
#include "code\datums\components\ghost_direct_control.dm"
|
||||
#include "code\datums\components\gps.dm"
|
||||
#include "code\datums\components\grillable.dm"
|
||||
#include "code\datums\components\ground_sinking.dm"
|
||||
|
||||
@@ -12,32 +12,40 @@ export const Mule = (props, context) => {
|
||||
load,
|
||||
mode,
|
||||
modeStatus,
|
||||
haspai,
|
||||
autoReturn,
|
||||
autoPickup,
|
||||
reportDelivery,
|
||||
destination,
|
||||
home,
|
||||
id,
|
||||
allow_possession,
|
||||
possession_enabled,
|
||||
destinations = [],
|
||||
} = data;
|
||||
const locked = data.locked && !data.siliconUser;
|
||||
return (
|
||||
<Window width={350} height={425}>
|
||||
<Window width={350} height={445}>
|
||||
<Window.Content>
|
||||
<InterfaceLockNoticeBox />
|
||||
<Section
|
||||
title="Status"
|
||||
minHeight="110px"
|
||||
buttons={
|
||||
!locked && (
|
||||
<>
|
||||
<Button
|
||||
icon={on ? 'power-off' : 'times'}
|
||||
content={on ? 'On' : 'Off'}
|
||||
selected={on}
|
||||
onClick={() => act('on')}
|
||||
icon="fa-poll-h"
|
||||
content="Rename"
|
||||
onClick={() => act('rename')}
|
||||
/>
|
||||
)
|
||||
{!locked && (
|
||||
<Button
|
||||
icon={on ? 'power-off' : 'times'}
|
||||
content={on ? 'On' : 'Off'}
|
||||
selected={on}
|
||||
onClick={() => act('on')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}>
|
||||
<ProgressBar
|
||||
value={cell ? cellPercent / 100 : 0}
|
||||
@@ -66,22 +74,13 @@ export const Mule = (props, context) => {
|
||||
<Section
|
||||
title="Controls"
|
||||
buttons={
|
||||
<>
|
||||
{!!load && (
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Unload"
|
||||
onClick={() => act('unload')}
|
||||
/>
|
||||
)}
|
||||
{!!haspai && (
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject PAI"
|
||||
onClick={() => act('ejectpai')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
!!load && (
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Unload"
|
||||
onClick={() => act('unload')}
|
||||
/>
|
||||
)
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="ID">
|
||||
@@ -137,6 +136,14 @@ export const Mule = (props, context) => {
|
||||
content="Report Delivery"
|
||||
onClick={() => act('report')}
|
||||
/>
|
||||
<br />
|
||||
{allow_possession && (
|
||||
<Button.Checkbox
|
||||
checked={possession_enabled}
|
||||
content="Download Personality"
|
||||
onClick={() => act('toggle_personality')}
|
||||
/>
|
||||
)}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
|
||||
@@ -8,21 +8,18 @@ type SimpleBotContext = {
|
||||
locked: number;
|
||||
emagged: number;
|
||||
has_access: number;
|
||||
pai: Pai;
|
||||
settings: Settings;
|
||||
custom_controls: Controls;
|
||||
};
|
||||
|
||||
type Pai = {
|
||||
allow_pai: number;
|
||||
card_inserted: number;
|
||||
};
|
||||
|
||||
type Settings = {
|
||||
power: number;
|
||||
airplane_mode: number;
|
||||
maintenance_lock: number;
|
||||
patrol_station: number;
|
||||
allow_possession: number;
|
||||
possession_enabled: number;
|
||||
has_personality: number;
|
||||
};
|
||||
|
||||
type Controls = {
|
||||
@@ -59,13 +56,18 @@ export const SimpleBot = (props, context) => {
|
||||
/** Creates a lock button at the top of the controls */
|
||||
const TabDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<SimpleBotContext>(context);
|
||||
const { can_hack, has_access, locked, pai } = data;
|
||||
const { allow_pai } = pai;
|
||||
const { can_hack, has_access, locked } = data;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!can_hack && <HackButton />}
|
||||
{!!allow_pai && <PaiButton />}
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="fa-poll-h"
|
||||
onClick={() => act('rename')}
|
||||
tooltip="Update the bot's name registration.">
|
||||
Rename
|
||||
</Button>
|
||||
<Button
|
||||
color="transparent"
|
||||
disabled={!has_access && !can_hack}
|
||||
@@ -101,38 +103,18 @@ const HackButton = (props, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
/** Creates a button indicating PAI status and offers the eject action */
|
||||
const PaiButton = (props, context) => {
|
||||
const { act, data } = useBackend<SimpleBotContext>(context);
|
||||
const { card_inserted } = data.pai;
|
||||
|
||||
if (!card_inserted) {
|
||||
return (
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="robot"
|
||||
tooltip={multiline`Insert an active PAI card to control this device.`}>
|
||||
No PAI Inserted
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
disabled={!card_inserted}
|
||||
icon="eject"
|
||||
onClick={() => act('eject_pai')}
|
||||
tooltip={multiline`Ejects the current PAI.`}>
|
||||
Eject PAI
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/** Displays the bot's standard settings: Power, patrol, etc. */
|
||||
const SettingsDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<SimpleBotContext>(context);
|
||||
const { settings } = data;
|
||||
const { airplane_mode, patrol_station, power, maintenance_lock } = settings;
|
||||
const {
|
||||
airplane_mode,
|
||||
patrol_station,
|
||||
power,
|
||||
maintenance_lock,
|
||||
allow_possession,
|
||||
possession_enabled,
|
||||
} = settings;
|
||||
|
||||
return (
|
||||
<LabeledControls>
|
||||
@@ -187,6 +169,23 @@ const SettingsDisplay = (props, context) => {
|
||||
/>
|
||||
</Tooltip>
|
||||
</LabeledControls.Item>
|
||||
{allow_possession && (
|
||||
<LabeledControls.Item label="Personality">
|
||||
<Tooltip
|
||||
content={
|
||||
possession_enabled
|
||||
? 'Resets personality to factory default.'
|
||||
: 'Enables download of a unique personality.'
|
||||
}>
|
||||
<Icon
|
||||
size={2}
|
||||
name="robot"
|
||||
color={possession_enabled ? 'good' : 'gray'}
|
||||
onClick={() => act('toggle_personality')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</LabeledControls.Item>
|
||||
)}
|
||||
</LabeledControls>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ type SpawnersMenuContext = {
|
||||
type spawner = {
|
||||
name: string;
|
||||
amount_left: number;
|
||||
desc?: string;
|
||||
you_are_text?: string;
|
||||
flavor_text?: string;
|
||||
important_text?: string;
|
||||
@@ -54,15 +55,23 @@ export const SpawnersMenu = (props, context) => {
|
||||
</Stack>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Origin">
|
||||
{spawner.you_are_text || 'Unknown'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Directives">
|
||||
{spawner.flavor_text || 'None'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item color="bad" label="Conditions">
|
||||
{spawner.important_text || 'None'}
|
||||
</LabeledList.Item>
|
||||
{spawner.desc ? (
|
||||
<LabeledList.Item label="Description">
|
||||
{spawner.desc}
|
||||
</LabeledList.Item>
|
||||
) : (
|
||||
<div>
|
||||
<LabeledList.Item label="Origin">
|
||||
{spawner.you_are_text || 'Unknown'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Directives">
|
||||
{spawner.flavor_text || 'None'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item color="bad" label="Conditions">
|
||||
{spawner.important_text || 'None'}
|
||||
</LabeledList.Item>
|
||||
</div>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
|
||||
Reference in New Issue
Block a user