Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions aquila/aquila.dm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "code\__DEFINES\wires.dm"
#include "code\__DEFINES\antagonists.dm"
#include "code\__DEFINES\is_helpers.dm"
#include "code\__HELPERS\markov.dm"
#include "code\__HELPERS\names.dm"
#include "code\_onclick\hud\alert.dm"
#include "code\controllers\configuration\entries\game_options.dm"
Expand All @@ -36,6 +37,7 @@
#include "code\datums\mutations\actions.dm"
#include "code\datums\mutations\body.dm"
#include "code\datums\saymode.dm"
#include "code\datums\wires\wires.dm"
#include "code\game\atoms_movable.dm"
#include "code\game\atoms.dm"
#include "code\game\dynamic\dynamic_rulesets_roundstart.dm"
Expand Down Expand Up @@ -172,6 +174,9 @@
#include "code\modules\mob\living\simple_animal\friendly\gondola.dm"
#include "code\modules\mob\living\simple_animal\friendly\mouse.dm"
#include "code\modules\mob\living\simple_animal\friendly\snake.dm"
#include "code\modules\mob\living\simple_animal\gremlin\event.dm"
#include "code\modules\mob\living\simple_animal\gremlin\gremlin.dm"
#include "code\modules\mob\living\simple_animal\gremlin\gremlin_act.dm"
#include "code\modules\mob\living\simple_animal\hostile\alien.dm"
#include "code\modules\mob\living\simple_animal\hostile\carp.dm"
#include "code\modules\mob\living\simple_animal\hostile\gorilla\gorilla.dm"
Expand Down
3 changes: 3 additions & 0 deletions aquila/code/__DEFINES/mob.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//Gremlins
#define NPC_TAMPER_ACT_FORGET 1 //Don't try to tamper with this again
#define NPC_TAMPER_ACT_NOMSG 2 //Don't produce a visible message
61 changes: 61 additions & 0 deletions aquila/code/__HELPERS/markov.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#define MAXIMUM_MARKOV_LENGTH 25000

/proc/markov_chain(var/text, var/order = 4, var/length = 250)
if(!text || order < 0 || order > 20 || length < 1 || length > MAXIMUM_MARKOV_LENGTH)
return

var/table = markov_table(text, order)
var/markov = markov_text(length, table, order)
return markov

/proc/markov_table(var/text, var/look_forward = 4)
if(!text)
return
var/list/table = list()

for(var/i = 1, i <= length(text), i++)
var/char = copytext(text, i, look_forward+i)
if(!table[char])
table[char] = list()

for(var/i = 1, i <= (length(text) - look_forward), i++)
var/char_index = copytext(text, i, look_forward+i)
var/char_count = copytext(text, i+look_forward, (look_forward*2)+i)

if(table[char_index][char_count])
table[char_index][char_count]++
else
table[char_index][char_count] = 1

return table

/proc/markov_text(var/length = 250, var/table, var/look_forward = 4)
if(!table)
return
var/char = pick(table)
var/o = char

for(var/i = 0, i <= (length / look_forward), i++)
var/newchar = markov_weighted_char(table[char])

if(newchar)
char = newchar
o += "[newchar]"
else
char = pick(table)

return o

/proc/markov_weighted_char(var/list/array)
if(!array || !array.len)
return

var/total = 0
for(var/i in array)
total += array[i]
var/r = rand(1, total)
for(var/i in array)
var/weight = array[i]
if(r <= weight)
return i
r -= weight
10 changes: 10 additions & 0 deletions aquila/code/datums/wires/wires.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/datum/wires/proc/npc_tamper(mob/living/L)
if(!wires.len)
return

var/wire_to_screw = pick(wires)

if(is_color_cut(wire_to_screw) || prob(50)) //CutWireColour() proc handles both cutting and mending wires. If the wire is already cut, always mend it back. Otherwise, 50% to cut it and 50% to pulse it
cut(wire_to_screw)
else
pulse(wire_to_screw, L)
43 changes: 43 additions & 0 deletions aquila/code/modules/mob/living/simple_animal/gremlin/event.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/datum/round_event_control/gremlin
name = "Spawn Gremlins"
typepath = /datum/round_event/gremlin
weight = 10
max_occurrences = 2
min_players = 5



