1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2012 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 *
19 */
20
21 #include "CombatInfo.h"
22
23 #include "Interface.h"
24 #include "System/StringBuffer.h"
25 #include "Scriptable/Actor.h"
26
27 namespace GemRB {
28
29 static bool third = false;
30
31 /*
32 * Shared code between the classes
33 */
SetBonusInternal(int & current,int bonus,int mod)34 static void SetBonusInternal(int& current, int bonus, int mod)
35 {
36 int newBonus = current;
37
38 switch (mod) {
39 case 0: // cummulative modifier
40 if (third) {
41 // 3ed boni don't stack
42 // but, use some extra logic so any negative boni first try to cancel out
43 int tmp = bonus;
44 if ((current < 0) ^ (bonus < 0)) {
45 tmp = current + bonus;
46 }
47 if (tmp != bonus) {
48 // we just summed the boni, so we need to be careful about the resulting sign, since (-2+3)>(-2), but abs(-2+3)<abs(-2)
49 if (tmp > current) {
50 newBonus = tmp;
51 } // else leave it be at the current value
52 } else {
53 if (abs(tmp) > abs(current)) {
54 newBonus = tmp;
55 } // else leave it be at the current value
56 }
57 } else {
58 newBonus += bonus;
59 }
60 break;
61 // like with other effects, the following options have chicked-and-egg problems and the result depends on the order of application
62 case 1: // flat modifier
63 newBonus = bonus;
64 break;
65 case 2: // percent modifier
66 newBonus = current * bonus / 100;
67 break;
68 default:
69 error("CombatInfo", "Bad bonus mod type: %d", mod);
70 }
71
72 current = newBonus;
73 }
74
75
76 /*
77 * Class holding the main armor class stat and general boni
78 */
ArmorClass()79 ArmorClass::ArmorClass()
80 {
81 natural = 0;
82 Owner = NULL;
83 ResetAll();
84
85 third = !!core->HasFeature(GF_3ED_RULES);
86 }
87
SetOwner(Actor * owner)88 void ArmorClass::SetOwner( Actor* owner)
89 {
90 Owner = owner;
91 // rerun this, so both the stats get set correctly
92 SetNatural(natural);
93 }
94
GetTotal() const95 int ArmorClass::GetTotal() const
96 {
97 return total;
98 }
99
RefreshTotal()100 void ArmorClass::RefreshTotal()
101 {
102 total = natural + deflectionBonus + armorBonus + shieldBonus + dexterityBonus + wisdomBonus + genericBonus;
103 // add a maximum_values[IE_ARMORCLASS] check here if needed
104 if (Owner) { // not true for a short while during init, but we make amends immediately
105 Owner->Modified[IE_ARMORCLASS] = total;
106 }
107 }
108
109 // resets all the boni (natural is skipped, since it holds the base value)
ResetAll()110 void ArmorClass::ResetAll() {
111 deflectionBonus = 0;
112 armorBonus = 0;
113 shieldBonus = 0;
114 dexterityBonus = 0;
115 wisdomBonus = 0;
116 genericBonus = 0;
117 RefreshTotal();
118 }
119
SetNatural(int AC,int)120 void ArmorClass::SetNatural(int AC, int /*mod*/)
121 {
122 natural = AC;
123 if (Owner) { // not true for a short while during init, but we make amends immediately
124 Owner->BaseStats[IE_ARMORCLASS] = AC;
125 }
126 RefreshTotal();
127 }
128
SetDeflectionBonus(int bonus,int mod)129 void ArmorClass::SetDeflectionBonus(int bonus, int mod)
130 {
131 SetBonus(deflectionBonus, bonus, mod);
132 }
133
SetArmorBonus(int bonus,int mod)134 void ArmorClass::SetArmorBonus(int bonus, int mod)
135 {
136 SetBonus(armorBonus, bonus, mod);
137 }
138
SetShieldBonus(int bonus,int mod)139 void ArmorClass::SetShieldBonus(int bonus, int mod)
140 {
141 SetBonus(shieldBonus, bonus, mod);
142 }
143
SetDexterityBonus(int bonus,int mod)144 void ArmorClass::SetDexterityBonus(int bonus, int mod)
145 {
146 SetBonus(dexterityBonus, bonus, mod);
147 }
148
SetWisdomBonus(int bonus,int mod)149 void ArmorClass::SetWisdomBonus(int bonus, int mod)
150 {
151 SetBonus(wisdomBonus, bonus, mod);
152 }
153
SetGenericBonus(int bonus,int mod)154 void ArmorClass::SetGenericBonus(int bonus, int mod)
155 {
156 SetBonus(genericBonus, bonus, mod);
157 }
158
SetBonus(int & current,int bonus,int mod)159 void ArmorClass::SetBonus(int& current, int bonus, int mod)
160 {
161 SetBonusInternal(current, bonus, mod);
162 RefreshTotal();
163 }
164
HandleFxBonus(int mod,bool permanent)165 void ArmorClass::HandleFxBonus(int mod, bool permanent)
166 {
167 if (permanent) {
168 if (Owner->IsReverseToHit()) {
169 SetNatural(natural-mod);
170 } else {
171 SetNatural(natural+mod);
172 }
173 return;
174 }
175 // this was actually aditively modifying Modified directly before
176 if (Owner->IsReverseToHit()) {
177 SetGenericBonus(-mod, 0);
178 } else {
179 SetGenericBonus(mod, 0);
180 }
181 }
182
dump() const183 void ArmorClass::dump() const
184 {
185 StringBuffer buffer;
186 buffer.appendFormatted("Debugdump of ArmorClass of %s:\n", Owner->GetName(1));
187 buffer.appendFormatted("TOTAL: %d\n", total);
188 buffer.appendFormatted("Natural: %d\tGeneric: %d\tDeflection: %d\n", natural, genericBonus, deflectionBonus);
189 buffer.appendFormatted("Armor: %d\tShield: %d\n", armorBonus, shieldBonus);
190 buffer.appendFormatted("Dexterity: %d\tWisdom: %d\n\n", dexterityBonus, wisdomBonus);
191 Log(DEBUG, "ArmorClass", buffer);
192 }
193
194 /*
195 * Class holding the main to-hit/thac0 stat and general boni
196 * NOTE: Always use it through GetCombatDetails to get the full state
197 */
ToHitStats()198 ToHitStats::ToHitStats()
199 {
200 base = 0;
201 babDecrement = 0;
202 Owner = NULL;
203 ResetAll();
204
205 third = !!core->HasFeature(GF_3ED_RULES);
206 }
207
SetOwner(Actor * owner)208 void ToHitStats::SetOwner(Actor* owner)
209 {
210 Owner = owner;
211 // rerun this, so both the stats get set correctly
212 SetBase(base);
213 }
214
GetTotal() const215 int ToHitStats::GetTotal() const
216 {
217 return total;
218 }
219
RefreshTotal()220 void ToHitStats::RefreshTotal()
221 {
222 total = base + proficiencyBonus + armorBonus + shieldBonus + abilityBonus + weaponBonus + genericBonus + fxBonus;
223 if (Owner) { // not true for a short while during init, but we make amends immediately
224 Owner->Modified[IE_TOHIT] = total;
225 }
226 }
227
228 // resets all the boni
ResetAll()229 void ToHitStats::ResetAll() {
230 weaponBonus = 0;
231 armorBonus = 0;
232 shieldBonus = 0;
233 abilityBonus = 0;
234 proficiencyBonus = 0;
235 genericBonus = 0;
236 fxBonus = 0;
237 RefreshTotal();
238 }
239
SetBase(int tohit,int)240 void ToHitStats::SetBase(int tohit, int /*mod*/)
241 {
242 base = tohit;
243 if (Owner) { // not true for a short while during init, but we make amends immediately
244 Owner->BaseStats[IE_TOHIT] = tohit;
245 }
246 RefreshTotal();
247 }
248
SetProficiencyBonus(int bonus,int mod)249 void ToHitStats::SetProficiencyBonus(int bonus, int mod)
250 {
251 SetBonus(proficiencyBonus, bonus, mod);
252 }
253
SetArmorBonus(int bonus,int mod)254 void ToHitStats::SetArmorBonus(int bonus, int mod)
255 {
256 SetBonus(armorBonus, bonus, mod);
257 }
258
SetShieldBonus(int bonus,int mod)259 void ToHitStats::SetShieldBonus(int bonus, int mod)
260 {
261 SetBonus(shieldBonus, bonus, mod);
262 }
263
SetAbilityBonus(int bonus,int mod)264 void ToHitStats::SetAbilityBonus(int bonus, int mod)
265 {
266 SetBonus(abilityBonus, bonus, mod);
267 }
268
SetWeaponBonus(int bonus,int mod)269 void ToHitStats::SetWeaponBonus(int bonus, int mod)
270 {
271 SetBonus(weaponBonus, bonus, mod);
272 }
273
SetGenericBonus(int bonus,int mod)274 void ToHitStats::SetGenericBonus(int bonus, int mod)
275 {
276 SetBonus(genericBonus, bonus, mod);
277 }
278
SetFxBonus(int bonus,int mod)279 void ToHitStats::SetFxBonus(int bonus, int mod)
280 {
281 SetBonus(fxBonus, bonus, mod);
282 }
283
SetBonus(int & current,int bonus,int mod)284 void ToHitStats::SetBonus(int& current, int bonus, int mod)
285 {
286 SetBonusInternal(current, bonus, mod);
287 RefreshTotal();
288 }
289
HandleFxBonus(int mod,bool permanent)290 void ToHitStats::HandleFxBonus(int mod, bool permanent)
291 {
292 if (permanent) {
293 if (Owner->IsReverseToHit()) {
294 SetBase(base-mod);
295 } else {
296 SetBase(base+mod);
297 }
298 return;
299 }
300 // this was actually aditively modifying Modified directly before
301 if (Owner->IsReverseToHit()) {
302 SetFxBonus(-mod, 0);
303 } else {
304 SetFxBonus(mod, 0);
305 }
306 }
307
SetBABDecrement(int decrement)308 void ToHitStats::SetBABDecrement(int decrement) {
309 babDecrement = decrement;
310 }
311
GetTotalForAttackNum(unsigned int number) const312 int ToHitStats::GetTotalForAttackNum(unsigned int number) const
313 {
314 if (number <= 1) { // out of combat, we'd get 0 and that's fine
315 return total;
316 }
317 number--;
318 // compute the cascaded values
319 // at low levels with poor stats, even the total can be negatice
320 return total-number*babDecrement;
321 }
322
dump() const323 void ToHitStats::dump() const
324 {
325 StringBuffer buffer;
326 buffer.appendFormatted("Debugdump of ToHit of %s:\n", Owner->GetName(1));
327 buffer.appendFormatted("TOTAL: %d\n", total);
328 buffer.appendFormatted("Base: %2d\tGeneric: %d\tEffect: %d\n", base, genericBonus, fxBonus);
329 buffer.appendFormatted("Armor: %d\tShield: %d\n", armorBonus, shieldBonus);
330 buffer.appendFormatted("Weapon: %d\tProficiency: %d\tAbility: %d\n\n", weaponBonus, proficiencyBonus, abilityBonus);
331 Log(DEBUG, "ToHit", buffer);
332 }
333
334
335 }
336