Raptor Rework - Ranching and Companionship (#93564)

This commit is contained in:
SmArtKar
2025-11-01 14:13:29 +03:00
committed by GitHub
parent 30151ccc20
commit 1c6c506936
58 changed files with 2608 additions and 1621 deletions
@@ -48,6 +48,7 @@
lighting_cutoff_blue = 25
can_be_held = TRUE
worn_slot_flags = ITEM_SLOT_HEAD
inhand_holder_type = /obj/item/mob_holder/drone
/// `TRUE` if we have picked our visual appearance, `FALSE` otherwise (default)
var/picked = FALSE
/// Stored drone color, restored when unhacked
@@ -80,6 +80,7 @@
if(beegent)
reee[beegent.type] = 5
holder.AddComponentFrom(SOURCE_EDIBLE_INNATE, /datum/component/edible, reee, null, BEE_FOODGROUPS, 10, 0, list("bee"), null, 10)
SEND_SIGNAL(src, COMSIG_LIVING_SCOOPED_UP, picker, holder)
picker.visible_message(span_warning("[picker] scoops up [src]!"))
picker.put_in_hands(holder)
@@ -1,12 +1,3 @@
GLOBAL_LIST_INIT(raptor_growth_paths, list(
/mob/living/basic/raptor/baby_raptor/red = list(RAPTOR_PURPLE, RAPTOR_WHITE),
/mob/living/basic/raptor/baby_raptor/white = list(RAPTOR_GREEN, RAPTOR_PURPLE),
/mob/living/basic/raptor/baby_raptor/purple = list(RAPTOR_GREEN, RAPTOR_WHITE),
/mob/living/basic/raptor/baby_raptor/yellow = list(RAPTOR_GREEN, RAPTOR_RED),
/mob/living/basic/raptor/baby_raptor/green = list(RAPTOR_RED, RAPTOR_YELLOW),
/mob/living/basic/raptor/baby_raptor/blue = list(RAPTOR_RED, RAPTOR_PURPLE)
))
GLOBAL_LIST_INIT(raptor_inherit_traits, list(
BB_BASIC_DEPRESSED = "Depressed",
BB_RAPTOR_MOTHERLY = "Motherly",
@@ -16,23 +7,25 @@ GLOBAL_LIST_INIT(raptor_inherit_traits, list(
GLOBAL_LIST_EMPTY(raptor_population)
#define HAPPINESS_BOOST_DAMPENER 0.3
/// Innate raptor offsets
#define RAPTOR_INNATE_SOURCE "raptor_innate"
/mob/living/basic/raptor
name = "raptor"
desc = "A trusty, powerful steed. Taming it might prove difficult..."
icon = 'icons/mob/simple/lavaland/raptor_big.dmi'
speed = 2
icon_state = "raptor_red"
base_icon_state = "raptor"
pixel_w = -12
base_pixel_w = -12
speed = 0.5
mob_biotypes = MOB_ORGANIC|MOB_BEAST
maxHealth = 270
health = 270
maxHealth = 200
health = 200
melee_damage_lower = 10
melee_damage_upper = 15
combat_mode = TRUE
mob_size = MOB_SIZE_LARGE
head_icon = 'icons/mob/clothing/back/pets_back.dmi'
worn_slot_flags = ITEM_SLOT_BACK
held_w_class = WEIGHT_CLASS_BULKY
unsuitable_atmos_damage = 0
minimum_survivable_temperature = BODYTEMP_COLD_ICEBOX_SAFE
maximum_survivable_temperature = INFINITY
@@ -45,14 +38,13 @@ GLOBAL_LIST_EMPTY(raptor_population)
/obj/item/food/meat/slab/chicken = 4,
/obj/item/stack/sheet/bone = 2,
)
ai_controller = /datum/ai_controller/basic_controller/raptor
///can this mob breed
// AI controller is set by our color
ai_controller = null
/// Can this raptor breed?
var/can_breed = TRUE
///should we change offsets on direction change?
/// Should we change offsets on direction change?
var/change_offsets = TRUE
///can we ride this mob
var/ridable_component = /datum/component/riding/creature/raptor
//pet commands when we tame the raptor
/// Pet commands when we tame the raptor
var/static/list/pet_commands = list(
/datum/pet_command/breed,
/datum/pet_command/idle,
@@ -62,24 +54,47 @@ GLOBAL_LIST_EMPTY(raptor_population)
/datum/pet_command/follow,
/datum/pet_command/fetch,
)
///things we inherited from our parent
var/datum/raptor_inheritance/inherited_stats
///our color
var/raptor_color
///the description that appears in the dex
var/dex_description
///path of our child
var/child_path
/// Can we wear a collar? If so, what is our icon state prefix for it?
var/collar_state = "raptor"
/// Raptor color datum assigned to this raptor, this is a singleton
var/datum/raptor_color/raptor_color = null
/// Are we an adult, youngling or baby?
var/growth_stage = RAPTOR_ADULT
/// Our current growth progress towards the next stage if we're a youngling or a baby
var/growth_progress = 0
/// Probability of getting progress in the baby phase each second
var/growth_probability = 80
/// Food types that we can consume
var/static/list/food_types = list(
/obj/item/stack/ore = 0,
/obj/item/food/meat = 15,
/obj/item/food/meat/slab = 25,
/obj/item/food/meat/slab/spider = -15, // Toxic meats
/obj/item/food/meat/slab/xeno = -15,
/obj/item/food/meat/steak = 50,
/obj/item/food/grown/ash_flora = 10,
/obj/item/fish = 15,
/obj/item/organ = 25,
)
/// Inheritance datum we store our genetic data in
var/datum/raptor_inheritance/inherited_stats = null
/// Current happiness value of the raptor
var/happiness_percentage = 0
/mob/living/basic/raptor/Initialize(mapload)
/mob/living/basic/raptor/Initialize(mapload, datum/raptor_color/color_type, datum/raptor_inheritance/passed_stats)
. = ..()
if(SSmapping.is_planetary())
change_offsets = FALSE
icon = 'icons/mob/simple/lavaland/raptor_icebox.dmi'
inherited_stats = passed_stats || new(src)
// First thing as to go before tameable in change_growth_stage()
AddElement(/datum/element/basic_eating, food_types = food_types)
raptor_color = GLOB.raptor_colors[color_type || raptor_color || pick(GLOB.raptor_colors)]
raptor_color.setup_raptor(src)
AddElement(/datum/element/wears_collar)
add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE), INNATE_TRAIT)
if (growth_stage == RAPTOR_ADULT)
raptor_color.setup_adult(src)
else
change_growth_stage(growth_stage, RAPTOR_ADULT)
add_traits(list(TRAIT_ASHSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE, TRAIT_MINING_AOE_IMMUNE), INNATE_TRAIT)
AddElement(\
/datum/element/crusher_loot,\
trophy_type = /obj/item/crusher_trophy/raptor_feather,\
@@ -87,15 +102,364 @@ GLOBAL_LIST_EMPTY(raptor_population)
drop_immediately = FALSE,\
)
if(!mapload)
if (!mapload)
GLOB.raptor_population += REF(src)
AddComponent(/datum/component/obeys_commands, pet_commands)
AddComponent(/datum/component/obeys_commands, pet_commands, list(0, -base_pixel_w))
AddElement(\
/datum/element/change_force_on_death,\
move_resist = MOVE_RESIST_DEFAULT,\
)
RegisterSignal(src, COMSIG_MOB_ATE, PROC_REF(on_eat))
RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(on_pre_eat))
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/ai_flee_while_injured, stop_fleeing_at = 0.5, start_fleeing_below = 0.2)
if (can_breed)
add_breeding_component()
// Babies handle it in their change_growth_stage
if (growth_stage != RAPTOR_BABY)
update_blackboard()
AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_CLAW)
RegisterSignal(src, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_dir_change))
RegisterSignal(src, COMSIG_LIVING_SCOOPED_UP, PROC_REF(on_picked_up))
adjust_offsets(dir)
add_happiness_component()
/mob/living/basic/raptor/Destroy()
raptor_color = null
GLOB.raptor_population -= REF(src)
return ..()
/mob/living/basic/raptor/death(gibbed)
. = ..()
GLOB.raptor_population -= REF(src)
/mob/living/basic/raptor/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, buckle_mob_flags= NONE)
if(!iscarbon(target))
return
return ..()
/mob/living/basic/raptor/proc/on_dir_change(datum/source, old_dir, new_dir)
SIGNAL_HANDLER
adjust_offsets(new_dir)
/mob/living/basic/raptor/proc/adjust_offsets(direction)
if (!change_offsets)
return
switch (direction)
if (NORTH, SOUTH)
add_offsets(RAPTOR_INNATE_SOURCE, w_add = 0, animate = FALSE)
if (EAST, SOUTHEAST, NORTHEAST)
add_offsets(RAPTOR_INNATE_SOURCE, w_add = -8, animate = FALSE)
if (WEST, SOUTHWEST, NORTHWEST)
add_offsets(RAPTOR_INNATE_SOURCE, w_add = 8, animate = FALSE)
/mob/living/basic/raptor/examine(mob/user)
. = ..()
if (stat == DEAD)
return
switch (health / maxHealth)
if (0 to 0.2)
. += span_italics(span_bolddanger("[p_They()] [p_are()] gruesomly wounded, barely staying up on [p_their()] feet!"))
if (0.2 to 0.4)
. += span_danger("[p_They()] [p_have()] heavy injuries and open wounds all around [p_their()] body!")
if (0.4 to 0.6)
. += span_warning("[p_They()] [p_are()] noticeably hurt, limping from [p_their()] cuts and bruises.")
if (0.6 to 0.8)
. += span_warning("[p_They()] [p_are()] visibly injured, a few bruises and cuts showing between [p_their()] feathers.")
if (0.8 to 0.999)
. += span_notice("[p_They()] [p_have()] a few minor bruises and scratches.")
/mob/living/basic/raptor/Life(seconds_per_tick, times_fired)
. = ..()
if (growth_stage != RAPTOR_BABY || HAS_TRAIT(src, TRAIT_STASIS) || stat == DEAD)
return
if (!SPT_PROB(growth_probability * (1 + happiness_percentage * RAPTOR_GROWTH_HAPPINESS_MULTIPLIER), seconds_per_tick))
return
growth_progress += rand(RAPTOR_BABY_GROWTH_LOWER, RAPTOR_BABY_GROWTH_UPPER)
if (growth_progress >= RAPTOR_GROWTH_REQUIRED)
change_growth_stage(RAPTOR_YOUNG)
growth_progress = 0
/mob/living/basic/raptor/early_melee_attack(atom/target, list/modifiers, ignore_cooldown)
. = ..()
if(!.)
return FALSE
if(!istype(target, /obj/structure/ore_container/food_trough/raptor_trough))
return TRUE
var/obj/ore_food = locate(/obj/item/stack/ore) in target
if(isnull(ore_food))
balloon_alert(src, "no food!")
else
UnarmedAttack(ore_food, TRUE, modifiers)
return FALSE
/mob/living/basic/raptor/melee_attack(mob/living/target, list/modifiers, ignore_cooldown)
if (!combat_mode && istype(target, /mob/living/basic/raptor))
var/mob/living/basic/raptor/possible_baby = target
if (possible_baby.growth_stage == RAPTOR_BABY)
return target.attack_hand(src, list(LEFT_CLICK = TRUE))
return ..()
/mob/living/basic/raptor/proc/add_breeding_component()
var/static/list/partner_types = typecacheof(list(/mob/living/basic/raptor))
var/static/list/baby_types = list(/obj/item/food/egg = 1) ///raptor_egg = 1)
AddComponent(\
/datum/component/breed, \
can_breed_with = partner_types, \
baby_paths = baby_types, \
breed_timer = 3 MINUTES, \
post_birth = CALLBACK(src, PROC_REF(egg_inherit)), \
)
/mob/living/basic/raptor/proc/add_happiness_component()
var/static/list/percentage_callbacks = list(0, 15, 25, 35, 50, 75, 90, 100)
// Higher happiness cap so it decays slower, about 15 minutes from full to zero
AddComponent(\
/datum/component/happiness, \
maximum_happiness = 900, \
on_petted_change = 50, \
on_groom_change = 200, \
on_eat_change = 150, \
callback_percentages = percentage_callbacks,\
happiness_callback = CALLBACK(src, PROC_REF(happiness_change)),\
)
/mob/living/basic/raptor/proc/happiness_change(percent_value)
var/attack_boost = round((percent_value - happiness_percentage) * RAPTOR_HAPPINESS_DAMAGE_BOOST, 1)
melee_damage_lower += attack_boost
melee_damage_upper += attack_boost
happiness_percentage = percent_value
/mob/living/basic/raptor/projectile_hit(obj/projectile/hitting_projectile, def_zone, piercing_hit, blocked)
// Most colors will redirect shots to their rider as to increase their own survivability, and only tank melee attacks
if (raptor_color.redirect_shots && length(buckled_mobs))
return buckled_mobs[1].projectile_hit(hitting_projectile, def_zone, piercing_hit, blocked)
return ..()
/// Pass our genetic data to the egg
/mob/living/basic/raptor/proc/egg_inherit(obj/item/food/egg/raptor_egg/baby_egg, mob/living/basic/raptor/partner)
var/datum/raptor_inheritance/child_genes = new()
child_genes.set_parents(src, partner)
baby_egg.inherited_stats = child_genes
baby_egg.child_color = get_child_color(partner)
// Halve our food modifiers every time we breed
for (var/food_type in inherited_stats.foods_eaten)
var/list/stat_mods = inherited_stats.foods_eaten[food_type]
stat_mods["amount"] /= 2
stat_mods["attack"] /= 2
stat_mods["health"] /= 2
stat_mods["speed"] /= 2
stat_mods["ability"] /= 2
stat_mods["growth"] /= 2
var/list/trait_list = stat_mods["traits"]
for (var/i in 1 to ceil(length(trait_list) / 2))
trait_list -= pick(trait_list)
var/list/color_chances = stat_mods["color_chances"]
for (var/datum/raptor_color/color_type as anything in color_chances)
color_chances[color_type] = floor(color_chances[color_type] / 2)
if (!color_chances[color_type])
color_chances -= color_type
/mob/living/basic/raptor/proc/get_child_color(mob/living/basic/raptor/partner)
if (raptor_color == partner.raptor_color)
return raptor_color.type
if (raptor_color.guaranteed_crossbreeds[partner.raptor_color.type])
return raptor_color.guaranteed_crossbreeds[partner.raptor_color.type]
// This should be redundant as they should be mirroring eachother, but just in case
if (partner.raptor_color.guaranteed_crossbreeds[raptor_color.type])
return partner.raptor_color.guaranteed_crossbreeds[raptor_color.type]
// We've got all the colors in our family tree and aren't rolling a guarantee, bingo
if (length(inherited_stats.parent_colors | partner.inherited_stats.parent_colors | raptor_color.type | partner.raptor_color.type) == length(GLOB.raptor_colors))
return /datum/raptor_color/black
var/list/prob_list = list()
for (var/datum/raptor_color/color_type as anything in GLOB.raptor_colors)
prob_list[color_type] = color_type::spawn_chance
var/amount_eaten = 0
for (var/food_type in inherited_stats.foods_eaten)
var/list/stat_mods = inherited_stats.foods_eaten[food_type]
amount_eaten += stat_mods["amount"]
for (var/food_type in inherited_stats.foods_eaten)
var/list/stat_mods = inherited_stats.foods_eaten[food_type]
var/list/color_chances = stat_mods["color_chances"]
for (var/datum/raptor_color/color_type as anything in color_chances)
prob_list[color_type] += floor(color_chances[color_type] * stat_mods["amount"])
return pick_weight(prob_list)
/mob/living/basic/raptor/proc/on_picked_up(mob/living/basic/raptor/source, mob/living/user, obj/item/mob_holder/holder)
SIGNAL_HANDLER
// Our inventory code sucks so we have to do this
holder.icon = 'icons/mob/simple/lavaland/raptor_baby.dmi'
holder.icon_state = icon_state
holder.alternate_worn_layer = HEAD_LAYER
holder.pixel_w = 0
holder.pixel_z = 0
holder.base_pixel_w = 0
holder.base_pixel_z = 0
/mob/living/basic/raptor/proc/on_pre_eat(datum/source, obj/item/potential_food, list/effect_mult)
SIGNAL_HANDLER
if (isorgan(potential_food))
var/obj/item/organ/guts = potential_food
if (!(guts.organ_flags & ORGAN_EDIBLE) || !(guts.organ_flags & ORGAN_ORGANIC))
return COMSIG_MOB_CANCEL_EAT
if (happiness_percentage)
effect_mult += happiness_percentage * RAPTOR_GROWTH_HAPPINESS_MULTIPLIER
/mob/living/basic/raptor/proc/on_eat(datum/source, atom/food, mob/living/feeder)
SIGNAL_HANDLER
if (!istype(food, /obj/item/food))
return
var/obj/item/food/meal = food
var/is_flora = istype(meal, /obj/item/food/grown/ash_flora)
var/is_meat = (meal.foodtypes & (MEAT|GORE))
// Babies cannot gain growth from eating meat, only plants, but they get some passively
if ((!is_meat || growth_stage == RAPTOR_BABY) && !is_flora)
return
if (growth_stage == RAPTOR_ADULT)
return
// Better meals make your raptor grow faster
var/growth_value = meal.crafting_complexity * RAPTOR_MEAL_COMPLEXITY_GROWTH_FACTOR + (is_flora ? RAPTOR_GROWTH_BASE_PLANT : RAPTOR_GROWTH_BASE_MEAT)
growth_progress += growth_value * (1 + inherited_stats.growth_modifier) * (1 + happiness_percentage * RAPTOR_GROWTH_HAPPINESS_MULTIPLIER)
if (growth_progress >= RAPTOR_GROWTH_REQUIRED)
change_growth_stage(growth_stage == RAPTOR_BABY ? RAPTOR_YOUNG : RAPTOR_ADULT)
growth_progress = 0
/// Changes the raptor to a new growth stage. Only should be done forwards, or on raptor init as the first thing before everything else
/// Sorry for the monolith, but splitting it up results in even worse looking code with a ton of duplicate calls and assignments
/// And making a *second* datum is just insanity
/mob/living/basic/raptor/proc/change_growth_stage(new_stage, prev_stage = growth_stage)
if (new_stage == prev_stage)
return FALSE
if (SEND_SIGNAL(src, COMSIG_RAPTOR_GROWTH_STAGE_CHANGE, new_stage, prev_stage) & COMPONENT_CANCEL_RAPTOR_GROWTH)
return FALSE
growth_stage = new_stage
// Visuals
switch (new_stage)
if (RAPTOR_BABY)
name = "baby raptor"
desc = "Will this grow into something useful?"
icon = 'icons/mob/simple/lavaland/raptor_baby.dmi'
base_icon_state = "baby"
base_pixel_w = 0
mob_size = MOB_SIZE_TINY
if (RAPTOR_YOUNG)
name = "raptor youngling"
desc = "A young raptor that can grow into a robust, trusty steed. Rather naive at such an age, it shouldn't be too hard to tame."
icon = 'icons/mob/simple/lavaland/raptor_big.dmi'
base_icon_state = "young"
base_pixel_w = initial(base_pixel_w)
mob_size = MOB_SIZE_HUMAN
if (RAPTOR_ADULT)
name = "raptor"
desc = initial(desc)
icon = 'icons/mob/simple/lavaland/raptor_big.dmi'
base_icon_state = "raptor"
base_pixel_w = initial(base_pixel_w)
mob_size = initial(mob_size)
can_be_held = initial(density)
density = initial(density)
move_resist = initial(move_resist)
can_breed = initial(can_breed)
change_offsets = initial(change_offsets)
if (new_stage == RAPTOR_ADULT)
// Adults need to be tamed with skill rather than snacks
qdel(GetComponent(/datum/component/tameable))
else // Make us teeny-tiny
can_be_held = TRUE
density = FALSE
can_breed = FALSE
move_resist = MOVE_RESIST_DEFAULT
change_offsets = FALSE
if (prev_stage == RAPTOR_ADULT)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, unique = TRUE)
if (change_offsets)
adjust_offsets(dir)
else
remove_offsets(RAPTOR_INNATE_SOURCE, FALSE)
if (can_breed)
add_breeding_component()
else
qdel(GetComponent(/datum/component/breed))
var/obj/item/mob_holder/holder = null
if (istype(loc, /obj/item/mob_holder))
holder = loc
if (!can_be_held)
holder.release()
holder = null
if (collar_state)
RemoveElement(/datum/element/wears_collar, collar_icon = 'icons/mob/simple/lavaland/raptor_big.dmi', collar_icon_state = "[collar_state]_")
if (new_stage == RAPTOR_BABY)
collar_state = null
var/list/friends = ai_controller?.blackboard[BB_FRIENDS_LIST]
if (friends)
friends = friends.Copy()
QDEL_NULL(ai_controller)
ai_controller = new /datum/ai_controller/basic_controller/baby_raptor(src)
for (var/old_friend in friends)
ai_controller.insert_blackboard_key_lazylist(BB_FRIENDS_LIST, old_friend)
update_blackboard()
held_w_class = WEIGHT_CLASS_SMALL
worn_slot_flags = NONE
holder?.update_weight_class(held_w_class)
else
collar_state = base_icon_state
AddElement(/datum/element/wears_collar, collar_icon = 'icons/mob/simple/lavaland/raptor_big.dmi', collar_icon_state = "[collar_state]_")
if (prev_stage == RAPTOR_BABY)
var/list/friends = ai_controller?.blackboard[BB_FRIENDS_LIST]
if (friends)
friends = friends.Copy()
QDEL_NULL(ai_controller)
ai_controller = new raptor_color.ai_controller(src)
for (var/old_friend in friends)
ai_controller.insert_blackboard_key_lazylist(BB_FRIENDS_LIST, old_friend)
update_blackboard()
held_w_class = WEIGHT_CLASS_BULKY
worn_slot_flags = ITEM_SLOT_BACK
holder?.update_weight_class(held_w_class)
// And finish the setup on our color's side
switch (new_stage)
if (RAPTOR_BABY)
raptor_color.setup_baby(src)
if (RAPTOR_YOUNG)
raptor_color.setup_young(src)
if (RAPTOR_ADULT)
raptor_color.setup_adult(src)
return TRUE
/mob/living/basic/raptor/proc/update_blackboard()
var/static/list/display_emote = list(
BB_EMOTE_SAY = list("Chirp chirp chirp!", "Kweh!", "Bwark!"),
BB_EMOTE_SEE = list("shakes its feathers!", "stretches!", "flaps its wings!", "pecks at the ground!"),
@@ -110,235 +474,110 @@ GLOBAL_LIST_EMPTY(raptor_population)
)
ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, display_emote)
inherited_stats = new
inherit_properties()
var/list/my_food = string_list(list(/obj/item/stack/ore))
AddElement(/datum/element/basic_eating, food_types = my_food)
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/ai_flee_while_injured, stop_fleeing_at = 0.5, start_fleeing_below = 0.2)
if(ridable_component)
AddElement(/datum/element/ridable, ridable_component)
var/static/list/preferred_foods = typecacheof(list(
/obj/item/food/meat,
/obj/item/food/grown/ash_flora,
)) - typecacheof(list( // Don't seek out toxic foods
/obj/item/food/meat/slab/spider,
/obj/item/food/meat/slab/xeno,
))
if(can_breed)
add_breeding_component()
ai_controller.set_blackboard_key(BB_BASIC_FOODS, preferred_foods)
AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_CLAW)
RegisterSignal(src, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_dir_change))
adjust_offsets(dir)
add_happiness_component()
/mob/living/basic/raptor/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, buckle_mob_flags= NONE)
if(!iscarbon(target))
return
return ..()
/mob/living/basic/raptor/proc/add_happiness_component()
var/static/list/percentage_callbacks = list(0, 15, 25, 35, 50, 75, 90, 100)
AddComponent(\
/datum/component/happiness,\
on_petted_change = 100,\
on_groom_change = 100,\
on_eat_change = 400,\
callback_percentages = percentage_callbacks,\
happiness_callback = CALLBACK(src, PROC_REF(happiness_change)),\
)
/mob/living/basic/raptor/proc/on_dir_change(datum/source, old_dir, new_dir)
SIGNAL_HANDLER
adjust_offsets(new_dir)
/mob/living/basic/raptor/proc/adjust_offsets(direction)
if (!change_offsets)
return
switch (direction)
if (NORTH)
add_offsets(RAPTOR_INNATE_SOURCE, w_add = -8, animate = FALSE)
if (SOUTH)
add_offsets(RAPTOR_INNATE_SOURCE, w_add = 0, animate = FALSE)
if (EAST, SOUTHEAST, NORTHEAST)
add_offsets(RAPTOR_INNATE_SOURCE, w_add = -20, animate = FALSE)
if (WEST, SOUTHWEST, NORTHWEST)
add_offsets(RAPTOR_INNATE_SOURCE, w_add = -5, animate = FALSE)
/mob/living/basic/raptor/early_melee_attack(atom/target, list/modifiers, ignore_cooldown)
. = ..()
if(!.)
return FALSE
if(!istype(target, /obj/structure/ore_container/food_trough/raptor_trough))
return TRUE
var/obj/ore_food = locate(/obj/item/stack/ore) in target
if(isnull(ore_food))
balloon_alert(src, "no food!")
else
UnarmedAttack(ore_food, TRUE, modifiers)
return FALSE
/mob/living/basic/raptor/melee_attack(mob/living/target, list/modifiers, ignore_cooldown)
if(!combat_mode && istype(target, /mob/living/basic/raptor/baby_raptor))
target.attack_hand(src, list(LEFT_CLICK = TRUE))
return
return ..()
/mob/living/basic/raptor/death(gibbed)
. = ..()
GLOB.raptor_population -= REF(src)
/mob/living/basic/raptor/proc/happiness_change(percent_value)
var/attack_boost = round(initial(melee_damage_lower) * percent_value * HAPPINESS_BOOST_DAMPENER, 1)
melee_damage_lower = initial(melee_damage_lower) + attack_boost
melee_damage_upper = melee_damage_lower + 5
///pass down our inheritance to the egg
/mob/living/basic/raptor/proc/egg_inherit(obj/item/food/egg/raptor_egg/baby_egg, mob/living/basic/raptor/partner)
var/datum/raptor_inheritance/inherit = new
inherit.set_parents(inherited_stats, partner.inherited_stats)
baby_egg.inherited_stats = inherit
baby_egg.determine_growth_path(src, partner)
/mob/living/basic/raptor/proc/inherit_properties()
if(isnull(inherited_stats))
return
for(var/trait in GLOB.raptor_inherit_traits) // done this way to allow overriding of traits when assigned new inherit datum
var/should_inherit = (trait in inherited_stats.inherit_traits)
for(var/trait in GLOB.raptor_inherit_traits)
var/should_inherit = (trait in inherited_stats.personality_traits)
ai_controller?.set_blackboard_key(trait, should_inherit)
melee_damage_lower += inherited_stats.attack_modifier
melee_damage_upper += melee_damage_lower + 5
maxHealth += inherited_stats.health_modifier
heal_overall_damage(maxHealth)
/mob/living/basic/raptor/proc/add_breeding_component()
var/static/list/partner_types = typecacheof(list(/mob/living/basic/raptor))
var/static/list/baby_types = list(/obj/item/food/egg/raptor_egg = 1)
AddComponent(\
/datum/component/breed,\
can_breed_with = typecacheof(list(/mob/living/basic/raptor)),\
baby_paths = baby_types,\
post_birth = CALLBACK(src, PROC_REF(egg_inherit)),\
breed_timer = 3 MINUTES,\
)
/mob/living/basic/raptor/Destroy()
QDEL_NULL(inherited_stats)
return ..()
// Raptor types for mappers to use
/mob/living/basic/raptor/red
name = "red raptor"
icon_state = "raptor_red"
icon_living = "raptor_red"
icon_dead = "raptor_red_dead"
melee_damage_lower = 15
melee_damage_upper = 20
raptor_color = RAPTOR_RED
ridable_component = /datum/component/riding/creature/raptor/combat
dex_description = "A resilient breed of raptors, battle-tested and bred for the purpose of humbling its foes in combat, \
This breed demonstrates higher combat capabilities than its peers and oozes ruthless aggression."
child_path = /mob/living/basic/raptor/baby_raptor/red
raptor_color = /datum/raptor_color/red
/mob/living/basic/raptor/purple
name = "purple raptor"
icon_state = "raptor_purple"
icon_living = "raptor_purple"
icon_dead = "raptor_purple_dead"
raptor_color = RAPTOR_PURPLE
dex_description = "A dependable mount, bred for the purpose of long distance pilgrimages. This breed is also able to store its rider's possessions."
child_path = /mob/living/basic/raptor/baby_raptor/purple
/mob/living/basic/raptor/purple/Initialize(mapload)
. = ..()
create_storage(
max_specific_storage = WEIGHT_CLASS_NORMAL,
max_total_storage = 10,
storage_type = /datum/storage/raptor_storage,
)
raptor_color = /datum/raptor_color/purple
/mob/living/basic/raptor/green
name = "green raptor"
icon_state = "raptor_green"
icon_living = "raptor_green"
icon_dead = "raptor_green_dead"
maxHealth = 400
health = 400
raptor_color = RAPTOR_GREEN
dex_description = "A tough breed of raptor, made to withstand the harshest of punishment and to laugh in the face of pain, \
this breed is able to withstand more punishment than its peers."
child_path = /mob/living/basic/raptor/baby_raptor/green
/mob/living/basic/raptor/green/Initialize(mapload)
. = ..()
AddComponent(/datum/component/proficient_miner)
raptor_color = /datum/raptor_color/green
/mob/living/basic/raptor/white
name = "white raptor"
icon_state = "raptor_white"
icon_living = "raptor_white"
icon_dead = "raptor_white_dead"
raptor_color = RAPTOR_WHITE
dex_description = "A loving sort, it cares for it peers and rushes to their aid with reckless abandon. It is able to heal any raptors' ailments."
child_path = /mob/living/basic/raptor/baby_raptor/white
/mob/living/basic/raptor/white/Initialize(mapload)
. = ..()
AddComponent(\
/datum/component/healing_touch,\
heal_brute = melee_damage_upper,\
heal_burn = melee_damage_upper,\
heal_time = 0,\
valid_targets_typecache = typecacheof(list(/mob/living/basic/raptor)),\
)
raptor_color = /datum/raptor_color/white
/mob/living/basic/raptor/black
name = "black raptor"
icon_state = "raptor_black"
icon_living = "raptor_black"
icon_dead = "raptor_black_dead"
maxHealth = 400
health = 400
speed = 1.5
melee_damage_lower = 20
melee_damage_upper = 25
raptor_color = RAPTOR_BLACK
dex_description = "An ultra rare breed. Due to its sparse nature, not much is known about this sort. However it is said to possess many of its peers' abilities."
child_path = /mob/living/basic/raptor/baby_raptor/black
raptor_color = /datum/raptor_color/black
/mob/living/basic/raptor/yellow
name = "yellow raptor"
icon_state = "raptor_yellow"
icon_living = "raptor_yellow"
icon_dead = "raptor_yellow_dead"
speed = 1.5
raptor_color = RAPTOR_YELLOW
dex_description = "This breed possesses greasy fast speed, DEMON speed, making light work of long pilgrimages. It's said that a thunderclap could be heard when this breed reaches its maximum speed."
child_path = /mob/living/basic/raptor/baby_raptor/yellow
raptor_color = /datum/raptor_color/yellow
/mob/living/basic/raptor/blue
name = "blue raptor"
icon_state = "raptor_blue"
icon_living = "raptor_blue"
icon_dead = "raptor_blue_dead"
raptor_color = RAPTOR_BLUE
dex_description = "Known to produce nutritous and equally delicious milk, which is also said to possess healing properties."
child_path = /mob/living/basic/raptor/baby_raptor/blue
raptor_color = /datum/raptor_color/blue
/mob/living/basic/raptor/blue/Initialize(mapload)
. = ..()
AddComponent(\
/datum/component/udder,\
udder_type = /obj/item/udder/raptor,\
)
/mob/living/basic/raptor/young
growth_stage = RAPTOR_YOUNG
/datum/storage/raptor_storage
animated = FALSE
insert_on_attack = FALSE
/mob/living/basic/raptor/young/red
icon_state = "young_red"
raptor_color = /datum/raptor_color/red
/datum/storage/raptor_storage/on_mousedropped_onto(datum/source, obj/item/dropping, mob/user)
..()
return NONE
/mob/living/basic/raptor/young/purple
icon_state = "young_purple"
raptor_color = /datum/raptor_color/purple
#undef HAPPINESS_BOOST_DAMPENER
#undef RAPTOR_INNATE_SOURCE
/mob/living/basic/raptor/young/green
icon_state = "young_green"
raptor_color = /datum/raptor_color/green
/mob/living/basic/raptor/young/white
icon_state = "young_white"
raptor_color = /datum/raptor_color/white
/mob/living/basic/raptor/young/black
icon_state = "young_black"
raptor_color = /datum/raptor_color/black
/mob/living/basic/raptor/young/yellow
icon_state = "young_yellow"
raptor_color = /datum/raptor_color/yellow
/mob/living/basic/raptor/young/blue
icon_state = "young_blue"
raptor_color = /datum/raptor_color/blue
/mob/living/basic/raptor/baby
icon = 'icons/mob/simple/lavaland/raptor_baby.dmi'
growth_stage = RAPTOR_BABY
/mob/living/basic/raptor/baby/red
icon_state = "baby_red"
raptor_color = /datum/raptor_color/red
/mob/living/basic/raptor/baby/purple
icon_state = "baby_purple"
raptor_color = /datum/raptor_color/purple
/mob/living/basic/raptor/baby/green
icon_state = "baby_green"
raptor_color = /datum/raptor_color/green
/mob/living/basic/raptor/baby/white
icon_state = "baby_white"
raptor_color = /datum/raptor_color/white
/mob/living/basic/raptor/baby/black
icon_state = "baby_black"
raptor_color = /datum/raptor_color/black
/mob/living/basic/raptor/baby/yellow
icon_state = "baby_yellow"
raptor_color = /datum/raptor_color/yellow
/mob/living/basic/raptor/baby/blue
icon_state = "baby_blue"
raptor_color = /datum/raptor_color/blue
@@ -1,102 +0,0 @@
/mob/living/basic/raptor/baby_raptor
name = "baby raptor"
desc = "Will this grow into something useful?"
icon = 'icons/mob/simple/lavaland/raptor_baby.dmi'
speed = 5
maxHealth = 25
health = 25
melee_damage_lower = 3
melee_damage_upper = 5
density = FALSE
can_breed = FALSE
move_resist = MOVE_RESIST_DEFAULT
ai_controller = /datum/ai_controller/basic_controller/baby_raptor
ridable_component = null
change_offsets = FALSE
dex_description = "A cute baby raptor, Having it near a parent or a birds-nest could encourage it to grow faster, \
grooming it and feeding it could also ensure that it grows up quicker!"
///what do we grow into
var/growth_path
///probability we are to be rolled
var/roll_rate = 100
/mob/living/basic/raptor/baby_raptor/Initialize(mapload)
. = ..()
if(isnull(growth_path))
return
AddComponent(\
/datum/component/growth_and_differentiation,\
growth_time = null,\
growth_path = growth_path,\
growth_probability = 80,\
lower_growth_value = 0.5,\
upper_growth_value = 0.8,\
signals_to_kill_on = list(COMSIG_MOB_CLIENT_LOGIN),\
optional_checks = CALLBACK(src, PROC_REF(check_grow)),\
optional_grow_behavior = CALLBACK(src, PROC_REF(ready_to_grow)),\
)
/mob/living/basic/raptor/baby_raptor/add_happiness_component()
AddComponent(/datum/component/happiness, on_petted_change = 100)
/mob/living/basic/raptor/baby_raptor/proc/check_grow()
return (stat != DEAD)
/mob/living/basic/raptor/baby_raptor/proc/ready_to_grow()
var/mob/living/basic/raptor/grown_mob = new growth_path(get_turf(src))
QDEL_NULL(grown_mob.inherited_stats)
grown_mob.inherited_stats = inherited_stats
inherited_stats = null
grown_mob.inherit_properties()
ADD_TRAIT(grown_mob, TRAIT_MOB_HATCHED, INNATE_TRAIT) //pass on the hatched trait
qdel(src)
/mob/living/basic/raptor/baby_raptor/black
name = "baby black raptor"
icon_state = "baby_black"
icon_living = "baby_black"
icon_dead = "baby_black_dead"
growth_path = /mob/living/basic/raptor/black
roll_rate = 10
/mob/living/basic/raptor/baby_raptor/red
name = "baby red raptor"
icon_state = "baby_red"
icon_living = "baby_red"
icon_dead = "baby_red_dead"
growth_path = /mob/living/basic/raptor/red
/mob/living/basic/raptor/baby_raptor/purple
name = "baby purple raptor"
icon_state = "baby_purple"
icon_living = "baby_purple"
icon_dead = "baby_purple_dead"
growth_path = /mob/living/basic/raptor/purple
/mob/living/basic/raptor/baby_raptor/white
name = "baby white raptor"
icon_state = "baby_white"
icon_living = "baby_white"
icon_dead = "baby_white_dead"
growth_path = /mob/living/basic/raptor/white
/mob/living/basic/raptor/baby_raptor/yellow
name = "baby yellow raptor"
icon_state = "baby_yellow"
icon_living = "baby_yellow"
icon_dead = "baby_yellow_dead"
growth_path = /mob/living/basic/raptor/yellow
/mob/living/basic/raptor/baby_raptor/green
name = "baby green raptor"
icon_state = "baby_green"
icon_living = "baby_green"
icon_dead = "baby_green_dead"
growth_path = /mob/living/basic/raptor/green
/mob/living/basic/raptor/baby_raptor/blue
name = "baby blue raptor"
icon_state = "baby_blue"
icon_living = "baby_blue"
icon_dead = "baby_blue_dead"
growth_path = /mob/living/basic/raptor/blue
@@ -8,7 +8,10 @@
return (source != target && target.health < target.maxHealth)
/datum/ai_behavior/find_hunt_target/raptor_baby/valid_dinner(mob/living/source, mob/living/target, radius)
return can_see(source, target, radius) && target.stat != DEAD
if (!can_see(source, target, radius) || target.stat == DEAD || !istype(target, /mob/living/basic/raptor))
return FALSE
var/mob/living/basic/raptor/raptor = target
return raptor.growth_stage == RAPTOR_BABY
/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off/care_for_young
@@ -21,3 +24,15 @@
/datum/ai_behavior/find_hunt_target/raptor_trough/valid_dinner(mob/living/source, atom/movable/trough, radius)
return !!(locate(/obj/item/stack/ore) in trough.contents)
/datum/ai_behavior/find_injured_rider/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range)
var/mob/living/living_mob = controller.pawn
if (!length(living_mob.buckled_mobs) || !isliving(living_mob.buckled_mobs[1]))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
var/mob/living/rider = living_mob.buckled_mobs[1]
if (rider.stat == CONSCIOUS || rider.stat == DEAD || rider.health >= rider.maxHealth)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(hunting_target_key, rider)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
@@ -9,8 +9,8 @@
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/raptor),
BB_BABIES_CHILD_TYPES = list(/mob/living/basic/raptor/baby_raptor),
BB_MAX_CHILDREN = 5,
BB_RAPTOR_FLEE_THRESHOLD = 0.25,
)
ai_movement = /datum/ai_movement/basic_avoidance
@@ -19,6 +19,7 @@
/datum/ai_planning_subtree/escape_captivity,
/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor,
/datum/ai_planning_subtree/flee_target/from_flee_key,
/datum/ai_planning_subtree/find_and_hunt_target/heal_rider,
/datum/ai_planning_subtree/find_and_hunt_target/heal_raptors,
/datum/ai_planning_subtree/random_speech/blackboard,
/datum/ai_planning_subtree/pet_planning,
@@ -26,6 +27,42 @@
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/find_and_hunt_target/raptor_trough,
/datum/ai_planning_subtree/find_food,
/datum/ai_planning_subtree/find_and_hunt_target/care_for_young,
/datum/ai_planning_subtree/make_babies,
/datum/ai_planning_subtree/express_happiness,
/datum/ai_planning_subtree/find_and_hunt_target/play_with_owner/raptor,
)
/// Angry raptors with no faction check on retaliation
/datum/ai_controller/basic_controller/raptor/aggressive
blackboard = list(
BB_INTERACTIONS_WITH_OWNER = list(
"pecks",
"nuzzles",
"wags their tail against",
"playfully leans against"
),
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/exact_match,
BB_HUNT_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/raptor),
BB_MAX_CHILDREN = 5,
BB_RAPTOR_FLEE_THRESHOLD = 0.1,
)
planning_subtrees = list(
/datum/ai_planning_subtree/escape_captivity,
/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor,
/datum/ai_planning_subtree/flee_target/from_flee_key,
/datum/ai_planning_subtree/find_and_hunt_target/heal_raptors,
/datum/ai_planning_subtree/random_speech/blackboard,
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/target_retaliate,
/datum/ai_planning_subtree/simple_find_target/hunt,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/find_and_hunt_target/raptor_trough,
/datum/ai_planning_subtree/find_food,
/datum/ai_planning_subtree/find_and_hunt_target/care_for_young,
/datum/ai_planning_subtree/make_babies,
/datum/ai_planning_subtree/express_happiness,
@@ -55,7 +92,6 @@
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
BB_FIND_MOM_TYPES = list(/mob/living/basic/raptor),
BB_IGNORE_MOM_TYPES = list(/mob/living/basic/raptor/baby_raptor),
)
ai_movement = /datum/ai_movement/basic_avoidance
@@ -67,5 +103,5 @@
/datum/ai_planning_subtree/random_speech/blackboard,
/datum/ai_planning_subtree/find_and_hunt_target/raptor_trough,
/datum/ai_planning_subtree/express_happiness,
/datum/ai_planning_subtree/look_for_adult,
/datum/ai_planning_subtree/look_for_adult/raptor,
)
@@ -11,19 +11,41 @@
return
return ..()
/datum/ai_planning_subtree/find_and_hunt_target/heal_rider
target_key = BB_INJURED_RAPTOR
hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/heal_raptor
finding_behavior = /datum/ai_behavior/find_injured_rider
hunt_targets = list(/mob/living/carbon/human)
/datum/ai_planning_subtree/find_and_hunt_target/heal_rider/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
if(!controller.blackboard[BB_BASIC_MOB_HEALER])
return
return ..()
/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor
target_key = BB_BASIC_MOB_FLEE_TARGET
/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
if(!controller.blackboard[BB_RAPTOR_COWARD])
if(controller.blackboard[BB_RAPTOR_COWARD])
return ..()
var/mob/living/basic/raptor/raptor = controller.pawn
if (raptor.health <= raptor.maxHealth * controller.blackboard[BB_RAPTOR_FLEE_THRESHOLD])
return ..()
if (!length(raptor.buckled_mobs))
return
return ..()
var/mob/living/buckled_to = raptor.buckled_mobs[1]
// Flee if our owner is badly injured and out
if (buckled_to.stat != CONSCIOUS && buckled_to.stat != DEAD)
return ..()
/datum/ai_planning_subtree/find_and_hunt_target/care_for_young
target_key = BB_RAPTOR_BABY
hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off/care_for_young
finding_behavior = /datum/ai_behavior/find_hunt_target/raptor_baby
hunt_targets = list(/mob/living/basic/raptor/baby_raptor)
hunt_targets = list(/mob/living/basic/raptor)
hunt_chance = 75
hunt_range = 9
@@ -0,0 +1,439 @@
GLOBAL_LIST_INIT(raptor_colors, init_raptor_colors())
/proc/init_raptor_colors()
var/list/colors = list()
for (var/datum/raptor_color/color_type as anything in subtypesof(/datum/raptor_color))
colors[color_type] = new color_type()
return colors
/datum/raptor_color
/// Color name used for raptor name and icon
var/color = "error"
/// RaptorDex description for the raptor
var/description = "Report this!"
/// Raptor's health
var/health = 220
/// Raptor's speed
var/speed = 0.5
// Minimal and maximal damage for the raptor
var/melee_damage_lower = 10
var/melee_damage_upper = 15
/// Does this raptor redirect projectile hits onto its rider?
var/redirect_shots = TRUE
/// Rideable component type to assign to the raptor
var/rideable_component = /datum/component/riding/creature/raptor
/// Guaranteed cross-breeding colors, other parent color -> child color
var/guaranteed_crossbreeds = list()
/// Type of AI controller the raptor uses
var/datum/ai_controller/ai_controller = /datum/ai_controller/basic_controller/raptor
/// Chance that a newborn baby raptor will be of this color
var/spawn_chance = 33
/// Shared proc, only called once on raptor init for color-specific traits and properties
/datum/raptor_color/proc/setup_raptor(mob/living/basic/raptor/raptor)
if (raptor.ai_controller)
CRASH("setup_raptor called on a raptor ([raptor]) with a present AI controller! This is most likely a result of a second call to setup_raptor.")
raptor.ai_controller = new ai_controller(raptor)
/datum/raptor_color/proc/setup_adult(mob/living/basic/raptor/raptor)
var/datum/raptor_inheritance/stats = raptor.inherited_stats
var/real_health = health + stats.health_modifier
// If we grow up while damaged, keep the damage percentage the same
raptor.health *= real_health / raptor.maxHealth
raptor.maxHealth = real_health
// -0.33 ~ 0.33 gets rounded to 0 rather than +-0.5
var/speed_mod = clamp(round(abs(stats.speed_modifier - 0.08), 0.5) * sign(stats.speed_modifier), -0.5, 0.5)
raptor.set_varspeed(speed - speed_mod)
raptor.melee_damage_lower = melee_damage_lower + stats.attack_modifier
raptor.melee_damage_upper = melee_damage_upper + stats.attack_modifier
if (rideable_component)
raptor.AddElement(/datum/element/ridable, rideable_component)
setup_appearance(raptor)
/datum/raptor_color/proc/setup_young(mob/living/basic/raptor/raptor)
var/datum/raptor_inheritance/stats = raptor.inherited_stats
var/real_health = health + stats.health_modifier
raptor.health *= real_health / 2 / raptor.maxHealth
raptor.maxHealth = real_health / 2
var/speed_mod = clamp(round(abs(stats.speed_modifier - 0.08), 0.5) * sign(stats.speed_modifier), -0.5, 0.5)
raptor.set_varspeed(speed - speed_mod)
raptor.melee_damage_lower = floor((melee_damage_lower + stats.attack_modifier) / 2)
raptor.melee_damage_upper = floor((melee_damage_upper + stats.attack_modifier) / 2)
setup_appearance(raptor)
/datum/raptor_color/proc/setup_baby(mob/living/basic/raptor/raptor)
var/datum/raptor_inheritance/stats = raptor.inherited_stats
var/real_health = health + stats.health_modifier
raptor.health *= real_health / 8 / raptor.maxHealth
raptor.maxHealth = real_health / 8
var/speed_mod = clamp(round(abs(stats.speed_modifier - 0.08), 0.5) * sign(stats.speed_modifier), -0.5, 0.5)
raptor.set_varspeed(speed + 4.5 - speed_mod)
raptor.melee_damage_lower = floor((melee_damage_lower + stats.attack_modifier) / 3)
raptor.melee_damage_upper = floor((melee_damage_upper + stats.attack_modifier) / 3)
setup_appearance(raptor)
/datum/raptor_color/proc/setup_appearance(mob/living/basic/raptor/raptor)
raptor.name = "[color] [raptor.name]"
raptor.icon_state = "[raptor.base_icon_state]_[color]"
raptor.held_state = "[raptor.base_icon_state]_[color]"
raptor.icon_living = "[raptor.base_icon_state]_[color]"
raptor.icon_dead = "[raptor.base_icon_state]_[color]_dead"
raptor.update_appearance()
/datum/raptor_color/red
color = "red"
description = "A resilient breed of raptors, battle-tested and bred for the purpose of humbling its foes in combat, \
This breed demonstrates higher combat capabilities than its peers and oozes ruthless aggression."
melee_damage_lower = 15
melee_damage_upper = 20
health = 300
rideable_component = /datum/component/riding/creature/raptor/combat
redirect_shots = FALSE
guaranteed_crossbreeds = list(
/datum/raptor_color/green = /datum/raptor_color/yellow,
/datum/raptor_color/blue = /datum/raptor_color/purple,
)
// Doesn't care for your excuses for friendly fire
ai_controller = /datum/ai_controller/basic_controller/raptor/aggressive
/datum/raptor_color/purple
color = "purple"
description = "A small, nimble breed, these raptors have been bred as travel companions rather than mounts, capable of storing the owner's possessions and helping them escape from danger unscathed."
health = 140 // smol
rideable_component = /datum/component/riding/creature/raptor/small
guaranteed_crossbreeds = list(
/datum/raptor_color/green = /datum/raptor_color/white,
/datum/raptor_color/yellow = /datum/raptor_color/blue,
)
/datum/raptor_color/purple/setup_raptor(mob/living/basic/raptor/raptor)
. = ..()
RegisterSignal(raptor, COMSIG_MOVABLE_PREBUCKLE, PROC_REF(on_pre_buckle))
raptor.inhand_holder_type = /obj/item/mob_holder/purple_raptor
/datum/raptor_color/purple/proc/on_pre_buckle(mob/living/basic/raptor/source, mob/living/potential_rider, force = FALSE, ride_check_flags = NONE)
SIGNAL_HANDLER
if (!ishuman(potential_rider))
return COMPONENT_BLOCK_BUCKLE
var/mob/living/carbon/human/rider = potential_rider
if (rider.mob_height > HUMAN_HEIGHT_SHORTEST)
to_chat(rider, span_warning("Your tall stature will crush [source] were you attempt to ride [source.p_them()]!"))
return COMPONENT_BLOCK_BUCKLE
// Purple raptors never "fully" grow up, and remain usable as backpacks
/datum/raptor_color/purple/setup_adult(mob/living/basic/raptor/raptor)
raptor.base_pixel_w = initial(raptor.base_pixel_w)
raptor.can_be_held = TRUE
raptor.density = FALSE
raptor.move_resist = MOVE_RESIST_DEFAULT
raptor.change_offsets = FALSE
raptor.remove_offsets(RAPTOR_INNATE_SOURCE, FALSE)
raptor.held_w_class = WEIGHT_CLASS_BULKY
. = ..()
// Non-shorties cannot ride these, so we gotta keep em tameable through food
raptor.AddComponent(/datum/component/tameable, food_types = raptor.food_types, tame_chance = 25, bonus_tame_chance = 15, unique = TRUE)
if (raptor.atom_storage)
return
// A bit bigger (23 vs 21) than a backpack at max size, a bit less by default
var/storage_volume = floor(19 * (1 + raptor.inherited_stats.ability_modifier))
raptor.create_storage(
max_total_storage = storage_volume,
max_slots = storage_volume,
storage_type = /datum/storage/raptor_storage,
)
/datum/raptor_color/purple/setup_young(mob/living/basic/raptor/raptor)
. = ..()
if (raptor.atom_storage)
return
var/storage_volume = floor(19 * (1 + raptor.inherited_stats.ability_modifier))
raptor.create_storage(
max_total_storage = storage_volume,
max_slots = storage_volume,
storage_type = /datum/storage/raptor_storage,
)
/obj/item/mob_holder/purple_raptor
/// Wings action granted to whoever is wearing us on their back
var/datum/action/innate/raptor_wings/flight_action = null
/// Are our wings open?
var/wings_open = FALSE
/// Wings underlay added to the owner, because human rendering code is a mess
var/mutable_appearance/wings_underlay = null
/// Our drift force
var/drift_force = 2 NEWTONS
/// Our stabilizing force
var/stabilizer_force = 4.5 NEWTONS
/obj/item/mob_holder/purple_raptor/Initialize(mapload, mob/living/held_mob, worn_state, head_icon, lh_icon, rh_icon, worn_slot_flags)
. = ..()
var/mob/living/basic/raptor/raptor = held_mob
if (raptor.growth_stage == RAPTOR_BABY)
return
// Create a mirror storage for our raptor when picked up to handle interactions
var/datum/storage/raptor_storage = create_storage(
max_total_storage = held_mob.atom_storage.max_total_storage,
max_slots = held_mob.atom_storage.max_slots,
storage_type = /datum/storage/raptor_storage,
)
raptor_storage.set_real_location(held_mob)
raptor_storage.insert_on_attack = TRUE
if (raptor.growth_stage != RAPTOR_ADULT)
return
flight_action = new(src)
AddComponent( \
/datum/component/jetpack, \
TRUE, \
drift_force, \
stabilizer_force, \
COMSIG_RAPTOR_WINGS_OPENED, \
COMSIG_RAPTOR_WINGS_CLOSED, \
null, \
CALLBACK(src, PROC_REF(can_fly)), \
CALLBACK(src, PROC_REF(can_fly)), \
)
/obj/item/mob_holder/purple_raptor/Destroy()
if (ishuman(loc) && wings_open)
toggle_wings(loc)
QDEL_NULL(flight_action)
return ..()
/obj/item/mob_holder/purple_raptor/equipped(mob/user, slot, initial)
. = ..()
if ((slot & ITEM_SLOT_BACK) && ishuman(user) && flight_action)
flight_action.Grant(held_mob)
flight_action.GiveAction(user)
/obj/item/mob_holder/purple_raptor/dropped(mob/user, silent)
. = ..()
if (wings_open)
toggle_wings(user)
// Removed in Destroy()
if (flight_action)
flight_action.Remove(held_mob)
flight_action.HideFrom(user)
/obj/item/mob_holder/purple_raptor/proc/on_weight_updated(mob/living/carbon/human/source)
SIGNAL_HANDLER
if (source.mob_height <= HUMAN_HEIGHT_SHORTEST && !HAS_TRAIT(source, TRAIT_FAT))
source.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/raptor/slow)
source.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/raptor)
else
source.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/raptor)
source.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/raptor/slow)
/obj/item/mob_holder/purple_raptor/proc/can_fly()
var/mob/living/carbon/human/user = loc
if (!istype(user) || user.stat || user.body_position == LYING_DOWN || isnull(user.client))
return FALSE
if (user.get_organ_slot(ORGAN_SLOT_EXTERNAL_WINGS))
return FALSE
var/turf/location = get_turf(user)
if (!istype(location))
return FALSE
var/datum/gas_mixture/environment = location.return_air()
if (environment?.return_pressure() >= HAZARD_LOW_PRESSURE + 10)
return TRUE
to_chat(user, span_warning("The atmosphere is too thin for you to fly!"))
return FALSE
/obj/item/mob_holder/purple_raptor/proc/toggle_wings(mob/living/carbon/human/user)
// In case something goes wrong
if (!istype(user))
wings_open = FALSE
worn_icon_state = icon_state
SEND_SIGNAL(src, COMSIG_RAPTOR_WINGS_CLOSED, user)
STOP_PROCESSING(SSprocessing, src)
return
if (!wings_open && !can_fly())
return
wings_open = !wings_open
worn_icon_state = "[icon_state][wings_open ? "_wings_out" : ""]"
user.update_worn_back()
// Raptors won't have the best of times keeping up tall humans or fatties up in the air
var/struggling = HAS_TRAIT(user, TRAIT_FAT) || user.mob_height > HUMAN_HEIGHT_SHORTEST
if (wings_open)
wings_underlay = user.apply_height_offsets(mutable_appearance(worn_icon, "raptor_purple_wings", -BODY_BEHIND_LAYER, user), UPPER_BODY)
user.add_overlay(wings_underlay)
user.physiology.stun_mod *= 2
user.add_traits(list(TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), REF(src))
if (struggling)
user.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/raptor/slow)
else
user.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/raptor)
user.AddElement(/datum/element/forced_gravity, 0)
passtable_on(user, REF(src))
to_chat(user, span_notice("You begin gently hovering above ground as [held_mob] on your back starts furiously flapping [held_mob.p_their()] wings[struggling ? ", struggling to keep you up in the air" : ""]!"))
user.set_resting(FALSE, TRUE)
user.refresh_gravity()
START_PROCESSING(SSprocessing, src)
RegisterSignals(user, list(COMSIG_HUMAN_HEIGHT_UPDATED, SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT)), PROC_REF(on_weight_updated))
SEND_SIGNAL(src, COMSIG_RAPTOR_WINGS_OPENED, user)
return
user.cut_overlay(wings_underlay)
QDEL_NULL(wings_underlay)
user.physiology.stun_mod *= 0.5
user.remove_traits(list(TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), REF(src))
user.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/raptor/slow)
user.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/raptor)
user.RemoveElement(/datum/element/forced_gravity, 0)
passtable_off(user, REF(src))
to_chat(user, span_notice("You settle gently back onto the ground[struggling ? ", [held_mob] on your back breathing out a sigh of releif" : ""]..."))
user.refresh_gravity()
STOP_PROCESSING(SSprocessing, src)
UnregisterSignal(user, list(COMSIG_HUMAN_HEIGHT_UPDATED, SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT)))
SEND_SIGNAL(src, COMSIG_RAPTOR_WINGS_CLOSED, user)
/obj/item/mob_holder/purple_raptor/process(seconds_per_tick)
if (!can_fly())
toggle_wings(loc)
return PROCESS_KILL
/datum/storage/raptor_storage
animated = FALSE
insert_on_attack = FALSE // should flip when worn on the back
/datum/storage/raptor_storage/on_mousedropped_onto(datum/source, obj/item/dropping, mob/user)
return NONE
/datum/action/innate/raptor_wings
name = "Toggle Flight"
check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_IMMOBILE | AB_CHECK_INCAPACITATED
button_icon = 'icons/mob/actions/actions_animal.dmi'
button_icon_state = "raptor_wings"
/datum/action/innate/raptor_wings/Activate()
var/obj/item/mob_holder/purple_raptor/holder = target
var/mob/living/carbon/human/user = holder.loc
if (istype(user) && user.get_item_by_slot(ITEM_SLOT_BACK) == holder)
holder.toggle_wings(user)
/datum/raptor_color/green
color = "green"
description = "A tough breed of raptor, made to withstand the harshest of punishment and to laugh in the face of pain, \
this breed is able to withstand more punishment than its peers."
health = 400
// redirect_shots = FALSE // Need to figure out if I want this or not here
guaranteed_crossbreeds = list(
/datum/raptor_color/purple = /datum/raptor_color/white,
/datum/raptor_color/red = /datum/raptor_color/yellow,
)
/datum/raptor_color/green/setup_adult(mob/living/basic/raptor/raptor)
. = ..()
var/ability_scale = 1 - INVERSE_LERP(RAPTOR_INHERIT_MIN_MODIFIER, RAPTOR_INHERIT_MAX_MODIFIER, raptor.inherited_stats.ability_modifier)
var/mining_mod = round(ability_scale * 0.1, 0.025)
if (ability_scale >= 0.75)
mining_mod = 0
raptor.AddComponent(/datum/component/proficient_miner, mining_mod, TRUE)
/datum/raptor_color/white
color = "white"
description = "A loving sort, it cares for it peers and rushes to their aid with reckless abandon. It is able to heal any raptors' ailments, and rescue its owner in case of an emergency."
rideable_component = /datum/component/riding/creature/raptor/healer
guaranteed_crossbreeds = list(
/datum/raptor_color/blue = /datum/raptor_color/green,
/datum/raptor_color/yellow = /datum/raptor_color/red,
)
/datum/raptor_color/white/setup_young(mob/living/basic/raptor/raptor)
. = ..()
raptor.AddComponent( \
/datum/component/healing_touch, \
heal_brute = melee_damage_upper * 0.75 * (1 + raptor.inherited_stats.ability_modifier), \
heal_burn = melee_damage_upper * 0.75 * (1 + raptor.inherited_stats.ability_modifier), \
heal_time = 0, \
valid_targets_typecache = typecacheof(list(/mob/living/basic/raptor)), \
)
/datum/raptor_color/white/setup_adult(mob/living/basic/raptor/raptor)
. = ..()
qdel(raptor.GetComponent(/datum/component/healing_touch))
raptor.AddComponent( \
/datum/component/healing_touch, \
heal_brute = melee_damage_upper * (1 + raptor.inherited_stats.ability_modifier), \
heal_burn = melee_damage_upper * (1 + raptor.inherited_stats.ability_modifier), \
heal_time = 0, \
valid_targets_typecache = typecacheof(list(/mob/living/basic/raptor, /mob/living/carbon/human)), \
extra_checks = CALLBACK(src, PROC_REF(heal_checks)), \
healing_multiplier = CALLBACK(src, PROC_REF(heal_multiplier)), \
)
/datum/raptor_color/white/proc/heal_checks(mob/living/healer, mob/living/target)
if (istype(target, /mob/living/basic/raptor))
return TRUE
// Only heal raptors, or critted rider
if (target.stat == CONSCIOUS || target.stat == DEAD)
return FALSE
return target.buckled == healer
/datum/raptor_color/white/proc/heal_multiplier(mob/living/healer, mob/living/target)
if (istype(target, /mob/living/basic/raptor))
return 1
// The healing is slow so this is fine
return 0.67
/datum/raptor_color/yellow
color = "yellow"
description = "This breed possesses greasy fast speed, DEMON speed, making light work of long pilgrimages. \
It's said that a thunderclap could be heard when this breed reaches its maximum speed."
speed = 0
guaranteed_crossbreeds = list(
/datum/raptor_color/purple = /datum/raptor_color/blue,
/datum/raptor_color/white = /datum/raptor_color/red,
)
/datum/raptor_color/blue
color = "blue"
description = "Covered in tough, lava-resistant feathers with thick insulated fur underneath, this breed is capable of marching through lava and fire alike."
guaranteed_crossbreeds = list(
/datum/raptor_color/red = /datum/raptor_color/purple,
/datum/raptor_color/white = /datum/raptor_color/green,
)
/datum/raptor_color/blue/setup_raptor(mob/living/basic/raptor/raptor)
. = ..()
raptor.add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_NOFIRE_SPREAD), INNATE_TRAIT)
/datum/raptor_color/black
color = "black"
description = "An ultra rare breed. Due to its sparse nature, not much is known about this sort. However it is said to possess many of its peers' abilities."
health = 400
speed = 0
melee_damage_lower = 20
melee_damage_upper = 25
redirect_shots = FALSE
rideable_component = /datum/component/riding/creature/raptor/combat
ai_controller = /datum/ai_controller/basic_controller/raptor/aggressive
spawn_chance = 1 // 1 in 150 chance without modifiers
/datum/raptor_color/black/setup_raptor(mob/living/basic/raptor/raptor)
. = ..()
raptor.add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_NOFIRE_SPREAD), INNATE_TRAIT)
/datum/raptor_color/black/setup_adult(mob/living/basic/raptor/raptor)
. = ..()
// Slightly worse than greens at this
var/ability_scale = 1 - INVERSE_LERP(RAPTOR_INHERIT_MIN_MODIFIER, RAPTOR_INHERIT_MAX_MODIFIER, raptor.inherited_stats.ability_modifier)
var/mining_mod = round(ability_scale * 0.2, 0.025)
if (ability_scale >= 0.8)
mining_mod = 0
raptor.AddComponent(/datum/component/proficient_miner, mining_mod, TRUE)
@@ -1,62 +1,64 @@
/obj/item/raptor_dex
name = "raptor Dex"
name = "RaptorDex"
desc = "A device used to analyze lavaland raptors!"
icon = 'icons/obj/devices/scanner.dmi'
icon_state = "raptor_dex"
item_flags = NOBLUDGEON
///current raptor we are analyzing
var/datum/weakref/raptor
/// Raptor scan data we have stored
var/list/scan_data = list("raptor_scan" = FALSE)
/obj/item/raptor_dex/ui_interact(mob/user, datum/tgui/ui)
if(isnull(raptor?.resolve()))
balloon_alert(user, "no specimen data!")
return TRUE
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "RaptorDex")
ui.open()
/obj/item/raptor_dex/ui_static_data(mob/user)
var/list/data = list()
var/mob/living/basic/raptor/my_raptor = raptor.resolve()
data["raptor_image"] = icon2base64(icon(icon = my_raptor.icon, icon_state = my_raptor.icon_state, frame = 1))
data["raptor_attack"] = my_raptor.melee_damage_lower
data["raptor_health"] = my_raptor.maxHealth
data["raptor_speed"] = my_raptor.speed
data["raptor_color"] = my_raptor.name
data["raptor_gender"] = my_raptor.gender
data["raptor_description"] = my_raptor.dex_description
var/happiness_percentage = my_raptor.ai_controller?.blackboard[BB_BASIC_HAPPINESS]
var/obj/effect/overlay/happiness_overlay/display = new
display.set_hearts(happiness_percentage)
display.pixel_y = ICON_SIZE_Y * 0.5
data["raptor_happiness"] = icon2base64(getFlatIcon(display, no_anim = TRUE))
qdel(display)
var/datum/raptor_inheritance/inherit = my_raptor.inherited_stats
if(isnull(inherit))
return data
data["inherited_attack"] = inherit.attack_modifier
data["inherited_attack_max"] = RAPTOR_INHERIT_MAX_ATTACK
data["inherited_health"] = inherit.health_modifier
data["inherited_health_max"] = RAPTOR_INHERIT_MAX_HEALTH
data["inherited_traits"] = list()
for(var/index in inherit.inherit_traits)
data["inherited_traits"] += GLOB.raptor_inherit_traits[index]
return data
/obj/item/raptor_dex/ui_data(mob/user)
return scan_data
/obj/item/raptor_dex/interact_with_atom(atom/attacked_atom, mob/living/user)
if(!istype(attacked_atom, /mob/living/basic/raptor))
return NONE
raptor = WEAKREF(attacked_atom)
var/mob/living/basic/raptor/my_raptor = attacked_atom
var/datum/movespeed_modifier/intent_mod = my_raptor.get_move_intent_slowdown()
scan_data = list()
scan_data["raptor_scan"] = TRUE
scan_data["raptor_image"] = icon2base64(icon(icon = my_raptor.icon, icon_state = my_raptor.icon_state, dir = SOUTH, frame = 1))
scan_data["raptor_attack"] = my_raptor.melee_damage_lower
scan_data["raptor_health"] = my_raptor.health
scan_data["raptor_max_health"] = my_raptor.maxHealth
scan_data["raptor_speed"] = my_raptor.speed + intent_mod?.multiplicative_slowdown
scan_data["raptor_color"] = my_raptor.name
scan_data["raptor_gender"] = my_raptor.gender
scan_data["raptor_description"] = my_raptor.raptor_color.description
var/happiness_percentage = my_raptor.ai_controller?.blackboard[BB_BASIC_HAPPINESS]
var/obj/effect/overlay/happiness_overlay/display = new()
display.set_hearts(happiness_percentage)
display.pixel_y = ICON_SIZE_Y * 0.5
scan_data["raptor_happiness"] = icon2base64(getFlatIcon(display, no_anim = TRUE))
qdel(display)
var/datum/raptor_inheritance/inherit = my_raptor.inherited_stats
if(!isnull(inherit))
scan_data["inherited_attack"] = inherit.attack_modifier
scan_data["inherited_attack_max"] = RAPTOR_INHERIT_MAX_ATTACK
scan_data["inherited_health"] = inherit.health_modifier
scan_data["inherited_health_max"] = RAPTOR_INHERIT_MAX_HEALTH
scan_data["inherited_speed"] = inherit.speed_modifier
scan_data["inherited_speed_max"] = RAPTOR_INHERIT_MAX_SPEED
scan_data["inherited_ability"] = inherit.ability_modifier
scan_data["inherited_ability_max"] = RAPTOR_INHERIT_MAX_MODIFIER
scan_data["inherited_growth"] = inherit.growth_modifier
scan_data["inherited_growth_max"] = RAPTOR_INHERIT_MAX_MODIFIER
scan_data["inherited_traits"] = list()
for(var/index in inherit.personality_traits)
scan_data["inherited_traits"] += GLOB.raptor_inherit_traits[index]
playsound(src, 'sound/mobs/non-humanoids/orbie/orbie_send_out.ogg', 20)
balloon_alert(user, "scanned")
balloon_alert(my_raptor, "scanned")
ui_interact(user)
return ITEM_INTERACT_SUCCESS
@@ -1,58 +1,52 @@
/obj/item/food/egg/raptor_egg
name = "raptor egg"
desc = "An uneven egg with a rough, thick shell."
icon = 'icons/mob/simple/lavaland/raptor_baby.dmi'
icon_state = "raptor_egg"
///inheritance datum to pass on to the child
var/datum/raptor_inheritance/inherited_stats
resistance_flags = LAVA_PROOF|FIRE_PROOF
/// Color typepath of the child we spawn
var/datum/raptor_color/child_color = /datum/raptor_color
/// Inheritance data to pass onto the child
var/datum/raptor_inheritance/inherited_stats = null
/// Current growth progress
var/growth_progress = 0
/// Minimum growth progress per second
var/min_growth_rate = 0.5
/// Maximum growth progress per second
var/max_growth_rate = 1
/obj/item/food/egg/raptor_egg/Initialize(mapload)
. = ..()
if(SSmapping.is_planetary())
icon = 'icons/mob/simple/lavaland/raptor_icebox.dmi'
/obj/item/food/egg/raptor_egg/proc/determine_growth_path(mob/living/basic/raptor/dad, mob/living/basic/raptor/mom)
if(dad.type == mom.type)
add_growth_component(dad.child_path)
return
var/dad_color = dad.raptor_color
var/mom_color = mom.raptor_color
var/list/my_colors = list(dad_color, mom_color)
sortTim(my_colors, GLOBAL_PROC_REF(cmp_text_asc))
for(var/path in GLOB.raptor_growth_paths) //guaranteed spawns
var/list/required_colors = GLOB.raptor_growth_paths[path]
if(!compare_list(my_colors, required_colors))
continue
add_growth_component(path)
return
var/list/valid_subtypesof = list()
var/static/list/all_subtypes = subtypesof(/mob/living/basic/raptor/baby_raptor)
for(var/path in all_subtypes)
var/mob/living/basic/raptor/baby_raptor/raptor_path = path
if(!prob(initial(raptor_path.roll_rate)))
continue
valid_subtypesof += raptor_path
add_growth_component(pick(valid_subtypesof))
/obj/item/food/egg/raptor_egg/proc/add_growth_component(growth_path)
if(length(GLOB.raptor_population) >= MAX_RAPTOR_POP)
return
AddComponent(\
/datum/component/fertile_egg,\
embryo_type = growth_path,\
minimum_growth_rate = 0.5,\
maximum_growth_rate = 1,\
total_growth_required = 100,\
current_growth = 0,\
location_allowlist = typecacheof(list(/turf)),\
post_hatch = CALLBACK(src, PROC_REF(post_hatch)),\
)
/obj/item/food/egg/raptor_egg/proc/post_hatch(mob/living/basic/raptor/baby)
if(!istype(baby))
return
QDEL_NULL(baby.inherited_stats)
baby.inherited_stats = inherited_stats
inherited_stats = null
START_PROCESSING(SSobj, src)
/obj/item/food/egg/raptor_egg/Destroy()
STOP_PROCESSING(SSobj, src)
QDEL_NULL(inherited_stats)
return ..()
/obj/item/food/egg/raptor_egg/examine(mob/user)
. = ..()
if (growth_progress >= RAPTOR_EGG_GROWTH_PROGRESS)
. += span_boldnotice("Its noticeably shaking, ready to hatch!")
/obj/item/food/egg/raptor_egg/process(seconds_per_tick)
if (!isturf(loc) || length(GLOB.raptor_population) >= MAX_RAPTOR_POP)
return
var/growth_value = rand(min_growth_rate, max_growth_rate) * seconds_per_tick * (1 + inherited_stats.growth_modifier)
// Slower growth off hot lavaland
if (!SSmapping.level_trait(z, ZTRAIT_ASHSTORM))
growth_value *= 0.75
// Faster growth in hot hot lava
if (islava(loc))
growth_value *= 1.5
growth_progress += growth_value
// Don't hatch on lava though, or the chick will die instantly
if (growth_progress < RAPTOR_EGG_GROWTH_PROGRESS || islava(loc))
return
visible_message(span_notice("[src] hatches with a quiet cracking sound."))
new /mob/living/basic/raptor(loc, child_color, inherited_stats)
inherited_stats = null
qdel(src)
@@ -1,31 +1,97 @@
#define RANDOM_INHERIT_AMOUNT 2
/datum/raptor_inheritance
///list of traits we inherit
var/list/inherit_traits = list()
///attack modifier
var/attack_modifier
///health_modifier
var/health_modifier
/// Inherited parent personality traits
var/list/personality_traits = list()
/// Flat damage modifier
var/attack_modifier = 0
/// Flat health modifier
var/health_modifier = 0
/// Speed modifier, not randomized by default
var/speed_modifier = 0
/// Primary ability stat modifier, not randomized by default
/// Multiplier equates to 1 + this
var/ability_modifier = 0
/// Growth rate modifier, not randomized by default
/// Multiplier equates to 1 + this
var/growth_modifier = 0
/// Foods eaten that can affect our child's stats -> stats they modify
var/list/foods_eaten = list()
/// List of all colors in our family tree
var/list/parent_colors = list()
/datum/raptor_inheritance/New(datum/raptor_inheritance/father, datum/raptor_inheritance/mother)
// Owner being set will randomize the stats as it means we're spawned not via an egg
// otherwise wait for set_parents call to inherit genetics
/datum/raptor_inheritance/New(mob/living/basic/raptor/owner)
. = ..()
randomize_stats()
/datum/raptor_inheritance/proc/randomize_stats()
attack_modifier = rand(0, RAPTOR_INHERIT_MAX_ATTACK)
health_modifier = rand(0, RAPTOR_INHERIT_MAX_HEALTH)
var/list/traits_to_pick = GLOB.raptor_inherit_traits.Copy()
for(var/i in 1 to RANDOM_INHERIT_AMOUNT)
inherit_traits += pick_n_take(traits_to_pick)
/datum/raptor_inheritance/proc/set_parents(datum/raptor_inheritance/father, datum/raptor_inheritance/mother)
if(isnull(father) || isnull(mother))
if (!owner)
return
if(length(father.inherit_traits))
inherit_traits += pick(father.inherit_traits)
if(length(mother.inherit_traits))
inherit_traits += pick(mother.inherit_traits)
attack_modifier = rand(min(father.attack_modifier, mother.attack_modifier), max(father.attack_modifier, mother.attack_modifier))
health_modifier = rand(min(father.health_modifier, mother.health_modifier), max(father.health_modifier, mother.health_modifier))
#undef RANDOM_INHERIT_AMOUNT
attack_modifier = rand(RAPTOR_INHERIT_MIN_ATTACK * 0.5, RAPTOR_INHERIT_MAX_ATTACK * 0.5)
health_modifier = rand(RAPTOR_INHERIT_MIN_HEALTH * 0.5, RAPTOR_INHERIT_MAX_HEALTH * 0.5)
var/list/traits_to_pick = GLOB.raptor_inherit_traits.Copy()
for(var/i in 1 to rand(1, RAPTOR_TRAIT_INHERIT_AMOUNT))
personality_traits += pick_n_take(traits_to_pick)
/datum/raptor_inheritance/proc/set_parents(mob/living/basic/raptor/mother, mob/living/basic/raptor/father)
var/datum/raptor_inheritance/mom_stats = mother.inherited_stats
var/datum/raptor_inheritance/dad_stats = father.inherited_stats
parent_colors = mom_stats.parent_colors | dad_stats.parent_colors | mother.raptor_color.type | father.raptor_color.type
var/list/traits_to_pick = list()
// + so shared traits have double the weight
for (var/raptor_trait in mom_stats.personality_traits + dad_stats.personality_traits)
if (!traits_to_pick[raptor_trait])
traits_to_pick[raptor_trait] = 0
traits_to_pick[raptor_trait] += 1
var/attack_mod = 0
var/health_mod = 0
var/speed_mod = 0
var/ability_mod = 0
var/growth_mod = 0
var/amount_eaten = 0
for (var/food_type in foods_eaten)
var/list/stat_mods = foods_eaten[food_type]
amount_eaten += stat_mods["amount"]
for (var/food_type in foods_eaten)
var/list/stat_mods = foods_eaten[food_type]
// First multiply stats themselves, then multiply to get a proportion of this food from amount_eaten
var/amount = stat_mods["amount"] ** 2
// Eating other foods reduces the effects of a specific one
attack_mod += stat_mods["attack"] / amount_eaten * amount
health_mod += stat_mods["health"] / amount_eaten * amount
speed_mod += stat_mods["speed"] / amount_eaten * amount
ability_mod += stat_mods["ability"] / amount_eaten * amount
growth_mod += stat_mods["growth"] / amount_eaten * amount
var/list/trait_list = stat_mods["traits"]
for (var/raptor_trait in trait_list)
if (!traits_to_pick[raptor_trait])
traits_to_pick[raptor_trait] = 0
traits_to_pick[raptor_trait] += trait_list[raptor_trait] * stat_mods["amount"]
// If we don't clamp these, RNG and drift can get wildly out of control and result in polar values
// and this way we at most get twice the minimum/maximum value, so at least half the rand spread is within the clamp values
attack_mod = clamp(attack_mod, RAPTOR_INHERIT_MIN_ATTACK, RAPTOR_INHERIT_MAX_ATTACK)
health_mod = clamp(health_mod, RAPTOR_INHERIT_MIN_HEALTH, RAPTOR_INHERIT_MAX_HEALTH)
speed_mod = clamp(speed_mod, RAPTOR_INHERIT_MIN_SPEED, RAPTOR_INHERIT_MAX_SPEED)
ability_mod = clamp(ability_mod, RAPTOR_INHERIT_MIN_MODIFIER, RAPTOR_INHERIT_MAX_MODIFIER)
growth_mod = clamp(growth_mod, RAPTOR_INHERIT_MIN_MODIFIER, RAPTOR_INHERIT_MAX_MODIFIER)
attack_modifier = rand((min(mom_stats.attack_modifier, dad_stats.attack_modifier) + min(0, attack_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MIN_ATTACK, (max(mom_stats.attack_modifier, dad_stats.attack_modifier) + max(0, attack_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MAX_ATTACK)
health_modifier = rand((min(mom_stats.health_modifier, dad_stats.health_modifier) + min(0, health_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MIN_HEALTH, (max(mom_stats.health_modifier, dad_stats.health_modifier) + max(0, health_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MAX_HEALTH)
speed_modifier = rand((min(mom_stats.speed_modifier, dad_stats.speed_modifier) + min(0, speed_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MIN_SPEED, (max(mom_stats.speed_modifier, dad_stats.speed_modifier) + max(0, speed_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MAX_SPEED)
ability_modifier = rand((min(mom_stats.ability_modifier, dad_stats.ability_modifier) + min(0, ability_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MIN_MODIFIER, (max(mom_stats.ability_modifier, dad_stats.ability_modifier) + max(0, ability_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MAX_MODIFIER)
growth_modifier = rand((min(mom_stats.growth_modifier, dad_stats.growth_modifier) + min(0, growth_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MIN_MODIFIER, (max(mom_stats.growth_modifier, dad_stats.growth_modifier) + max(0, growth_mod)) + RAPTOR_GENETIC_DRIFT * RAPTOR_INHERIT_MAX_MODIFIER)
for(var/i in 1 to min(length(traits_to_pick), RAPTOR_TRAIT_INHERIT_AMOUNT))
var/chosen_trait = pick_weight(traits_to_pick)
traits_to_pick -= chosen_trait
personality_traits += chosen_trait
attack_modifier = clamp(attack_modifier, RAPTOR_INHERIT_MIN_ATTACK, RAPTOR_INHERIT_MAX_ATTACK)
health_modifier = clamp(health_modifier, RAPTOR_INHERIT_MIN_HEALTH, RAPTOR_INHERIT_MAX_HEALTH)
speed_modifier = clamp(speed_modifier, RAPTOR_INHERIT_MIN_SPEED, RAPTOR_INHERIT_MAX_SPEED)
ability_modifier = clamp(ability_modifier, RAPTOR_INHERIT_MIN_MODIFIER, RAPTOR_INHERIT_MAX_MODIFIER)
growth_modifier = clamp(growth_modifier, RAPTOR_INHERIT_MIN_MODIFIER, RAPTOR_INHERIT_MAX_MODIFIER)
@@ -81,6 +81,7 @@
/mob/living/basic/snail/mob_pickup(mob/living/user)
var/obj/item/mob_holder/snail/holder = new(get_turf(src), src, held_state, head_icon, held_lh, held_rh, worn_slot_flags)
SEND_SIGNAL(src, COMSIG_LIVING_SCOOPED_UP, user, holder)
var/display_message = "[user] [HAS_TRAIT(src, TRAIT_MOVE_FLOATING) ? "scoops up [src]" : "peels [src] off the ground"]!"
user.visible_message(span_warning(display_message))
user.put_in_hands(holder)