diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index e641d8c773f..ac841e220df 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -331,8 +331,8 @@ Turf and target are seperate in case you want to teleport some distance from a t if(oldname != real_name) R.notify_ai(3, oldname, newname) if(R.camera) - R.camera.c_tag = real_name - + R.camera.c_tag = real_name + if(oldname) //update the datacore records! This is goig to be a bit costly. for(var/list/L in list(data_core.general,data_core.medical,data_core.security,data_core.locked)) @@ -639,26 +639,6 @@ var/list/sortMobsOrder = list( "/mob/living/silicon/ai", // if a valid atom reference is supplied, call the atom's Topic() with "close=1" // otherwise, just reset the client mob's machine var. // -/client/verb/windowclose(var/atomref as text) - set hidden = 1 // hide this verb from the user's panel - set name = ".windowclose" // no autocomplete on cmd line - - //world << "windowclose: [atomref]" - if(atomref!="null") // if passed a real atomref - var/hsrc = locate(atomref) // find the reffed atom - var/href = "close=1" - if(hsrc) - //world << "[src] Topic [href] [hsrc]" - usr = src.mob - src.Topic(href, params2list(href), hsrc) // this will direct to the atom's - return // Topic() proc via client.Topic() - - // no atomref specified (or not found) - // so just reset the user mob's machine var - if(src && src.mob) - //world << "[src] was [src.mob.machine], setting to null" - src.mob.unset_machine() - return //Will return the location of the turf an atom is ultimatly sitting on /proc/get_turf_loc(var/atom/movable/M) //gets the location of the turf that the atom is on, or what the atom is in is on, etc diff --git a/code/datums/browser.dm b/code/datums/browser.dm index da5a80443ba..a36b204a0f3 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -160,7 +160,7 @@ // if a valid atom reference is supplied, call the atom's Topic() with "close=1" // otherwise, just reset the client mob's machine var. // -/client/verb/windowclosed(var/atomref as text) +/client/verb/windowclose(var/atomref as text) set hidden = 1 // hide this verb from the user's panel set name = ".windowclose" // no autocomplete on cmd line diff --git a/code/game/gamemodes/events/ninja_equipment.dm b/code/game/gamemodes/events/ninja_equipment.dm index ae6d6892050..5de2a202071 100644 --- a/code/game/gamemodes/events/ninja_equipment.dm +++ b/code/game/gamemodes/events/ninja_equipment.dm @@ -682,17 +682,21 @@ ________________________________________________________________________________ pai.attack_self(U) if("Eject pAI") - var/turf/T = get_turf(loc) - if(!U.get_active_hand()) - U.put_in_hands(pai) - pai.add_fingerprint(U) - pai = null - else - if(T) - pai.loc = T + if(pai) + if(pai.loc != src) pai = null else - U << "\red ERROR: \black Could not eject pAI card." + var/turf/T = get_turf(loc) + if(!U.get_active_hand()) + U.put_in_hands(pai) + pai.add_fingerprint(U) + pai = null + else + if(T) + pai.loc = T + pai = null + else + U << "\red ERROR: \black Could not eject pAI card." if("Override AI Laws") var/law_zero = A.laws.zeroth//Remembers law zero, if there is one. diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index d86e7d96b0a..592cfdc6ef9 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -17,7 +17,7 @@ if (src.req_access && src.req_access.len) src.icon_state = "[src.icon_state]" src.base_state = src.icon_state - + color = color_windows(src) return @@ -56,7 +56,7 @@ if (!( ticker )) return var/mob/M = AM - if(!M.restrained()) + if(!M.restrained() && !M.small) bumpopen(M) return @@ -187,7 +187,7 @@ visible_message("[src] has been hit by [M.name].") take_damage(M.force) return - + /obj/machinery/door/window/attack_ai(mob/user as mob) return src.attack_hand(user) diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 8ebd6e30dc5..b536d1405ad 100755 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -768,14 +768,18 @@ var/global/list/obj/item/device/pda/PDAs = list() //pAI FUNCTIONS=================================== if("pai") - switch(href_list["option"]) - if("1") // Configure pAI device - pai.attack_self(U) - if("2") // Eject pAI device - var/turf/T = get_turf_or_move(src.loc) - if(T) - pai.loc = T - pai = null + if(pai) + if(pai.loc != src) + pai = null + else + switch(href_list["option"]) + if("1") // Configure pAI device + pai.attack_self(U) + if("2") // Eject pAI device + var/turf/T = get_turf_or_move(src.loc) + if(T) + pai.loc = T + pai = null else mode = text2num(href_list["choice"]) diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index cf14ffb6d6e..fece7e13f8a 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -162,19 +162,19 @@ - - - diff --git a/code/game/verbs/suicide.dm b/code/game/verbs/suicide.dm index 7f963bb4e1c..0b351b7619a 100644 --- a/code/game/verbs/suicide.dm +++ b/code/game/verbs/suicide.dm @@ -170,6 +170,8 @@ set name = "pAI Suicide" var/answer = input("REALLY kill yourself? This action can't be undone.", "Suicide", "No") in list ("Yes", "No") if(answer == "Yes") + if(canmove && !resting) + close_up() var/obj/item/device/paicard/card = loc card.removePersonality() var/turf/T = get_turf_or_move(card.loc) diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm index 127091a854c..b945315ac2b 100644 --- a/code/modules/mob/living/silicon/pai/death.dm +++ b/code/modules/mob/living/silicon/pai/death.dm @@ -1,5 +1,13 @@ /mob/living/silicon/pai/death(gibbed) if(stat == DEAD) return + var/turf/T = get_turf_or_move(loc) + for (var/mob/M in viewers(T)) + M.show_message("\red A shower of sparks spray from [src]'s inner workings.", 3, "\red You hear and smell the ozone hiss of electrical sparks being expelled violently.", 2) + var/obj/effect/decal/cleanable/deadpai = new /obj/effect/decal/cleanable/robot_debris(loc) + if(canmove || resting) + deadpai.icon = 'icons/mob/pai.dmi' + deadpai.icon_state = "[chassis]_dead" + del(src) stat = DEAD canmove = 0 if(blind) blind.layer = 0 @@ -15,4 +23,3 @@ if(mind) del(mind) living_mob_list -= src ghostize() - del(src) \ No newline at end of file diff --git a/code/modules/mob/living/silicon/pai/life.dm b/code/modules/mob/living/silicon/pai/life.dm index 423287f60c1..5ddbc0efe38 100644 --- a/code/modules/mob/living/silicon/pai/life.dm +++ b/code/modules/mob/living/silicon/pai/life.dm @@ -25,4 +25,6 @@ stat = CONSCIOUS else health = 100 - getBruteLoss() - getFireLoss() + if(health <= 0) + death(0) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 02c8ea04223..ab04798293b 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -1,10 +1,13 @@ /mob/living/silicon/pai name = "pAI" - icon = 'icons/mob/mob.dmi'// - icon_state = "shadow" + icon = 'icons/mob/pai.dmi'// + icon_state = "repairbot" robot_talk_understand = 0 emote_type = 2 // pAIs emotes are heard, not seen, so they can be seen through a container (eg. person) + small = 1 + pass_flags = 1 + density = 0 var/network = "SS13" var/obj/machinery/camera/current = null @@ -15,9 +18,22 @@ var/obj/item/device/paicard/card // The card we inhabit var/obj/item/device/radio/radio // Our primary radio - var/speakStatement = "states" - var/speakExclamation = "declares" - var/speakQuery = "queries" + var/chassis = "repairbot" // A record of your chosen chassis. + var/global/list/possible_chassis = list( + "Drone" = "repairbot", + "Cat" = "cat", + "Mouse" = "mouse", + "Monkey" = "monkey" + ) + + var/global/list/possible_say_verbs = list( + "Robotic" = list("states","declares","queries"), + "Natural" = list("says","yells","asks"), + "Beep" = list("beeps","beeps loudly","boops"), + "Chirp" = list("chirps","chirrups","cheeps"), + "Feline" = list("purrs","yowls","meows") + ) + var/obj/item/weapon/pai_cable/cable // The cable we produce and use when door or camera jacking @@ -63,6 +79,10 @@ card.radio = new /obj/item/device/radio(src.card) radio = card.radio + //Verbs for pAI mobile form, chassis and Say flavor text + verbs += /mob/living/silicon/pai/proc/choose_chassis + verbs += /mob/living/silicon/pai/proc/choose_verbs + //PDA pda = new(src) spawn(5) @@ -76,20 +96,20 @@ ..() usr << browse_rsc('html/paigrid.png') // Go ahead and cache the interface resources as early as possible - + // this function shows the information about being silenced as a pAI in the Status panel /mob/living/silicon/pai/proc/show_silenced() if(src.silence_time) var/timeleft = round((silence_time - world.timeofday)/10 ,1) stat(null, "Communications system reboot in -[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]") - - + + /mob/living/silicon/pai/Stat() ..() statpanel("Status") if (src.client.statpanel == "Status") show_silenced() - + if (proc_holder_list.len)//Generic list for proc_holder objects. for(var/obj/effect/proc_holder/P in proc_holder_list) statpanel("[P.panel]","",P) @@ -108,7 +128,12 @@ return 0 /mob/living/silicon/pai/restrained() - return 0 + if(istype(src.loc,/obj/item/device/paicard)) + return 0 + ..() + +/mob/living/silicon/pai/MouseDrop(atom/over_object) + return /mob/living/silicon/pai/emp_act(severity) // Silence for 2 minutes @@ -270,3 +295,188 @@ 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(stat || sleeping || paralysis || weakened) + return + + if(src.loc != card) + src << "\red You are already in your mobile form!" + return + + if(world.time <= last_special) + src << "\red You must wait before folding your chassis out again!" + return + + last_special = world.time + 200 + + canmove = 1 + + //I'm not sure how much of this is necessary, but I would rather avoid issues. + if(istype(card.loc,/mob)) + var/mob/holder = card.loc + holder.drop_from_inventory(card) + else if(istype(card.loc,/obj/item/clothing/suit/space/space_ninja)) + var/obj/item/clothing/suit/space/space_ninja/holder = card.loc + holder.pai = null + else if(istype(card.loc,/obj/item/device/pda)) + var/obj/item/device/pda/holder = card.loc + holder.pai = null + + + src.client.perspective = EYE_PERSPECTIVE + src.client.eye = src + src.forceMove(get_turf(card)) + + card.forceMove(src) + card.screen_loc = null + + var/turf/T = get_turf(src) + if(istype(T)) T.visible_message("[src] folds outwards, expanding into a mobile form.") + +/mob/living/silicon/pai/verb/fold_up() + set category = "pAI Commands" + set name = "Collapse Chassis" + + if(stat || sleeping || paralysis || weakened) + return + + if(src.loc == card) + src << "\red You are already in your card form!" + return + + if(world.time <= last_special) + src << "\red You must wait before returning to your card form!" + 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? This decision can only be made once.") as null|anything in possible_chassis + if(!choice) return + + icon_state = possible_chassis[choice] + finalized = alert("Look at your sprite. Is this what you wish to use?",,"No","Yes") + + chassis = possible_chassis[choice] + verbs -= /mob/living/silicon/pai/proc/choose_chassis + +/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? This decision can only be made once.") 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)] + + verbs -= /mob/living/silicon/pai/proc/choose_verbs + +/mob/living/silicon/pai/lay_down() + set name = "Rest" + set category = "IC" + + if(istype(src.loc,/obj/item/device/paicard)) + resting = 0 + else + resting = !resting + icon_state = resting ? "[chassis]_rest" : "[chassis]" + src << "\blue You are now [resting ? "resting" : "getting up"]" + + canmove = !resting + +//Overriding this will stop a number of headaches down the track. +/mob/living/silicon/pai/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(W.force) + visible_message("[user.name] attacks [src] with [W]!") + src.adjustBruteLoss(W.force) + src.updatehealth() + else + visible_message("[user.name] bonks [src] harmlessly with [W].") + if(stat != 2) close_up() + return + +/mob/living/silicon/pai/attack_hand(mob/user as mob) + if(stat == 2) return + 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 + 200 + + if(src.loc == card) + return + + var/turf/T = get_turf(src) + if(istype(T)) T.visible_message("[src] neatly folds inwards, compacting down to a rectangular card.") + + src.stop_pulling() + src.client.perspective = EYE_PERSPECTIVE + src.client.eye = card + + //This seems redundant but not including the forced loc setting messes the behavior up. + src.loc = card + card.loc = get_turf(card) + src.forceMove(card) + card.forceMove(card.loc) + canmove = 0 + icon_state = "[chassis]" + +/mob/living/silicon/pai/start_pulling(var/atom/movable/AM) + + if(istype(AM,/obj/item)) + src << "You are far too small to pull anything!" + +/mob/living/silicon/pai/examine() + + set src in oview() + + if(!usr || !src) return + if( (usr.sdisabilities & BLIND || usr.blinded || usr.stat) && !istype(usr,/mob/dead/observer) ) + usr << "Something is there but you can't see it." + return + + var/msg = "*---------*\nThis is \icon[src][name], a personal AI!" + + switch(src.stat) + if(CONSCIOUS) + if(!src.client) msg += "\nIt appears to be in stand-by mode." //afk + if(UNCONSCIOUS) msg += "\nIt doesn't seem to be responding." + if(DEAD) msg += "\nIt looks completely unsalvageable." + msg += "\n*---------*" + + if(print_flavor_text()) msg += "\n[print_flavor_text()]\n" + + if (pose) + if( findtext(pose,".",lentext(pose)) == 0 && findtext(pose,"!",lentext(pose)) == 0 && findtext(pose,"?",lentext(pose)) == 0 ) + pose = addtext(pose,".") //Makes sure all emotes end with a period. + msg += "\nIt is [pose]" + + usr << msg + +/mob/living/silicon/pai/bullet_act(var/obj/item/projectile/Proj) + ..(Proj) + updatehealth() + if (stat != 2) + close_up() + return 2 + diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 6cf3bf2761f..4f7bd1a1c61 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -122,6 +122,8 @@ return var/soft = href_list["software"] var/sub = href_list["sub"] + if(!soft && !sub) + return if(soft) src.screen = soft if(sub) @@ -528,14 +530,21 @@ dat += {"

