Welp, I got bored, and redid combat.lua, mostly to see if I could. Here's the highlights:
- re-ordered the code a bit. _M:attackTarget now only chooses the weapons, attack modifier, and percentage of strength damage to add for the attacks, and _M:attackRoll now takes that info to determine hits, crits, and damage
- You automatically miss on a roll of 1 (not 0)
- attack rolls meeting the AC now hit
- Finesse has penalties to attack if you have a shield, and is disabled if it is worse than not using it.
- Critical threats require a hit (to prevent weird effects with high-threat weapons)
- Critical hit damage is applied before damage resistance, and only applies to damage through dice, not magic or STR.
- attack damage calculated correctly
- Offhand and two weapon fighting penalties applied correctly
- Additional mainhand attacks are automatically gained at 6, 11, and 16 BAB, at -5, -10, and -15 attack, respectively
- Additional offhand attacks from improved/greater TWF feats applied at -5 and -10 attack
- attack rolls equaling the AC now hit instead of miss
- Offhand attacks get only 50% of Str mod as bonus damage
- With nothing in offhand, and while wielding a non-light, non-double weapon, wield it twohanded to deal 150% of str mod as bonus damage.
Known bugs:
- Damage resistance is never called (combat_dr is not used outside of Combat.lua)
- I broke archery (should be a fairly simple fix, I think)
- Improved Critical is not implemented
- Attacking unarmed does not have non-proficiency penalties
- It's a random mix of tabs and 4x spaces for keeping code in line.
- I only modified weapons to be double weapons on my end (add double = true to their properties)
- Rapiers can be wielded twohanded for 150% str mod bonus damage.
Code (modified from 0.9, also attached below as .txt):
Code: Select all
-- Veins of the Earth
-- Zireael
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
require "engine.class"
local DamageType = require "engine.DamageType"
local Map = require "engine.Map"
local Target = require "engine.Target"
local Talents = require "engine.interface.ActorTalents"
--- Interface to add ToME combat system
module(..., package.seeall, class.make)
--- Checks what to do with the target
-- Talk ? attack ? displace ?
function _M:bumpInto(target)
local reaction = self:reactionToward(target)
if reaction < 0 then
return self:attackTarget(target)
elseif reaction >= 0 then
if self.move_others then
-- Displace
game.level.map:remove(self.x, self.y, Map.ACTOR)
game.level.map:remove(target.x, target.y, Map.ACTOR)
game.level.map(self.x, self.y, Map.ACTOR, target)
game.level.map(target.x, target.y, Map.ACTOR, self)
self.x, self.y, target.x, target.y = target.x, target.y, self.x, self.y
end
end
end
function _M:attackTarget(target, noenergy)
if self.combat then
-- returns your weapon if you are armed, or unarmed combat.
local weapon = (self:getInven("MAIN_HAND") and self:getInven("MAIN_HAND")[1]) or self
-- returns your offhand weapon (not shield) or your weapon again if it is double
local offweapon = (self:getInven("OFF_HAND") and self:getInven("OFF_HAND")[1] and self:getInven("OFF_HAND")[1].combat and self:getInven("OFF_HAND")[1]) or (weapon and weapon.double and weapon)
local twohanded = false
if not (self:getInven("OFF_HAND") and self:getInven("OFF_HAND")[1]) and not weapon.double and not weapon.light then
twohanded = true
end
-- add in modifiers for dualwielding, etc.
local attackmod = 0
local strmod = 1
if twohanded then strmod = 1.5 end
if offweapon then
attackmod = -6
if offweapon.light or weapon.double then attackmod = attackmod + 2 end
if self:knowTalent(self.T_TWO_WEAPON_FIGHTING) then attackmod = attackmod + 2 end
end
self:attackRoll(target, weapon, attackmod, strmod)
--extra attacks for high BAB, at lower bonuses
if self.combat_bab >=6 then
self:attackRoll(target, weapon, attackmod - 5, strmod)
end
if self.combat_bab >=11 then
self:attackRoll(target, weapon, attackmod - 10, strmod)
end
if self.combat_bab >=16 then
self:attackRoll(target, weapon, attackmod - 15, strmod)
end
-- offhand/double weapon attacks
if offweapon then
strmod = 0.5
attackmod = -10
if offweapon.light or weapon.double then attackmod = attackmod + 2 end
if self:knowTalent(self.T_TWO_WEAPON_FIGHTING) then attackmod = attackmod + 6 end
self:attackRoll(target, offweapon, attackmod, strmod)
if self:knowTalent(self.T_IMPROVED_TWO_WEAPON_FIGHTING) then
self:attackRoll(target, offweapon, attackmod - 5, strmod)
end
if self:knowTalent(self.T_GREATER_TWO_WEAPON_FIGHTING) then
self:attackRoll(target, offweapon, attackmod - 10, strmod)
end
end
end
-- We use up our own energy
if not noenergy then
self:useEnergy(game.energy_to_act)
end
end
function _M:attackRoll(target, weapon, atkmod, strmod)
local d = rng.range(1,20)
local hit = true
local crit = false
local attack = (self.combat_bab or 0) + (self.combat_attack or 0)
-- Proficiency penalties
if weapon and weapon.simple and not self:knowTalent(self.T_SIMPLE_WEAPON_PROFICIENCY) then
attack = (attack -4)
end
if weapon and weapon.martial and not self:knowTalent(self.T_MARTIAL_WEAPON_PROFICIENCY) then
attack = (attack -4)
end
-- Feat bonuses
if self:knowTalent(self.T_WEAPON_FOCUS) and weapon and weapon.subtype == self.weapon_type then
attack = (attack + 1)
end
-- Stat bonuses
local stat_used = "str"
if weapon and weapon.ranged then
stat_used = "dex"
end
-- Finesse
if self:knowTalent(self.T_FINESSE) and weapon and not weapon.ranged then
local success = false
-- hack to get the armour check penalty of the shield. Returns 4 instead of 10 for tower shields, and does not account for mithril bonuses.
local shield = self:getInven("OFF_HAND") and self:getInven("OFF_HAND")[1] and self:getInven("OFF_HAND")[1].subtype == shield and self:getInven("OFF_HAND")[1].wielder.combat_shield
if not weapon.light then
local a = {"rapier", "whip", "spiked chain"}
for _, w in pairs(a) do
if weapon.subtype == w then
success = true
break
end
end
else
success = true
end
-- final check if Finesse improves attack
if self:getStat("dex") > self:getStat("str") + shield * 2 then
success = false
end
if success then
stat_used = "dex"
end
end
attack = attack + (atkmod or 0) + (weapon and weapon.combat.magic_bonus or 0) + (self:getStat(stat_used)-10)/2 or 0
local ac = target:getAC()
-- Hit check
if self:isConcealed(target) and rng.chance(self:isConcealed(target)) then hit = false
elseif d == 1 then hit = false
elseif d == 20 then hit = true
elseif d + attack < ac then hit = false
end
-- log message
if hit then
game.log(("%s hits the enemy! %d + %d = %d vs AC %d"):format(self.name:capitalize(), d, attack, d+attack, ac))
else
game.log(("%s misses the enemy! %d + %d = %d vs AC %d"):format(self.name:capitalize(), d, attack, d+attack, ac))
end
-- Crit check TODO Improved Critical
local threat = 0 + (weapon and weapon.combat.threat or 0)
if hit and d >= 20 - threat then
-- threatened critical hit confirmation roll
if not (rng.range(1,20) + attack < ac) then
crit = true
end
end
if hit then
local dam = rng.dice(self.combat.dam[1],self.combat.dam[2])
if crit then
game.log(("%s makes a critical attack!"):format(self.name:capitalize()))
dam = dam * (weapon and weapon.combat.critical or 2)
end
-- magic damage bonus
dam = dam + (weapon and weapon.combat.magic_bonus or 0)
-- Stat damage bonus
if weapon and weapon.ranged then
strmod = strmod or 0
else
strmod = strmod or 1
end
dam = dam + strmod * (self:getStr()-10)/2
if self:knowTalent(self.T_FAVORED_ENEMY) then
if target.type ~= "humanoid" then
if target.type == self.favored_enemy then dam = dam + 2 end
else
if target.subtype == self.favored_enemy then dam = dam + 2 end
end
end
--Minimum 1 point of damage unless Damage Reduction works
dam = math.max(1, dam)
dam = dam - (target.combat_dr or 0)
target:takeHit(dam, self)
game.log(("%s deals %d damage to %s!"):format(self.name:capitalize(), dam, target.name:capitalize()))
end
end