[go: up one dir, main page]

Menu

[r69]: / tfm / code / combat / attack.pe  Maximize  Restore  History

Download this file

988 lines (754 with data), 18.0 kB

#######################################
#                                     #
#  attack - Generalised attack logic  #
#                                     #
#######################################

/*

NOTES

Each weapon (or combatant) will have it's ATTACK function set to either
'close_attack' or 'range_attack'.

Whichever is chosen, it will be called from the main 'attack' function,
which will be called by the 'A' key (in the case of the player)
or as a behaviour task for NPCs and monsters.  This is the main entry point
to the attack engine.

Weapons can also have an additional function for special effects and such
(e.g. chairs and bottles break when used as weapons).
The function name is put in the USER2 function for the weapon.
These special effect functions are in combat/weapons.pe

*/



integer hit_damage
integer hit_range
integer arrest_only = 0
integer attack_user2 = 0

##
##	Attack:  Top-level attack function
##
##	Allocate a weapon for each hand and call appropriate attack driver
##

function attack

# Set the attacker to the current character
let attacker = me
let hit_damage = 0

# Not born yet!
if_exists me.parent
	return
endif

# Computer-controlled attackers need decisions made for them
if attacker <> player

	# Do we really have an enemy?
	if_not_flag attacker.enemy IS_ON
		resume_activity attacker
		print "atk: noenemy"
		printcr
		return			# Don't waste my time
	endif


	# Are they alive?

	if attacker.enemy.stats.hp < 1
		stop_action attacker
		resume_activity attacker
		return			# Don't waste time pounding the corpse
	endif

	# Are we totally and utterly unable to reach the enemy?
	if attacker.user.counter > 40
		# We've lost them.  Forget about it
		stop_action attacker
		resume_activity attacker
		# Reset the counter (in case attack is the only activity)
		let attacker.user.counter = 0
		return
	endif

	# If we don't have a weapon, find one
	if_not_exists attacker.wield.l_hand
		if_not_exists attacker.wield.r_hand
			let current = attacker
			call find_best_weapon
		endif
	endif

endif

# Can't attack if you're dead
if attacker.stats.hp < 1
	return
endif

# Are we critical?
/*
if attacker.stats.hp < 15

	set_flag attacker IS_CRITICAL = 1
	set_flag attacker NOT_CLOSE_DOORS = 1

	if attacker <> player
		if attacker.enemy <> player
			stop_action attacker.enemy	# Stop killing
			reset_flag attacker.enemy NOT_CLOSE_DOORS
			if attacker.enemy.activity < 1
				resume_activity attacker.enemy
			endif
		endif
		stop_activity attacker
		start_activity attacker does enemy_flee
		return
	endif

endif
*/

# If the attacker has no weapon, we must sort something out for them.
if_not_exists attacker.wield.l_hand
or attacker.wield.l_hand.funcs.acache <= 0 # Not a real weapon
	# Use the right-hand weapon
	let attackweapon = attacker.wield.r_hand

	# There was a weapon, yes?
	if_not_exists attackweapon
		# No.  Bare hands.
		let attackweapon = attacker
	else
		# Is it really a weapon?
		if attackweapon.funcs.acache <= 0
			# No.. use bare hands
			let attackweapon = attacker
		endif
	endif

	# Skip the left-hand attack
	goto attack_righthand
endif



# Do the Left Hand Attack
let attackweapon = attacker.wield.l_hand

# It's possible the weapon was 'confiscated' if we're out of range
# Check first, and attack if possible

if_exists attackweapon
	# Do we have an attack method?
	if attackweapon.funcs.acache > 0
		let current = attackweapon	# Some weapon code may need this
		call attackweapon.funcs.acache

		# Status report
		if_onscreen attacker.enemy
			call attack_status
			redraw_text
		endif

	endif
endif


# Do the Right-Hand Attack

let attackweapon = attacker.wield.r_hand

# If we had no weapon, this is the entrypoint.
label attack_righthand

# Has the attacker been killed since his first blow?
if attacker.stats.hp < 1
	let attackweapon = null		# No second try for you
endif

# Try and attack
if_exists attackweapon

	# Do we have an attack method?
	if attackweapon.funcs.acache > 0

		let current = attackweapon	# Some weapon code may need this
		call attackweapon.funcs.acache

		# Status report
		if_onscreen attacker.enemy
			call attack_status
			redraw_text
		endif
	endif
endif

# Done
let attacker = null


end


##
## Attack core : Calculate damage, do the actual violence
##
##		 Called from the attack drivers, e.g. close_attack
##

function attack_core

if attacker <> player
	# They're already dead.  Why bother?
	if attacker.enemy.stats.hp < 1
		return
	endif
endif

