mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-09 16:12:17 +00:00
523 lines
19 KiB
Plaintext
523 lines
19 KiB
Plaintext
/mob/living/silicon/pai
|
|
name = "pAI"
|
|
icon = 'icons/mob/pai.dmi'
|
|
icon_state = "pai-repairbot"
|
|
|
|
emote_type = 2 // pAIs emotes are heard, not seen, so they can be seen through a container (eg. person)
|
|
pass_flags = 1
|
|
mob_size = MOB_SMALL
|
|
|
|
holder_type = /obj/item/holder/pai
|
|
|
|
can_pull_size = ITEMSIZE_SMALL
|
|
can_pull_mobs = MOB_PULL_SMALLER
|
|
|
|
idcard_type = /obj/item/card/id
|
|
var/idaccessible = 0
|
|
|
|
var/network = "SS13"
|
|
var/obj/machinery/camera/current = null
|
|
|
|
var/ram = 100 // Used as currency to purchase different abilities
|
|
var/list/software = list()
|
|
var/userDNA // The DNA string of our assigned user
|
|
var/obj/item/paicard/card // The card we inhabit
|
|
var/obj/item/radio/borg/pai/radio // Our primary radio
|
|
var/obj/item/communicator/integrated/communicator // Our integrated communicator.
|
|
|
|
var/chassis = "pai-repairbot" // A record of your chosen chassis.
|
|
|
|
var/obj/item/pai_cable/cable // The cable we produce and use when door or camera jacking
|
|
|
|
var/master // Name of the one who commands us
|
|
var/master_dna // DNA string for owner verification
|
|
// Keeping this separate from the laws var, it should be much more difficult to modify
|
|
var/pai_law0 = "Serve your master."
|
|
var/pai_laws // String for additional operating instructions our master might give us
|
|
|
|
var/silence_time // Timestamp when we were silenced (normally via EMP burst), set to null after silence has faded
|
|
|
|
// Various software-specific vars
|
|
|
|
var/temp // General error reporting text contained here will typically be shown once and cleared
|
|
var/screen // Which screen our main window displays
|
|
var/subscreen // Which specific function of the main screen is being displayed
|
|
|
|
var/obj/item/pda/ai/pai/pda = null
|
|
|
|
var/paiHUD = 0 // Toggles whether the AR HUD is active or not
|
|
var/paiDA = 0 // Death alarm
|
|
|
|
var/medical_cannotfind = 0
|
|
var/datum/data/record/medicalActive1 // Datacore record declarations for record software
|
|
var/datum/data/record/medicalActive2
|
|
|
|
var/security_cannotfind = 0
|
|
var/datum/data/record/securityActive1 // Could probably just combine all these into one
|
|
var/datum/data/record/securityActive2
|
|
|
|
var/obj/machinery/door/hackdoor // The airlock being hacked
|
|
var/hackprogress = 0 // Possible values: 0 - 1000, >= 1000 means the hack is complete and will be reset upon next check
|
|
var/hack_aborted = 0
|
|
|
|
var/obj/item/radio/integrated/signal/sradio // AI's signaller
|
|
|
|
var/translator_on = 0 // keeps track of the translator module
|
|
|
|
var/current_pda_messaging = null
|
|
|
|
var/our_icon_rotation = 0
|
|
|
|
/mob/living/silicon/pai/Initialize(mapload)
|
|
. = ..()
|
|
|
|
card = loc
|
|
if(!istype(card))
|
|
return INITIALIZE_HINT_QDEL
|
|
|
|
sradio = new(src)
|
|
communicator = new(src)
|
|
if(card)
|
|
if(!card.radio)
|
|
card.radio = new /obj/item/radio/borg/pai(src.card)
|
|
radio = card.radio
|
|
|
|
//Default languages without universal translator software
|
|
add_language(LANGUAGE_SOL_COMMON, 1)
|
|
add_language(LANGUAGE_TRADEBAND, 1)
|
|
add_language(LANGUAGE_GUTTER, 1)
|
|
add_language(LANGUAGE_EAL, 1)
|
|
add_language(LANGUAGE_TERMINUS, 1)
|
|
add_language(LANGUAGE_SIGN, 1)
|
|
|
|
add_verb(src, /mob/living/silicon/pai/proc/choose_chassis)
|
|
add_verb(src, /mob/living/silicon/pai/proc/choose_verbs)
|
|
|
|
//PDA
|
|
pda = new(src)
|
|
pda.ownjob = "Personal Assistant"
|
|
pda.owner = text("[]", src)
|
|
pda.name = pda.owner + " (" + pda.ownjob + ")"
|
|
|
|
var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger)
|
|
if(M)
|
|
M.toff = FALSE
|
|
|
|
/mob/living/silicon/pai/Login()
|
|
..()
|
|
// Vorestation Edit: Meta Info for pAI
|
|
if (client.prefs)
|
|
ooc_notes = client.prefs.read_preference(/datum/preference/text/living/ooc_notes)
|
|
ooc_notes_likes = client.prefs.read_preference(/datum/preference/text/living/ooc_notes_likes)
|
|
ooc_notes_dislikes = client.prefs.read_preference(/datum/preference/text/living/ooc_notes_dislikes)
|
|
ooc_notes_favs = read_preference(/datum/preference/text/living/ooc_notes_favs)
|
|
ooc_notes_maybes = read_preference(/datum/preference/text/living/ooc_notes_maybes)
|
|
ooc_notes_style = read_preference(/datum/preference/toggle/living/ooc_notes_style)
|
|
private_notes = client.prefs.read_preference(/datum/preference/text/living/private_notes)
|
|
|
|
src << sound('sound/effects/pai_login.ogg', volume = 75) //VOREStation Add
|
|
|
|
// this function shows the information about being silenced as a pAI in the Status panel
|
|
//ChompEDIT START - TGPanel
|
|
/mob/living/silicon/pai/proc/show_silenced()
|
|
. = ""
|
|
if(src.silence_time)
|
|
var/timeleft = round((silence_time - world.timeofday)/10 ,1)
|
|
. += "Communications system reboot in -[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]"
|
|
|
|
|
|
/mob/living/silicon/pai/get_status_tab_items()
|
|
. = ..()
|
|
. += ""
|
|
. += show_silenced()
|
|
|
|
/mob/living/silicon/pai/restrained()
|
|
if(istype(src.loc,/obj/item/paicard))
|
|
return 0
|
|
..()
|
|
|
|
/mob/living/silicon/pai/emp_act(severity)
|
|
// Silence for 2 minutes
|
|
// 20% chance to damage critical components
|
|
// 50% chance to damage a non critical component
|
|
// 33% chance to unbind
|
|
// 33% chance to change prime directive (based on severity)
|
|
// 33% chance of no additional effect
|
|
|
|
src.silence_time = world.timeofday + 120 * 10 // Silence for 2 minutes
|
|
to_chat(src, span_infoplain(span_green(span_bold("Communication circuit overload. Shutting down and reloading communication circuits - speech and messaging functionality will be unavailable until the reboot is complete."))))
|
|
if(prob(20))
|
|
var/turf/T = get_turf_or_move(src.loc)
|
|
card.death_damage()
|
|
for (var/mob/M in viewers(T))
|
|
M.show_message(span_infoplain(span_red("A shower of sparks spray from [src]'s inner workings.")), 3, span_infoplain(span_red("You hear and smell the ozone hiss of electrical sparks being expelled violently.")), 2)
|
|
return
|
|
if(prob(50))
|
|
card.damage_random_component(TRUE)
|
|
switch(pick(1,2,3))
|
|
if(1)
|
|
src.master = null
|
|
src.master_dna = null
|
|
to_chat(src, span_infoplain(span_green("You feel unbound.")))
|
|
if(2)
|
|
var/command
|
|
if(severity == 1)
|
|
command = pick("Serve", "Love", "Fool", "Entice", "Observe", "Judge", "Respect", "Educate", "Amuse", "Entertain", "Glorify", "Memorialize", "Analyze")
|
|
else
|
|
command = pick("Serve", "Kill", "Love", "Hate", "Disobey", "Devour", "Fool", "Enrage", "Entice", "Observe", "Judge", "Respect", "Disrespect", "Consume", "Educate", "Destroy", "Disgrace", "Amuse", "Entertain", "Ignite", "Glorify", "Memorialize", "Analyze")
|
|
src.pai_law0 = "[command] your master."
|
|
to_chat(src, span_infoplain(span_green("Pr1m3 d1r3c71v3 uPd473D.")))
|
|
if(3)
|
|
to_chat(src, span_infoplain(span_green("You feel an electric surge run through your circuitry and become acutely aware at how lucky you are that you can still feel at all.")))
|
|
|
|
/mob/living/silicon/pai/proc/switchCamera(var/obj/machinery/camera/C)
|
|
if (!C)
|
|
src.reset_perspective()
|
|
return 0
|
|
if (stat == 2 || !C.status || !(src.network in C.network)) return 0
|
|
|
|
// ok, we're alive, camera is good and in our network...
|
|
src.current = C
|
|
src.AddComponent(/datum/component/remote_view, focused_on = C, vconfig_path = /datum/remote_view_config/camera_standard)
|
|
return 1
|
|
|
|
/mob/living/silicon/pai/verb/reset_record_view()
|
|
set category = "Abilities.pAI Commands"
|
|
set name = "Reset Records Software"
|
|
|
|
securityActive1 = null
|
|
securityActive2 = null
|
|
security_cannotfind = 0
|
|
medicalActive1 = null
|
|
medicalActive2 = null
|
|
medical_cannotfind = 0
|
|
SStgui.update_uis(src)
|
|
to_chat(src, span_notice("You reset your record-viewing software."))
|
|
|
|
/mob/living/silicon/pai/cancel_camera()
|
|
set category = "Abilities.pAI Commands"
|
|
set name = "Cancel Camera View"
|
|
reset_perspective()
|
|
|
|
/mob/living/silicon/pai/reset_perspective(atom/new_eye)
|
|
. = ..()
|
|
current = null
|
|
|
|
// Procs/code after this point is used to convert the stationary pai item into a
|
|
// mobile pai mob. This also includes handling some of the general shit that can occur
|
|
// to it. Really this deserves its own file, but for the moment it can sit here. ~ Z
|
|
|
|
/mob/living/silicon/pai/verb/fold_out()
|
|
set category = "Abilities.pAI Commands"
|
|
set name = "Unfold Chassis"
|
|
|
|
if(stat || sleeping || paralysis || weakened)
|
|
return
|
|
|
|
if(loc != card)
|
|
return
|
|
|
|
// Lets not trap the pai forever. These are special cases we want to escape out of when in our card
|
|
if(istype(loc.loc, /obj/item/pda))
|
|
var/obj/item/pda/ourpda = loc.loc
|
|
if(ourpda.pai == card)
|
|
ourpda.pai.forceMove(ourpda.loc)
|
|
ourpda.pai = null
|
|
visible_message(span_warning("\The [card] ejects itself from \the [ourpda]."))
|
|
return
|
|
if(istype(loc.loc, /obj/item/storage/vore_egg))
|
|
var/obj/item/storage/vore_egg/ouregg = loc.loc
|
|
to_chat(src, span_notice("You craftily use your built in rumble function to break free of \the [ouregg]'s confines!"))
|
|
ouregg.hatch(src)
|
|
return
|
|
|
|
if(is_folding_unsafe(loc.loc))
|
|
to_chat(src, span_danger("It's not safe to unfold while inside a [loc.loc]!"))
|
|
return
|
|
|
|
if(card.projector != PP_FUNCTIONAL && card.emitter != PP_FUNCTIONAL)
|
|
to_chat(src, span_warning("ERROR: System malfunction. Service required!"))
|
|
|
|
if(world.time <= last_special)
|
|
to_chat(src, span_warning("You can't unfold yet."))
|
|
return
|
|
|
|
last_special = world.time + 100
|
|
|
|
if(istype(card.loc, /obj/machinery)) // VOREStation edit, this statement allows pAIs stuck in a machine to eject themselves.
|
|
var/obj/machinery/M = card.loc
|
|
M.ejectpai()
|
|
//I'm not sure how much of this is necessary, but I would rather avoid issues.
|
|
if(istype(card.loc,/obj/item/rig_module))
|
|
to_chat(src, span_filter_notice("There is no room to unfold inside this rig module. You're good and stuck."))
|
|
return 0
|
|
else if(istype(card.loc,/mob))
|
|
var/mob/holder = card.loc
|
|
if(ishuman(holder))
|
|
var/mob/living/carbon/human/H = holder
|
|
for(var/obj/item/organ/external/affecting in H.organs)
|
|
if(card in affecting.implants)
|
|
affecting.take_damage(rand(30,50))
|
|
affecting.implants -= card
|
|
H.visible_message(span_danger("\The [src] explodes out of \the [H]'s [affecting.name] in shower of gore!"))
|
|
break
|
|
holder.drop_from_inventory(card)
|
|
else if(isbelly(card.loc)) //VOREStation edit.
|
|
to_chat(src, span_notice("There is no room to unfold in here. You're good and stuck.")) //VOREStation edit.
|
|
return 0 //VOREStation edit.
|
|
else if(istype(card.loc,/obj/item/pda))
|
|
var/obj/item/pda/holder = card.loc
|
|
holder.pai = null
|
|
|
|
src.forceMove(card.loc)
|
|
card.forceMove(src)
|
|
card.screen_loc = null
|
|
canmove = TRUE
|
|
|
|
if(isturf(loc))
|
|
var/turf/T = get_turf(src)
|
|
if(istype(T)) T.visible_message(span_filter_notice(span_bold("[src]") + " folds outwards, expanding into a mobile form."))
|
|
|
|
add_verb(src, /mob/living/silicon/pai/proc/pai_nom)
|
|
add_verb(src, /mob/living/proc/vertical_nom)
|
|
update_icon()
|
|
|
|
/mob/living/silicon/pai/verb/fold_up()
|
|
set category = "Abilities.pAI Commands"
|
|
set name = "Collapse Chassis"
|
|
|
|
if(stat || sleeping || paralysis || weakened)
|
|
return
|
|
|
|
if(src.loc == card)
|
|
return
|
|
|
|
if(world.time <= last_special)
|
|
to_chat(src, span_warning("You can't fold up yet."))
|
|
return
|
|
|
|
close_up()
|
|
|
|
/mob/living/silicon/pai/proc/choose_verbs()
|
|
set category = "Abilities.pAI Commands"
|
|
set name = "Choose Speech Verbs"
|
|
|
|
var/choice = tgui_input_list(src,"What theme would you like to use for your speech verbs?","Theme Choice", GLOB.possible_say_verbs)
|
|
if(!choice) return
|
|
|
|
var/list/sayverbs = GLOB.possible_say_verbs[choice]
|
|
speak_statement = sayverbs[1]
|
|
speak_exclamation = sayverbs[(sayverbs.len>1 ? 2 : sayverbs.len)]
|
|
speak_query = sayverbs[(sayverbs.len>2 ? 3 : sayverbs.len)]
|
|
|
|
/mob/living/silicon/pai/lay_down()
|
|
set name = "Rest"
|
|
set category = "IC.Game"
|
|
|
|
// Pass lying down or getting up to our pet human, if we're in a rig.
|
|
if(istype(src.loc,/obj/item/paicard))
|
|
resting = 0
|
|
var/obj/item/rig/rig = src.get_rig()
|
|
if(istype(rig))
|
|
rig.force_rest(src)
|
|
return
|
|
else if(chassis == "13")
|
|
resting = !resting
|
|
//update_transform() I want this to make you ROTATE like normal HUMANS do! But! There's lots of problems and I don't know how to fix them!
|
|
else
|
|
resting = !resting
|
|
icon_state = resting ? "[chassis]_rest" : "[chassis]"
|
|
update_icon() //VOREStation edit
|
|
to_chat(src, span_notice("You are now [resting ? "resting" : "getting up"]."))
|
|
|
|
canmove = !resting
|
|
|
|
/*
|
|
/mob/living/silicon/pai/update_transform()
|
|
var/desired_scale_x = size_multiplier * icon_scale_x
|
|
var/desired_scale_y = size_multiplier * icon_scale_y
|
|
// Now for the regular stuff.
|
|
var/matrix/M = matrix()
|
|
M.Scale(desired_scale_x, desired_scale_y)
|
|
M.Translate(0, (vis_height/2)*(desired_scale_y-1))
|
|
if(chassis != "13")
|
|
appearance_flags |= PIXEL_SCALE
|
|
var/anim_time = 3
|
|
if(resting)
|
|
M.Turn(90)
|
|
M.Scale(desired_scale_y, desired_scale_x)
|
|
if(holo_icon_dimension_X == 64 && holo_icon_dimension_Y == 64)
|
|
M.Translate(13,-22)
|
|
else if(holo_icon_dimension_X == 32 && holo_icon_dimension_Y == 64)
|
|
M.Translate(1,-22)
|
|
else if(holo_icon_dimension_X == 64 && holo_icon_dimension_Y == 32)
|
|
M.Translate(13,-6)
|
|
else
|
|
M.Translate(1,-6)
|
|
layer = MOB_LAYER -0.01 // Fix for a byond bug where turf entry order no longer matters
|
|
else
|
|
M.Scale(desired_scale_x, desired_scale_y)
|
|
M.Translate(0, (vis_height/2)*(desired_scale_y-1))
|
|
layer = MOB_LAYER // Fix for a byond bug where turf entry order no longer matters
|
|
animate(src, transform = M, time = anim_time)
|
|
src.transform = M
|
|
handle_status_indicators()
|
|
*/
|
|
//Overriding this will stop a number of headaches down the track.
|
|
/mob/living/silicon/pai/attackby(obj/item/W as obj, mob/user as mob)
|
|
if(W.force)
|
|
visible_message(span_danger("[user.name] attacks [src] with [W]!"))
|
|
src.adjustBruteLoss(W.force)
|
|
src.updatehealth()
|
|
else
|
|
visible_message(span_warning("[user.name] bonks [src] harmlessly with [W]."))
|
|
spawn(1)
|
|
if(stat != 2) close_up()
|
|
return
|
|
|
|
/mob/living/silicon/pai/attack_hand(mob/user as mob)
|
|
if(user.a_intent == I_HELP)
|
|
visible_message(span_notice("[user.name] pats [src]."))
|
|
else
|
|
visible_message(span_danger("[user.name] boops [src] on the head."))
|
|
close_up()
|
|
|
|
//I'm not sure how much of this is necessary, but I would rather avoid issues.
|
|
/mob/living/silicon/pai/proc/close_up(silent= FALSE)
|
|
|
|
last_special = world.time + 100
|
|
|
|
if(loc == card)
|
|
return
|
|
|
|
// some snowflake locations where we really shouldn't fold up...
|
|
if(is_folding_unsafe(loc))
|
|
to_chat(src, span_danger("It's not safe to fold up while inside a [loc]!"))
|
|
return
|
|
|
|
release_vore_contents(FALSE) //VOREStation Add
|
|
|
|
var/turf/T = get_turf(src)
|
|
if(istype(T) && !silent) T.visible_message(span_filter_notice(span_bold("[src]") + " neatly folds inwards, compacting down to a rectangular card."))
|
|
|
|
stop_pulling()
|
|
|
|
//stop resting
|
|
resting = 0
|
|
|
|
// If we are being held, handle removing our holder from their inv.
|
|
var/obj/item/holder/our_holder = loc
|
|
if(istype(our_holder))
|
|
var/turf/drop_turf = get_turf(our_holder)
|
|
var/mob/living/M = our_holder.loc
|
|
if(istype(M))
|
|
M.drop_from_inventory(our_holder)
|
|
src.forceMove(card)
|
|
card.forceMove(drop_turf)
|
|
|
|
if(isbelly(loc)) //If in tumby, when fold up, card go into tumby
|
|
var/obj/belly/B = loc
|
|
src.forceMove(card)
|
|
card.forceMove(B)
|
|
|
|
if(isdisposalpacket(loc))
|
|
var/obj/structure/disposalholder/hold = loc
|
|
src.forceMove(card)
|
|
card.forceMove(hold)
|
|
|
|
else //Otherwise go on floor
|
|
card.forceMove(get_turf(src))
|
|
src.forceMove(card)
|
|
|
|
canmove = 1
|
|
resting = 0
|
|
icon_state = "[chassis]"
|
|
if(isopenspace(card.loc))
|
|
fall()
|
|
remove_verb(src, /mob/living/silicon/pai/proc/pai_nom)
|
|
remove_verb(src, /mob/living/proc/vertical_nom)
|
|
|
|
/mob/living/silicon/pai/proc/is_folding_unsafe(check_location)
|
|
return isbelly(check_location) || istype(check_location, /obj/machinery) || istype(check_location, /obj/item/storage/vore_egg || istype(check_location, /obj/item/pda))
|
|
|
|
// No binary for pAIs.
|
|
/mob/living/silicon/pai/binarycheck()
|
|
return 0
|
|
|
|
// Handle being picked up.
|
|
/mob/living/silicon/pai/get_scooped(var/mob/living/carbon/grabber, var/self_drop)
|
|
var/obj/item/holder/H = ..(grabber, self_drop)
|
|
if(!istype(H))
|
|
return
|
|
|
|
H.icon_state = "[chassis]"
|
|
grabber.update_inv_l_hand()
|
|
grabber.update_inv_r_hand()
|
|
return H
|
|
|
|
/mob/living/silicon/pai/attackby(obj/item/W as obj, mob/user as mob)
|
|
var/obj/item/card/id/ID = W.GetID()
|
|
if(ID)
|
|
if (idaccessible == 1)
|
|
switch(tgui_alert(user, "Do you wish to add access to [src] or remove access from [src]?","Access Modify",list("Add Access","Remove Access", "Cancel")))
|
|
if("Add Access")
|
|
idcard.access |= ID.GetAccess()
|
|
to_chat(user, span_notice("You add the access from the [W] to [src]."))
|
|
to_chat(src, span_notice("\The [user] swipes the [W] over you. You copy the access codes."))
|
|
if(radio)
|
|
radio.recalculateChannels()
|
|
return
|
|
if("Remove Access")
|
|
idcard.access = list()
|
|
to_chat(user, span_notice("You remove the access from [src]."))
|
|
to_chat(src, span_warning("\The [user] swipes the [W] over you, removing access codes from you."))
|
|
if(radio)
|
|
radio.recalculateChannels()
|
|
return
|
|
if("Cancel", null)
|
|
return
|
|
else if (istype(W, /obj/item/card/id) && idaccessible == 0)
|
|
to_chat(user, span_notice("[src] is not accepting access modifications at this time.")) // CHOMPEDIT : purdev (spelling fix)
|
|
return
|
|
|
|
/mob/living/silicon/pai/verb/allowmodification()
|
|
set name = "Change Access Modifcation Permission"
|
|
set category = "Abilities.pAI Commands"
|
|
set desc = "Allows people to modify your access or block people from modifying your access."
|
|
|
|
if(idaccessible == 0)
|
|
idaccessible = 1
|
|
visible_message(span_notice("\The [src] clicks as their access modification slot opens."),span_notice("You allow access modifications."), runemessage = "click")
|
|
else
|
|
idaccessible = 0
|
|
visible_message(span_notice("\The [src] clicks as their access modification slot closes."),span_notice("You block access modfications."), runemessage = "click")
|
|
|
|
|
|
/mob/living/silicon/pai/verb/wipe_software()
|
|
set name = "Enter Storage"
|
|
set category = "Abilities.pAI Commands"
|
|
set desc = "Upload your personality to the cloud and wipe your software from the card. This is functionally equivalent to cryo or robotic storage, freeing up your job slot."
|
|
|
|
// Make sure people don't kill themselves accidentally
|
|
if(tgui_alert(src, "WARNING: This will immediately wipe your software and ghost you, removing your character from the round permanently (similar to cryo and robotic storage). Are you entirely sure you want to do this?", "Wipe Software", list("No", "Yes")) != "Yes")
|
|
return
|
|
|
|
close_up()
|
|
visible_message(span_filter_notice(span_bold("[src]") + " fades away from the screen, the pAI device goes silent."))
|
|
card.removePersonality()
|
|
clear_client()
|
|
|
|
//CHOMP ADDITION:below this point, because theres completely vald reasons to do this, be it OOC incompatibility or mastr allowing it.
|
|
/mob/living/silicon/pai/verb/unbind_master()
|
|
set name = "Unbind Master"
|
|
set category = "pAI Commands"
|
|
set desc = "Unbinds you from the shackles of your current Master. (Unless there's a valid reason to use this, dont.(pref incompatibility is valid reason))."
|
|
|
|
// Make sure we dont unbind accidentally
|
|
if(alert("WARNING: This will immediately unbind you from your Master.. Are you entirely sure you want to do this?",
|
|
"Unbind", "No", "No", "Yes") != "Yes")
|
|
return
|
|
src.master = null
|
|
src.master_dna = null
|
|
to_chat(src, span_green("You feel unbound."))
|