/datum/round_event/gremlin
var/static/list/acceptable_spawns = list("xeno_spawn", "generic event spawn", "blobstart", "Assistant")

/datum/round_event/gremlin/announce()
priority_announce("Bioscans indicate that some gremlins entered through the vents. Deal with them!", "Gremlin Alert", 'sound/ai/attention.ogg')

/datum/round_event/gremlin/start()

var/list/spawn_locs = list()

for(var/obj/effect/landmark/L in GLOB.landmarks_list)
if(isturf(L.loc) && !isspaceturf(L.loc))
if(L.name in acceptable_spawns)
spawn_locs += L.loc
if(!spawn_locs.len) //If we can't find any gremlin spawns, try the xeno spawns
for(var/obj/effect/landmark/L in GLOB.landmarks_list)
if(isturf(L.loc))
switch(L.name)
if("Assistant")
spawn_locs += L.loc
if(!spawn_locs.len) //If we can't find THAT, then just give up and cry
return MAP_ERROR

var/gremlins_to_spawn = rand(2,5)
var/list/gremlin_areas = list()
for(var/i = 0, i <= gremlins_to_spawn, i++)
var/spawnat = pick(spawn_locs)
spawn_locs -= spawnat
gremlin_areas += get_area(spawnat)
new /mob/living/simple_animal/hostile/gremlin(spawnat)
var/grems = gremlin_areas.Join(", ")
message_admins("Gremlins have been spawned at the areas: [grems]")
log_game("Gremlins have been spawned at the areas: [grems]")
return SUCCESSFUL_SPAWN
165 changes: 165 additions & 0 deletions aquila/code/modules/mob/living/simple_animal/gremlin/gremlin.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//Gremlins
//Small monsters that don't attack humans or other animals. Instead they mess with electronics, computers and machinery

//List of objects that gremlins can't tamper with (because nobody coded an interaction for it)
//List starts out empty. Whenever a gremlin finds a machine that it couldn't tamper with, the machine's type is added here, and all machines of such type are ignored from then on (NOT SUBTYPES)
var/list/bad_gremlin_items = list()

/mob/living/simple_animal/hostile/gremlin
name = "gremlin"
desc = "This tiny creature finds great joy in discovering and using technology. Nothing excites it more than pushing random buttons on a computer to see what it might do."
icon = 'hippiestation/icons/mob/mob.dmi'
icon_state = "gremlin"
icon_living = "gremlin"
icon_dead = "gremlin_dead"

health = 18
maxHealth = 18
search_objects = 3 //Completely ignore mobs

//Tampering is handled by the 'npc_tamper()' obj proc
wanted_objects = list(
/obj/machinery,
/obj/item/reagent_containers/food
)

dextrous = TRUE
possible_a_intents = list(INTENT_HELP, INTENT_GRAB, INTENT_DISARM, INTENT_HARM)
faction = list("meme", "gremlin")
speed = 0.5
gold_core_spawnable = 2
unique_name = TRUE

//Ensure gremlins don't attack other mobs
melee_damage_upper = 0
melee_damage_lower = 0
attack_sound = null
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE

//List of objects that we don't even want to try to tamper with
//Subtypes of these are calculated too
var/list/unwanted_objects = list(/obj/machinery/atmospherics/pipe, /turf, /obj/structure) //ensure gremlins dont try to fuck with walls / normal pipes / glass / etc

//Amount of ticks spent pathing to the target. If it gets above a certain amount, assume that the target is unreachable and stop
var/time_chasing_target = 0

//If you're going to make gremlins slower, increase this value - otherwise gremlins will abandon their targets too early
var/max_time_chasing_target = 2

var/next_eat = 0

//Last 20 heard messages are remembered by gremlins, and will be used to generate messages for comms console tampering, etc...
var/list/hear_memory = list()
var/const/max_hear_memory = 20

/mob/living/simple_animal/hostile/gremlin/AttackingTarget()
var/is_hungry = world.time >= next_eat || prob(25)
if(istype(target, /obj/item/reagent_containers/food) && is_hungry) //eat food if we're hungry or bored
visible_message("<span class='danger'>[src] hungrily devours [target]!</span>")
playsound(src, "sound/items/eatfood.ogg", 50, 1)
qdel(target)
LoseTarget()
next_eat = world.time + rand(700, 3000) //anywhere from 70 seconds to 5 minutes until the gremlin is hungry again
return
if(istype(target, /obj))
var/obj/M = target
tamper(M)
if(prob(50)) //50% chance to move to the next machine
LoseTarget()

