mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-02-05 22:09:05 +00:00
* New station trait job: Human AI (#81681) This PR does many things, I'll try to explain the basic/background stuff to the main thing first: 1. Adds a new remote that allows a human to function like an AI. It controls a fly that will fly around the station slowly, and when it reaches a machine then the person can interact with it as if they were an AI. This required changing a lot of silicon/AI checks with one that also checks for this remote, and some messing with shared ui state. 2. Moves req_access from the obj and bot to ``/atom/movable`` which lets it be shared between the two, no more copy-paste and one side lacking features/checks/signals the other has. 3. Adds a check for AI config for AI-related station traits, which was lacking prior Now for the good part... Adds a new station trait that replaces the AI with a Human. This person is equipped with an AI headset (including Binary), an advanced camera console, an omni door wand, the machine controller, and their laws. They are immune to the SAT's turrets (even if set to target borgs) and are slow outside of the SAT, mimicing the actions of the AI. They interact with the world through their advanced camera console, which allows them to do most AI stuff needed, and the holopad they can connect to without having to ring first (like Command can). They are given a paper with the laws they must follow, but since they are human they are able to bend it. Cyborgs that run the default lawset are "slaved" to them via an unremovable law 0, so the Human AI can bend the laws if they really need to (for their own survival n such), and make the cyborgs obey their commands above laws, but in general this shouldn't be a frequent occurrence. This does take into account the unique AI trait, so it's not guaranteed Asimov. When this station trait rolls, all Intellicards, AI uploads, and AI core boards are destroyed and are unresearchable. They can be spawned by admins in-game if necessary. Maybe in the future we can also exclude Oldstation from this but I haven't really decided. Extra perks: Human AI spawns with a Robotic voicebox (unless they are a body purist) and teleport blocking implant, so they can't use teleporters to bypass their on-station slowdown. They also have an infinite laser pointer that can be used to blind through their camera console. This is unfortunately nerfed from the recent borg balance PR that removed its stun. This was meant to be the alternative to no longer being able to permanently lock borgs down like AIs can (or more than one, for that matter). They aren't affected by Roburgers, Acid, and Fuel's toxicity. Bots salute them like they do Beepsky (which is now a trait) They spawn with SyndEye to replace the AI's tracking ability They do not have a bank account The machine remote has a little fly in it that flies to the machines it is pointed to, working as the arms and legs of the Human AI. It scans the machine and punches in the action the AI does, and is how the AI accesses basically anything. This fly slowly moves from one machine to the next, and can be recalled with Alt Click. It works on machines and bots. https://github.com/tgstation/tgstation/assets/53777086/e16509f8-8bed-42b5-9fbf-7e37165a11e8 I've seen a funny screenshot one day of a person replacing the AI by using a bunch of door remotes, camera console, crew monitoring console, and a few other things. I've been thinking about that for a few years and really wanted to make it official if not easier to make possible, because it is an incredibly funny interaction. This makes it a reality, and while they aren't as powerful as regular AIs, I think it makes for better and funnier in-game moments. With the same weight as Cargorilla (1), I hope this wouldn't be rolling too often and ruin rounds, but instead show off the different capabilities that Humans and AIs can do, to do the job of an AI. You win some you lose some. 🆑 JohnFulpWillard, Tattax add: Adds a new station trait job: The Human AI. /🆑 --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> * Oh right * so this works * whoooops --------- Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Useroth <37159550+Useroth@users.noreply.github.com> Co-authored-by: Pinta <68373373+softcerv@users.noreply.github.com>
329 lines
12 KiB
Plaintext
329 lines
12 KiB
Plaintext
/datum/computer_file/program/budgetorders
|
|
filename = "orderapp"
|
|
filedesc = "NT IRN"
|
|
downloader_category = PROGRAM_CATEGORY_SUPPLY
|
|
program_open_overlay = "request"
|
|
extended_desc = "Nanotrasen Internal Requisition Network interface for supply purchasing using a department budget account."
|
|
program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET
|
|
can_run_on_flags = PROGRAM_LAPTOP | PROGRAM_PDA
|
|
size = 10
|
|
tgui_id = "NtosCargo"
|
|
///Are you actually placing orders with it?
|
|
var/requestonly = TRUE
|
|
///Can the tablet see or buy illegal stuff?
|
|
var/contraband = FALSE
|
|
///Is it being bought from a personal account, or is it being done via a budget/cargo?
|
|
var/self_paid = FALSE
|
|
///Can this console approve purchase requests?
|
|
var/can_approve_requests = FALSE
|
|
///What do we say when the shuttle moves with living beings on it.
|
|
var/safety_warning = "For safety and ethical reasons, the automated supply shuttle cannot transport live organisms, \
|
|
human remains, classified nuclear weaponry, mail, undelivered departmental order crates, syndicate bombs, \
|
|
homing beacons, unstable eigenstates, or machinery housing any form of artificial intelligence."
|
|
///If you're being raided by pirates, what do you tell the crew?
|
|
var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible."
|
|
///The name of the shuttle template being used as the cargo shuttle. 'cargo' is default and contains critical code. Don't change this unless you know what you're doing.
|
|
var/cargo_shuttle = "cargo"
|
|
///The docking port called when returning to the station.
|
|
var/docking_home = "cargo_home"
|
|
///The docking port called when leaving the station.
|
|
var/docking_away = "cargo_away"
|
|
///If this console can loan the cargo shuttle. Set to false to disable.
|
|
var/stationcargo = TRUE
|
|
///The account this console processes and displays. Independent from the account the shuttle processes.
|
|
var/cargo_account = ACCOUNT_CAR
|
|
|
|
/datum/computer_file/program/budgetorders/proc/is_visible_pack(mob/user, paccess_to_check, list/access, contraband)
|
|
if(HAS_SILICON_ACCESS(user)) //Borgs can't buy things.
|
|
return FALSE
|
|
if(computer.obj_flags & EMAGGED)
|
|
return TRUE
|
|
else if(contraband) //Hide contrband when non-emagged.
|
|
return FALSE
|
|
if(!paccess_to_check) // No required_access, allow it.
|
|
return TRUE
|
|
if(isAdminGhostAI(user))
|
|
return TRUE
|
|
|
|
//Aquire access from the inserted ID card.
|
|
if(!length(access))
|
|
var/obj/item/card/id/D = computer?.computer_id_slot?.GetID()
|
|
if(!D)
|
|
return FALSE
|
|
access = D.GetAccess()
|
|
|
|
if(paccess_to_check in access)
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
/datum/computer_file/program/budgetorders/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["location"] = SSshuttle.supply.getStatusText()
|
|
data["department"] = "Cargo"
|
|
var/datum/bank_account/buyer = SSeconomy.get_dep_account(cargo_account)
|
|
var/obj/item/card/id/id_card = computer.computer_id_slot?.GetID()
|
|
if(id_card?.registered_account)
|
|
if((ACCESS_COMMAND in id_card.access))
|
|
requestonly = FALSE
|
|
buyer = SSeconomy.get_dep_account(id_card.registered_account.account_job.paycheck_department)
|
|
can_approve_requests = TRUE
|
|
else
|
|
requestonly = TRUE
|
|
can_approve_requests = FALSE
|
|
if(ACCESS_COMMAND in id_card.access)
|
|
// If buyer is a departmental budget, replaces "Cargo" with that budget - we're not using the cargo budget here
|
|
data["department"] = addtext(buyer.account_holder, " Requisitions")
|
|
else
|
|
requestonly = TRUE
|
|
if(buyer)
|
|
data["points"] = buyer.account_balance
|
|
|
|
//Otherwise static data, that is being applied in ui_data as the crates visible and buyable are not static, and are determined by inserted ID.
|
|
data["requestonly"] = requestonly
|
|
data["supplies"] = list()
|
|
for(var/pack in SSshuttle.supply_packs)
|
|
var/datum/supply_pack/P = SSshuttle.supply_packs[pack]
|
|
if(!is_visible_pack(user, P.access_view , null, P.contraband) || P.hidden)
|
|
continue
|
|
if(!data["supplies"][P.group])
|
|
data["supplies"][P.group] = list(
|
|
"name" = P.group,
|
|
"packs" = list()
|
|
)
|
|
if((P.hidden && (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.drop_pod_only))
|
|
continue
|
|
data["supplies"][P.group]["packs"] += list(list(
|
|
"name" = P.name,
|
|
"cost" = P.get_cost(),
|
|
"id" = pack,
|
|
"desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name.
|
|
"goody" = P.goody,
|
|
"access" = P.access
|
|
))
|
|
|
|
//Data regarding the User's capability to buy things.
|
|
data["has_id"] = id_card
|
|
data["away"] = SSshuttle.supply.getDockedId() == docking_away
|
|
data["self_paid"] = self_paid
|
|
data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE
|
|
data["loan"] = !!SSshuttle.shuttle_loan
|
|
data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched
|
|
data["can_send"] = FALSE //There is no situation where I want the app to be able to send the shuttle AWAY from the station, but conversely is fine.
|
|
data["can_approve_requests"] = can_approve_requests
|
|
data["app_cost"] = TRUE
|
|
var/message = "Remember to stamp and send back the supply manifests."
|
|
if(SSshuttle.centcom_message)
|
|
message = SSshuttle.centcom_message
|
|
if(SSshuttle.supply_blocked)
|
|
message = blockade_warning
|
|
data["message"] = message
|
|
var/list/amount_by_name = list()
|
|
var/cart_list = list()
|
|
for(var/datum/supply_order/order in SSshuttle.shopping_list)
|
|
if(cart_list[order.pack.name])
|
|
amount_by_name[order.pack.name] += 1
|
|
cart_list[order.pack.name][1]["amount"]++
|
|
cart_list[order.pack.name][1]["cost"] += order.get_final_cost()
|
|
if(order.department_destination)
|
|
cart_list[order.pack.name][1]["dep_order"]++
|
|
if(!isnull(order.paying_account))
|
|
cart_list[order.pack.name][1]["paid"]++
|
|
continue
|
|
|
|
cart_list[order.pack.name] = list(list(
|
|
"cost_type" = order.cost_type,
|
|
"object" = order.pack.name,
|
|
"cost" = order.get_final_cost(),
|
|
"id" = order.id,
|
|
"amount" = 1,
|
|
"orderer" = order.orderer,
|
|
"paid" = !isnull(order.paying_account) ? 1 : 0, //number of orders purchased privatly
|
|
"dep_order" = order.department_destination ? 1 : 0, //number of orders purchased by a department
|
|
"can_be_cancelled" = order.can_be_cancelled,
|
|
))
|
|
data["cart"] = list()
|
|
for(var/item_id in cart_list)
|
|
data["cart"] += cart_list[item_id]
|
|
|
|
data["requests"] = list()
|
|
for(var/datum/supply_order/order in SSshuttle.request_list)
|
|
var/datum/supply_pack/pack = order.pack
|
|
amount_by_name[pack.name] += 1
|
|
data["requests"] += list(list(
|
|
"object" = pack.name,
|
|
"cost" = pack.get_cost(),
|
|
"orderer" = order.orderer,
|
|
"reason" = order.reason,
|
|
"id" = order.id
|
|
))
|
|
data["amount_by_name"] = amount_by_name
|
|
|
|
return data
|
|
|
|
/datum/computer_file/program/budgetorders/ui_static_data(mob/user)
|
|
var/list/data = list()
|
|
data["max_order"] = CARGO_MAX_ORDER
|
|
return data
|
|
|
|
/datum/computer_file/program/budgetorders/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
switch(action)
|
|
if("send")
|
|
if(!SSshuttle.supply.canMove())
|
|
computer.say(safety_warning)
|
|
return
|
|
if(SSshuttle.supply_blocked)
|
|
computer.say(blockade_warning)
|
|
return
|
|
if(SSshuttle.supply.getDockedId() == docking_home)
|
|
SSshuttle.moveShuttle(cargo_shuttle, docking_away, TRUE)
|
|
computer.say("The supply shuttle is departing.")
|
|
usr.investigate_log("sent the supply shuttle away.", INVESTIGATE_CARGO)
|
|
else
|
|
usr.investigate_log("called the supply shuttle.", INVESTIGATE_CARGO)
|
|
computer.say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.")
|
|
SSshuttle.moveShuttle(cargo_shuttle, docking_home, TRUE)
|
|
. = TRUE
|
|
if("loan")
|
|
if(!SSshuttle.shuttle_loan)
|
|
return
|
|
if(SSshuttle.supply_blocked)
|
|
computer.say(blockade_warning)
|
|
return
|
|
else if(SSshuttle.supply.mode != SHUTTLE_IDLE)
|
|
return
|
|
else if(SSshuttle.supply.getDockedId() != docking_away)
|
|
return
|
|
else if(stationcargo != TRUE)
|
|
return
|
|
else
|
|
SSshuttle.shuttle_loan.loan_shuttle()
|
|
computer.say("The supply shuttle has been loaned to CentCom.")
|
|
usr.investigate_log("accepted a shuttle loan event.", INVESTIGATE_CARGO)
|
|
usr.log_message("accepted a shuttle loan event.", LOG_GAME)
|
|
. = TRUE
|
|
if("add")
|
|
var/id = text2path(params["id"])
|
|
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
|
|
if(!istype(pack))
|
|
return
|
|
if(pack.hidden || pack.contraband || pack.drop_pod_only || (pack.special && !pack.special_enabled))
|
|
return
|
|
|
|
var/name = "*None Provided*"
|
|
var/rank = "*None Provided*"
|
|
var/ckey = usr.ckey
|
|
if(ishuman(usr))
|
|
var/mob/living/carbon/human/H = usr
|
|
name = H.get_authentification_name()
|
|
rank = H.get_assignment(hand_first = TRUE)
|
|
else if(issilicon(usr))
|
|
name = usr.real_name
|
|
rank = "Silicon"
|
|
|
|
var/datum/bank_account/account
|
|
if(self_paid)
|
|
var/mob/living/carbon/human/H = usr
|
|
var/obj/item/card/id/id_card = H.get_idcard(TRUE)
|
|
if(!istype(id_card))
|
|
computer.say("No ID card detected.")
|
|
return
|
|
if(IS_DEPARTMENTAL_CARD(id_card))
|
|
computer.say("[id_card] cannot be used to make purchases.")
|
|
return
|
|
account = id_card.registered_account
|
|
if(!istype(account))
|
|
computer.say("Invalid bank account.")
|
|
return
|
|
|
|
var/reason = ""
|
|
if((requestonly && !self_paid) || !(computer.computer_id_slot?.GetID()))
|
|
reason = tgui_input_text(usr, "Reason", name)
|
|
if(isnull(reason) || ..())
|
|
return
|
|
|
|
if(pack.goody && !self_paid)
|
|
playsound(computer, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
|
|
computer.say("ERROR: Small crates may only be purchased by private accounts.")
|
|
return
|
|
|
|
if(SSshuttle.supply.get_order_count(pack) == OVER_ORDER_LIMIT)
|
|
playsound(computer, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
|
|
computer.say("ERROR: No more then [CARGO_MAX_ORDER] of any pack may be ordered at once")
|
|
return
|
|
|
|
if(!requestonly && !self_paid && ishuman(usr) && !account)
|
|
var/obj/item/card/id/id_card = computer.computer_id_slot?.GetID()
|
|
account = SSeconomy.get_dep_account(id_card?.registered_account?.account_job.paycheck_department)
|
|
|
|
var/turf/T = get_turf(computer)
|
|
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account)
|
|
SO.generateRequisition(T)
|
|
if((requestonly && !self_paid) || !(computer.computer_id_slot?.GetID()))
|
|
SSshuttle.request_list += SO
|
|
else
|
|
SSshuttle.shopping_list += SO
|
|
if(self_paid)
|
|
computer.say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.")
|
|
. = TRUE
|
|
if("remove")
|
|
var/id = text2num(params["id"])
|
|
for(var/datum/supply_order/SO in SSshuttle.shopping_list)
|
|
if(SO.id == id)
|
|
SSshuttle.shopping_list -= SO
|
|
. = TRUE
|
|
break
|
|
if("clear")
|
|
for(var/datum/supply_order/cancelled_order in SSshuttle.shopping_list)
|
|
if(cancelled_order.department_destination || cancelled_order.can_be_cancelled)
|
|
continue //don't cancel other department's orders or orders that can't be cancelled
|
|
SSshuttle.shopping_list -= cancelled_order
|
|
. = TRUE
|
|
if("approve")
|
|
var/id = text2num(params["id"])
|
|
for(var/datum/supply_order/SO in SSshuttle.request_list)
|
|
if(SO.id == id)
|
|
var/obj/item/card/id/id_card = computer.computer_id_slot?.GetID()
|
|
if(id_card && id_card?.registered_account)
|
|
SO.paying_account = SSeconomy.get_dep_account(id_card?.registered_account?.account_job.paycheck_department)
|
|
SSshuttle.request_list -= SO
|
|
SSshuttle.shopping_list += SO
|
|
. = TRUE
|
|
break
|
|
if("deny")
|
|
var/id = text2num(params["id"])
|
|
for(var/datum/supply_order/SO in SSshuttle.request_list)
|
|
if(SO.id == id)
|
|
SSshuttle.request_list -= SO
|
|
. = TRUE
|
|
break
|
|
if("denyall")
|
|
SSshuttle.request_list.Cut()
|
|
. = TRUE
|
|
if("toggleprivate")
|
|
self_paid = !self_paid
|
|
. = TRUE
|
|
//SKYRAT EDIT START
|
|
if("company_import_window")
|
|
var/datum/component/armament/company_imports/gun_comp = computer.GetComponent(/datum/component/armament/company_imports)
|
|
if(!gun_comp)
|
|
computer.AddComponent(/datum/component/armament/company_imports, subtypesof(/datum/armament_entry/company_import), 0)
|
|
gun_comp = computer.GetComponent(/datum/component/armament/company_imports)
|
|
gun_comp.parent_prog ||= src
|
|
gun_comp.ui_interact(usr)
|
|
. = TRUE
|
|
//SKYRAT EDIT END
|
|
if(.)
|
|
post_signal(cargo_shuttle)
|
|
|
|
/datum/computer_file/program/budgetorders/proc/post_signal(command)
|
|
|
|
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
|
|
|
|
if(!frequency)
|
|
return
|
|
|
|
var/datum/signal/status_signal = new(list("command" = command))
|
|
frequency.post_signal(src, status_signal)
|