# 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