Skip to content

Commit

Permalink
Rework damage calculation (#434)
Browse files Browse the repository at this point in the history
* rework damage calculation

* cleanup and fix for no active weapon
  • Loading branch information
thokkat committed Apr 2, 2023
1 parent c07f3ca commit b1f1b83
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 41 deletions.
6 changes: 4 additions & 2 deletions game/game/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ enum {
};

enum {
MaxBowRange = 3500, // from Focus_Ranged
MaxMagRange = 3500,
ReferenceBowRangeG1 = 2000,
ReferenceBowRangeG2 = 1500,
MaxBowRange = 4500,
MaxMagRange = 3500, // from Focus_Ranged
};

enum BodyState:uint32_t {
Expand Down
71 changes: 48 additions & 23 deletions game/game/damagecalculator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ DamageCalculator::Val DamageCalculator::damageValue(Npc& src, Npc& other, const
ret = swordDamage(src,other);
}

if(ret.hasHit && !ret.invinsible)
if(ret.hasHit && !ret.invincible && Gothic::inst().version().game==2)
ret.value = std::max<int32_t>(ret.value,MinDamage);
if(other.isImmortal() || (other.isPlayer() && Gothic::inst().isGodMode()))
ret.value = 0;
Expand All @@ -40,9 +40,9 @@ DamageCalculator::Val DamageCalculator::damageFall(Npc& npc, float speed) {
int32_t prot = npc.protection(::PROT_FALL);

Val ret;
ret.invinsible = (prot<0 || npc.isImmortal() || (npc.isPlayer() && Gothic::inst().isGodMode()));
ret.invincible = (prot<0 || npc.isImmortal() || (npc.isPlayer() && Gothic::inst().isGodMode()));
ret.value = int32_t(dmgPerMeter*(height-h0)/100.f - float(prot));
if(ret.value<=0 || ret.invinsible) {
if(ret.value<=0 || ret.invincible) {
ret.value = 0;
return ret;
}
Expand All @@ -51,17 +51,42 @@ DamageCalculator::Val DamageCalculator::damageFall(Npc& npc, float speed) {
}

DamageCalculator::Val DamageCalculator::rangeDamage(Npc& nsrc, Npc& nother, const Bullet& b, const CollideMask bMsk) {
bool invinsible = !checkDamageMask(nsrc,nother,&b);
if(b.pathLength() > float(MaxBowRange) * b.hitChance() && b.hitChance()<1.f)
return Val(0,false,invinsible);
float dist = b.pathLength();
bool noHit = dist>float(MaxMagRange);
bool invincible = !checkDamageMask(nsrc,nother,&b);
auto dmg = b.damage();

if(!b.isSpell()) {
auto& script = nsrc.world().script();
float hitChance = float(script.rand(100))/100.f;
float hitCh = 0;
bool g2 = Gothic::inst().version().game==2;
float refRange = g2 ? ReferenceBowRangeG2 : ReferenceBowRangeG1;
float skill = b.hitChanceVal();

if(dist<refRange)
hitCh = (skill - 1.f) / refRange * dist + 1.f; else
hitCh = skill / (refRange - float(MaxBowRange)) * (dist - float(MaxBowRange));

noHit = (dist>float(MaxBowRange) || hitCh<=hitChance);

if(!g2 && !noHit && !invincible) {
int critChance = int(script.rand(100));
if(std::lround(100.f * b.critChance())>critChance)
dmg*=2;
}
}

if(noHit)
return Val(0,false,invincible);

if(invinsible)
if(invincible)
return Val(0,true,true);

if((bMsk & (COLL_APPLYDAMAGE | COLL_APPLYDOUBLEDAMAGE | COLL_APPLYHALVEDAMAGE | COLL_DOEVERYTHING))==0)
return Val(0,true,true);

return rangeDamage(nsrc,nother,b.damage(),bMsk);
return rangeDamage(nsrc,nother,dmg,bMsk);
}

DamageCalculator::Val DamageCalculator::rangeDamage(Npc&, Npc& nother, Damage dmg, const CollideMask bMsk) {
Expand All @@ -72,7 +97,7 @@ DamageCalculator::Val DamageCalculator::rangeDamage(Npc&, Npc& nother, Damage dm
if(bMsk & COLL_APPLYHALVEDAMAGE)
dmg/=2;

int value = 0;
int value = 0;
for(unsigned int i=0; i<phoenix::damage_type::count; ++i) {
if(dmg[size_t(i)]==0)
continue;
Expand All @@ -88,35 +113,35 @@ DamageCalculator::Val DamageCalculator::swordDamage(Npc& nsrc, Npc& nother) {
if(!checkDamageMask(nsrc,nother,nullptr))
return Val(0,true,true);

auto& script = nsrc.world().script();
auto& script = nsrc.world().script();
auto& src = nsrc.handle();
auto& other = nother.handle();

// Swords/Fists
const int dtype = damageTypeMask(nsrc);
uint8_t hitCh = TALENT_UNKNOWN;
Talent tal = TALENT_UNKNOWN;
int str = nsrc.attribute(Attribute::ATR_STRENGTH);
int critChance = int(script.rand(100));

int value=0;
int value = 0;

if(auto w = nsrc.inventory().activeWeapon()){
if(auto w = nsrc.inventory().activeWeapon()) {
if(w->is2H())
hitCh = TALENT_2H; else
hitCh = TALENT_1H;
tal = TALENT_2H; else
tal = TALENT_1H;
}

if(Gothic::inst().version().game==2) {
if(nsrc.isMonster() && hitCh==TALENT_UNKNOWN) {
if(nsrc.isMonster() && tal==TALENT_UNKNOWN) {
// regular monsters always do critical damage
critChance = 0;
}

for(unsigned int i=0; i<phoenix::damage_type::count; ++i){
for(unsigned int i=0; i<phoenix::damage_type::count; ++i) {
if((dtype & (1<<i))==0)
continue;
int vd = std::max(str + src.damage[i] - other.protection[i],0);
if(src.hitchance[hitCh]<critChance)
if(src.hitchance[tal]<=critChance)
vd = (vd-1)/10;
if(other.protection[i]>=0) // Filter immune
value += vd;
Expand All @@ -127,10 +152,10 @@ DamageCalculator::Val DamageCalculator::swordDamage(Npc& nsrc, Npc& nother) {
for(unsigned int i=0; i<phoenix::damage_type::count; ++i) {
if((dtype & (1<<i))==0)
continue;
int vd = std::max(str + src.damage[i] - other.protection[i],0);
if(src.hitchance[hitCh]<critChance)
vd = std::max(str + src.damage[i] - other.protection[i],0); else
vd = std::max(str + src.damage[i]*2 - other.protection[i],0);
int vd = 0;
if(nsrc.talentValue(tal)<=critChance)
vd = std::max(str + src.damage[i] - other.protection[i],0); else
vd = std::max(str + 2*src.damage[i] - other.protection[i],0);
if(other.protection[i]>=0) // Filter immune
value += vd;
}
Expand Down Expand Up @@ -168,7 +193,7 @@ bool DamageCalculator::checkDamageMask(Npc& nsrc, Npc& nother, const Bullet* b)

DamageCalculator::Damage DamageCalculator::rangeDamageValue(Npc& src) {
const int dtype = damageTypeMask(src);
int d = src.attribute(Attribute::ATR_DEXTERITY);
int d = Gothic::inst().version().game==2 ? src.attribute(Attribute::ATR_DEXTERITY) : 0;
Damage ret={};
for(unsigned int i=0;i<phoenix::damage_type::count;++i){
if((dtype & (1<<i))==0)
Expand Down
4 changes: 2 additions & 2 deletions game/game/damagecalculator.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ class DamageCalculator {
struct Val final {
Val()=default;
Val(int32_t v,bool b):value(v),hasHit(b){}
Val(int32_t v,bool b,bool i):value(v),hasHit(b),invinsible(i){}
Val(int32_t v,bool b,bool i):value(v),hasHit(b),invincible(i){}

int32_t value = 0;
bool hasHit = false;
bool invinsible = false;
bool invincible = false;
};

struct Damage final {
Expand Down
4 changes: 2 additions & 2 deletions game/game/fightalgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ float FightAlgo::weaponRange(GameScript &owner, const Npc &npc) {
auto& gv = owner.guildVal();
auto w = npc.inventory().activeWeapon();
int add = w ? w->swordLength() : 0;
auto bR = Gothic::inst().version().game==2 ? ReferenceBowRangeG2 : ReferenceBowRangeG1;

switch(npc.weaponState()) {
case WeaponState::W1H:
Expand All @@ -298,9 +299,8 @@ float FightAlgo::weaponRange(GameScript &owner, const Npc &npc) {
case WeaponState::Fist:
return float(gv.fight_range_fist[gl]);
case WeaponState::Bow:
return float((MaxBowRange*npc.hitChanse(TALENT_BOW))/100);
case WeaponState::CBow:
return float((MaxBowRange*npc.hitChanse(TALENT_CROSSBOW))/100);
return float(bR);
case WeaponState::Mage:
return float(MaxMagRange);
}
Expand Down
2 changes: 1 addition & 1 deletion game/ui/gamemenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,7 @@ void GameMenu::setPlayer(const Npc &pl) {
continue;

const int sk = pl.talentSkill(Talent(i));
const int val = g2 ? pl.hitChanse(Talent(i)) : pl.talentValue(Talent(i));
const int val = g2 ? pl.hitChance(Talent(i)) : pl.talentValue(Talent(i));

set(string_frm("MENU_ITEM_TALENT_",i,"_TITLE"), str);
set(string_frm("MENU_ITEM_TALENT_",i,"_SKILL"), strEnum(talV->get_string(size_t(i)),sk,textBuf));
Expand Down
11 changes: 7 additions & 4 deletions game/world/bullet.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ class Bullet final : public DynamicWorld::BulletCallback {
auto damage() const -> const DamageCalculator::Damage& { return dmg; }
void setDamage(DamageCalculator::Damage d) { dmg=d; }

float hitChance() const { return hitCh; }
void setHitChance(float h) { hitCh=h; }
float critChance() const { return critCh; }
void setCritChance(float v) { critCh=v; }
float hitChanceVal() const { return hitCh; }
void setHitChanceVal(float v) { hitCh=v; }
bool isFinished() const;

float pathLength() const;
Expand All @@ -67,8 +69,9 @@ class Bullet final : public DynamicWorld::BulletCallback {
World* wrld=nullptr;
Npc* ow=nullptr;

DamageCalculator::Damage dmg = {};
float hitCh = 1.f;
DamageCalculator::Damage dmg = {};
float hitCh = 1.f;
float critCh = 0.f;

MeshObjects::Mesh view;
Effect vfx;
Expand Down
18 changes: 12 additions & 6 deletions game/world/objects/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1116,7 +1116,7 @@ int32_t Npc::talentValue(Talent t) const {
return 0;
}

int32_t Npc::hitChanse(Talent t) const {
int32_t Npc::hitChance(Talent t) const {
if(t<=phoenix::c_npc::hitchance_count)
return hnpc->hitchance[t];
return 0;
Expand Down Expand Up @@ -2686,7 +2686,6 @@ void Npc::commitSpell() {

auto& b = owner.shootSpell(*active, *this, currentTarget);
b.setDamage(dmg);
b.setHitChance(1.f);
b.setOrigin(this);
b.setTarget(nullptr);
visual.setMagicWeaponKey(owner,SpellFxKey::Init);
Expand Down Expand Up @@ -3505,10 +3504,17 @@ bool Npc::shootBow(Interactive* focOverride) {
b.setDamage(DamageCalculator::rangeDamageValue(*this));

auto rgn = currentRangeWeapon();
if(rgn!=nullptr && rgn->isCrossbow())
b.setHitChance(float(hnpc->hitchance[TALENT_CROSSBOW])/100.f); else
b.setHitChance(float(hnpc->hitchance[TALENT_BOW] )/100.f);

if(Gothic::inst().version().game==1) {
b.setHitChanceVal(float(hnpc->attribute[ATR_DEXTERITY])/100.f);
if(rgn!=nullptr && rgn->isCrossbow())
b.setCritChance(float(talentsVl[TALENT_CROSSBOW])/100.f); else
b.setCritChance(float(talentsVl[TALENT_BOW] )/100.f);
}
else {
if(rgn!=nullptr && rgn->isCrossbow())
b.setHitChanceVal(float(hnpc->hitchance[TALENT_CROSSBOW])/100.f); else
b.setHitChanceVal(float(hnpc->hitchance[TALENT_BOW] )/100.f);
}
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion game/world/objects/npc.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class Npc final {

void setTalentValue(Talent t,int32_t lvl);
int32_t talentValue(Talent t) const;
int32_t hitChanse(Talent t) const;
int32_t hitChance(Talent t) const;

void setRefuseTalk(uint64_t milis);
bool isRefuseTalk() const;
Expand Down

0 comments on commit b1f1b83

Please sign in to comment.