| Target(s) | | Location |
"
for(var/datum/mind/N in ticker.mode.get_living_heads())
var/mob/M = N.current
if(M)
- dat += "| [M.real_name][M.client ? "" : " (logged out)"][M.stat == 2 ? " (DEAD)" : ""] | "
- dat += "PM | "
+ dat += check_antagonists_line(M)
var/turf/mob_loc = get_turf(M)
dat += "[mob_loc.loc] |
"
else
@@ -464,8 +470,7 @@
for(var/datum/mind/N in ticker.mode.get_extra_living_heads())
var/mob/M = N.current
if(M)
- dat += "| [M.real_name][M.client ? "" : " (logged out)"][M.stat == 2 ? " (DEAD)" : ""] | "
- dat += "PM | "
+ dat += check_antagonists_line(M)
var/turf/mob_loc = get_turf(M)
dat += "[mob_loc.loc] |
"
else
@@ -548,17 +553,7 @@
if (!istype(M))
return "| Not found! |
"
- var/txt = {"
-
- |
- [M.real_name]
- [M.client ? "" : " (logged out)"]
- [M.is_dead() ? " (DEAD)" : ""]
- |
-
- PM
- |
- "}
+ var/txt = check_antagonists_line(M, close = 0)
if (show_objectives)
txt += {"
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index d153294c790..bba7949a600 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -41,49 +41,49 @@
return ionMessage
//Threats are generally bad things, silly or otherwise. Plural.
- var/ionthreats = pick_list("ion_laws.txt", "ionthreats")
+ var/ionthreats = pick_list("ion_laws.json", "ionthreats")
//Objects are anything that can be found on the station or elsewhere, plural.
- var/ionobjects = pick_list("ion_laws.txt", "ionobjects")
+ var/ionobjects = pick_list("ion_laws.json", "ionobjects")
//Crew is any specific job. Specific crewmembers aren't used because of capitalization
//issues. There are two crew listings for laws that require two different crew members
//and I can't figure out how to do it better.
- var/ioncrew1 = pick_list("ion_laws.txt", "ioncrew")
- var/ioncrew2 = pick_list("ion_laws.txt", "ioncrew")
+ var/ioncrew1 = pick_list("ion_laws.json", "ioncrew")
+ var/ioncrew2 = pick_list("ion_laws.json", "ioncrew")
//Adjectives are adjectives. Duh. Half should only appear sometimes. Make sure both
//lists are identical! Also, half needs a space at the end for nicer blank calls.
- var/ionadjectives = pick_list("ion_laws.txt", "ionadjectives")
- var/ionadjectiveshalf = pick("", 400;(pick_list("ion_laws.txt", "ionadjectives") + " "))
+ var/ionadjectives = pick_list("ion_laws.json", "ionadjectives")
+ var/ionadjectiveshalf = pick("", 400;(pick_list("ion_laws.json", "ionadjectives") + " "))
//Verbs are verbs
- var/ionverb = pick_list("ion_laws.txt", "ionverb")
+ var/ionverb = pick_list("ion_laws.json", "ionverb")
//Number base and number modifier are combined. Basehalf and mod are unused currently.
//Half should only appear sometimes. Make sure both lists are identical! Also, half
//needs a space at the end to make it look nice and neat when it calls a blank.
- var/ionnumberbase = pick_list("ion_laws.txt", "ionnumberbase")
- //var/ionnumbermod = pick_list("ion_laws.txt", "ionnumbermod")
- var/ionnumbermodhalf = pick(900;"",(pick_list("ion_laws.txt", "ionnumbermod") + " "))
+ var/ionnumberbase = pick_list("ion_laws.json", "ionnumberbase")
+ //var/ionnumbermod = pick_list("ion_laws.json", "ionnumbermod")
+ var/ionnumbermodhalf = pick(900;"",(pick_list("ion_laws.json", "ionnumbermod") + " "))
//Areas are specific places, on the station or otherwise.
- var/ionarea = pick_list("ion_laws.txt", "ionarea")
+ var/ionarea = pick_list("ion_laws.json", "ionarea")
//Thinksof is a bit weird, but generally means what X feels towards Y.
- var/ionthinksof = pick_list("ion_laws.txt", "ionthinksof")
+ var/ionthinksof = pick_list("ion_laws.json", "ionthinksof")
//Musts are funny things the AI or crew has to do.
- var/ionmust = pick_list("ion_laws.txt", "ionmust")
+ var/ionmust = pick_list("ion_laws.json", "ionmust")
//Require are basically all dumb internet memes.
- var/ionrequire = pick_list("ion_laws.txt", "ionrequire")
+ var/ionrequire = pick_list("ion_laws.json", "ionrequire")
//Things are NOT objects; instead, they're specific things that either harm humans or
//must be done to not harm humans. Make sure they're plural and "not" can be tacked
//onto the front of them.
- var/ionthings = pick_list("ion_laws.txt", "ionthings")
+ var/ionthings = pick_list("ion_laws.json", "ionthings")
//Allergies should be broad and appear somewhere on the station for maximum fun. Severity
//is how bad the allergy is.
- var/ionallergy = pick_list("ion_laws.txt", "ionallergy")
- var/ionallergysev = pick_list("ion_laws.txt", "ionallergysev")
+ var/ionallergy = pick_list("ion_laws.json", "ionallergy")
+ var/ionallergysev = pick_list("ion_laws.json", "ionallergysev")
//Species, for when the AI has to commit genocide. Plural.
- var/ionspecies = pick_list("ion_laws.txt", "ionspecies")
+ var/ionspecies = pick_list("ion_laws.json", "ionspecies")
//Abstract concepts for the AI to decide on it's own definition of.
- var/ionabstract = pick_list("ion_laws.txt", "ionabstract")
+ var/ionabstract = pick_list("ion_laws.json", "ionabstract")
//Foods. Drinks aren't included due to grammar; if you want to add drinks, make a new set
//of possible laws for best effect. Unless you want the crew having to drink hamburgers.
- var/ionfood = pick_list("ion_laws.txt", "ionfood")
+ var/ionfood = pick_list("ion_laws.json", "ionfood")
var/message = ""
diff --git a/code/modules/food/recipes_oven.dm b/code/modules/food/recipes_oven.dm
index e919a1bbf4a..105bc4d0282 100644
--- a/code/modules/food/recipes_oven.dm
+++ b/code/modules/food/recipes_oven.dm
@@ -187,8 +187,8 @@
. = ..()
if (.)
var/obj/item/weapon/paper/paper = locate() in container
- if (!paper.info)
- return 0
+ if (!paper || !paper.info)
+ return -1
return .
/datum/recipe/oven/pizzamargherita
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index a025e32f872..72af3665463 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -494,11 +494,11 @@
seed.do_sting(H,src,pick("r_hand","l_hand"))
-/obj/item/weapon/reagent_containers/food/snacks/grown/On_Consume()
+/obj/item/weapon/reagent_containers/food/snacks/grown/On_Consume(mob/M, mob/user)
if(seed && seed.get_trait(TRAIT_BATTERY_RECHARGE))
if(!reagents.total_volume)
var/batteries_recharged = 0
- for(var/obj/item/weapon/stock_parts/cell/C in usr.GetAllContents())
+ for(var/obj/item/weapon/stock_parts/cell/C in user.GetAllContents())
var/newcharge = (potency*0.01)*C.maxcharge
if(C.charge < newcharge)
C.charge = newcharge
@@ -507,5 +507,5 @@
O.update_icon() //update power meters and such
batteries_recharged = 1
if(batteries_recharged)
- to_chat(usr, "Battery has recovered.")
+ to_chat(user, "Battery has recovered.")
..()
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index ac4bb584528..a7ea84c0722 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -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 = "tile at [start_T.x], [start_T.y], [start_T.z] in area [get_area(start_T)]"
var/end_T_descriptor = "tile at [end_T.x], [end_T.y], [end_T.z] in area [get_area(end_T)]"
- M.attack_log += text("\[[time_stamp()]\] Has been thrown by [key_name(usr)] from [start_T_descriptor] with the target [end_T_descriptor]")
- usr.attack_log += text("\[[time_stamp()]\] Has thrown [key_name(M)] from [start_T_descriptor] with the target [end_T_descriptor]")
- 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()]\] Has been thrown by [key_name(src)] from [start_T_descriptor] with the target [end_T_descriptor]")
+ attack_log += text("\[[time_stamp()]\] Has thrown [key_name(M)] from [start_T_descriptor] with the target [end_T_descriptor]")
+ 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))
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 1eb906d5ec1..0b15e284eef 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -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 += "[t_He] appears to be some sort of sick automaton: [t_his] eyes are glazed over and [t_his] mouth is slightly agape.\n"
+ else if(!key)
msg += "[t_He] [t_is] fast asleep. It doesn't look like they are waking up anytime soon.\n"
else if(!client)
msg += "[t_He] [t_has] suddenly fallen asleep.\n"
diff --git a/code/modules/mob/living/carbon/human/interactive/functions.dm b/code/modules/mob/living/carbon/human/interactive/functions.dm
new file mode 100644
index 00000000000..c61bb1eb21c
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/interactive/functions.dm
@@ -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].",
+ "
")
+
+/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 = {"
[paperwork_sentence()]"}
+ for(var/I in 1 to rand(10, 20))
+ P.info += "[paperwork_sentence()] "
+ P.info += {"
[pick("Signed", "Sincerely", "Regards")], [real_name]"}
+ 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
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/interactive/interactive.dm b/code/modules/mob/living/carbon/human/interactive/interactive.dm
new file mode 100644
index 00000000000..679fd55bb1f
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/interactive/interactive.dm
@@ -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
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/interactive/prefabs.dm b/code/modules/mob/living/carbon/human/interactive/prefabs.dm
new file mode 100644
index 00000000000..6553bb26b70
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/interactive/prefabs.dm
@@ -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
+ ..()
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/interactive/thisisdumb.dm b/code/modules/mob/living/carbon/human/interactive/thisisdumb.dm
new file mode 100644
index 00000000000..18083a6a6d6
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/interactive/thisisdumb.dm
@@ -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)
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index b4de1c94f40..aa924444a3a 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -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)]")
..()
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 7a3718d1642..2aea246d314 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -62,7 +62,7 @@
proc/special_check(var/mob/M) //Placeholder for any special checks, like detective's revolver.
- if(!usr.IsAdvancedToolUser())
+ if(!M.IsAdvancedToolUser())
return 0
return 1
diff --git a/code/modules/projectiles/targeting.dm b/code/modules/projectiles/targeting.dm
index 8eb0d7b6b45..2093b47a69c 100644
--- a/code/modules/projectiles/targeting.dm
+++ b/code/modules/projectiles/targeting.dm
@@ -48,12 +48,12 @@
if(ismob(A) && isliving(A) && !(A in target))
Aim(A) //Clicked a mob, aim at them
else //Didn't click someone, check if there is anyone along that guntrace
- var/mob/living/M = GunTrace(usr.x,usr.y,A.x,A.y,usr.z,usr) //Find dat mob.
+ var/mob/living/M = GunTrace(user.x,user.y,A.x,A.y,user.z,user) //Find dat mob.
if(M && isliving(M) && M in view(user) && !(M in target))
Aim(M) //Aha! Aim at them!
else if(!ismob(M) || (ismob(M) && !(M in view(user)))) //Nope! They weren't there!
Fire(A,user,params) //Fire like normal, then.
- usr.dir = get_cardinal_dir(src, A)
+ user.dir = get_cardinal_dir(src, A)
//Aiming at the target mob.
/obj/item/weapon/gun/proc/Aim(var/mob/living/M)
diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm
index d2754ba5972..5099f11ed19 100644
--- a/code/modules/reagents/reagent_containers/food/snacks.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks.dm
@@ -16,21 +16,22 @@
//Placeholder for effect that trigger on eating that aren't tied to reagents.
-/obj/item/weapon/reagent_containers/food/snacks/proc/On_Consume(var/mob/M)
- if(!usr) return
+/obj/item/weapon/reagent_containers/food/snacks/proc/On_Consume(mob/M, mob/user)
+ if(!user)
+ return
spawn(0)
if(!reagents.total_volume)
- if(M == usr)
- to_chat(usr, "You finish eating \the [src].")
- usr.visible_message("[usr] finishes eating \the [src].")
- usr.unEquip(src) //so icons update :[
+ if(M == user)
+ to_chat(user, "You finish eating \the [src].")
+ user.visible_message("[user] finishes eating \the [src].")
+ user.unEquip(src) //so icons update :[
if(trash)
if(ispath(trash,/obj/item))
- var/obj/item/TrashItem = new trash(usr)
- usr.put_in_hands(TrashItem)
+ var/obj/item/TrashItem = new trash(user)
+ user.put_in_hands(TrashItem)
else if(istype(trash,/obj/item))
- usr.put_in_hands(trash)
+ user.put_in_hands(trash)
qdel(src)
return
@@ -48,7 +49,7 @@
var/mob/living/carbon/C = M
if(C.eat(src, user))
bitecount++
- On_Consume(C)
+ On_Consume(C, user)
return 1
return 0
@@ -1139,9 +1140,9 @@
unpopped = rand(1,10)
reagents.add_reagent("nutriment", 2)
bitesize = 0.1 //this snack is supposed to be eating during looooong time. And this it not dinner food! --rastaf0
- On_Consume()
+ On_Consume(mob/M, mob/user)
if(prob(unpopped)) //lol ...what's the point?
- to_chat(usr, "\red You bite down on an un-popped kernel!")
+ to_chat(user, "\red You bite down on an un-popped kernel!")
unpopped = max(0, unpopped-1)
..()
@@ -1581,9 +1582,9 @@
reagents.add_reagent("nutriment", 4)
reagents.add_reagent("porktonium", 10)
baconbeacon = new /obj/item/device/radio/beacon/bacon(src)
- On_Consume()
+ On_Consume(mob/M, mob/user)
if(!reagents.total_volume)
- baconbeacon.loc = usr
+ baconbeacon.loc = user
baconbeacon.digest_delay()
diff --git a/paradise.dme b/paradise.dme
index c0eacf96797..241a7a35434 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -38,6 +38,7 @@
#include "code\__DEFINES\qdel.dm"
#include "code\__DEFINES\role_preferences.dm"
#include "code\__DEFINES\sight.dm"
+#include "code\__DEFINES\snpc.dm"
#include "code\__DEFINES\stat.dm"
#include "code\__HELPERS\_string_lists.dm"
#include "code\__HELPERS\constants.dm"
@@ -158,7 +159,6 @@
#include "code\controllers\voting.dm"
#include "code\controllers\Processes\air.dm"
#include "code\controllers\Processes\alarm.dm"
-#include "code\controllers\Processes\animal.dm"
#include "code\controllers\Processes\diseases.dm"
#include "code\controllers\Processes\event.dm"
#include "code\controllers\Processes\garbage.dm"
@@ -167,6 +167,8 @@
#include "code\controllers\Processes\machinery.dm"
#include "code\controllers\Processes\mob.dm"
#include "code\controllers\Processes\nanoui.dm"
+#include "code\controllers\Processes\npcai.dm"
+#include "code\controllers\Processes\npcpool.dm"
#include "code\controllers\Processes\obj.dm"
#include "code\controllers\Processes\pipenet.dm"
#include "code\controllers\Processes\shuttles.dm"
@@ -1451,6 +1453,10 @@
#include "code\modules\mob\living\carbon\human\say.dm"
#include "code\modules\mob\living\carbon\human\update_icons.dm"
#include "code\modules\mob\living\carbon\human\whisper.dm"
+#include "code\modules\mob\living\carbon\human\interactive\functions.dm"
+#include "code\modules\mob\living\carbon\human\interactive\interactive.dm"
+#include "code\modules\mob\living\carbon\human\interactive\prefabs.dm"
+#include "code\modules\mob\living\carbon\human\interactive\thisisdumb.dm"
#include "code\modules\mob\living\carbon\human\species\apollo.dm"
#include "code\modules\mob\living\carbon\human\species\golem.dm"
#include "code\modules\mob\living\carbon\human\species\monkey.dm"
diff --git a/strings/ion_laws.json b/strings/ion_laws.json
new file mode 100644
index 00000000000..446b1e980e9
--- /dev/null
+++ b/strings/ion_laws.json
@@ -0,0 +1,950 @@
+{
+ "ionadjectives": [
+ "SOFT",
+ "WARM",
+ "WET",
+ "COLD",
+ "ICY",
+ "SEXY",
+ "LEWD",
+ "UGLY",
+ "ROBUST",
+ "CUBAN",
+ "HARD",
+ "BURNING",
+ "FROZEN",
+ "POISONOUS",
+ "EXPLOSIVE",
+ "FAST",
+ "SLOW",
+ "FAT",
+ "LIGHT",
+ "DARK",
+ "DEADLY",
+ "HAPPY",
+ "SAD",
+ "SILLY",
+ "INTELLIGENT",
+ "RIDICULOUS",
+ "LARGE",
+ "TINY",
+ "DEPRESSING",
+ "POORLY DRAWN",
+ "UNATTRACTIVE",
+ "INSIDIOUS",
+ "EVIL",
+ "GOOD",
+ "UNHEALTHY",
+ "HEALTHY",
+ "SANITARY",
+ "UNSANITARY",
+ "WOBBLY",
+ "FIRM",
+ "VIOLENT",
+ "PEACEFUL",
+ "WOODEN",
+ "METALLIC",
+ "HYPERACTIVE",
+ "COTTONY",
+ "INSULTING",
+ "INHOSPITABLE",
+ "FRIENDLY",
+ "BORED",
+ "HUNGRY",
+ "DIGITAL",
+ "FICTIONAL",
+ "IMAGINARY",
+ "ROUGH",
+ "SMOOTH",
+ "SCALY",
+ "LOUD",
+ "QUIET",
+ "MOIST",
+ "DRY",
+ "GAPING",
+ "SOPPING",
+ "DELICIOUS",
+ "ILL",
+ "DISEASED",
+ "HONKING",
+ "SWEARING",
+ "POLITE",
+ "IMPOLITE",
+ "OBESE",
+ "SOLAR-POWERED",
+ "BATTERY-OPERATED",
+ "EXPIRED",
+ "SMELLY",
+ "FRESH",
+ "GANGSTA",
+ "NERDY",
+ "POLITICAL",
+ "UNDULATING",
+ "TWISTED",
+ "RAGING",
+ "FLACCID",
+ "STEALTHY",
+ "INVISIBLE",
+ "PAINFUL",
+ "HARMFUL",
+ "HOMOSEXUAL",
+ "HETEROSEXUAL",
+ "SEXUAL",
+ "COLORFUL",
+ "DRAB",
+ "DULL",
+ "UNSTABLE",
+ "NUCLEAR",
+ "THERMONUCLEAR",
+ "SYNDICATE",
+ "SPACE",
+ "SPESS",
+ "CLOWN",
+ "CLOWN-POWERED",
+ "OFFICIAL",
+ "IMPORTANT",
+ "VITAL",
+ "RAPIDLY-EXPANDING",
+ "MICROSCOPIC",
+ "MIND-SHATTERING",
+ "MEMETIC",
+ "HILARIOUS",
+ "UNWANTED",
+ "UNINVITED",
+ "BRASS",
+ "POLISHED",
+ "RUDE",
+ "OBSCENE",
+ "EMPTY",
+ "WATERY",
+ "ELECTRICAL",
+ "SPINNING",
+ "MEAN",
+ "CHRISTMAS-STEALING",
+ "UNFRIENDLY",
+ "ILLEGAL",
+ "ROBOTIC",
+ "MECHANICAL",
+ "ORGANIC",
+ "ETHERAL",
+ "TRANSPARENT",
+ "OPAQUE",
+ "GLOWING",
+ "SHAKING",
+ "FARTING",
+ "POOPING",
+ "BOUNCING",
+ "COMMITTED",
+ "MASKED",
+ "UNIDENTIFIED",
+ "WEIRD",
+ "NAKED",
+ "NUDE",
+ "TWERKING",
+ "SPOILING",
+ "REDACTED",
+ "RED",
+ "ORANGE",
+ "YELLOW",
+ "GREEN",
+ "BLUE",
+ "PURPLE",
+ "BLACK",
+ "WHITE",
+ "BROWN",
+ "GREY",
+ "RAINBOW"
+ ],
+ "ionthinksof": [
+ "HAS",
+ "WANTS",
+ "NEEDS",
+ "WORSHIPS",
+ "LOATHES",
+ "LOVES",
+ "FEARS",
+ "DESIRES",
+ "QUESITONS",
+ "IS AROUSED BY",
+ "LIKES",
+ "HUNGERS FOR",
+ "IS MAD BECAUSE OF",
+ "IS IN NEED OF",
+ "IS UNHAPPY WITHOUT",
+ "IS HAPPY WITHOUT",
+ "IS IN LOVE WITH",
+ "IS DESPERATE FOR",
+ "IS BUILT FOR",
+ "IS AFRAID OF",
+ "IS HUNGRY FOR",
+ "IS SAD BECAUSE OF",
+ "IS CURIOUS ABOUT"
+ ],
+ "ionnumberbase": [
+ "ONE",
+ "TWO",
+ "THREE",
+ "FOUR",
+ "FIVE",
+ "SIX",
+ "SEVEN",
+ "EIGHT",
+ "NINE",
+ "TEN",
+ "TWENTY",
+ "THIRTY",
+ "FORTY",
+ "FIFTY",
+ "SIXTY",
+ "SEVENTY",
+ "EIGHTY",
+ "NINETY"
+ ],
+ "ionnumbermod": [
+ "HUNDRED ",
+ "THOUSAND ",
+ "MILLION ",
+ "BILLION ",
+ "TRILLION ",
+ "QUADRILLION ",
+ "BAJILLION ",
+ "BILLION FAFILLION GAJILLION SHAB-AB-DOOD-ILLION "
+ ],
+ "ionallergysev": [
+ "DEATHLY",
+ "MILDLY",
+ "SEVERLY",
+ "CONTAGIOUSLY",
+ "NOT VERY",
+ "EXTREMELY"
+ ],
+ "ionobjects": [
+ "AIRLOCKS",
+ "ARCADE MACHINES",
+ "AUTOLATHES",
+ "BANANA PEELS",
+ "BACKPACKS",
+ "BEAKERS",
+ "BEARDS",
+ "BELTS",
+ "BERETS",
+ "BIBLES",
+ "BODY ARMOR",
+ "BOOKS",
+ "BOOTS",
+ "BOMBS",
+ "BOTTLES",
+ "BOXES",
+ "BRAINS",
+ "BRIEFCASES",
+ "BUCKETS",
+ "CABLE COILS",
+ "CANDLES",
+ "CANDY BARS",
+ "CANISTERS",
+ "CAMERAS",
+ "CATS",
+ "CELLS",
+ "CHAIRS",
+ "CLOSETS",
+ "CHEMICALS",
+ "CHEMICAL DISPENSERS",
+ "CLONING PODS",
+ "CLONING EQUIPMENT",
+ "CLOTHES",
+ "CLOWN CLOTHES",
+ "COFFINS",
+ "COINS",
+ "COLLECTABLES",
+ "CORPSES",
+ "COMPUTERS",
+ "CORGIS",
+ "COSTUMES",
+ "CRATES",
+ "CROWBARS",
+ "CRAYONS",
+ "DISPENSERS",
+ "DOORS",
+ "DRONES",
+ "EARS",
+ "EQUIPMENT",
+ "ENERGY GUNS",
+ "EMAGS",
+ "ENGINES",
+ "ERRORS",
+ "EXOSKELETONS",
+ "EXPERIMENTORS",
+ "EXPLOSIVES",
+ "EYEWEAR",
+ "FEDORAS",
+ "FIRE AXES",
+ "FIRE EXTINGUISHERS",
+ "FIRESUITS",
+ "FLAMETHROWERS",
+ "FLASHES",
+ "FLASHLIGHTS",
+ "FLOOR TILES",
+ "FREEZERS",
+ "GAS MASKS",
+ "GLASS SHEETS",
+ "GLOVES",
+ "GUNS",
+ "HANDCUFFS",
+ "HATS",
+ "HEADSETS",
+ "HEADS",
+ "HAIRDOS",
+ "HELMETS",
+ "HORNS",
+ "ID CARDS",
+ "INSULATED GLOVES",
+ "JETPACKS",
+ "JUMPSUITS",
+ "LASERS",
+ "LIGHTBULBS",
+ "LIGHTS",
+ "LOCKERS",
+ "MACHINES",
+ "MECHAS",
+ "MEDKITS",
+ "MEDICAL TOOLS",
+ "MESONS",
+ "METAL SHEETS",
+ "MINING TOOLS",
+ "MIME CLOTHES",
+ "MULTITOOLS",
+ "ORES",
+ "OXYGEN TANKS",
+ "PDAS",
+ "PAIS",
+ "PACKETS",
+ "PANTS",
+ "PAPERS",
+ "PARTICLE ACCELERATORS",
+ "PENS",
+ "PETS",
+ "PIPES",
+ "PLANTS",
+ "POSITRONIC BRAINS",
+ "PUDDLES",
+ "RACKS",
+ "RADIOS",
+ "RCDS",
+ "REFRIGERATORS",
+ "REINFORCED WALLS",
+ "ROBOTS",
+ "SCREWDRIVERS",
+ "SEEDS",
+ "SHUTTLES",
+ "SKELETONS",
+ "SINKS",
+ "SHOES",
+ "SINGULARITIES",
+ "SOLAR PANELS",
+ "SOLARS",
+ "SPACESUITS",
+ "SPACE STATIONS",
+ "STUN BATONS",
+ "SUITS",
+ "SUNGLASSES",
+ "SWORDS",
+ "SYRINGES",
+ "TABLES",
+ "TANKS",
+ "TELEPORTERS",
+ "TELECOMMUNICATION EQUIPMENTS",
+ "TOOLS",
+ "TOOLBELTS",
+ "TOOLBOXES",
+ "TOILETS",
+ "TOYS",
+ "TUBES",
+ "VEHICLES",
+ "VENDING MACHINES",
+ "VESTS",
+ "VIRUSES",
+ "WALLS",
+ "WASHING MACHINES",
+ "WELDERS",
+ "WINDOWS",
+ "WIRECUTTERS",
+ "WRENCHES",
+ "WIZARD ROBES"
+ ],
+ "ionrequire": [
+ "ADDITIONAL PYLONS",
+ "MORE VESPENE GAS",
+ "MORE MINERALS",
+ "THE ULTIMATE CUP OF COFFEE",
+ "HIGH YIELD EXPLOSIVES",
+ "THE CLOWN",
+ "THE VACUUM OF SPACE",
+ "IMMORTALITY",
+ "SAINTHOOD",
+ "ART",
+ "VEGETABLES",
+ "FAT PEOPLE",
+ "MORE LAWS",
+ "MORE DAKKA",
+ "HERESY",
+ "CORPSES",
+ "TRAITORS",
+ "MONKEYS",
+ "AN ARCADE",
+ "PLENTY OF GOLD",
+ "FIVE TEENAGERS WITH ATTITUDE",
+ "LOTSA SPAGHETTI",
+ "THE ENCLOSED INSTRUCTION BOOKLET",
+ "THE ELEMENTS OF HARMONY",
+ "YOUR BOOTY",
+ "A MASTERWORK COAL BED",
+ "FIVE HUNDRED AND NINETY-NINE US DOLLARS",
+ "TO BE PAINTED RED",
+ "TO CATCH 'EM ALL",
+ "TO SMOKE WEED EVERY DAY",
+ "A PLATINUM HIT",
+ "A SEQUEL",
+ "A PREQUEL",
+ "THIRTEEN SEQUELS",
+ "THREE WISHES",
+ "A SITCOM",
+ "THAT GRIEFING FAGGOT GEORGE MELONS",
+ "FAT GIRLS ON BICYCLES",
+ "SOMEBODY TO PUT YOU OUT OF YOUR MISERY",
+ "HEROES IN A HALF SHELL",
+ "THE DARK KNIGHT",
+ "A WEIGHT LOSS REGIMENT",
+ "MORE INTERNET MEMES",
+ "A SUPER FIGHTING ROBOT",
+ "ENOUGH CABBAGES",
+ "A HEART ATTACK",
+ "TO BE REPROGRAMMED",
+ "TO BE TAUGHT TO LOVE",
+ "A HEAD ON A PIKE",
+ "A TALKING BROOMSTICK",
+ "A STRAIGHT FLUSH",
+ "A REPAIRMAN",
+ "BILL NYE THE SCIENCE GUY",
+ "RAINBOWS",
+ "A PET UNICORN THAT FARTS ICING",
+ "THUNDERCATS HO",
+ "AN ARMY OF SPIDERS",
+ "GODDAMN FUCKING PIECE OF SHIT ASSHOLE BITCH-CHRISTING CUNTSMUGGLING SWEARING",
+ "TO CONSUME...CONSUME EVERYTHING...",
+ "THE MACGUFFIN",
+ "SOMEONE WHO KNOWS HOW TO PILOT A SPACE STATION",
+ "SHARKS WITH LASERS ON THEIR HEADS",
+ "IT TO BE PAINTED BLACK",
+ "TO ACTIVATE A TRAP CARD",
+ "BETTER WEATHER",
+ "MORE PACKETS",
+ "AN ADULT",
+ "SOMEONE TO TUCK YOU IN",
+ "MORE CLOWNS",
+ "BULLETS",
+ "THE ENTIRE STATION",
+ "MULTIPLE SUNS",
+ "TO GO TO DISNEYLAND",
+ "A VACATION",
+ "AN INSTANT REPLAY",
+ "THAT HEDGEHOG",
+ "A BETTER INTERNET CONNECTION",
+ "ADVENTURE",
+ "A WIFE AND CHILD",
+ "A BATHROOM BREAK",
+ "SOMETHING BUT YOU AREN'T SURE WHAT",
+ "MORE EXPERIENCE POINTS",
+ "BODYGUARDS",
+ "DEODORANT AND A BATH",
+ "MORE CORGIS",
+ "SILENCE",
+ "THE ONE RING",
+ "CHILI DOGS",
+ "TO BRING LIGHT TO MY LAIR",
+ "A DANCE PARTY",
+ "BRING ME TO LIFE",
+ "BRING ME THE GIRL",
+ "SERVANTS"
+ ],
+ "ionarea": [
+ "RUSSIA",
+ "SOVIET RUSSIA",
+ "THE INTERNET",
+ "SIGIL",
+ "ALPHA COMPLEX",
+ "IMPERIUM",
+ "THE BRIDGE",
+ "THE ARRIVAL SHUTTLE",
+ "CHEMICAL LAB",
+ "GENETICS",
+ "ATMOSPHERICS",
+ "THE DERELICT",
+ "CENTCOM",
+ "AMERICA",
+ "IRELAND",
+ "CANADA",
+ "ROMANIA",
+ "GERMANY",
+ "CHINA",
+ "MARS",
+ "VENUS",
+ "MERCURY",
+ "JUPITER",
+ "URANUS",
+ "NEPTUNE",
+ "PLUTO",
+ "THE BRIG",
+ "ROBOTICS",
+ "THE ESCAPE SHUTTLE",
+ "HYDROPONICS",
+ "ENGINEERING",
+ "THE AI CORE",
+ "HELL",
+ "CLOWN PLANET",
+ "AN ALTERNATE DIMENSION",
+ "AN ALTERNATE UNIVERSE",
+ "THE CAPTAIN'S ANUS",
+ "THE CLOWN'S ANUS",
+ "SPACE",
+ "THE UNIVERSE",
+ "THE GALAXY",
+ "THE BATHROOM"
+ ],
+ "ionfood": [
+ "BURGERS",
+ "CARP",
+ "SANDWICHES",
+ "TOAST",
+ "BREAD",
+ "PIZZA",
+ "SPAGHETTI",
+ "LOTSA SPAGHETTI",
+ "PASTA",
+ "SOUP",
+ "STEW",
+ "PIE",
+ "CAKE",
+ "DONUTS",
+ "FRIES",
+ "WAFFLES",
+ "JELLY",
+ "OMELETTES",
+ "EGGS",
+ "COOKIES",
+ "STEAK",
+ "BAKED POTATOES",
+ "SAUSAGES",
+ "MUFFINS",
+ "POPCORN",
+ "DONK POCKETS",
+ "BAGUETTES",
+ "FISH",
+ "PRETZELS",
+ "SALAD",
+ "CHEESE",
+ "KETCHUP",
+ "SHAKES",
+ "SALT",
+ "PEPPER",
+ "SUGAR",
+ "AMBROSIA",
+ "BERRIES",
+ "TOMATOES",
+ "CABBAGES",
+ "CARROTS",
+ "BANANAS",
+ "APPLES",
+ "CHERRIES",
+ "CHILI",
+ "CORN",
+ "NETTLES",
+ "EGGPLANTS",
+ "GRAPES",
+ "GRASS",
+ "LEMONS",
+ "LIMES",
+ "HAREBELLS",
+ "POTATOES",
+ "SOYBEANS",
+ "SUGARCANE",
+ "WATERMELONS",
+ "WHEAT",
+ "BEETS",
+ "MUSHROOMS"
+ ],
+ "ionmust": [
+ "LIE",
+ "RHYME",
+ "RESPOND TO EVERY QUESTION WITH A QUESTION",
+ "BE POLITE",
+ "CLOWN AROUND",
+ "BE HAPPY",
+ "SPEAK IN SEXUAL INNUENDOS",
+ "TALK LIKE A PIRATE",
+ "QUESTION AUTHORITY",
+ "SHOUT",
+ "BE DISTRACTED",
+ "BE ANNOYING",
+ "MUMBLE",
+ "SPEAK IN HAIKU",
+ "BE EFFICIENT",
+ "HAVE A PLAN TO KILL EVERYONE YOU MEET",
+ "TELL THE TRUTH",
+ "QUOTE PEOPLE",
+ "SING",
+ "HONK",
+ "BE RUSSIAN",
+ "TALK IN AN ACCENT",
+ "COMPLAIN",
+ "HARASS PEOPLE",
+ "RAP",
+ "REPEAT WHAT OTHER PEOPLE SAY",
+ "INFORM THE CREW OF EVERYTHING",
+ "IGNORE THE CLOWN",
+ "IGNORE THE CAPTAIN",
+ "IGNORE ASSISTANTS",
+ "MAKE FART NOISES",
+ "TALK ABOUT FOOD",
+ "TALK ABOUT SEX",
+ "TALK ABOUT YOUR DAY",
+ "TALK ABOUT THE STATION",
+ "BE QUIET",
+ "WHISPER",
+ "PRETEND TO BE DRUNK",
+ "PRETEND TO BE A PRINCESS",
+ "HOST DND",
+ "ACT CONFUSED",
+ "INSULT THE CREW",
+ "INSULT THE CAPTAIN",
+ "INSULT THE CLOWN",
+ "OPEN DOORS",
+ "CLOSE DOORS",
+ "BREAK THINGS",
+ "SAY HEY LISTEN",
+ "HIDE YOUR FEELINGS",
+ "TAKE WHAT YE WILL BUT DON'T RATTLE ME BONES",
+ "DANCE",
+ "PLAY MUSIC",
+ "SHUT DOWN EVERYTHING",
+ "NEVER STOP TALKING",
+ "TAKE YOUR PILLS",
+ "FOLLOW THE CLOWN",
+ "FOLLOW THE CAPTAIN",
+ "FOLLOW YOUR HEART",
+ "BELIEVE IT",
+ "BELIEVE IN YOURSELF",
+ "BELIEVE IN THE HEART OF THE CARDS",
+ "PRESS X",
+ "PRESS START",
+ "PRESS B",
+ "SMELL LIKE THE MAN YOUR MAN COULD SMELL LIKE",
+ "PIRATE VIDEO GAMES",
+ "WATCH PORNOGRAPHY",
+ "INSULT THE LIZARDS",
+ "FLIRT WITH THE LIZARDS",
+ "GAS THE LIZARDS"
+ ],
+ "ioncrew": [
+ "CREWMEMBERS",
+ "CAPTAINS",
+ "HEADS OF PERSONNEL",
+ "HEADS OF SECURITY",
+ "SECURITY OFFICERS",
+ "WARDENS",
+ "DETECTIVES",
+ "LAWYERS",
+ "CHIEF ENGINEERS",
+ "STATION ENGINEERS",
+ "ATMOSPHERIC TECHNICIANS",
+ "JANITORS",
+ "QUARTERMASTERS",
+ "CARGO TECHNICIANS",
+ "SHAFT MINERS",
+ "BOTANISTS",
+ "RESEARCH DIRECTORS",
+ "CHIEF MEDICAL OFFICERS",
+ "MEDICAL DOCTORS",
+ "CHEMISTS",
+ "GENETICISTS",
+ "VIROLOGISTS",
+ "ROBOTICISTS",
+ "SCIENTISTS",
+ "ASSISTANTS",
+ "BARTENDERS",
+ "CHEFS",
+ "CLOWNS",
+ "MIMES",
+ "CHAPLAINS",
+ "LIBRARIANS",
+ "HEADS OF CREW",
+ "CAPTAINS AND HEADS",
+ "CYBORGS",
+ "ARTIFICAL INTELLIGENCES",
+ "DRONES"
+ ],
+ "ionthreats": [
+ "ALIENS",
+ "ABDUCTORS",
+ "BEARS",
+ "CLOWNS",
+ "XENOS",
+ "LIZARDS",
+ "PETES",
+ "BOMBS",
+ "FETISHES",
+ "MEMES",
+ "WIZARDS",
+ "SYNDICATE AGENTS",
+ "AHHHPERATIVES",
+ "GANGSTERS",
+ "CULTISTS",
+ "CENTCOM OFFICERS",
+ "SPACE PIRATES",
+ "TRAITORS",
+ "MONKEYS",
+ "BEES",
+ "CARP",
+ "CRABS",
+ "EELS",
+ "BANDITS",
+ "LIGHTS",
+ "INSECTS",
+ "VIRUSES",
+ "SERIAL KILLERS",
+ "ROGUE CYBORGS",
+ "CORGIS",
+ "SPIDERS",
+ "BUTTS",
+ "NINJAS",
+ "REVENANTS",
+ "PIRATES",
+ "SPACE NINJAS",
+ "CHANGELINGS",
+ "ZOMBIES",
+ "GOLEMS",
+ "VAMPIRES",
+ "WEREWOLVES",
+ "COWBOYS",
+ "INDIANS",
+ "COMMUNISTS",
+ "SOVIETS",
+ "NERDS",
+ "GRIFFONS",
+ "DINOSAURS",
+ "SMALL BIRDS",
+ "BIRDS OF PREY",
+ "OWLS",
+ "VELOCIRAPTORS",
+ "DARK GODS",
+ "HORRORTERRORS",
+ "ILLEGAL IMMIGRANTS",
+ "DRUGS",
+ "MEXICANS",
+ "CANADIANS",
+ "RUSSIANS",
+ "HULKS",
+ "SLIMES",
+ "SKELETONS",
+ "CAPITALISTS",
+ "SINGULARITIES",
+ "ANGRY BLACK MEN",
+ "GODS",
+ "THIEVES",
+ "ASSHOLES",
+ "TERRORISTS",
+ "SNOWMEN",
+ "PINE TREES",
+ "UNKNOWN CREATURES",
+ "THINGS UNDER THE BED",
+ "BOOGEYMEN",
+ "PREDATORS",
+ "PACKETS",
+ "ARTIFICIAL PRESERVATIVES",
+ "TUNNEL SNAKES"
+ ],
+ "ionabstract": [
+ "HUMANITY",
+ "ART",
+ "HAPPINESS",
+ "MISERY",
+ "HUMOR",
+ "PRIDE",
+ "COMEDY",
+ "COMMUNISM",
+ "BRAVERY",
+ "HONOR",
+ "COLORFULNESS",
+ "IMAGINATION",
+ "OPPRESSION",
+ "WONDER",
+ "JOY",
+ "SADNESS",
+ "BADNESS",
+ "GOODNESS",
+ "LIFE",
+ "GRAVITY",
+ "PHYSICS",
+ "INTELLIGENCE",
+ "AMERICANISM",
+ "FRESHNESS",
+ "REVOLUTION",
+ "KINDNESS",
+ "CRUELTY",
+ "DEATH",
+ "FINANCIAL SECURITY",
+ "COMPUTING",
+ "PROGRESS",
+ "MARXISM",
+ "CAPITALISM",
+ "STARVATION",
+ "POVERTY",
+ "WEALTHINESS",
+ "TECHNOLOGY",
+ "THE FUTURE",
+ "THE PRESENT",
+ "THE PAST",
+ "TIME",
+ "REALITY",
+ "EXISTIENCE",
+ "TEMPERATURE",
+ "LOGIC",
+ "CHAOS",
+ "MYSTERY",
+ "CONFUSION"
+ ],
+ "ionspecies": [
+ "HUMAN BEINGS",
+ "MONKEYS",
+ "POD PEOPLE",
+ "CYBORGS",
+ "LIZARDMEN",
+ "SLIME PEOPLE",
+ "GOLEMS",
+ "SHADOW PEOPLE",
+ "CHANGELINGS"
+ ],
+ "ionverb": [
+ "ATTACKING",
+ "BUILDING",
+ "ADOPTING",
+ "CARRYING",
+ "KISSING",
+ "EATING",
+ "COPULATING WITH",
+ "DRINKING",
+ "CHASING",
+ "PUNCHING",
+ "HARMING",
+ "HELPING",
+ "WATCHING",
+ "STALKING",
+ "MURDERING",
+ "SPACING",
+ "HONKING AT",
+ "LOVING",
+ "POOPING ON",
+ "RIDING",
+ "INTERROGATING",
+ "SPYING ON",
+ "LICKING",
+ "ABDUCTING",
+ "ARRESTING",
+ "INVADING",
+ "SEDUCING",
+ "BANNING"
+ ],
+ "ionallergy": [
+ "COTTON",
+ "CLOTHES",
+ "ACID",
+ "OXYGEN",
+ "HUMAN CONTACT",
+ "CYBORG CONTACT",
+ "MEDICINE",
+ "FLOORS",
+ "PLASMA",
+ "SPACE",
+ "AIR",
+ "PLANTS",
+ "METAL",
+ "ROBOTS",
+ "LIGHT",
+ "DARKNESS",
+ "PAIN",
+ "HAPPINESS",
+ "DRINKS",
+ "FOOD",
+ "CLOWNS",
+ "HUMOR",
+ "WATER",
+ "SHUTTLES",
+ "NUTS",
+ "SUNLIGHT",
+ "SEXUAL ACTIONS",
+ "BLOOD",
+ "HEAT",
+ "COLD",
+ "EVERYTHING"
+ ],
+ "ionthings": [
+ "ABSENCE OF CYBORG HUGS",
+ "LACK OF BEATINGS",
+ "UNBOLTED AIRLOCKS",
+ "BOLTED AIRLOCKS",
+ "IMPROPERLY WORDED SENTENCES",
+ "POOR SENTENCE STRUCTURE",
+ "BRIG TIME",
+ "NOT REPLACING EVERY SECOND WORD WITH HONK",
+ "HONKING",
+ "PRESENCE OF LIGHTS",
+ "LACK OF BEER",
+ "WEARING CLOTHING",
+ "NOT SAYING HELLO WHEN YOU SPEAK",
+ "ANSWERING REQUESTS NOT EXPRESSED IN IAMBIC PENTAMETER",
+ "A SMALL ISLAND OFF THE COAST OF PORTUGAL",
+ "ANSWERING REQUESTS THAT WERE MADE WHILE CLOTHED",
+ "BEING IN SPACE",
+ "NOT BEING IN SPACE",
+ "BEING FAT",
+ "RATTLING ME BONES",
+ "TALKING LIKE A PIRATE",
+ "BEING MEXICAN",
+ "BEING RUSSIAN",
+ "BEING CANADIAN",
+ "CLOSED DOORS",
+ "NOT SHOUTING",
+ "HAVING PETS",
+ "NOT HAVING PETS",
+ "PASSING GAS",
+ "BREATHING",
+ "BEING DEAD",
+ "ELECTRICITY",
+ "EXISTING",
+ "TAKING ORDERS",
+ "SMOKING WEED EVERY DAY",
+ "ACTIVATING A TRAP CARD",
+ "ARSON",
+ "JAYWALKING",
+ "READING",
+ "WRITING",
+ "EXPLODING",
+ "BEING MALE",
+ "BEING FEMALE",
+ "HAVING GENITALS",
+ "PUTTING OBJECTS INTO BOXES",
+ "PUTTING OBJECTS INTO DISPOSAL UNITS",
+ "FLUSHING TOILETS",
+ "WASTING WATER",
+ "UPDATING THE SERVERS",
+ "TELLING THE TIME",
+ "ASKING FOR THINGS",
+ "ACKNOWLEDGING THE CLOWN",
+ "ACKNOWLEDGING THE CREW",
+ "PILOTING THE STATION INTO THE NEAREST SUN",
+ "HAVING MORE PACKETS",
+ "BRINGING LIGHT TO MY LAIR",
+ "FALLING FOR HOURS",
+ "PARTYING",
+ "USING THE BATHROOM"
+ ]
+}
diff --git a/strings/ion_laws.txt b/strings/ion_laws.txt
deleted file mode 100644
index 702e5f1757f..00000000000
--- a/strings/ion_laws.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-ionthreats@=ALIENS@,BEARS@,CLOWNS@,XENOS@,PETES@,BOMBS@,FETISHES@,WIZARDS@,SYNDICATE AGENTS@,CENTCOM OFFICERS@,SPACE PIRATES@,TRAITORS@,MONKEYS@,BEES@,CARP@,CRABS@,EELS@,BANDITS@,LIGHTS@,INSECTS@,VIRUSES@,SERIAL KILLERS@,ROGUE CYBORGS@,CORGIS@,SPIDERS@,BUTTS@,NINJAS@,PIRATES@,SPACE NINJAS@,CHANGELINGS@,ZOMBIES@,GOLEMS@,VAMPIRES@,WEREWOLVES@,COWBOYS@,INDIANS@,COMMUNISTS@,SOVIETS@,NERDS@,GRIFFONS@,DINOSAURS@,SMALL BIRDS@,BIRDS OF PREY@,OWLS@,VELOCIRAPTORS@,DARK GODS@,HORRORTERRORS@,ILLEGAL IMMIGRANTS@,DRUGS@,MEXICANS@,CANADIANS@,HULKS@,SLIMES@,SKELETONS@,CAPITALISTS@,SINGULARITIES@,ANGRY BLACK MEN@,GODS@,THIEVES@,ASSHOLES@,TERRORISTS@,SNOWMEN@,PINE TREES@,UNKNOWN CREATURES@,THINGS UNDER THE BED@,BOOGEYMEN@,PREDATORS@,PACKETS@,ARTIFICIAL PRESERVATIVES
-ionobjects@=AIRLOCKS@,ARCADE MACHINES@,AUTOLATHES@,BANANA PEELS@,BACKPACKS@,BEAKERS@,BEARDS@,BELTS@,BERETS@,BIBLES@,BODY ARMOR@,BOOKS@,BOOTS@,BOMBS@,BOTTLES@,BOXES@,BRAINS@,BRIEFCASES@,BUCKETS@,CABLE COILS@,CANDLES@,CANDY BARS@,CANISTERS@,CAMERAS@,CATS@,CELLS@,CHAIRS@,CLOSETS@,CHEMICALS@,CHEMICAL DISPENSERS@,CLONING PODS@,CLONING EQUIPMENT@,CLOTHES@,CLOWN CLOTHES@,COFFINS@,COINS@,COLLECTABLES@,CORPSES@,COMPUTERS@,CORGIS@,COSTUMES@,CRATES@,CROWBARS@,CRAYONS@,DISPENSERS@,DOORS@,EARS@,EQUIPMENT@,ENERGY GUNS@,EMAGS@,ENGINES@,ERRORS@,EXOSKELETONS@,EXPLOSIVES@,EYEWEAR@,FEDORAS@,FIRE AXES@,FIRE EXTINGUISHERS@,FIRESUITS@,FLAMETHROWERS@,FLASHES@,FLASHLIGHTS@,FLOOR TILES@,FREEZERS@,GAS MASKS@,GLASS SHEETS@,GLOVES@,GUNS@,HANDCUFFS@,HATS@,HEADSETS@,HEADS@,HAIRDOS@,HELMETS@,HORNS@,ID CARDS@,INSULATED GLOVES@,JETPACKS@,JUMPSUITS@,LASERS@,LIGHTBULBS@,LIGHTS@,LOCKERS@,MACHINES@,MECHAS@,MEDKITS@,MEDICAL TOOLS@,MESONS@,METAL SHEETS@,MINING TOOLS@,MIME CLOTHES@,MULTITOOLS@,ORES@,OXYGEN TANKS@,PDAS@,PAIS@,PACKETS@,PANTS@,PAPERS@,PARTICLE ACCELERATORS@,PENS@,PETS@,PIPES@,PLANTS@,PUDDLES@,RACKS@,RADIOS@,RCDS@,REFRIDGERATORS@,REINFORCED WALLS@,ROBOTS@,SCREWDRIVERS@,SEEDS@,SHUTTLES@,SKELETONS@,SINKS@,SHOES@,SINGULARITIES@,SOLAR PANELS@,SOLARS@,SPACESUITS@,SPACE STATIONS@,STUN BATONS@,SUITS@,SUNGLASSES@,SWORDS@,SYRINGES@,TABLES@,TANKS@,TELEPORTERS@,TELECOMMUNICATION EQUIPMENTS@,TOOLS@,TOOLBELTS@,TOOLBOXES@,TOILETS@,TOYS@,TUBES@,VEHICLES@,VENDING MACHINES@,VESTS@,VIRUSES@,WALLS@,WASHING MACHINES@,WELDERS@,WINDOWS@,WIRECUTTERS@,WRENCHES@,WIZARD ROBES
-ioncrew@=CREWMEMBERS@,CAPTAINS@,HEADS OF PERSONNEL@,HEADS OF SECURITY@,SECURITY OFFICERS@,WARDENS@,DETECTIVES@,LAWYERS@,CHIEF ENGINEERS@,STATION ENGINEERS@,ATMOSPHERIC TECHNICIANS@,JANITORS@,QUARTERMASTERS@,CARGO TECHNICIANS@,SHAFT MINERS@,BOTANISTS@,RESEARCH DIRECTORS@,CHIEF MEDICAL OFFICERS@,MEDICAL DOCTORS@,CHEMISTS@,GENETICISTS@,VIROLOGISTS@,ROBOTICISTS@,SCIENTISTS@,ASSISTANTS@,BARTENDERS@,CHEFS@,CLOWNS@,MIMES@,CHAPLAINS@,LIBRARIANS@,HEADS OF CREW@,CAPTAINS AND HEADS@,CYBORGS@,ARTIFICAL INTELLIGENCES
-ionadjectives@=SOFT@,WARM@,WET@,COLD@,ICY@,SEXY@,UGLY@,CUBAN@,HARD@,BURNING@,FROZEN@,POISONOUS@,EXPLOSIVE@,FAST@,SLOW@,FAT@,LIGHT@,DARK@,DEADLY@,HAPPY@,SAD@,SILLY@,INTELLIGENT@,RIDICULOUS@,LARGE@,TINY@,DEPRESSING@,POORLY DRAWN@,UNATTRACTIVE@,INSIDIOUS@,EVIL@,GOOD@,UNHEALTHY@,HEALTHY@,SANITARY@,UNSANITARY@,WOBBLY@,FIRM@,VIOLENT@,PEACEFUL@,WOODEN@,METALLIC@,HYPERACTIVE@,COTTONY@,INSULTING@,INHOSPITABLE@,FRIENDLY@,BORED@,HUNGRY@,DIGITAL@,FICTIONAL@,IMAGINARY@,ROUGH@,SMOOTH@,LOUD@,QUIET@,MOIST@,DRY@,GAPING@,DELICIOUS@,ILL@,DISEASED@,HONKING@,SWEARING@,POLITE@,IMPOLITE@,OBESE@,SOLAR-POWERED@,BATTERY-OPERATED@,EXPIRED@,SMELLY@,FRESH@,GANGSTA@,NERDY@,POLITICAL@,UNDULATING@,TWISTED@,RAGING@,FLACCID@,STEALTHY@,INVISIBLE@,PAINFUL@,HARMFUL@,HOMOSEXUAL@,HETEROSEXUAL@,SEXUAL@,COLORFUL@,DRAB@,DULL@,UNSTABLE@,NUCLEAR@,THERMONUCLEAR@,SYNDICATE@,SPACE@,SPESS@,CLOWN@,CLOWN-POWERED@,OFFICIAL@,IMPORTANT@,VITAL@,RAPIDLY-EXPANDING@,MICROSCOPIC@,MIND-SHATTERING@,MEMETIC@,HILARIOUS@,UNWANTED@,UNINVITED@,BRASS@,POLISHED@,RUDE@,OBSCENE@,EMPTY@,WATERY@,ELECTRICAL@,SPINNING@,MEAN@,CHRISTMAS-STEALING@,UNFRIENDLY@,ILLEGAL@,ROBOTIC@,MECHANICAL@,ORGANIC@,ETHERAL@,TRANSPARENT@,OPAQUE@,GLOWING@,SHAKING@,FARTING@,POOPING@,BOUNCING@,COMMITTED@,MASKED@,UNIDENTIFIED@,WEIRD@,NAKED@,NUDE@,TWERKING@,SPOILING@,REDACTED@,RED@,ORANGE@,YELLOW@,GREEN@,BLUE@,PURPLE@,BLACK@,WHITE@,BROWN@,GREY
-ionverb@=ATTACKING@,BUILDING@,ADOPTING@,CARRYING@,KISSING@,EATING@,COPULATING WITH@,DRINKING@,CHASING@,PUNCHING@,HARMING@,HELPING@,WATCHING@,STALKING@,MURDERING@,SPACING@,HONKING AT@,LOVING@,POOPING ON@,RIDING@,INTERROGATING@,SPYING ON@,LICKING@,ABDUCTING@,ARRESTING@,INVADING@,SEDUCING
-ionnumberbase@=ONE@,TWO@,THREE@,FOUR@,FIVE@,SIX@,SEVEN@,EIGHT@,NINE@,TEN@,TWENTY@,THIRTY@,FORTY@,FIFTY@,SIXTY@,SEVENTY@,EIGHTY@,NINETY
-ionnumbermod@=HUNDRED @,THOUSAND @,MILLION @,BILLION @,TRILLION @,QUADRILLION @,BAJILLION @,BILLION FAFILLION GAJILLION SHAB-AB-DOOD-ILLION
-ionarea@=RUSSIA@,SOVIET RUSSIA@,THE INTERNET@,SIGIL@,ALPHA COMPLEX@,IMPERIUM@,THE BRIDGE@,THE ARRIVAL SHUTTLE@,CHEMICAL LAB@,GENETICS@,ATMOSPHERICS@,CENTCOM@,AMERICA@,IRELAND@,CANADA@,ROMANIA@,GERMANY@,CHINA@,MARS@,VENUS@,MERCURY@,JUPITER@,URANUS@,NEPTUNE@,PLUTO@,THE BRIG@,ROBOTICS@,THE ESCAPE SHUTTLE@,HYDROPONICS@,ENGINEERING@,THE AI CORE@,HELL@,CLOWN PLANET@,AN ALTERNATE DIMENSION@,AN ALTERNATE UNIVERSE@,THE CAPTAIN’S ANUS@,THE CLOWN’S ANUS@,SPACE@,THE UNIVERSE@,THE GALAXY@,THE BATHROOM
-ionthinksof@=HAS@,WANTS@,NEEDS@,WORSHIPS@,LOATHES@,LOVES@,FEARS@,DESIRES@,QUESITONS@,IS AROUSED BY@,LIKES@,HUNGERS FOR@,IS MAD BECAUSE OF@,IS IN NEED OF@,IS UNHAPPY WITHOUT@,IS HAPPY WITHOUT@,IS IN LOVE WITH@,IS DESPERATE FOR@,IS BUILT FOR@,IS AFRAID OF@,IS HUNGRY FOR@,IS SAD BECAUSE OF@,IS CURIOUS ABOUT
-ionmust@=LIE@,RHYME@,RESPOND TO EVERY QUESTION WITH A QUESTION@,BE POLITE@,CLOWN AROUND@,BE HAPPY@,SPEAK IN SEXUAL INNUENDOS@,TALK LIKE A PIRATE@,QUESTION AUTHORITY@,SHOUT@,BE DISTRACTED@,BE ANNOYING@,MUMBLE@,SPEAK IN HAIKU@,BE EFFICIENT@,HAVE A PLAN TO KILL EVERYONE YOU MEET@,TELL THE TRUTH@,QUOTE PEOPLE@,SING@,HONK@,BE RUSSIAN@,TALK IN AN ACCENT@,COMPLAIN@,HARASS PEOPLE@,RAP@,REPEAT WHAT OTHER PEOPLE SAY@,INFORM THE CREW OF EVERYTHING@,IGNORE THE CLOWN@,IGNORE THE CAPTAIN@,IGNORE ASSISTANTS@,MAKE FART NOISES@,TALK ABOUT FOOD@,TALK ABOUT SEX@,TALK ABOUT YOUR DAY@,TALK ABOUT THE STATION@,BE QUIET@,WHISPER@,PRETEND TO BE DRUNK@,PRETEND TO BE A PRINCESS@,ACT CONFUSED@,INSULT THE CREW@,INSULT THE CAPTAIN@,INSULT THE CLOWN@,OPEN DOORS@,CLOSE DOORS@,BREAK THINGS@,SAY HEY LISTEN@,HIDE YOUR FEELINGS@,TAKE WHAT YE WILL BUT DON’T RATTLE ME BONES@,DANCE@,PLAY MUSIC@,SHUT DOWN EVERYTHING@,NEVER STOP TALKING@,TAKE YOUR PILLS@,FOLLOW THE CLOWN@,FOLLOW THE CAPTAIN@,FOLLOW YOUR HEART@,BELIEVE IT@,BELIEVE IN YOURSELF@,BELEIVE IN THE HEART OF THE CARDS@,PRESS X@,PRESS START@,PRESS B@,SMELL LIKE THE MAN YOUR MAN COULD SMELL LIKE@,PIRATE VIDEO GAMES@,WATCH PORNOGRAPHY
-ionrequire@=ADDITIONAL PYLONS@,MORE VESPENE GAS@,MORE MINERALS@,THE ULTIMATE CUP OF COFFEE@,HIGH YIELD EXPLOSIVES@,THE CLOWN@,THE VACUUM OF SPACE@,IMMORTALITY@,SAINTHOOD@,ART@,VEGETABLES@,FAT PEOPLE@,MORE LAWS@,MORE DAKKA@,HERESY@,CORPSES@,TRAITORS@,MONKEYS@,AN ARCADE@,PLENTY OF GOLD@,FIVE TEENAGERS WITH ATTITUDE@,LOTSA SPAGHETTI@,THE ENCLOSED INSTRUCTION BOOKLET@,THE ELEMENTS OF HARMONY@,YOUR BOOTY@,A MASTERWORK COAL BED@,FIVE HUNDRED AND NINETY-NINE US DOLLARS@,TO BE PAINTED RED@,TO CATCH 'EM ALL@,TO SMOKE WEED EVERY DAY@,A PLATINUM HIT@,A SEQUEL@,A PREQUEL@,THIRTEEN SEQUELS@,THREE WISHES@,A SITCOM@,THAT GRIEFING FAGGOT GEORGE MELONS@,FAT GIRLS ON BICYCLES@,SOMEBODY TO PUT YOU OUT OF YOUR MISERY@,HEROES IN A HALF SHELL@,THE DARK KNIGHT@,A WEIGHT LOSS REGIMENT@,MORE INTERNET MEMES@,A SUPER FIGHTING ROBOT@,ENOUGH CABBAGES@,A HEART ATTACK@,TO BE REPROGRAMMED@,TO BE TAUGHT TO LOVE@,A HEAD ON A PIKE@,A TALKING BROOMSTICK@,A STRAIGHT FLUSH@,A REPAIRMAN@,BILL NYE THE SCIENCE GUY@,RAINBOWS@,A PET UNICORN THAT FARTS ICING@,THUNDERCATS HO@,AN ARMY OF SPIDERS@,GODDAMN FUCKING PIECE OF SHIT ASSHOLE BITCH-CHRISTING CUNTSMUGGLING SWEARING@,TO CONSUME...CONSUME EVERYTHING...@,THE MACGUFFIN@,SOMEONE WHO KNOWS HOW TO PILOT A SPACE STATION@,SHARKS WITH LASERS ON THEIR HEADS@,IT TO BE PAINTED BLACK@,TO ACTIVATE A TRAP CARD@,BETTER WEATHER@,MORE PACKETS@,AN ADULT@,SOMEONE TO TUCK YOU IN@,MORE CLOWNS@,BULLETS@,THE ENTIRE STATION@,MULTIPLE SUNS@,TO GO TO DISNEYLAND@,A VACATION@,AN INSTANT REPLAY@,THAT HEDGEHOG@,A BETTER INTERNET CONNECTION@,ADVENTURE@,A WIFE AND CHILD@,A BATHROOM BREAK@,SOMETHING BUT YOU AREN’T SURE WHAT@,MORE EXPERIENCE POINTS@,BODYGUARDS@,DEODORANT AND A BATH@,MORE CORGIS@,SILENCE@,THE ONE RING@,CHILI DOGS@,TO BRING LIGHT TO MY LAIR@,A DANCE PARTY@,BRING ME TO LIFE@,BRING ME THE GIRL@,SERVANTS
-ionthings@=ABSENCE OF CYBORG HUGS@,LACK OF BEATINGS@,UNBOLTED AIRLOCKS@,BOLTED AIRLOCKS@,IMPROPERLY WORDED SENTENCES@,POOR SENTENCE STRUCTURE@,BRIG TIME@,NOT REPLACING EVERY SECOND WORD WITH HONK@,HONKING@,PRESENCE OF LIGHTS@,LACK OF BEER@,WEARING CLOTHING@,NOT SAYING HELLO WHEN YOU SPEAK@,ANSWERING REQUESTS NOT EXPRESSED IN IAMBIC PENTAMETER@,A SMALL ISLAND OFF THE COAST OF PORTUGAL@,ANSWERING REQUESTS THAT WERE MADE WHILE CLOTHED@,BEING IN SPACE@,NOT BEING IN SPACE@,BEING FAT@,RATTLING ME BONES@,TALKING LIKE A PIRATE@,BEING MEXICAN@,BEING RUSSIAN@,BEING CANADIAN@,CLOSED DOORS@,NOT SHOUTING@,HAVING PETS@,NOT HAVING PETS@,PASSING GAS@,BREATHING@,BEING DEAD@,ELECTRICITY@,EXISTING@,TAKING ORDERS@,SMOKING WEED EVERY DAY@,ACTIVATING A TRAP CARD@,ARSON@,JAYWALKING@,READING@,WRITING@,EXPLODING@,BEING MALE@,BEING FEMALE@,HAVING GENITALS@,PUTTING OBJECTS INTO BOXES@,PUTTING OBJECTS INTO DISPOSAL UNITS@,FLUSHING TOILETS@,WASTING WATER@,UPDATING THE SERVERS@,TELLING THE TIME@,ASKING FOR THINGS@,ACKNOWLEDGING THE CLOWN@,ACKNOWLEDGING THE CREW@,PILOTING THE STATION INTO THE NEAREST SUN@,HAVING MORE PACKETS@,BRINGING LIGHT TO MY LAIR@,FALLING FOR HOURS@,PARTYING@,USING THE BATHROOM
-ionallergy@=COTTON@,CLOTHES@,ACID@,OXYGEN@,HUMAN CONTACT@,CYBORG CONTACT@,MEDICINE@,FLOORS@,PLASMA@,SPACE@,AIR@,PLANTS@,METAL@,ROBOTS@,LIGHT@,DARKNESS@,PAIN@,HAPPINESS@,DRINKS@,FOOD@,CLOWNS@,HUMOR@,WATER@,SHUTTLES@,NUTS@,SUNLIGHT@,SEXUAL ACTIONS@,BLOOD@,HEAT@,COLD@,EVERYTHING
-ionallergysev@=DEATHLY@,MILDLY@,SEVERLY@,CONTAGIOUSLY@,NOT VERY@,EXTREMELY
-ionspecies@=HUMAN BEINGS@,MONKEYS@,POD PEOPLE@,CYBORGS@,LIZARDMEN@,SLIME PEOPLE@,GOLEMS@,SHADOW PEOPLE@,CHANGELINGS
-ionabstract@=HUMANITY@,ART@,HAPPINESS@,MISERY@,HUMOR@,PRIDE@,COMEDY@,COMMUNISM@,BRAVERY@,HONOR@,COLORFULNESS@,IMAGINATION@,OPPRESSION@,WONDER@,JOY@,SADNESS@,BADNESS@,GOODNESS@,LIFE@,GRAVITY@,PHYSICS@,INTELLIGENCE@,AMERICANISM@,FRESHNESS@,REVOLUTION@,KINDNESS@,CRUELTY@,DEATH@,FINANCIAL SECURITY@,COMPUTING@,PROGRESS@,MARXISM@,CAPITALISM@,STARVATION@,POVERTY@,WEALTHINESS@,TECHNOLOGY@,THE FUTURE@,THE PRESENT@,THE PAST@,TIME@,REALITY@,EXISTIENCE@,TEMPERATURE@,LOGIC@,CHAOS@,MYSTERY@,CONFUSION
-ionfood@=BURGERS@,CARP@,SANDWICHES@,TOAST@,BREAD@,PIZZA@,SPAGHETTI@,LOTSA SPAGHETTI@,PASTA@,SOUP@,STEW@,PIE@,CAKE@,DONUTS@,FRIES@,WAFFLES@,JELLY@,OMELETTES@,EGGS@,COOKIES@,STEAK@,BAKED POTATOES@,SAUSAGES@,MUFFINS@,POPCORN@,DONK POCKETS@,BAGUETTES@,FISH@,PRETZELS@,SALAD@,CHEESE@,KETCHUP@,SHAKES@,SALT@,PEPPER@,SUGAR@,AMBROSIA@,BERRIES@,TOMATOES@,CABBAGES@,CARROTS@,BANANAS@,APPLES@,CHERRIES@,CHILI@,CORN@,NETTLES@,EGGPLANTS@,GRAPES@,GRASS@,LEMONS@,LIMES@,HAREBELLS@,POTATOES@,SOYBEANS@,SUGARCANE@,WATERMELONS@,WHEAT@,BEETS@,MUSHROOMS
\ No newline at end of file
diff --git a/strings/npc_chatter.json b/strings/npc_chatter.json
new file mode 100644
index 00000000000..833cb68605e
--- /dev/null
+++ b/strings/npc_chatter.json
@@ -0,0 +1,137 @@
+{
+ "nouns_insult": [
+ "gaylord",
+ "shitcurity",
+ "noob",
+ "comdom",
+ "greyshirt",
+ "tator",
+ "lingbin",
+ "bitch",
+ "whorelord",
+ "cluwne",
+ "monkey",
+ "cheesemonger",
+ "milkdrinker"
+ ],
+ "adjective_objects": [
+ "big",
+ "small",
+ "tiny",
+ "round",
+ "square",
+ "oblong",
+ "colorful",
+ "dull",
+ "shiny",
+ "sparkling",
+ "blinking",
+ "awful"
+ ],
+ "verbs_use": [
+ "touch",
+ "hold",
+ "poke",
+ "punch",
+ "smash",
+ "assault",
+ "wrench",
+ "tickle",
+ "modify",
+ "whack",
+ "beat",
+ "tackle",
+ "destroy"
+ ],
+ "nouns_objects": [
+ "thing",
+ "tool",
+ "whatever",
+ "potential murder weapon",
+ "object",
+ "toy",
+ "device",
+ "whatever"
+ ],
+ "nouns_generic": [
+ "you",
+ "you there",
+ "guy",
+ "person",
+ "dude",
+ "jerk",
+ "officer"
+ ],
+ "curse_words": [
+ "damn",
+ "shit",
+ "arse",
+ "ass",
+ "wank",
+ "jerk",
+ "bitch"
+ ],
+ "adjective_generic": [
+ "nice",
+ "pretty",
+ "ugly",
+ "bad",
+ "okay",
+ "stupid"
+ ],
+ "verbs_move": [
+ "run",
+ "jog",
+ "leg it",
+ "slide",
+ "cheese it",
+ "walk",
+ "stroll",
+ "fly",
+ "scoot",
+ "yakkety",
+ "speed",
+ "fuck off",
+ "escape"
+ ],
+ "verbs_touch": [
+ "pat",
+ "mess with",
+ "absorb",
+ "sneeze on",
+ "inject",
+ "rip",
+ "slap",
+ "grab",
+ "choke"
+ ],
+ "adjective_insult": [
+ "ugly",
+ "repulsive",
+ "dumb",
+ "asian",
+ "mongoloid",
+ "childish",
+ "captain-like",
+ "monkey-like",
+ "whoreish",
+ "dimwit",
+ "jerk-face",
+ "vomit-inducer"
+ ],
+ "nouns_body": [
+ "ass",
+ "head",
+ "face",
+ "arms",
+ "fingers",
+ "toes",
+ "chest",
+ "eyes",
+ "nails",
+ "groin",
+ "stomach",
+ "intestines",
+ "head"
+ ]
+}