OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
damagecalculator.cpp
Go to the documentation of this file.
1#include "damagecalculator.h"
2
3#include "world/objects/npc.h"
5#include "world/world.h"
6#include "world/bullet.h"
7#include "gothic.h"
8
9// https://forum.worldofplayers.de/forum/threads/127320-Damage-System?p=2198181#post2198181
10// https://strafkolonie-online.net/forum/board/thread/1895-info-erkl%C3%A4rung-der-berechnung-der-trefferchance-im-fernkampf/
11
12static float mix(float x, float y, float a) {
13 return x + (y-x)*a;
14 }
15
16DamageCalculator::Val DamageCalculator::damageValue(Npc& src, Npc& other, const Bullet* b, bool isSpell, const DamageCalculator::Damage& splDmg, const CollideMask bMsk) {
18 if(b!=nullptr) {
19 ret = rangeDamage(src,other,*b,bMsk);
20 } else
21 if(isSpell) {
22 ret = rangeDamage(src,other,splDmg,bMsk);
23 }
24 else {
25 ret = swordDamage(src,other);
26 }
27
28 if(ret.hasHit && !ret.invincible && Gothic::inst().version().game==2)
29 ret.value = std::max<int32_t>(ret.value,MinDamage);
30 return ret;
31 }
32
34 auto gl = npc.guild();
35 auto& g = npc.world().script().guildVal();
36
37 float gravity = DynamicWorld::gravity;
38 float fallTime = speed/gravity;
39 float height = 0.5f*std::abs(gravity)*fallTime*fallTime;
40 float h0 = float(g.falldown_height[gl]);
41 float dmgPerMeter = float(g.falldown_damage[gl]);
42 int32_t prot = npc.protection(::PROT_FALL);
43
44 Val ret;
45 ret.invincible = (prot<0);
46 ret.value = int32_t(dmgPerMeter*(height-h0)/100.f - float(prot));
47 if(ret.value<=0 || ret.invincible) {
48 ret.value = 0;
49 return ret;
50 }
51 ret.hasHit = true;
52 return ret;
53 }
54
55DamageCalculator::Val DamageCalculator::rangeDamage(Npc& nsrc, Npc& nother, const Bullet& b, const CollideMask bMsk) {
56 float dist = b.pathLength();
57 bool noHit = dist>float(MaxMagRange);
58 bool invincible = !checkDamageMask(nsrc,nother,&b);
59 auto dmg = b.damage();
60
61 if(!b.isSpell()) {
62 auto& script = nsrc.world().script();
63 float hitChance = float(script.rand(100))/100.f;
64 float hitCh = 0;
65 bool g2 = Gothic::inst().version().game==2;
66 float refRange = g2 ? ReferenceBowRangeG2 : ReferenceBowRangeG1;
67 float maxRange = float(MaxBowRange);
68 float chance = b.hitChance();
69
70 if(dist<refRange)
71 hitCh = mix(1.f, chance, (dist / refRange));
72 else if(dist<maxRange)
73 hitCh = mix(chance, 0.f, (dist-refRange) / (maxRange-refRange));
74 else
75 hitCh = 0;
76
77 noHit = (dist>float(MaxBowRange) || hitCh<=hitChance);
78
79 if(!g2 && !noHit && !invincible) {
80 const int32_t mul = script.criticalDamageMultiplyer();
81 const int critChance = int(script.rand(100));
82 if(std::lround(100.f * b.critChance())>critChance)
83 dmg *= mul;
84 }
85 }
86
87 if(noHit)
88 return Val(0,false,invincible);
89
90 if(invincible)
91 return Val(0,true,true);
92
94 return Val(0,true,true);
95
96 return rangeDamage(nsrc,nother,dmg,bMsk);
97 }
98
99DamageCalculator::Val DamageCalculator::rangeDamage(Npc&, Npc& nother, Damage dmg, const CollideMask bMsk) {
100 auto& other = nother.handle();
101
102 if(bMsk & COLL_APPLYDOUBLEDAMAGE)
103 dmg*=2;
104 if(bMsk & COLL_APPLYHALVEDAMAGE)
105 dmg/=2;
106
107 int value = 0;
108 bool invincible = true;
109 for(unsigned int i=0; i<zenkit::DamageType::NUM; ++i) {
110 if(dmg[size_t(i)]==0)
111 continue;
112 int vd = std::max(dmg[size_t(i)] - other.protection[i],0);
113 if(other.protection[i]>=0) { // Filter immune
114 value += vd;
115 invincible = false;
116 }
117 }
118
119 return Val(value,true,invincible);
120 }
121
122DamageCalculator::Val DamageCalculator::swordDamage(Npc& nsrc, Npc& nother) {
123 if(!checkDamageMask(nsrc,nother,nullptr))
124 return Val(0,true,true);
125
126 auto& script = nsrc.world().script();
127 auto& src = nsrc.handle();
128 auto& other = nother.handle();
129
130 // Swords/Fists
131 const int dtype = damageTypeMask(nsrc);
133 int str = nsrc.attribute(Attribute::ATR_STRENGTH);
134 int critChance = int(script.rand(100));
135
136 int value = 0;
137
138 if(auto w = nsrc.inventory().activeWeapon()) {
139 if(w->is2H())
140 tal = TALENT_2H; else
141 tal = TALENT_1H;
142 }
143
144 if(Gothic::inst().version().game==2) {
145 if(nsrc.isMonster() && tal==TALENT_UNKNOWN) {
146 // regular monsters always do critical damage
147 critChance = -1;
148 }
149
150 bool invincible = true;
151 for(unsigned int i=0; i<zenkit::DamageType::NUM; ++i) {
152 if((dtype & (1<<i))==0)
153 continue;
154 int vd = std::max(str + src.damage[i] - other.protection[i],0);
155 if(src.hitchance[tal]<=critChance)
156 vd = (vd-1)/10;
157 if(other.protection[i]>=0) { // Filter immune
158 value += vd;
159 invincible = false;
160 }
161 }
162
163 return Val(value,true,invincible);
164 } else {
165 bool invincible = true;
166 const int32_t mul = script.criticalDamageMultiplyer();
167 for(unsigned int i=0; i<zenkit::DamageType::NUM; ++i) {
168 if((dtype & (1<<i))==0)
169 continue;
170 int vd = 0;
171 if(nsrc.talentValue(tal)<=critChance)
172 vd = std::max(str + src.damage[i] - other.protection[i],0); else
173 vd = std::max(str + mul*src.damage[i] - other.protection[i],0);
174 if(other.protection[i]>=0) { // Filter immune
175 value += vd;
176 invincible = false;
177 }
178 }
179
180 return Val(value,true,invincible);
181 }
182 }
183
185 if(auto w = npc.inventory().activeWeapon())
186 return w->handle().damage_type;
187 return npc.handle().damage_type;
188 }
189
190bool DamageCalculator::checkDamageMask(Npc& nsrc, Npc& nother, const Bullet* b) {
191 auto& other = nother.handle();
192
193 if(b!=nullptr) {
194 auto dmg = b->damage();
195 for(unsigned int i=0;i<zenkit::DamageType::NUM;++i) {
196 if(dmg[size_t(i)]>0 && other.protection[i]>=0)
197 return true;
198 }
199 } else {
200 const int dtype = damageTypeMask(nsrc);
201 for(unsigned int i=0;i<zenkit::DamageType::NUM;++i){
202 if((dtype & (1<<i))==0)
203 continue;
204 return true;
205 }
206 }
207
208 return false;
209 }
210
212 const int dtype = damageTypeMask(src);
213 int d = Gothic::inst().version().game==2 ? src.attribute(Attribute::ATR_DEXTERITY) : 0;
214 Damage ret={};
215 for(unsigned int i=0;i<zenkit::DamageType::NUM;++i){
216 if((dtype & (1<<i))==0)
217 continue;
218 ret[size_t(i)] = d + src.handle().damage[i];
219 }
220 return ret;
221 }
float hitChance() const
Definition bullet.h:54
auto damage() const -> const DamageCalculator::Damage &
Definition bullet.h:49
float pathLength() const
Definition bullet.cpp:106
bool isSpell() const
Definition bullet.cpp:73
float critChance() const
Definition bullet.h:52
static auto rangeDamageValue(Npc &src) -> Damage
static Val damageValue(Npc &src, Npc &other, const Bullet *b, bool isSpell, const DamageCalculator::Damage &splDmg, const CollideMask bMsk)
static Val damageFall(Npc &src, float speed)
static int32_t damageTypeMask(Npc &npc)
static constexpr float gravity
static Gothic & inst()
Definition gothic.cpp:249
auto version() const -> const VersionInfo &
Definition gothic.cpp:263
Definition npc.h:25
int32_t protection(Protection p) const
Definition npc.cpp:1208
uint32_t guild() const
Definition npc.cpp:1223
int32_t talentValue(Talent t) const
Definition npc.cpp:1143
int32_t attribute(Attribute a) const
Definition npc.cpp:1171
auto inventory() const -> const Inventory &
Definition npc.h:330
auto world() -> World &
Definition npc.cpp:624
zenkit::INpc & handle()
Definition npc.h:327
bool isMonster() const
Definition npc.cpp:1227
CollideMask
Definition constants.h:252
@ COLL_APPLYHALVEDAMAGE
Definition constants.h:256
@ COLL_APPLYDOUBLEDAMAGE
Definition constants.h:257
@ COLL_APPLYDAMAGE
Definition constants.h:255
@ COLL_DOEVERYTHING
Definition constants.h:254
Talent
Definition constants.h:435
@ TALENT_1H
Definition constants.h:437
@ TALENT_2H
Definition constants.h:438
@ TALENT_UNKNOWN
Definition constants.h:436
@ MaxBowRange
Definition constants.h:135
@ ReferenceBowRangeG2
Definition constants.h:134
@ MaxMagRange
Definition constants.h:136
@ ReferenceBowRangeG1
Definition constants.h:133
@ ATR_STRENGTH
Definition constants.h:467
@ ATR_DEXTERITY
Definition constants.h:468
@ PROT_FALL
Definition constants.h:492
static float mix(float x, float y, float a)