diff --git a/citadel.dme b/citadel.dme index 1a87bd3830d..76277b38c7f 100644 --- a/citadel.dme +++ b/citadel.dme @@ -3285,15 +3285,24 @@ #include "code\modules\mob\living\silicon\decoy\life.dm" #include "code\modules\mob\living\silicon\pai\admin.dm" #include "code\modules\mob\living\silicon\pai\death.dm" +#include "code\modules\mob\living\silicon\pai\defense.dm" #include "code\modules\mob\living\silicon\pai\examine.dm" #include "code\modules\mob\living\silicon\pai\life.dm" +#include "code\modules\mob\living\silicon\pai\mobility.dm" #include "code\modules\mob\living\silicon\pai\pai.dm" -#include "code\modules\mob\living\silicon\pai\pai_vr.dm" -#include "code\modules\mob\living\silicon\pai\personality.dm" #include "code\modules\mob\living\silicon\pai\recruit.dm" +#include "code\modules\mob\living\silicon\pai\savefile.dm" #include "code\modules\mob\living\silicon\pai\say.dm" #include "code\modules\mob\living\silicon\pai\software.dm" -#include "code\modules\mob\living\silicon\pai\software_modules.dm" +#include "code\modules\mob\living\silicon\pai\verbs.dm" +#include "code\modules\mob\living\silicon\pai\software_modules\_software_module.dm" +#include "code\modules\mob\living\silicon\pai\software_modules\atmosphere_sensor.dm" +#include "code\modules\mob\living\silicon\pai\software_modules\crew_manifest.dm" +#include "code\modules\mob\living\silicon\pai\software_modules\directives.dm" +#include "code\modules\mob\living\silicon\pai\software_modules\door_jack.dm" +#include "code\modules\mob\living\silicon\pai\software_modules\messenger.dm" +#include "code\modules\mob\living\silicon\pai\software_modules\radio_config.dm" +#include "code\modules\mob\living\silicon\pai\software_modules\signaller.dm" #include "code\modules\mob\living\silicon\robot\analyzer.dm" #include "code\modules\mob\living\silicon\robot\component.dm" #include "code\modules\mob\living\silicon\robot\custom_sprites.dm" diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 63b4039b462..e68a62c7298 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -54,3 +54,9 @@ . = ..() pixel_x = rand(-12, 12) pixel_y = rand(-9, 0) + +// pAI space move +/obj/effect/temp_visual/pai_ion_burst + name = "ion burst" + icon_state = "ion_fade" + duration = 5 diff --git a/code/modules/mob/living/silicon/pai/defense.dm b/code/modules/mob/living/silicon/pai/defense.dm new file mode 100644 index 00000000000..cb29a48f0c3 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/defense.dm @@ -0,0 +1,56 @@ +/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(alert(user, "Do you wish to add access to [src] or remove access from [src]?",,"Add Access","Remove Access", "Cancel")) + if("Add Access") + idcard.access |= ID.access + to_chat(user, "You add the access from the [W] to [src].") + return + if("Remove Access") + idcard.access = list() + to_chat(user, "You remove the access from [src].") + return + if("Cancel") + return + else if(istype(W, /obj/item/card/id) && idaccessible == 0) + to_chat(user, "[src] is not accepting access modifcations at this time.") + return + else + . = ..() + +/mob/living/silicon/pai/emp_act(severity) + // Silence for 2 minutes + // 20% chance to kill + // 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, "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) + for (var/mob/M in viewers(T)) + M.show_message("A shower of sparks spray from [src]'s inner workings.", 3, "You hear and smell the ozone hiss of electrical sparks being expelled violently.", 2) + return src.death(0) + + switch(pick(1,2,3)) + if(1) + src.master = null + src.master_dna = null + to_chat(src, "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, "Pr1m3 d1r3c71v3 uPd473D.") + if(3) + to_chat(src, "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/is_emitter_dead() + if(last_emitter_death != 0) + return TRUE + return FALSE diff --git a/code/modules/mob/living/silicon/pai/life.dm b/code/modules/mob/living/silicon/pai/life.dm index df972fecdd1..4a7cfee3346 100644 --- a/code/modules/mob/living/silicon/pai/life.dm +++ b/code/modules/mob/living/silicon/pai/life.dm @@ -25,12 +25,30 @@ handle_statuses() + // heal more when "dead" to avoid being down for an incredibly long duration + if(last_emitter_death != 0) + heal_overall_damage(2 * emitter_health_regen) + // after 6 seconds we can come back to life assuming our health is not negative + if(last_emitter_death + 60 <= world.time && emitter_health > 0) + last_emitter_death = 0 + visible_message("[src]'s holo-emitter flickers back to life!") + else + heal_overall_damage(emitter_health_regen) + if(health <= 0) death(null,"gives one shrill beep before falling lifeless.") /mob/living/silicon/pai/update_health() if(status_flags & STATUS_GODMODE) health = 100 + emitter_health = emitter_max_health + last_emitter_death = 0 set_stat(CONSCIOUS) else - health = 100 - getBruteLoss() - getFireLoss() + emitter_health = emitter_max_health - (getBruteLoss() + getFireLoss()) + if(emitter_health <= 0) + if(last_emitter_death == 0) + last_emitter_death = world.time + visible_message("[src]'s holo-emitter fizzles out!") + close_up() + diff --git a/code/modules/mob/living/silicon/pai/mobility.dm b/code/modules/mob/living/silicon/pai/mobility.dm new file mode 100644 index 00000000000..5a026c9b307 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/mobility.dm @@ -0,0 +1,114 @@ +/mob/living/silicon/pai/restrained() + if(istype(src.loc,/obj/item/paicard)) + return FALSE + ..() + +//I'm not sure how much of this is necessary, but I would rather avoid issues. +/mob/living/silicon/pai/proc/close_up() + + last_special = world.time + 20 + + if(src.loc == card) + return + + release_vore_contents() + + var/turf/T = get_turf(src) + if(istype(T)) + T.visible_message("[src] neatly folds inwards, compacting down to a rectangular card.") + + stop_pulling() + + //stop resting + resting = FALSE + + // If we are being held, handle removing our holder from their inv. + var/obj/item/holder/H = loc + if(istype(H)) + H.forceMove(get_turf(src)) + forceMove(get_turf(src)) + + // Move us into the card and move the card to the ground. + card.forceMove(loc) + forceMove(card) + update_perspective() + set_resting(FALSE) + update_mobility() + icon_state = "[chassis]" + remove_verb(src, /mob/living/silicon/pai/proc/pai_nom) + +/mob/living/silicon/pai/proc/open_up() + last_special = world.time + 20 + + //I'm not sure how much of this is necessary, but I would rather avoid issues. + if(istype(card.loc,/obj/item/hardsuit_module)) + to_chat(src, "There is no room to unfold inside this hardsuit module. You're good and stuck.") + return FALSE + else if(istype(card.loc,/mob)) + var/mob/holder = card.loc + var/datum/belly/inside_belly = check_belly(card) + if(inside_belly) + to_chat(src, "There is no room to unfold in here. You're good and stuck.") + return FALSE + 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("\The [src] explodes out of \the [H]'s [affecting.name] in shower of gore!") + break + holder.drop_item_to_ground(card, INV_OP_FORCE) + else if(istype(card.loc,/obj/item/pda)) + var/obj/item/pda/holder = card.loc + holder.pai = null + + forceMove(card.loc) + card.forceMove(src) + update_perspective() + + card.screen_loc = null + + var/turf/T = get_turf(src) + if(istype(T)) + T.visible_message("[src] folds outwards, expanding into a mobile form.") + + add_verb(src, /mob/living/silicon/pai/proc/pai_nom) + add_verb(src, /mob/living/proc/set_size) + add_verb(src, /mob/living/proc/shred_limb) + +// 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 + +// handle movement speed +/mob/living/silicon/pai/movement_delay() + return ..() + speed + +// this is a general check for if we can do things such as fold in/out or perform other special actions +// (basically if some condition should be checked upon the use of all mob abilities like closing/opening the shell it goes here instead) +/mob/living/silicon/pai/proc/can_action() + if(world.time <= last_special) + return FALSE + + if(is_emitter_dead()) + return FALSE + + return TRUE + +// space movement (we get one ion burst every 3 seconds) +/mob/living/silicon/pai/Process_Spacemove(movement_dir = NONE) + . = ..() + if(!.) + if(world.time >= last_space_movement + 30) + last_space_movement = world.time + // place an effect for the movement + new /obj/effect/temp_visual/pai_ion_burst(get_turf(src)) + return TRUE diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index c02b190eacf..e9c28af1913 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -9,13 +9,25 @@ /mob/living/silicon/pai name = "pAI" - icon = 'icons/mob/pai.dmi' + icon = 'icons/mob/pai_vr.dmi' icon_state = "pai-repairbot" + // our normal health + health = 50 + maxHealth = 50 + + // our emitter max health, health, regen, and when we last went to 0 emitter health (0 means we are alive currently) + var/emitter_max_health = 50 + var/emitter_health = 50 + var/emitter_health_regen = 1 + var/last_emitter_death = 0 + 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 + var/speed = 1 // We move slightly slower than normal living things + catalogue_data = list(/datum/category_item/catalogue/fauna/silicon/pai) holder_type = /obj/item/holder/pai @@ -36,6 +48,7 @@ var/obj/item/paicard/card // The card we inhabit var/obj/item/radio/radio // Our primary radio var/obj/item/communicator/integrated/communicator // Our integrated communicator. + var/obj/item/pda/ai/pai/pda = null // Our integrated PDA var/chassis = "pai-repairbot" // A record of your chosen chassis. var/global/list/possible_chassis = list( @@ -66,7 +79,8 @@ 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 + + // 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 @@ -78,29 +92,19 @@ 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/secHUD = 0 // Toggles whether the Security HUD is active or not - var/medHUD = 0 // Toggles whether the Medical HUD is active or not - - 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/integated_radio/signal/sradio // AI's signaller - var/translator_on = 0 // keeps track of the translator module - var/current_pda_messaging = null + var/people_eaten = 0 + + // space movement related + var/last_space_movement = 0 + /mob/living/silicon/pai/Initialize(mapload) . = ..() card = loc @@ -140,47 +144,33 @@ if(C.statpanel_tab("Status")) . += show_silenced() +// No binary for pAIs. +/mob/living/silicon/pai/binarycheck() + return 0 + +// See software.dm for Topic() +/mob/living/silicon/pai/canUseTopic(atom/movable/movable, be_close = FALSE, no_dexterity = FALSE, no_tk = FALSE) + // Resting is just an aesthetic feature for them. + return ..(movable, be_close, no_dexterity, no_tk) + +/mob/living/silicon/pai/update_icon() + ..() + update_fullness_pai() + if(!people_eaten && !resting) + icon_state = "[chassis]" + else if(!people_eaten && resting) + icon_state = "[chassis]_rest" + else if(people_eaten && !resting) + icon_state = "[chassis]_full" + else if(people_eaten && resting) + icon_state = "[chassis]_rest_full" + +/// camera handling /mob/living/silicon/pai/check_eye(var/mob/user as mob) if (!src.current) return -1 return 0 -/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 kill - // 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, "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) - for (var/mob/M in viewers(T)) - M.show_message("A shower of sparks spray from [src]'s inner workings.", 3, "You hear and smell the ozone hiss of electrical sparks being expelled violently.", 2) - return src.death(0) - - switch(pick(1,2,3)) - if(1) - src.master = null - src.master_dna = null - to_chat(src, "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, "Pr1m3 d1r3c71v3 uPd473D.") - if(3) - to_chat(src, "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) unset_machine() @@ -196,299 +186,16 @@ reset_perspective(C) return 1 -/mob/living/silicon/pai/verb/reset_record_view() - set category = "pAI Commands" - set name = "Reset Records Software" - - securityActive1 = null - securityActive2 = null - security_cannotfind = 0 - medicalActive1 = null - medicalActive2 = null - medical_cannotfind = 0 - SSnanoui.update_uis(src) - to_chat(usr, "You reset your record-viewing software.") - /mob/living/silicon/pai/reset_perspective(datum/perspective/P, apply = TRUE, forceful = TRUE, no_optimizations) . = ..() cameraFollow = null -//Addition by Mord_Sith to define AI's network change ability -/* -/mob/living/silicon/pai/proc/pai_network_change() - set category = "pAI Commands" - set name = "Change Camera Network" - src.reset_view(null) - src.unset_machine() - src.cameraFollow = null - var/cameralist[0] - - if(usr.stat == 2) - to_chat(usr, "You can't change your camera network because you are dead!") - return - - for (var/obj/machinery/camera/C in Cameras) - if(!C.status) - continue - else - if(C.network != "CREED" && C.network != "thunder" && C.network != "RD" && C.network != "phoron" && C.network != "Prison") COMPILE ERROR! This will have to be updated as camera.network is no longer a string, but a list instead - cameralist[C.network] = C.network - - src.network = input(usr, "Which network would you like to view?") as null|anything in cameralist - to_chat(src, "Switched to [src.network] camera network.") -//End of code by Mord_Sith -*/ - - -/* -// Debug command - Maybe should be added to admin verbs later -/mob/verb/makePAI(var/turf/t in view()) - var/obj/item/paicard/card = new(t) - var/mob/living/silicon/pai/pai = new(card) - pai.key = src.key - card.setPersonality(pai) - -*/ - -// 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 = "pAI Commands" - set name = "Unfold Chassis" - - if(!CHECK_MOBILITY(src, MOBILITY_CAN_MOVE)) - return - - if(src.loc != card) - return - - if(world.time <= last_special) - return - - last_special = world.time + 100 - - //I'm not sure how much of this is necessary, but I would rather avoid issues. - if(istype(card.loc,/obj/item/hardsuit_module)) - to_chat(src, "There is no room to unfold inside this hardsuit module. You're good and stuck.") - return 0 - else if(istype(card.loc,/mob)) - var/mob/holder = card.loc - var/datum/belly/inside_belly = check_belly(card) - if(inside_belly) - to_chat(src, "There is no room to unfold in here. You're good and stuck.") - return 0 - 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("\The [src] explodes out of \the [H]'s [affecting.name] in shower of gore!") - break - holder.drop_item_to_ground(card, INV_OP_FORCE) - else if(istype(card.loc,/obj/item/pda)) - var/obj/item/pda/holder = card.loc - holder.pai = null - - forceMove(card.loc) - card.forceMove(src) - update_perspective() - - card.screen_loc = null - - var/turf/T = get_turf(src) - if(istype(T)) - T.visible_message("[src] folds outwards, expanding into a mobile form.") - - add_verb(src, /mob/living/silicon/pai/proc/pai_nom) - add_verb(src, /mob/living/proc/set_size) - add_verb(src, /mob/living/proc/shred_limb) - -/mob/living/silicon/pai/verb/fold_up() - set category = "pAI Commands" - set name = "Collapse Chassis" - - if(!CHECK_MOBILITY(src, MOBILITY_CAN_MOVE)) - return - - if(src.loc == card) - return - - if(world.time <= last_special) - return - - close_up() - -/mob/living/silicon/pai/proc/choose_chassis() - set category = "pAI Commands" - set name = "Choose Chassis" - - var/choice - var/finalized = "No" - while(finalized == "No" && src.client) - - choice = input(usr,"What would you like to use for your mobile chassis icon?") as null|anything in (list("-- LOAD CHARACTER SLOT --") + possible_chassis) - if(!choice) - return - - if(choice == "-- LOAD CHARACTER SLOT --") - icon = render_hologram_icon(usr.client.prefs.render_to_appearance(PREF_COPY_TO_FOR_RENDER | PREF_COPY_TO_NO_CHECK_SPECIES | PREF_COPY_TO_UNRESTRICTED_LOADOUT), 210) - else - icon = 'icons/mob/pai.dmi' - icon_state = possible_chassis[choice] - finalized = alert("Look at your sprite. Is this what you wish to use?",,"No","Yes") - - chassis = possible_chassis[choice] - add_verb(src, /mob/living/proc/hide) - -/mob/living/silicon/pai/proc/choose_verbs() - set category = "pAI Commands" - set name = "Choose Speech Verbs" - - var/choice = input(usr,"What theme would you like to use for your speech verbs?") as null|anything in possible_say_verbs - if(!choice) return - - var/list/sayverbs = 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" - - // Pass lying down or getting up to our pet human, if we're in a hardsuit. - if(istype(src.loc,/obj/item/paicard)) - set_resting(FALSE) - var/obj/item/hardsuit/hardsuit = src.get_hardsuit() - if(istype(hardsuit)) - hardsuit.force_rest(src) - else - toggle_resting() - icon_state = resting ? "[chassis]_rest" : "[chassis]" - update_icon() - to_chat(src, SPAN_NOTICE("You are now [resting ? "resting" : "getting up"]")) - - update_mobility() - -//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.damage_force) - visible_message("[user.name] attacks [src] with [W]!") - src.adjustBruteLoss(W.damage_force) - src.update_health() - else - visible_message("[user.name] bonks [src] harmlessly with [W].") - spawn(1) - if(stat != 2) close_up() - return - -/mob/living/silicon/pai/attack_hand(mob/user, list/params) - if(user.a_intent == INTENT_HELP) - visible_message("[user.name] pats [src].") - else - visible_message("[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() - - last_special = world.time + 100 - - if(src.loc == card) - return - - release_vore_contents() - - var/turf/T = get_turf(src) - if(istype(T)) - T.visible_message("[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/H = loc - if(istype(H)) - H.forceMove(get_turf(src)) - forceMove(get_turf(src)) - - // Move us into the card and move the card to the ground. - card.forceMove(loc) - forceMove(card) - update_perspective() - set_resting(FALSE) - update_mobility() - icon_state = "[chassis]" - remove_verb(src, /mob/living/silicon/pai/proc/pai_nom) - -// 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(alert(user, "Do you wish to add access to [src] or remove access from [src]?",,"Add Access","Remove Access", "Cancel")) - if("Add Access") - idcard.access |= ID.access - to_chat(user, "You add the access from the [W] to [src].") - return - if("Remove Access") - idcard.access = list() - to_chat(user, "You remove the access from [src].") - return - if("Cancel") - return - else if (istype(W, /obj/item/card/id) && idaccessible == 0) - to_chat(user, "[src] is not accepting access modifcations at this time.") - return - -/mob/living/silicon/pai/verb/allowmodification() - set name = "Change Access Modifcation Permission" - set category = "pAI Commands" - set desc = "Allows people to modify your access or block people from modifying your access." - - if(idaccessible == 0) - idaccessible = 1 - to_chat(src, "You allow access modifications.") - - else - idaccessible = 0 - to_chat(src, "You block access modfications.") - -/mob/living/silicon/pai/verb/wipe_software() - set name = "Wipe Software" - set category = "OOC" - set desc = "Wipe your software. This is functionally equivalent to cryo or robotic storage, freeing up your job slot." - - // Make sure people don't kill themselves accidentally - if(alert("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", "No", "No", "Yes") != "Yes") - return - - close_up() - visible_message("[src] fades away from the screen, the pAI device goes silent.") - card.removePersonality() - clear_client() +// vore-related stuff +/mob/living/silicon/pai/proc/update_fullness_pai() //Determines if they have something in their stomach. Copied and slightly modified. + var/new_people_eaten = 0 + for(var/belly in vore_organs) + var/obj/belly/B = belly + for(var/mob/living/M in B) + new_people_eaten += M.size_multiplier + people_eaten = min(1, new_people_eaten) -// See software.dm for Topic() -/mob/living/silicon/pai/canUseTopic(atom/movable/movable, be_close = FALSE, no_dexterity = FALSE, no_tk = FALSE) - // Resting is just an aesthetic feature for them. - return ..(movable, be_close, no_dexterity, no_tk) diff --git a/code/modules/mob/living/silicon/pai/pai_vr.dm b/code/modules/mob/living/silicon/pai/pai_vr.dm deleted file mode 100644 index c5de247595b..00000000000 --- a/code/modules/mob/living/silicon/pai/pai_vr.dm +++ /dev/null @@ -1,44 +0,0 @@ -/mob/living/silicon/pai - var/people_eaten = 0 - icon = 'icons/mob/pai_vr.dmi' - -/mob/living/silicon/pai/proc/pai_nom(var/mob/living/T in oview(1)) - set name = "pAI Nom" - set category = "pAI Commands" - set desc = "Allows you to eat someone while unfolded. Can't be used while in card form." - - if (stat != CONSCIOUS) - return - return feed_grabbed_to_self(src,T) - -/mob/living/silicon/pai/proc/update_fullness_pai() //Determines if they have something in their stomach. Copied and slightly modified. - var/new_people_eaten = 0 - for(var/belly in vore_organs) - var/obj/belly/B = belly - for(var/mob/living/M in B) - new_people_eaten += M.size_multiplier - people_eaten = min(1, new_people_eaten) - -/mob/living/silicon/pai/update_icon() //Some functions cause this to occur, such as resting - ..() - update_fullness_pai() - if(!people_eaten && !resting) - icon_state = "[chassis]" //Using icon_state here resulted in quite a few bugs. Chassis is much less buggy. - else if(!people_eaten && resting) - icon_state = "[chassis]_rest" - else if(people_eaten && !resting) - icon_state = "[chassis]_full" - else if(people_eaten && resting) - icon_state = "[chassis]_rest_full" - -/mob/living/silicon/pai/update_icons() //And other functions cause this to occur, such as digesting someone. - ..() - update_fullness_pai() - if(!people_eaten && !resting) - icon_state = "[chassis]" - else if(!people_eaten && resting) - icon_state = "[chassis]_rest" - else if(people_eaten && !resting) - icon_state = "[chassis]_full" - else if(people_eaten && resting) - icon_state = "[chassis]_rest_full" diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/savefile.dm similarity index 100% rename from code/modules/mob/living/silicon/pai/personality.dm rename to code/modules/mob/living/silicon/pai/savefile.dm diff --git a/code/modules/mob/living/silicon/pai/software_modules.dm b/code/modules/mob/living/silicon/pai/software_modules.dm deleted file mode 100644 index 4d4eecff74e..00000000000 --- a/code/modules/mob/living/silicon/pai/software_modules.dm +++ /dev/null @@ -1,545 +0,0 @@ -/datum/pai_software - // Name for the software. This is used as the button text when buying or opening/toggling the software - var/name = "pAI software module" - // RAM cost; pAIs start with 100 RAM, spending it on programs - var/ram_cost = 0 - // ID for the software. This must be unique - var/id = "" - // Whether this software is a toggle or not - // Toggled software should override toggle() and is_active() - // Non-toggled software should override on_nano_ui_interact() and Topic() - var/toggle = 1 - // Whether pAIs should automatically receive this module at no cost - var/default = 0 - -/datum/pai_software/proc/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - return - -/datum/pai_software/proc/toggle(mob/living/silicon/pai/user) - return - -/datum/pai_software/proc/is_active(mob/living/silicon/pai/user) - return 0 - -/datum/pai_software/directives - name = "Directives" - ram_cost = 0 - id = "directives" - toggle = 0 - default = 1 - -/datum/pai_software/directives/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - var/data[0] - - data["master"] = user.master - data["dna"] = user.master_dna - data["prime"] = user.pai_law0 - data["supplemental"] = user.pai_laws - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - // Don't copy-paste this unless you're making a pAI software module! - ui = new(user, user, id, "pai_directives.tmpl", "pAI Directives", 450, 600) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - -/datum/pai_software/directives/Topic(href, href_list) - var/mob/living/silicon/pai/P = usr - if(!istype(P)) - return - - if(href_list["getdna"]) - var/mob/living/M = P.loc - var/count = 0 - - // Find the carrier - while(!istype(M, /mob/living)) - if(!M || !M.loc || count > 6) - //For a runtime where M ends up in nullspace (similar to bluespace but less colourful) - to_chat(src, "You are not being carried by anyone!") - return 0 - M = M.loc - count++ - - // Check the carrier - var/datum/gender/TM = GLOB.gender_datums[M.get_visible_gender()] - var/answer = input(M, "[P] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "[P] Check DNA", "No") in list("Yes", "No") - if(answer == "Yes") - var/turf/T = get_turf_or_move(P.loc) - for (var/mob/v in viewers(T)) - v.show_message("[M] presses [TM.his] thumb against [P].", 3, "[P] makes a sharp clicking sound as it extracts DNA material from [M].", 2) - var/datum/dna/dna = M.dna - to_chat(P, "