/mob/living/simple_animal/hostile/gremlin/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode)
. = ..()
if(message)
hear_memory.Insert(1, raw_message)
if(hear_memory.len > max_hear_memory)
hear_memory.Cut(hear_memory.len)

/mob/living/simple_animal/hostile/gremlin/proc/generate_markov_input()
var/result = ""

for(var/memory in hear_memory)
result += memory + " "

return result

/mob/living/simple_animal/hostile/gremlin/proc/generate_markov_chain()
return markov_chain(generate_markov_input(), rand(2,5), rand(100,700)) //The numbers are chosen arbitarily

/mob/living/simple_animal/hostile/gremlin/proc/tamper(obj/M)
switch(M.npc_tamper_act(src))
if(NPC_TAMPER_ACT_FORGET)
visible_message(pick(
"<span class='notice'>\The [src] plays around with \the [M], but finds it rather boring.</span>",
"<span class='notice'>\The [src] tries to think of some more ways to screw \the [M] up, but fails miserably.</span>",
"<span class='notice'>\The [src] decides to ignore \the [M], and starts looking for something more fun.</span>"))

bad_gremlin_items.Add(M.type)
return FALSE
if(NPC_TAMPER_ACT_NOMSG)
//Don't create a visible message
M.suit_fibers += "Hairs from a gremlin."
return TRUE

else
visible_message(pick(
"<span class='danger'>\The [src]'s eyes light up as \he tampers with \the [M].</span>",
"<span class='danger'>\The [src] twists some knobs around on \the [M] and bursts into laughter!</span>",
"<span class='danger'>\The [src] presses a few buttons on \the [M] and giggles mischievously.</span>",
"<span class='danger'>\The [src] rubs its hands devilishly and starts messing with \the [M].</span>",
"<span class='danger'>\The [src] turns a small valve on \the [M].</span>"))

//Add a clue for detectives to find. The clue is only added if no such clue already existed on that machine
M.suit_fibers += "Hairs from a gremlin."
return TRUE

/mob/living/simple_animal/hostile/gremlin/CanAttack(atom/new_target)
if(bad_gremlin_items.Find(new_target.type))
return FALSE
if(is_type_in_list(new_target, unwanted_objects))
return FALSE
if(istype(new_target, /obj/machinery))
var/obj/machinery/M = new_target
if(M.stat) //Unpowered or broken
return FALSE
else if(istype(new_target, /obj/machinery/door/firedoor))
var/obj/machinery/door/firedoor/F = new_target
//Only tamper with firelocks that are closed, opening them!
if(!F.density)
return FALSE

return ..()

/mob/living/simple_animal/hostile/gremlin/Life()
//Don't try to path to one target for too long. If it takes longer than a certain amount of time, assume it can't be reached and find a new one
if(!target)
time_chasing_target = 0
else
if(++time_chasing_target > max_time_chasing_target)
LoseTarget()
time_chasing_target = 0

. = ..()

/mob/living/simple_animal/hostile/gremlin/EscapeConfinement()
if(istype(loc, /obj) && CanAttack(loc)) //If we're inside a machine, screw with it
var/obj/M = loc
tamper(M)

return ..()

//This allows player-controlled gremlins to tamper with machinery
/mob/living/simple_animal/hostile/gremlin/UnarmedAttack(var/atom/A)
if(istype(A, /obj/machinery) || istype(A, /obj/structure))
tamper(A)
if(istype(target, /obj/item/reagent_containers/food)) //eat food
visible_message("<span class='danger'>[src] hungrily devours [target]!</span>", "<span class='danger'>You hungrily devour [target]!</span>")
playsound(src, "sound/items/eatfood.ogg", 50, 1)
qdel(target)
LoseTarget()
next_eat = world.time + rand(700, 3000) //anywhere from 70 seconds to 5 minutes until the gremlin is hungry again

return ..()

/mob/living/simple_animal/hostile/gremlin/IsAdvancedToolUser()
return 1
Loading