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