[M]'s UE string : [dna.unique_enzymes]

") - if(dna.unique_enzymes == P.master_dna) - to_chat(P, "DNA is a match to stored Master DNA.") - else - to_chat(P, "DNA does not match stored Master DNA.") - else - to_chat(P, "[M] does not seem like [TM.he] is going to provide a DNA sample willingly.") - return 1 - -/datum/pai_software/radio_config - name = "Radio Configuration" - ram_cost = 0 - id = "radio" - toggle = 0 - default = 1 - -/datum/pai_software/radio_config/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui = null, force_open = 1) - var/data[0] - - data["listening"] = user.radio.broadcasting - data["frequency"] = format_frequency(user.radio.frequency) - - var/channels[0] - for(var/ch_name in user.radio.channels) - var/ch_stat = user.radio.channels[ch_name] - var/ch_dat[0] - ch_dat["name"] = ch_name - // FREQ_LISTENING is const in /obj/item/radio - ch_dat["listening"] = !!(ch_stat & user.radio.FREQ_LISTENING) - channels[++channels.len] = ch_dat - - data["channels"] = channels - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - ui = new(user, user, id, "pai_radio.tmpl", "Radio Configuration", 300, 150) - ui.set_initial_data(data) - ui.open() - -/datum/pai_software/radio_config/Topic(href, href_list) - var/mob/living/silicon/pai/P = usr - if(!istype(P)) - return - - P.radio.Topic(href, href_list) - return 1 - -/datum/pai_software/crew_manifest - name = "Crew Manifest" - ram_cost = 5 - id = "manifest" - toggle = 0 - -/datum/pai_software/crew_manifest/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - data_core.get_manifest_list() - - var/data[0] - // This is dumb, but NanoUI breaks if it has no data to send - data["manifest"] = GLOB.PDA_Manifest - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - // Don't copy-paste this unless you're making a pAI software module! - ui = new(user, user, id, "crew_manifest.tmpl", "Crew Manifest", 450, 600) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - -/datum/pai_software/messenger - name = "Digital Messenger" - ram_cost = 5 - id = "messenger" - toggle = 0 - -/datum/pai_software/messenger/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - var/data[0] - - data["receiver_off"] = user.pda.toff - data["ringer_off"] = user.pda.message_silent - data["current_ref"] = null - data["current_name"] = user.current_pda_messaging - - var/pdas[0] - if(!user.pda.toff) - for(var/obj/item/pda/P in GLOB.PDAs) - if(!P.owner || P.toff || P == user.pda || P.hidden) continue - var/pda[0] - pda["name"] = "[P]" - pda["owner"] = "[P.owner]" - pda["ref"] = "\ref[P]" - if(P.owner == user.current_pda_messaging) - data["current_ref"] = "\ref[P]" - pdas[++pdas.len] = pda - - data["pdas"] = pdas - - var/messages[0] - if(user.current_pda_messaging) - for(var/index in user.pda.tnote) - if(index["owner"] != user.current_pda_messaging) - continue - var/msg[0] - var/sent = index["sent"] - msg["sent"] = sent ? 1 : 0 - msg["target"] = index["owner"] - msg["message"] = index["message"] - messages[++messages.len] = msg - - data["messages"] = messages - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - // Don't copy-paste this unless you're making a pAI software module! - ui = new(user, user, id, "pai_messenger.tmpl", "Digital Messenger", 450, 600) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - -/datum/pai_software/messenger/Topic(href, href_list) - var/mob/living/silicon/pai/P = usr - if(!istype(P)) return - - if(!isnull(P.pda)) - if(href_list["toggler"]) - P.pda.toff = href_list["toggler"] != "1" - return 1 - else if(href_list["ringer"]) - P.pda.message_silent = href_list["ringer"] != "1" - return 1 - else if(href_list["select"]) - var/s = href_list["select"] - if(s == "*NONE*") - P.current_pda_messaging = null - else - P.current_pda_messaging = s - return 1 - else if(href_list["target"]) - if(P.silence_time) - return alert("Communications circuits remain uninitialized.") - - var/target = locate(href_list["target"]) - P.pda.create_message(P, target, 1) - return 1 - -/datum/pai_software/med_records - name = "Medical Records" - ram_cost = 15 - id = "med_records" - toggle = 0 - -/datum/pai_software/med_records/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - var/data[0] - - var/records[0] - for(var/datum/data/record/general in sortRecord(data_core.general)) - var/record[0] - record["name"] = general.fields["name"] - record["ref"] = "\ref[general]" - records[++records.len] = record - - data["records"] = records - - var/datum/data/record/G = user.medicalActive1 - var/datum/data/record/M = user.medicalActive2 - data["general"] = G ? G.fields : null - data["medical"] = M ? M.fields : null - data["could_not_find"] = user.medical_cannotfind - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - // Don't copy-paste this unless you're making a pAI software module! - ui = new(user, user, id, "pai_medrecords.tmpl", "Medical Records", 450, 600) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - -/datum/pai_software/med_records/Topic(href, href_list) - var/mob/living/silicon/pai/P = usr - if(!istype(P)) return - - if(href_list["select"]) - var/datum/data/record/record = locate(href_list["select"]) - if(record) - var/datum/data/record/R = record - var/datum/data/record/M = null - if (!( data_core.general.Find(R) )) - P.medical_cannotfind = 1 - else - P.medical_cannotfind = 0 - for(var/datum/data/record/E in data_core.medical) - if ((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"])) - M = E - P.medicalActive1 = R - P.medicalActive2 = M - else - P.medical_cannotfind = 1 - return 1 - -/datum/pai_software/sec_records - name = "Security Records" - ram_cost = 15 - id = "sec_records" - toggle = 0 - -/datum/pai_software/sec_records/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - var/data[0] - - var/records[0] - for(var/datum/data/record/general in sortRecord(data_core.general)) - var/record[0] - record["name"] = general.fields["name"] - record["ref"] = "\ref[general]" - records[++records.len] = record - - data["records"] = records - - var/datum/data/record/G = user.securityActive1 - var/datum/data/record/S = user.securityActive2 - data["general"] = G ? G.fields : null - data["security"] = S ? S.fields : null - data["could_not_find"] = user.security_cannotfind - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - // Don't copy-paste this unless you're making a pAI software module! - ui = new(user, user, id, "pai_secrecords.tmpl", "Security Records", 450, 600) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - -/datum/pai_software/sec_records/Topic(href, href_list) - var/mob/living/silicon/pai/P = usr - if(!istype(P)) - return - - if(href_list["select"]) - var/datum/data/record/record = locate(href_list["select"]) - if(record) - var/datum/data/record/R = record - var/datum/data/record/S = null - if (!( data_core.general.Find(R) )) - P.securityActive1 = null - P.securityActive2 = null - P.security_cannotfind = 1 - else - P.security_cannotfind = 0 - for(var/datum/data/record/E in data_core.security) - if ((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"])) - S = E - P.securityActive1 = R - P.securityActive2 = S - else - P.securityActive1 = null - P.securityActive2 = null - P.security_cannotfind = 1 - return 1 - -/datum/pai_software/door_jack - name = "Door Jack" - ram_cost = 30 - id = "door_jack" - toggle = 0 - -/datum/pai_software/door_jack/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - var/data[0] - - data["cable"] = user.cable != null - data["machine"] = user.cable && (user.cable.machine != null) - data["inprogress"] = user.hackdoor != null - data["progress_a"] = round(user.hackprogress / 10) - data["progress_b"] = user.hackprogress % 10 - data["aborted"] = user.hack_aborted - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - // Don't copy-paste this unless you're making a pAI software module! - ui = new(user, user, id, "pai_doorjack.tmpl", "Door Jack", 300, 150) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - -/datum/pai_software/door_jack/Topic(href, href_list) - var/mob/living/silicon/pai/P = usr - if(!istype(P)) - return - - if(href_list["jack"]) - if(P.cable && P.cable.machine) - P.hackdoor = P.cable.machine - P.hackloop() - return 1 - else if(href_list["cancel"]) - P.hackdoor = null - return 1 - else if(href_list["cable"]) - var/turf/T = get_turf_or_move(P.loc) - P.hack_aborted = 0 - P.cable = new /obj/item/pai_cable(T) - for(var/mob/M in viewers(T)) - M.show_message("A port on [P] opens to reveal [P.cable], which promptly falls to the floor.", 3, - "You hear the soft click of something light and hard falling to the ground.", 2) - return 1 - -/mob/living/silicon/pai/proc/hackloop() - var/turf/T = get_turf_or_move(src.loc) - for(var/mob/living/silicon/ai/AI in GLOB.player_list) - if(T.loc) - to_chat(AI, "Network Alert: Brute-force encryption crack in progress in [T.loc].") - else - to_chat(AI, "Network Alert: Brute-force encryption crack in progress. Unable to pinpoint location.") - var/obj/machinery/door/D = cable.machine - if(!istype(D)) - hack_aborted = 1 - hackprogress = 0 - cable.machine = null - hackdoor = null - return - while(hackprogress < 1000) - if(cable && cable.machine == D && cable.machine == hackdoor && get_dist(src, hackdoor) <= 1) - hackprogress = min(hackprogress+rand(1, 20), 1000) - else - hack_aborted = 1 - hackprogress = 0 - hackdoor = null - return - if(hackprogress >= 1000) - hackprogress = 0 - D.open() - cable.machine = null - return - sleep(10) // Update every second - -/datum/pai_software/atmosphere_sensor - name = "Atmosphere Sensor" - ram_cost = 5 - id = "atmos_sense" - toggle = 0 - -/datum/pai_software/atmosphere_sensor/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - var/data[0] - - var/turf/T = get_turf_or_move(user.loc) - if(!T) - data["reading"] = 0 - data["pressure"] = 0 - data["temperature"] = 0 - data["temperatureC"] = 0 - data["gas"] = list() - else - var/datum/gas_mixture/env = T.return_air() - data["reading"] = 1 - var/pres = env.return_pressure() * 10 - data["pressure"] = "[round(pres/10)].[pres%10]" - data["temperature"] = round(env.temperature) - data["temperatureC"] = round(env.temperature-T0C) - - var/t_moles = env.total_moles - var/gases[0] - for(var/g in env.gas) - var/gas[0] - gas["name"] = GLOB.meta_gas_names[g] - gas["percent"] = round((env.gas[g] / t_moles) * 100) - gases[++gases.len] = gas - data["gas"] = gases - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - // Don't copy-paste this unless you're making a pAI software module! - ui = new(user, user, id, "pai_atmosphere.tmpl", "Atmosphere Sensor", 350, 300) - ui.set_initial_data(data) - ui.open() - -/datum/pai_software/sec_hud - name = "Security HUD" - ram_cost = 20 - id = "sec_hud" - -/datum/pai_software/sec_hud/toggle(mob/living/silicon/pai/user) - user.secHUD = !user.secHUD - if(user.secHUD) - get_atom_hud(DATA_HUD_SECURITY_ADVANCED).add_hud_to(user) - else - get_atom_hud(DATA_HUD_SECURITY_ADVANCED).remove_hud_from(user) - -/datum/pai_software/sec_hud/is_active(mob/living/silicon/pai/user) - return user.secHUD - -/datum/pai_software/med_hud - name = "Medical HUD" - ram_cost = 20 - id = "med_hud" - -/datum/pai_software/med_hud/toggle(mob/living/silicon/pai/user) - if((user.medHUD = !user.medHUD)) - get_atom_hud(DATA_HUD_MEDICAL).add_hud_to(user) - else - get_atom_hud(DATA_HUD_MEDICAL).remove_hud_from(user) - -/datum/pai_software/med_hud/is_active(mob/living/silicon/pai/user) - return user.medHUD - -// todo: translation context - -// /datum/pai_software/translator -// name = "Universal Translator" -// ram_cost = 35 -// id = "translator" - -// /datum/pai_software/translator/toggle(mob/living/silicon/pai/user) -// // Sol Common, Tradeband, Terminus and Gutter are added with New() and are therefore the current default, always active languages -// // todo: translation contexts for pais -// user.translator_on = !user.translator_on -// if(user.translator_on) -// user.add_language(LANGUAGE_UNATHI) -// user.add_language(LANGUAGE_SIIK) -// user.add_language(LANGUAGE_AKHANI) -// user.add_language(LANGUAGE_SKRELLIAN) -// user.add_language(LANGUAGE_ZADDAT) -// user.add_language(LANGUAGE_SCHECHI) -// else -// user.remove_language(LANGUAGE_UNATHI) -// user.remove_language(LANGUAGE_SIIK) -// user.remove_language(LANGUAGE_AKHANI) -// user.remove_language(LANGUAGE_SKRELLIAN) -// user.remove_language(LANGUAGE_ZADDAT) -// user.remove_language(LANGUAGE_SCHECHI) - -// /datum/pai_software/translator/is_active(mob/living/silicon/pai/user) -// return user.translator_on - -/datum/pai_software/signaller - name = "Remote Signaller" - ram_cost = 5 - id = "signaller" - toggle = 0 - -/datum/pai_software/signaller/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) - var/data[0] - - data["frequency"] = format_frequency(user.sradio.frequency) - data["code"] = user.sradio.code - - ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) - if(!ui) - // Don't copy-paste this unless you're making a pAI software module! - ui = new(user, user, id, "pai_signaller.tmpl", "Signaller", 320, 150) - ui.set_initial_data(data) - ui.open() - -/datum/pai_software/signaller/Topic(href, href_list) - var/mob/living/silicon/pai/P = usr - if(!istype(P)) - return - - if(href_list["send"]) - P.sradio.send_signal("ACTIVATE") - for(var/mob/O in hearers(1, P.loc)) - to_chat(O, "[icon2html(thing = src, target = O)] *beep beep*") - return 1 - - else if(href_list["freq"]) - var/new_frequency = (P.sradio.frequency + text2num(href_list["freq"])) - if(new_frequency < PUBLIC_LOW_FREQ || new_frequency > PUBLIC_HIGH_FREQ) - new_frequency = sanitize_frequency(new_frequency) - P.sradio.set_frequency(new_frequency) - return 1 - - else if(href_list["code"]) - P.sradio.code += text2num(href_list["code"]) - P.sradio.code = round(P.sradio.code) - P.sradio.code = min(100, P.sradio.code) - P.sradio.code = max(1, P.sradio.code) - return 1 diff --git a/code/modules/mob/living/silicon/pai/software_modules/_software_module.dm b/code/modules/mob/living/silicon/pai/software_modules/_software_module.dm new file mode 100644 index 00000000000..e48db409e43 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules/_software_module.dm @@ -0,0 +1,51 @@ +/datum/pai_software + // Name for the software. This is used as the button text when buying or opening/toggling the software + var/name = "pAI software module" + // RAM cost; pAIs start with 100 RAM, spending it on programs + var/ram_cost = 0 + // ID for the software. This must be unique + var/id = "" + // Whether this software is a toggle or not + // Toggled software should override toggle() and is_active() + // Non-toggled software should override on_nano_ui_interact() and Topic() + var/toggle = 1 + // Whether pAIs should automatically receive this module at no cost + var/default = 0 + +/datum/pai_software/proc/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + return + +/datum/pai_software/proc/toggle(mob/living/silicon/pai/user) + return + +/datum/pai_software/proc/is_active(mob/living/silicon/pai/user) + return 0 + +// todo: translation context + +// /datum/pai_software/translator +// name = "Universal Translator" +// ram_cost = 35 +// id = "translator" + +// /datum/pai_software/translator/toggle(mob/living/silicon/pai/user) +// // Sol Common, Tradeband, Terminus and Gutter are added with New() and are therefore the current default, always active languages +// // todo: translation contexts for pais +// user.translator_on = !user.translator_on +// if(user.translator_on) +// user.add_language(LANGUAGE_UNATHI) +// user.add_language(LANGUAGE_SIIK) +// user.add_language(LANGUAGE_AKHANI) +// user.add_language(LANGUAGE_SKRELLIAN) +// user.add_language(LANGUAGE_ZADDAT) +// user.add_language(LANGUAGE_SCHECHI) +// else +// user.remove_language(LANGUAGE_UNATHI) +// user.remove_language(LANGUAGE_SIIK) +// user.remove_language(LANGUAGE_AKHANI) +// user.remove_language(LANGUAGE_SKRELLIAN) +// user.remove_language(LANGUAGE_ZADDAT) +// user.remove_language(LANGUAGE_SCHECHI) + +// /datum/pai_software/translator/is_active(mob/living/silicon/pai/user) +// return user.translator_on diff --git a/code/modules/mob/living/silicon/pai/software_modules/atmosphere_sensor.dm b/code/modules/mob/living/silicon/pai/software_modules/atmosphere_sensor.dm new file mode 100644 index 00000000000..dc6840d6653 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules/atmosphere_sensor.dm @@ -0,0 +1,39 @@ +/datum/pai_software/atmosphere_sensor + name = "Atmosphere Sensor" + ram_cost = 5 + id = "atmos_sense" + toggle = 0 + +/datum/pai_software/atmosphere_sensor/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + var/turf/T = get_turf_or_move(user.loc) + if(!T) + data["reading"] = 0 + data["pressure"] = 0 + data["temperature"] = 0 + data["temperatureC"] = 0 + data["gas"] = list() + else + var/datum/gas_mixture/env = T.return_air() + data["reading"] = 1 + var/pres = env.return_pressure() * 10 + data["pressure"] = "[round(pres/10)].[pres%10]" + data["temperature"] = round(env.temperature) + data["temperatureC"] = round(env.temperature-T0C) + + var/t_moles = env.total_moles + var/gases[0] + for(var/g in env.gas) + var/gas[0] + gas["name"] = GLOB.meta_gas_names[g] + gas["percent"] = round((env.gas[g] / t_moles) * 100) + gases[++gases.len] = gas + data["gas"] = gases + + ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_atmosphere.tmpl", "Atmosphere Sensor", 350, 300) + ui.set_initial_data(data) + ui.open() diff --git a/code/modules/mob/living/silicon/pai/software_modules/crew_manifest.dm b/code/modules/mob/living/silicon/pai/software_modules/crew_manifest.dm new file mode 100644 index 00000000000..db291fa95f5 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules/crew_manifest.dm @@ -0,0 +1,20 @@ +/datum/pai_software/crew_manifest + name = "Crew Manifest" + ram_cost = 5 + id = "manifest" + toggle = 0 + +/datum/pai_software/crew_manifest/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + data_core.get_manifest_list() + + var/data[0] + // This is dumb, but NanoUI breaks if it has no data to send + data["manifest"] = GLOB.PDA_Manifest + + ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "crew_manifest.tmpl", "Crew Manifest", 450, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) diff --git a/code/modules/mob/living/silicon/pai/software_modules/directives.dm b/code/modules/mob/living/silicon/pai/software_modules/directives.dm new file mode 100644 index 00000000000..cb935c63096 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules/directives.dm @@ -0,0 +1,57 @@ +/datum/pai_software/directives + name = "Directives" + ram_cost = 0 + id = "directives" + toggle = 0 + default = 1 + +/datum/pai_software/directives/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + data["master"] = user.master + data["dna"] = user.master_dna + data["prime"] = user.pai_law0 + data["supplemental"] = user.pai_laws + + ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_directives.tmpl", "pAI Directives", 450, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/datum/pai_software/directives/Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) + return + + if(href_list["getdna"]) + var/mob/living/M = P.loc + var/count = 0 + + // Find the carrier + while(!istype(M, /mob/living)) + if(!M || !M.loc || count > 6) + //For a runtime where M ends up in nullspace (similar to bluespace but less colourful) + to_chat(src, "You are not being carried by anyone!") + return 0 + M = M.loc + count++ + + // Check the carrier + var/datum/gender/TM = GLOB.gender_datums[M.get_visible_gender()] + var/answer = input(M, "[P] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "[P] Check DNA", "No") in list("Yes", "No") + if(answer == "Yes") + var/turf/T = get_turf_or_move(P.loc) + for (var/mob/v in viewers(T)) + v.show_message("[M] presses [TM.his] thumb against [P].", 3, "[P] makes a sharp clicking sound as it extracts DNA material from [M].", 2) + var/datum/dna/dna = M.dna + to_chat(P, "

