1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003-2005 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 "GameData.h"
22 
23 #include "globals.h"
24 
25 #include "ActorMgr.h"
26 #include "AnimationMgr.h"
27 #include "Cache.h"
28 #include "CharAnimations.h"
29 #include "Effect.h"
30 #include "EffectMgr.h"
31 #include "Factory.h"
32 #include "Game.h"
33 #include "ImageFactory.h"
34 #include "ImageMgr.h"
35 #include "Interface.h"
36 #include "Item.h"
37 #include "ItemMgr.h"
38 #include "PluginMgr.h"
39 #include "ResourceDesc.h"
40 #include "ScriptedAnimation.h"
41 #include "Spell.h"
42 #include "SpellMgr.h"
43 #include "StoreMgr.h"
44 #include "VEFObject.h"
45 #include "Scriptable/Actor.h"
46 #include "System/FileStream.h"
47 
48 #include <cstdio>
49 
50 namespace GemRB {
51 
ReleaseItem(void * poi)52 static void ReleaseItem(void *poi)
53 {
54 	delete ((Item *) poi);
55 }
56 
ReleaseSpell(void * poi)57 static void ReleaseSpell(void *poi)
58 {
59 	delete ((Spell *) poi);
60 }
61 
ReleaseEffect(void * poi)62 static void ReleaseEffect(void *poi)
63 {
64 	delete ((Effect *) poi);
65 }
66 
67 GEM_EXPORT GameData* gamedata;
68 
GameData()69 GameData::GameData()
70 {
71 	factory = new Factory();
72 }
73 
~GameData()74 GameData::~GameData()
75 {
76 	delete factory;
77 	ItemSounds.clear();
78 }
79 
ClearCaches()80 void GameData::ClearCaches()
81 {
82 	ItemCache.RemoveAll(ReleaseItem);
83 	SpellCache.RemoveAll(ReleaseSpell);
84 	EffectCache.RemoveAll(ReleaseEffect);
85 	PaletteCache.clear ();
86 
87 	while (!stores.empty()) {
88 		Store *store = stores.begin()->second;
89 		stores.erase(stores.begin());
90 		delete store;
91 	}
92 	for (auto& c : colors) {
93 		free(const_cast<char*>(c.first));
94 	}
95 }
96 
GetCreature(const char * ResRef,unsigned int PartySlot)97 Actor *GameData::GetCreature(const char* ResRef, unsigned int PartySlot)
98 {
99 	DataStream* ds = GetResource( ResRef, IE_CRE_CLASS_ID );
100 	if (!ds)
101 		return 0;
102 
103 	PluginHolder<ActorMgr> actormgr(IE_CRE_CLASS_ID);
104 	if (!actormgr->Open(ds)) {
105 		return 0;
106 	}
107 	Actor* actor = actormgr->GetActor(PartySlot);
108 	return actor;
109 }
110 
LoadCreature(const char * ResRef,unsigned int PartySlot,bool character,int VersionOverride)111 int GameData::LoadCreature(const char* ResRef, unsigned int PartySlot, bool character, int VersionOverride)
112 {
113 	DataStream *stream;
114 
115 	Actor* actor;
116 	if (character) {
117 		char nPath[_MAX_PATH], fName[16];
118 		snprintf( fName, sizeof(fName), "%s.chr", ResRef);
119 		PathJoin( nPath, core->GamePath, "characters", fName, NULL );
120 		stream = FileStream::OpenFile(nPath);
121 		PluginHolder<ActorMgr> actormgr(IE_CRE_CLASS_ID);
122 		if (!actormgr->Open(stream)) {
123 			return -1;
124 		}
125 		actor = actormgr->GetActor(PartySlot);
126 	} else {
127 		actor = GetCreature(ResRef, PartySlot);
128 	}
129 
130 	if ( !actor ) {
131 		return -1;
132 	}
133 
134 	if (VersionOverride != -1) {
135 		actor->version = VersionOverride;
136 	}
137 
138 	//both fields are of length 9, make this sure!
139 	memcpy(actor->Area, core->GetGame()->CurrentArea, sizeof(actor->Area) );
140 	if (actor->BaseStats[IE_STATE_ID] & STATE_DEAD) {
141 		actor->SetStance( IE_ANI_TWITCH );
142 	} else {
143 		actor->SetStance( IE_ANI_AWAKE );
144 	}
145 	actor->SetOrientation( 0, false );
146 
147 	if ( PartySlot != 0 ) {
148 		return core->GetGame()->JoinParty( actor, JP_JOIN|JP_INITPOS );
149 	}
150 	else {
151 		return core->GetGame()->AddNPC( actor );
152 	}
153 }
154 
155 /** Loads a 2DA Table, returns -1 on error or the Table Index on success */
LoadTable(const ieResRef ResRef,bool silent)156 int GameData::LoadTable(const ieResRef ResRef, bool silent)
157 {
158 	int ind = GetTableIndex( ResRef );
159 	if (ind != -1) {
160 		tables[ind].refcount++;
161 		return ind;
162 	}
163 	//print("(%s) Table not found... Loading from file", ResRef);
164 	DataStream* str = GetResource( ResRef, IE_2DA_CLASS_ID, silent );
165 	if (!str) {
166 		return -1;
167 	}
168 	PluginHolder<TableMgr> tm(IE_2DA_CLASS_ID);
169 	if (!tm) {
170 		delete str;
171 		return -1;
172 	}
173 	if (!tm->Open(str)) {
174 		return -1;
175 	}
176 	Table t;
177 	t.refcount = 1;
178 	CopyResRef(t.ResRef, ResRef);
179 	t.tm = tm;
180 	ind = -1;
181 	for (size_t i = 0; i < tables.size(); i++) {
182 		if (tables[i].refcount == 0) {
183 			ind = ( int ) i;
184 			break;
185 		}
186 	}
187 	if (ind != -1) {
188 		tables[ind] = t;
189 		return ind;
190 	}
191 	tables.push_back( t );
192 	return ( int ) tables.size() - 1;
193 }
194 /** Gets the index of a loaded table, returns -1 on error */
GetTableIndex(const char * ResRef) const195 int GameData::GetTableIndex(const char* ResRef) const
196 {
197 	for (size_t i = 0; i < tables.size(); i++) {
198 		if (tables[i].refcount == 0)
199 			continue;
200 		if (strnicmp( tables[i].ResRef, ResRef, 8 ) == 0)
201 			return ( int ) i;
202 	}
203 	return -1;
204 }
205 /** Gets a Loaded Table by its index, returns NULL on error */
GetTable(size_t index) const206 Holder<TableMgr> GameData::GetTable(size_t index) const
207 {
208 	if (index >= tables.size()) {
209 		return NULL;
210 	}
211 	if (tables[index].refcount == 0) {
212 		return NULL;
213 	}
214 	return tables[index].tm;
215 }
216 
217 /** Frees a Loaded Table, returns false on error, true on success */
DelTable(unsigned int index)218 bool GameData::DelTable(unsigned int index)
219 {
220 	if (index==0xffffffff) {
221 		tables.clear();
222 		return true;
223 	}
224 	if (index >= tables.size()) {
225 		return false;
226 	}
227 	if (tables[index].refcount == 0) {
228 		return false;
229 	}
230 	tables[index].refcount--;
231 	if (tables[index].refcount == 0)
232 		if (tables[index].tm)
233 			tables[index].tm.release();
234 	return true;
235 }
236 
GetPalette(const ResRef resname)237 PaletteHolder GameData::GetPalette(const ResRef resname)
238 {
239 	auto iter = PaletteCache.find(resname);
240 	if (iter != PaletteCache.end())
241 		return iter->second;
242 
243 	ResourceHolder<ImageMgr> im = GetResourceHolder<ImageMgr>(resname);
244 	if (im == nullptr) {
245 		PaletteCache[resname] = nullptr;
246 		return NULL;
247 	}
248 
249 	PaletteHolder palette = new Palette();
250 	im->GetPalette(256,palette->col);
251 	palette->named=true;
252 	PaletteCache[resname] = palette;
253 	return palette;
254 }
255 
FreePalette(PaletteHolder & pal,const ieResRef)256 void GameData::FreePalette(PaletteHolder &pal, const ieResRef)
257 {
258 	// This was previously much hairier, trying to keep track of two different
259 	// palette refcounts.  Now we just rely on Holder/Held to make sure memory
260 	// is freed, while not bothering about freeing named palettes from the
261 	// map.
262 	pal = nullptr;
263 }
264 
GetItem(const ieResRef resname,bool silent)265 Item* GameData::GetItem(const ieResRef resname, bool silent)
266 {
267 	Item *item = (Item *) ItemCache.GetResource(resname);
268 	if (item) {
269 		return item;
270 	}
271 	DataStream* str = GetResource(resname, IE_ITM_CLASS_ID, silent);
272 	PluginHolder<ItemMgr> sm(IE_ITM_CLASS_ID);
273 	if (!sm) {
274 		delete ( str );
275 		return NULL;
276 	}
277 	if (!sm->Open(str)) {
278 		return NULL;
279 	}
280 
281 	item = new Item();
282 	//this is required for storing the 'source'
283 	strnlwrcpy(item->Name, resname, 8);
284 	sm->GetItem( item );
285 
286 	ItemCache.SetAt(resname, (void *) item);
287 	return item;
288 }
289 
290 //you can supply name for faster access
FreeItem(Item const * itm,const ieResRef name,bool free)291 void GameData::FreeItem(Item const *itm, const ieResRef name, bool free)
292 {
293 	int res;
294 
295 	res=ItemCache.DecRef((void *) itm, name, free);
296 	if (res<0) {
297 		error("Core", "Corrupted Item cache encountered (reference count went below zero), Item name is: %.8s\n", name);
298 	}
299 	if (res) return;
300 	if (free) delete itm;
301 }
302 
GetSpell(const ieResRef resname,bool silent)303 Spell* GameData::GetSpell(const ieResRef resname, bool silent)
304 {
305 	Spell *spell = (Spell *) SpellCache.GetResource(resname);
306 	if (spell) {
307 		return spell;
308 	}
309 	DataStream* str = GetResource( resname, IE_SPL_CLASS_ID, silent );
310 	PluginHolder<SpellMgr> sm(IE_SPL_CLASS_ID);
311 	if (!sm) {
312 		delete ( str );
313 		return NULL;
314 	}
315 	if (!sm->Open(str)) {
316 		return NULL;
317 	}
318 
319 	spell = new Spell();
320 	//this is required for storing the 'source'
321 	strnlwrcpy(spell->Name, resname, 8);
322 	sm->GetSpell( spell, silent );
323 
324 	SpellCache.SetAt(resname, (void *) spell);
325 	return spell;
326 }
327 
FreeSpell(Spell * spl,const ieResRef name,bool free)328 void GameData::FreeSpell(Spell *spl, const ieResRef name, bool free)
329 {
330 	int res;
331 
332 	res=SpellCache.DecRef((void *) spl, name, free);
333 	if (res<0) {
334 		error("Core", "Corrupted Spell cache encountered (reference count went below zero), Spell name is: %.8s or %.8s\n",
335 			name, spl->Name);
336 	}
337 	if (res) return;
338 	if (free) delete spl;
339 }
340 
GetEffect(const ieResRef resname)341 Effect* GameData::GetEffect(const ieResRef resname)
342 {
343 	Effect *effect = (Effect *) EffectCache.GetResource(resname);
344 	if (effect) {
345 		return effect;
346 	}
347 	DataStream* str = GetResource( resname, IE_EFF_CLASS_ID );
348 	PluginHolder<EffectMgr> em(IE_EFF_CLASS_ID);
349 	if (!em) {
350 		delete ( str );
351 		return NULL;
352 	}
353 	if (!em->Open(str)) {
354 		return NULL;
355 	}
356 
357 	effect = em->GetEffect(new Effect() );
358 	if (effect == NULL) {
359 		return NULL;
360 	}
361 
362 	EffectCache.SetAt(resname, (void *) effect);
363 	return effect;
364 }
365 
FreeEffect(Effect * eff,const ieResRef name,bool free)366 void GameData::FreeEffect(Effect *eff, const ieResRef name, bool free)
367 {
368 	int res;
369 
370 	res=EffectCache.DecRef((void *) eff, name, free);
371 	if (res<0) {
372 		error("Core", "Corrupted Effect cache encountered (reference count went below zero), Effect name is: %.8s\n", name);
373 	}
374 	if (res) return;
375 	if (free) delete eff;
376 }
377 
378 //if the default setup doesn't fit for an animation
379 //create a vvc for it!
GetScriptedAnimation(const char * effect,bool doublehint)380 ScriptedAnimation* GameData::GetScriptedAnimation( const char *effect, bool doublehint)
381 {
382 	ScriptedAnimation *ret = NULL;
383 
384 	if (Exists( effect, IE_VVC_CLASS_ID, true ) ) {
385 		DataStream *ds = GetResource( effect, IE_VVC_CLASS_ID );
386 		ret = new ScriptedAnimation(ds);
387 	} else {
388 		AnimationFactory *af = (AnimationFactory *)
389 			GetFactoryResource( effect, IE_BAM_CLASS_ID, IE_NORMAL );
390 		if (af) {
391 			ret = new ScriptedAnimation();
392 			ret->LoadAnimationFactory( af, doublehint?2:0);
393 		}
394 	}
395 	if (ret) {
396 		strnlwrcpy(ret->ResName, effect, 8);
397 	}
398 	return ret;
399 }
400 
GetVEFObject(const char * effect,bool doublehint)401 VEFObject* GameData::GetVEFObject(const char *effect, bool doublehint)
402 {
403 	VEFObject *ret = NULL;
404 
405 	if (Exists( effect, IE_VEF_CLASS_ID, true ) ) {
406 		DataStream *ds = GetResource( effect, IE_VEF_CLASS_ID );
407 		ret = new VEFObject();
408 		strnlwrcpy(ret->ResName, effect, 8);
409 		ret->LoadVEF(ds);
410 	} else {
411 		if (Exists( effect, IE_2DA_CLASS_ID, true ) ) {
412 			ret = new VEFObject();
413 			ret->Load2DA(effect);
414 		} else {
415 			ScriptedAnimation *sca = GetScriptedAnimation(effect, doublehint);
416 			if (sca) {
417 				ret = new VEFObject(sca);
418 			}
419 		}
420 	}
421 	return ret;
422 }
423 
424 // Return single BAM frame as a sprite. Use if you want one frame only,
425 // otherwise it's not efficient
GetBAMSprite(const ieResRef ResRef,int cycle,int frame,bool silent)426 Holder<Sprite2D> GameData::GetBAMSprite(const ieResRef ResRef, int cycle, int frame, bool silent)
427 {
428 	Holder<Sprite2D> tspr;
429 	AnimationFactory* af = ( AnimationFactory* )
430 		GetFactoryResource( ResRef, IE_BAM_CLASS_ID, IE_NORMAL, silent );
431 	if (!af) return 0;
432 	if (cycle == -1)
433 		tspr = af->GetFrameWithoutCycle( (unsigned short) frame );
434 	else
435 		tspr = af->GetFrame( (unsigned short) frame, (unsigned char) cycle );
436 	return tspr;
437 }
438 
GetAnySprite(const char * resRef,int cycle,int frame,bool silent)439 Holder<Sprite2D> GameData::GetAnySprite(const char *resRef, int cycle, int frame, bool silent)
440 {
441 	Holder<Sprite2D> img = gamedata->GetBAMSprite(resRef, cycle, frame, silent);
442 	if (img) return img;
443 
444 	// try static image formats to support PNG
445 	ResourceHolder<ImageMgr> im = GetResourceHolder<ImageMgr>(resRef);
446 	if (im) {
447 		img = im->GetSprite2D();
448 	}
449 	return img;
450 }
451 
GetFactoryResource(const char * resname,SClass_ID type,unsigned char mode,bool silent)452 FactoryObject* GameData::GetFactoryResource(const char* resname, SClass_ID type,
453 	unsigned char mode, bool silent)
454 {
455 	int fobjindex = factory->IsLoaded(resname,type);
456 	// already cached
457 	if ( fobjindex != -1)
458 		return factory->GetFactoryObject( fobjindex );
459 
460 	// empty resref
461 	if (!resname || !strcmp(resname, "")) return nullptr;
462 
463 	switch (type) {
464 	case IE_BAM_CLASS_ID:
465 	{
466 		DataStream* ret = GetResource( resname, type, silent );
467 		if (ret) {
468 			PluginHolder<AnimationMgr> ani(IE_BAM_CLASS_ID);
469 			if (!ani)
470 				return NULL;
471 			if (!ani->Open(ret))
472 				return NULL;
473 			AnimationFactory* af = ani->GetAnimationFactory( resname, mode );
474 			factory->AddFactoryObject( af );
475 			return af;
476 		}
477 		return NULL;
478 	}
479 	case IE_BMP_CLASS_ID:
480 	{
481 		ResourceHolder<ImageMgr> img = GetResourceHolder<ImageMgr>(resname, silent);
482 		if (img) {
483 			ImageFactory* fact = img->GetImageFactory( resname );
484 			factory->AddFactoryObject( fact );
485 			return fact;
486 		}
487 
488 		return NULL;
489 	}
490 	default:
491 		Log(MESSAGE, "KEYImporter", "%s files are not supported.",
492 			core->TypeExt(type));
493 		return NULL;
494 	}
495 }
496 
AddFactoryResource(FactoryObject * res)497 void GameData::AddFactoryResource(FactoryObject* res)
498 {
499 	factory->AddFactoryObject(res);
500 }
501 
GetStore(const ieResRef ResRef)502 Store* GameData::GetStore(const ieResRef ResRef)
503 {
504 	StoreMap::iterator it = stores.find(ResRef);
505 	if (it != stores.end()) {
506 		return it->second;
507 	}
508 
509 	DataStream* str = gamedata->GetResource(ResRef, IE_STO_CLASS_ID);
510 	PluginHolder<StoreMgr> sm(IE_STO_CLASS_ID);
511 	if (sm == nullptr) {
512 		delete ( str );
513 		return NULL;
514 	}
515 	if (!sm->Open(str)) {
516 		return NULL;
517 	}
518 
519 	Store* store = sm->GetStore(new Store());
520 	if (store == NULL) {
521 		return NULL;
522 	}
523 	strnlwrcpy(store->Name, ResRef, 8);
524 	// The key needs to last as long as the store,
525 	// so use the one we just copied.
526 	stores[store->Name] = store;
527 	return store;
528 }
529 
SaveStore(Store * store)530 void GameData::SaveStore(Store* store)
531 {
532 	if (!store)
533 		return;
534 	StoreMap::iterator it = stores.find(store->Name);
535 	if (it == stores.end()) {
536 		error("GameData", "Saving a store that wasn't cached.");
537 	}
538 
539 	PluginHolder<StoreMgr> sm(IE_STO_CLASS_ID);
540 	if (sm == nullptr) {
541 		error("GameData", "Can't save store to cache.");
542 	}
543 
544 	FileStream str;
545 
546 	if (!str.Create(store->Name, IE_STO_CLASS_ID)) {
547 		error("GameData", "Can't create file while saving store.");
548 	}
549 	if (!sm->PutStore(&str, store)) {
550 		error("GameData", "Error saving store.");
551 	}
552 
553 	stores.erase(it);
554 	delete store;
555 }
556 
SaveAllStores()557 void GameData::SaveAllStores()
558 {
559 	while (!stores.empty()) {
560 		SaveStore(stores.begin()->second);
561 	}
562 }
563 
ReadItemSounds()564 void GameData::ReadItemSounds()
565 {
566 	AutoTable itemsnd("itemsnd");
567 	if (!itemsnd) {
568 		return;
569 	}
570 
571 	int rowCount = itemsnd->GetRowCount();
572 	int colCount = itemsnd->GetColumnCount();
573 	for (int i = 0; i < rowCount; i++) {
574 		ItemSounds[i] = std::vector<const char*>();
575 		for (int j = 0; j < colCount; j++) {
576 			ieResRef snd;
577 			strnlwrcpy(snd, itemsnd->QueryField(i, j), 8);
578 			if (!strcmp(snd, "*")) break;
579 			ItemSounds[i].push_back(strdup(snd));
580 		}
581 	}
582 }
583 
GetItemSound(ResRef & Sound,ieDword ItemType,const char * ID,ieDword Col)584 bool GameData::GetItemSound(ResRef &Sound, ieDword ItemType, const char *ID, ieDword Col)
585 {
586 	Sound = 0;
587 
588 	if (ItemSounds.empty()) {
589 		ReadItemSounds();
590 	}
591 
592 	if (Col >= ItemSounds[ItemType].size()) {
593 		return false;
594 	}
595 
596 	if (ID && ID[1] == 'A') {
597 		//the last 4 item sounds are used for '1A', '2A', '3A' and '4A' (pst)
598 		//item animation types
599 		ItemType = ItemSounds.size()-4 + ID[0]-'1';
600 	}
601 
602 	if (ItemType >= (ieDword) ItemSounds.size()) {
603 		return false;
604 	}
605 	Sound = ItemSounds[ItemType][Col];
606 	return true;
607 }
608 
GetSwingCount(ieDword ItemType)609 int GameData::GetSwingCount(ieDword ItemType)
610 {
611 	if (ItemSounds.empty()) {
612 		ReadItemSounds();
613 	}
614 
615 	// everything but the unrelated preceding columns (IS_SWINGOFFSET)
616 	return ItemSounds[ItemType].size() - 2;
617 }
618 
GetRacialTHAC0Bonus(ieDword proficiency,const char * raceName)619 int GameData::GetRacialTHAC0Bonus(ieDword proficiency, const char *raceName)
620 {
621 	static bool loadedRacialTHAC0 = false;
622 	if (!loadedRacialTHAC0) {
623 		raceTHAC0Bonus.load("racethac", true);
624 		loadedRacialTHAC0 = true;
625 	}
626 
627 	// not all games have the table
628 	if (!raceTHAC0Bonus || !raceName) return 0;
629 
630 	char profString[5];
631 	snprintf(profString, sizeof(profString), "%u", proficiency);
632 	return atoi(raceTHAC0Bonus->QueryField(profString, raceName));
633 }
634 
HasInfravision(const char * raceName)635 bool GameData::HasInfravision(const char *raceName)
636 {
637 	if (!racialInfravision.ok()) {
638 		racialInfravision.load("racefeat", true);
639 	}
640 	if (!raceName) return false;
641 
642 	return atoi(racialInfravision->QueryField(raceName, "VALUE")) & 1;
643 }
644 
GetSpellAbilityDie(const Actor * target,int which)645 int GameData::GetSpellAbilityDie(const Actor *target, int which)
646 {
647 	static bool loadedSpellAbilityDie = false;
648 	if (!loadedSpellAbilityDie) {
649 		if (!spellAbilityDie.load("clssplab", true)) {
650 			Log(ERROR, "GameData", "GetSpellAbilityDie failed loading clssplab.2da!");
651 			return 6;
652 		}
653 		loadedSpellAbilityDie = true;
654 	}
655 
656 	ieDword cls = target->GetActiveClass();
657 	if (cls >= spellAbilityDie->GetRowCount()) cls = 0;
658 	return atoi(spellAbilityDie->QueryField(cls, which));
659 }
660 
GetTrapSaveBonus(ieDword level,int cls)661 int GameData::GetTrapSaveBonus(ieDword level, int cls)
662 {
663 	if (!core->HasFeature(GF_3ED_RULES)) return 0;
664 
665 	if (!trapSaveBonus.ok()) {
666 		trapSaveBonus.load("trapsave", true);
667 	}
668 
669 	return atoi(trapSaveBonus->QueryField(level - 1, cls - 1));
670 }
671 
GetTrapLimit(Scriptable * trapper)672 int GameData::GetTrapLimit(Scriptable *trapper)
673 {
674 	if (!trapLimit.ok()) {
675 		trapLimit.load("traplimt", true);
676 	}
677 
678 	if (trapper->Type != ST_ACTOR) {
679 		return 6; // not using table default, since EE's file has it at 0
680 	}
681 
682 	const Actor *caster = (Actor *) trapper;
683 	ieDword kit = caster->GetStat(IE_KIT);
684 	const char *rowName;
685 	if (kit != 0x4000) { // KIT_BASECLASS
686 		rowName = caster->GetKitName(kit);
687 	} else {
688 		ieDword cls = caster->GetActiveClass();
689 		rowName = caster->GetClassName(cls);
690 	}
691 
692 	return atoi(trapLimit->QueryField(rowName, "LIMIT"));
693 }
694 
GetSummoningLimit(ieDword sex)695 int GameData::GetSummoningLimit(ieDword sex)
696 {
697 	if (!summoningLimit.ok()) {
698 		summoningLimit.load("summlimt", true);
699 	}
700 
701 	unsigned int row = 1000;
702 	switch (sex) {
703 		case SEX_SUMMON:
704 		case SEX_SUMMON_DEMON:
705 			row = 0;
706 			break;
707 		case SEX_BOTH:
708 			row = 1;
709 			break;
710 		default:
711 			break;
712 	}
713 	return atoi(summoningLimit->QueryField(row, 0));
714 }
715 
GetColor(const char * row)716 const Color& GameData::GetColor(const char *row)
717 {
718 	// preload converted colors
719 	if (colors.empty()) {
720 		AutoTable colorTable("colors", true);
721 		for (size_t r = 0; r < colorTable->GetRowCount(); r++) {
722 			ieDword c = strtol(colorTable->QueryField(r, 0), nullptr, 0);
723 			colors[strdup(colorTable->GetRowName(r))] = Color(c);
724 		}
725 	}
726 	const auto it = colors.find(row);
727 	if (it != colors.end()) {
728 		return it->second;
729 	}
730 	return ColorRed;
731 }
732 
733 // wspatck bonus handling
GetWeaponStyleAPRBonus(int row,int col)734 int GameData::GetWeaponStyleAPRBonus(int row, int col)
735 {
736 	// preload optimized version, since this gets called each tick several times
737 	if (weaponStyleAPRBonusMax.IsZero()) {
738 		AutoTable bonusTable("wspatck", true);
739 		if (!bonusTable.ok()) {
740 			weaponStyleAPRBonusMax.w = -1;
741 			return 0;
742 		}
743 
744 		int rows = bonusTable->GetRowCount();
745 		int cols = bonusTable->GetColumnCount();
746 		weaponStyleAPRBonusMax.h = rows;
747 		weaponStyleAPRBonusMax.w = cols;
748 		weaponStyleAPRBonus.resize(rows * cols);
749 		for (int i = 0; i < rows; i++) {
750 			for (int j = 0; j < cols; j++) {
751 				int tmp = atoi(bonusTable->QueryField(i, j));
752 				// negative values relate to x/2, so we adjust them
753 				// positive values relate to x, so we must times by 2
754 				if (tmp < 0) {
755 					tmp = -2 * tmp - 1;
756 				} else {
757 					tmp *= 2;
758 				}
759 				weaponStyleAPRBonus[i * cols + j] = tmp;
760 			}
761 		}
762 	} else if (weaponStyleAPRBonusMax.w == -1) {
763 		return 0;
764 	}
765 
766 	if (row >= weaponStyleAPRBonusMax.h) {
767 		row = weaponStyleAPRBonusMax.h - 1;
768 	}
769 	if (col >= weaponStyleAPRBonusMax.w) {
770 		col = weaponStyleAPRBonusMax.w - 1;
771 	}
772 	return weaponStyleAPRBonus[row * weaponStyleAPRBonusMax.w + col];
773 }
774 
775 }
776