Ports SNPCs

This commit is contained in:
Tastyfish
2016-04-09 00:10:01 -04:00
parent ea9489db70
commit 7d259d35c7
31 changed files with 3447 additions and 208 deletions
+12 -12
View File
@@ -521,12 +521,12 @@ var/list/ventcrawl_machinery = list(/obj/machinery/atmospherics/unary/vent_pump,
/mob/living/carbon/throw_item(atom/target)
throw_mode_off()
if(usr.stat || !target)
if(stat || !target)
return
if(target.type == /obj/screen)
return
var/atom/movable/item = src.get_active_hand()
var/atom/movable/item = get_active_hand()
if(!item || (item.flags & NODROP))
return
@@ -542,14 +542,14 @@ var/list/ventcrawl_machinery = list(/obj/machinery/atmospherics/unary/vent_pump,
var/start_T_descriptor = "<font color='#6b5d00'>tile at [start_T.x], [start_T.y], [start_T.z] in area [get_area(start_T)]</font>"
var/end_T_descriptor = "<font color='#6b4400'>tile at [end_T.x], [end_T.y], [end_T.z] in area [get_area(end_T)]</font>"
M.attack_log += text("\[[time_stamp()]\] <font color='orange'>Has been thrown by [key_name(usr)] from [start_T_descriptor] with the target [end_T_descriptor]</font>")
usr.attack_log += text("\[[time_stamp()]\] <font color='red'>Has thrown [key_name(M)] from [start_T_descriptor] with the target [end_T_descriptor]</font>")
msg_admin_attack("[key_name_admin(usr)] has thrown [key_name_admin(M)] from [start_T_descriptor] with the target [end_T_descriptor]")
M.attack_log += text("\[[time_stamp()]\] <font color='orange'>Has been thrown by [key_name(src)] from [start_T_descriptor] with the target [end_T_descriptor]</font>")
attack_log += text("\[[time_stamp()]\] <font color='red'>Has thrown [key_name(M)] from [start_T_descriptor] with the target [end_T_descriptor]</font>")
msg_admin_attack("[key_name_admin(src)] has thrown [key_name_admin(M)] from [start_T_descriptor] with the target [end_T_descriptor]")
if(!iscarbon(usr))
if(!iscarbon(src))
M.LAssailant = null
else
M.LAssailant = usr
M.LAssailant = src
if(!item)
return //Grab processing has a chance of returning null
@@ -557,17 +557,17 @@ var/list/ventcrawl_machinery = list(/obj/machinery/atmospherics/unary/vent_pump,
unEquip(item)
update_icons()
if (istype(usr, /mob/living/carbon)) //Check if a carbon mob is throwing. Modify/remove this line as required.
item.loc = usr.loc
if(src.client)
src.client.screen -= item
if (istype(src, /mob/living/carbon)) //Check if a carbon mob is throwing. Modify/remove this line as required.
item.loc = loc
if(client)
client.screen -= item
if(istype(item, /obj/item))
item:dropped(src) // let it know it's been dropped
//actually throw it!
if (item)
item.layer = initial(item.layer)
src.visible_message("\red [src] has thrown [item].")
visible_message("\red [src] has thrown [item].")
newtonian_move(get_dir(target, src))
@@ -254,7 +254,9 @@
msg += "[t_He] [t_has] a stupid expression on [t_his] face.\n"
if(species.show_ssd && (!species.has_organ["brain"] || get_int_organ(/obj/item/organ/internal/brain)) && stat != DEAD)
if(!key)
if(istype(src, /mob/living/carbon/human/interactive))
msg += "<span class='deadsay'>[t_He] appears to be some sort of sick automaton: [t_his] eyes are glazed over and [t_his] mouth is slightly agape.</span>\n"
else if(!key)
msg += "<span class='deadsay'>[t_He] [t_is] fast asleep. It doesn't look like they are waking up anytime soon.</span>\n"
else if(!client)
msg += "[t_He] [t_has] suddenly fallen asleep.\n"
@@ -0,0 +1,874 @@
/mob/living/carbon/human/interactive/proc/chatter(obj)
var/verbs_use = pick_list(speak_file, "verbs_use")
var/verbs_touch = pick_list(speak_file, "verbs_touch")
var/verbs_move = pick_list(speak_file, "verbs_move")
var/nouns_insult = pick_list(speak_file, "nouns_insult")
var/nouns_generic = pick_list(speak_file, "nouns_generic")
var/nouns_objects = pick_list(speak_file, "nouns_objects")
var/nouns_body = pick_list(speak_file, "nouns_body")
var/adjective_insult = pick_list(speak_file, "adjective_insult")
var/adjective_objects = pick_list(speak_file, "adjective_objects")
var/adjective_generic = pick_list(speak_file, "adjective_generic")
var/curse_words = pick_list(speak_file, "curse_words")
var/chatmsg = ""
if(prob(10)) // 10% chance to broadcast it over the radio
chatmsg = ";"
if(prob(chattyness) || knownStrings.len < 10) // say a generic phrase, otherwise draw from our strings.
if(doing & SNPC_INTERACTING)
if(prob(chattyness))
chatmsg += pick("This [nouns_objects] is a little [adjective_objects].",
"Well [verbs_use] my [nouns_body], this [nouns_insult] is pretty [adjective_insult].",
"[capitalize(curse_words)], what am I meant to do with this [adjective_insult] [nouns_objects].")
else if(doing & SNPC_TRAVEL)
if(prob(chattyness))
chatmsg += pick("Oh [curse_words], [verbs_move]!",
"Time to get my [adjective_generic] [adjective_insult] [nouns_body] elsewhere.",
"I wonder if there is anything to [verbs_use] and [verbs_touch] somewhere else..")
else if(doing & SNPC_FIGHTING)
if(prob(chattyness))
chatmsg += pick("I'm going to [verbs_use] you, you [adjective_insult] [nouns_insult]!",
"Rend and [verbs_touch], Rend and [verbs_use]!",
"You [nouns_insult], I'm going to [verbs_use] you right in the [nouns_body]. JUST YOU WAIT!")
if(prob(chattyness/2))
chatmsg = ";"
var/what = pick(1,2,3,4,5)
switch(what)
if(1)
chatmsg += "Well [curse_words], this is a [adjective_generic] situation."
if(2)
chatmsg += "Oh [curse_words], that [nouns_insult] was one hell of an [adjective_insult] [nouns_body]."
if(3)
chatmsg += "I want to [verbs_use] that [nouns_insult] when I find them."
if(4)
chatmsg += "[pick("Innocent","Guilty","Traitorous","Honk")] until proven [adjective_generic]!"
if(5)
var/toSay = ""
for(var/i = 0; i < 5; i++)
curse_words = pick_list(speak_file, "curse_words")
toSay += "[curse_words] "
chatmsg += "Hey [nouns_generic], why dont you go [copytext(toSay, 1, length(toSay))], you [nouns_insult]!"
else if(prob(chattyness))
chatmsg += pick(knownStrings)
if(prob(25)) // cut out some phrases now and then to make sure we're fresh and new
knownStrings -= pick(chatmsg)
if(chatmsg != ";" && chatmsg != "")
say(chatmsg)
/mob/living/carbon/human/interactive/proc/traitor(obj)
if(traitorType == SNPC_PSYCHO && nearby.len)
traitorTarget = pick(nearby)
if(prob(traitorScale))
if(!Adjacent(traitorTarget) && !(traitorType == SNPC_PSYCHO))
tryWalk(get_turf(traitorTarget))
else
switch(traitorType)
if(SNPC_BRUTE)
retal = 1
retal_target = traitorTarget
if(SNPC_STEALTH)
if(istype(traitorTarget,/mob)) // it's inside something, lets kick their shit in
var/mob/M = traitorTarget
if(!M.stat)
retal = 1
retal_target = traitorTarget
else
var/obj/item/I = traitorTarget
I.loc = get_turf(traitorTarget) // pull it outta them
else
take_to_slot(traitorTarget)
if(SNPC_MARTYR)
custom_emote(1, "[src]'s chest opens up, revealing a large mass of explosives and tangled wires!")
if(inactivity_period <= 0)
inactivity_period = 9999 // technically infinite
if(do_after(src, 60, target=traitorTarget))
custom_emote(1, "A fire bursts from [src]'s eyes, igniting white hot and consuming their body in a flaming explosion!")
explosion(src, 6, 6, 6)
else
inactivity_period = 0
custom_emote(1, "[src]'s chest closes, hiding their insides.")
if(SNPC_PSYCHO)
var/choice = pick(typesof(/obj/item/weapon/grenade/chem_grenade) - /obj/item/weapon/grenade/chem_grenade)
new choice(src)
retal = 1
retal_target = traitorTarget
/mob/living/carbon/human/interactive/proc/botany(obj)
if(shouldModulePass())
return
if(enforceHome())
return
var/list/allContents = getAllContents()
var/obj/item/weapon/reagent_containers/glass/beaker/bluespace/internalBeaker = locate(/obj/item/weapon/reagent_containers/glass/beaker/bluespace) in allContents
var/obj/item/weapon/storage/bag/plants/internalBag = locate(/obj/item/weapon/storage/bag/plants) in allContents
if(!internalBag)
internalBag = new/obj/item/weapon/storage/bag/plants(src)
if(!internalBeaker)
internalBeaker = new/obj/item/weapon/reagent_containers/glass/beaker/bluespace(src)
internalBeaker.name = "Grow-U-All Super Spray"
if(internalBeaker && internalBag)
var/obj/machinery/portable_atmospherics/hydroponics/HP
//consider the appropriate target
var/list/considered = list()
for(var/obj/machinery/portable_atmospherics/hydroponics/tester in view(12,src))
considered[tester] = 1
if(!tester.seed)
considered[tester] += 50
if(tester.weedlevel > 0)
considered[tester] += 5
if(tester.pestlevel > 0)
considered[tester] += 5
if(tester.nutrilevel < tester.maxnutri)
considered[tester] += 15
if(tester.waterlevel < tester.maxwater)
considered[tester] += 15
if(tester.harvest || tester.dead)
considered[tester] += 100
considered[tester] = max(1, considered[tester] - get_dist(src,tester))
var/index = 0
for(var/A in considered)
++index
if(considered[A] > considered[HP] || !HP)
HP = considered[index]
if(HP)
TARGET = HP
if(!Adjacent(HP))
tryWalk(get_turf(HP))
else
if(HP.harvest || HP.dead)
HP.attack_hand(src)
else if(!HP.seed)
var/obj/item/seeds/SEED = new /obj/item/seeds/random(src)
custom_emote(1, "[pick("gibbers","drools","slobbers","claps wildly","spits")] towards [TARGET], producing a [SEED]!")
HP.attackby(SEED, src)
else
var/change = 0
if(HP.weedlevel > 0)
change = 1
if(!internalBeaker.reagents.has_reagent("atrazine", 10))
internalBeaker.reagents.add_reagent("atrazine", 10)
if(HP.pestlevel > 0)
change = 1
if(!internalBeaker.reagents.has_reagent("diethylamine", 10))
internalBeaker.reagents.add_reagent("diethylamine", 10)
if(HP.nutrilevel < HP.maxnutri)
change = 1
if(!internalBeaker.reagents.has_reagent("eznutrient", 15))
internalBeaker.reagents.add_reagent("eznutrient", 15)
if(!internalBeaker.reagents.has_reagent("diethylamine", 15))
internalBeaker.reagents.add_reagent("diethylamine", 15)
if(HP.waterlevel < HP.maxwater)
change = 1
if(!internalBeaker.reagents.has_reagent("holywater", 50))
internalBeaker.reagents.add_reagent("holywater", 50)
if(change)
HP.attackby(internalBeaker,src)
var/obj/item/weapon/reagent_containers/food/snacks/grown/GF = locate(/obj/item/weapon/reagent_containers/food/snacks/grown) in view(12,src)
if(GF)
if(!Adjacent(GF))
tryWalk(get_turf(GF))
else
GF.attackby(internalBag,src)
if(internalBag.contents.len)
var/obj/machinery/smartfridge/SF = locate(/obj/machinery/smartfridge) in range(12, src)
if(!Adjacent(SF))
tryWalk(get_turf(SF))
else
custom_emote(1, "[pick("gibbers","drools","slobbers","claps wildly","spits")], upending the [internalBag]'s contents all over the [SF]!")
//smartfridges call updateUsrDialog when you call attackby, so we're going to have to cheese-magic-space this
for(var/obj/toLoad in internalBag.contents)
if(contents.len >= SF.max_n_of_items)
break
if(SF.accept_check(toLoad))
SF.load(toLoad, src)
else
qdel(toLoad) // destroy everything we dont need
/mob/living/carbon/human/interactive/proc/bartend(obj)
if(shouldModulePass())
return
if(enforceHome())
return
var/list/rangeCheck = oview(6,src)
var/obj/structure/table/RT
var/mob/living/carbon/human/serveTarget
for(var/mob/living/carbon/human/H in rangeCheck)
if(!locate(/obj/item/weapon/reagent_containers/food/drinks) in orange(1, H))
serveTarget = H
if(serveTarget)
RT = locate(/obj/structure/table) in orange(1, serveTarget)
if(RT && serveTarget)
if(!Adjacent(RT))
tryWalk(get_turf(RT))
else
var/drinkChoice = pick(typesof(/obj/item/weapon/reagent_containers/food/drinks) - /obj/item/weapon/reagent_containers/food/drinks)
if(drinkChoice)
var/obj/item/weapon/reagent_containers/food/drinks/D = new drinkChoice(get_turf(src))
RT.attackby(D,src)
say("[pick("Something to wet your whistle!","Down the hatch, a tasty beverage!","One drink, coming right up!","Tasty liquid for your oral intake!","Enjoy!")]")
custom_emote(2, "[pick("gibbers","drools","slobbers","claps wildly","spits")], serving up a [D]!")
/mob/living/carbon/human/interactive/proc/shitcurity(obj)
var/list/allContents = getAllContents()
if(retal && TARGET && Adjacent(TARGET))
var/obj/item/weapon/restraints/R = locate() in allContents
if(R)
R.attack(TARGET, src) // go go bluespace restraint launcher!
/mob/living/carbon/human/interactive/proc/clowning(obj)
if(shouldModulePass())
return
var/list/allContents = getAllContents()
var/list/rangeCheck = oview(12, src)
var/mob/living/carbon/human/clownTarget
var/list/clownPriority = list()
var/obj/item/weapon/reagent_containers/spray/S
if(!locate(/obj/item/weapon/reagent_containers/spray) in allContents)
new/obj/item/weapon/reagent_containers/spray(src)
else
S = locate(/obj/item/weapon/reagent_containers/spray) in allContents
for(var/mob/living/carbon/human/C in rangeCheck)
var/pranksNearby = 100
for(var/turf/simulated/T in orange(1, C))
for(var/obj/item/A in T)
if(istype(A,/obj/item/weapon/soap) || istype(A,/obj/item/weapon/reagent_containers/food/snacks/grown/banana) || istype(A,/obj/item/weapon/bananapeel))
pranksNearby--
if(T.wet)
pranksNearby -= 10
clownPriority[C] = pranksNearby
for(var/A in clownPriority)
if(!clownTarget)
clownTarget = A
else
if(clownPriority[A] > clownPriority[clownTarget])
clownTarget = clownPriority[A]
if(clownTarget)
if(!Adjacent(clownTarget))
tryWalk(clownTarget)
else
var/hasPranked = 0
for(var/A in allContents)
if(prob(smartness/2) && !hasPranked)
if(istype(A,/obj/item/weapon/soap))
npcDrop(A)
hasPranked = 1
if(istype(A,/obj/item/weapon/reagent_containers/food/snacks/grown/banana))
var/obj/item/weapon/reagent_containers/food/snacks/B = A
B.attack(src, src)
if(istype(A,/obj/item/weapon/bananapeel))
npcDrop(A)
hasPranked = 1
if(!hasPranked)
if(S.reagents.total_volume <= 5)
S.reagents.add_reagent("water", 25)
S.afterattack(get_turf(pick(orange(1, clownTarget))),src)
/mob/living/carbon/human/interactive/proc/healpeople(obj)
var/shouldTryHeal = 0
var/obj/item/stack/medical/M
var/list/allContents = getAllContents()
for(var/A in allContents)
if(istype(A,/obj/item/stack/medical))
shouldTryHeal = 1
M = A
var/obj/item/weapon/reagent_containers/hypospray/HPS
if(!locate(/obj/item/weapon/reagent_containers/hypospray) in allContents)
new/obj/item/weapon/reagent_containers/hypospray(src)
else
HPS = locate(/obj/item/weapon/reagent_containers/hypospray) in allContents
if(!shouldTryHeal)
shouldTryHeal = 2 // we have no stackables to use, lets use our internal hypospray instead
if(shouldTryHeal == 1)
for(var/mob/living/carbon/human/C in nearby)
if(istype(C,/mob/living/carbon/human)) //I haven't the foggiest clue why this is turning up non-carbons but sure here whatever
if(C.health <= 75)
if(get_dist(src,C) <= 2)
src.say("Wait, [C], let me heal you!")
M.attack(C,src)
sleep(25)
else
tryWalk(get_turf(C))
else if(shouldTryHeal == 2)
if(HPS)
if(HPS.reagents.total_volume <= 0)
HPS.reagents.add_reagent("omnizine",30)
for(var/mob/living/carbon/human/C in nearby)
if(istype(C,/mob/living/carbon/human))
if(C.health <= 75 && C.reagents.get_reagent_amount("omnizine") <= 0) // make sure they wont be overdosing
if(get_dist(src,C) <= 2)
src.say("Wait, [C], let me heal you!")
HPS.attack(C,src)
sleep(25)
else
tryWalk(get_turf(C))
/mob/living/carbon/human/interactive/proc/dojanitor(obj)
if(shouldModulePass())
return
var/list/allContents = getAllContents()
//now with bluespace magic!
var/obj/item/weapon/reagent_containers/spray/SP = locate() in allContents
if(!SP)
SP = new /obj/item/weapon/reagent_containers/spray(src)
var/obj/item/weapon/soap/SO = locate() in allContents
if(!SO)
SO = new /obj/item/weapon/soap(src)
if(SP.reagents.total_volume <= 5)
SP.reagents.add_reagent("cleaner", 25) // bluespess water delivery for AI
var/obj/effect/decal/cleanable/TC
TC = locate(/obj/effect/decal/cleanable) in view(6, src)
if(TC)
if(!Adjacent(TC))
tryWalk(TC)
else
var/turf/T = get_turf(TC)
if(T && !T.density)
SP.afterattack(TC, src, 1)
else
SO.afterattack(TC, src, 1)
qdel(TC)
// START COOKING MODULE
/mob/living/carbon/human/interactive/proc/refundrecipe(list/ingredients)
for(var/obj/I in ingredients)
I.loc = contents
// Ingredients may be in the rangeCheck or inside the chef's inventory.
// As they are checked off, they are moved to a seperate list as to not be counted twice
// If the recipe fails (eg somebody else took an ingredient before it was picked up) the ingredients are dumped back to inventory
// That way, the chef has a better chance of making a recipe next ptick, if he has more stuff on him
/mob/living/carbon/human/interactive/proc/makerecipe(datum/recipe/R, allContents, rangeCheck)
set background = 1
var/list/ingredients[0]
for(var/P in R.items)
var/obj/item/I = locate(P) in allContents
if(I)
ingredients += I
I.forceMove(null)
continue
I = locate(P) in rangeCheck
TARGET = I
if(I && !Adjacent(I))
tryWalk(get_turf(I))
sleep(get_dist(src, I))
if(!I || !(I in rangeCheck))
refundrecipe(ingredients)
return 0
custom_emote(1, "[pick("gibbers","drools","slobbers","claps wildly","spits")], picking up [I].")
ingredients += I
I.forceMove(null)
for(var/P in R.fruit)
for(var/i = 1 to R.fruit[P])
var/obj/item/I = locate(P) in allContents
if(I)
ingredients += I
I.forceMove(null)
continue
I = locate(P) in rangeCheck
TARGET = I
if(I && !Adjacent(I))
tryWalk(get_turf(I))
sleep(get_dist(src, I))
if(!I || !(I in rangeCheck))
refundrecipe(ingredients)
return 0
custom_emote(1, "[pick("gibbers","drools","slobbers","claps wildly","spits")], picking up [I].")
ingredients += I
I.forceMove(null)
// cheaply cook the ingredients into result
sleep(R.time)
for(var/obj/I in ingredients)
qdel(I)
ingredients.Cut()
var/result = new R.result(get_turf(src))
custom_emote(1, "[pick("gibbers","drools","slobbers","claps wildly","spits")] and, with a bang, \a [result] is cooked!")
return 1
/mob/living/carbon/human/interactive/proc/souschef(obj)
if(shouldModulePass() || enforceHome() || prob(SNPC_FUZZY_CHANCE_LOW))
return
if(doing & SNPC_SPECIAL)
return
doing |= SNPC_SPECIAL
var/static/list/customableTypes = list(/obj/item/weapon/reagent_containers/food/snacks/customizable,/obj/item/weapon/reagent_containers/food/snacks/breadslice,/obj/item/weapon/reagent_containers/food/snacks/bun,/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough,/obj/item/weapon/reagent_containers/food/snacks/boiledspagetti,/obj/item/trash/plate,/obj/item/trash/bowl)
var/static/list/rawtypes = list(/obj/item/weapon/reagent_containers/food/snacks/grown, /obj/item/weapon/reagent_containers/food/snacks/rawcutlet, /obj/item/weapon/reagent_containers/food/snacks/rawmeatball, /obj/item/weapon/reagent_containers/food/snacks/rawsticks, /obj/item/weapon/reagent_containers/food/snacks/salmonmeat, /obj/item/weapon/reagent_containers/food/snacks/carpmeat, /obj/item/weapon/reagent_containers/food/snacks/catfishmeat, /obj/item/weapon/reagent_containers/food/snacks/spagetti, /obj/item/weapon/reagent_containers/food/snacks/dough_ball, /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, /obj/item/weapon/reagent_containers/food/snacks/doughslice, /obj/item/weapon/reagent_containers/food/snacks/meat, /obj/item/weapon/reagent_containers/food/snacks/boiledrice, /obj/item/weapon/reagent_containers/food/snacks/cheesewedge)
try
var/list/allContents = getAllContents()
var/list/rangeCheck = view(6, src)
//Bluespace in some inbuilt tools
var/obj/item/weapon/kitchen/rollingpin/RP = locate() in allContents
if(!RP)
RP = new /obj/item/weapon/kitchen/rollingpin(src)
var/obj/item/weapon/kitchen/knife/KK = locate() in allContents
if(!KK)
KK = new /obj/item/weapon/kitchen/knife(src)
var/global/list/available_recipes
if(!available_recipes)
available_recipes = list()
for (var/type in subtypesof(/datum/recipe))
var/datum/recipe/recipe = new type
if(recipe.result) // Ignore recipe subtypes that lack a result
available_recipes += recipe
else
qdel(recipe)
var/foundCookable = 0
//Make some basic custom food
var/foundCustom
for(var/customType in customableTypes)
var/A = locate(customType) in rangeCheck
var/obj/item/weapon/reagent_containers/food/snacks/customizable/I = A
if(A && (!istype(I) || I.ingredients.len <= 3))
foundCustom = A // this will eventually wittle down to 0
break
var/obj/machinery/smartfridge/SF = locate(/obj/machinery/smartfridge) in rangeCheck
if(SF)
if(SF.contents.len > 0)
if(!Adjacent(SF))
tryWalk(get_turf(SF))
else
custom_emote(2, "[pick("gibbers","drools","slobbers","claps wildly","spits")], grabbing various foodstuffs from [SF] and sticking them in it's mouth!")
for(var/obj/item/A in SF.contents)
if(prob(smartness/2))
var/count = SF.item_quants[A.name]
if(count)
SF.item_quants[A.name] = count - 1
A.forceMove(src)
if(foundCustom)
var/obj/item/weapon/reagent_containers/food/snacks/FC = foundCustom
for(var/obj/item/weapon/reagent_containers/food/snacks/toMake in allContents)
if(prob(smartness))
if(FC.reagents)
if(toMake.reagents)
var/raw = 0
for(var/type in rawtypes)
if(istype(toMake, type))
raw = 1
break
if(!raw)
npcDrop(toMake)
FC.attackby(toMake, src)
allContents -= toMake
break
else
qdel(FC) // this food is usless, toss it
// Process tool-based ingredients
var/obj/item/weapon/reagent_containers/food/snacks/dough/D = locate() in rangeCheck
var/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough/FD = locate() in rangeCheck
var/obj/item/weapon/reagent_containers/food/snacks/rawcutlet/RC = locate() in rangeCheck
var/obj/item/weapon/reagent_containers/food/snacks/sliceable/cheesewheel/CW = locate() in rangeCheck
var/obj/item/weapon/reagent_containers/food/snacks/grown/potato/PO = locate() in rangeCheck
var/obj/item/weapon/reagent_containers/food/snacks/meat/ME = locate() in rangeCheck
if(D)
TARGET = D
if(prob(50))
tryWalk(get_turf(D))
sleep(get_dist(src, D))
D.attackby(RP, src)
foundCookable = 1
else if(FD)
TARGET = FD
if(prob(50))
tryWalk(get_turf(FD))
sleep(get_dist(src, FD))
FD.attackby(KK, src)
foundCookable = 1
else if(RC)
TARGET = RC
if(prob(50))
tryWalk(get_turf(RC))
sleep(get_dist(src, RC))
RC.attackby(KK, src)
foundCookable = 1
else if(CW)
TARGET = CW
if(prob(50))
tryWalk(get_turf(CW))
sleep(get_dist(src, CW))
CW.attackby(KK, src)
foundCookable = 1
else if(PO)
TARGET = PO
if(prob(50))
tryWalk(get_turf(PO))
sleep(get_dist(src, PO))
PO.attackby(KK, src)
foundCookable = 1
else if(ME)
TARGET = ME
if(prob(50))
tryWalk(get_turf(ME))
sleep(get_dist(src, ME))
ME.attackby(KK, src)
foundCookable = 1
// refresh
allContents = getAllContents()
rangeCheck = view(6, src)
// Find most complex recipe we can cook
var/ingredientZone = rangeCheck + allContents
var/highest_count = 0
var/datum/recipe/winner = null
for(var/datum/recipe/R in available_recipes)
if(R.check_items(ingredientZone) >= 0 && R.check_fruit(ingredientZone) >= 0)
var/count = (R.items ? R.items.len : 0) + (R.fruit ? R.fruit.len : 0)
if(count > highest_count)
highest_count = count
winner = R
// make winner
if(winner)
if(makerecipe(winner, allContents, rangeCheck))
foundCookable = 1
// refresh
allContents = getAllContents()
rangeCheck = view(6, src)
// dont display our ingredients
var/list/finishedList = list()
for(var/obj/item/weapon/reagent_containers/food/snacks/toDisplay in allContents)
var/raw = 0
for(var/type in rawtypes)
if(istype(toDisplay, type))
raw = 1
break
if(!raw)
finishedList += toDisplay
for(var/obj/item/weapon/reagent_containers/food/snacks/toGrab in rangeCheck)
if(!(locate(/obj/structure/table/reinforced) in get_turf(toGrab))) //it's not being displayed
var/raw = 0
for(var/type in rawtypes)
if(istype(toGrab, type))
raw = 1
break
if(!raw)
foundCookable = 1
if(!Adjacent(toGrab))
tryWalk(toGrab)
else
finishedList += toGrab
toGrab.forceMove(src)
if(finishedList.len)
var/obj/structure/table/reinforced/RT
for(var/obj/structure/table/reinforced/toCheck in rangeCheck)
var/counted = 0
for(var/obj/item/weapon/reagent_containers/food/snacks/S in get_turf(toCheck))
++counted
if(counted < 12) // make sure theres not too much food here
RT = toCheck
break
if(RT)
foundCookable = 1
if(!Adjacent(RT))
tryWalk(get_turf(RT))
else
for(var/obj/item/weapon/reagent_containers/food/snacks/toPlop in finishedList)
RT.attackby(toPlop,src)
if(!foundCookable)
var/chosenType = pick(rawtypes)
var/obj/item/weapon/reagent_containers/food/snacks/newSnack = new chosenType(get_turf(src))
TARGET = newSnack
newSnack.reagents.remove_any((newSnack.reagents.total_volume/2)-1)
newSnack.name = "Synthetic [newSnack.name]"
custom_emote(2, "[pick("gibbers","drools","slobbers","claps wildly","spits")] as they vomit [newSnack] from their mouth!")
catch(var/exception/e)
log_to_dd("Cooking error: [e] in [e.file]:[e.line]")
doing &= ~SNPC_SPECIAL
// END COOKING MODULE
/mob/living/carbon/human/interactive/proc/combat(obj)
set background = 1
enforce_hands()
if(canmove)
if((graytide || (TRAITS & TRAIT_MEAN)) || retal)
interest += targetInterestShift
a_intent = "harm"
zone_sel.selecting = pick("chest","r_leg","l_leg","r_arm","l_arm","head")
doing |= SNPC_FIGHTING
if(retal)
TARGET = retal_target
else
var/mob/living/M = locate(/mob/living) in oview(7, src)
if(!M)
doing = doing & ~SNPC_FIGHTING
else if(M != src && !compareFaction(M.faction))
TARGET = M
//no infighting
if(retal)
if(retal_target)
if(compareFaction(retal_target.faction))
retal = 0
retal_target = null
TARGET = null
doing = 0
//ensure we're using the best object possible
var/obj/item/weapon/best
var/foundFav = 0
var/list/allContents = getAllContents()
for(var/test in allContents)
for(var/a in favoured_types)
if(ispath(test,a) && !(doing & SNPC_FIGHTING)) // if we're not in combat and we find our favourite things, use them (for people like janitor and doctors)
best = test
foundFav = 1
return
if(!foundFav)
if(istype(test,/obj/item/weapon))
var/obj/item/weapon/R = test
if(R.force > 2) // make sure we don't equip any non-weaponlike items, ie bags and stuff
if(!best)
best = R
else
if(best.force < R.force)
best = R
if(istype(R, /obj/item/weapon/gun))
var/obj/item/weapon/gun/G = R
if(G.can_fire())
best = R
break // gun with ammo? screw the rest
if(best && best != main_hand)
take_to_slot(best,1)
if(TARGET && (doing & SNPC_FIGHTING)) // this is a redundancy check
var/mob/living/M = TARGET
if(istype(M,/mob/living))
if(M.health > 1 && (M in oview(src, 6)))
//THROWING OBJECTS
for(var/A in allContents)
if(istype(A,/obj/item/weapon/gun)) // guns are for shooting, not throwing.
continue
if(prob(robustness))
if(istype(A,/obj/item/weapon))
var/obj/item/weapon/W = A
if(W.throwforce > 19) // Only throw worthwile stuff, no more lobbing wrenches at wenches
npcDrop(W, 1)
throw_item(TARGET)
if(istype(A,/obj/item/weapon/grenade)) // Allahu ackbar! ALLAHU ACKBARR!!
var/obj/item/weapon/grenade/G = A
G.attack_self(src)
if(prob(smartness))
npcDrop(G, 1)
sleep(15)
throw_item(TARGET)
if(!main_hand && other_hand)
swap_hands()
if(main_hand)
if(main_hand.force != 0)
if(istype(main_hand,/obj/item/weapon/gun))
var/obj/item/weapon/gun/G = main_hand
if(G.special_check(src))
var/shouldFire = 1
if(istype(main_hand, /obj/item/weapon/gun/energy))
var/obj/item/weapon/gun/energy/P = main_hand
var/stunning = 0
if(ispath(text2path(P.projectile_type), /obj/item/projectile/energy/electrode))
stunning = 1
var/mob/stunCheck = TARGET
if(stunning && stunCheck.stunned)
shouldFire = 0
if(shouldFire)
if(!G.process_chambered())
G.click_empty(src)
npcDrop(G, 1)
else
G.Fire(TARGET, src)
else
if(get_dist(src,TARGET) > 6)
if(!walk2derpless(TARGET))
timeout++
else
var/obj/item/weapon/W = main_hand
W.attack(TARGET, src)
else
npcDrop(G, 1)
else
if(targetRange(TARGET) > 2)
tryWalk(TARGET)
else
if(Adjacent(TARGET))
a_intent = pick("disarm", "harm")
M.attack_hand(src)
timeout++
else if(timeout >= 10 || !(targetRange(M) > 14))
doing = doing & ~SNPC_FIGHTING
timeout = 0
TARGET = null
retal = 0
retal_target = null
else
timeout++
/mob/living/carbon/human/interactive/proc/past_verb(word)
if(copytext(word, length(word)) == "e")
return "[word]d"
else
return "[word]ed"
/mob/living/carbon/human/interactive/proc/paperwork_sentence()
var/verbs_use = pick_list(speak_file, "verbs_use")
var/verbs_touch = pick_list(speak_file, "verbs_touch")
var/verbs_move = pick_list(speak_file, "verbs_move")
var/nouns_insult = pick_list(speak_file, "nouns_insult")
var/nouns_generic = pick_list(speak_file, "nouns_generic")
var/nouns_objects = pick_list(speak_file, "nouns_objects")
var/nouns_body = pick_list(speak_file, "nouns_body")
var/adjective_insult = pick_list(speak_file, "adjective_insult")
var/adjective_objects = pick_list(speak_file, "adjective_objects")
var/adjective_generic = pick_list(speak_file, "adjective_generic")
var/curse_words = pick_list(speak_file, "curse_words")
var/result = pick("fired", "promoted", "demoted", "terminated", "incinerated", "sent to CentCom", "borged")
return pick(
"The [adjective_insult] [pick(command_positions)] [past_verb(verbs_touch)] the [adjective_objects] [nouns_objects] and was [result].",
"The [adjective_generic] [pick(engineering_positions)] [past_verb(verbs_touch)] the [adjective_insult] [nouns_objects]. [capitalize(curse_words)], the [nouns_insult] was [result].",
"The [adjective_insult] [pick(security_positions)] [past_verb(verbs_touch)] the [adjective_generic] [nouns_insult] [pick(support_positions)]'s [nouns_body] and had to be [result].",
"Medical had to [verbs_use] the [adjective_generic] [pick(command_positions)]'s [adjective_objects] [nouns_body] after the incident.",
"I demand that [nouns_generic] respond with a [adjective_generic] update ASAP.",
"Would like an update from [nouns_generic] regarding the [adjective_insult] [pick(command_positions)] being [result].",
"The [pick(medical_positions)] needs to [verbs_move] faster when the crew's [adjective_objects] [nouns_body]s are injured, or they will be [result].",
"[capitalize(curse_words)] [adjective_insult] [curse_words].",
"<br><br>")
/mob/living/carbon/human/interactive/proc/pickStamp(allContents)
var/list/stamps[0]
for(var/obj/item/weapon/stamp/S in allContents)
stamps += S
if(stamps.len)
return pick(stamps)
// just make one, maybe
if(prob(SNPC_FUZZY_CHANCE_LOW / 2))
for(var/p in favoured_types)
if(ispath(p, /obj/item/weapon/stamp))
return new p(src)
return null
/mob/living/carbon/human/interactive/proc/paperwork(obj)
set background = 1
if(shouldModulePass() || !prob(SNPC_FUZZY_CHANCE_LOW / 4))
return
var/list/allContents = getAllContents()
var/obj/item/weapon/paper/P = locate() in allContents
var/obj/item/weapon/stamp/S = pickStamp(allContents)
var/mob/living/carbon/human/H = locate() in nearby
if(!P)
P = new /obj/item/weapon/paper(src)
if(P && S && H)
if(!P.stamped || !P.stamped.len)
// generate form
P.name = pick("Inspection Report", "Re: Crew", "Complaint", "To: CentCom")
P.info = {"<center><img src=ntlogo.png><br><b><font size="1">[paperwork_sentence()]</font></b></center>"}
for(var/I in 1 to rand(10, 20))
P.info += "[paperwork_sentence()] "
P.info += {"<br><br>[pick("Signed", "Sincerely", "Regards")], <font face="Times New Roman"><i>[real_name]</i></font>"}
P.update_icon()
P.stamp(S)
custom_emote(2, "[pick("gibbers","drools","slobbers","claps wildly","spits")] and throws [P] on the ground!")
npcDrop(P)
P.throw_at(H, P.throw_range, P.throw_speed, src)
/mob/living/carbon/human/interactive/proc/stamping(obj)
if(shouldModulePass())
return
var/list/allContents = getAllContents()
var/list/rangeCheck = view(7, src)
var/obj/item/weapon/stamp/S = pickStamp(allContents)
if(!S)
return
// stamp a paper we're holding, and drop it
var/obj/item/weapon/paper/P = locate() in allContents
if(P)
if(!P.stamped || !P.stamped.len)
P.stamp(S)
custom_emote(2, "[pick("gibbers","drools","slobbers","claps wildly","spits")] as they stamp \the [P] with \a [S]!")
npcDrop(P, 1)
return
// stamp a paper in the world
for(var/obj/item/weapon/paper/A in rangeCheck)
if(!A.stamped || !A.stamped.len)
if(!Adjacent(A))
tryWalk(A)
else
A.stamp(S)
custom_emote(2, "[pick("gibbers","drools","slobbers","claps wildly","spits")] as they stamp \the [A] with \a [S]!")
return
@@ -0,0 +1,955 @@
/*
NPC VAR EXPLANATIONS (for modules and other things)
doing = their current action, SNPC_INTERACTING, SNPC_TRAVEL or SNPC_FIGHTING
interest = how interested the NPC is in the situation, if they are idle, this drops
timeout = this is internal
TARGET = their current target
LAST_TARGET = their last target
nearby = a list of nearby mobs
best_force = the highest force object, used for checking when to swap items
retal = this is internal
retal_target = this is internal
update_hands = this is a bool (1/0) to determine if the NPC should update what is in his hands
MYID = their ID card
MYPDA = their PDA
main_hand = what is in their "main" hand (chosen from left > right)
TRAITS = the traits assigned to this npc
mymjob = the job assigned to the npc
robustness = the chance for the npc to hit something
smartness = the inverse chance for an npc to do stupid things
attitude = the chance for an npc to do rude or mean things
slyness = the chance for an npc to do naughty things ie thieving
functions = the list of procs that the npc will use for modules
graytide = shitmin var to make them go psycho
*/
/mob/living/carbon/human/interactive
name = "interactive station member"
var/doing = 0
var/interest = 10
var/maxInterest = 10
var/timeout = 0
var/inactivity_period = 0
var/atom/TARGET = null
var/atom/LAST_TARGET = null
var/list/nearby = list()
var/best_force = 0
var/retal = 0
var/mob/retal_target = null
var/update_hands = 0
var/list/blacklistItems = list() // items we should be ignoring
var/maxStepsTick = 6 // step as many times as we can per frame
//Job and mind data
var/obj/item/weapon/card/id/MYID
var/obj/item/weapon/card/id/RPID // the "real" idea they use
var/obj/item/device/pda/MYPDA
var/obj/item/main_hand
var/obj/item/other_hand
var/TRAITS = 0
var/obj/item/weapon/card/id/Path_ID
var/default_job = /datum/job/civilian // the type for the default job
var/datum/job/myjob
var/list/myPath = list()
faction = list("synth")
//trait vars
var/robustness = 50
var/smartness = 50
var/attitude = 50
var/slyness = 50
var/graytide = 0
var/list/favoured_types = list() // allow a mob to favour a type, and hold onto them
var/chattyness = SNPC_CHANCE_TALK
var/targetInterestShift = 5 // how much a good action should "reward" the npc
//modules
var/list/functions = list("nearbyscan","combat","shitcurity","chatter")
var/restrictedJob = 0
var/shouldUseDynamicProc = 0 // switch to make the AI control it's own proccessing
var/alternateProcessing = 0
var/forceProcess = 0
var/processTime = 10
var/speak_file = "npc_chatter.json"
var/list/knownStrings = list()
//snpc traitor variables
var/isTraitor = 0
var/traitorTarget
var/traitorScale = 0 // our ability as a traitor
var/traitorType = 0
/// SNPC voice handling
/mob/living/carbon/human/interactive/proc/loadVoice()
var/savefile/S = new /savefile("data/npc_saves/snpc.sav")
S["knownStrings"] >> knownStrings
if(isnull(knownStrings))
knownStrings = list()
/mob/living/carbon/human/interactive/proc/saveVoice()
var/savefile/S = new /savefile("data/npc_saves/snpc.sav")
S["knownStrings"] << knownStrings
//botPool funcs
/mob/living/carbon/human/interactive/proc/takeDelegate(mob/living/carbon/human/interactive/from,doReset=TRUE)
change_eye_color(255, 0, 0)
if(from == src)
return FALSE
TARGET = from.TARGET
LAST_TARGET = from.LAST_TARGET
retal = from.retal
retal_target = from.retal_target
doing = from.doing
//
timeout = 0
inactivity_period = 0
interest = maxInterest
//
update_icons()
if(doReset)
from.TARGET = null
from.LAST_TARGET = null
from.retal = 0
from.retal_target = null
from.doing = 0
return TRUE
//end pool funcs
/mob/living/carbon/human/interactive/proc/random()
//this is here because this has no client/prefs/brain whatever.
age = rand(AGE_MIN, AGE_MAX)
change_gender(pick("male", "female"))
rename_character(real_name, species.get_random_name(gender))
//job handling
myjob = new default_job()
job = myjob.title
mind.assigned_role = job
myjob.equip(src)
myjob.apply_fingerprints(T)
/mob/living/carbon/human/interactive/proc/reset()
walk(src, 0)
timeout = 100
retal = 0
doing = 0
inactivity_period = 0
/client/proc/resetSNPC(mob/living/carbon/human/interactive/T in npc_master.botPool_l)
set name = "Reset SNPC"
set desc = "Reset the SNPC"
set category = "Debug"
if(!holder)
return
if(istype(T))
T.reset()
/client/proc/toggleSNPC(mob/living/carbon/human/interactive/T in npc_master.botPool_l)
set name = "Toggle SNPC Proccessing Mode"
set desc = "Toggle SNPC Proccessing Mode"
set category = "Debug"
if(!holder)
return
if(istype(T))
T.alternateProcessing = !T.alternateProcessing
T.forceProcess = 1
to_chat(usr, "[T]'s processing has been switched to [T.alternateProcessing ? "High Profile" : "Low Profile"]")
/client/proc/customiseSNPC(mob/living/carbon/human/interactive/T in npc_master.botPool_l)
set name = "Customize SNPC"
set desc = "Customise the SNPC"
set category = "Debug"
if(!holder)
return
if(!istype(T))
return
var/list/jobs[0]
for(var/datum/job/j in job_master.occupations)
if(j.title != "AI" && j.title != "Cyborg")
jobs[j.title] = j
jobs = sortAssoc(jobs)
var/job_name = input("Choose Job") as null|anything in jobs
if(job_name)
var/datum/job/cjob = jobs[job_name]
var/alt_title = input("Choose Alt Title") in (list(job_name) + cjob.alt_titles)
T.myjob = cjob
T.job = cjob.title
T.mind.assigned_role = cjob.title
for(var/obj/item/W in T)
qdel(W)
T.myjob.equip(T)
T.myjob.apply_fingerprints(T)
T.doSetup(alt_title)
var/shouldDoppel = input("Do you want the SNPC to disguise themself as a crewmember?") as anything in list("Yes", "No")
if(shouldDoppel == "Yes")
var/list/validchoices = list()
for(var/mob/living/carbon/human/M in mob_list)
validchoices += M
var/mob/living/carbon/human/chosen = input("Which crewmember?") as null|anything in validchoices
if(chosen)
var/datum/dna/toDoppel = chosen.dna
T.real_name = toDoppel.real_name
T.set_species(chosen.species.name)
T.body_accessory = chosen.body_accessory
T.dna = toDoppel.Clone()
T.UpdateAppearance()
domutcheck(T)
var/doTrait = input("Do you want the SNPC to be a traitor?") as anything in list("Yes", "No")
if(doTrait == "Yes")
var/list/tType = list("Brute" = SNPC_BRUTE, "Stealth" = SNPC_STEALTH, "Martyr" = SNPC_MARTYR, "Psycho" = SNPC_PSYCHO)
var/cType = input("Choose the traitor personality.") as null|anything in tType
if(cType)
var/value = tType[cType]
T.makeTraitor(value)
var/doTele = input("Place the SNPC in their department?") as anything in list("Yes", "No")
if(doTele == "Yes")
T.loc = pick(get_area_turfs(T.job2area(T.myjob)))
T.revive()
/mob/living/carbon/human/interactive/proc/doSetup(alt_title = null)
Path_ID = new /obj/item/weapon/card/id(src)
var/datum/job/captain/C = new/datum/job/captain
Path_ID.access = C.get_access()
if(!alt_title)
alt_title = job
MYID = new(src)
MYID.name = "[real_name]'s ID Card ([alt_title])"
MYID.assignment = "[alt_title]"
MYID.rank = job
MYID.sex = capitalize(gender)
MYID.age = age
MYID.registered_name = real_name
MYID.photo = get_id_photo(src)
MYID.access = Path_ID.access // Automatons have strange powers... strange indeed
RPID = new(src)
RPID.name = "[real_name]'s ID Card ([alt_title])"
RPID.assignment = "[alt_title]"
RPID.rank = job
RPID.sex = capitalize(gender)
RPID.age = age
RPID.registered_name = real_name
RPID.photo = get_id_photo(src)
RPID.access = myjob.get_access()
equip_to_slot_or_del(MYID, slot_wear_id)
MYPDA = new(src)
MYPDA.owner = real_name
MYPDA.ownjob = alt_title
MYPDA.ownrank = job
MYPDA.name = "PDA-[real_name] ([alt_title])"
equip_to_slot_or_del(MYPDA, slot_belt)
zone_sel.selecting = "chest"
//arms
if(prob((SNPC_FUZZY_CHANCE_LOW+SNPC_FUZZY_CHANCE_HIGH)/4))
var/obj/item/organ/external/R = organs_by_name["r_arm"]
if(R)
R.robotize()
else
var/obj/item/organ/external/L = organs_by_name["l_arm"]
if(L)
L.robotize()
//legs
if(prob((SNPC_FUZZY_CHANCE_LOW+SNPC_FUZZY_CHANCE_HIGH)/4))
var/obj/item/organ/external/R = organs_by_name["r_leg"]
if(R)
R.robotize()
else
var/obj/item/organ/external/L = organs_by_name["l_leg"]
if(L)
L.robotize()
UpdateDamageIcon()
regenerate_icons()
hand = 0
functions = list("nearbyscan", "combat", "shitcurity", "chatter") // stop customize adding multiple copies of a function
setup_job(job)
if(TRAITS & TRAIT_ROBUST)
robustness = 75
else if(TRAITS & TRAIT_UNROBUST)
robustness = 25
//modifiers are prob chances, lower = smarter
if(TRAITS & TRAIT_SMART)
smartness = 75
else if(TRAITS & TRAIT_DUMB)
disabilities |= CLUMSY
smartness = 25
if(TRAITS & TRAIT_MEAN)
attitude = 75
else if(TRAITS & TRAIT_FRIENDLY)
attitude = 1
if(TRAITS & TRAIT_THIEVING)
slyness = 75
/mob/living/carbon/human/interactive/proc/setup_job(thejob)
switch(thejob)
if("Civilian")
favoured_types = list(/obj/item/clothing, /obj/item/weapon)
if("Captain", "Head of Personnel")
favoured_types = list(/obj/item/clothing, /obj/item/weapon/stamp/captain,/obj/item/weapon/disk/nuclear)
if("Nanotrasen Representative")
favoured_types = list(/obj/item/clothing, /obj/item/weapon/stamp/centcom, /obj/item/weapon/paper, /obj/item/weapon/melee/baton/loaded/ntcane)
functions += "paperwork"
if("Magistrate", "Internal Affairs Agent")
favoured_types = list(/obj/item/clothing, /obj/item/weapon/stamp/law, /obj/item/weapon/paper)
functions += "paperwork"
if("Quartermaster", "Cargo Technician")
favoured_types = list(/obj/item/clothing, /obj/item/weapon/stamp/granted, /obj/item/weapon/stamp/denied, /obj/item/weapon/paper, /obj/item/weapon/clipboard)
functions += "stamping"
if("Chef")
favoured_types = list(/obj/item/weapon/reagent_containers/food, /obj/item/weapon/kitchen)
functions += "souschef"
restrictedJob = 1
if("Bartender")
favoured_types = list(/obj/item/weapon/reagent_containers/food, /obj/item/weapon/kitchen)
functions += "bartend"
restrictedJob = 1
if("Station Engineer", "Chief Engineer", "Life Support Specialist", "Mechanic")
favoured_types = list(/obj/item/stack, /obj/item/weapon, /obj/item/clothing)
if("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Geneticist", "Psychiatrist", "Paramedic", "Brig Physician")
favoured_types = list(/obj/item/weapon/reagent_containers/glass/beaker, /obj/item/weapon/storage/firstaid, /obj/item/stack/medical, /obj/item/weapon/reagent_containers/syringe)
functions += "healpeople"
if("Research Director", "Scientist", "Roboticist")
favoured_types = list(/obj/item/weapon/reagent_containers/glass/beaker, /obj/item/stack, /obj/item/weapon/reagent_containers)
if("Head of Security", "Warden", "Security Officer", "Detective", "Security Pod Pilot", "Blueshield")
favoured_types = list(/obj/item/clothing, /obj/item/weapon, /obj/item/weapon/restraints)
if("Janitor")
favoured_types = list(/obj/item/weapon/mop, /obj/item/weapon/reagent_containers/glass/bucket, /obj/item/weapon/reagent_containers/spray/cleaner, /obj/effect/decal/cleanable)
functions += "dojanitor"
if("Clown")
favoured_types = list(/obj/item/weapon/soap, /obj/item/weapon/reagent_containers/food/snacks/grown/banana, /obj/item/weapon/bananapeel)
functions += "clowning"
if("Botanist")
favoured_types = list(/obj/machinery/portable_atmospherics/hydroponics, /obj/item/weapon/reagent_containers, /obj/item/weapon)
functions += "botany"
restrictedJob = 1
else
favoured_types = list(/obj/item/clothing)
/mob/living/carbon/human/interactive/proc/makeTraitor(var/inPers)
isTraitor = 1
traitorScale = (slyness + smartness) + rand(-10,10)
traitorType = inPers
ticker.mode.traitors += mind
mind.special_role = "traitor"
var/datum/mindslaves/slaved = new()
slaved.masters += mind
mind.som = slaved
ticker.mode.update_traitor_icons_added(mind)
switch(traitorType)
if(SNPC_BRUTE) // SMASH KILL RAAARGH
var/datum/objective/assassinate/A = new
A.owner = mind
A.find_target()
mind.objectives += A
traitorTarget = A.target.current
if(SNPC_STEALTH) // Shhh we is sneekies
var/datum/objective/steal/S = new
S.owner = mind
S.find_target()
mind.objectives += S
traitorTarget = locate(S.steal_target.typepath) in world
if(SNPC_MARTYR) // MY LIFE FOR SPESZUL
var/targetType = pick(/obj/structure/particle_accelerator, /obj/machinery/gravity_generator/main, /obj/machinery/power/smes)
traitorTarget = locate(targetType) in world
var/datum/objective/O = new("Destroy \the [traitorTarget].")
O.owner = mind
mind.objectives += O
if(SNPC_PSYCHO) // YOU'RE LIKE A FLESH BICYLE AND I WANT TO DISMANTLE YOU
var/datum/objective/hijack/H = new
H.owner = mind
mind.objectives += H
traitorTarget = null
functions += "traitor"
faction -= "neutral"
faction += "hostile"
/mob/living/carbon/human/interactive/New()
..()
snpc_list += src
// setup screen HUD because the game expects user mobs to have zone_sel etc
if(hud_used)
qdel(hud_used) //remove the hud objects
hud_used = null
hud_used = new /datum/hud(src)
hud_used.human_hud()
sync_mind()
random()
doSetup()
npc_master.insertBot(src)
loadVoice()
hear_radio_list += src
// a little bit of variation to make individuals more unique
robustness += rand(-10, 10)
smartness += rand(-10, 10)
attitude += rand(-10, 10)
slyness += rand(-10, 10)
doProcess()
/mob/living/carbon/human/interactive/Destroy()
hear_radio_list -= src
snpc_list -= src
return ..()
/mob/living/carbon/human/interactive/proc/retalTarget(mob/living/target)
var/mob/living/M = target
if(istype(M))
if(health > 0)
if(M.a_intent == "help" && !incapacitated())
chatter()
if(istype(target, /mob/living/carbon) && !retal && prob(SNPC_FUZZY_CHANCE_LOW))
var/mob/living/carbon/C = target
if(!Adjacent(target))
tryWalk(target)
else
C.help_shake_act(src)
if(M.a_intent == "harm")
retal = 1
retal_target = target
//Retaliation clauses
/mob/living/carbon/human/interactive/attacked_by(obj/item/I, mob/living/user, def_zone)
..()
retalTarget(user)
/mob/living/carbon/human/interactive/hitby(atom/movable/AM)
..()
var/mob/living/carbon/C = locate(/mob/living/carbon) in view(SNPC_MIN_RANGE_FIND, src)
if(C)
retalTarget(C)
/mob/living/carbon/human/interactive/bullet_act(obj/item/projectile/P)
..()
retalTarget(P.firer)
/mob/living/carbon/human/interactive/attack_hand(mob/living/carbon/human/M)
..()
retalTarget(M)
/mob/living/carbon/human/interactive/show_inv(mob/user)
..()
retalTarget(user)
/mob/living/carbon/human/interactive/can_inject(mob/user, error_msg, target_zone, var/penetrate_thick = 0)
..()
retalTarget(user)
//THESE EXIST FOR DEBUGGING OF THE DOING/INTEREST SYSTEM EASILY
/mob/living/carbon/human/interactive/proc/doing2string(doin)
var/toReturn = ""
if(!doin)
toReturn = "not doing anything"
if(doin & SNPC_INTERACTING)
toReturn += "interacting with something, "
if(doin & SNPC_FIGHTING)
toReturn += "engaging in combat, "
if(doin & SNPC_TRAVEL)
toReturn += "and going somewhere"
return toReturn
/mob/living/carbon/human/interactive/proc/interest2string(inter)
var/toReturn = "Flatlined"
if(inter >= 0 && inter <= 25)
toReturn = "Very Bored"
if(inter >= 26 && inter <= 50)
toReturn = "Bored"
if(inter >= 51 && inter <= 75)
toReturn = "Content"
if(inter >= 76)
toReturn = "Excited"
return toReturn
//END DEBUG
/mob/living/carbon/human/interactive/proc/IsDeadOrIncap(checkDead = TRUE)
if(!canmove)
return 1
if(health <= 0 && checkDead)
return 1
if(restrained())
return 1
if(paralysis)
return 1
if(stunned)
return 1
if(stat)
return 1
if(inactivity_period > 0)
return 1
return 0
/mob/living/carbon/human/interactive/proc/enforce_hands()
if(main_hand)
if(main_hand.loc != src)
main_hand = null
if(other_hand)
if(other_hand.loc != src)
other_hand = null
if(hand)
if(!l_hand)
main_hand = null
if(r_hand)
swap_hands()
else
if(!r_hand)
main_hand = null
if(l_hand)
swap_hands()
/mob/living/carbon/human/interactive/proc/swap_hands()
hand = !hand
var/obj/item/T = other_hand
main_hand = other_hand
other_hand = T
update_hands = 1
/mob/living/carbon/human/interactive/proc/take_to_slot(obj/item/G, var/hands=0)
var/list/slots = list("left pocket" = slot_l_store, "right pocket" = slot_r_store, "left hand" = slot_l_hand, "right hand" = slot_r_hand)
if(hands)
slots = list("left hand" = slot_l_hand, "right hand" = slot_r_hand)
G.loc = src
if(G.force && G.force > best_force)
best_force = G.force
equip_in_one_of_slots(G, slots)
update_hands = 1
/mob/living/carbon/human/interactive/proc/insert_into_backpack()
var/list/slots = list(slot_l_store, slot_r_store, slot_l_hand, slot_r_hand)
var/obj/item/I = get_item_by_slot(pick(slots))
var/obj/item/weapon/storage/BP = get_item_by_slot(slot_back)
if(back && BP && I)
if(BP.can_be_inserted(I))
BP.handle_item_insertion(I)
else
unEquip(I,TRUE)
update_hands = 1
/mob/living/carbon/human/interactive/proc/targetRange(towhere)
return get_dist(get_turf(towhere), get_turf(src))
/mob/living/carbon/human/interactive/death()
saveVoice()
..()
/mob/living/carbon/human/interactive/hear_say(message, verb = "says", datum/language/language = null, alt_name = "", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol)
if(!istype(speaker, /mob/living/carbon/human/interactive))
knownStrings |= html_decode(message)
..()
/mob/living/carbon/human/interactive/hear_radio(message, verb = "says", datum/language/language=null, part_a, part_b, mob/speaker = null, hard_to_hear = 0, vname = "", atom/follow_target)
if(!istype(speaker, /mob/living/carbon/human/interactive))
knownStrings |= html_decode(message)
..()
/mob/living/carbon/human/interactive/proc/doProcess()
forceProcess = 0
if(shouldUseDynamicProc)
var/isSeen = 0
for(var/mob/living/carbon/human/A in orange(12, src))
if(A.client)
isSeen = 1
alternateProcessing = isSeen
if(alternateProcessing)
forceProcess = 1
if(IsDeadOrIncap())
reset()
return
//---------------------------
//---- interest flow control
if(interest < 0 || inactivity_period < 0)
if(interest < 0)
interest = 0
if(inactivity_period < 0)
inactivity_period = 0
if(interest > maxInterest)
interest = maxInterest
//---------------------------
//VIEW FUNCTIONS
//doorscan is now integrated into life and runs before all other procs
var/mistake_chance = (100 - smartness) / 2
spawn(0)
for(var/dir in alldirs)
var/turf/T = get_step(src, dir)
if(T)
for(var/obj/machinery/door/D in T.contents)
if(!istype(D,/obj/machinery/door/poddoor) && D.density)
spawn(0)
if(istype(D,/obj/machinery/door/airlock))
var/obj/machinery/door/airlock/AL = D
if(!AL.CanAStarPass(RPID)) // only crack open doors we can't get through
AL.p_open = 1
AL.update_icon()
AL.shock(src, mistake_chance)
sleep(5)
AL.unlock()
if(prob(mistake_chance))
if(!AL.wires.IsIndexCut(AIRLOCK_WIRE_DOOR_BOLTS))
AL.wires.CutWireIndex(AIRLOCK_WIRE_DOOR_BOLTS)
else
if(AL.wires.IsIndexCut(AIRLOCK_WIRE_DOOR_BOLTS))
AL.wires.CutWireIndex(AIRLOCK_WIRE_DOOR_BOLTS, 1)
if(AL.locked)
AL.wires.UpdatePulsed(AIRLOCK_WIRE_DOOR_BOLTS)
if(!AL.wires.IsIndexCut(AIRLOCK_WIRE_MAIN_POWER1))
AL.wires.CutWireIndex(AIRLOCK_WIRE_MAIN_POWER1)
if(!AL.wires.IsIndexCut(AIRLOCK_WIRE_MAIN_POWER2))
AL.wires.CutWireIndex(AIRLOCK_WIRE_MAIN_POWER2)
if(prob(mistake_chance) && !AL.wires.IsIndexCut(AIRLOCK_WIRE_SAFETY))
AL.wires.CutWireIndex(AIRLOCK_WIRE_SAFETY)
if(prob(mistake_chance) && !AL.wires.IsIndexCut(AIRLOCK_WIRE_ELECTRIFY))
AL.wires.CutWireIndex(AIRLOCK_WIRE_ELECTRIFY)
sleep(5)
AL.p_open = 0
AL.update_icon()
D.open()
if(update_hands)
if(l_hand || r_hand)
if(l_hand)
hand = 1
main_hand = l_hand
if(r_hand)
other_hand = r_hand
else if(r_hand)
hand = 0
main_hand = r_hand
if(l_hand) //this technically shouldnt occur, but its a redundancy
other_hand = l_hand
update_icons()
update_hands = 0
if(grabbed_by.len > 0)
for(var/obj/item/weapon/grab/G in grabbed_by)
if(Adjacent(G))
a_intent = "disarm"
G.assailant.attack_hand(src)
inactivity_period = 10
if(buckled)
resist()
inactivity_period = 10
//proc functions
for(var/Proc in functions)
if(!IsDeadOrIncap())
callfunction(Proc)
//target interaction stays hardcoded
if(TARGET && (TARGET in blacklistItems)) // don't use blacklisted items
TARGET = null
if(TARGET && Adjacent(TARGET))
doing |= SNPC_INTERACTING
//--------DOORS
if(istype(TARGET, /obj/machinery/door))
var/obj/machinery/door/D = TARGET
if(D.check_access(MYID) && !istype(D,/obj/machinery/door/poddoor))
D.open()
sleep(15)
var/turf/T = get_step(get_step(D.loc, dir), dir) //recursion yo
tryWalk(T)
//THIEVING SKILLS
insert_into_backpack() // dump random item into backpack to make space
//---------ITEMS
if(istype(TARGET, /obj/item))
var/obj/item/I = TARGET
if(I.anchored)
TARGET = null
else if(istype(TARGET, /obj/item/weapon))
var/obj/item/weapon/W = TARGET
if(W.force >= best_force || prob((SNPC_FUZZY_CHANCE_LOW + SNPC_FUZZY_CHANCE_HIGH) / 2) || favouredObjIn(list(W)))
if(!l_hand || !r_hand)
put_in_hands(W)
else
insert_into_backpack()
else
if(!l_hand || !r_hand)
put_in_hands(TARGET)
else
insert_into_backpack()
//---------FASHION
if(istype(TARGET, /obj/item/clothing))
var/obj/item/clothing/C = TARGET
drop_item()
spawn(5)
take_to_slot(C,1)
if(!equip_to_appropriate_slot(C))
var/obj/item/I = get_item_by_slot(C)
unEquip(I)
spawn(5)
equip_to_appropriate_slot(C)
update_hands = 1
if(MYPDA in loc)
equip_to_appropriate_slot(MYPDA)
if(MYID in loc)
equip_to_appropriate_slot(MYID)
//THIEVING SKILLS END
//-------------TOUCH ME
if(istype(TARGET, /obj/structure))
var/obj/structure/STR = TARGET
if(main_hand)
var/obj/item/weapon/W = main_hand
STR.attackby(W, src)
else
STR.attack_hand(src)
interest += targetInterestShift
doing = doing & ~SNPC_INTERACTING
timeout = 0
TARGET = null
else
if(TARGET)
tryWalk(TARGET)
timeout++
if(doing == 0)
interest--
else
interest++
if(inactivity_period > 0)
inactivity_period--
if(interest <= 0 || timeout >= 10) // facilitate boredom functions
TARGET = null
doing = 0
timeout = 0
myPath = list()
//this is boring, lets move
if(!doing && !IsDeadOrIncap() && !TARGET)
doing |= SNPC_TRAVEL
if(!isTraitor || !traitorTarget || get_dist(src, traitorTarget) >= SNPC_MAX_RANGE_FIND || get_dist(src, traitorTarget) <= 1)
var/choice = rand(1,50)
switch(choice)
if(1 to 30)
//chance to chase an item
TARGET = locate(/obj/item) in favouredObjIn(oview(SNPC_MIN_RANGE_FIND, src))
if(31 to 40)
TARGET = safepick(get_area_turfs(job2area(myjob)))
if(41 to 45)
TARGET = pick(target_filter(favouredObjIn(urange(SNPC_MAX_RANGE_FIND, src, 1))))
if(46 to 50)
TARGET = pick(target_filter(oview(SNPC_MIN_RANGE_FIND, src)))
else
TARGET = traitorTarget
tryWalk(TARGET)
LAST_TARGET = TARGET
if(alternateProcessing)
spawn(processTime)
doProcess()
/mob/living/carbon/human/interactive/proc/favouredObjIn(list/inList)
var/list/outList = list()
for(var/i in inList)
for(var/path in favoured_types)
if(istype(i, path))
outList += i
if(!outList.len)
outList = inList
return outList
/mob/living/carbon/human/interactive/proc/callfunction(Proc)
spawn(0)
call(src, Proc)(src)
/mob/living/carbon/human/interactive/proc/tryWalk(turf/inTarget)
if(restrictedJob) // we're a job that has to stay in our home
if(!(get_turf(inTarget) in get_area_turfs(job2area(myjob))))
TARGET = null
return
if(!IsDeadOrIncap())
if(!walk2derpless(inTarget))
timeout++
else
timeout++
/mob/living/carbon/human/interactive/proc/getGoodPath(target, maxtries=512)
set background = 1
var/turf/end = get_turf(target)
var/turf/current = get_turf(src)
var/list/path = list()
var/tries = 0
while(current != end && tries < maxtries)
var/turf/shortest = current
for(var/turf/T in view(current,1))
var/foundDense = 0
for(var/atom/A in T)
if(A.density)
foundDense = 1
if(T.density == 0 && !foundDense)
if(get_dist(T, target) < get_dist(shortest,target))
shortest = T
else
tries++
else
tries++
current = shortest
path += shortest
return path
/mob/living/carbon/human/interactive/proc/walk2derpless(target)
set background = 1
if(!target)
return 0
if(myPath.len <= 0)
myPath = get_path_to(src, get_turf(target), /turf/proc/Distance, SNPC_MAX_RANGE_FIND + 1, 250,1, id=Path_ID)
if(myPath)
if(myPath.len > 0)
doing = doing & ~SNPC_TRAVEL
for(var/i = 0; i < maxStepsTick; ++i)
if(!IsDeadOrIncap())
if(myPath.len >= 1)
walk_to(src,myPath[1],0,5)
myPath -= myPath[1]
return 1
return 0
/mob/living/carbon/human/interactive/proc/job2area(target)
var/datum/job/T = target
switch(T.title)
if("Civilian", "Paramedic")
return /area/hallway/primary
if("Captain", "Head of Personnel", "Blueshield")
return /area/bridge
if("Bartender")
return /area/crew_quarters/bar
if("Chef")
return /area/crew_quarters/kitchen
if("Station Engineer", "Chief Engineer", "Mechanic")
return /area/engine
if("Life Support Specialist")
return /area/atmos
if("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Psychiatrist")
return /area/medical
if("Geneticist")
return /area/medical/genetics
if("Research Director", "Scientist")
return /area/toxins
if("Roboticist")
return /area/assembly/robotics
if("Head of Security", "Warden", "Security Officer", "Detective", "Security Pod Pilot", "Brig Physician", "Magistrate", "Internal Affairs Agent")
return /area/security
if("Botanist")
return /area/hydroponics
else
return pick(/area/hallway, /area/crew_quarters)
/mob/living/carbon/human/interactive/proc/target_filter(target)
var/list/filtered_targets = list(/area, /turf, /obj/machinery/door, /atom/movable/lighting_overlay, /obj/structure/cable, /obj/machinery/atmospherics, /obj/item/device/radio/intercom)
var/list/L = target
for(var/atom/A in target) // added a bunch of "junk" that clogs up the general find procs
if(is_type_in_list(A,filtered_targets))
L -= A
return L
/mob/living/carbon/human/interactive/proc/shouldModulePass() // returns 1 if the npc is in anything "primary"
if(doing & (SNPC_FIGHTING | SNPC_SPECIAL))
return 1
if(retal)
return 1
return 0
/mob/living/carbon/human/interactive/proc/getAllContents()
var/list/allContents = list()
for(var/atom/A in contents)
allContents += A
if(A.contents.len)
for(var/atom/B in A)
allContents += B
return allContents
/mob/living/carbon/human/interactive/proc/enforceHome()
var/list/validHome = get_area_turfs(job2area(myjob))
if(TARGET)
var/atom/tcheck = TARGET
if(tcheck)
if(!(get_turf(tcheck) in validHome))
TARGET = null
return 1
if(!(get_turf(src) in validHome))
tryWalk(pick(get_area_turfs(job2area(myjob))))
return 1
return 0
/mob/living/carbon/human/interactive/proc/npcDrop(obj/item/A, blacklist = 0)
if(blacklist)
blacklistItems += A
unEquip(A)
enforce_hands()
update_icons()
/mob/living/carbon/human/interactive/proc/compareFaction(list/targetFactions)
var/hasSame = 0
for(var/A in targetFactions)
if(A in faction)
hasSame = 1
return hasSame
/mob/living/carbon/human/interactive/proc/nearbyscan(obj)
nearby = list()
for(var/mob/living/M in view(4,src))
if(M != src)
nearby += M
/mob/living/carbon/human/interactive/rename_character(oldname, newname)
if(!..())
return 0
if(oldname)
if(MYID)
MYID.registered_name = newname
MYID.name = "[newname]'s ID Card ([MYID.assignment])"
if(RPID)
RPID.registered_name = MYID.registered_name
RPID.name = MYID.name
return 1
@@ -0,0 +1,24 @@
/mob/living/carbon/human/interactive/angry/New()
TRAITS |= TRAIT_ROBUST
TRAITS |= TRAIT_MEAN
faction += "bot_angry"
..()
/mob/living/carbon/human/interactive/friendly/New()
TRAITS |= TRAIT_FRIENDLY
TRAITS |= TRAIT_UNROBUST
faction += "bot_friendly"
faction += "neutral"
functions -= "combat"
..()
/mob/living/carbon/human/interactive/greytide/New()
TRAITS |= TRAIT_ROBUST
TRAITS |= TRAIT_MEAN
TRAITS |= TRAIT_THIEVING
TRAITS |= TRAIT_DUMB
maxInterest = 5 // really short attention span
targetInterestShift = 2 // likewise
faction += "bot_grey"
graytide = 1
..()
@@ -0,0 +1,107 @@
/mob/living/carbon/human/interactive/tigercat/New(loc)
..(loc, "Tajaran")
rename_character(name, "Tigercat")
/mob/living/carbon/human/interactive/tigercat/random()
..()
gender = "male"
/mob/living/carbon/human/interactive/tigercat/Life()
reagents.add_reagent("sodiumchloride", 1)
..()
/mob/living/carbon/human/interactive/tigercat/chatter()
if(prob(50))
if(prob(25))
say("GOD FUCKING DAMNIT FUCKING FUCKING GODDAMN CODE FUCK")
else
..()
/mob/living/carbon/human/interactive/fethas/New(loc)
..(loc, "Unathi")
rename_character(name, "Fethas")
/mob/living/carbon/human/interactive/fethas/random()
..()
gender = "female"
/mob/living/carbon/human/interactive/fethas/setup_job()
..()
favoured_types += /obj/structure/closet/cardboard
functions += "hidebox"
/mob/living/carbon/human/interactive/fethas/proc/hidebox()
if(shouldModulePass() || !prob(SNPC_FUZZY_CHANCE_LOW))
return
var/list/rangeCheck = view(7, src)
var/obj/structure/closet/C = locate() in rangeCheck
if(!C)
C = new /obj/structure/closet/cardboard(get_turf(loc))
custom_emote(1, "[pick("gibbers","drools","slobbers","claps wildly","spits")], and folds together \a [C]!")
if(C)
if(!Adjacent(C))
tryWalk(C)
else
if(!C.opened)
C.open()
if(!C.opened)
return
forceMove(get_turf(C))
custom_emote(1, "goes inside \the [C].")
C.close()
/mob/living/carbon/human/interactive/fethas/chatter()
if(prob(50))
if(prob(50))
custom_emote(2, "[pick("mutters", "gibbers")] something about a box.")
else
..()
/mob/living/carbon/human/interactive/angry/crazylemon/New(loc)
..(loc, "Machine")
rename_character(name, "Crazylemon")
status_flags &= ~CANSTUN
var/obj/item/weapon/twohanded/mjollnir/M = new(src)
M.force = 0
M.force_unwielded = 0
M.force_wielded = 0
M.flags |= NODROP
take_to_slot(M, 1)
M.wield(src)
/mob/living/carbon/human/interactive/angry/crazylemon/random()
..()
gender = "male"
/mob/living/carbon/human/interactive/fox
default_job = /datum/job/scientist
/mob/living/carbon/human/interactive/fox/New(loc)
..(loc, "Vulpkanin")
rename_character(name, "Fox P McCloud")
/mob/living/carbon/human/interactive/fox/doSetup()
..("Chemical Researcher")
/mob/living/carbon/human/interactive/fox/random()
..()
gender = "male"
r_eyes = 0
g_eyes = 255
b_eyes = 0
r_facial = 255
g_facial = 255
b_facial = 255
r_skin = 150
g_skin = 35
b_skin = 1
h_style = "None"
f_style = "Vulpine and Earfluff"
ha_style = "Vulpine and Earfluff"
body_accessory = body_accessory_by_name["Vulpkanin Alt 2 (Straight)"]
take_to_slot(new /obj/item/weapon/reagent_containers/food/snacks/grown/orange, 1)
@@ -87,6 +87,7 @@
/mob/living/simple_animal/parrot/New()
..()
hear_radio_list += src
if(!ears)
var/headset = pick(/obj/item/device/radio/headset/headset_sec, \
/obj/item/device/radio/headset/headset_eng, \
@@ -102,6 +103,9 @@
/mob/living/simple_animal/parrot/verb/drop_held_item_player, \
/mob/living/simple_animal/parrot/proc/perch_player)
/mob/living/simple_animal/parrot/Destroy()
hear_radio_list -= src
return ..()
/mob/living/simple_animal/parrot/death()
if(held_item)
@@ -697,14 +701,14 @@
/mob/living/simple_animal/parrot/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "",var/italics = 0, var/mob/speaker = null)
if(prob(50))
parrot_hear(message)
..(message,verb,language,alt_name,italics,speaker)
parrot_hear(html_decode(message))
..()
/mob/living/simple_animal/parrot/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/mob/speaker = null, var/hard_to_hear = 0, var/atom/follow_target)
if(prob(50))
parrot_hear("[pick(available_channels)][message]")
parrot_hear("[pick(available_channels)][html_decode(message)]")
..()