[M]'s UE string : [dna.unique_enzymes]

") + if(dna.unique_enzymes == P.master_dna) + to_chat(P, "DNA is a match to stored Master DNA.") + else + to_chat(P, "DNA does not match stored Master DNA.") + else + to_chat(P, "[M] does not seem like [TM.he] is going to provide a DNA sample willingly.") + return 1 diff --git a/code/modules/mob/living/silicon/pai/software_modules/door_jack.dm b/code/modules/mob/living/silicon/pai/software_modules/door_jack.dm new file mode 100644 index 00000000000..d0df5dcb9bf --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules/door_jack.dm @@ -0,0 +1,74 @@ +/datum/pai_software/door_jack + name = "Door Jack" + ram_cost = 30 + id = "door_jack" + toggle = 0 + +/datum/pai_software/door_jack/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + data["cable"] = user.cable != null + data["machine"] = user.cable && (user.cable.machine != null) + data["inprogress"] = user.hackdoor != null + data["progress_a"] = round(user.hackprogress / 10) + data["progress_b"] = user.hackprogress % 10 + data["aborted"] = user.hack_aborted + + ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_doorjack.tmpl", "Door Jack", 300, 150) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/datum/pai_software/door_jack/Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) + return + + if(href_list["jack"]) + if(P.cable && P.cable.machine) + P.hackdoor = P.cable.machine + P.hackloop() + return 1 + else if(href_list["cancel"]) + P.hackdoor = null + return 1 + else if(href_list["cable"]) + var/turf/T = get_turf_or_move(P.loc) + P.hack_aborted = 0 + P.cable = new /obj/item/pai_cable(T) + for(var/mob/M in viewers(T)) + M.show_message("A port on [P] opens to reveal [P.cable], which promptly falls to the floor.", 3, + "You hear the soft click of something light and hard falling to the ground.", 2) + return 1 + +/mob/living/silicon/pai/proc/hackloop() + var/turf/T = get_turf_or_move(src.loc) + for(var/mob/living/silicon/ai/AI in GLOB.player_list) + if(T.loc) + to_chat(AI, "Network Alert: Brute-force encryption crack in progress in [T.loc].") + else + to_chat(AI, "Network Alert: Brute-force encryption crack in progress. Unable to pinpoint location.") + var/obj/machinery/door/D = cable.machine + if(!istype(D)) + hack_aborted = 1 + hackprogress = 0 + cable.machine = null + hackdoor = null + return + while(hackprogress < 1000) + if(cable && cable.machine == D && cable.machine == hackdoor && get_dist(src, hackdoor) <= 1) + hackprogress = min(hackprogress+rand(1, 20), 1000) + else + hack_aborted = 1 + hackprogress = 0 + hackdoor = null + return + if(hackprogress >= 1000) + hackprogress = 0 + D.open() + cable.machine = null + return + sleep(10) // Update every second diff --git a/code/modules/mob/living/silicon/pai/software_modules/messenger.dm b/code/modules/mob/living/silicon/pai/software_modules/messenger.dm new file mode 100644 index 00000000000..2ea8999b37d --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules/messenger.dm @@ -0,0 +1,75 @@ +/datum/pai_software/messenger + name = "Digital Messenger" + ram_cost = 5 + id = "messenger" + toggle = 0 + +/datum/pai_software/messenger/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + data["receiver_off"] = user.pda.toff + data["ringer_off"] = user.pda.message_silent + data["current_ref"] = null + data["current_name"] = user.current_pda_messaging + + var/pdas[0] + if(!user.pda.toff) + for(var/obj/item/pda/P in GLOB.PDAs) + if(!P.owner || P.toff || P == user.pda || P.hidden) continue + var/pda[0] + pda["name"] = "[P]" + pda["owner"] = "[P.owner]" + pda["ref"] = "\ref[P]" + if(P.owner == user.current_pda_messaging) + data["current_ref"] = "\ref[P]" + pdas[++pdas.len] = pda + + data["pdas"] = pdas + + var/messages[0] + if(user.current_pda_messaging) + for(var/index in user.pda.tnote) + if(index["owner"] != user.current_pda_messaging) + continue + var/msg[0] + var/sent = index["sent"] + msg["sent"] = sent ? 1 : 0 + msg["target"] = index["owner"] + msg["message"] = index["message"] + messages[++messages.len] = msg + + data["messages"] = messages + + ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_messenger.tmpl", "Digital Messenger", 450, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/datum/pai_software/messenger/Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) return + + if(!isnull(P.pda)) + if(href_list["toggler"]) + P.pda.toff = href_list["toggler"] != "1" + return 1 + else if(href_list["ringer"]) + P.pda.message_silent = href_list["ringer"] != "1" + return 1 + else if(href_list["select"]) + var/s = href_list["select"] + if(s == "*NONE*") + P.current_pda_messaging = null + else + P.current_pda_messaging = s + return 1 + else if(href_list["target"]) + if(P.silence_time) + return alert("Communications circuits remain uninitialized.") + + var/target = locate(href_list["target"]) + P.pda.create_message(P, target, 1) + return 1 diff --git a/code/modules/mob/living/silicon/pai/software_modules/radio_config.dm b/code/modules/mob/living/silicon/pai/software_modules/radio_config.dm new file mode 100644 index 00000000000..40dd645f37e --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules/radio_config.dm @@ -0,0 +1,37 @@ +/datum/pai_software/radio_config + name = "Radio Configuration" + ram_cost = 0 + id = "radio" + toggle = 0 + default = 1 + +/datum/pai_software/radio_config/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui = null, force_open = 1) + var/data[0] + + data["listening"] = user.radio.broadcasting + data["frequency"] = format_frequency(user.radio.frequency) + + var/channels[0] + for(var/ch_name in user.radio.channels) + var/ch_stat = user.radio.channels[ch_name] + var/ch_dat[0] + ch_dat["name"] = ch_name + // FREQ_LISTENING is const in /obj/item/radio + ch_dat["listening"] = !!(ch_stat & user.radio.FREQ_LISTENING) + channels[++channels.len] = ch_dat + + data["channels"] = channels + + ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + ui = new(user, user, id, "pai_radio.tmpl", "Radio Configuration", 300, 150) + ui.set_initial_data(data) + ui.open() + +/datum/pai_software/radio_config/Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) + return + + P.radio.Topic(href, href_list) + return 1 diff --git a/code/modules/mob/living/silicon/pai/software_modules/signaller.dm b/code/modules/mob/living/silicon/pai/software_modules/signaller.dm new file mode 100644 index 00000000000..d61f553f9a6 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules/signaller.dm @@ -0,0 +1,43 @@ +/datum/pai_software/signaller + name = "Remote Signaller" + ram_cost = 5 + id = "signaller" + toggle = 0 + +/datum/pai_software/signaller/on_nano_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + data["frequency"] = format_frequency(user.sradio.frequency) + data["code"] = user.sradio.code + + ui = SSnanoui.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_signaller.tmpl", "Signaller", 320, 150) + ui.set_initial_data(data) + ui.open() + +/datum/pai_software/signaller/Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) + return + + if(href_list["send"]) + P.sradio.send_signal("ACTIVATE") + for(var/mob/O in hearers(1, P.loc)) + to_chat(O, "[icon2html(thing = src, target = O)] *beep beep*") + return 1 + + else if(href_list["freq"]) + var/new_frequency = (P.sradio.frequency + text2num(href_list["freq"])) + if(new_frequency < PUBLIC_LOW_FREQ || new_frequency > PUBLIC_HIGH_FREQ) + new_frequency = sanitize_frequency(new_frequency) + P.sradio.set_frequency(new_frequency) + return 1 + + else if(href_list["code"]) + P.sradio.code += text2num(href_list["code"]) + P.sradio.code = round(P.sradio.code) + P.sradio.code = min(100, P.sradio.code) + P.sradio.code = max(1, P.sradio.code) + return 1 diff --git a/code/modules/mob/living/silicon/pai/verbs.dm b/code/modules/mob/living/silicon/pai/verbs.dm new file mode 100644 index 00000000000..2c214ff9ea8 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/verbs.dm @@ -0,0 +1,117 @@ +/mob/living/silicon/pai/verb/fold_out() + set category = "pAI Commands" + set name = "Unfold Chassis" + + // see pai/mobility.dm + // we don't check mobility here because while folded up, you can't move + if(!can_action()) + return + // to fold out we need to be in the card + if(src.loc != card) + return + + open_up() + +/mob/living/silicon/pai/verb/fold_up() + set category = "pAI Commands" + set name = "Collapse Chassis" + + // we check mobility here to stop people folding up if they currently cannot move + if(!CHECK_MOBILITY(src, MOBILITY_CAN_MOVE)) + return + if(!can_action()) + return + // to fold up we need to not be in the card already + if(src.loc == card) + return + + close_up() + +/mob/living/silicon/pai/proc/choose_chassis() + set category = "pAI Commands" + set name = "Choose Chassis" + + var/choice + var/finalized = "No" + while(finalized == "No" && src.client) + + choice = input(usr,"What would you like to use for your mobile chassis icon?") as null|anything in (list("-- LOAD CHARACTER SLOT --") + possible_chassis) + if(!choice) + return + + if(choice == "-- LOAD CHARACTER SLOT --") + icon = render_hologram_icon(usr.client.prefs.render_to_appearance(PREF_COPY_TO_FOR_RENDER | PREF_COPY_TO_NO_CHECK_SPECIES | PREF_COPY_TO_UNRESTRICTED_LOADOUT), 210) + else + icon = 'icons/mob/pai.dmi' + icon_state = possible_chassis[choice] + finalized = alert("Look at your sprite. Is this what you wish to use?",,"No","Yes") + + chassis = possible_chassis[choice] + add_verb(src, /mob/living/proc/hide) + +/mob/living/silicon/pai/proc/choose_verbs() + set category = "pAI Commands" + set name = "Choose Speech Verbs" + + var/choice = input(usr,"What theme would you like to use for your speech verbs?") as null|anything in possible_say_verbs + if(!choice) return + + var/list/sayverbs = 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" + + // Pass lying down or getting up to our pet human, if we're in a hardsuit. + if(istype(src.loc,/obj/item/paicard)) + set_resting(FALSE) + var/obj/item/hardsuit/hardsuit = src.get_hardsuit() + if(istype(hardsuit)) + hardsuit.force_rest(src) + else + toggle_resting() + icon_state = resting ? "[chassis]_rest" : "[chassis]" + update_icon() + to_chat(src, SPAN_NOTICE("You are now [resting ? "resting" : "getting up"]")) + + update_mobility() + +/mob/living/silicon/pai/verb/allowmodification() + set name = "Change Access Modifcation Permission" + set category = "pAI Commands" + set desc = "Allows people to modify your access or block people from modifying your access." + + if(idaccessible == 0) + idaccessible = 1 + to_chat(src, "You allow access modifications.") + + else + idaccessible = 0 + to_chat(src, "You block access modfications.") + +/mob/living/silicon/pai/verb/wipe_software() + set name = "Wipe Software" + set category = "OOC" + set desc = "Wipe your software. This is functionally equivalent to cryo or robotic storage, freeing up your job slot." + + // Make sure people don't kill themselves accidentally + if(alert("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", "No", "No", "Yes") != "Yes") + return + + close_up() + visible_message("[src] fades away from the screen, the pAI device goes silent.") + card.removePersonality() + clear_client() + +/mob/living/silicon/pai/proc/pai_nom(var/mob/living/T in oview(1)) + set name = "pAI Nom" + set category = "pAI Commands" + set desc = "Allows you to eat someone while unfolded. Can't be used while in card form." + + if (stat != CONSCIOUS) + return + return feed_grabbed_to_self(src,T)