# do the damage
if hit_damage >= 0

	# If weapon has special method, call it
	if attackweapon.funcs.user2 <> ""
		let attackweapon.enemy = attacker.enemy
		let current = attackweapon

		call attackweapon.funcs.user2

		# Quit if weapon malfunctioned (out of ammo etc)
		if hit_damage < 0
			return
		endif
	endif

	# Hurt the enemy
	add attacker.enemy.stats.hp - hit_damage
	# Clip damage to sensible levels to prevent ash
	if attacker.enemy.stats.hp < -200
		let attacker.enemy.stats.hp = -200
	endif

	# If the victim is technically dead but we are supposed to arrest
	# them and not kill them, bring them back
	if arrest_only
		if attacker.enemy.stats.hp < 1
			let attacker.enemy.stats.hp = 1
		endif
	endif

####### Justice #######

	# Mark who attacked
	let attacker.enemy.enemy = attacker

	# play sounds, react to damage etc
/*
	print "attacker "
	print attacker.name
	print " enemy = "
	print attacker.enemy.name
	printcr
*/
	check_hurt attacker.enemy

	# Was the attack permitted?
	if attacker.user.vigilante = 1
		# Yes, don't involve any Justice
		return
	endif

	# This justice system is for humans, others have their own..
	if attacker.enemy.label.race = "human"
	or attacker.enemy.label.race = "waarg"

		# Mark that you assaulted them and Justify it
		if_flag attacker.enemy IS_PERSON	# is a creature
			if_not_flag attacker.enemy IS_PARTY	# not one of us
				let suspect = attacker
				let victim = attacker.enemy
				call justice_assault
			endif
		endif
	endif

else
	if attacker = player
		print "Missed!"
		printcr
	endif
endif

end



##
##	For thrown objects, mess up the attacker's aim depending on dexterity
##

function fudge_thrown

int minor
int points
int r

# Can they miss?

if attacker.stats.dex > 100
	# No
	return
endif


# We don't have floating point, so multiply the lower figure by 100
# and then divide by the maximum to get a percentage chance


# First, do we have any dexterity (i.e. hope of hitting the target) at all?
if attacker.maxstats.dex > 0

	let minor = attacker.stats.dex * 100
	let points = minor / attacker.maxstats.dex
	random r between 1 100
	if r <= points
		return
	endif
endif

# Okay, we're still here, so the user probably missed
# Jiggle the X coordinate

random r between 0 2
add r - 1
add new_x + r

# Jiggle the Y coordinate

random r between 0 2
add r - 1
add new_y + r

end


##
##	For close combat, mess up the attacker's aim depending on dexterity
##

function fudge_closecombat

int minor
int points
int r

# We don't have floating point, so multiply the lower figure by 100
# and then divide by the maximum to get a percentage chance

# First, do we have any dexterity (i.e. hope of hitting the target) at all?
if attacker.maxstats.dex > 0

	let minor = attacker.stats.dex * 100
	let points = minor / attacker.maxstats.dex
	random r between 1 100
	if r <= points
		return
	endif
endif


# Okay, we're still here, so the user probably missed.
# Fiddle the damage around to reduce or eliminate it
if hit_damage > 10
	random hit_damage between 10 hit_damage
	add hit_damage - 10
	if hit_damage < 0
		let hit_damage = -1
	endif
endif
end

##
##	Calculate distance between attacker and their victim
##

function get_hitrange
integer tx
integer ty

if_not_exist attacker
	return
endif

if_not_exist attacker.enemy
	return
endif

let tx = attacker.x - attacker.enemy.x
let ty = attacker.y - attacker.enemy.y

if tx < 0
	let tx = 0 - tx
endif
if ty < 0
	let ty = 0 - ty
endif

if ty > tx
	let tx = ty
endif

let hit_range = tx
end


##
##  Calculate damage to cause in the attack
##  handles armour and suchlike
##

function calculate_attackdamage
integer ac

# Get basic damage amount
let hit_damage = attackweapon.stats.damage

# The splat of blood will apppear here
let splatvictim = attacker.enemy

# Check for a DECOR object, since any decor slain shall be avenged with
# much ravaging and spilling of core (it will crash the game)

if_flag attacker.enemy IS_DECOR
	let hit_damage = -1
	return
endif

# Work out the armour class
let ac = 0

if_exists attacker.enemy.wield.body
	add ac + attacker.enemy.wield.body.stats.armour
endif

if_exists attacker.enemy.wield.l_hand
	add ac + attacker.enemy.wield.l_hand.stats.armour
endif

if_exists attacker.enemy.wield.r_hand
	add ac + attacker.enemy.wield.r_hand.stats.armour
endif

# Martyn's armour calculation: 100/((ac/25)+1)
# This gives the percentage of the damage remaining.

