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