Files
Paradise/code/game/objects/items/devices/paicard.dm
warriorstar-orion 525c68d617 Attack chain, initial setup. (pull *immediately* for *any* TM issues) (#26834)
* refactor: Attack chain, initial setup.

* migrate curtain to make dreamchecker happy

* update thurible

* don't call attacked_by separately for legacy attack chain

* remove duplicate proc

* condense similar code, put allowances for legacy code in new procs

* update docs, include diagram source

* add comment on how to update diagram

* fix admonition

* mindflayer updates

* remove commented out code

* clarify all steps

* after_attack should be overridable

* whoops

* retrofit recent changes

* duh, can't restrict this yet because of tool_acts

* i hate ore bags with the fire of a thousand suns

* return correct value for object attack logic

* Various cleanups.

We don't want to attempt to pull stuff out of `/obj/item/attackby`,
because those pieces are part of the related objects' migrations, not
`/obj/item` itself. Attempting to do this causes knockon effects where
things expected to call e.g. `/obj/item/storage/attackby` in the call
chain were not ferried over to the new item interaction code, because
the related objects hadn't actually been migrated over yet.

I've used refactoring /obj/vehicle as the example for migrating
`attackby` methods instead.

* simplify some argument names

* fuck it

* make it do the thing

* Rename CI module call

* Prove that CI works

* improve test output

* aaand fix it again

* fix curtain tool interactions

* fix compile error

* fix compile error

* Better docs, introduce migration plan tool.
2024-12-02 23:36:36 +00:00

335 lines
9.4 KiB
Plaintext

/obj/item/paicard
name = "personal AI device"
desc = "A handheld device that allows you to install an artificial intelligence to be your companion."
icon = 'icons/obj/aicards.dmi'
icon_state = "pai"
item_state = "electronic"
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_BELT
origin_tech = "programming=2"
var/request_cooldown = 5 // five seconds
var/last_request
var/obj/item/radio/radio
var/looking_for_personality = 0
var/mob/living/silicon/pai/pai
var/list/faction = list("neutral") // The factions the pAI will inherit from the card
var/current_emotion = 1
resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
/obj/item/paicard/syndicate
name = "syndicate personal AI device"
faction = list("syndicate")
/obj/item/paicard/New()
..()
overlays += "pai-off"
/obj/item/paicard/Destroy()
if(pai)
pai.ghostize()
QDEL_NULL(pai)
QDEL_NULL(radio)
return ..()
/obj/item/paicard/attack_self__legacy__attackchain(mob/user)
if(!in_range(src, user))
return
user.set_machine(src)
var/dat = {"
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">
<html><meta charset='utf-8'>
<head>
<style>
body {
margin-top:5px;
font-family:Verdana;
color:white;
font-size:13px;
background-image:url('uiBackground.png');
background-repeat:repeat-x;
background-color:#272727;
background-position:center top;
}
table {
font-size:13px;
margin-left:-2px;
}
table.request {
border-collapse:collapse;
}
table.desc {
border-collapse:collapse;
font-size:13px;
border: 1px solid #161616;
width:100%;
}
table.download {
border-collapse:collapse;
font-size:13px;
border: 1px solid #161616;
width:100%;
}
tr.d0 td, tr.d0 th {
background-color: #506070;
color: white;
}
tr.d1 td, tr.d1 th {
background-color: #708090;
color: white;
}
tr.d2 td {
background-color: #00FF00;
color: white;
text-align:center;
}
td.button {
border: 1px solid #161616;
background-color: #40628a;
}
td.button {
border: 1px solid #161616;
background-color: #40628a;
text-align: center;
}
td.button_red {
border: 1px solid #161616;
background-color: #B04040;
text-align: center;
}
td.download {
border: 1px solid #161616;
background-color: #40628a;
text-align: center;
}
th {
text-align:left;
width:125px;
}
td.request {
width:140px;
vertical-align:top;
}
td.radio {
width:90px;
vertical-align:top;
}
td.request {
vertical-align:top;
}
a {
color:#4477E0;
}
a.button {
color:white;
text-decoration: none;
}
h2 {
font-size:15px;
}
</style>
</head>
<body>
"}
if(pai)
dat += {"
<b><font size='3px'>Personal AI Device</font></b><br><br>
<table class="request">
<tr>
<td class="request">Installed Personality:</td>
<td>[pai.name]</td>
</tr>
<tr>
<td class="request">Prime directive:</td>
<td>[pai.pai_law0]</td>
</tr>
<tr>
<td class="request">Additional directives:</td>
<td>[pai.pai_laws]</td>
</tr>
</table>
<br>
"}
dat += {"
<table>
<td class="button">
<a href='byond://?src=[UID()];setlaws=1' class='button'>Configure Directives</a>
</td>
</table>
"}
if(pai && (!pai.master_dna || !pai.master))
dat += {"
<table>
<td class="button">
<a href='byond://?src=[UID()];setdna=1' class='button'>Imprint Master DNA</a>
</td>
</table>
"}
dat += "<br>"
if(radio)
dat += "<b>Radio Uplink</b>"
dat += {"
<table class="request">
<tr>
<td class="radio">Transmit:</td>
<td><a href='byond://?src=[UID()];wires=4'>[radio.broadcasting ? "<font color=#55FF55>En" : "<font color=#FF5555>Dis" ]abled</font></a>
</td>
</tr>
<tr>
<td class="radio">Receive:</td>
<td><a href='byond://?src=[UID()];wires=2'>[radio.listening ? "<font color=#55FF55>En" : "<font color=#FF5555>Dis" ]abled</font></a>
</td>
</tr>
</table>
<br>
"}
else
dat += "<b>Radio Uplink</b><br>"
dat += "<font color=red><i>Radio firmware not loaded. Please install a pAI personality to load firmware.</i></font><br>"
dat += {"
<table>
<td class="button_red"><a href='byond://?src=[UID()];wipe=1' class='button'>Wipe current pAI personality</a>
</td>
</table>
"}
else
if(looking_for_personality)
dat += {"
<b><font size='3px'>pAI Request Module</font></b><br><br>
<p>Requesting AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.</p>
Searching for personalities, please wait...<br><br>
<table>
<tr>
<td class="button">
<a href='byond://?src=[UID()];request=1' class="button">Refresh available personalities</a>
</td>
</tr>
</table><br>
"}
else
dat += {"
<b><font size='3px'>pAI Request Module</font></b><br><br>
<p>No personality is installed.</p>
<table>
<tr>
<td class="button"><a href='byond://?src=[UID()];request=1' class="button">Request personality</a>
</td>
</tr>
</table>
<br>
<p>Each time this button is pressed, a request will be sent out to any available personalities. Check back often give plenty of time for personalities to respond. This process could take anywhere from 15 seconds to several minutes, depending on the available personalities' timeliness.</p>
"}
user << browse(dat, "window=paicard")
onclose(user, "paicard")
return
/obj/item/paicard/Topic(href, href_list)
var/mob/U = usr
if(!usr || usr.stat)
return
if(pai)
if(!in_range(src, U))
U << browse(null, "window=paicard")
usr.unset_machine()
return
if(href_list["setdna"])
if(pai.master_dna)
return
var/mob/M = usr
if(!iscarbon(M))
to_chat(usr, "<font color=blue>You don't have any DNA, or your DNA is incompatible with this device.</font>")
else
var/datum/dna/dna = usr.dna
pai.master = M.real_name
pai.master_dna = dna.unique_enzymes
to_chat(pai, "<font color = red><h3>You have been bound to a new master.</h3></font>")
if(href_list["request"])
var/delta = (world.time / 10) - last_request
if(request_cooldown > delta)
var/cooldown_time = round(request_cooldown - ((world.time / 10) - last_request), 1)
to_chat(usr, "<span class='warning'>The request system is currently offline. Please wait another [cooldown_time] seconds.</span>")
return
last_request = world.time / 10
looking_for_personality = 1
GLOB.paiController.findPAI(src, usr)
if(href_list["wipe"])
var/confirm = tgui_alert(usr, "Are you certain you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("No", "Yes"))
if(confirm == "Yes")
for(var/mob/M in src)
to_chat(M, "<font color = #ff0000><h2>You feel yourself slipping away from reality.</h2></font>")
to_chat(M, "<font color = #ff4d4d><h3>Byte by byte you lose your sense of self.</h3></font>")
to_chat(M, "<font color = #ff8787><h4>Your mental faculties leave you.</h4></font>")
to_chat(M, "<font color = #ffc4c4><h5>oblivion... </h5></font>")
var/mob/living/silicon/pai/P = M
if(istype(P))
if(IS_HORIZONTAL(P))
P.close_up()
M.death(0, 1)
removePersonality()
if(href_list["wires"])
var/t1 = text2num(href_list["wires"])
switch(t1)
if(4)
radio.ToggleBroadcast()
if(2)
radio.ToggleReception()
if(href_list["setlaws"])
var/newlaws = tgui_input_text(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.pai_laws)
if(newlaws)
pai.pai_laws = newlaws
to_chat(pai, "Your supplemental directives have been updated. Your new directives are:")
to_chat(pai, "Prime Directive: <br>[pai.pai_law0]")
to_chat(pai, "Supplemental Directives: <br>[pai.pai_laws]")
attack_self__legacy__attackchain(usr)
// WIRE_SIGNAL = 1
// WIRE_RECEIVE = 2
// WIRE_TRANSMIT = 4
/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality)
pai = personality
overlays += "pai-happy"
/obj/item/paicard/proc/removePersonality()
pai = null
overlays.Cut()
overlays += "pai-off"
/obj/item/paicard/proc/setEmotion(emotion)
if(pai)
overlays.Cut()
switch(emotion)
if(1) overlays += "pai-happy"
if(2) overlays += "pai-cat"
if(3) overlays += "pai-extremely-happy"
if(4) overlays += "pai-face"
if(5) overlays += "pai-laugh"
if(6) overlays += "pai-off"
if(7) overlays += "pai-sad"
if(8) overlays += "pai-angry"
if(9) overlays += "pai-what"
current_emotion = emotion
/obj/item/paicard/proc/alertUpdate()
var/turf/T = get_turf(loc)
for(var/mob/M in viewers(T))
M.show_message("<span class='notice'>[src] flashes a message across its screen, \"Additional personalities available for download.\"</span>", 3, "<span class='notice'>[src] bleeps electronically.</span>", 2)
/obj/item/paicard/emp_act(severity)
for(var/mob/M in src)
M.emp_act(severity)
..()
/obj/item/paicard/extinguish_light(force = FALSE)
if(pai)
pai.extinguish_light()
set_light(0)