Created
July 16, 2012 17:15
-
-
Save MEXAHOTABOP/3123846 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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