Medical Analysis Suite


Host Bioscan

"} - var/mob/living/M = src.loc - if(!istype(M, /mob/living)) - while (!istype(M, /mob/living)) - M = M.loc - if(istype(M, /turf)) - src.temp = "Error: No biological host found.
" - src.subscreen = 0 - return dat + var/mob/living/M + //If we are not deployed, check the holder of the card. + if(src.loc == card) + M = card.loc + + //If we are deployed or the card is not held, check the first living mob in our turf. + if(!M || !istype(M)) + var/turf/T = get_turf(src) + M = locate(/mob/living/) in T.contents + + if(!M || !istype(M)) + src.temp = "Error: No biological host found.
" + src.subscreen = 0 + return dat + dat += {"Bioscan Results for [M]:
Overall Status: [M.stat > 1 ? "dead" : "[M.health]% healthy"]

@@ -664,7 +673,7 @@ dat += "
    " if(!pda.toff) for (var/obj/item/device/pda/P in sortAtom(PDAs)) - if (!P.owner||P.toff||P == src.pda) continue + if (!P.owner||P.toff||P == src.pda||P.hidden) continue dat += "
  • [P]" dat += "
  • " dat += "
" diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index c53d13aeb8a..7329eb5b677 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -69,7 +69,6 @@ var/speed = 0 //Cause sec borgs gotta go fast //No they dont! var/scrambledcodes = 0 // Used to determine if a borg shows up on the robotics console. Setting to one hides them. var/braintype = "Cyborg" - var/pose var/base_icon = "" var/crisis = 0 var/hiddenborg = 0 @@ -1364,20 +1363,6 @@ lockcharge = state update_canmove() -/mob/living/silicon/robot/verb/pose() - set name = "Set Pose" - set desc = "Sets a description which will be shown when someone examines you." - set category = "IC" - - pose = copytext(sanitize(input(usr, "This is [src]. It is...", "Pose", null) as text), 1, MAX_MESSAGE_LEN) - -/mob/living/silicon/robot/verb/set_flavor() - set name = "Set Flavour Text" - set desc = "Sets an extended description of your character's features." - set category = "IC" - - flavor_text = copytext(sanitize(input(usr, "Please enter your new flavour text.", "Flavour text", null) as text), 1) - /mob/living/silicon/robot/proc/choose_icon(var/triesleft, var/list/module_sprites) if(triesleft<1 || !module_sprites.len) @@ -1434,7 +1419,7 @@ radio = new /obj/item/device/radio/borg/deathsquad(src) module = new /obj/item/weapon/robot_module/deathsquad(src) laws = new /datum/ai_laws/deathsquad() - + Namepick() /mob/living/silicon/robot/deathsquad/attack_hand(mob/user) @@ -1445,7 +1430,7 @@ ghosts += G get_borg_occupant(user, ghosts) return - + /mob/living/silicon/robot/deathsquad/proc/get_borg_occupant(mob/user as mob, var/list/possiblecandidates = list()) var/time_passed = world.time searching_for_ckey = 1 diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 04d55d5bd9b..ef2b067835b 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -2,11 +2,11 @@ var/ending = copytext(text, length(text)) if (ending == "?") - return "queries, \"[text]\""; + return "[speak_query], \"[text]\""; else if (ending == "!") - return "declares, \"[text]\""; + return "[speak_exclamation], \"[text]\""; - return "states, \"[text]\""; + return "[speak_statement], \"[text]\""; /mob/living/silicon/say(var/message) if (!message) @@ -115,12 +115,12 @@ if(isrobot(src)) var/mob/living/silicon/S = src if(S.designation) - desig = trim_left(S.designation) + desig = trim_left(S.designation) else desig = "Default" else if(isAI(src)) desig = "AI" - + message = trim(message) if (!message) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index fbd29d63595..6f88c99c71e 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -12,7 +12,11 @@ var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) var/designation = "" var/obj/item/device/camera/siliconcam/aiCamera = null //photography - +//Used in say.dm, allows for pAIs to have different say flavor text, as well as silicons, although the latter is not implemented. + var/speak_statement = "states" + var/speak_exclamation = "declares" + var/speak_query = "queries" + var/pose //Yes, now AIs can pose too. var/sensor_mode = 0 //Determines the current HUD. #define SEC_HUD 1 //Security HUD mode #define MED_HUD 2 //Medical HUD mode @@ -239,7 +243,21 @@ /mob/living/silicon/assess_threat() //Secbots won't hunt silicon units return -10 - + +/mob/living/silicon/verb/pose() + set name = "Set Pose" + set desc = "Sets a description which will be shown when someone examines you." + set category = "IC" + + pose = copytext(sanitize(input(usr, "This is [src]. It is...", "Pose", null) as text), 1, MAX_MESSAGE_LEN) + +/mob/living/silicon/verb/set_flavor() + set name = "Set Flavour Text" + set desc = "Sets an extended description of your character's features." + set category = "IC" + + flavor_text = copytext(sanitize(input(usr, "Please enter your new flavour text.", "Flavour text", null) as text), 1) + /mob/living/silicon/proc/toggle_sensor_mode() var/sensor_type = input("Please select sensor type.", "Sensor Integration", null) in list("Security", "Medical","Disable") switch(sensor_type) @@ -251,4 +269,4 @@ src << "Life signs monitor overlay enabled." if ("Disable") sensor_mode = 0 - src << "Sensor augmentations disabled." + src << "Sensor augmentations disabled." diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 7842bc402f6..a78d7995122 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -761,6 +761,7 @@ var/list/slot_equipment_priority = list( \ /mob/MouseDrop(mob/M as mob) ..() if(M != usr) return + if(M.small) return // Stops pAI drones and small mobs (borers, parrots, crabs) from stripping people. --DZD if(usr == src) return if(!Adjacent(usr)) return if(istype(M,/mob/living/silicon/ai)) return @@ -1309,6 +1310,6 @@ mob/proc/yank_out_object() if(paralysis) AdjustParalysis(-1) return paralysis - + /mob/proc/assess_threat() //For sec bot threat assessment return \ No newline at end of file diff --git a/icons/mob/pai.dmi b/icons/mob/pai.dmi new file mode 100644 index 00000000000..f286389bffc Binary files /dev/null and b/icons/mob/pai.dmi differ
Transmit:[(radio.wires & 4) ? "En" : "Dis" ]abled + [(!radio.isWireCut(WIRE_TRANSMIT)) ? "En" : "Dis" ]abled
Receive:[(radio.wires & 2) ? "En" : "Dis" ]abled + [(!radio.isWireCut(WIRE_RECEIVE)) ? "En" : "Dis" ]abled
Signal Pulser:[(radio.wires & 1) ? "En" : "Dis" ]abled + [(!radio.isWireCut(WIRE_SIGNAL)) ? "En" : "Dis" ]abled