Skip to content

Instantly share code, notes, and snippets.

@MEXAHOTABOP
Created July 16, 2012 17:15
Show Gist options
  • Save MEXAHOTABOP/3123846 to your computer and use it in GitHub Desktop.
Save MEXAHOTABOP/3123846 to your computer and use it in GitHub Desktop.
// Author: Atom, cvet
// 21-08-2010 05:09
// vanilla
#include "_macros.fos"
#include "critical_table.fos"
#include "combat_msg.fos"
#include "npc_ai.fos"
#include "combat_h.fos"
import void DeteriorateItem(Critter& cr, Item& item, int deteriorateCount) from "repair";
import void AffectPoison(Critter& cr, int value) from "poison";
import void AffectRadiation(Critter& cr, int value) from "radiation";
import void QuakeScreen(Map& map) from "effects";
import uint GetSmokePenalty(Map@ map, Critter@ attacker, Critter@ target) from "smoke_grenade";
import void SmokeBlast(Map& map, uint16 hexX, uint16 hexY, uint16 smokePid, uint ownerId) from "smoke_grenade";
class AttackStruct
{
Critter@ Attacker;
Item@ RealWeapon;
uint16 Hx;
uint16 Hy;
uint8 Aim;
bool IsBurst;
bool BloodyMess;
bool CombatMessage;
bool scoreUnarmed;
int WeaponPerk;
uint8 WeaponSubtype;
int DmgMin;
int DmgMax;
int DmgType;
int BonusDmg;
int DmgMul;
int DRMod;
int DMMod;
int DDMod;
uint TargetId;
bool TargetHit;
uint ForceFlags;
bool ShowHitAnimForce;
AttackStruct()
{
@Attacker=null;
@RealWeapon=null;
Hx=0;
Hy=0;
Aim=HIT_LOCATION_UNCALLED;
IsBurst=false;
BloodyMess=false;
CombatMessage=false;
scoreUnarmed=false;
WeaponPerk=-1;
WeaponSubtype=0;
DmgMin=0;
DmgMax=0;
DmgType=0;
BonusDmg=0;
DmgMul=2;
DRMod=0;
DMMod=1;
DDMod=1;
TargetId=0;
TargetHit=false;
ForceFlags=0;
ShowHitAnimForce=false;
}
};
void CombatAttack(Critter& cr, Critter@ target, ProtoItem& weapon, uint8 weaponMode, ProtoItem@ ammo, uint16 hexX, uint16 hexY) // Export
{
if(not valid(target) && hexX==0 && hexY==0)
{
return;
}
uint8 use=_WeaponModeUse(weaponMode);
uint8 aim=_WeaponModeAim(weaponMode);
Item@ realWeapon=_CritGetItemHand(cr);
Map@ map=cr.GetMap();
int wpnMaxDist=_WeaponMaxDist(weapon,use);
int skillNum=_WeaponSkill(weapon,use);
if (skillNum == SK_THROWING) wpnMaxDist = MIN(wpnMaxDist, 3 * MIN(int(10), (cr.Stat[ST_STRENGTH] + 2*cr.Perk[PE_HEAVE_HO])));
int skillVal=cr.Skill[skillNum];
uint8 weaponSubtype=
(skillNum==SK_SMALL_GUNS || skillNum==SK_BIG_GUNS || skillNum==SK_ENERGY_WEAPONS)?WS_GUN:((skillNum==SK_THROWING)?WS_THROWING:(skillNum==SK_MELEE_WEAPONS)?WS_MELEE:WS_UNARMED);
bool isRanged=(weaponSubtype == WS_THROWING || weaponSubtype == WS_GUN);
bool isUnarmed=weapon.Weapon_IsUnarmed;
bool isHthAttack=(weaponSubtype==WS_MELEE || weaponSubtype==WS_UNARMED);
uint16 ammoRound=_WeaponRound(weapon,use);
bool wpnIsRemoved=_WeaponRemove(weapon,use);
uint16 hx=cr.HexX;
uint16 hy=cr.HexY;
uint16 tx=(valid(target))?target.HexX:hexX;
uint16 ty=(valid(target))?target.HexY:hexY;
uint16 weapPid=weapon.ProtoId;
uint16 ammoPid=0;
if (valid(ammo)) ammoPid=ammo.ProtoId;
bool isBurst=(ammoRound>1); // will suffice for now
if (isBurst) aim=HIT_LOCATION_UNCALLED;
int dmgType=_WeaponDmgType(weapon,use);
bool isGrenade=weaponSubtype == WS_THROWING && (dmgType==DAMAGE_PLASMA || dmgType==DAMAGE_EMP || dmgType==DAMAGE_EXPLODE); // like in fo2
//bool isAmmo=(ammoPid == PID_SIGNAL_ROCKET) && (dmgType==DAMAGE_PLASMA || dmgType==DAMAGE_EMP || dmgType==DAMAGE_EXPLODE); // like in fo2
bool isFlamethrower=(weapPid==PID_FLAMER) || (weapPid==PID_IMPROVED_FLAMETHROWER) || (weapPid==PID_FIRE_GECKO_FLAME_WEAPON);
bool isRocket=(ammoPid == PID_EXPLOSIVE_ROCKET) || (ammoPid == PID_ROCKET_AP) || (ammoPid == PID_SIGNAL_ROCKET) || (ammoPid == PID_ROBO_ROCKET_AMMO);
//bool isRocke=(ammoPid == PID_SIGNAL_ROCKET);
bool eyeDamage=cr.Damage[DAMAGE_EYE]!=0;
int weaponPerk=weapon.Weapon_Perk;
bool crIsPlayer=cr.IsPlayer();
bool isHit=false;
bool isCritical=false;
bool hitRandomly=false;
uint critfailFlags=0;
Critter@ realTarget;
bool isSneak = false;
bool useNormal=false; // used to change target during RunFlyEffect
bool useHex=false; // used to change end hex of RunFlyEffect, i.e. for thrown weapons and rockets.
Critter@ normalTarget; // used as a target for weapon animations;
CombatRes[] results;
int acmod=0; // used many times
// Begin turn based combat
if(!map.IsTurnBased() && map.IsTurnBasedAvailability()) map.BeginTurnBased(cr);
// always unsneak
if(cr.Mode[MODE_HIDE]!=0)
{
cr.ModeBase[MODE_HIDE]=0;
isSneak=true;
}
if(valid(target))
{
target.ModeBase[MODE_HIDE]=0;
}
cr.SetDir(GetDirection(hx,hy,tx,ty));
// now we change aim:
if(aim==HIT_LOCATION_EYES && valid(cr) && valid(target) && (cr.Dir+3)%6!=target.Dir) aim=HIT_LOCATION_HEAD;
if(valid(target))
{
int targetTimeout=BATTLE_TIMEOUT(target);
if(target.Timeout[TO_BATTLE]<targetTimeout-__FullSecond) target.TimeoutBase[TO_BATTLE]=targetTimeout;
}
if(valid(target))
{
int targetTimeout=BATTLE_TIMEOUT(target);
if(target.Timeout[TO_BATTLE]<targetTimeout-__FullSecond) target.TimeoutBase[TO_BATTLE]=targetTimeout;
}
cr.TimeoutBase[TO_SNEAK]=SNEAK_TIMEOUT(cr);
if(valid(target))
{
target.TimeoutBase[TO_SNEAK]=SNEAK_TIMEOUT(target);
}
// leaderboard
if(crIsPlayer)
{
if (weaponSubtype == WS_GUN) cr.AddScore(SCORE_SHOOTER,1);
else if (skillNum==SK_MELEE_WEAPONS || skillNum==SK_THROWING) cr.AddScore(SCORE_MELEE,1);
else if (skillNum==SK_UNARMED) cr.AddScore(SCORE_UNARMED,1);
}
// Npc attack text
if(!crIsPlayer) AI_TrySayCombatText(cr,COMBAT_TEXT_ATTACK);
// Add event, always
if(valid(target))
{
target.EventAttacked(cr);
}
// essential start
// baseToHit used for all targets, adjusted for distance and other target-dependent qualities every time it is necessary
int baseToHit=skillVal;
if (eyeDamage) baseToHit-=25;
if (cr.Perk[PE_VAMPIRE_ACCURACY]>0 && IS_NIGHT(__Hour)) baseToHit+=13; // Quest pe
if (!isUnarmed)
{
if (cr.Trait[TRAIT_ONE_HANDER]!=0) baseToHit+=(FLAG(weapon.Flags,ITEM_TWO_HANDS)?-40:20);
int handlingStrength=cr.Stat[ST_STRENGTH];
int reqStrength=weapon.Weapon_MinStrength;
if (cr.Perk[PE_WEAPON_HANDLING]!=0) handlingStrength+=3;
if (handlingStrength<reqStrength) baseToHit-=(reqStrength-handlingStrength)*20;
if (weaponPerk == WEAPON_PERK_ACCURATE) baseToHit+=20;
}
// main attack structure
AttackStruct attack;
@attack.Attacker=cr;
@attack.RealWeapon=realWeapon;
attack.Hx=hx;
attack.Hy=hy;
attack.Aim=aim;
attack.IsBurst=isBurst;
attack.BloodyMess=cr.Trait[TRAIT_BLOODY_MESS]!=0;
attack.CombatMessage=true;
attack.scoreUnarmed=(weaponSubtype==WS_UNARMED);
attack.WeaponPerk=(isUnarmed && weapon.Weapon_UnarmedArmorPiercing)?WEAPON_PERK_PENETRATE:weaponPerk;
attack.WeaponSubtype=weaponSubtype;
attack.DmgMin=_WeaponDmgMin(weapon,use);
attack.DmgMax=_WeaponDmgMax(weapon,use);
attack.DmgType=dmgType;
attack.BonusDmg=0;
if (isHthAttack) attack.DmgMax+=cr.Stat[ST_MELEE_DAMAGE];
if (weaponSubtype == WS_GUN) attack.BonusDmg+=cr.Perk[PE_BONUS_RANGED_DAMAGE]*2;
attack.DmgMul=2;
// Ammo
if(valid(ammo))
{
attack.DRMod=ammo.Ammo_DRMod;
attack.DMMod=ammo.Ammo_DmgMult;
attack.DDMod=ammo.Ammo_DmgDiv;
if(attack.DMMod==0) attack.DMMod=1;
if(attack.DDMod==0) attack.DDMod=1;
}
// to check if the target was hit:
if(valid(target))
{
attack.TargetId=target.Id;
}
// here we're deciding the real toHit;
int toHit = baseToHit;
toHit-=isHthAttack?(GetHitAim(aim)/2):GetHitAim(aim);
// range considerations, we're storing everything for a later use
int distmod1=2; // used for initial weapon bonus
int distmod2=0; // minimal distance
if (weaponPerk==WEAPON_PERK_LONG_RANGE) distmod1=4;
else if (weaponPerk==WEAPON_PERK_SCOPE_RANGE)
{
distmod1=5;
distmod2=8;
}
int perception=cr.Stat[ST_PERCEPTION];
int dist=GetDistantion(hx,hy,tx,ty);
int acc=dist;
int accloss=(crIsPlayer?(perception-2)*distmod1:(perception*distmod1));
int sharpshooter=2*cr.Perk[PE_SHARPSHOOTER];
if (!isHthAttack)
{
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
int blockers=map.GetCrittersPath(hx, hy, tx, ty, 0.0f, dist, FIND_LIFE, null);
if (valid(target) && !target.IsKnockout()) blockers--;
toHit-=10*blockers;
}
if(valid(target))
{
acmod=target.Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (target.IsKnockout()) toHit+=40;
if (target.GetMultihex()>0) toHit+=15;
}
toHit=CLAMP(toHit,5,95);
// smoke penalty
if (!isHthAttack) toHit-=GetSmokePenalty(map, cr, target);
// main roll
int margin=toHit-Random(1,100);
if (margin<0) // if missed
{
// not setting isHit, we try a critical failure
if (cr.Mode[MODE_INVULNERABLE]==0)
{
isCritical = ((-margin)/10 >= Random(1,100));
// basic jinxed behaviour (upgrade if anyone is jinxed and coinflip is passed)
if (!isCritical)
isCritical= (Random(0,1)==0 &&
(cr.Trait[TRAIT_JINXED]!=0 || cr.Perk[PE_JINXED_II]!=0 || (valid(target) &&(target.Trait[TRAIT_JINXED]!=0 || target.Perk[PE_JINXED_II]!=0))));
if (isCritical)
{
int roll = Random(1,100) - 5*(cr.Stat[ST_LUCK]-5);
if (roll <= 20) roll = 0;
else if (roll <=50) roll = 1;
else if (roll <=75) roll = 2;
else if (roll <=95) roll = 3;
else roll = 4;
critfailFlags=CriticalFailureTable[5*weapon.Weapon_CriticalFailture+roll];
if (critfailFlags==0) isCritical=false;
hitRandomly=FLAG(critfailFlags,MF_HIT_RANDOMLY);
}
}
}
else // if hit
{
isHit=true;
if (isHthAttack && cr.Perk[PE_SLAYER]!=0) isCritical=true;
else
isCritical = (Random(1,100) <= cr.Stat[ST_CRITICAL_CHANCE] + margin/10 + GetHitAim(aim));
if (!isCritical && weaponSubtype==WS_GUN && cr.Perk[PE_SNIPER]!=0) isCritical;
if (!isCritical && weaponSubtype==WS_GUN && cr.Perk[PE_SNIPER_2]!=0) isCritical = (Random(1,100) <= cr.Stat[ST_LUCK]*6 + cr.Stat[ST_CRITICAL_CHANCE]);
if (!isCritical && isUnarmed) isCritical = Random(1,100) <= weapon.Weapon_UnarmedCriticalBonus;
}
if (hitRandomly && valid(target))
{
Critter@ randomTarget=ChooseRandomTarget(map,cr,target,wpnMaxDist);
if (@randomTarget!=null)
{
@realTarget=randomTarget;
tx=realTarget.HexX;
ty=realTarget.HexY;
attack.Aim=HIT_LOCATION_UNCALLED;
realTarget.ModeBase[MODE_HIDE]=0; // done here to allow combat notifications later
NotifyOops(cr, target, realTarget, results);
}
}
else
@realTarget=target;
// initial animations
cr.Action(ACTION_USE_WEAPON,(((!isHit && isCritical && !hitRandomly)?1:0)<<8)|(aim<<4)|use,realWeapon);
// animations are now played, and we're updating the hit randomly status now
bool changedTarget = (hitRandomly && (@realTarget!=null));
// commencing critical failures
if (!isHit && isCritical && (!hitRandomly || @realTarget==null))
{
CriticalFailure(cr, weapon, use, ammo, critfailFlags, results);
return;
}
// commencing attack, various instances
bool criticalHit = isHit && isCritical;
if (isHthAttack) // UNARMED AND MELEE ATTACK
{
if (isHit)
{
int crDir = cr.Dir;
int tDir = realTarget.Dir;
if (isSneak && cr.Perk[PE_SILENT_DEATH]!=0 && (crDir == tDir || ((crDir+1)%6)==tDir || ((crDir+5)%6)==tDir)) attack.DmgMul*=2;
ApplyDamage(attack, realTarget, 1, criticalHit, true, results);
}
else
{
if(changedTarget)
ApplyDamage(attack, realTarget, 1, false, false, results);
else
NotifyMiss(cr, results);
}
}
else if ((weaponSubtype == WS_GUN) && !isBurst && !isRocket && !isFlamethrower) // single shot, normal
{
if (isHit || changedTarget) ApplyDamage(attack, realTarget, 1, criticalHit, !changedTarget, results);
else // standard miss here
{
Critter@[] critsLine;
attack.Aim=HIT_LOCATION_UNCALLED;
map.GetCrittersPath(hx, hy, tx, ty, 0.0f, wpnMaxDist, FIND_LIFE_AND_KO, critsLine);
int bl=0;
bool anyHit=false;
for (int i=0, j=critsLine.length(); (i<j) && !anyHit ; i++)
{
if (critsLine[i].Id == realTarget.Id) {bl++; continue;} // skip the primary target
// adjust tohit
dist=GetDistantion(hx, hy, critsLine[i].HexX, critsLine[i].HexY);
acc=dist;
toHit = baseToHit;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=critsLine[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
toHit-=10*bl;
if (critsLine[i].IsKnockout()) toHit+=40;
else bl++;
if (critsLine[i].GetMultihex()>0) toHit+=15;
toHit=CLAMP(toHit,5,95);
toHit/=3; // after clamp
if (Random(1,100)<=toHit)
{
NotifyOops(cr, target, critsLine[i], results);
@normalTarget=critsLine[i];
useNormal=true;
ApplyDamage(attack, normalTarget, 1, false, false, results);
anyHit=true;
}
}
if (!anyHit) NotifyMiss(cr, results);
}
}
else if (isFlamethrower) // FLAME ATTACK
{
// adjust toHit if random hit
if (changedTarget)
{
dist=GetDistantion(hx, hy, tx, ty);
acc=dist;
toHit = baseToHit;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=realTarget.Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (realTarget.IsKnockout()) toHit+=40;
if (realTarget.GetMultihex()>0) toHit+=15;
int blockers=map.GetCrittersPath(hx, hy, tx, ty, 0.0f, dist, FIND_LIFE, null);
if (!realTarget.IsKnockout()) blockers--;
toHit-=10*blockers;
toHit=CLAMP(toHit,5,95);
}
// critical hit bonus, toHit can be increased over 100
if (criticalHit)
toHit+=20;
// proceed with the flame attack
Critter@[] critsHit(0);
uint[] critsHitBullets(0);
int len_=0;
if (Random(1,100)<=toHit)
{
critsHit.resize(1);
@critsHit[0]=realTarget;
critsHitBullets.resize(1);
critsHitBullets[0]+=1;
len_++;
}
Critter@[] lineCentral;
map.GetCrittersPath(hx,hy,tx,ty,0.0f,wpnMaxDist,FIND_LIFE_AND_KO,lineCentral);
int bl;
bool threeLines=(GetDistantion(hx,hy,lineCentral[0].HexX,lineCentral[0].HexY)>1); // target not adjacent and weapon not shotgun
// already shot: MAX(rounds/6,1);
for (int lineCount=0,lineMax=(threeLines?1:3);lineCount<lineMax;lineCount++)
{
// lineCentral
bl=0; // zero blockers
for (int i=0,j=lineCentral.length();i<j; i++)
{
// adjust tohit
toHit=baseToHit-10*bl;
dist=GetDistantion(hx,hy,lineCentral[i].HexX,lineCentral[i].HexY);
acc=dist;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=lineCentral[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (lineCentral[i].IsKnockout()) toHit+=40;
else bl++;
if (lineCentral[i].GetMultihex()>0) toHit+=15;
toHit=CLAMP(toHit,5,95);
if (Random(1,100)<=toHit)
{
int crIndex=FindCritterInArray(critsHit, lineCentral[i]);
if (crIndex==-1)
{
critsHit.resize(len_+1);
@critsHit[len_]=lineCentral[i];
critsHitBullets.resize(len_+1);
crIndex=len_;
len_++;
}
critsHitBullets[crIndex]+=1;
}
}
// lineCentral end
}
if (threeLines)
{
// rounds for the left stack
// real left
uint8 leftDir=GetOffsetDir(hx,hy,tx,ty,89.0f);
uint16 sx=hx;
uint16 sy=hy;
uint16 ex=tx;
uint16 ey=ty;
map.MoveHexByDir(sx,sy,leftDir,1);
map.MoveHexByDir(ex,ey,leftDir,1);
Critter@[] lineLeft;
map.GetCrittersPath(sx,sy,ex,ey,0.0f,wpnMaxDist-1,FIND_LIFE_AND_KO,lineLeft);
int leftStart=0;
int leftLen=lineLeft.length();
while ((leftStart<leftLen) && (GetDistantion(hx,hy,lineLeft[leftStart].HexX,lineLeft[leftStart].HexY))<3) leftStart++;
for (int i=leftStart,j=leftLen;i<j; i++)
{
// adjust tohit
dist=GetDistantion(hx,hy,lineLeft[i].HexX,lineLeft[i].HexY);
bl=map.GetCrittersPath(hx,hy,lineLeft[i].HexX,lineLeft[i].HexY,0.0f,dist,FIND_LIFE,null)-1;
toHit=baseToHit-10*bl;
acc=dist;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=lineLeft[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (lineLeft[i].IsKnockout()) toHit+=40;
if (lineLeft[i].GetMultihex()>0) toHit+=15;
toHit=CLAMP(toHit,5,95);
if (Random(1,100)<=toHit)
{
int crIndex=FindCritterInArray(critsHit, lineLeft[i]);
if (crIndex==-1)
{
critsHit.resize(len_+1);
@critsHit[len_]=lineLeft[i];
critsHitBullets.resize(len_+1);
crIndex=len_;
len_++;
}
critsHitBullets[crIndex]+=1;
}
} // left line
uint8 rightDir=GetOffsetDir(hx,hy,tx,ty,-89.0f);
sx=hx;
sy=hy;
ex=tx;
ey=ty;
map.MoveHexByDir(sx,sy,rightDir,1);
map.MoveHexByDir(ex,ey,rightDir,1);
Critter@[] lineRight;
map.GetCrittersPath(sx,sy,ex,ey,0.0f,wpnMaxDist-1,FIND_LIFE_AND_KO,lineRight);
int rightStart=0;
int rightLen=lineRight.length();
while ((rightStart<rightLen) && (GetDistantion(hx,hy,lineRight[rightStart].HexX,lineRight[rightStart].HexY))<3) rightStart++;
for (int i=rightStart,j=rightLen;i<j; i++)
{
// adjust tohit
dist=GetDistantion(hx,hy,lineRight[i].HexX,lineRight[i].HexY);
bl=map.GetCrittersPath(hx,hy,lineRight[i].HexX,lineRight[i].HexY,0.0f,dist,FIND_LIFE,null)-1;
toHit=baseToHit-10*bl;
acc=dist;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=lineRight[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (lineRight[i].IsKnockout()) toHit+=40;
if (lineRight[i].GetMultihex()>0) toHit+=15;
toHit=CLAMP(toHit,5,95);
if (Random(1,100)<=toHit)
{
int crIndex=FindCritterInArray(critsHit, lineRight[i]);
if (crIndex==-1)
{
critsHit.resize(len_+1);
@critsHit[len_]=lineRight[i];
critsHitBullets.resize(len_+1);
crIndex=len_;
len_++;
}
critsHitBullets[crIndex]+=1;
}
} // right line
}
for (int i=0,j=len_;i<j;i++)
ApplyDamage(attack, critsHit[i], 1, (realTarget.Id == critsHit[i].Id) && criticalHit, (cr.IsPlayer() && !changedTarget) || (cr.IsNpc() && critsHit[i].Id==target.Id), results);
if (!changedTarget && !attack.TargetHit) NotifyMiss(cr, results);
}
else if ((weaponSubtype == WS_GUN) && isBurst) // BURST FIRE
{
// adjust toHit if random hit
if (changedTarget)
{
dist=GetDistantion(hx, hy, tx, ty);
acc=dist;
toHit = baseToHit;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=realTarget.Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (realTarget.IsKnockout()) toHit+=40;
if (realTarget.GetMultihex()>0) toHit+=15;
int blockers=map.GetCrittersPath(hx, hy, tx, ty, 0.0f, dist, FIND_LIFE, null);
if (!realTarget.IsKnockout()) blockers--;
toHit-=10*blockers;
toHit=CLAMP(toHit,5,95);
}
// critical hit bonus, toHit can be increased over 100
if (criticalHit)
toHit+=20;
// proceed with the burst attack
int rounds=ammoRound;
if(valid(realWeapon) && realWeapon.WeaponAmmoCount<rounds) rounds=realWeapon.WeaponAmmoCount;
Critter@[] critsHit(0);
uint[] critsHitBullets(0);
int len_=0;
int volleyRounds = MAX((rounds/8),1);
int curRounds=0;
for (int i=0; i<volleyRounds; i++) { if (Random(1,100)<=toHit) curRounds++; }
if (curRounds!=0)
{
critsHit.resize(1);
critsHitBullets.resize(1);
@critsHit[0]=realTarget;
critsHitBullets[0]=curRounds;
len_++;
}
volleyRounds-=curRounds;
Critter@[] lineCentral;
map.GetCrittersPath(hx,hy,tx,ty,0.0f,wpnMaxDist,FIND_LIFE_AND_KO,lineCentral);
int bl=0;
curRounds=0;
for (int i=0,j=lineCentral.length();(i<j) && (volleyRounds > 0); i++)
{
if (lineCentral[i].Id == realTarget.Id) { bl++; continue; } // skip the primary target, but add blocker
// adjust tohit
toHit=baseToHit-10*bl;
dist=GetDistantion(hx,hy,lineCentral[i].HexX,lineCentral[i].HexY);
acc=dist;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=lineCentral[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
toHit=CLAMP(toHit,5,95);
if (lineCentral[i].IsKnockout()) toHit+=40;
else bl++;
if (lineCentral[i].GetMultihex()>0) toHit+=15;
for (curRounds=0;curRounds<volleyRounds;)
{
if (Random(1,100)<=toHit) curRounds++;
else break;
}
volleyRounds-=curRounds;
if (curRounds>0)
{
int crIndex=FindCritterInArray(critsHit, lineCentral[i]);
if (crIndex==-1)
{
critsHit.resize(len_+1);
@critsHit[len_]=lineCentral[i];
critsHitBullets.resize(len_+1);
critsHitBullets[len_]=0;
crIndex=len_;
len_++;
}
critsHitBullets[crIndex]+=curRounds;
}
}
// now we're shooting the rest of the bullets (=rounds-MAX(1,rounds/6))
bool threeLines=(GetDistantion(hx,hy,lineCentral[0].HexX,lineCentral[0].HexY)>1) && (ammoRound>3); // target not adjacent and weapon not shotgun
// already shot: MAX(rounds/6,1);
for (int lineCount=0,lineMax=(threeLines?1:3);lineCount<lineMax;lineCount++)
{
// rounds for central stack
if (lineCount==0)
{
volleyRounds=rounds-(((rounds+1)/3)+(rounds/3)); // second central, always
volleyRounds-=MAX(rounds/6,1);
}
else
{ if (lineCount==1)
volleyRounds=(rounds+1)/3; // left line
else
volleyRounds=rounds/3; // right line
}
if (volleyRounds==0) continue ; // end this
// lineCentral
bl=0; // zero blockers
for (int i=0,j=lineCentral.length();(i<j) && (volleyRounds > 0); i++)
{
// adjust tohit
toHit=baseToHit-10*bl;
dist=GetDistantion(hx,hy,lineCentral[i].HexX,lineCentral[i].HexY);
acc=dist;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=lineCentral[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (lineCentral[i].IsKnockout()) toHit+=40;
if (lineCentral[i].GetMultihex()>0) toHit+=15;
toHit=CLAMP(toHit,5,95);
bl++;
for (curRounds=0;curRounds<volleyRounds;)
{
if (Random(1,100)<=toHit) curRounds++;
else break;
}
volleyRounds-=curRounds;
if (curRounds>0)
{
int crIndex=FindCritterInArray(critsHit, lineCentral[i]);
if (crIndex==-1)
{
critsHit.resize(len_+1);
@critsHit[len_]=lineCentral[i];
critsHitBullets.resize(len_+1);
critsHitBullets[len_]=0;
crIndex=len_;
len_++;
}
critsHitBullets[crIndex]+=curRounds;
}
}
// lineCentral end
}
if (threeLines)
{
// rounds for the left stack
volleyRounds=(rounds+1)/3;
if (volleyRounds>0) // real left
{
uint8 leftDir=GetOffsetDir(hx,hy,tx,ty,89.0f);
uint16 sx=hx;
uint16 sy=hy;
uint16 ex=tx;
uint16 ey=ty;
map.MoveHexByDir(sx,sy,leftDir,1);
map.MoveHexByDir(ex,ey,leftDir,1);
Critter@[] lineLeft;
map.GetCrittersPath(sx,sy,ex,ey,0.0f,wpnMaxDist-1,FIND_LIFE_AND_KO,lineLeft);
int leftStart=0;
int leftLen=lineLeft.length();
while ((leftStart<leftLen) && (GetDistantion(hx,hy,lineLeft[leftStart].HexX,lineLeft[leftStart].HexY))<3) leftStart++;
for (int i=leftStart,j=leftLen;(i<j) && (volleyRounds > 0); i++)
{
// adjust tohit
dist=GetDistantion(hx,hy,lineLeft[i].HexX,lineLeft[i].HexY);
bl=map.GetCrittersPath(hx,hy,lineLeft[i].HexX,lineLeft[i].HexY,0.0f,dist,FIND_LIFE,null)-1;
toHit=baseToHit-10*bl;
acc=dist;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=lineLeft[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (lineLeft[i].IsKnockout()) toHit+=40;
if (lineLeft[i].GetMultihex()>0) toHit+=15;
toHit=CLAMP(toHit,5,95);
for (curRounds=0;curRounds<volleyRounds;)
{
if (Random(1,100)<=toHit) curRounds++;
else break;
}
volleyRounds-=curRounds;
if (curRounds>0)
{
int crIndex=FindCritterInArray(critsHit, lineLeft[i]);
if (crIndex==-1)
{
critsHit.resize(len_+1);
@critsHit[len_]=lineLeft[i];
critsHitBullets.resize(len_+1);
critsHitBullets[len_]=0;
crIndex=len_;
len_++;
}
critsHitBullets[crIndex]+=curRounds;
}
}
} // left line
// rounds for the right stack
volleyRounds=(rounds)/3;
if (volleyRounds>0)
{
uint8 rightDir=GetOffsetDir(hx,hy,tx,ty,-89.0f);
uint16 sx=hx;
uint16 sy=hy;
uint16 ex=tx;
uint16 ey=ty;
map.MoveHexByDir(sx,sy,rightDir,1);
map.MoveHexByDir(ex,ey,rightDir,1);
Critter@[] lineRight;
map.GetCrittersPath(sx,sy,ex,ey,0.0f,wpnMaxDist-1,FIND_LIFE_AND_KO,lineRight);
int rightStart=0;
int rightLen=lineRight.length();
while ((rightStart<rightLen) && (GetDistantion(hx,hy,lineRight[rightStart].HexX,lineRight[rightStart].HexY))<3) rightStart++;
for (int i=rightStart,j=rightLen;(i<j) && (volleyRounds > 0); i++)
{
// adjust tohit
dist=GetDistantion(hx,hy,lineRight[i].HexX,lineRight[i].HexY);
bl=map.GetCrittersPath(hx,hy,lineRight[i].HexX,lineRight[i].HexY,0.0f,dist,FIND_LIFE,null)-1;
toHit=baseToHit-10*bl;
acc=dist;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=lineRight[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (lineRight[i].IsKnockout()) toHit+=40;
if (lineRight[i].GetMultihex()>0) toHit+=15;
toHit=CLAMP(toHit,5,95);
for (curRounds=0;curRounds<volleyRounds;)
{
if (Random(1,100)<=toHit) curRounds++;
else break;
}
volleyRounds-=curRounds;
if (curRounds>0)
{
int crIndex=FindCritterInArray(critsHit, lineRight[i]);
if (crIndex==-1)
{
critsHit.resize(len_+1);
@critsHit[len_]=lineRight[i];
critsHitBullets.resize(len_+1);
critsHitBullets[len_]=0;
crIndex=len_;
len_++;
}
critsHitBullets[crIndex]+=curRounds;
}
}
} // right line
}
for (int i=0,j=len_;i<j;i++)
ApplyDamage(attack, critsHit[i], critsHitBullets[i], (realTarget.Id == critsHit[i].Id) && criticalHit, (cr.IsPlayer() && !changedTarget) || (cr.IsNpc() && critsHit[i].Id==target.Id), results);
if (!changedTarget && !attack.TargetHit) NotifyMiss(cr, results);
} // burst attack
else if (isRocket || (weaponSubtype==WS_THROWING)) // ROCKETS, THROWING
{
bool exploding=isRocket || isGrenade;
if (isHit || changedTarget)
{
if (exploding)
CommenceExplosion(ammoPid, attack, map, tx, ty, realTarget, weapPid, criticalHit, valid(realTarget)?realTarget.Id:0, isRocket, results);
else
ApplyDamage(attack, realTarget, 1, criticalHit, !changedTarget, results);
}
else
{ // miss with missiles
attack.Aim=HIT_LOCATION_UNCALLED;
if (weaponSubtype == WS_THROWING) sharpshooter=0;
uint16 bx=0;
uint16 by=0;
uint16 pbx=0;
uint16 pby=0;
Critter@[] critsLine;
map.GetCrittersPath(hx, hy, tx, ty, 0.0f, wpnMaxDist, FIND_LIFE_AND_KO, critsLine, pbx, pby, bx, by);
int bl=0;
bool anyHit=false;
for (int i=0, j=critsLine.length(); (i<j) && !anyHit ; i++)
{
if (valid(realTarget) && critsLine[i].Id == realTarget.Id) {bl++; continue;} // skip the primary target
// adjust tohit
dist=GetDistantion(hx, hy, critsLine[i].HexX, critsLine[i].HexY);
acc=dist;
toHit = baseToHit;
if (dist < distmod2) acc+=distmod2;
else acc-=accloss;
if (-2*perception > acc) acc = -2*perception;
acc-=sharpshooter;
if (acc>0 && eyeDamage) acc*=3;
acc*=-4;
toHit+=acc;
acmod=critsLine[i].Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
toHit-=10*bl;
if (critsLine[i].IsKnockout()) toHit+=40;
else bl++;
if (critsLine[i].GetMultihex()>0) toHit+=15;
toHit=CLAMP(toHit,5,95);
toHit/=3; // after clamp
if (Random(1,100)<=toHit)
{
tx=critsLine[i].HexX;
ty=critsLine[i].HexY;
@normalTarget=critsLine[i];
anyHit=true;
}
}
if (anyHit)
{
NotifyOops(cr, target, normalTarget, results);
useNormal=true;
if (exploding)
CommenceExplosion(ammoPid, attack, map, tx, ty, normalTarget, weapPid, false, valid(realTarget)?realTarget.Id:0, isRocket, results);
else
ApplyDamage(attack, normalTarget, 1, false, false, results);
}
else
{
useHex=true;
NotifyMiss(cr, results);
if (isGrenade)
{
tx = valid(realTarget)?realTarget.HexX:hexX;
ty = valid(realTarget)?realTarget.HexY:hexY;
int newdist=GetDistantion(hx,hy,tx,ty)+1;
map.MoveHexByDir(tx,ty,Random(0,5),Random(1,newdist/2));
newdist=GetDistantion(hx,hy,tx,ty);
map.GetCrittersPath(hx, hy, tx, ty, 0.0f, newdist, FIND_LIFE, null, tx, ty, bx, by);
}
else
{
if (isRocket)
{
tx=bx;
ty=by;
}
else
{
tx=pbx;
ty=pby;
}
}
if (exploding)
CommenceExplosion(ammoPid, attack , map, tx, ty, null, weapPid, false, valid(realTarget)?realTarget.Id:0, isRocket, results);
}
} // miss
}
else
cr.Say(SAY_NETMSG, "Combat error: weapon PID="+weapPid+" not handled, please send bug report.");
// combat messages
FlushResults(results);
// stuff like animations, effects
// Shoot
if (_WeaponEffect(weapon,use)!=0)
{
if (useHex||not valid(realTarget))
map.RunFlyEffect(_WeaponEffect(weapon,use),cr,null,hx,hy,tx,ty); // yeah, the target can be null (see: grenades, rocket launcher)
else
map.RunFlyEffect(_WeaponEffect(weapon,use),cr,(useNormal?normalTarget:realTarget),hx,hy,tx,ty);
}
if(ammoRound>0 && valid(realWeapon) && cr.Mode[MODE_UNLIMITED_AMMO]==0)
{
if(realWeapon.WeaponAmmoCount<=ammoRound) realWeapon.WeaponAmmoCount=0;
else realWeapon.WeaponAmmoCount-=ammoRound;
realWeapon.Update();
}
if(valid(realWeapon))
{
if(realWeapon.IsDeteriorable()) DeteriorateItem(cr,realWeapon,(MAX_SKILL_VAL-skillVal)/3);
// One time weapon, erase current and push next (if exist)
if(wpnIsRemoved && cr.Mode[MODE_UNLIMITED_AMMO]==0)
{
bool placeOnHex=(skillNum==SK_THROWING && !isGrenade);
if(realWeapon.IsStackable())
{
// Place on hex
if(placeOnHex) map.AddItem(tx,ty,weapPid,1);
// Sub 1 item
if(realWeapon.GetCount()>1)
realWeapon.SetCount(realWeapon.GetCount()-1);
else
DeleteItem(realWeapon);
}
else
{
MoveItem(realWeapon,0,map,tx,ty);
}
}
}
// Npc miss text
if (!attack.TargetHit && valid(target))
{
if(!target.IsPlayer()) AI_TrySayCombatText(target,COMBAT_TEXT_MISS);
target.Action(ACTION_DODGE,0,null); // Todo: type front/back
}
return;
}
void ApplyDamage(AttackStruct &attack, Critter &target, uint rounds, bool isCritical, bool intentionally, CombatRes[]& results)
{
int dmgMul = attack.DmgMul;
int bt=target.Stat[ST_BODY_TYPE];
uint eff=0;
uint msgEff=0;
int weaponPerk=attack.WeaponPerk;
Critter@ attacker=attack.Attacker;
bool validAttacker = valid(attacker);
Map@ map=target.GetMap();
bool isBloodyMess=attack.BloodyMess;
bool isCombatText=attack.CombatMessage;
Item@ realWeapon=attack.RealWeapon;
uint aim=attack.Aim;
uint extraMessage=0;
uint extraMessageSelf=0;
bool targetIsPlayer=target.IsPlayer();
bool attackerIsPlayer=(valid(attacker)?attacker.IsPlayer():false);
if(target.IsDead() || not valid(map)) return;
if (validAttacker && intentionally && target.Id!=attack.TargetId) target.EventAttacked(attacker);
// check if hit
if (target.Id == attack.TargetId) attack.TargetHit = true;
// Check no PvP
if(targetIsPlayer && attackerIsPlayer && validAttacker && (attacker.Mode[MODE_NO_PVP]!=0 || target.Mode[MODE_NO_PVP]!=0))
{
if(validAttacker) attacker.Say(SAY_NETMSG,"No PvP.");
target.Say(SAY_NETMSG,"No PvP.");
return;
}
// Мины от игроков, ушедших на глобал
if(targetIsPlayer && !validAttacker && target.Mode[MODE_NO_PVP]!=0)
{
target.Say(SAY_NETMSG,"No PvP.");
return;
}
if(target.Mode[MODE_HIDE]!=0) target.ModeBase[MODE_HIDE]=0;
int targetTimeout=BATTLE_TIMEOUT(target);
if(target.Timeout[TO_BATTLE]<targetTimeout-__FullSecond) target.TimeoutBase[TO_BATTLE]=targetTimeout;
target.TimeoutBase[TO_SNEAK]=SNEAK_TIMEOUT(target);
if (target.Mode[MODE_INVULNERABLE]!=0) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]!=0 && (target.StatBase[ST_STRENGTH]+target.StatBase[ST_ENDURANCE])*4 +
(target.Perk[PE_ANTI_CRIT_10]*2 +
target.Perk[PE_ANTI_CRIT_15]*2 +
target.Perk[PE_ANTI_CRIT_20]*2 +
target.Perk[PE_ANTI_CRIT_25]*4 +
target.Perk[PE_ANTI_CRIT_30]*4 +
target.Perk[PE_ANTI_CRIT_40]*6 +
target.Perk[PE_ANTI_CRIT_45]*6 +
target.Perk[PE_ANTI_CRIT_50]*8 +
target.Perk[PE_ANTI_CRIT_55]*8 +
target.Perk[PE_ANTI_CRIT_60]*10 +
target.Perk[PE_ANTI_CRIT_65]*10)>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_MELEE || attack.WeaponSubtype == WS_UNARMED) && target.Perk[PE_WAR_BOT]*80>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_MELEE || attack.WeaponSubtype == WS_UNARMED) && target.Perk[PE_HANDY_BOT]*80>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_10]*10>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_15]*15>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_20]*20>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_25]*25>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_30]*30>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_40]*40>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_45]*45>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_50]*50>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_55]*55>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_60]*60>=Random(1,100)) isCritical=false;
if (isCritical && (attack.WeaponSubtype == WS_GUN || attack.WeaponSubtype == WS_THROWING) && target.Perk[PE_TERMINATOR]==0 && target.Perk[PE_ANTI_CRIT_65]*65>=Random(1,100)) isCritical=false;
else if (isCritical || attack.ForceFlags!=0)
{
if (isCritical)
{
int aim_=aim;
if(aim_==HIT_LOCATION_NONE) aim_=HIT_LOCATION_UNCALLED;
aim_--;
int roll=Random(0,100)+(validAttacker?( (attacker.Perk[PE_BETTER_CRITICALS]!=0?20:0)+(attacker.Trait[TRAIT_HEAVY_HANDED]!=0?-30:0)+(target.Perk[PE_ANTI_CRIT_10]!=0?-10:0)+(target.Perk[PE_ANTI_CRIT_15]!=0?-15:0)
+(target.Perk[PE_ANTI_CRIT_20]!=0?-20:0)+(target.Perk[PE_ANTI_CRIT_25]!=0?-25:0)+(target.Perk[PE_ANTI_CRIT_30]!=0?-30:0)+(target.Perk[PE_ANTI_CRIT_40]!=0?-40:0)+(target.Perk[PE_ANTI_CRIT_45]!=0?-40:0)
+(target.Perk[PE_ANTI_CRIT_50]!=0?-50:0)+(target.Perk[PE_ANTI_CRIT_55]!=0?-50:0)+(target.Perk[PE_ANTI_CRIT_60]!=0?-55:0)+(target.Perk[PE_ANTI_CRIT_65]!=0?-55:0)):0);
if(roll<=20) roll=0;
else if(roll<=45) roll=1;
else if(roll<=70) roll=2;
else if(roll<=90) roll=3;
else if(roll<=100) roll=4;
else roll=5;
// 0x00000001 - knockout
// 0x00000002 - knockdown
// 0x00000004 - crippled left leg
// 0x00000008 - crippled right leg
// 0x00000010 - crippled left arm
// 0x00000020 - crippled right arm
// 0x00000040 - blinded
// 0x00000080 - death
// 0x00000400 !- on fire - triggers the 'flamedance' animation, but not if the hit is fatal (probably a bug) // not used now
// 0x00000800 - bypass armor
// 0x00004000 - dropped weapon (doesn't work) // haha, really?
// 0x00008000 - lose next turn
// 0x00200000 - random
uint offset=bt*9*6*7+aim_*6*7+roll*7;
dmgMul*=CriticalTable[offset];
dmgMul>>=1;
eff=CriticalTable[offset+1];
int stat=CriticalTable[offset+2];
int statMod=CriticalTable[offset+3];
uint cMessage=CriticalTable[offset+5];
uint cFailureMessage=CriticalTable[offset+6];
extraMessage=cMessage;
if (stat!=-1)
if (Random(1,10) > target.Stat[stat] + statMod)
{
eff|=CriticalTable[offset+4];
extraMessage=cFailureMessage;
}
if (weaponPerk == WEAPON_PERK_ENHANCED_KNOCKOUT) SETFLAG(eff, HF_KNOCKOUT);
if(target.Mode[MODE_NO_KNOCK]!=0) UNSETFLAG(eff,HF_KNOCKDOWN);
else if (FLAG(eff, HF_KNOCKDOWN) && target.Perk[PE_STONEWALL]!=0 && (Random(0,100)<50)) UNSETFLAG(eff,HF_KNOCKDOWN);
}
if (attack.ForceFlags!=0)
{
eff|=attack.ForceFlags;
isCritical=true;
}
// in the following: in no lose limbs, then clear the effects and set msgEff
// otherwise set msgEff first, then check for HF_RANDOM
if (target.Mode[MODE_NO_LOOSE_LIMBS]!=0)
{
UNSETFLAG(eff,(HF_CRIPPLED_LEFT_LEG|HF_CRIPPLED_RIGHT_LEG|HF_CRIPPLED_LEFT_ARM|HF_CRIPPLED_RIGHT_ARM|HF_RANDOM));
msgEff=eff;
}
else
{
msgEff=eff;
if (FLAG(eff, HF_RANDOM))
{
UNSETFLAG(eff, HF_RANDOM);
switch(Random(1,4))
{
case 1: SETFLAG(eff, HF_CRIPPLED_LEFT_LEG); break;
case 2: SETFLAG(eff, HF_CRIPPLED_RIGHT_LEG); break;
case 3: SETFLAG(eff, HF_CRIPPLED_LEFT_ARM); break;
case 4: SETFLAG(eff, HF_CRIPPLED_RIGHT_ARM); break;
default: break;
}
}
}
if (target.Mode[MODE_NO_DROP]!=0)
{
UNSETFLAG(eff,HF_DROPPED_WEAPON);
UNSETFLAG(msgEff,HF_DROPPED_WEAPON);
}
if(validAttacker && attackerIsPlayer) attacker.AddScore(SCORE_SNIPER,1);
} // end isCritical
if (weaponPerk == WEAPON_PERK_ENHANCED_KNOCKOUT)
{
if (validAttacker)
if (Random(1,100)<=(attacker.Stat[ST_STRENGTH])-8) SETFLAG(eff,HF_KNOCKOUT);
} // not messaged
if (weaponPerk == WEAPON_PERK_BEZRUK)
{
int dmgType=attack.DmgType;
int targetDR=target.Stat[ST_NORMAL_RESIST+dmgType-1];
int targetDT=target.Stat[ST_NORMAL_ABSORB+dmgType-1];
int totalDmg=200;
dmgMul*=attack.DMMod;
if(Random(1,10)>=5) totalDmg=dmgMul*8;
if(attacker.StatBase[ST_STRENGTH]<=7) attacker.DamageBase[DAMAGE_RIGHT_ARM]=Random(0,5);
if(attacker.StatBase[ST_STRENGTH]<=7) attacker.DamageBase[DAMAGE_LEFT_ARM]=Random(0,5);
}
int dmgType=attack.DmgType;
int targetDR=target.Stat[ST_NORMAL_RESIST+dmgType-1];
int targetDT=target.Stat[ST_NORMAL_ABSORB+dmgType-1];
if (FLAG(eff,HF_BYPASS_ARMOR) && dmgType!=DAMAGE_EMP)
{
targetDT/=5;
targetDR/=5;
targetDR-=(valid(attacker)?(attacker.Trait[TRAIT_FINESSE]!=0?30:0):0);
}
else if (weaponPerk == WEAPON_PERK_PENETRATE) targetDT/=5;
targetDR+=(validAttacker?(attacker.Trait[TRAIT_FINESSE]!=0?30:0):0);
targetDR+=attack.DRMod; //always
targetDR=CLAMP(targetDR,0,100);
int dmgMin=attack.DmgMin + attack.BonusDmg;;
int dmgMax=attack.DmgMax + attack.BonusDmg;;
int rawDmg;
int totalDmg=0;
dmgMul*=attack.DMMod;
int dmgDiv=2*attack.DDMod;
for (uint i=0;i<rounds;i++)
{
rawDmg=Random(dmgMin,dmgMax);
rawDmg*=dmgMul;
rawDmg/=dmgDiv;
rawDmg-=targetDT;
rawDmg-=(rawDmg*targetDR)/100;
if (rawDmg > 0) totalDmg+=rawDmg;
}
if (validAttacker)
{
if (attack.Attacker.Perk[PE_LIVING_ANATOMY]!=0 && !(bt==BT_ALIEN || bt==BT_ROBOT)) totalDmg+=10; // yes, pulse grenade works
if (attack.Attacker.Perk[PE_PYROMANIAC]!=0 && dmgType == DAMAGE_FIRE) totalDmg+=50;
if (attack.Attacker.Perk[WEAPON_PERK_BEZRUK]!=0 && dmgType == DAMAGE_NORMAL) totalDmg+=50;
if (attack.Attacker.Perk[PE_FANATIC]!=0 && dmgType == DAMAGE_LASER) totalDmg+=attack.Attacker.Stat[ST_PERCEPTION]*5;
if (attack.Attacker.Perk[PE_FANATIC]!=0 && dmgType == DAMAGE_PLASMA) totalDmg+=attack.Attacker.Stat[ST_PERCEPTION]*5; //
if (attack.Attacker.Perk[PE_FANATIC]!=0 && dmgType == DAMAGE_ELECTR) totalDmg+=attack.Attacker.Stat[ST_PERCEPTION]*5; //<= cr.Stat[ST_PERCEPTION]*5
}
// 0x00000001 - knockout
// 0x00000002 - knockdown
// 0x00000004 - crippled left leg
// 0x00000008 - crippled right leg
// 0x00000010 - crippled left arm
// 0x00000020 - crippled right arm
// 0x00000040 - blinded
// 0x00000080 - death
// 0x00000400 !- on fire - triggers the 'flamedance' animation, but not if the hit is fatal (probably a bug)
// 0x00000800 - bypass armor
// 0x00004000 !- dropped weapon (doesn't work)
// 0x00008000 - lose next turn
// code for dropping weapon, adapted from critical failures
if (FLAG(eff,HF_DROPPED_WEAPON))
{
Item@ realWeaponTarget=_CritGetItemHand(target);
if (valid(realWeaponTarget))
{
if(realWeaponTarget.IsStackable())
{
Item@ dropped = map.AddItem(target.HexX,target.HexY,realWeaponTarget.GetProtoId(),1);
if(realWeaponTarget.GetCount()>1)
realWeaponTarget.SetCount(realWeaponTarget.GetCount()-1);
else
DeleteItem(realWeaponTarget);
if (!target.IsPlayer())
{
NpcPlane@ plane = CreatePlane();
NpcPlane@[] crPlanes(0);
if (target.GetPlanes(crPlanes)>0)
plane.Priority=crPlanes[0].Priority+1;
else
plane.Priority=70;
plane.Type=AI_PLANE_PICK;
plane.Pick_HexX=dropped.HexX;
plane.Pick_HexY=dropped.HexY;
plane.Pick_Pid=dropped.GetProtoId();
plane.Pick_UseItemId=0;
plane.Pick_ToOpen=false;
plane.Run=true;
target.AddPlane(plane);
}
}
else
{
MoveItem(realWeaponTarget,0,map,target.HexX,target.HexY);
if (!target.IsPlayer())
{
NpcPlane@ plane = CreatePlane();
NpcPlane@[] crPlanes(0);
if (target.GetPlanes(crPlanes)>0)
plane.Priority=crPlanes[0].Priority+1;
else
plane.Priority=70;
plane.Type=AI_PLANE_PICK;
plane.Pick_HexX=realWeaponTarget.HexX;
plane.Pick_HexY=realWeaponTarget.HexY;
plane.Pick_Pid=realWeaponTarget.GetProtoId();
plane.Pick_UseItemId=0;
plane.Pick_ToOpen=false;
plane.Run=true;
target.AddPlane(plane);
}
}
}
}
bool isKo=false;
bool knockDown=false;
bool knockOut=false;
bool isDead=false;
bool loseTurn=false;
bool isBelow=false;
int knockDmg=totalDmg;
if(target.Mode[MODE_INVULNERABLE]!=0) eff=0;
// Knock down
if(FLAG(eff,HF_KNOCKDOWN))
{
isKo=true;
knockDown=true;
}
else
if (dmgType==DAMAGE_EXPLODE && totalDmg>9) isKo=true;
// Clipped LLeg
if(FLAG(eff,HF_CRIPPLED_LEFT_LEG)) target.DamageBase[DAMAGE_LEFT_LEG]=1;
// Clipped RLeg
if(FLAG(eff,HF_CRIPPLED_RIGHT_LEG)) target.DamageBase[DAMAGE_RIGHT_LEG]=1;
// Clipped LArm
if(FLAG(eff,HF_CRIPPLED_LEFT_ARM)) target.DamageBase[DAMAGE_LEFT_ARM]=1;
// Clipped RArm
if(FLAG(eff,HF_CRIPPLED_RIGHT_ARM)) target.DamageBase[DAMAGE_RIGHT_ARM]=1;
// Blinded
if(FLAG(eff,HF_BLINDED)) target.DamageBase[DAMAGE_EYE]=1;
// Lose turn
if(FLAG(eff,HF_LOST_NEXT_TURN))
{
isKo=true;
loseTurn=true;
}
// Knock out
if(FLAG(eff,HF_KNOCKOUT))
{
isKo=true;
knockOut=true;
loseTurn=false; // overridden
}
// Instant death
if(FLAG(eff,HF_DEATH))
if(target.Stat[ST_CURRENT_HP]>totalDmg) totalDmg=target.Stat[ST_CURRENT_HP];
if(totalDmg>0)
{
if (target.Mode[MODE_INVULNERABLE]==0)
{
target.StatBase[ST_CURRENT_HP]-=totalDmg;
if(target.Stat[ST_CURRENT_HP]<1)
{
if(target.Stat[ST_CURRENT_HP]>__DeadHitPoints && target.Mode[MODE_NO_KNOCK]==0) { isKo=true; isBelow = true; }
else isDead=true;
}
}
// Score
if(attack.scoreUnarmed && validAttacker && attackerIsPlayer) attacker.AddScore(SCORE_UNARMED_DAMAGE,totalDmg);
}
bool attackFront=true;
uint dir=GetDirection(attack.Hx,attack.Hy,target.HexX,target.HexY);
if ((dir == target.Dir || ((dir+1)%6)==target.Dir || ((dir+5)%6)==target.Dir)) attackFront=false;
int armorDr=target.Stat[ST_NORMAL_RESIST+dmgType-1];
Item@ armor=_CritGetItemArmor(target);
if(valid(armor) && targetIsPlayer) DeteriorateItem(target,armor,((100-armorDr)*rounds+totalDmg)/3);
// Dead
if(isDead)
{
bool burst=attack.IsBurst;
uint anim2Dead=0;
if(target.IsKnockout())
{
if(target.Anim2Knockout==ANIM2_IDLE_PRONE_FRONT)
anim2Dead=ANIM2_DEAD_PRONE_FRONT;
else
anim2Dead=ANIM2_DEAD_PRONE_BACK;
}
else
{
if(attackFront)
anim2Dead=ANIM2_DEAD_FRONT;
else
anim2Dead=ANIM2_DEAD_BACK;
switch(dmgType)
{
case DAMAGE_UNCALLED:
break;
case DAMAGE_NORMAL:
if(burst)
{
if(isCritical || isBloodyMess) anim2Dead=ANIM2_DEAD_BLOODY_BURST;
else anim2Dead=ANIM2_DEAD_BURST;
}
else
{
if(isCritical || isBloodyMess) anim2Dead=ANIM2_DEAD_BLOODY_SINGLE;
}
break;
case DAMAGE_LASER:
anim2Dead=ANIM2_DEAD_LASER;
break;
case DAMAGE_FIRE:
if(Random(0,1)==0) anim2Dead=ANIM2_DEAD_BURN;
else anim2Dead=ANIM2_DEAD_BURN_RUN;
break;
case DAMAGE_PLASMA:
anim2Dead=ANIM2_DEAD_FUSED;
break;
case DAMAGE_ELECTR:
if(Random(0,1)==0) anim2Dead=ANIM2_DEAD_PULSE;
else anim2Dead=ANIM2_DEAD_PULSE_DUST;
break;
case DAMAGE_EXPLODE:
anim2Dead=ANIM2_DEAD_EXPLODE;
break;
default:
break;
}
}
if(targetIsPlayer) target.AddScore(SCORE_ZOMBY,1);
if(validAttacker && attackerIsPlayer)
{
attacker.AddScore(SCORE_KILLER,1);
if(targetIsPlayer) attacker.AddScore(SCORE_MANIAC,1);
else if (target.Id == attack.TargetId) target.AddEnemyInStack(attacker.Id);
}
if(validAttacker && !attackerIsPlayer) attacker.EraseEnemyFromStack(target.Id);
if(valid(attacker))
{
attacker.StatBase[ST_EXPERIENCE]+=(target.Stat[ST_KILL_EXPERIENCE]*2);
attacker.KillBase[KILL_BEGIN+bt]++;
}
target.ToDead(anim2Dead,attacker);
}
else if (isKo)
{
int maxAp=target.Stat[ST_ACTION_POINTS];
int currentAp=target.Stat[ST_CURRENT_AP];
int targetAp=currentAp;
if (loseTurn) { targetAp=-maxAp; target.StatBase[ST_MOVE_AP]=0; }
if (knockOut) targetAp=-Random(maxAp,3*maxAp);
if (knockDown && !(target.IsKnockout() && map.IsTurnBased())) targetAp-=(target.Perk[PE_QUICK_RECOVERY]!=0?1:3);
if ((isBelow || knockOut || knockDown || dmgType == DAMAGE_EXPLODE) && !target.IsKnockout() && target.Mode[MODE_NO_KNOCK]==0)
{
int knockDist=knockDmg;
if ((dmgType == DAMAGE_EXPLODE || attack.WeaponSubtype == WS_MELEE) && target.IsCanWalk())
{
if (weaponPerk==WEAPON_PERK_KNOCKBACK) knockDist/=5;
else knockDist/=10;
if (target.Perk[PE_STONEWALL]!=0) knockDist/=2;
knockDist--;
if (knockDist>10) knockDist=10;
uint16 knockHx=attack.Hx;
uint16 knockHy=attack.Hy;
if (knockDist>0)
{
map.GetHexCoordWall(target.HexX, target.HexY, knockHx, knockHy, 180.0f, knockDist);
target.ToKnockout(KNOCKOUT_ANIM2_DEFAULT(attackFront),0,knockHx,knockHy);
}
else
target.ToKnockout(KNOCKOUT_ANIM2_DEFAULT(attackFront),0,target.HexX,target.HexY);
}
else
target.ToKnockout(KNOCKOUT_ANIM2_DEFAULT(attackFront),0,target.HexX,target.HexY);
}
if (targetAp < currentAp) target.StatBase[ST_CURRENT_AP]=100*targetAp;
}
// Damage
else if(not target.IsKnockout())
{
// Animate hit
if(attack.ShowHitAnimForce)
target.Action(ACTION_DAMAGE_FORCE,attackFront?0:1,null);
else
target.Action(ACTION_DAMAGE,attackFront?0:1,null);
// Npc hit message
if(isCombatText && dmgType!=DAMAGE_UNCALLED && !targetIsPlayer)
{
switch(aim)
{
case HIT_LOCATION_NONE:
case HIT_LOCATION_UNCALLED:
case HIT_LOCATION_TORSO: AI_TrySayCombatText(target,COMBAT_TEXT_HIT_TORSO); break;
case HIT_LOCATION_EYES: AI_TrySayCombatText(target,COMBAT_TEXT_HIT_EYES); break;
case HIT_LOCATION_HEAD: AI_TrySayCombatText(target,COMBAT_TEXT_HIT_HEAD); break;
case HIT_LOCATION_LEFT_ARM: AI_TrySayCombatText(target,COMBAT_TEXT_HIT_LEFT_ARM); break;
case HIT_LOCATION_RIGHT_ARM:AI_TrySayCombatText(target,COMBAT_TEXT_HIT_RIGHT_ARM); break;
case HIT_LOCATION_GROIN: AI_TrySayCombatText(target,COMBAT_TEXT_HIT_GROIN); break;
case HIT_LOCATION_RIGHT_LEG:AI_TrySayCombatText(target,COMBAT_TEXT_HIT_RIGHT_LEG); break;
case HIT_LOCATION_LEFT_LEG: AI_TrySayCombatText(target,COMBAT_TEXT_HIT_LEFT_LEG); break;
default: break;
}
}
}
bool isAimed = (aim!=HIT_LOCATION_NONE) && (aim!=HIT_LOCATION_TORSO) && (aim!=HIT_LOCATION_UNCALLED);
uint mainMsg=CMSG_HIT;
uint length=3;
if (isCritical)
{
mainMsg+=2;
length+=2;
}
if (isDead)
{
mainMsg+=4;
if (isCritical) length-=1;
}
if (isAimed)
{
mainMsg+=1;
length+=1;
}
uint[] sendEff(length);
sendEff[0]=mainMsg;
sendEff[1]=target.Id;
uint now=2;
if (isAimed)
{
sendEff[2]=aim;
now++;
}
sendEff[now]=totalDmg;
now++;
if (isCritical)
{
if (!isDead)
{
sendEff[now]=msgEff;
now++;
}
sendEff[now]=extraMessage;
}
Critter@[] source={target};
Critter@[] crits;
if (@target.GetMap() != null)
{
target.GetMap().GetCrittersSeeing(source, true, FIND_ALL|FIND_ONLY_PLAYERS, crits);
for (int i=0,j=crits.length(); i<j; i++)
if (crits[i].Id != target.Id) AddEff(crits[i],sendEff,results);
}
if (isCritical) sendEff[now]=extraMessage + 1500; // men_player - men_npc
AddEff(target,sendEff,results);
// poisoning and irradiating:
if (validAttacker)
{
int poison=attacker.Stat[ST_TOXIC];
int radiation=attacker.Stat[ST_RADIOACTIVE];
if(poison!=0 || radiation!=0)
{
if(Random(2,11)>target.Stat[ST_LUCK])
{
if(poison>0) AffectPoison(target,Random(poison/3,poison));
if(radiation>0) AffectRadiation(target,Random(radiation/3,radiation));
}
}
else
{
int attackerBt=attacker.Stat[ST_BODY_TYPE];
if(attackerBt==BT_RADSCORPION || attackerBt==BT_FLOATER || attackerBt==BT_GIANT_WASP || attackerBt==BT_ALIEN || attackerBt==BT_CENTAUR)
{
if(Random(2,11)>target.Stat[ST_LUCK])
{
if(attackerBt==BT_RADSCORPION) AffectPoison(target,Random(3,15));
else if(attackerBt==BT_FLOATER) AffectPoison(target,Random(10,40));
else if(attackerBt==BT_GIANT_WASP) AffectPoison(target,Random(10,40));
else if(attackerBt==BT_ALIEN) AffectRadiation(target,Random(10,40));
else if(attackerBt==BT_CENTAUR)
{
AffectPoison(target,Random(10,20));
AffectRadiation(target,Random(10,20));
}
}
}
}
}
}
void CommenceExplosion(uint16 ammoPid, AttackStruct &attack, Map@ map, uint16 tx, uint16 ty, Critter@ target, uint weapPid, bool isCritical, uint intentionallyId, bool isRocket, CombatRes[]& results)
{
uint radius=isRocket?3:2;
if(weapPid==PID_MOLOTOV_COCKTAIL) map.RunEffect(PID_EXPLODE_FIRE_BIG,tx,ty,2);
else if (weapPid==PID_FRAG_GRENADE || weapPid==PID_HOLY_HAND_GRENADE) map.RunEffect(PID_EXPLODE_FIRE_SMALL,tx,ty,2);
else if (weapPid==PID_PULSE_GRENADE) map.RunEffect(PID_EXPLODE_EMP,tx,ty,2);
else if (weapPid==PID_PLASMA_GRENADE) map.RunEffect(PID_EXPLODE_PLASMA,tx,ty,2);
else if (weapPid==PID_SMOKE_GRENADE) SmokeBlast(map, tx, ty, PID_SMOKE, (valid(attack.Attacker)?attack.Attacker.Id : 0));
else if (weapPid==PID_MUSTARD_GAS_GRENADE) SmokeBlast(map, tx, ty, PID_MUSTARD_GAS, (valid(attack.Attacker)?attack.Attacker.Id : 0));
else if (ammoPid==PID_PULSE_ROKET) map.RunEffect(PID_EXPLODE_EMP,tx,ty,2);
else if (isRocket) map.RunEffect(PID_EXPLODE_ROCKET,tx,ty,1);
else map.RunEffect(PID_EXPLODE_FIRE_BIG,tx,ty,2); // default case
if(weapPid==PID_MUSTARD_GAS_GRENADE || weapPid==PID_SMOKE_GRENADE)
return; //
QuakeScreen(map);
Critter@[] critsHit;
map.GetCrittersHex(tx, ty, radius, FIND_LIFE_AND_KO, critsHit);
bool validTarget = valid(target);
uint targetId=0;
bool intentionally=(valid(attack.Attacker) && !attack.Attacker.IsNpc() || targetId==intentionallyId);
if (validTarget)
{
targetId=target.Id;
ApplyDamage(attack, target, 1, isCritical, intentionally, results);
}
attack.Hx=tx;
attack.Hy=ty;
for (int i=0,j=critsHit.length();i<j;i++)
if (!validTarget || targetId != critsHit[i].Id) ApplyDamage(attack, critsHit[i], 1, false, intentionally, results);
}
void CriticalFailure(Critter &cr, ProtoItem& weapon, uint8 weaponUse, ProtoItem@ ammo, uint eff, CombatRes[]& results)
{
// 0x00000100 hit self OK
// 0x00100000 weapon exploded OK
// 0x00000200 lost rest of ammo OK
// 0x00000400 fired dud shot OK?
// 0x00000800 hurt self OK
// 0x00001000 hit randomly ~OK, TODO
// 0x00002000 crippled random limb OK
// 0x00040000 on fire NOT USED NOW, TODO?
// 0x00200000 weapon destroyed OK
// 0x00400000 weapon dropped OK
// 0x00800000 lost next turn OK
// 0x02000000 knocked down OK
int totalDmg=0;
bool tookDamage=false;
Item@ realWeapon=_CritGetItemHand(cr);
bool wpnIsRemoved=_WeaponRemove(weapon,weaponUse);
Map@ map=cr.GetMap();
if (cr.Mode[MODE_NO_DROP]!=0) UNSETFLAG(eff,MF_WEAPON_DROPPED);
if (FLAG(eff,MF_HIT_SELF) || FLAG(eff,MF_WEAPON_EXPLODED)) // only one bullet because burst weapons do not have the ability
{
bool validAmmo=valid(ammo);
int dmgType=_WeaponDmgType(weapon,weaponUse);
int targetDR=cr.Stat[ST_NORMAL_RESIST+dmgType-1];
int targetDT=cr.Stat[ST_NORMAL_ABSORB+dmgType-1];
if (weapon.Weapon_Perk == WEAPON_PERK_PENETRATE) targetDT/=5;
targetDR+=(cr.Trait[TRAIT_FINESSE]!=0?30:0);
targetDR+=validAmmo?ammo.Ammo_DRMod:0;
targetDR=CLAMP(targetDR,0,100);
int dmgMin=_WeaponDmgMin(weapon,weaponUse);
int dmgMax=_WeaponDmgMax(weapon,weaponUse);
int dmgMul=validAmmo?ammo.Ammo_DmgMult:1; // technically, should be *2
int dmgDiv=validAmmo?ammo.Ammo_DmgDiv:1; // technically, should be *2
totalDmg=Random(dmgMin,dmgMax) + 2*cr.Perk[PE_BONUS_RANGED_DAMAGE];
totalDmg*=dmgMul;
totalDmg/=dmgDiv;
totalDmg-=targetDT;
totalDmg-=(totalDmg*targetDR)/100;
if (totalDmg < 0) totalDmg=0;
int bt=cr.Stat[ST_BODY_TYPE];
if (cr.Perk[PE_LIVING_ANATOMY]!=0 && !(bt==BT_ALIEN || bt==BT_ROBOT)) totalDmg+=5;
if (cr.Perk[PE_PYROMANIAC]!=0 && dmgType == DAMAGE_FIRE) totalDmg+=5;
tookDamage=true;
}
uint16 ammoRound=_WeaponRound(weapon,weaponUse);
if(ammoRound>0 && valid(realWeapon) && cr.Mode[MODE_UNLIMITED_AMMO]==0)
{
if(realWeapon.WeaponAmmoCount<=ammoRound || FLAG(eff,MF_LOST_REST_OF_AMMO)) realWeapon.WeaponAmmoCount=0;
else realWeapon.WeaponAmmoCount-=ammoRound;
realWeapon.Update();
}
if (cr.Mode[MODE_NO_DROP]!=0) UNSETFLAG(eff,MF_WEAPON_DROPPED);
if (FLAG(eff,MF_WEAPON_DROPPED) && valid(realWeapon))
{
if(realWeapon.IsStackable())
{
Item@ dropped = map.AddItem(cr.HexX,cr.HexY,weapon.ProtoId,1);
if(realWeapon.GetCount()>1)
realWeapon.SetCount(realWeapon.GetCount()-1);
else
DeleteItem(realWeapon);
if (!cr.IsPlayer())
{
NpcPlane@ plane = CreatePlane();
NpcPlane@[] crPlanes(0);
if (cr.GetPlanes(crPlanes)>0)
plane.Priority=crPlanes[0].Priority+1;
else
plane.Priority=70;
plane.Type=AI_PLANE_PICK;
plane.Pick_HexX=dropped.HexX;
plane.Pick_HexY=dropped.HexY;
plane.Pick_Pid=dropped.GetProtoId();
plane.Pick_UseItemId=0;
plane.Pick_ToOpen=false;
plane.Run=true;
cr.AddPlane(plane);
}
}
else
{
MoveItem(realWeapon,0,map,cr.HexX,cr.HexY);
if (!cr.IsPlayer())
{
NpcPlane@ plane = CreatePlane();
NpcPlane@[] crPlanes(0);
if (cr.GetPlanes(crPlanes)>0)
plane.Priority=crPlanes[0].Priority+1;
else
plane.Priority=70;
plane.Type=AI_PLANE_PICK;
plane.Pick_HexX=realWeapon.HexX;
plane.Pick_HexY=realWeapon.HexY;
plane.Pick_Pid=realWeapon.GetProtoId();
plane.Pick_UseItemId=0;
plane.Pick_ToOpen=false;
plane.Run=true;
cr.AddPlane(plane);
}
}
}
if ((FLAG(eff,MF_WEAPON_DESTROYED) || FLAG(eff,MF_WEAPON_EXPLODED)) && valid(realWeapon))
{
if(realWeapon.IsStackable())
{
if(realWeapon.GetCount()>1) realWeapon.SetCount(realWeapon.GetCount()-1);
else
DeleteItem(realWeapon);
}
else
DeleteItem(realWeapon);
}
if (FLAG(eff,MF_HURT_SELF)) { totalDmg+=Random(1,5); tookDamage=true; }
if (FLAG(eff,MF_LOST_NEXT_TURN)) { cr.StatBase[ST_CURRENT_AP]=-100*cr.Stat[ST_ACTION_POINTS]; cr.StatBase[ST_MOVE_AP]=0;}
if (FLAG(eff,MF_KNOCKED_DOWN) && !cr.IsKnockout()) cr.ToKnockout(KNOCKOUT_ANIM2_DEFAULT(true),cr.Perk[PE_QUICK_RECOVERY]!=0?1:3,cr.HexX,cr.HexY);
if (FLAG(eff, MF_CRIPPLED_RANDOM_LIMB) && cr.Mode[MODE_NO_LOOSE_LIMBS]==0)
{
switch(Random(1,4))
{
case 1: cr.DamageBase[DAMAGE_LEFT_LEG]=1; break;
case 2: cr.DamageBase[DAMAGE_RIGHT_LEG]=1; break;
case 3: cr.DamageBase[DAMAGE_LEFT_ARM]=1; break;
case 4: cr.DamageBase[DAMAGE_RIGHT_ARM]=1; break;
default: break;
}
}
if (FLAG(eff,MF_WEAPON_EXPLODED))
map.RunEffect(PID_EXPLODE_FIRE_SMALL,cr.HexX,cr.HexY,2);
if (totalDmg>0) cr.StatBase[ST_CURRENT_HP]-=totalDmg;
uint[] allEff={CMSG_CRIT_MISS,cr.Id,eff};
if (tookDamage)
{
allEff.resize(4);
allEff[3]=totalDmg;
allEff[0]=CMSG_CRIT_MISS_DAMAGE;
}
if(cr.Stat[ST_CURRENT_HP]<1)
{
if(cr.Stat[ST_CURRENT_HP]>__DeadHitPoints && cr.Mode[MODE_NO_KNOCK]==0) { if (!cr.IsKnockout()) cr.ToKnockout(KNOCKOUT_ANIM2_DEFAULT(true),0,cr.HexX,cr.HexY); }
else
{
uint anim2Dead=ANIM2_DEAD_FRONT;
if(cr.IsKnockout())
{
if(cr.Anim2Knockout==ANIM2_IDLE_PRONE_FRONT)
anim2Dead=ANIM2_DEAD_PRONE_FRONT;
else
anim2Dead=ANIM2_DEAD_PRONE_BACK;
}
cr.ToDead(anim2Dead,null);
allEff[2]|=MF_WAS_KILLED;
}
}
Critter@[] him={cr};
Critter@[] crits;
cr.GetMap().GetCrittersSeeing(him, true, FIND_ALL|FIND_ONLY_PLAYERS, crits);
for (int i=0, j=crits.length(); i<j; i++)
AddEff(crits[i],allEff,results);
FlushResults(results);
return;
}
void InjureCritter(Critter& cr, uint dmg, uint dmgType, uint8 dir, uint attackerId) // Export
{
if(dmgType>DAMAGE_EXPLODE)
{
Log("Invalid damage type.");
return;
}
Critter@ attacker=null;
if(attackerId!=0 && cr.Id!=attackerId) @attacker=GetCritter(attackerId);
AttackStruct attack;
@attack.Attacker=attacker;
uint16 hx=cr.HexX;
uint16 hy=cr.HexY;
Map@ map=cr.GetMap();
if(valid(map)) map.MoveHexByDir(hx,hy,(dir+3)%6,1);
attack.Hx=hx;
attack.Hy=hy;
attack.Aim=HIT_LOCATION_UNCALLED;
attack.IsBurst=false;
attack.BloodyMess=false;
attack.CombatMessage=false;
attack.WeaponPerk=-1;
attack.WeaponSubtype=0;
attack.DmgMin=dmg;
attack.DmgMax=dmg;
attack.DmgType=dmgType;
attack.BonusDmg=0;
attack.DmgMul=2;
attack.DRMod=0;
attack.DMMod=1;
attack.DDMod=1;
attack.TargetId=cr.Id;
attack.ShowHitAnimForce=true;
CombatRes[] results;
ApplyDamage(attack, cr, 1, false, true, results);
FlushResults(results);
}
void InjureCritter(Critter& cr, uint dmg, uint dmgType, uint8 dir, uint attackerId, uint forceFlags) // Export
{
if(dmgType>DAMAGE_EXPLODE)
{
Log("Invalid damage type.");
return;
}
Critter@ attacker=null;
if(attackerId!=0 && cr.Id!=attackerId) @attacker=GetCritter(attackerId);
AttackStruct attack;
@attack.Attacker=attacker;
uint16 hx=cr.HexX;
uint16 hy=cr.HexY;
Map@ map=cr.GetMap();
if(valid(map)) map.MoveHexByDir(hx,hy,(dir+3)%6,1);
attack.Hx=hx;
attack.Hy=hy;
attack.Aim=HIT_LOCATION_UNCALLED;
attack.IsBurst=false;
attack.BloodyMess=false;
attack.CombatMessage=false;
attack.WeaponPerk=-1;
attack.WeaponSubtype=0;
attack.DmgMin=dmg;
attack.DmgMax=dmg;
attack.DmgType=dmgType;
attack.BonusDmg=0;
attack.DmgMul=2;
attack.DRMod=0;
attack.DMMod=1;
attack.DDMod=1;
attack.TargetId=cr.Id;
attack.ForceFlags=forceFlags;
attack.ShowHitAnimForce=true;
CombatRes[] results;
ApplyDamage(attack, cr, 1, false, true, results);
FlushResults(results);
}
int FindCritterInArray(Critter@[]& crits, Critter& cr)
{
uint crId=cr.Id;
for(uint i=0,j=crits.length();i<j;i++)
if(crits[i].Id==crId) return i;
return -1;
}
uint GetAimApCost(int hitLocation)
{
switch(hitLocation)
{
case HIT_LOCATION_NONE: break;
case HIT_LOCATION_UNCALLED: break;
case HIT_LOCATION_TORSO: return __ApCostAimTorso;
case HIT_LOCATION_EYES: return __ApCostAimEyes;
case HIT_LOCATION_HEAD: return __ApCostAimHead;
case HIT_LOCATION_LEFT_ARM:
case HIT_LOCATION_RIGHT_ARM: return __ApCostAimArms;
case HIT_LOCATION_GROIN: return __ApCostAimGroin;
case HIT_LOCATION_RIGHT_LEG:
case HIT_LOCATION_LEFT_LEG: return __ApCostAimLegs;
default: break;
}
return 0;
}
uint GetHitAim(int hitLocation)
{
switch(hitLocation)
{
case HIT_LOCATION_NONE: break;
case HIT_LOCATION_UNCALLED: break;
case HIT_LOCATION_TORSO: return __HitAimTorso;
case HIT_LOCATION_EYES: return __HitAimEyes;
case HIT_LOCATION_HEAD: return __HitAimHead;
case HIT_LOCATION_LEFT_ARM:
case HIT_LOCATION_RIGHT_ARM: return __HitAimArms;
case HIT_LOCATION_GROIN: return __HitAimGroin;
case HIT_LOCATION_RIGHT_LEG:
case HIT_LOCATION_LEFT_LEG: return __HitAimLegs;
default: break;
}
return 0;
}
// client only, possible use by AI
int RawToHit(Critter &cr, Critter &target, ProtoItem@ weapon, uint8 weaponUse, ProtoItem@ ammo)
{
int skillNum = valid(weapon) ? _WeaponSkill(weapon,weaponUse) : SK_UNARMED;
int toHit=cr.Skill[skillNum];
int weaponPerk = valid(weapon) ? weapon.Weapon_Perk : 0;
int blockers=0;
int dist=GetDistantion(cr.HexX,cr.HexY,target.HexX,target.HexY);
if (skillNum!=SK_UNARMED && skillNum!=SK_MELEE_WEAPONS)
{
// ranged attack modifiers
int distmod1=2; // used for initial weapon bonus
int distmod2=0; // minimal distance
if (weaponPerk==WEAPON_PERK_LONG_RANGE) distmod1=4;
else if (weaponPerk==WEAPON_PERK_SCOPE_RANGE) {
distmod1=5;
distmod2=8;
}
int perception=cr.Stat[ST_PERCEPTION];
int acc=dist;
if (dist < distmod2) acc+=distmod2;
else {
if (cr.IsPlayer()) acc-=(perception-2)*distmod1;
else acc-=perception*distmod1;
}
if (-2*perception > acc) acc = -2*perception;
acc-=2*cr.Perk[PE_SHARPSHOOTER];
if (acc >= 0)
{
if (cr.Damage[DAMAGE_EYE]!=0) acc*=-12;
else acc*=-4;
}
else acc*=-4;
toHit+=acc;
Map@ map=cr.GetMap();
blockers=map.GetCrittersPath(cr.HexX, cr.HexY, target.HexX, target.HexY, 0.0f, dist, FIND_LIFE, null);
if (!target.IsKnockout()) blockers--;
toHit-=10*blockers;
} // end range modifiers
if (valid(weapon))
{
if (!(weapon.Weapon_IsUnarmed) && cr.Trait[TRAIT_ONE_HANDER]!=0)
toHit+=(FLAG(weapon.Flags,ITEM_TWO_HANDS)?-40:20);
int handlingStrength=cr.Stat[ST_STRENGTH];
int reqStrength=weapon.Weapon_MinStrength;
if (cr.Perk[PE_WEAPON_HANDLING]!=0) handlingStrength+=3;
if (handlingStrength<reqStrength) toHit-=(reqStrength-handlingStrength)*20;
if (weaponPerk == WEAPON_PERK_ACCURATE) toHit+=20;
}
int acmod=target.Stat[ST_ARMOR_CLASS];
if (valid(ammo)) acmod += ammo.Ammo_ACMod;
if (acmod>0) toHit-=acmod;
if (cr.Damage[DAMAGE_EYE]!=0) toHit-=25;
if (target.IsKnockout()) toHit+=40;
if (target.GetMultihex()>0) toHit+=15;
if (skillNum!=SK_UNARMED && skillNum!=SK_MELEE_WEAPONS)
toHit-=GetSmokePenalty(cr.GetMap(), cr, target);
return toHit;
}
void NotifyOops(Critter@ cr, Critter@ t1, Critter@ t2, CombatRes[]& results)
{
// if (t2==null) pass "cr critically missed and hit randomly."
// otherwise pass "oops! t2 was hit instead of t1!"
if (@t2==null)
{
uint[] allEff={CMSG_HIT_RANDOMLY, cr.Id};
Critter@[] him={cr};
Critter@[] crits;
cr.GetMap().GetCrittersSeeing(him, true, FIND_ALL|FIND_ONLY_PLAYERS, crits);
for (int i=0, j=crits.length(); i<j; i++)
AddEff(crits[i],allEff,results);
}
else
{
uint[] allEff={CMSG_OOPS, t1.Id, t2.Id};
Critter@[] them={t1,t2};
Critter@[] crits;
t1.GetMap().GetCrittersSeeing(them, true, FIND_ALL|FIND_ONLY_PLAYERS, crits);
for (int i=0, j=crits.length(); i<j; i++)
AddEff(crits[i],allEff,results);
}
return;
}
void NotifyMiss(Critter@ cr, CombatRes[]& results)
{
uint[] allEff={CMSG_MISS, cr.Id};
Critter@[] him={cr};
Critter@[] crits;
cr.GetMap().GetCrittersSeeing(him, true, FIND_ALL|FIND_ONLY_PLAYERS, crits);
for (int i=0, j=crits.length(); i<j; i++)
AddEff(crits[i],allEff,results);
return;
}
Critter@ ChooseRandomTarget(Map& map, Critter& cr, Critter& target, uint wpnMaxDist)
{
Critter@[] crits;
uint16 hx=cr.HexX;
uint16 hy=cr.HexY;
uint n = map.GetCrittersHex(hx, hy, wpnMaxDist, FIND_LIFE_AND_KO, crits);
if (n==0) return null; // should never happen
uint start=Random(0,n-1);
uint16 bx=0;
uint16 by=0;
uint16 pbx=0;
uint16 pby=0;
for (uint i=start;i<n;i++)
{
if (!valid(crits[i])) continue;
if (crits[i].Id == cr.Id || (valid(target) && crits[i].Id == target.Id)) continue;
// wallcheck:
map.GetCrittersPath(hx, hy, crits[i].HexX, crits[i].HexY, 0.0f, 0, FIND_LIFE_AND_KO, null, pbx, pby, bx, by);
if (bx==crits[i].HexX && by==crits[i].HexY) return crits[i];
}
for (uint i=0;i<start;i++)
{
if (!valid(crits[i])) continue;
if (crits[i].Id == cr.Id || (valid(target) && crits[i].Id == target.Id)) continue;
// wallcheck:
map.GetCrittersPath(hx, hy, crits[i].HexX, crits[i].HexY, 0.0f, 0, FIND_LIFE_AND_KO, null, pbx, pby, bx, by);
if (bx==crits[i].HexX && by==crits[i].HexY) return crits[i];
}
return null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment