Files
CHOMPStation2/code/modules/mob/mob_helpers.dm
2023-04-30 16:14:51 +12:00

701 lines
22 KiB
Plaintext

// fun if you want to typecast humans/monkeys/etc without writing long path-filled lines.
/proc/isxenomorph(A)
if(istype(A, /mob/living/carbon/human))
var/mob/living/carbon/human/H = A
return istype(H.species, /datum/species/xenos)
return 0
/proc/issmall(A)
if(A && istype(A, /mob/living))
var/mob/living/L = A
return L.mob_size <= MOB_SMALL
return 0
//returns the number of size categories between two mob_sizes, rounded. Positive means A is larger than B
/proc/mob_size_difference(var/mob_size_A, var/mob_size_B)
return round(log(2, mob_size_A/mob_size_B), 1)
/mob/proc/can_wield_item(obj/item/W)
if(W.w_class >= ITEMSIZE_LARGE && issmall(src))
return FALSE //M is too small to wield this
return TRUE
/proc/istiny(A)
if(A && istype(A, /mob/living))
var/mob/living/L = A
return L.mob_size <= MOB_TINY
return 0
/proc/ismini(A)
if(A && istype(A, /mob/living))
var/mob/living/L = A
return L.mob_size <= MOB_MINISCULE
return 0
/mob/living/silicon/isSynthetic()
return 1
/mob/proc/isMonkey()
return 0
/mob/living/carbon/human/isMonkey()
return istype(species, /datum/species/monkey)
/proc/isdeaf(A)
if(istype(A, /mob))
var/mob/M = A
return (M.sdisabilities & DEAF) || M.ear_deaf
return 0
/mob/proc/get_ear_protection()
return 0
/mob/proc/break_cloak()
return
/mob/proc/is_cloaked()
return FALSE
/proc/hasorgans(A) // Fucking really??
return ishuman(A)
/proc/iscuffed(A)
if(istype(A, /mob/living/carbon))
var/mob/living/carbon/C = A
if(C.handcuffed)
return 1
return 0
/proc/hassensorlevel(A, var/level)
var/mob/living/carbon/human/H = A
if(istype(H) && istype(H.w_uniform, /obj/item/clothing/under))
var/obj/item/clothing/under/U = H.w_uniform
return U.sensor_mode >= level
return 0
/proc/getsensorlevel(A)
var/mob/living/carbon/human/H = A
if(istype(H) && istype(H.w_uniform, /obj/item/clothing/under))
var/obj/item/clothing/under/U = H.w_uniform
return U.sensor_mode
return SUIT_SENSOR_OFF
/proc/is_admin(var/mob/user)
return check_rights(R_ADMIN|R_EVENT, 0, user) != 0
/* CHOMPEdit - See modular_chomp/code/_HELPERS/type2type/color.dm
/proc/hsl2rgb(h, s, l)
return //TODO: Implement
*/
/*
Miss Chance
*/
/proc/check_zone(zone)
if(!zone) return BP_TORSO
switch(zone)
if(O_EYES)
zone = BP_HEAD
if(O_MOUTH)
zone = BP_HEAD
return zone
// Returns zone with a certain probability. If the probability fails, or no zone is specified, then a random body part is chosen.
// Do not use this if someone is intentionally trying to hit a specific body part.
// Use get_zone_with_miss_chance() for that.
/proc/ran_zone(zone, probability)
if (zone)
zone = check_zone(zone)
if (prob(probability))
return zone
var/ran_zone = zone
while (ran_zone == zone)
ran_zone = pick (
organ_rel_size[BP_HEAD]; BP_HEAD,
organ_rel_size[BP_TORSO]; BP_TORSO,
organ_rel_size[BP_GROIN]; BP_GROIN,
organ_rel_size[BP_L_ARM]; BP_L_ARM,
organ_rel_size[BP_R_ARM]; BP_R_ARM,
organ_rel_size[BP_L_LEG]; BP_L_LEG,
organ_rel_size[BP_R_LEG]; BP_R_LEG,
organ_rel_size[BP_L_HAND]; BP_L_HAND,
organ_rel_size[BP_R_HAND]; BP_R_HAND,
organ_rel_size[BP_L_FOOT]; BP_L_FOOT,
organ_rel_size[BP_R_FOOT]; BP_R_FOOT,
)
return ran_zone
// Emulates targetting a specific body part, and miss chances
// May return null if missed
// miss_chance_mod may be negative.
/proc/get_zone_with_miss_chance(zone, var/mob/target, var/miss_chance_mod = 0, var/ranged_attack=0, var/force_hit = FALSE)
zone = check_zone(zone)
if(!ranged_attack)
// you cannot miss if your target is prone or restrained
if(target.buckled || target.lying)
return zone
// if your target is being grabbed aggressively by someone you cannot miss either
for(var/obj/item/weapon/grab/G in target.grabbed_by)
if(G.state >= GRAB_AGGRESSIVE)
return zone
if(force_hit)
return zone
var/miss_chance = 10
if (zone in base_miss_chance)
miss_chance = base_miss_chance[zone]
if (zone == "eyes" || zone == "mouth")
miss_chance = base_miss_chance["head"]
// miss_chance = max(miss_chance + miss_chance_mod, 0) CHOMPEDIT - removing baymiss
if(prob(miss_chance_mod)) //CHOMPADD - removing baymiss. Checking the miss chance for legacy. This should only be affected by special cases and evasion.
return null
if(prob(miss_chance))
//if(prob(70))
// return null CHOMPEDIT - removing baymiss
return pick(base_miss_chance)
return zone
/proc/stars(n, pr)
if (pr == null)
pr = 25
if (pr < 0)
return null
else
if (pr >= 100)
return n
var/te = n
var/t = ""
n = length(n)
var/p = null
p = 1
var/intag = 0
while(p <= n)
var/char = copytext(te, p, p + 1)
if (char == "<") //let's try to not break tags
intag = !intag
if (intag || char == " " || prob(pr))
t = text("[][]", t, char)
else
t = text("[]*", t)
if (char == ">")
intag = !intag
p++
return t
/proc/stars_all(list/message_pieces, pr)
// eugh, we have to clone the list to avoid collateral damage due to the nature of these messages
. = list()
for(var/datum/multilingual_say_piece/S in message_pieces)
. += new /datum/multilingual_say_piece(S.speaking, stars(S.message))
/proc/slur(phrase)
phrase = html_decode(phrase)
var/leng=length(phrase)
var/counter=length(phrase)
var/newphrase=""
var/newletter=""
while(counter>=1)
newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2)
if(rand(1,3)==3)
if(lowertext(newletter)=="o") newletter="u"
if(lowertext(newletter)=="s") newletter="ch"
if(lowertext(newletter)=="a") newletter="ah"
if(lowertext(newletter)=="c") newletter="k"
switch(rand(1,9))
if(1,3,5,8) newletter="[lowertext(newletter)]"
//if(2,4,6,15) newletter="[uppertext(newletter)]"
if(2,4,6,9) newletter="[uppertext(newletter)]"
if(7) newletter+="'"
//if(9,10) newletter="<b>[newletter]</b>"
//if(11,12) newletter="<big>[newletter]</big>"
//if(13) newletter="<small>[newletter]</small>"
newphrase+="[newletter]";counter-=1
return newphrase
/proc/stutter(n)
var/te = html_decode(n)
var/t = ""//placed before the message. Not really sure what it's for.
n = length(n)//length of the entire word
var/p = null
p = 1//1 is the start of any word
while(p <= n)//while P, which starts at 1 is less or equal to N which is the length.
var/n_letter = copytext(te, p, p + 1)//copies text from a certain distance. In this case, only one letter at a time.
if (prob(80) && (ckey(n_letter) in list("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z")))
if (prob(10))
n_letter = text("[n_letter]-[n_letter]-[n_letter]-[n_letter]")//replaces the current letter with this instead.
else
if (prob(20))
n_letter = text("[n_letter]-[n_letter]-[n_letter]")
else
if (prob(5))
n_letter = null
else
n_letter = text("[n_letter]-[n_letter]")
t = text("[t][n_letter]")//since the above is ran through for each letter, the text just adds up back to the original word.
p++//for each letter p is increased to find where the next letter will be.
return sanitize(t)
/proc/Gibberish(t, p)//t is the inputted message, and any value higher than 70 for p will cause letters to be replaced instead of added
/* Turn text into complete gibberish! */
var/returntext = ""
for(var/i = 1, i <= length(t), i++)
var/letter = copytext(t, i, i+1)
if(prob(50))
if(p >= 70)
letter = ""
for(var/j = 1, j <= rand(0, 2), j++)
letter += pick("#","@","*","&","%","$","/", "<", ">", ";","*","*","*","*","*","*","*")
returntext += letter
return returntext
/proc/ninjaspeak(n)
/*
The difference with stutter is that this proc can stutter more than 1 letter
The issue here is that anything that does not have a space is treated as one word (in many instances). For instance, "LOOKING," is a word, including the comma.
It's fairly easy to fix if dealing with single letters but not so much with compounds of letters./N
*/
var/te = html_decode(n)
var/t = ""
n = length(n)
var/p = 1
while(p <= n)
var/n_letter
var/n_mod = rand(1,4)
if(p+n_mod>n+1)
n_letter = copytext(te, p, n+1)
else
n_letter = copytext(te, p, p+n_mod)
if (prob(50))
if (prob(30))
n_letter = text("[n_letter]-[n_letter]-[n_letter]")
else
n_letter = text("[n_letter]-[n_letter]")
else
n_letter = text("[n_letter]")
t = text("[t][n_letter]")
p=p+n_mod
return sanitize(t)
/proc/shake_camera(mob/M, duration, strength=1)
if(!M || !M.client || M.shakecamera || M.stat || isEye(M) || isAI(M))
return
M.shakecamera = 1
spawn(1)
if(!M.client)
return
var/atom/oldeye=M.client.eye
var/aiEyeFlag = 0
if(istype(oldeye, /mob/observer/eye/aiEye))
aiEyeFlag = 1
var/x
for(x=0; x<duration, x++)
if(aiEyeFlag)
M.client.eye = locate(dd_range(1,oldeye.loc.x+rand(-strength,strength),world.maxx),dd_range(1,oldeye.loc.y+rand(-strength,strength),world.maxy),oldeye.loc.z)
else
M.client.eye = locate(dd_range(1,M.loc.x+rand(-strength,strength),world.maxx),dd_range(1,M.loc.y+rand(-strength,strength),world.maxy),M.loc.z)
sleep(1)
M.client.eye=oldeye
M.shakecamera = 0
/proc/findname(msg)
for(var/mob/M in mob_list)
if (M.real_name == text("[msg]"))
return 1
return 0
/mob/proc/abiotic(var/full_body = 0)
return 0
//converts intent-strings into numbers and back
var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT)
/proc/intent_numeric(argument)
if(istext(argument))
switch(argument)
if(I_HELP) return 0
if(I_DISARM) return 1
if(I_GRAB) return 2
else return 3
else
switch(argument)
if(0) return I_HELP
if(1) return I_DISARM
if(2) return I_GRAB
else return I_HURT
//change a mob's act-intent. Input the intent as a string such as "help" or use "right"/"left
/mob/verb/a_intent_change(input as text)
set name = "a-intent"
set hidden = 1
if(isliving(src) && !isrobot(src))
switch(input)
if(I_HELP,I_DISARM,I_GRAB,I_HURT)
a_intent = input
if("right")
a_intent = intent_numeric((intent_numeric(a_intent)+1) % 4)
if("left")
a_intent = intent_numeric((intent_numeric(a_intent)+3) % 4)
if(hud_used && hud_used.action_intent)
hud_used.action_intent.icon_state = "intent_[a_intent]"
else if(isrobot(src))
switch(input)
if(I_HELP)
a_intent = I_HELP
if(I_HURT)
a_intent = I_HURT
if("right","left")
a_intent = intent_numeric(intent_numeric(a_intent) - 3)
if(hud_used && hud_used.action_intent)
if(a_intent == I_HURT)
hud_used.action_intent.icon_state = I_HURT
else
hud_used.action_intent.icon_state = I_HELP
/proc/is_blind(A)
if(istype(A, /mob/living/carbon))
var/mob/living/carbon/C = A
if(C.sdisabilities & BLIND || C.blinded)
return 1
return 0
/proc/mobs_in_area(var/area/A)
var/list/mobs = list()
for(var/M in mob_list)
if(get_area(M) == A)
mobs += M
return mobs
//Direct dead say used both by emote and say
//It is somewhat messy. I don't know what to do.
//I know you can't see the change, but I rewrote the name code. It is significantly less messy now
/proc/say_dead_direct(var/message, var/mob/subject = null)
var/name
var/keyname
if(subject && subject.client)
var/client/C = subject.client
keyname = (C.holder && C.holder.fakekey) ? C.holder.fakekey : C.key
if(C.mob) //Most of the time this is the dead/observer mob; we can totally use him if there is no better name
var/mindname
var/realname = C.mob.real_name
if(C.mob.mind)
mindname = C.mob.mind.name
if(C.mob.mind.original && C.mob.mind.original.real_name)
realname = C.mob.mind.original.real_name
if(mindname && mindname != realname)
name = "[realname] died as [mindname]"
else
name = realname
if(subject && subject.forbid_seeing_deadchat && !subject.client.holder)
return // Can't talk in deadchat if you can't see it.
for(var/mob/M in player_list)
if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights && M.is_preference_enabled(/datum/client_preference/holder/show_staff_dsay))) && M.is_preference_enabled(/datum/client_preference/show_dsay))
var/follow
var/lname
if(M.forbid_seeing_deadchat && !M.client.holder)
continue
if(subject)
if(M.is_key_ignored(subject.client.key)) // If we're ignored, do nothing.
continue
if(subject != M)
follow = "([ghost_follow_link(subject, M)]) "
if(M.stat != DEAD && M.client.holder)
follow = "([admin_jump_link(subject, M.client.holder)]) "
var/mob/observer/dead/DM
if(istype(subject, /mob/observer/dead))
DM = subject
if(M.client.holder) // What admins see
lname = "[keyname][(DM && DM.anonsay) ? "*" : (DM ? "" : "^")] ([name])"
else
if(DM && DM.anonsay) // If the person is actually observer they have the option to be anonymous
lname = "Ghost of [name]"
else if(DM) // Non-anons
lname = "[keyname] ([name])"
else // Everyone else (dead people who didn't ghost yet, etc.)
lname = name
lname = "<span class='name'>[lname]</span> "
to_chat(M, "<span class='deadsay'>" + create_text_tag("dead", "DEAD:", M.client) + " [lname][follow][message]</span>")
/proc/say_dead_object(var/message, var/obj/subject = null)
for(var/mob/M in player_list)
if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights && M.is_preference_enabled(/datum/client_preference/holder/show_staff_dsay))) && M.is_preference_enabled(/datum/client_preference/show_dsay))
var/follow
var/lname = "Game Master"
if(M.forbid_seeing_deadchat && !M.client.holder)
continue
if(subject)
lname = "[subject.name] ([subject.x],[subject.y],[subject.z])"
lname = "<span class='name'>[lname]</span> "
to_chat(M, "<span class='deadsay'>" + create_text_tag("event_dead", "EVENT:", M.client) + " [lname][follow][message]</span>")
//Announces that a ghost has joined/left, mainly for use with wizards
/proc/announce_ghost_joinleave(O, var/joined_ghosts = 1, var/message = "")
var/client/C
//Accept any type, sort what we want here
if(istype(O, /mob))
var/mob/M = O
if(M.client)
C = M.client
else if(istype(O, /client))
C = O
else if(istype(O, /datum/mind))
var/datum/mind/M = O
if(M.current && M.current.client)
C = M.current.client
else if(M.original && M.original.client)
C = M.original.client
if(C)
var/name
if(C.mob)
var/mob/M = C.mob
if(M.mind && M.mind.name)
name = M.mind.name
if(M.real_name && M.real_name != name)
if(name)
name += " ([M.real_name])"
else
name = M.real_name
if(!name)
name = (C.holder && C.holder.fakekey) ? C.holder.fakekey : C.key
if(joined_ghosts)
//say_dead_direct("The ghost of <span class='name'>[name]</span> now [pick("skulks","lurks","prowls","creeps","stalks")] among the dead. [message]") //ChompEDIT - Removed for ghost privacy
log_and_message_admins("<span class='name'>[name]</span> entered ghost/observe mode. [message]") //ChompEDIT
else
//say_dead_direct("<span class='name'>[name]</span> no longer [pick("skulks","lurks","prowls","creeps","stalks")] in the realm of the dead. [message]") //ChompEDIT - Removed for ghost privacy
log_and_message_admins("<span class='name'>[name]</span> left ghost/observe mode. [message]") //ChompEDIT
/mob/proc/switch_to_camera(var/obj/machinery/camera/C)
if (!C.can_use() || stat || (get_dist(C, src) > 1 || machine != src || blinded || !canmove))
return 0
check_eye(src)
return 1
/mob/living/silicon/ai/switch_to_camera(var/obj/machinery/camera/C)
if(!C.can_use() || !is_in_chassis())
return 0
eyeobj.setLoc(C)
return 1
// Returns true if the mob has a client which has been active in the last given X minutes.
/mob/proc/is_client_active(var/active = 1)
return client && client.inactivity < active MINUTES
/mob/proc/can_eat()
return 1
/mob/proc/can_force_feed()
return 1
#define SAFE_PERP -50
/mob/living/proc/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest)
if(stat == DEAD)
return SAFE_PERP
return 0
/mob/living/carbon/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest)
if(handcuffed)
return SAFE_PERP
return ..()
/mob/living/carbon/human/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest)
var/threatcount = ..()
if(. == SAFE_PERP)
return SAFE_PERP
//Agent cards lower threatlevel.
var/obj/item/weapon/card/id/id = GetIdCard()
if(id && istype(id, /obj/item/weapon/card/id/syndicate))
threatcount -= 2
// A proper CentCom id is hard currency.
else if(id && istype(id, /obj/item/weapon/card/id/centcom))
return SAFE_PERP
if(check_access && !access_obj.allowed(src))
threatcount += 4
if(auth_weapons && !access_obj.allowed(src))
if(istype(l_hand, /obj/item/weapon/gun) || istype(l_hand, /obj/item/weapon/melee) && !istype(l_hand, /obj/item/weapon/gun/energy/floragun) && !istype(l_hand, /obj/item/weapon/gun/energy/sizegun) && !istype(l_hand, /obj/item/weapon/gun/launcher/confetti_cannon) && !istype(l_hand, /obj/item/weapon/gun/energy/lasertag)) //CHOMP Edit: Specific guns to the exclusion list.
threatcount += 4
if(istype(r_hand, /obj/item/weapon/gun) || istype(r_hand, /obj/item/weapon/melee))
threatcount += 4
if(istype(belt, /obj/item/weapon/gun) || istype(belt, /obj/item/weapon/melee))
threatcount += 2
if(species.name != SPECIES_HUMAN)
threatcount += 2
if(check_records || check_arrest)
var/perpname = name
if(id)
perpname = id.registered_name
var/datum/data/record/R = find_security_record("name", perpname)
if(check_records && !R)
threatcount += 4
if(check_arrest && R && (R.fields["criminal"] == "*Arrest*"))
threatcount += 4
return threatcount
/mob/living/simple_mob/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest)
var/threatcount = ..()
if(. == SAFE_PERP)
return SAFE_PERP
if(has_AI() && ai_holder.hostile && faction != "neutral") // Otherwise Runtime gets killed.
threatcount += 4
return threatcount
// Beepsky will (try to) only beat 'bad' slimes.
/mob/living/simple_mob/slime/xenobio/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest)
var/threatcount = 0
if(stat == DEAD)
return SAFE_PERP
if(is_justified_to_discipline())
threatcount += 4
/*
if(discipline && !rabid)
if(!target_mob || istype(target_mob, /mob/living/carbon/human/monkey))
return SAFE_PERP
if(target_mob)
threatcount += 4
if(victim)
threatcount += 4
*/
if(has_AI())
var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder
if(AI.rabid)
threatcount = 10
return threatcount
#undef SAFE_PERP
//TODO: Integrate defence zones and targeting body parts with the actual organ system, move these into organ definitions.
//The base miss chance for the different defence zones
var/list/global/base_miss_chance = list(
BP_HEAD = 40,
BP_TORSO = 10,
BP_GROIN = 20,
BP_L_LEG = 20,
BP_R_LEG = 20,
BP_L_ARM = 20,
BP_R_ARM = 20,
BP_L_HAND = 50,
BP_R_HAND = 50,
BP_L_FOOT = 50,
BP_R_FOOT = 50,
)
//Used to weight organs when an organ is hit randomly (i.e. not a directed, aimed attack).
//Also used to weight the protection value that armour provides for covering that body part when calculating protection from full-body effects.
var/list/global/organ_rel_size = list(
BP_HEAD = 25,
BP_TORSO = 70,
BP_GROIN = 30,
BP_L_LEG = 25,
BP_R_LEG = 25,
BP_L_ARM = 25,
BP_R_ARM = 25,
BP_L_HAND = 10,
BP_R_HAND = 10,
BP_L_FOOT = 10,
BP_R_FOOT = 10,
)
/mob/proc/flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = FALSE, visual = FALSE, type = /obj/screen/fullscreen/flash)
return
//Recalculates what planes this mob can see using their plane_holder, for humans this is checking slots, for others, could be whatever.
/mob/proc/recalculate_vis()
return
//General HUD updates done regularly (health puppet things, etc)
/mob/proc/handle_regular_hud_updates()
return
//Handle eye things like the Byond SEE_TURFS, SEE_OBJS, etc.
/mob/proc/handle_vision()
return
//Icon is used to occlude things like huds from the faulty byond context menu.
// http://www.byond.com/forum/?post=2336679
var/global/image/backplane
/hook/startup/proc/generate_backplane()
backplane = image('icons/misc/win32.dmi')
backplane.alpha = 0
backplane.plane = -100
backplane.layer = MOB_LAYER-0.1
backplane.mouse_opacity = 0
return TRUE
/mob/proc/get_sound_env(var/pressure_factor)
if (pressure_factor < 0.5)
return SPACE
else
var/area/A = get_area(src)
return A.sound_env
/mob/proc/position_hud_item(var/obj/item/item, var/slot)
if(!istype(hud_used) || !slot || !LAZYLEN(hud_used.slot_info))
return
//They may have hidden their entire hud but the hands
if(!hud_used.hud_shown && slot > slot_r_hand)
item.screen_loc = null
return
//They may have hidden the icons in the bottom left with the hide button
if(!hud_used.inventory_shown && slot > slot_r_store)
item.screen_loc = null
return
var/screen_place = hud_used.slot_info["[slot]"]
if(!screen_place)
item.screen_loc = null
return
item.screen_loc = screen_place
/mob/proc/can_feed()
return TRUE