znmud-0.0.1/benchmark/
znmud-0.0.1/cmd/
znmud-0.0.1/cmd/emotes/
znmud-0.0.1/cmd/objects/
znmud-0.0.1/cmd/tiny/
znmud-0.0.1/doc/
znmud-0.0.1/farts/
znmud-0.0.1/lib/
znmud-0.0.1/lib/combat/
znmud-0.0.1/lib/core/bodytypes/
znmud-0.0.1/lib/engine/
znmud-0.0.1/lib/farts/
znmud-0.0.1/logs/
# Author: Craig Smith
#
# This source code copyright (C) 2009 Craig Smith
# All rights reserved.
#
# Released under the terms of the TeensyMUD Public License
# See LICENSE file for additional information.
#

require 'gettext'
require 'utility/log'
require 'combat/damage'
require 'combat/miss'
require 'combat/criticaldamage'
require 'combat/criticalmiss'

# This class is the combat system

# There is one roll to see if the attacker hit the victim
# Second determines if there was a critical strike


class Combat
   include GetText
   bindtextdomain("core")
   logger 'DEBUG'

   attr_accessor :numberofattacks

   # Initialize an attack
   # [+attackerid+] id of attacker
   # [+defenderid+] id of defender
   def initialize(attackerid, defenderid)
	@a = get_object(attackerid)
	@d = get_object(defenderid)
	@damagetype = nil
	@hitlocationid = nil
	@attackweaponid = nil
	@numberofattacks = 1
	srand
	if not @a or not @d
		log.error "Attacker defender pair did not load for attackerid=#{attackerid} defenderid=#{defenderid}"
	end
   end

   # Rolls dice and returns a number between low and high
   # [+low+] lowest result in the roll
   # [+high+] highest result in the roll
   # [+return+] a random number between low and high
   def roll(low, high)
	return rand(high) + low
   end

   # get_object taken from Root.rb
   def get_object(oid)
     Engine.instance.db.get(oid)
   end

   # Checks to see if player is stunned and sends a Msg
   # [+return+] True if stunned
   def playerStunned?
	if @a.has_attribute? :stunned
		@a.sendto _("You are STUNNED!")
		msg = Msg.new("^p1 is STUNNED!")
		msg.p1 = @a.name
		@a.sendroom(msg)
		return true
	end
	return false
   end

   # Determine what the attacker is attacking with
   # [+return+] Undefined
   def attack_method
	if @a.has_attribute? :zombie or @a.is_a? Zombie
		@attackweaponid = nil
		@damagetype = :zombie
		return
	end
	weapons = @a.body.wielding?
	if weapons.size == 1 # Attacher is weilding something, use it
		weapon = get_object(weapons[0])
		@attackweaponid = weapons[0]
		if weapon.has_val? :damagetype
			dtype = weapon.has_val?(:damagetype)
			dtype = dtype.to_sym if dtype.is_a? String
			@damagetype = dtype
		else
			@damagetype = :bludgeon
		end
	elsif weapons.size > 1 # Attacker has more than one possible weapon
		# Todo: Possibly add more attacks per weapon...
		weapon1 = get_object(weapons[0])
		weapon2 = get_object(weapons[1])
		if weapon1.has_val? :damagetype and weapon2.has_val? :damagetype
			# Both can deal some type of damage..pick a random one
			# TODO: Check for fuel or Ammo for this decission
			@attackweaponid = weapons[rand(weapons.size)]
		elsif not weapon1.has_val? :damagetype and not weapon2.has_val? :damagetype
			# Neither weapon has a special damage type, pick the heavier item
			if weapon1.weight > weapon2.weight
				@attackweaponid = weapon1.id
			elsif weapon2.weight > weapon1.weight
				@attackweaponid = weapon2.id
			else # Equal weight
				@attackweaponid = weapons[rand(weapons.size)]
			end	
		else
			# One weapon has a damage type and one doesn't, pick the one that does
			@attackweaponid = weapon1.id if weapon1.has_val? :damagetype
			@attackweaponid = weapon2.id if weapon2.has_val? :damagetype
		end
		weapon = get_object(@attackweaponid)
		if weapon.has_val? :damagetype
			dtype = weapon.has_val? :damagetype
			dtype = dtype.to_sym if dtype.is_a? String
			@damagetype = dtype
		else
			@damagetype = :bludgeon
		end
	else
		# Check for Race/Mobile special attack types
		if @a.has_val? :damagetype
			dtype = @a.has_val? :damagetype
			dtype = dtype.to_sym if dtype.is_a? String
			@damagetype = dtype
		else
			@damagetype = :melee
		end
	end
   end

   # Determine the targeted hit location
   # [+return+] The bodypart location id
   def get_hit_location
	@hitlocationid = @d.body.bodyparts[rand(@d.body.bodyparts.size)]
   end

   # Determine success of attack
   # [+return+] true if attacker hit defender
   def attacker_successful?
	chancetohit = 50
	# Harder to hit if your head is crippled
	chancetohit -= 20 if get_object(@a.body.getbodyid("head")[0]).crippled
	# Subtract 10 % if in the dark and can't see
	chancetohit -= 10 if not @a.cansee? :dark
	# Apply Accuracy modifiers
	if @attackweaponid
	   weapon = get_object(@attackweaponid)
	   if weapon.has_modifier? :accuracy
		# Fuel based weapons...
		if weapon.is_a? Container
			chancetohit += weapon.get_modifier :accuracy if weapon.has_power?
		else
			chancetohit += weapon.get_modifier :accuracy
		end
	   end
	end
	if roll(1,100) < chancetohit
		return true
	end
	false
   end

   # If the attacker hit was it a critical attack?
   # [+return+] true if critical hit
   def critical_hit?
	chance4critical = 3
	# Apply Accuracy modifiers
	if @attackweaponid
	   weapon = get_object(@attackweaponid)
	   if weapon.has_modifier? :critical
		# Fuel based weapons...
		if weapon.is_a? Container
			chance4critical += weapon.get_modifier :critical if weapon.has_power?
		else
			chance4critical += weapon.get_modifier :critical
		end
	   end
	end
	if roll(1,100) < chance4critical
		return true
	end
	false
   end

   # Was the attack a critical miss?
   # [+return+] true if critical miss
   def critical_miss?
	chance4critical = 3
	# Apply Accuracy modifiers
	if @attackweaponid
	   weapon = get_object(@attackweaponid)
	   if weapon.has_modifier? :critical
		# Fuel based weapons...
		if weapon.is_a? Container
			chance4critical += weapon.get_modifier :critical if weapon.has_power?
		else
			chance4critical += weapon.get_modifier :critical
		end
	   end
	end
	if roll(1,100) < chance4critical
		return true
	end
	false
   end

   # Determines the amount of damage dealt
   # [+return+] false if damage was prevented (ie: armor)
   def deal_damage
	# TODO: Armor checks
	pain = Damage.new(@a, @d, @damagetype, @hitlocationid, @attackweaponid)
	pain.deal(roll(1,100))
	true
   end

   # Determines what type of critical strike occured
   def deal_critical_hit
	pain = CriticalDamage.new(@a, @d, @damagetype, @hitlocationid, @attackweaponid)
	pain.deal(roll(1,100))
   end

   # Determines what type of critical miss occured
   def deal_critical_miss
	pain = CriticalMiss.new(@a, @d, @damagetype, @hitlocationid, @attackweaponid)
	pain.deal(roll(1,100))
   end

   # Describes the results of a miss
   def describe_miss
	missmsg = Miss.new(@a, @d, @damagetype, @hitlocationid, @attackweaponid)
	missmsg.deal(roll(1,100))
   end

   # Ensures that the person hit is fighting them
   def add_combatant
	@d.combatants << @a.id if not @d.combatants.include? @a.id
   end

   # Execute an attack on defender
   def attack
	hit = false
	# If either part is already dead, return
	return if @a.health < 1 or @d.health < 1
	# If attacker is stunned then they can not attack
	return if playerStunned?
	# Determine how the attacker is attacking
	attack_method
	# Determine where the attack might hit the defender
	get_hit_location
	# Did attacker hit defender?
	if attacker_successful?
		if critical_hit?
			deal_damage
			deal_critical_hit
			hit = true
		else
			hit = deal_damage
		end
		add_combatant
	elsif critical_miss?
		deal_critical_miss
		hit = false
	else
		describe_miss
		hit = false
	end
	hit
   end
end