diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 850a2b0f39..6a63ced634 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -347,3 +347,7 @@
// / Breathing types. Lungs can access either by these or by a string, which will be considered a gas ID.
#define BREATH_OXY /datum/breathing_class/oxygen
#define BREATH_PLASMA /datum/breathing_class/plasma
+
+//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
diff --git a/code/__HELPERS/markov.dm b/code/__HELPERS/markov.dm
new file mode 100644
index 0000000000..7e195f8b9c
--- /dev/null
+++ b/code/__HELPERS/markov.dm
@@ -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
diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm
index 81f99cfa69..abee5ada06 100644
--- a/code/datums/wires/_wires.dm
+++ b/code/datums/wires/_wires.dm
@@ -329,3 +329,15 @@
to_chat(L, "You need an attachable assembly!")
#undef MAXIMUM_EMP_WIRES
+
+//gremlins
+/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)
diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin.dm
new file mode 100644
index 0000000000..003afad4f2
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/gremlin/gremlin.dm
@@ -0,0 +1,253 @@
+#define GREMLIN_VENT_CHANCE 1.75
+
+//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)
+GLOBAL_LIST(bad_gremlin_items)
+
+/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 = 'icons/mob/mob.dmi'
+ icon_state = "gremlin"
+ icon_living = "gremlin"
+ icon_dead = "gremlin_dead"
+
+ var/in_vent = FALSE
+
+ health = 20
+ maxHealth = 20
+ 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,
+ /obj/structure/sink
+ )
+
+ var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent
+ var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent
+
+ 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
+
+ var/min_next_vent = 0
+
+ //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/Initialize()
+ . = ..()
+ AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS)
+ access_card = new /obj/item/card/id(src)
+ var/datum/job/captain/C = new /datum/job/captain
+ access_card.access = C.get_access()
+
+/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("[src] hungrily devours [target]!")
+ 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(
+ "\The [src] plays around with \the [M], but finds it rather boring.",
+ "\The [src] tries to think of some more ways to screw \the [M] up, but fails miserably.",
+ "\The [src] decides to ignore \the [M], and starts looking for something more fun."))
+
+ LAZYADD(GLOB.bad_gremlin_items,M.type)
+ return FALSE
+ if(NPC_TAMPER_ACT_NOMSG)
+ //Don't create a visible message
+ return TRUE
+
+ else
+ visible_message(pick(
+ "\The [src]'s eyes light up as \he tampers with \the [M].",
+ "\The [src] twists some knobs around on \the [M] and bursts into laughter!",
+ "\The [src] presses a few buttons on \the [M] and giggles mischievously.",
+ "\The [src] rubs its hands devilishly and starts messing with \the [M].",
+ "\The [src] turns a small valve on \the [M]."))
+
+ //Add a clue for detectives to find. The clue is only added if no such clue already existed on that machine
+ return TRUE
+
+/mob/living/simple_animal/hostile/gremlin/CanAttack(atom/new_target)
+ if(LAZYFIND(GLOB.bad_gremlin_items,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/death(gibbed)
+ walk(src,0)
+ return ..()
+
+/mob/living/simple_animal/hostile/gremlin/Life()
+ . = ..()
+ if(!health || stat == DEAD)
+ return
+ //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(!client) //don't do this shit if there's a client, they're capable of ventcrawling manually
+ if(in_vent)
+ target = null
+ if(entry_vent && get_dist(src, entry_vent) <= 1)
+ var/list/vents = list()
+ var/datum/pipeline/entry_vent_parent = entry_vent.parents[1]
+ for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmosmch)
+ vents += temp_vent
+ if(!vents.len)
+ entry_vent = null
+ in_vent = FALSE
+ return
+ exit_vent = pick(vents)
+ visible_message("[src] crawls into the ventilation ducts!")
+
+ loc = exit_vent
+ var/travel_time = round(get_dist(loc, exit_vent.loc) / 2)
+ addtimer(CALLBACK(src, .proc/exit_vents), travel_time) //come out at exit vent in 2 to 20 seconds
+
+
+ if(world.time > min_next_vent && !entry_vent && !in_vent && prob(GREMLIN_VENT_CHANCE)) //small chance to go into a vent
+ for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src))
+ if(!v.welded)
+ entry_vent = v
+ in_vent = TRUE
+ walk_to(src, entry_vent)
+ break
+ 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 ..()
+
+/mob/living/simple_animal/hostile/gremlin/proc/exit_vents()
+ if(!exit_vent || exit_vent.welded)
+ loc = entry_vent
+ entry_vent = null
+ return
+ loc = exit_vent.loc
+ entry_vent = null
+ exit_vent = null
+ in_vent = FALSE
+ var/area/new_area = get_area(loc)
+ message_admins("[src] came out at [new_area][ADMIN_JMP(loc)]!")
+ if(new_area)
+ new_area.Entered(src)
+ visible_message("[src] climbs out of the ventilation ducts!")
+ min_next_vent = world.time + 900 //90 seconds between ventcrawls
+
+//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("[src] hungrily devours [target]!", "You hungrily devour [target]!")
+ 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
+
+/mob/living/simple_animal/hostile/gremlin/proc/divide()
+ //Health is halved and then reduced by 2. A new gremlin is spawned with the same health as the parent
+ //Need to have at least 6 health for this, otherwise resulting health would be less than 1
+ if(health < 7.5)
+ return
+
+ visible_message("\The [src] splits into two!")
+ var/mob/living/simple_animal/hostile/gremlin/G = new /mob/living/simple_animal/hostile/gremlin(get_turf(src))
+
+ if(mind)
+ mind.transfer_to(G)
+
+ health = round(health * 0.5) - 2
+ maxHealth = health
+ resize *= 0.9
+
+ G.health = health
+ G.maxHealth = maxHealth
+
+/mob/living/simple_animal/hostile/gremlin/traitor
+ health = 85
+ maxHealth = 85
+ gold_core_spawnable = 0
diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm
new file mode 100644
index 0000000000..ff0eb13dc7
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm
@@ -0,0 +1,214 @@
+/obj/proc/npc_tamper_act(mob/living/L)
+ return NPC_TAMPER_ACT_FORGET
+
+/obj/machinery/atmospherics/components/binary/passive_gate/npc_tamper_act(mob/living/L)
+ if(prob(50)) //Turn on/off
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS)
+ else //Change pressure
+ target_pressure = rand(0, MAX_OUTPUT_PRESSURE)
+ investigate_log("was set to [target_pressure] kPa by [key_name(L)]", INVESTIGATE_ATMOS)
+ update_icon()
+
+/obj/machinery/atmospherics/components/binary/pump/npc_tamper_act(mob/living/L)
+ if(prob(50)) //Turn on/off
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS)
+ else //Change pressure
+ target_pressure = rand(0, MAX_OUTPUT_PRESSURE)
+ investigate_log("was set to [target_pressure] kPa by [key_name(L)]", INVESTIGATE_ATMOS)
+ update_icon()
+
+/obj/machinery/atmospherics/components/binary/volume_pump/npc_tamper_act(mob/living/L)
+ if(prob(50)) //Turn on/off
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS)
+ else //Change pressure
+ transfer_rate = rand(0, MAX_TRANSFER_RATE)
+ investigate_log("was set to [transfer_rate] L/s by [key_name(L)]", INVESTIGATE_ATMOS)
+ update_icon()
+
+/obj/machinery/atmospherics/components/binary/valve/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/space_heater/npc_tamper_act(mob/living/L)
+ var/list/choose_modes = list("standby", "heat", "cool")
+ if(prob(50))
+ choose_modes -= mode
+ mode = pick(choose_modes)
+ else
+ on = !on
+ update_icon()
+
+/obj/machinery/shield_gen/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/firealarm/npc_tamper_act(mob/living/L)
+ alarm()
+
+/obj/machinery/airalarm/npc_tamper_act(mob/living/L)
+ if(panel_open)
+ wires.npc_tamper(L)
+ else
+ panel_open = !panel_open
+
+/obj/machinery/ignition_switch/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/flasher_button/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/crema_switch/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/camera/npc_tamper_act(mob/living/L)
+ if(!panel_open)
+ panel_open = !panel_open
+ if(wires)
+ wires.npc_tamper(L)
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/npc_tamper_act(mob/living/L)
+ if(prob(50))
+ if(beaker)
+ beaker.forceMove(loc)
+ beaker = null
+ else
+ if(occupant)
+ if(state_open)
+ if (close_machine() == usr)
+ on = TRUE
+ else
+ open_machine()
+
+/obj/machinery/door_control/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/door/airlock/npc_tamper_act(mob/living/L)
+ //Open the firelocks as well, otherwise they block the way for our gremlin which isn't fun
+ for(var/obj/machinery/door/firedoor/F in get_turf(src))
+ if(F.density)
+ F.npc_tamper_act(L)
+
+ if(prob(40)) //40% - mess with wires
+ if(!panel_open)
+ panel_open = !panel_open
+ if(wires)
+ wires.npc_tamper(L)
+ else //60% - just open it
+ open()
+
+/obj/machinery/gibber/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/light_switch/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/turretid/npc_tamper_act(mob/living/L)
+ enabled = rand(0, 1)
+ lethal = rand(0, 1)
+ updateTurrets()
+
+/obj/machinery/vending/npc_tamper_act(mob/living/L)
+ if(!panel_open)
+ panel_open = !panel_open
+ if(wires)
+ wires.npc_tamper(L)
+
+/obj/machinery/shower/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+
+/obj/machinery/deepfryer/npc_tamper_act(mob/living/L)
+ //Deepfry a random nearby item
+ var/list/pickable_items = list()
+
+ for(var/obj/item/I in range(1, L))
+ pickable_items.Add(I)
+
+ if(!pickable_items.len)
+ return
+
+ var/obj/item/I = pick(pickable_items)
+
+ attackby(I, L) //shove the item in, even if it can't be deepfried normally
+
+/obj/machinery/power/apc/npc_tamper_act(mob/living/L)
+ if(!panel_open)
+ panel_open = !panel_open
+ if(wires)
+ wires.npc_tamper(L)
+
+/obj/machinery/power/rad_collector/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/power/emitter/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/particle_accelerator/control_box/npc_tamper_act(mob/living/L)
+ if(!panel_open)
+ panel_open = !panel_open
+ if(wires)
+ wires.npc_tamper(L)
+
+/obj/machinery/computer/communications/npc_tamper_act(mob/living/user)
+ if(!authenticated)
+ if(prob(20)) //20% chance to log in
+ authenticated = TRUE
+
+ else //Already logged in
+ if(prob(50)) //50% chance to log off
+ authenticated = FALSE
+ else if(istype(user, /mob/living/simple_animal/hostile/gremlin)) //make a hilarious public message
+ var/mob/living/simple_animal/hostile/gremlin/G = user
+ var/result = G.generate_markov_chain()
+
+ if(result)
+ if(prob(85))
+ SScommunications.make_announcement(G, FALSE, result)
+ var/turf/T = get_turf(G)
+ log_say("[key_name(usr)] ([ADMIN_JMP(T)]) has made a captain announcement: [result]")
+ message_admins("[key_name_admin(G)] has made a captain announcement.", 1)
+ else
+ if(SSshuttle.emergency.mode == SHUTTLE_IDLE)
+ SSshuttle.requestEvac(G, result)
+ else if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE)
+ SSshuttle.cancelEvac(G)
+
+/obj/machinery/button/door/npc_tamper_act(mob/living/L)
+ attack_hand(L)
+
+/obj/machinery/sleeper/npc_tamper_act(mob/living/L)
+ if(prob(75))
+ inject_chem(pick(available_chems))
+ else
+ if(state_open)
+ close_machine()
+ else
+ open_machine()
+
+/obj/machinery/power/smes/npc_tamper_act(mob/living/L)
+ if(prob(50)) //mess with input
+ input_level = rand(0, input_level_max)
+ else //mess with output
+ output_level = rand(0, output_level_max)
+
+/obj/machinery/syndicatebomb/npc_tamper_act(mob/living/L) //suicide bomber gremlins
+ if(!open_panel)
+ open_panel = !open_panel
+ if(wires)
+ wires.npc_tamper(L)
+
+/obj/machinery/computer/bank_machine/npc_tamper_act(mob/living/L)
+ siphoning = !siphoning
+
+/obj/machinery/computer/slot_machine/npc_tamper_act(mob/living/L)
+ spin(L)
+
+/obj/structure/sink/npc_tamper_act(mob/living/L)
+ if(istype(L, /mob/living/simple_animal/hostile/gremlin))
+ visible_message("\The [L] climbs into \the [src] and turns the faucet on!")
+
+ var/mob/living/simple_animal/hostile/gremlin/G = L
+ G.divide()
+
+ return NPC_TAMPER_ACT_NOMSG
diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm
new file mode 100644
index 0000000000..20817e5686
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm
@@ -0,0 +1,44 @@
+/datum/round_event_control/gremlin
+ name = "Spawn Gremlins"
+ typepath = /datum/round_event/gremlin
+ weight = 15
+ max_occurrences = 2
+ earliest_start = 12000 //Meant to mix things up early-game.
+ 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/announcer/classic/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
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index 3137e8ac26..65efde8697 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 3a0b6b6250..941a00e201 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -178,6 +178,7 @@
#include "code\__HELPERS\icon_smoothing.dm"
#include "code\__HELPERS\icons.dm"
#include "code\__HELPERS\level_traits.dm"
+#include "code\__HELPERS\markov.dm"
#include "code\__HELPERS\matrices.dm"
#include "code\__HELPERS\mobs.dm"
#include "code\__HELPERS\mouse_control.dm"
@@ -2787,6 +2788,9 @@
#include "code\modules\mob\living\simple_animal\friendly\drone\say.dm"
#include "code\modules\mob\living\simple_animal\friendly\drone\verbs.dm"
#include "code\modules\mob\living\simple_animal\friendly\drone\visuals_icons.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\gremlin\gremlin_event.dm"
#include "code\modules\mob\living\simple_animal\guardian\guardian.dm"
#include "code\modules\mob\living\simple_animal\guardian\types\assassin.dm"
#include "code\modules\mob\living\simple_animal\guardian\types\charger.dm"