Merge branch 'master' into fairylights

This commit is contained in:
Dahlular
2022-05-04 14:12:41 -06:00
111 changed files with 3251 additions and 611 deletions
@@ -181,147 +181,6 @@
else
..()
/mob/living/simple_animal/chick
name = "\improper chick"
desc = "Adorable! They make such a racket though."
icon_state = "chick"
icon_living = "chick"
icon_dead = "chick_dead"
icon_gib = "chick_gib"
gender = FEMALE
mob_biotypes = MOB_ORGANIC|MOB_BEAST
speak = list("Cherp.","Cherp?","Chirrup.","Cheep!")
speak_emote = list("cheeps")
emote_hear = list("cheeps.")
emote_see = list("pecks at the ground.","flaps its tiny wings.")
density = FALSE
speak_chance = 2
turns_per_move = 2
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 1)
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "kicks"
attacktext = "kicks"
health = 3
maxHealth = 3
ventcrawler = VENTCRAWLER_ALWAYS
var/amount_grown = 0
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
mob_size = MOB_SIZE_TINY
gold_core_spawnable = FRIENDLY_SPAWN
do_footstep = TRUE
/mob/living/simple_animal/chick/Initialize()
. = ..()
pixel_x = rand(-6, 6)
pixel_y = rand(0, 10)
/mob/living/simple_animal/chick/Life()
. =..()
if(!.)
return
if(!stat && !ckey)
amount_grown += rand(1,2)
if(amount_grown >= 100)
new /mob/living/simple_animal/chicken(src.loc)
qdel(src)
/mob/living/simple_animal/chick/holo/Life()
..()
amount_grown = 0
/mob/living/simple_animal/chicken
name = "\improper chicken"
desc = "Hopefully the eggs are good this season."
gender = FEMALE
mob_biotypes = MOB_ORGANIC|MOB_BEAST
icon_state = "chicken_brown"
icon_living = "chicken_brown"
icon_dead = "chicken_brown_dead"
speak = list("Cluck!","BWAAAAARK BWAK BWAK BWAK!","Bwaak bwak.")
speak_emote = list("clucks","croons")
emote_hear = list("clucks.")
emote_see = list("pecks at the ground.","flaps its wings viciously.")
density = FALSE
speak_chance = 2
turns_per_move = 3
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 2)
var/egg_type = /obj/item/reagent_containers/food/snacks/egg
var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "kicks"
attacktext = "kicks"
health = 15
maxHealth = 15
ventcrawler = VENTCRAWLER_ALWAYS
var/eggsleft = 0
var/eggsFertile = TRUE
var/body_color
var/icon_prefix = "chicken"
pass_flags = PASSTABLE | PASSMOB
mob_size = MOB_SIZE_SMALL
var/list/feedMessages = list("It clucks happily.","It clucks happily.")
var/list/layMessage = EGG_LAYING_MESSAGES
var/list/validColors = list("brown","black","white")
gold_core_spawnable = FRIENDLY_SPAWN
var/static/chicken_count = 0
do_footstep = TRUE
/mob/living/simple_animal/chicken/Initialize()
. = ..()
if(!body_color)
body_color = pick(validColors)
icon_state = "[icon_prefix]_[body_color]"
icon_living = "[icon_prefix]_[body_color]"
icon_dead = "[icon_prefix]_[body_color]_dead"
pixel_x = rand(-6, 6)
pixel_y = rand(0, 10)
++chicken_count
/mob/living/simple_animal/chicken/Destroy()
--chicken_count
return ..()
/mob/living/simple_animal/chicken/attackby(obj/item/O, mob/user, params)
if(istype(O, food_type)) //feedin' dem chickens
if(!stat && eggsleft < 8)
var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]"
user.visible_message(feedmsg)
qdel(O)
eggsleft += rand(1, 4)
else
to_chat(user, "<span class='warning'>[name] doesn't seem hungry!</span>")
else
..()
/mob/living/simple_animal/chicken/Life()
. =..()
if(!.)
return
if((!stat && prob(3) && eggsleft > 0) && egg_type)
visible_message("<span class='alertalien'>[src] [pick(layMessage)]</span>")
eggsleft--
var/obj/item/E = new egg_type(get_turf(src))
E.pixel_x = rand(-6,6)
E.pixel_y = rand(-6,6)
if(eggsFertile)
if(chicken_count < MAX_CHICKENS && prob(25))
START_PROCESSING(SSobj, E)
/obj/item/reagent_containers/food/snacks/egg/var/amount_grown = 0
/obj/item/reagent_containers/food/snacks/egg/process()
if(isturf(loc))
amount_grown += rand(1,2)
if(amount_grown >= 100)
visible_message("[src] hatches with a quiet cracking sound.")
new /mob/living/simple_animal/chick(get_turf(src))
STOP_PROCESSING(SSobj, src)
qdel(src)
else
STOP_PROCESSING(SSobj, src)
// Space kiwis, ergo quite a copypasta of chickens.
@@ -0,0 +1,263 @@
#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/body_color
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)
ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, INNATE_TRAIT)
access_card = new /obj/item/card/id(src)
var/datum/job/captain/C = new /datum/job/captain
access_card.access = C.get_access()
if(!body_color)
body_color = pick(list("orange","blue","purple", "green", "crystal"))
AddElement(/datum/element/mob_holder, "gremlin_[body_color]")
icon_state = "gremlin_[body_color]"
icon_living = "gremlin_[body_color]"
icon_dead = "gremlin_[body_color]_dead"
/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>"))
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(
"<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
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)
QDEL_NULL(access_card)
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("<span class='notice'>[src] crawls into the ventilation ducts!</span>")
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("<span class='notice'>[src] climbs out of the ventilation ducts!</span>")
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("<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
/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("<span class='notice'>\The [src] splits into two!</span>")
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
@@ -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("<span class='danger'>\The [L] climbs into \the [src] and turns the faucet on!</span>")
var/mob/living/simple_animal/hostile/gremlin/G = L
G.divide()
return NPC_TAMPER_ACT_NOMSG
@@ -0,0 +1,44 @@
/datum/round_event_control/gremlin
name = "Spawn Gremlins"
typepath = /datum/round_event/gremlin
weight = 15
max_occurrences = 2
earliest_start = 20 MINUTES
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/beep.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