From 643aff0c287eed54913b0b308a6e3f7401c4ebd1 Mon Sep 17 00:00:00 2001 From: Joker66613 Date: Thu, 29 Feb 2024 15:59:49 +0100 Subject: [PATCH] Gremliny --- aquila/aquila.dm | 5 + aquila/code/__DEFINES/mob.dm | 3 + aquila/code/__HELPERS/markov.dm | 61 ++++++ aquila/code/datums/wires/wires.dm | 10 + .../mob/living/simple_animal/gremlin/event.dm | 43 ++++ .../living/simple_animal/gremlin/gremlin.dm | 165 ++++++++++++++ .../simple_animal/gremlin/gremlin_act.dm | 205 ++++++++++++++++++ aquila/icons/mob/mob.dmi | Bin 3391 -> 2619 bytes 8 files changed, 492 insertions(+) create mode 100644 aquila/code/__DEFINES/mob.dm create mode 100644 aquila/code/__HELPERS/markov.dm create mode 100644 aquila/code/datums/wires/wires.dm create mode 100644 aquila/code/modules/mob/living/simple_animal/gremlin/event.dm create mode 100644 aquila/code/modules/mob/living/simple_animal/gremlin/gremlin.dm create mode 100644 aquila/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm diff --git a/aquila/aquila.dm b/aquila/aquila.dm index 31595aaefdc..719a6ee8c0a 100644 --- a/aquila/aquila.dm +++ b/aquila/aquila.dm @@ -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" @@ -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" @@ -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" diff --git a/aquila/code/__DEFINES/mob.dm b/aquila/code/__DEFINES/mob.dm new file mode 100644 index 00000000000..77070cd59a6 --- /dev/null +++ b/aquila/code/__DEFINES/mob.dm @@ -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 diff --git a/aquila/code/__HELPERS/markov.dm b/aquila/code/__HELPERS/markov.dm new file mode 100644 index 00000000000..d306d75ab7d --- /dev/null +++ b/aquila/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 \ No newline at end of file diff --git a/aquila/code/datums/wires/wires.dm b/aquila/code/datums/wires/wires.dm new file mode 100644 index 00000000000..d3983cd56ab --- /dev/null +++ b/aquila/code/datums/wires/wires.dm @@ -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) diff --git a/aquila/code/modules/mob/living/simple_animal/gremlin/event.dm b/aquila/code/modules/mob/living/simple_animal/gremlin/event.dm new file mode 100644 index 00000000000..23857033883 --- /dev/null +++ b/aquila/code/modules/mob/living/simple_animal/gremlin/event.dm @@ -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 \ No newline at end of file diff --git a/aquila/code/modules/mob/living/simple_animal/gremlin/gremlin.dm b/aquila/code/modules/mob/living/simple_animal/gremlin/gremlin.dm new file mode 100644 index 00000000000..7758c8cd91b --- /dev/null +++ b/aquila/code/modules/mob/living/simple_animal/gremlin/gremlin.dm @@ -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("[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.")) + + 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( + "\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 + 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("[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 \ No newline at end of file diff --git a/aquila/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm b/aquila/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm new file mode 100644 index 00000000000..69e9633407b --- /dev/null +++ b/aquila/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm @@ -0,0 +1,205 @@ +/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/cooking/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) \ No newline at end of file diff --git a/aquila/icons/mob/mob.dmi b/aquila/icons/mob/mob.dmi index d243f9abd65a36a84b1b295a57c425613d42c502..e01baff996f93d54079b33227cc79506ffe12044 100644 GIT binary patch delta 2525 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 delta 3303 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