const TWEAK = 25	# Use a tweak factor of 25

# The tweak factor equals the AC needed for 50% damage absorption
# e.g.	AC 3 lets 89% of the damage past
# 	AC 25 lets 50% past (or absorbs 50%, depending on your POV)
#	AC 60 lets 29% past.
#
#	We have diminishing returns, so AC 100 lets 20% past.
#	Choosing a different Tweak will affect the curve.

let ac = ac * 100	# Need to *100 because we're using integer math
let ac = ac / TWEAK	# Divide it by tweak factor
add ac + 100		# add one (*100)
let ac = 10000 / ac	# Divide 100 (*100) by the number

# What we have left is the % of the damage left, convert to actual value
add hit_damage * ac
add hit_damage / 100

# Now the really kick-ass part is what happens when the damage is negative,
# e.g. for cursed armour.  The graph plotted from -100 to +100 resembles
# a tanwave, i.e. it rushes to the vertical and flips in the middle.
#
# Fun negative numbers are: 	-26 (does -2500% damage)
#				-25 divides by zero, thus absorbing all damage
#				-24 (does 2500% damage, incinerating you)
#				-36 (does something like 150% damage)

end


##
##  close_attack - Prepare for close combat attack.
##


function close_attack

# Safety check

if_not_exists attacker
	return
endif

# If the attacker is the player, we need to find the target of their ire

if attacker = player

	if attackweapon = player
		if player.label.race = "human"
			print "Attack with bare hands "
		else
			print "Attack with claws "
		endif
	else
		print "Attack with "
		print attackweapon.shortdesc
		print " "
	endif
	redraw_text

	# Orient around player for Get Near
	let current = player
	call get_near

	# If we have a victim, do the attack
	if_exists current
		print_bestname current
		printcr

		let attacker.enemy = current
		# Calculate initial damage
		call calculate_attackdamage
		# Introduce that element of chance
		call fudge_closecombat
		# Do it
		call attack_core
	else
		print "nothing."
		printcr
	endif

else	# If not the player, check range and attack if possible
	call get_hitrange
	if hit_range <= 1

		if attacker.enemy.stats.intel > 0
			print_bestname attacker
			print " attacks "
			print_bestname attacker.enemy
			printcr
			redraw_text
		endif

		# In range
		call calculate_attackdamage
		# Introduce that element of chance
		call fudge_closecombat
		# Do it
		call attack_core

	else
		# Out of range, correct this
		move_towards attacker attacker.enemy

		# If we're just in range, try attacking again
		# (It sounds unfair, but actually stops the player 'cheating')
		call get_hitrange
		if hit_range <= 1

			if attacker.enemy.stats.intel > 0
				print_bestname attacker
				print " attacks "
				print_bestname attacker.enemy
				printcr
				redraw_text
			endif

			call calculate_attackdamage
			call fudge_closecombat
			call attack_core
		endif

	endif

endif

end


##
##  range_attack - distant attack with infinite ammo
##


function range_attack
integer r

# Safety check

if_not_exists attacker
	return
endif

if attackweapon.stats.range < 2
	BUG START
	print "Attacker "
	print_bestname attacker
	print " ("
	print attacker.name
	print ") has Range_Attack but no range!"
	printcr
	BUG END

	let attackweapon.stats.range = 8
endif

# If the attacker is the player, we need to find the target of their ire

if attacker = player

	if attackweapon <> attacker
		# Describe the weapon so the player knows what they're doing
		print "Attack with "
		print attackweapon.shortdesc
		print " "
	else
		# Using some internal weapon.. be vague about it
		print "Attack "
	endif
	redraw_text

	# Orient around player for Get Far
	let current = player
	call get_far

	# If we have a victim, do the attack
	if_exists current
		print_bestname current
		printcr
		# Set up enemy
		let attacker.enemy = current
	else
		print "nothing."
		printcr
		return
	endif

endif

# check range and attack if possible

get_line_of_sight r = attacker attacker.enemy

if r > attackweapon.stats.range
or r = 0
	# Out of range
	if attacker = player
		print "Out of range."
		printcr
	else
		move_towards attacker attacker.enemy
	endif
	# Abandon
	return
endif

# We are in range, so do the attack

# Look at them
call face_enemy
# Calculate initial damage
call calculate_attackdamage
# Fudging will be done by the ammo driver.  Just to the attack
call attack_core

end

##
##  trample_attack - Try to stand on the victim and crush them
##

function trample_attack

# Safety check

if_not_exists attacker
	return
endif

# If the attacker is the player, there's a problem.

if attacker = player

	print "No. Trample_attack is not supported for the player."
	printcr

