Files
CHOMPStation2/code/modules/mob/living/simple_mob/combat.dm
2023-06-17 13:38:08 +02:00

282 lines
10 KiB
Plaintext

// Does a melee attack.
/mob/living/simple_mob/proc/attack_target(atom/A)
set waitfor = FALSE // For attack animations. Don't want the AI processor to get held up.
if(!A.Adjacent(src))
return ATTACK_FAILED
var/turf/their_T = get_turf(A)
face_atom(A)
if(melee_attack_delay)
melee_pre_animation(A)
. = ATTACK_SUCCESSFUL //Shoving this in here as a 'best guess' since this proc is about to sleep and return and we won't be able to know the real value
handle_attack_delay(A, melee_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
// Cooldown testing is done at click code (for players) and interface code (for AI).
// VOREStation Edit Start: Simplemob Injury
if(injury_enrages)
setClickCooldown(get_attack_speed() - ((injury_level / 2) SECONDS)) // Increase how fast we can attack by our injury level / 2
else
setClickCooldown(get_attack_speed() + ((injury_level / 2) SECONDS)) // Delay how fast we can attack by our injury level / 2
// VOREStation Edit Stop: Simplemob Injury
// Returns a value, but will be lost if
. = do_attack(A, their_T)
if(melee_attack_delay)
melee_post_animation(A)
// This does the actual attack.
// This is a seperate proc for the purposes of attack animations.
// A is the thing getting attacked, T is the turf A is/was on when attack_target was called.
/mob/living/simple_mob/proc/do_attack(atom/A, turf/T)
face_atom(A)
var/missed = FALSE
if(!isturf(A) && !(A in T) ) // Turfs don't contain themselves so checking contents is pointless if we're targeting a turf.
missed = TRUE
else if(!T.AdjacentQuick(src))
missed = TRUE
if(missed) // Most likely we have a slow attack and they dodged it or we somehow got moved.
add_attack_logs(src, A, "Animal-attacked (dodged)", admin_notify = FALSE)
playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1)
visible_message(span("warning", "\The [src] misses their attack."))
return FALSE
var/damage_to_do = rand(melee_damage_lower, melee_damage_upper)
damage_to_do = apply_bonus_melee_damage(A, damage_to_do)
for(var/datum/modifier/M in modifiers)
if(!isnull(M.outgoing_melee_damage_percent))
damage_to_do *= M.outgoing_melee_damage_percent
if(isliving(A)) // Check defenses.
var/mob/living/L = A
if(prob(melee_miss_chance))
add_attack_logs(src, L, "Animal-attacked (miss)", admin_notify = FALSE)
do_attack_animation(src)
playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1)
return FALSE // We missed.
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.check_shields(damage = damage_to_do, damage_source = src, attacker = src, def_zone = null, attack_text = "the attack"))
return FALSE // We were blocked.
if(apply_attack(A, damage_to_do))
apply_melee_effects(A)
if(attack_sound)
playsound(src, attack_sound, 75, 1)
return TRUE
// Generally used to do the regular attack.
// Override for doing special stuff with the direct result of the attack.
/mob/living/simple_mob/proc/apply_attack(atom/A, damage_to_do)
return A.attack_generic(src, damage_to_do, pick(attacktext))
// Override for special effects after a successful attack, like injecting poison or stunning the target.
/mob/living/simple_mob/proc/apply_melee_effects(atom/A)
return
// Override to modify the amount of damage the mob does conditionally.
// This must return the amount of outgoing damage.
// Note that this is done before mob modifiers scale the damage.
/mob/living/simple_mob/proc/apply_bonus_melee_damage(atom/A, damage_amount)
return damage_amount
//The actual top-level ranged attack proc
/mob/living/simple_mob/proc/shoot_target(atom/A)
set waitfor = FALSE
if(!istype(A) || QDELETED(A))
return
// VOREStation Edit Start: Simplemob Injury
if(injury_enrages)
setClickCooldown(get_attack_speed() - ((injury_level / 2) SECONDS)) // Increase how fast we can attack by our injury level / 2
else
setClickCooldown(get_attack_speed() + ((injury_level / 2) SECONDS)) // Delay how fast we can attack by our injury level / 2
// VOREStation Edit Stop: Simplemob Injury
face_atom(A)
if(ranged_attack_delay)
ranged_pre_animation(A)
handle_attack_delay(A, ranged_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
if(needs_reload)
if(reload_count >= reload_max)
try_reload()
return FALSE
//CHOMP Addition: This section here is special snowflake code for metroids only, or for whatever else in the future that you want to have move and shoot at the same time. Basically, this is a non-stupid version of the above intended for ranged vore mobs i.e. metroids. ranged_attack_delay is stupid because it sleeps the entire mob. This new ranged_cooldown_time is smarter in the sense that it is an internalized timer. Try not to confuse the names.
if(ranged_cooldown_time) //If you have a non-zero number in a mob's variables, this pattern begins.
if(ranged_cooldown <= world.time) //Further down, a timer keeps adding to the ranged_cooldown variable automatically.
visible_message("<span class='danger'><b>\The [src]</b> fires at \the [A]!</span>") //Leave notice of shooting.
shoot(A) //Perform the shoot action
if(casingtype) //If the mob is designated to leave casings...
new casingtype(loc) //... leave the casing.
ranged_cooldown = world.time + ranged_cooldown_time + ((injury_level / 2) SECONDS) //Special addition here. This is a timer. Keeping updating the time after shooting. Add that ranged cooldown time specified in the mob to the world time.
return TRUE //End these commands here.
visible_message("<span class='danger'><b>\The [src]</b> fires at \the [A]!</span>")
shoot(A)
if(casingtype)
new casingtype(loc)
if(ranged_attack_delay)
ranged_post_animation(A)
return TRUE
// Shoot a bullet at something.
/mob/living/simple_mob/proc/shoot(atom/A)
if(A == get_turf(src))
return
face_atom(A)
var/obj/item/projectile/P = new projectiletype(src.loc)
if(!P)
return
// If the projectile has its own sound, use it.
// Otherwise default to the mob's firing sound.
playsound(src, P.fire_sound ? P.fire_sound : projectilesound, 80, 1)
// For some reason there isn't an argument for accuracy, so access the projectile directly instead.
// Also, placing dispersion here instead of in forced_spread will randomize the chosen angle between dispersion and -dispersion in fire() instead of having to do that here.
P.accuracy += calculate_accuracy()
P.dispersion += calculate_dispersion()
P.launch_projectile(target = A, target_zone = null, user = src, params = null, angle_override = null, forced_spread = 0)
if(needs_reload)
reload_count++
// if(distance >= special_attack_min_range && distance <= special_attack_max_range)
// return TRUE
/mob/living/simple_mob/proc/try_reload()
set waitfor = FALSE
set_AI_busy(TRUE)
if(do_after(src, reload_time))
if(reload_sound)
playsound(src, reload_sound, 70, 1)
reload_count = 0
. = TRUE
else
. = FALSE
set_AI_busy(FALSE)
/mob/living/simple_mob/proc/calculate_dispersion()
. = projectile_dispersion // Start with the basic var.
// Some modifiers change dispersion. This makes simple_mobs respect that.
for(var/datum/modifier/M in modifiers)
if(!isnull(M.accuracy_dispersion))
. += M.accuracy_dispersion
// Make sure we don't go under zero dispersion.
. = max(., 0)
/mob/living/simple_mob/proc/calculate_accuracy()
. = projectile_accuracy // Start with the basic var.
// Some modifiers make it harder or easier to hit things.
for(var/datum/modifier/M in modifiers)
if(!isnull(M.accuracy))
. += M.accuracy
// Can we currently do a special attack?
/mob/living/simple_mob/proc/can_special_attack(atom/A)
// Validity check.
if(!istype(A))
return FALSE
// Ability check.
if(isnull(special_attack_min_range) || isnull(special_attack_max_range))
return FALSE
// Distance check.
var/distance = get_dist(src, A)
if(distance < special_attack_min_range || distance > special_attack_max_range)
return FALSE
// Cooldown check.
if(!isnull(special_attack_cooldown) && last_special_attack + special_attack_cooldown > world.time)
return FALSE
// Charge check.
if(!isnull(special_attack_charges) && special_attack_charges <= 0)
return FALSE
return TRUE
// Should we do one? Used to make the AI not waste their special attacks. Only checked for AI. Players are free to screw up on their own.
/mob/living/simple_mob/proc/should_special_attack(atom/A)
return TRUE
// Special attacks, like grenades or blinding spit or whatever.
// Don't override this, override do_special_attack() for your blinding spit/etc.
/mob/living/simple_mob/proc/special_attack_target(atom/A)
face_atom(A)
if(special_attack_delay)
special_pre_animation(A)
handle_attack_delay(A, special_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
last_special_attack = world.time
if(do_special_attack(A))
if(special_attack_charges)
special_attack_charges -= 1
. = TRUE
else
. = FALSE
if(special_attack_delay)
special_post_animation(A)
// Override this for the actual special attack.
/mob/living/simple_mob/proc/do_special_attack(atom/A)
return FALSE
// Sleeps the proc that called it for the correct amount of time.
// Also makes sure the AI doesn't do anything stupid in the middle of the delay.
/mob/living/simple_mob/proc/handle_attack_delay(atom/A, delay_amount)
set_AI_busy(TRUE)
// Click delay modifiers also affect telegraphing time.
// This means berserked enemies will leave less time to dodge.
var/true_attack_delay = delay_amount
for(var/datum/modifier/M in modifiers)
if(!isnull(M.attack_speed_percent))
true_attack_delay *= M.attack_speed_percent
setClickCooldown(true_attack_delay) // Insurance against a really long attack being longer than default click delay.
sleep(true_attack_delay)
set_AI_busy(FALSE)
// Override these four for special custom animations (like the GOLEM).
/mob/living/simple_mob/proc/melee_pre_animation(atom/A)
do_windup_animation(A, melee_attack_delay)
/mob/living/simple_mob/proc/melee_post_animation(atom/A)
/mob/living/simple_mob/proc/ranged_pre_animation(atom/A)
do_windup_animation(A, ranged_attack_delay) // Semi-placeholder.
/mob/living/simple_mob/proc/ranged_post_animation(atom/A)
/mob/living/simple_mob/proc/special_pre_animation(atom/A)
do_windup_animation(A, special_attack_delay) // Semi-placeholder.
/mob/living/simple_mob/proc/special_post_animation(atom/A)