Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into ninjasarecum
# Conflicts: # tgstation.dme
This commit is contained in:
@@ -1058,3 +1058,58 @@
|
||||
/datum/sprite_accessory/hair/zone
|
||||
name = "Zone"
|
||||
icon_state = "hair_zone"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient
|
||||
icon = 'icons/mob/hair_gradients.dmi'
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/none
|
||||
name = "None"
|
||||
icon_state = "none"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/fadeup
|
||||
name = "Fade Up"
|
||||
icon_state = "fadeup"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/fadedown
|
||||
name = "Fade Down"
|
||||
icon_state = "fadedown"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/vertical_split
|
||||
name = "Vertical Split"
|
||||
icon_state = "vsplit"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/_split
|
||||
name = "Horizontal Split"
|
||||
icon_state = "bottomflat"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/reflected
|
||||
name = "Reflected"
|
||||
icon_state = "reflected_high"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/reflected_inverse
|
||||
name = "Reflected Inverse"
|
||||
icon_state = "reflected_inverse_high"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/wavy
|
||||
name = "Wavy"
|
||||
icon_state = "wavy"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/long_fade_up
|
||||
name = "Long Fade Up"
|
||||
icon_state = "long_fade_up"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/long_fade_down
|
||||
name = "Long Fade Down"
|
||||
icon_state = "long_fade_down"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/short_fade_up
|
||||
name = "Short Fade Up"
|
||||
icon_state = "short_fade_up"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/short_fade_down
|
||||
name = "Short Fade Down"
|
||||
icon_state = "short_fade_down"
|
||||
|
||||
/datum/sprite_accessory/hair_gradient/wavy_spike
|
||||
name = "Spiked Wavy"
|
||||
icon_state = "wavy_spiked"
|
||||
|
||||
@@ -38,14 +38,14 @@
|
||||
icon = 'modular_citadel/icons/mob/synthliz_body_markings.dmi'
|
||||
name = "Synthetic Lizard - Pecs Light"
|
||||
icon_state = "synthlizpecslight"
|
||||
covered_limbs = list("Chest" = MATRIX_GREEN_BLUE)
|
||||
covered_limbs = list("Chest" = MATRIX_GREEN_BLUE, "Left Arm" = MATRIX_BLUE, "Right Arm" = MATRIX_BLUE, "Left Leg" = MATRIX_GREEN, "Right Leg" = MATRIX_GREEN)
|
||||
|
||||
/datum/sprite_accessory/mam_body_markings/synthliz
|
||||
recommended_species = list("synthliz")
|
||||
icon = 'modular_citadel/icons/mob/synthliz_body_markings.dmi'
|
||||
name = "Synthetic Lizard - Plates"
|
||||
icon_state = "synthlizscutes"
|
||||
covered_limbs = list("Chest" = MATRIX_GREEN)
|
||||
covered_limbs = list("Chest" = MATRIX_GREEN, "Left Leg" = MATRIX_GREEN, "Right Leg" = MATRIX_GREEN)
|
||||
|
||||
//Synth tails
|
||||
/datum/sprite_accessory/tails/mam_tails/synthliz
|
||||
|
||||
@@ -542,6 +542,20 @@
|
||||
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
|
||||
matrixed_sections = MATRIX_RED_GREEN
|
||||
|
||||
/datum/sprite_accessory/tails/human/takahiro_kitsune
|
||||
name = "Takahiro Kitsune Tails" //takahiro had five tails i just wanted to follow the 'T' naming convention vs. tamamo and triple
|
||||
icon_state = "7sune"
|
||||
color_src = MATRIXED
|
||||
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
|
||||
matrixed_sections = MATRIX_RED_GREEN
|
||||
|
||||
/datum/sprite_accessory/tails_animated/human/takahiro_kitsune
|
||||
name = "Takahiro Kitsune Tails"
|
||||
icon_state = "7sune"
|
||||
color_src = MATRIXED
|
||||
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
|
||||
matrixed_sections = MATRIX_RED_GREEN
|
||||
|
||||
/datum/sprite_accessory/tails/human/tentacle
|
||||
name = "Tentacle"
|
||||
icon_state = "tentacle"
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
if (isturf(T))
|
||||
update_z(T.z)
|
||||
|
||||
update_icon(preferred_form)
|
||||
update_icon(null, preferred_form)
|
||||
updateghostimages()
|
||||
|
||||
@@ -176,7 +176,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
|
||||
* Hair will always update its dir, so if your sprite has no dirs the haircut will go all over the place.
|
||||
* |- Ricotez
|
||||
*/
|
||||
/mob/dead/observer/update_icon(new_form)
|
||||
/mob/dead/observer/update_icon(updates=ALL, new_form=null)
|
||||
. = ..()
|
||||
if(client) //We update our preferences in case they changed right before update_icon was called.
|
||||
ghost_accs = client.prefs.ghost_accs
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
var/hair_color = "000"
|
||||
var/hair_style = "Bald"
|
||||
|
||||
///Colour used for the hair gradient.
|
||||
var/grad_color = "000"
|
||||
///Style used for the hair gradient.
|
||||
var/grad_style
|
||||
|
||||
//Facial hair colour and style
|
||||
var/facial_hair_color = "000"
|
||||
var/facial_hair_style = "Shaved"
|
||||
@@ -81,6 +86,8 @@
|
||||
|
||||
tooltips = TRUE
|
||||
|
||||
var/additional_language //the additional language this human can speak from their preference selection
|
||||
|
||||
/// Unarmed parry data for human
|
||||
/datum/block_parry_data/unarmed/human
|
||||
parry_respect_clickdelay = TRUE
|
||||
|
||||
@@ -52,6 +52,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
var/hair_color
|
||||
///The alpha used by the hair. 255 is completely solid, 0 is invisible.
|
||||
var/hair_alpha = 255
|
||||
///The gradient style used for the mob's hair.
|
||||
var/grad_style
|
||||
///The gradient color used to color the gradient.
|
||||
var/grad_color
|
||||
|
||||
///Does the species use skintones or not? As of now only used by humans.
|
||||
var/use_skintones = FALSE
|
||||
@@ -678,6 +682,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
|
||||
if(!hair_hidden || dynamic_hair_suffix)
|
||||
var/mutable_appearance/hair_overlay = mutable_appearance(layer = -HAIR_LAYER)
|
||||
var/mutable_appearance/gradient_overlay = mutable_appearance(layer = -HAIR_LAYER)
|
||||
if(!hair_hidden && !H.getorgan(/obj/item/organ/brain)) //Applies the debrained overlay if there is no brain
|
||||
if(!(NOBLOOD in species_traits))
|
||||
hair_overlay.icon = 'icons/mob/human_parts.dmi'
|
||||
@@ -713,8 +718,21 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
hair_overlay.color = "#" + hair_color
|
||||
else
|
||||
hair_overlay.color = "#" + H.hair_color
|
||||
|
||||
//Gradients
|
||||
grad_style = H.grad_style
|
||||
grad_color = H.grad_color
|
||||
if(grad_style)
|
||||
var/datum/sprite_accessory/gradient = GLOB.hair_gradients_list[grad_style]
|
||||
var/icon/temp = icon(gradient.icon, gradient.icon_state)
|
||||
var/icon/temp_hair = icon(hair_file, hair_state)
|
||||
temp.Blend(temp_hair, ICON_ADD)
|
||||
gradient_overlay.icon = temp
|
||||
gradient_overlay.color = "#" + grad_color
|
||||
|
||||
else
|
||||
hair_overlay.color = forced_colour
|
||||
|
||||
hair_overlay.alpha = hair_alpha
|
||||
|
||||
if(OFFSET_HAIR in H.dna.species.offset_features)
|
||||
@@ -723,6 +741,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
|
||||
if(hair_overlay.icon)
|
||||
standing += hair_overlay
|
||||
standing += gradient_overlay
|
||||
|
||||
if(standing.len)
|
||||
H.overlays_standing[HAIR_LAYER] = standing
|
||||
@@ -1980,7 +1999,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if(CHECK_MOBILITY(target, MOBILITY_STAND))
|
||||
target.adjustStaminaLoss(5)
|
||||
else
|
||||
target.adjustStaminaLoss(target.getStaminaLoss() > 75? 5 : 75)
|
||||
target.adjustStaminaLoss(IS_STAMCRIT(target)? 2 : 10)
|
||||
|
||||
if(target.is_shove_knockdown_blocked())
|
||||
return
|
||||
|
||||
@@ -807,11 +807,11 @@
|
||||
|
||||
/datum/species/golem/plastic/on_species_gain(mob/living/carbon/C, datum/species/old_species)
|
||||
. = ..()
|
||||
C.AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS)
|
||||
C.AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_NUDE)
|
||||
|
||||
/datum/species/golem/plastic/on_species_loss(mob/living/carbon/C)
|
||||
. = ..()
|
||||
C.RemoveElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS)
|
||||
C.RemoveElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_NUDE)
|
||||
|
||||
/datum/species/golem/bronze
|
||||
name = "Bronze Golem"
|
||||
|
||||
@@ -103,13 +103,16 @@
|
||||
var/_method = override[thing]
|
||||
if(_method == ITEM_PARRY)
|
||||
using_item = thing
|
||||
tool = using_item
|
||||
method = ITEM_PARRY
|
||||
data = using_item.block_parry_data
|
||||
else if(_method == UNARMED_PARRY)
|
||||
method = UNARMED_PARRY
|
||||
tool = src
|
||||
data = thing
|
||||
if(!using_item && !method && length(other_items))
|
||||
using_item = other_items[1]
|
||||
tool = using_item
|
||||
method = ITEM_PARRY
|
||||
data = using_item.block_parry_data
|
||||
if(!method)
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
throwpower = I.throwforce
|
||||
var/impacting_zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest
|
||||
var/list/block_return = list()
|
||||
var/total_damage = I.throwforce
|
||||
var/total_damage = AM.throwforce
|
||||
if(mob_run_block(AM, throwpower, "\the [AM.name]", ATTACK_TYPE_THROWN, 0, throwingdatum?.thrower, impacting_zone, block_return) & BLOCK_SUCCESS)
|
||||
hitpush = FALSE
|
||||
skipcatch = TRUE
|
||||
|
||||
@@ -322,6 +322,17 @@
|
||||
else
|
||||
to_chat(user, "Encryption Key ports not configured.")
|
||||
|
||||
/obj/item/paicard/emag_act(mob/user) // Emag to wipe the master DNA and supplemental directive
|
||||
. = ..()
|
||||
if(!pai)
|
||||
return
|
||||
to_chat(user, "<span class='notice'>You override [pai]'s directive system, clearing its master string and supplied directive.</span>")
|
||||
to_chat(pai, "<span class='danger'>Warning: System override detected, check directive sub-system for any changes.'</span>")
|
||||
log_game("[key_name(user)] emagged [key_name(pai)], wiping their master DNA and supplemental directive.")
|
||||
pai.master = null
|
||||
pai.master_dna = null
|
||||
pai.laws.supplied[1] = "None." // Sets supplemental directive to this
|
||||
|
||||
/mob/living/silicon/pai/proc/short_radio()
|
||||
if(radio_short_timerid)
|
||||
deltimer(radio_short_timerid)
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
|
||||
/**
|
||||
* Returns the thing in our active hand (whatever is in our active module-slot, in this case)
|
||||
*
|
||||
* Arguments
|
||||
* * get_gripper - If the active module is a gripper, should we return the gripper or the contained item? (if the gripper contains nothing, returns the gripper anyways)
|
||||
*/
|
||||
/mob/living/silicon/robot/get_active_held_item()
|
||||
/mob/living/silicon/robot/get_active_held_item(get_gripper = FALSE)
|
||||
var/item = module_active
|
||||
// snowflake handler for the gripper
|
||||
if(istype(item, /obj/item/weapon/gripper))
|
||||
var/obj/item/weapon/gripper/G = item
|
||||
if(istype(item, /obj/item/gripper) && !get_gripper)
|
||||
var/obj/item/gripper/G = item
|
||||
if(G.wrapped)
|
||||
if(G.wrapped.loc != G)
|
||||
G.wrapped = null
|
||||
@@ -284,9 +287,14 @@
|
||||
|
||||
/**
|
||||
* Unequips the active held item, if there is one.
|
||||
*
|
||||
* Will always consider dropping gripper contents first.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/uneq_active()
|
||||
if(module_active)
|
||||
var/obj/item/gripper/gripper = get_active_held_item(TRUE)
|
||||
if(istype(gripper) && gripper.drop_held())
|
||||
return
|
||||
unequip_module_from_slot(module_active, get_selected_module())
|
||||
|
||||
/**
|
||||
@@ -302,11 +310,12 @@
|
||||
* Checks if the item is currently in a slot.
|
||||
*
|
||||
* If the item is found in a slot, this returns TRUE. Otherwise, it returns FALSE
|
||||
* Modified to accept items inside of grippers, used for `code\modules\tgui\states\hands.dm:27`
|
||||
* Arguments
|
||||
* * item_module - the item being checked
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/activated(obj/item/item_module)
|
||||
if(item_module in held_items)
|
||||
if(get_active_held_item() == item_module || (item_module in held_items))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@
|
||||
/obj/item/crowbar/cyborg,
|
||||
/obj/item/healthanalyzer,
|
||||
/obj/item/reagent_containers/borghypo,
|
||||
/obj/item/weapon/gripper/medical,
|
||||
/obj/item/gripper/medical,
|
||||
/obj/item/reagent_containers/dropper,
|
||||
/obj/item/reagent_containers/syringe,
|
||||
/obj/item/surgical_drapes,
|
||||
@@ -448,7 +448,7 @@
|
||||
/obj/item/analyzer,
|
||||
/obj/item/storage/part_replacer/cyborg,
|
||||
/obj/item/holosign_creator/combifan,
|
||||
/obj/item/weapon/gripper,
|
||||
/obj/item/gripper,
|
||||
/obj/item/lightreplacer/cyborg,
|
||||
/obj/item/geiger_counter/cyborg,
|
||||
/obj/item/assembly/signaler/cyborg,
|
||||
@@ -923,7 +923,7 @@
|
||||
/obj/item/gun/energy/kinetic_accelerator/cyborg,
|
||||
/obj/item/gun/energy/plasmacutter/cyborg,
|
||||
/obj/item/gps/cyborg,
|
||||
/obj/item/weapon/gripper/mining,
|
||||
/obj/item/gripper/mining,
|
||||
/obj/item/cyborg_clamp,
|
||||
/obj/item/stack/marker_beacon,
|
||||
/obj/item/destTagger,
|
||||
@@ -1075,7 +1075,7 @@
|
||||
/obj/item/multitool/cyborg,
|
||||
/obj/item/storage/part_replacer/cyborg,
|
||||
/obj/item/holosign_creator/atmos,
|
||||
/obj/item/weapon/gripper,
|
||||
/obj/item/gripper,
|
||||
/obj/item/lightreplacer/cyborg,
|
||||
/obj/item/stack/sheet/metal/cyborg,
|
||||
/obj/item/stack/sheet/glass/cyborg,
|
||||
|
||||
@@ -98,6 +98,18 @@
|
||||
qdel(bigcheese)
|
||||
evolve()
|
||||
return
|
||||
for(var/obj/item/trash/garbage in range(1, src))
|
||||
if(is_station_level(z))
|
||||
if(prob(2))
|
||||
qdel(garbage)
|
||||
evolve_plague()
|
||||
return
|
||||
for(var/obj/effect/decal/cleanable/blood/gibs/leftovers in range(1, src))
|
||||
if(is_station_level(z))
|
||||
if(prob(2))
|
||||
qdel(leftovers)
|
||||
evolve_plague()
|
||||
return
|
||||
|
||||
/**
|
||||
*Checks the mouse cap, if it's above the cap, doesn't spawn a mouse. If below, spawns a mouse and adds it to cheeserats.
|
||||
@@ -123,6 +135,17 @@
|
||||
mind.transfer_to(regalrat)
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_animal/mouse/proc/evolve_plague()
|
||||
return
|
||||
|
||||
/*
|
||||
var/mob/living/simple_animal/hostile/plaguerat = new /mob/living/simple_animal/hostile/plaguerat(loc)
|
||||
visible_message("<span class='warning'>[src] devours the food! He rots into something worse!</span>")
|
||||
if(mind)
|
||||
mind.transfer_to(plaguerat)
|
||||
qdel(src)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Mouse types
|
||||
*/
|
||||
@@ -169,3 +192,17 @@ GLOBAL_VAR(tom_existed)
|
||||
/obj/item/reagent_containers/food/snacks/deadmouse/on_grind()
|
||||
reagents.clear_reagents()
|
||||
|
||||
/mob/living/simple_animal/mouse/proc/miasma(datum/gas_mixture/environment, check_temp = FALSE)
|
||||
if(isturf(src.loc) && isopenturf(src.loc))
|
||||
var/turf/open/ST = src.loc
|
||||
var/miasma_moles = ST.air.get_moles(GAS_MIASMA)
|
||||
if(prob(5) && miasma_moles >= 5)
|
||||
evolve_plague()
|
||||
else if(miasma_moles >= 20)
|
||||
evolve_plague()
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/mouse/handle_environment(datum/gas_mixture/environment)
|
||||
. = ..()
|
||||
if(is_station_level(z))
|
||||
miasma()
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
#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("<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/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
|
||||
@@ -60,6 +60,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
|
||||
var/magic_fluff_string = "<span class='holoparasite'>You draw the Coder, symbolizing bugs and errors. This shouldn't happen! Submit a bug report!</span>"
|
||||
var/tech_fluff_string = "<span class='holoparasite'>BOOT SEQUENCE COMPLETE. ERROR MODULE LOADED. THIS SHOULDN'T HAPPEN. Submit a bug report!</span>"
|
||||
var/carp_fluff_string = "<span class='holoparasite'>CARP CARP CARP SOME SORT OF HORRIFIC BUG BLAME THE CODERS CARP CARP CARP</span>"
|
||||
var/customized
|
||||
/// sigh, fine.
|
||||
var/datum/song/holoparasite/music_datum
|
||||
|
||||
@@ -142,11 +143,13 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
|
||||
to_chat(src, "<span class='holoparasite'>You are capable of manifesting or recalling to your master with the buttons on your HUD. You will also find a button to communicate with [summoner.p_them()] privately there.</span>")
|
||||
to_chat(src, "<span class='holoparasite'>While personally invincible, you will die if [summoner.real_name] does, and any damage dealt to you will have a portion passed on to [summoner.p_them()] as you feed upon [summoner.p_them()] to sustain yourself.</span>")
|
||||
to_chat(src, playstyle_string)
|
||||
guardiancustomize()
|
||||
if(!customized)
|
||||
guardiancustomize()
|
||||
|
||||
/mob/living/simple_animal/hostile/guardian/proc/guardiancustomize()
|
||||
guardianrecolor()
|
||||
guardianrename()
|
||||
customized = TRUE
|
||||
|
||||
/mob/living/simple_animal/hostile/guardian/proc/guardianrecolor()
|
||||
guardiancolor = input(src,"What would you like your color to be?","Choose Your Color","#ffffff") as color|null
|
||||
|
||||
@@ -528,21 +528,7 @@ Difficulty: Very Hard
|
||||
/obj/machinery/anomalous_crystal/emitter/ActivationReaction(mob/user, method)
|
||||
if(..())
|
||||
var/obj/item/projectile/P = new generated_projectile(get_turf(src))
|
||||
P.setDir(dir)
|
||||
switch(dir)
|
||||
if(NORTH)
|
||||
P.yo = 20
|
||||
P.xo = 0
|
||||
if(EAST)
|
||||
P.yo = 0
|
||||
P.xo = 20
|
||||
if(WEST)
|
||||
P.yo = 0
|
||||
P.xo = -20
|
||||
else
|
||||
P.yo = -20
|
||||
P.xo = 0
|
||||
P.fire()
|
||||
P.fire(angle2dir(dir))
|
||||
|
||||
/obj/machinery/anomalous_crystal/dark_reprise //Revives anyone nearby, but turns them into shadowpeople and renders them uncloneable, so the crystal is your only hope of getting up again if you go down.
|
||||
observer_desc = "When activated, this crystal revives anyone nearby, but turns them into Shadowpeople and makes them unclonable, making the crystal their only hope of getting up again."
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
#define RAT_VENT_CHANCE 1.75
|
||||
GLOBAL_LIST_EMPTY(plague_rats)
|
||||
|
||||
/mob/living/simple_animal/hostile/plaguerat
|
||||
name = "plague rat"
|
||||
desc = "A large decaying rat. It spreads its filth and emits a putrid odor to create more of its kind."
|
||||
icon_state = "plaguerat"
|
||||
icon_living = "plaguerat"
|
||||
icon_dead = "plaguerat_dead"
|
||||
speak = list("Skree!","SKREEE!","Squeak?")
|
||||
speak_emote = list("squeaks")
|
||||
emote_hear = list("Hisses.")
|
||||
emote_see = list("runs in a circle.", "stands on its hind legs.")
|
||||
gender = NEUTER
|
||||
speak_chance = 1
|
||||
turns_per_move = 5
|
||||
maxHealth = 100
|
||||
health = 100
|
||||
see_in_dark = 6
|
||||
obj_damage = 10
|
||||
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1)
|
||||
response_help_continuous = "glares at"
|
||||
response_help_simple = "glare at"
|
||||
response_disarm_continuous = "skoffs at"
|
||||
response_disarm_simple = "skoff at"
|
||||
response_harm_continuous = "slashes"
|
||||
response_harm_simple = "slash"
|
||||
melee_damage_lower = 6
|
||||
melee_damage_upper = 8
|
||||
attack_verb_continuous = "slashes"
|
||||
attack_verb_simple = "slash"
|
||||
attack_sound = 'sound/weapons/punch1.ogg'
|
||||
faction = list("rat")
|
||||
density = FALSE
|
||||
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
|
||||
mob_size = MOB_SIZE_TINY
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST
|
||||
var/datum/action/cooldown/scavenge
|
||||
var/last_spawn_time = 0
|
||||
var/in_vent = FALSE
|
||||
var/min_next_vent = 0
|
||||
var/obj/machinery/atmospherics/components/unary/entry_vent
|
||||
var/obj/machinery/atmospherics/components/unary/exit_vent
|
||||
|
||||
/mob/living/simple_animal/hostile/plaguerat/Initialize()
|
||||
. = ..()
|
||||
GLOB.plague_rats += src
|
||||
AddComponent(/datum/component/swarming)
|
||||
AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS)
|
||||
scavenge = new /datum/action/cooldown/scavenge
|
||||
scavenge.Grant(src)
|
||||
|
||||
/mob/living/simple_animal/hostile/plaguerat/Destroy()
|
||||
GLOB.plague_rats -= src
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/plaguerat/Life(seconds, times_fired)
|
||||
. = ..()
|
||||
//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
|
||||
//Literally only here to prevent farming and that's it.
|
||||
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/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(RAT_VENT_CHANCE)) //small chance to go into a vent
|
||||
for(var/obj/machinery/atmospherics/components/unary/v in view(7,src))
|
||||
if(!v.welded)
|
||||
entry_vent = v
|
||||
in_vent = TRUE
|
||||
walk_to(src, entry_vent)
|
||||
break
|
||||
|
||||
/mob/living/simple_animal/hostile/plaguerat/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(isopenturf(loc))
|
||||
var/turf/open/T = src.loc
|
||||
var/datum/gas_mixture/stank = new
|
||||
var/miasma_moles = T.air.get_moles(GAS_MIASMA)
|
||||
stank.set_moles(GAS_MIASMA,5)
|
||||
stank.set_temperature(BODYTEMP_NORMAL)
|
||||
if(T.air)
|
||||
if(miasma_moles < 200)
|
||||
T.assume_air(stank)
|
||||
T.air_update_turf()
|
||||
|
||||
if(prob(40))
|
||||
scavenge.Trigger()
|
||||
if(prob(50))
|
||||
var/turf/open/floor/F = get_turf(src)
|
||||
if(istype(F) && !F.intact)
|
||||
var/obj/structure/cable/C = locate() in F
|
||||
if(C && C.avail())
|
||||
visible_message("<span class='warning'>[src] chews through the [C]. It looks unharmed!</span>")
|
||||
playsound(src, 'sound/effects/sparks2.ogg', 100, TRUE)
|
||||
C.deconstruct()
|
||||
for(var/obj/O in range(1,src))
|
||||
if((world.time - last_spawn_time) > 10 SECONDS && istype(O, /obj/item/trash) || istype(O, /obj/effect/decal/cleanable/blood/gibs))
|
||||
qdel(O)
|
||||
be_fruitful()
|
||||
last_spawn_time = world.time
|
||||
|
||||
/mob/living/simple_animal/hostile/plaguerat/CanAttack(atom/the_target)
|
||||
if(istype(the_target,/mob/living/simple_animal))
|
||||
var/mob/living/A = the_target
|
||||
if(istype(the_target, /mob/living/simple_animal/hostile/plaguerat) && A.stat == CONSCIOUS)
|
||||
var/mob/living/simple_animal/hostile/plaguerat/R = the_target
|
||||
if(R.faction_check_mob(src, TRUE))
|
||||
return FALSE
|
||||
else
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/**
|
||||
*Checks the mouse cap, if it's above the cap, doesn't spawn a mouse. If below, spawns a mouse and adds it to cheeserats.
|
||||
*/
|
||||
|
||||
/mob/living/simple_animal/hostile/plaguerat/proc/be_fruitful()
|
||||
var/cap = 10
|
||||
if(LAZYLEN(GLOB.plague_rats) >= cap)
|
||||
visible_message("<span class='warning'>[src] gnaws into its food, [cap] rats are now on the station!</span>")
|
||||
return
|
||||
var/mob/living/newmouse = new /mob/living/simple_animal/hostile/plaguerat(loc)
|
||||
visible_message("<span class='notice'>[src] gnaws into its food, attracting another rat!</span>")
|
||||
|
||||
/mob/living/simple_animal/hostile/plaguerat/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
|
||||
|
||||
/**
|
||||
*Creates a chance to spawn more trash or gibs to repopulate. Otherwise, spawns a corpse or dirt.
|
||||
*/
|
||||
|
||||
/datum/action/cooldown/scavenge
|
||||
name = "Scavenge"
|
||||
desc = "Spread the plague, scavenge for trash and fresh meat to reproduce."
|
||||
icon_icon = 'icons/mob/actions/actions_animal.dmi'
|
||||
background_icon_state = "bg_clock"
|
||||
button_icon_state = "coffer"
|
||||
cooldown_time = 50
|
||||
|
||||
/datum/action/cooldown/scavenge/Trigger()
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
var/turf/T = get_turf(owner)
|
||||
var/loot = rand(1,100)
|
||||
switch(loot)
|
||||
if(1 to 3)
|
||||
var/pickedtrash = pick(GLOB.ratking_trash)
|
||||
to_chat(owner, "<span class='notice'>Excellent, you find more trash to spread your filth!</span>")
|
||||
new pickedtrash(T)
|
||||
if(4 to 6)
|
||||
to_chat(owner, "<span class='notice'>You find blood and gibs to feed your young!</span>")
|
||||
new /obj/effect/decal/cleanable/blood/gibs(T)
|
||||
if(!locate(/obj/effect/decal/cleanable/blood) in T)
|
||||
new /obj/effect/decal/cleanable/blood/(T)
|
||||
if(7 to 100)
|
||||
to_chat(owner, "<span class='notice'>Drat. Nothing.</span>")
|
||||
StartCooldown()
|
||||
@@ -109,7 +109,7 @@
|
||||
continue
|
||||
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
|
||||
visible_message("<span class='danger'>[src] vomits up [consumed_mob]!</span>")
|
||||
consumed_mob.forceMove(loc)
|
||||
consumed_mob.forceMove(get_turf(src))
|
||||
consumed_mob.Paralyze(50)
|
||||
if((rifts_charged == 3 || (SSshuttle.emergency.mode == SHUTTLE_DOCKED && rifts_charged > 0)) && !objective_complete)
|
||||
victory()
|
||||
@@ -123,6 +123,7 @@
|
||||
to_chat(src, "<span class='boldwarning'>You've failed to summon the rift in a timely manner! You're being pulled back from whence you came!</span>")
|
||||
destroy_rifts()
|
||||
playsound(src, 'sound/magic/demon_dies.ogg', 100, TRUE)
|
||||
empty_contents()
|
||||
QDEL_NULL(src)
|
||||
|
||||
/mob/living/simple_animal/hostile/space_dragon/AttackingTarget()
|
||||
@@ -351,7 +352,7 @@
|
||||
*/
|
||||
/mob/living/simple_animal/hostile/space_dragon/proc/empty_contents()
|
||||
for(var/atom/movable/AM in src)
|
||||
AM.forceMove(loc)
|
||||
AM.forceMove(get_turf(src))
|
||||
if(prob(90))
|
||||
step(AM, pick(GLOB.alldirs))
|
||||
|
||||
@@ -529,7 +530,7 @@
|
||||
/obj/structure/carp_rift
|
||||
name = "carp rift"
|
||||
desc = "A rift akin to the ones space carp use to travel long distances."
|
||||
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 50, BIO = 100, RAD = 100, FIRE = 100, ACID = 100)
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
|
||||
max_integrity = 300
|
||||
icon = 'icons/obj/carp_rift.dmi'
|
||||
icon_state = "carp_rift_carpspawn"
|
||||
@@ -636,7 +637,7 @@
|
||||
icon_state = "carp_rift_charged"
|
||||
light_color = LIGHT_COLOR_YELLOW
|
||||
update_light()
|
||||
armor = list(MELEE = 100, BULLET = 100, LASER = 100, ENERGY = 100, BOMB = 100, BIO = 100, RAD = 100, FIRE = 100, ACID = 100)
|
||||
armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
|
||||
resistance_flags = INDESTRUCTIBLE
|
||||
dragon.rifts_charged += 1
|
||||
if(dragon.rifts_charged != 3 && !dragon.objective_complete)
|
||||
|
||||
@@ -238,6 +238,7 @@
|
||||
|
||||
if(istype(W))
|
||||
if(equip_to_slot_if_possible(W, slot, FALSE, FALSE, FALSE, FALSE, TRUE))
|
||||
W.apply_outline()
|
||||
return TRUE
|
||||
|
||||
if(!W)
|
||||
|
||||
Reference in New Issue
Block a user