else	# If not the player, check range and attack if possible
 	let current = attacker

	let blockage = null
	call track_enemy
	if_exists blockage
		if blockage = attacker.enemy
			# Kill the enemy
			print "Trample on "
			print blockage.name
			printcr
			add blockage.stats.hp - attacker.stats.damage
			check_hurt blockage
		else
			move_towards attacker attacker.enemy
		endif
	endif

	# Do it again!
	redraw_map

	let blockage = null
	call track_enemy
	if_exists blockage
		if blockage = attacker.enemy
			# Kill the enemy
			print "Trample on "
			print blockage.name
			printcr
			add blockage.stats.hp - attacker.stats.damage
			check_hurt blockage
		else
			move_towards attacker attacker.enemy
		endif
	endif

endif

end



##
##  ammo_attack - distant attack using ammo
##

function ammo_attack
integer r
integer ammo_qty
object ammo

# Safety check

if_not_exists attacker
	return
endif

# If the attacker is the player, we need to find the target of their ire

if attacker = player

	if attackweapon <> attacker
		# Describe the weapon so the player knows what they're doing
		print "Attack with "
		print attackweapon.shortdesc
		print " "
	else
		# Using some internal weapon.. be vague about it
		print "Attack "
	endif
	redraw_text

	# Orient around player for Get Far
	let current = player
	call get_far

	# If we have a victim, do the attack
	if_exists current
		print_bestname current
		printcr
		# Set up enemy
		let attacker.enemy = current
	else
		print "nothing."
		printcr
		return
	endif

endif

# check range and attack if possible

get_line_of_sight r = attacker attacker.enemy

if r > attackweapon.stats.range
or r = 0
	# Out of range
	if attacker = player
		print "Out of range."
		printcr
	else
		move_towards attacker attacker.enemy
	endif
	# Abandon
	return
endif

# Make sure there is ammo for the weapon

find ammo = attackweapon.funcs.user1 in attacker
if_not_exists ammo
	let ammo_qty = 0
/*
	print "Didn't find "
	print attackweapon.funcs.user1
	print " inside "
	print attacker.name
	printcr
*/
else
	let ammo_qty = ammo.stats.str
/*
	print "Found "
	print ammo_qty
	printcr
*/
endif

if ammo_qty < 1
	if attacker == player
		print "Out of ammo!"
		printcr
	else
#		print "NPC OUT OF AMMO!|"
		let current = attacker
		call find_best_weapon
	endif
	return
endif

# Set damage level for the weapon, based on ammo
let attackweapon.stats.damage = ammo.stats.damage

# Take away a round
add ammo.stats.str - 1

if ammo.stats.str < 1
	destroy ammo	# Used up
else
	# Change ammo frame etc
	if ammo.funcs.user1 <> ""
		let current = ammo
		call ammo.funcs.user1
	endif
endif

# Look at them
call face_enemy
# Calculate initial damage
call calculate_attackdamage
# Fudging will be done by the ammo driver.  Just to the attack
call attack_core

end


##
##  magic_attack - distant attack using the Harm spell
##

function magic_attack
integer r

# Safety check

if_not_exists attacker
	return
endif

# This is only for NPCs not the player

if attacker = player
	return
endif

# We do have an enemy, don't we

if_not_exists attacker.enemy
	return
endif


# check range and attack if possible

get_line_of_sight r = attacker attacker.enemy

if r > attackweapon.stats.range
or r = 0
	# Out of range
	if attacker = player
		print "Out of range."
		printcr
	else
		move_towards attacker attacker.enemy
	endif
	# Abandon
	return
endif

# Blast them (add other kinds of spell later)

let spelltarget = attacker.enemy
let current = attacker
call spell_harm

end



##
##  Show the damage report, and make them go critical if necessary
##

function attack_status
object v
integer percent

let v = attacker.enemy

# Bail out if it isn't good
get_flag percent = v IS_PERSON
if percent = 0
	# Not a person, don't want "plate is critical" etc.
	return
endif

if v.maxstats.hp < 1
	# Not proper
	return
endif

if hit_damage < 1
	# Forget it
	return
endif

print_bestname v

# Calculate damage percentage
let percent = v.stats.hp * 100
add percent / v.maxstats.hp

if v.stats.hp < 1
	print " is dead!"
	printcr
	return
endif

if percent <= 10
	print " is critical!"
	printcr

	if v = player
		# Just warn the player.  Don't take over their body
		return
	endif

	# Send them running
	set_flag v IS_CRITICAL = 1
	set_flag v NOT_CLOSE_DOORS = 1

	stop_activity v
	start_activity v does enemy_flee

	return
endif

if percent <= 20
	print " is heavily wounded."
	printcr
	return
endif

if percent <= 50
	print " is wounded."
	printcr
	return
endif

if percent <= 80
	print " is lightly wounded."
	printcr
	return
endif

print " is barely wounded."
printcr
return

end