mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-30 04:17:13 +01:00
466 lines
15 KiB
Plaintext
466 lines
15 KiB
Plaintext
/**
|
|
* Simple mobs.
|
|
*
|
|
* * Unlike /carbon's and /silicon's, these are slightly less
|
|
* composition-based, with most of their properties in their
|
|
* frankly ridiculous amount of variables.
|
|
* * These tend to not have detailed simulation like attack
|
|
* zones and whatnot being handled.
|
|
*/
|
|
/mob/living/simple_mob
|
|
name = "animal"
|
|
desc = ""
|
|
icon = 'icons/mob/animal.dmi'
|
|
health = 20
|
|
maxHealth = 20
|
|
|
|
// Generally we don't want simple_mobs to get displaced when bumped into due to it trivializing combat with windup attacks.
|
|
// Some subtypes allow displacement, like passive animals.
|
|
mob_bump_flag = HEAVY
|
|
mob_swap_flags = ~HEAVY
|
|
mob_push_flags = ~HEAVY
|
|
|
|
iff_factions = MOB_IFF_FACTION_BIND_AUTO
|
|
|
|
//* Attacks - Basic *//
|
|
/// melee style
|
|
var/datum/melee_attack/unarmed/melee_style
|
|
|
|
//? Darksight
|
|
/// our innate darksight
|
|
var/datum/vision/baseline/vision_innate = /datum/vision/baseline/default
|
|
|
|
//* Inventory *//
|
|
/// how many hands we have
|
|
var/hand_count = 0
|
|
|
|
///Tooltip description
|
|
var/tt_desc = null
|
|
|
|
//* Settings for played mobs *//
|
|
/// Does the percentage health show in the stat panel for the mob
|
|
var/show_stat_health = TRUE
|
|
/// Can a player in this mob use things like guns or AI cards?
|
|
var/humanoid_hands = FALSE
|
|
/// Used in IsHumanoidToolUser. 'Your X are not fit-'.
|
|
var/hand_form = "hands"
|
|
/// Slots to show on the hud (typically none)
|
|
var/list/hud_gears
|
|
/// Icon file path to use for the HUD, otherwise generic icons are used
|
|
var/ui_icons
|
|
/// Message to print to players about 'how' to play this mob on login.
|
|
var/player_msg
|
|
|
|
//inv slots
|
|
var/list/inventory_slots
|
|
|
|
//* Mob icon/appearance settings *//
|
|
/// The iconstate if we're alive. //!REQUIRED
|
|
var/icon_living = ""
|
|
/// The iconstate if we're dead. //!REQUIRED
|
|
var/icon_dead = ""
|
|
/// The iconstate for being gibbed, optional. Defaults to a generic gib animation.
|
|
var/icon_gib = "generic_gib"
|
|
/// The iconstate for resting, optional
|
|
var/icon_rest = null
|
|
/// Holds overlays from modifiers.
|
|
var/image/modifier_overlay = null
|
|
/// Holds the eye overlay.
|
|
var/image/eye_layer = null
|
|
/// If true, adds an overlay over the lighting plane for [icon_state]-eyes.
|
|
var/has_eye_glow = FALSE
|
|
///Just the default, played like the weapon attack anim
|
|
attack_icon = 'icons/effects/effects.dmi'
|
|
///Just the default
|
|
attack_icon_state = "slash"
|
|
|
|
//* Mob talking settings *//
|
|
/// Can all mobs in the entire universe understand this one?
|
|
universal_speak = 0
|
|
/// Text name of their language if they speak something other than galcom. They speak the first one.
|
|
var/has_langs = list(LANGUAGE_GALCOM)
|
|
|
|
//* Movement *//
|
|
/// Base movement speed in tiles per second
|
|
var/movement_base_speed = 2
|
|
|
|
/// If set, will play this sound when it moves on its own will.
|
|
var/movement_sound = null
|
|
/// If set, plays the sound when the mob's dir changes in most cases.
|
|
var/turn_sound = null
|
|
/// If set, moving will shake the camera of all living mobs within this radius slightly.
|
|
var/movement_shake_radius = 0
|
|
/// If set, the mob will move through fluids with no hinderance.
|
|
var/aquatic_movement = 0
|
|
|
|
//* Mob interaction *//
|
|
/// If clicked on help intent.
|
|
var/response_help = "tries to help"
|
|
/// If clicked on disarm intent.
|
|
var/response_disarm = "tries to disarm"
|
|
/// If clicked on harm intent.
|
|
var/response_harm = "tries to hurt"
|
|
/// Mobs on this list wont get attacked regardless of faction status.
|
|
// TODO: THIS IS A GC FAILURE!
|
|
var/list/friends = list()
|
|
/// How much an unarmed harm click does to this mob.
|
|
var/harm_intent_damage = 3
|
|
/// The list of lootable objects to drop, with "/path = prob%" structure
|
|
var/list/loot_list = list()
|
|
/// An ID card if they have one to give them access to stuff.
|
|
var/obj/item/card/id/access_card
|
|
|
|
//* Mob environment settings *//
|
|
/// Minimum "okay" temperature in kelvin
|
|
var/minbodytemp = 250
|
|
/// Maximum of above
|
|
var/maxbodytemp = 350
|
|
/// Amount of damage applied if animal's body temperature is higher than maxbodytemp.
|
|
var/heat_damage_per_tick = 3
|
|
/// Same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp.
|
|
var/cold_damage_per_tick = 2
|
|
/// The mob's fire state: 0 = fine, 1 = hot, 2 = cold
|
|
var/fire_alert = 0
|
|
/// Oxygen in moles, minimum, 0 is 'no minimum'
|
|
var/min_oxy = 5
|
|
/// Oxygen in moles, maximum, 0 is 'no maximum'
|
|
var/max_oxy = 0
|
|
/// Phoron min
|
|
var/min_tox = 0
|
|
/// Phoron max
|
|
var/max_tox = 1
|
|
/// CO2 min
|
|
var/min_co2 = 0
|
|
/// CO2 max
|
|
var/max_co2 = 5
|
|
/// N2 min
|
|
var/min_n2 = 0
|
|
/// N2 max
|
|
var/max_n2 = 0
|
|
/// This damage is taken when atmos doesn't fit all the requirements set.
|
|
var/unsuitable_atoms_damage = 2
|
|
|
|
//* Hostility settings *//
|
|
/// Is the mob weak to tasers?
|
|
var/taser_kill = FALSE
|
|
|
|
//* Attack ranged settings *//
|
|
/// The projectiles I shoot.
|
|
var/projectiletype
|
|
/// The sound I make when I do it
|
|
var/projectilesound
|
|
/// Accuracy modifier to add onto the bullet when its fired.
|
|
var/projectile_accuracy = 0
|
|
/// How many degrees to vary when I do it.
|
|
var/projectile_dispersion = 0
|
|
/// What to make the hugely laggy casings pile out of.
|
|
var/casingtype
|
|
|
|
//* Reloading settings, part of ranged code *//
|
|
/// If TRUE, mob needs to reload occasionally.
|
|
var/needs_reload = FALSE
|
|
/// How many shots the mob gets before it has to reload, will not be used if needs_reload is FALSE
|
|
var/reload_max = 1
|
|
/// A counter to keep track of how many shots the mob has fired so far. Reloads when it hits reload_max.
|
|
var/reload_count = 0
|
|
/// How long it takes for a mob to reload. This is to buy a player a bit of time to run or fight.
|
|
var/reload_time = 1 SECONDS
|
|
/// What sound gets played when the mob successfully reloads. Defaults to the same sound as reloading guns. Can be null.
|
|
var/reload_sound = 'sound/weapons/flipblade.ogg'
|
|
|
|
//* Mob melee settings *//
|
|
|
|
/// Lower bound of randomized melee damage.
|
|
//! Legacy var, do not use
|
|
var/legacy_melee_damage_lower = 2
|
|
/// Upper bound of randomized melee damage.
|
|
//! Legacy var, do not use
|
|
var/legacy_melee_damage_upper = 6
|
|
/// "You are [attacktext] by the mob!"
|
|
var/list/attacktext = list("attacked")
|
|
/// "The mob [friendly] the person."
|
|
var/list/friendly = list("nuzzles")
|
|
/// Sound to play when I attack.
|
|
var/attack_sound = null
|
|
/// Percent chance to miss a melee attack.
|
|
var/melee_miss_chance = 0
|
|
/// What armor does this check?
|
|
var/attack_armor_type = "melee"
|
|
/// How much armor pen this attack has.
|
|
var/attack_armor_pen = 0
|
|
/// Is the attack sharp?
|
|
var/attack_sharp = FALSE
|
|
/// Does the attack have an edge?
|
|
var/attack_edge = FALSE
|
|
|
|
/// If set, the mob will do a windup animation and can miss if the target moves out of the way.
|
|
var/melee_attack_delay = null
|
|
/// If set, the mob will do a windup animation and can miss if the target moves out of the way.
|
|
var/ranged_attack_delay = null
|
|
/// If set, the mob will do a windup animation and can miss if the target moves out of the way.
|
|
var/special_attack_delay = null
|
|
|
|
//* Special attacks *//
|
|
/// The chance to ATTEMPT a special_attack_target(). If it fails, it will do a regular attack instead.
|
|
//? This is commented out to ease the AI attack logic by being (a bit more) determanistic.
|
|
//? You should instead limit special attacks using the below vars instead.
|
|
// var/special_attack_prob = 0
|
|
|
|
/// The minimum distance required for an attempt to be made.
|
|
var/special_attack_min_range = null
|
|
/// The maximum for an attempt.
|
|
var/special_attack_max_range = null
|
|
/// If set, special attacks will work off of a charge system, and won't be usable if all charges are expended. Good for grenades.
|
|
var/special_attack_charges = null
|
|
/// If set, special attacks will have a cooldown between uses.
|
|
var/special_attack_cooldown = null
|
|
/// world.time when a special attack occured last, for cooldown calculations.
|
|
var/last_special_attack = null
|
|
|
|
//* Damage resistances *//
|
|
/// Chance for a grab attempt to fail. Note that this is not a true resist and is just a prob() of failure.
|
|
var/grab_resist = 0
|
|
// Protection against heat/cold/electric/water effects.
|
|
// 0 is no protection, 1 is total protection. Negative numbers increase vulnerability.
|
|
var/heat_resist = 0.0
|
|
var/cold_resist = 0.0
|
|
var/shock_resist = 0.0
|
|
var/water_resist = 1.0
|
|
var/poison_resist = 0.0
|
|
|
|
/// Stops injections and "injections".
|
|
var/thick_armor = FALSE
|
|
|
|
//* Cult stuff. *//
|
|
var/purge = 0
|
|
var/supernatural = FALSE
|
|
|
|
|
|
/// Is it contained in a cage?
|
|
var/in_stasis = 0
|
|
|
|
//Randomization code base
|
|
var/mod_min = 70
|
|
var/mod_max = 130
|
|
/// Are we randomized?
|
|
var/randomized = FALSE
|
|
|
|
/// Used for if the mob can drop limbs. Overrides species dmi.
|
|
var/limb_icon
|
|
/// Used for if the mob can drop limbs. Overrides the icon cache key, so it doesn't keep remaking the icon needlessly.
|
|
var/limb_icon_key
|
|
|
|
// todo: remove
|
|
/// legacy armor, applied on init
|
|
var/list/armor_legacy_mob
|
|
|
|
///Does the simple mob drop organs when butchered?
|
|
butchery_drops_organs = FALSE
|
|
|
|
/mob/living/simple_mob/Initialize(mapload)
|
|
if(armor_legacy_mob)
|
|
var/list/translated = list()
|
|
for(var/key in armor_legacy_mob)
|
|
translated[key] = armor_legacy_mob[key] * 0.01 // new armor is / 100
|
|
set_armor(translated)
|
|
init_melee_style()
|
|
|
|
remove_verb(src, /mob/verb/observe)
|
|
health = maxHealth
|
|
randomize()
|
|
|
|
for(var/L in has_langs)
|
|
languages |= RSlanguages.legacy_resolve_language_name(L)
|
|
if(languages.len)
|
|
default_language = languages[1]
|
|
|
|
if(has_eye_glow)
|
|
add_eyes()
|
|
return ..()
|
|
|
|
/mob/living/simple_mob/Destroy()
|
|
release_vore_contents()
|
|
default_language = null
|
|
QDEL_NULL(access_card)
|
|
|
|
friends.Cut()
|
|
languages.Cut()
|
|
|
|
if(has_eye_glow)
|
|
remove_eyes()
|
|
return ..()
|
|
|
|
/mob/living/simple_mob/init_inventory()
|
|
if(inventory)
|
|
return
|
|
inventory = new(src)
|
|
inventory.set_inventory_slots(inventory_slots)
|
|
inventory.set_hand_count(get_usable_hand_count())
|
|
|
|
/mob/living/simple_mob/proc/init_melee_style()
|
|
melee_style = new
|
|
melee_style.damage = 0
|
|
melee_style.damage_add_low = legacy_melee_damage_lower
|
|
melee_style.damage_add_high = legacy_melee_damage_upper
|
|
melee_style.damage_mode = (attack_sharp? DAMAGE_MODE_SHARP : NONE) | (attack_edge? DAMAGE_MODE_EDGE : NONE)
|
|
melee_style.damage_flag = attack_armor_type
|
|
//* IT'S GAMER TIME *//
|
|
melee_style.damage_tier = 5
|
|
melee_style.attack_verb_legacy = attacktext
|
|
melee_style.verb_past_participle = attacktext
|
|
|
|
//* randomization code. *//
|
|
/mob/living/simple_mob/proc/randomize()
|
|
if(randomized == TRUE)
|
|
var/mod = rand(mod_min,mod_max)/100
|
|
size_multiplier = mod
|
|
maxHealth = round(maxHealth*mod)
|
|
health = round(health*mod)
|
|
legacy_melee_damage_lower = round(legacy_melee_damage_lower*mod)
|
|
legacy_melee_damage_upper = round(legacy_melee_damage_upper*mod)
|
|
movement_base_speed = movement_base_speed * mod
|
|
meat_amount = round(meat_amount*mod)
|
|
update_icons()
|
|
update_movespeed_base()
|
|
|
|
/mob/living/simple_mob/death()
|
|
update_icon()
|
|
..()
|
|
|
|
//Client attached
|
|
/mob/living/simple_mob/Login()
|
|
. = ..()
|
|
to_chat(src,"<b>You are \the [src].</b> [player_msg]")
|
|
|
|
/mob/living/simple_mob/SelfMove(turf/n, direct)
|
|
var/turf/old_turf = get_turf(src)
|
|
var/old_dir = dir
|
|
. = ..()
|
|
if(. && movement_shake_radius)
|
|
for(var/mob/living/L in range(movement_shake_radius, src))
|
|
shake_camera(L, 1, 1)
|
|
if(turn_sound && dir != old_dir)
|
|
playsound(src, turn_sound, 50, 1)
|
|
else if(movement_sound && old_turf != get_turf(src)) // Playing both sounds at the same time generally sounds bad.
|
|
playsound(src, movement_sound, 50, 1)
|
|
|
|
/mob/living/simple_mob/legacy_movement_delay()
|
|
. = ..()
|
|
var/tally = 0 //Incase I need to add stuff other than "speed" later
|
|
|
|
for(var/datum/modifier/M in modifiers)
|
|
if(!isnull(M.haste) && M.haste == TRUE)
|
|
return -3
|
|
if(!isnull(M.slowdown))
|
|
tally += M.slowdown
|
|
|
|
// Turf related slowdown
|
|
var/turf/T = get_turf(src)
|
|
if(T && T.slowdown && !is_avoiding_ground()) // Flying mobs ignore turf-based slowdown. Aquatic mobs ignore water slowdown, and can gain bonus speed in it.
|
|
if(istype(T,/turf/simulated/floor/water) && aquatic_movement)
|
|
tally -= aquatic_movement - 1
|
|
else
|
|
tally += T.slowdown
|
|
|
|
if(purge)//Purged creatures will move more slowly. The more time before their purge stops, the slower they'll move.
|
|
if(tally <= 0)
|
|
tally = 1
|
|
tally *= purge
|
|
|
|
return . + tally
|
|
|
|
|
|
/mob/living/simple_mob/statpanel_data(client/C)
|
|
. = ..()
|
|
if(C.statpanel_tab("Status") && show_stat_health)
|
|
INJECT_STATPANEL_DATA_LINE(., "Health: [round((health / getMaxHealth()) * 100)]%")
|
|
|
|
/mob/living/simple_mob/set_resting(value)
|
|
. = ..()
|
|
if(resting && icon_rest)
|
|
icon_state = icon_rest
|
|
else
|
|
icon_state = icon_living
|
|
update_icon()
|
|
|
|
/mob/living/simple_mob/say(var/message, var/datum/prototype/language/speaking = null, var/verb="says", var/alt_name="", var/whispering = 0)
|
|
verb = "says"
|
|
if(speak_emote.len)
|
|
verb = pick(speak_emote)
|
|
|
|
message = sanitize(message)
|
|
|
|
return ..()
|
|
|
|
/mob/living/simple_mob/get_speech_ending(verb, var/ending)
|
|
return verb
|
|
|
|
|
|
//TODO: This needs to be phased out for a newer butchering system. Though I am too scared to undo all our custom stuff. -Zandario
|
|
// Harvest an animal's delicious byproducts
|
|
/mob/living/simple_mob/harvest(mob/user)
|
|
var/actual_meat_amount = rand(0, meat_amount)
|
|
var/actual_bone_amount = rand(0, bone_amount)
|
|
var/actual_hide_amount = rand(0, hide_amount)
|
|
var/actual_exotic_amount = rand(0, exotic_amount)
|
|
if(stat != DEAD)
|
|
return
|
|
if(meat_type)
|
|
for(var/i in 1 to actual_meat_amount)
|
|
var/obj/item/meat = new meat_type(drop_location())
|
|
meat.name = "[name] [meat.name]"
|
|
if(bone_type)
|
|
for(var/i in 1 to actual_bone_amount)
|
|
var/obj/item/bone = new bone_type(drop_location())
|
|
bone.name = "[bone.name]"
|
|
if(hide_type)
|
|
for(var/i in 1 to actual_hide_amount)
|
|
new hide_type(drop_location())
|
|
if(exotic_type)
|
|
for(var/i in 1 to actual_exotic_amount)
|
|
new exotic_type(drop_location())
|
|
if(issmall(src))
|
|
user?.visible_message("<span class='danger'>[user] chops up \the [src]!</span>")
|
|
new /obj/effect/debris/cleanable/blood/splatter(get_turf(src))
|
|
qdel(src)
|
|
else
|
|
user.visible_message("<span class='danger'>[user] butchers \the [src] messily!</span>")
|
|
gib()
|
|
|
|
/* Replace the above ^^^^ with this if enabling the butchering component.
|
|
/mob/living/simple_mob/gib()
|
|
if(butcher_results || guaranteed_butcher_results)
|
|
var/list/butcher = list()
|
|
if(butcher_results)
|
|
butcher += butcher_results
|
|
if(guaranteed_butcher_results)
|
|
butcher += guaranteed_butcher_results
|
|
var/atom/Tsec = drop_location()
|
|
for(var/path in butcher)
|
|
for(var/i in 1 to butcher[path])
|
|
new path(Tsec)
|
|
..()
|
|
*/
|
|
|
|
/mob/living/simple_mob/is_sentient()
|
|
return mob_class & MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL|MOB_CLASS_SLIME // Update this if needed.
|
|
|
|
/mob/living/simple_mob/get_nametag_desc(mob/user)
|
|
return "<i>[tt_desc]</i>"
|
|
|
|
/// Override for special butchering checks.
|
|
/mob/living/simple_mob/can_butcher(var/mob/user, var/obj/item/I)
|
|
. = ..()
|
|
if(. && (!is_sharp(I) || !has_edge(I)))
|
|
return FALSE
|
|
|
|
//* Inventory *//
|
|
|
|
/mob/living/simple_mob/get_usable_hand_count()
|
|
return hand_count
|
|
|
|
/mob/living/simple_mob/get_usable_hand_indices()
|
|
. = list()
|
|
for(var/i in 1 to hand_count)
|
|
. += i
|