/
CharAnimations.cpp
2693 lines (2363 loc) · 65.5 KB
/
CharAnimations.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* GemRB - Infinity Engine Emulator
* Copyright (C) 2003 The GemRB Project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
*/
#include "CharAnimations.h"
#include "win32def.h"
#include "AnimationFactory.h"
#include "DataFileMgr.h"
#include "Game.h"
#include "GameData.h"
#include "ImageMgr.h"
#include "Interface.h"
#include "Map.h"
#include "Palette.h"
namespace GemRB {
static int AvatarsCount = 0;
static AvatarStruct *AvatarTable = NULL;
static const ieByte SixteenToNine[16]={0,1,2,3,4,5,6,7,8,7,6,5,4,3,2,1};
static const ieByte SixteenToFive[16]={0,0,1,1,2,2,3,3,4,4,3,3,2,2,1,1};
static const int zOrder_TwoPiece[2] = { 1, 0 };
static const int zOrder_Mirror16[16][4] = {
{ 0, 3, 2, 1 },
{ 0, 3, 2, 1 },
{ 0, 3, 1, 2 },
{ 0, 3, 1, 2 },
{ 1, 0, 3, 2 },
{ 1, 0, 3, 2 },
{ 1, 0, 3, 2 },
{ 1, 0, 3, 2 },
{ 1, 0, 3, 2 },
{ 1, 0, 3, 2 },
{ 1, 0, 3, 2 },
{ 1, 0, 3, 2 },
{ 1, 0, 3, 2 },
{ 0, 3, 1, 2 },
{ 0, 3, 1, 2 },
{ 0, 3, 2, 1 }
};
static const int zOrder_8[8][4] = {
{ 0, 3, 2, 1 },
{ 0, 3, 1, 2 },
{ 1, 0, 3, 2 },
{ 1, 2, 0, 3 },
{ 1, 2, 0, 3 },
{ 2, 0, 3, 1 },
{ 2, 0, 3, 1 },
{ 2, 0, 3, 1 }
};
struct EquipResRefData {
char Suffix[9];
unsigned char Cycle;
};
void CharAnimations::ReleaseMemory()
{
if (AvatarTable) {
free(AvatarTable);
AvatarTable=NULL;
}
}
int CharAnimations::GetAvatarsCount()
{
return AvatarsCount;
}
AvatarStruct *CharAnimations::GetAvatarStruct(int RowNum)
{
return AvatarTable+RowNum;
}
unsigned int CharAnimations::GetAnimationID() const
{
if (AvatarsRowNum==~0u) return 0;
return AvatarTable[AvatarsRowNum].AnimID;
}
int CharAnimations::GetCircleSize() const
{
if (AvatarsRowNum==~0u) return -1;
return AvatarTable[AvatarsRowNum].CircleSize;
}
int CharAnimations::NoPalette() const
{
if (AvatarsRowNum==~0u) return -1;
return AvatarTable[AvatarsRowNum].PaletteType;
}
int CharAnimations::GetAnimType() const
{
if (AvatarsRowNum==~0u) return -1;
return AvatarTable[AvatarsRowNum].AnimationType;
}
int CharAnimations::GetSize() const
{
if (AvatarsRowNum==~0u) return 0;
return AvatarTable[AvatarsRowNum].Size;
}
int CharAnimations::GetBloodColor() const
{
if(AvatarsRowNum==~0u) return 0;
return AvatarTable[AvatarsRowNum].BloodColor;
}
unsigned int CharAnimations::GetFlags() const
{
if(AvatarsRowNum==~0u) return 0;
return AvatarTable[AvatarsRowNum].Flags;
}
unsigned char CharAnimations::MaybeOverrideStance(unsigned char stance) const
{
if(AvatarsRowNum==~0u) return stance;
return AvatarTable[AvatarsRowNum].StanceOverride[stance];
}
static ieResRef EmptySound={0};
const ieResRef &CharAnimations::GetWalkSound() const
{
if(AvatarsRowNum==~0u) return EmptySound;
return AvatarTable[AvatarsRowNum].WalkSound;
}
int CharAnimations::GetWalkSoundCount() const
{
if(AvatarsRowNum==~0u) return 0;
return AvatarTable[AvatarsRowNum].WalkSoundCount;
}
int CharAnimations::GetActorPartCount() const
{
if (AvatarsRowNum==~0u) return -1;
switch (AvatarTable[AvatarsRowNum].AnimationType) {
case IE_ANI_NINE_FRAMES: //dragon animations
return 9;
case IE_ANI_FOUR_FRAMES: //wyvern animations
case IE_ANI_FOUR_FRAMES_2: //demogorgon animations
return 4;
case IE_ANI_TWO_PIECE: //ankheg animations
return 2;
case IE_ANI_PST_GHOST: //special pst anims
if (AvatarTable[AvatarsRowNum].Prefixes[1][0]=='*') {
return 1;
}
if (AvatarTable[AvatarsRowNum].Prefixes[2][0]=='*') {
return 2;
}
if (AvatarTable[AvatarsRowNum].Prefixes[3][0]=='*') {
return 3;
}
return 4;
default:
return 1;
}
}
int CharAnimations::GetTotalPartCount() const
{
if (AvatarsRowNum==~0u) return -1;
switch (AvatarTable[AvatarsRowNum].AnimationType) {
case IE_ANI_FOUR_FILES:
case IE_ANI_FOUR_FILES_2:
return GetActorPartCount() + 1; // only weapon
case IE_ANI_CODE_MIRROR:
return GetActorPartCount() + 3; // equipment
case IE_ANI_TWENTYTWO:
return GetActorPartCount() + 3; // equipment
default:
return GetActorPartCount();
}
}
const ieResRef& CharAnimations::GetArmourLevel(int ArmourLevel) const
{
//ignore ArmourLevel for the static pst anims (all sprites are displayed)
if (AvatarTable[AvatarsRowNum].AnimationType == IE_ANI_PST_GHOST) {
ArmourLevel = 0;
}
return AvatarTable[AvatarsRowNum].Prefixes[ArmourLevel];
}
void CharAnimations::SetArmourLevel(int ArmourLevel)
{
if (AvatarsRowNum==~0u) return;
//ignore ArmourLevel for the static pst anims (all sprites are displayed)
if (AvatarTable[AvatarsRowNum].AnimationType == IE_ANI_PST_GHOST) {
ArmourLevel = 0;
}
CopyResRef( ResRef, AvatarTable[AvatarsRowNum].Prefixes[ArmourLevel] );
DropAnims();
}
//RangedType could be weird, reducing its value to 0,1,2
void CharAnimations::SetRangedType(int rt)
{
if ((unsigned int) rt<2) {
RangedType=(ieByte) rt;
} else {
RangedType=2;
}
}
void CharAnimations::SetWeaponType(int wt)
{
if (wt != WeaponType) {
WeaponType = wt;
DropAnims();
}
}
void CharAnimations::SetHelmetRef(const char* ref)
{
HelmetRef[0] = ref[0];
HelmetRef[1] = ref[1];
// TODO: Only drop helmet anims?
// Note: this doesn't happen "often", so this isn't a performance
// bottleneck. (wjp)
DropAnims();
gamedata->FreePalette(palette[PAL_HELMET], 0);
gamedata->FreePalette(modifiedPalette[PAL_HELMET], 0);
}
void CharAnimations::SetWeaponRef(const char* ref)
{
WeaponRef[0] = ref[0];
WeaponRef[1] = ref[1];
// TODO: Only drop weapon anims?
DropAnims();
gamedata->FreePalette(palette[PAL_WEAPON], 0);
gamedata->FreePalette(modifiedPalette[PAL_WEAPON], 0);
}
void CharAnimations::SetOffhandRef(const char* ref)
{
OffhandRef[0] = ref[0];
OffhandRef[1] = ref[1];
// TODO: Only drop shield/offhand anims?
DropAnims();
gamedata->FreePalette(palette[PAL_OFFHAND], 0);
gamedata->FreePalette(modifiedPalette[PAL_OFFHAND], 0);
}
void CharAnimations::LockPalette(const ieDword *gradients)
{
if (lockPalette) return;
//cannot lock colors for PST animations
if (GetAnimType() >= IE_ANI_PST_ANIMATION_1)
{
return;
}
//force initialisation of animation
SetColors( gradients );
GetAnimation(0,0);
if (palette[PAL_MAIN]) {
lockPalette=true;
}
}
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
static const char StancePrefix[] = {'3','2','5','5','4','4','2','2','5','4','1','3','3','3','4','1','4','4','4'};
static const char CyclePrefix[] = {'0','0','1','1','1','1','0','0','1','1','0','0','0','0','1','1','1','1','1'};
static const unsigned int CycleOffset[] = { 0, 0, 0, 0, 0, 9, 0, 0, 0, 18, 0, 0, 9, 18, 0, 0, 0, 0, 0 };
#define NINE_FRAMES_PALETTE(stance) ((PaletteType) (StancePrefix[stance] - '1'))
void CharAnimations::SetColors(const ieDword *arg)
{
Colors = arg;
SetupColors(PAL_MAIN);
SetupColors(PAL_MAIN_2);
SetupColors(PAL_MAIN_3);
SetupColors(PAL_MAIN_4);
SetupColors(PAL_MAIN_5);
SetupColors(PAL_WEAPON);
SetupColors(PAL_OFFHAND);
SetupColors(PAL_HELMET);
}
void CharAnimations::CheckColorMod()
{
if (!GlobalColorMod.locked) {
if (GlobalColorMod.type != RGBModifier::NONE) {
GlobalColorMod.type = RGBModifier::NONE;
GlobalColorMod.speed = 0;
for (int i = 0; i < PAL_MAX; ++i) {
change[i] = true;
}
}
}
unsigned int location;
for (location = 0; location < PAL_MAX * 8; ++location) {
if (!ColorMods[location].phase) {
if (ColorMods[location].type != RGBModifier::NONE) {
ColorMods[location].type = RGBModifier::NONE;
ColorMods[location].speed = 0;
change[location>>3]=true;
}
}
}
//this is set by sanctuary and stoneskin (override global colors)
lockPalette = false;
}
void CharAnimations::SetupColors(PaletteType type)
{
Palette* pal = palette[type];
if (!pal) {
return;
}
if (!Colors) {
return;
}
if (GetAnimType() >= IE_ANI_PST_ANIMATION_1) {
// Only do main palette
if (type != PAL_MAIN) {
return;
}
// TODO: handle equipment colour glows
// Colors[6] is the COLORCOUNT stat in PST.
// It tells how many customisable color slots we have.
// The color slots start from the end of the palette and go
// backwards. There are 6 available slots with a size of 32 each.
// Actually, the slots seem to be written in the cre file
// but we ignore them, i'm not sure this is correct
int colorcount = Colors[6];
int size = 32;
//the color count shouldn't be more than 6!
if (colorcount>6) colorcount=6;
int dest = 256-colorcount*size;
bool needmod = false;
if (GlobalColorMod.type != RGBModifier::NONE) {
needmod = true;
}
//don't drop the palette, it disables rgb pulse effects
//also don't bail out, we need to free the modified palette later
//so this entire block is needless
/*
if ((colorcount == 0) && (needmod==false) ) {
gamedata->FreePalette(palette[PAL_MAIN], PaletteResRef);
PaletteResRef[0]=0;
return;
}
*/
for (int i = 0; i < colorcount; i++) {
core->GetPalette( Colors[i]&255, size,
&palette[PAL_MAIN]->col[dest] );
dest +=size;
}
if (needmod) {
if (!modifiedPalette[PAL_MAIN])
modifiedPalette[PAL_MAIN] = new Palette();
modifiedPalette[PAL_MAIN]->SetupGlobalRGBModification(palette[PAL_MAIN], GlobalColorMod);
} else {
gamedata->FreePalette(modifiedPalette[PAL_MAIN], 0);
}
return;
}
int PType = NoPalette();
if ( PType && (type <= PAL_MAIN_5) ) {
//handling special palettes like MBER_BL (black bear)
if (PType!=1) {
ieResRef oldResRef;
CopyResRef(oldResRef, PaletteResRef[type]);
if (GetAnimType()==IE_ANI_NINE_FRAMES) {
snprintf(PaletteResRef[type],9,"%.4s_%-.2s%c",ResRef, (char *) &PType, '1'+type);
} else {
snprintf(PaletteResRef[type],9,"%.4s_%-.2s",ResRef, (char *) &PType);
}
strlwr(PaletteResRef[type]);
Palette *tmppal = gamedata->GetPalette(PaletteResRef[type]);
if (tmppal) {
gamedata->FreePalette(palette[type], oldResRef);
palette[type] = tmppal;
} else {
PaletteResRef[type][0]=0;
}
}
bool needmod = GlobalColorMod.type != RGBModifier::NONE;
if (needmod) {
if (!modifiedPalette[type])
modifiedPalette[type] = new Palette();
modifiedPalette[type]->SetupGlobalRGBModification(palette[type], GlobalColorMod);
} else {
gamedata->FreePalette(modifiedPalette[type], 0);
}
return;
}
pal->SetupPaperdollColours(Colors, type);
if (lockPalette) {
return;
}
int i;
bool needmod = false;
if (GlobalColorMod.type != RGBModifier::NONE) {
needmod = true;
} else {
for (i = 0; i < 7; ++i) {
if (ColorMods[i+8*type].type != RGBModifier::NONE)
needmod = true;
}
}
if (needmod) {
if (!modifiedPalette[type])
modifiedPalette[type] = new Palette();
if (GlobalColorMod.type != RGBModifier::NONE) {
modifiedPalette[type]->SetupGlobalRGBModification(palette[type], GlobalColorMod);
} else {
modifiedPalette[type]->SetupRGBModification(palette[type],ColorMods, type);
}
} else {
gamedata->FreePalette(modifiedPalette[type], 0);
}
}
Palette* CharAnimations::GetPartPalette(int part)
{
int actorPartCount = GetActorPartCount();
PaletteType type = PAL_MAIN;
if (GetAnimType() == IE_ANI_NINE_FRAMES) {
//these animations use several palettes
type = NINE_FRAMES_PALETTE(StanceID);
}
else if (GetAnimType() == IE_ANI_FOUR_FRAMES_2) return NULL;
// always use unmodified BAM palette for the supporting part
else if (GetAnimType() == IE_ANI_TWO_PIECE && part == 1) return NULL;
else if (part == actorPartCount) type = PAL_WEAPON;
else if (part == actorPartCount+1) type = PAL_OFFHAND;
else if (part == actorPartCount+2) type = PAL_HELMET;
if (modifiedPalette[type])
return modifiedPalette[type];
return palette[type];
}
static int compare_avatars(const void *a, const void *b)
{
unsigned int aa = ((AvatarStruct *)a)->AnimID;
unsigned int bb = ((AvatarStruct *)b)->AnimID;
return (int) (aa-bb);
}
void CharAnimations::InitAvatarsTable()
{
AutoTable Avatars("avatars");
if (!Avatars) {
error("CharAnimations", "A critical animation file is missing!\n");
}
AvatarTable = (AvatarStruct *) calloc ( AvatarsCount = Avatars->GetRowCount(), sizeof(AvatarStruct) );
int i=AvatarsCount;
DataFileMgr *resdata = core->GetResDataINI();
while(i--) {
AvatarTable[i].AnimID=(unsigned int) strtol(Avatars->GetRowName(i),NULL,0 );
strnlwrcpy(AvatarTable[i].Prefixes[0],Avatars->QueryField(i,AV_PREFIX1),8);
strnlwrcpy(AvatarTable[i].Prefixes[1],Avatars->QueryField(i,AV_PREFIX2),8);
strnlwrcpy(AvatarTable[i].Prefixes[2],Avatars->QueryField(i,AV_PREFIX3),8);
strnlwrcpy(AvatarTable[i].Prefixes[3],Avatars->QueryField(i,AV_PREFIX4),8);
AvatarTable[i].AnimationType=(ieByte) atoi(Avatars->QueryField(i,AV_ANIMTYPE) );
AvatarTable[i].CircleSize=(ieByte) atoi(Avatars->QueryField(i,AV_CIRCLESIZE) );
const char *tmp = Avatars->QueryField(i,AV_USE_PALETTE);
//QueryField will always return a zero terminated string
//so tmp[0] must exist
if ( isalpha (tmp[0]) ) {
//this is a hack, we store 2 letters on an integer
//it was allocated with calloc, so don't bother erasing it
strncpy( (char *) &AvatarTable[i].PaletteType, tmp, 3);
}
else {
AvatarTable[i].PaletteType=atoi(Avatars->QueryField(i,AV_USE_PALETTE) );
}
char size = Avatars->QueryField(i,AV_SIZE)[0];
if (size == '*') {
size = 0;
}
AvatarTable[i].Size = size;
AvatarTable[i].WalkScale = 0;
AvatarTable[i].RunScale = 0;
AvatarTable[i].Bestiary = -1;
for (int j = 0; j < MAX_ANIMS; j++)
AvatarTable[i].StanceOverride[j] = j;
if (resdata) {
char section[12];
snprintf(section,10,"%d", i);
if (!resdata->GetKeysCount(section)) continue;
float walkscale = resdata->GetKeyAsFloat(section, "walkscale", 0.0f);
if (walkscale != 0.0f) AvatarTable[i].WalkScale = (int)(1000.0f / walkscale);
float runscale = resdata->GetKeyAsFloat(section, "runscale", 0.0f);
if (runscale != 0.0f) AvatarTable[i].RunScale = (int)(1000.0f / runscale);
AvatarTable[i].Bestiary = resdata->GetKeyAsInt(section, "bestiary", -1);
}
}
qsort(AvatarTable, AvatarsCount, sizeof(AvatarStruct), compare_avatars);
AutoTable blood("bloodclr");
if (blood) {
int rows = blood->GetRowCount();
for(int i=0;i<rows;i++) {
unsigned long value = 0;
unsigned long flags = 0;
unsigned long rmin = 0;
unsigned long rmax = 0xffff;
valid_number(blood->QueryField(i,0), (long &)value);
valid_number(blood->QueryField(i,1), (long &)rmin);
valid_number(blood->QueryField(i,2), (long &)rmax);
valid_number(blood->QueryField(i,3), (long &)flags);
if (value>255 || rmin>rmax || rmax>0xffff) {
Log(ERROR, "CharAnimations", "Invalid bloodclr entry: %02x %04x-%04x ",
(unsigned int) value, (unsigned int) rmin, (unsigned int) rmax);
continue;
}
for(int j=0;j<AvatarsCount;j++) {
if (rmax<AvatarTable[j].AnimID) break;
if (rmin>AvatarTable[j].AnimID) continue;
AvatarTable[j].BloodColor = (char) value;
AvatarTable[j].Flags = (unsigned int) flags;
}
}
}
AutoTable walk("walksnd");
if (walk) {
int rows = walk->GetRowCount();
for(int i=0;i<rows;i++) {
ieResRef value;
unsigned long rmin = 0;
unsigned long rmax = 0xffff;
unsigned long range = 0;
strnuprcpy(value, walk->QueryField(i,0), 8);
valid_number(walk->QueryField(i,1), (long &)rmin);
valid_number(walk->QueryField(i,2), (long &)rmax);
valid_number(walk->QueryField(i,3), (long &)range);
if (value[0]=='*') {
value[0]=0;
range = 0;
}
if (range>255 || rmin>rmax || rmax>0xffff) {
Log(ERROR, "CharAnimations", "Invalid walksnd entry: %02x %04x-%04x ",
(unsigned int) range, (unsigned int) rmin, (unsigned int) rmax);
continue;
}
for(int j=0;j<AvatarsCount;j++) {
if (rmax<AvatarTable[j].AnimID) break;
if (rmin>AvatarTable[j].AnimID) continue;
memcpy(AvatarTable[j].WalkSound, value, sizeof(ieResRef) );
AvatarTable[j].WalkSoundCount = (unsigned int) range;
}
}
}
AutoTable stances("stances", true);
if (stances) {
int rows = stances->GetRowCount();
for (int i = 0; i < rows; i++) {
unsigned long id = 0, s1 = 0, s2 = 0;
valid_number(stances->GetRowName(i), (long &)id);
valid_number(stances->QueryField(i, 0), (long &)s1);
valid_number(stances->QueryField(i, 1), (long &)s2);
if (s1 >= MAX_ANIMS || s2 >= MAX_ANIMS) {
Log(ERROR, "CharAnimations", "Invalid stances entry: %04x %d %d",
(unsigned int) id, (unsigned int) s1, (unsigned int) s2);
continue;
}
for (int j = 0; j < AvatarsCount; j++) {
if (id < AvatarTable[j].AnimID) break;
if (id == AvatarTable[j].AnimID) {
AvatarTable[j].StanceOverride[s1] = s2;
break;
}
}
}
}
}
CharAnimations::CharAnimations(unsigned int AnimID, ieDword ArmourLevel)
{
Colors = NULL;
int i,j;
for (i = 0; i < PAL_MAX; ++i) {
change[i] = true;
modifiedPalette[i] = NULL;
palette[i] = NULL;
}
nextStanceID = 0;
StanceID = 0;
autoSwitchOnEnd = false;
lockPalette = false;
if (!AvatarsCount) {
InitAvatarsTable();
}
for (i = 0; i < MAX_ANIMS; i++) {
for (j = 0; j < MAX_ORIENT; j++) {
Anims[i][j] = NULL;
}
}
ArmorType = 0;
RangedType = 0;
WeaponType = 0;
for (i = 0; i < 5; ++i) {
PaletteResRef[i][0] = 0;
}
WeaponRef[0] = 0;
HelmetRef[0] = 0;
OffhandRef[0] = 0;
for (i = 0; i < PAL_MAX * 8; ++i) {
ColorMods[i].type = RGBModifier::NONE;
ColorMods[i].speed = 0;
// make initial phase depend on location to make the pulse appear
// less even
ColorMods[i].phase = 5*i;
ColorMods[i].locked = false;
}
GlobalColorMod.type = RGBModifier::NONE;
GlobalColorMod.speed = 0;
GlobalColorMod.phase = 0;
GlobalColorMod.locked = false;
lastModUpdate = 0;
AvatarsRowNum=AvatarsCount;
if (core->HasFeature(GF_ONE_BYTE_ANIMID) ) {
ieDword tmp = AnimID&0xf000;
if (tmp==0x6000 || tmp==0xe000) {
AnimID&=0xff;
}
}
while (AvatarsRowNum--) {
if (AvatarTable[AvatarsRowNum].AnimID<=AnimID) {
SetArmourLevel( ArmourLevel );
return;
}
}
ResRef[0]=0;
Log(ERROR, "CharAnimations", "Invalid or nonexistent avatar entry:%04X", AnimID);
}
//we have to drop them when armourlevel changes
void CharAnimations::DropAnims()
{
Animation** tmppoi;
int partCount = GetTotalPartCount();
for (int StanceID = 0; StanceID < MAX_ANIMS; StanceID++) {
for (int i = 0; i < MAX_ORIENT; i++) {
if (Anims[StanceID][i]) {
tmppoi = Anims[StanceID][i];
for (int j = 0; j < partCount; j++)
delete Anims[StanceID][i][j];
delete[] tmppoi;
// anims can only be duplicated at the Animation** level
for (int IDb=StanceID;IDb < MAX_ANIMS; IDb++) {
for (int i2 = 0; i2<MAX_ORIENT; i2++) {
if (Anims[IDb][i2] == tmppoi) {
Anims[IDb][i2] = 0;
}
}
}
}
}
}
}
CharAnimations::~CharAnimations(void)
{
DropAnims();
int i;
for (i = 0; i <= PAL_MAIN_5; ++i)
gamedata->FreePalette(palette[i], PaletteResRef[i]);
for (; i < PAL_MAX; ++i)
gamedata->FreePalette(palette[i], 0);
for (i = 0; i < PAL_MAX; ++i)
gamedata->FreePalette(modifiedPalette[i], 0);
}
/*
This is a simple Idea of how the animation are coded
There are the following animation types:
IE_ANI_CODE_MIRROR: The code automatically mirrors the needed frames
(as in the example above)
These Animations are stores using the following template:
[NAME][ARMORTYPE][ACTIONCODE]
Each BAM File contains only 9 Orientations, the missing 7 Animations
are created by Horizontally Mirroring the 1-7 Orientations.
IE_ANI_CODE_MIRROR_2: another mirroring type with more animations
[NAME]g[1,11-15,2,21-26]
IE_ANI_CODE_MIRROR_3: Almost identical to IE_ANI_CODE_MIRROR_2, but with fewer cycles in g26
IE_ANI_ONE_FILE: The whole animation is in one file, no mirroring needed.
Each animation group is 16 Cycles.
IE_ANI_TWO_FILES: The whole animation is in 2 files. The East and West part are in 2 BAM Files.
Example:
ACHKg1
ACHKg1E
Each BAM File contains many animation groups, each animation group
stores 5 Orientations, the missing 3 are stored in East BAM Files.
IE_ANI_FOUR_FILES: The Animation is coded in Four Files. Probably it is an old Two File animation with
additional frames added in a second time.
IE_ANI_FOUR_FILES_2: Like IE_ANI_FOUR_FILES but with only 16 cycles per frame.
IE_ANI_FOUR_FILES_3: A variation of the four files animation without equipment, and
with even and odd orientations split across two files, plus the standard
separate eastern parts, so the layout is
[NAME][H|L]G1[/E]
IE_ANI_TWENTYTWO: This Animation Type stores the Animation in the following format
[NAME][ACTIONCODE][/E]
ACTIONCODE=A1-6, CA, SX, SA (sling is A1)
The g1 file contains several animation states. See MHR
Probably it could use A7-9 files too, bringing the file numbers to 28.
This is the original bg1 format.
IE_ANI_SIX_FILES: The layout for these files is:
[NAME][g1-3][/E]
Each state contains 16 Orientations, but the last 6 are stored in the East file.
g1 contains only the walking animation.
G2 contains stand, ready, get hit, die and twitch.
g3 contains 3 attacks.
IE_ANI_SIX_FILES_2: Similar to SIX_FILES, but the orientation numbers are reduced like in FOUR_FILES. Only one animation uses it: MOGR
IE_ANI_TWO_FILES_2: Animations using this type are stored using the following template:
[NAME]g1[/E]
Each state contains 8 Orientations, but the second 4 are stored in the East file.
From the standard animations, only AHRS and ACOW belong to this type.
IE_ANI_TWO_FILES_3: Animations using this type are stored using the following template:
[NAME][ACTIONTYPE][/E]
Example:
MBFI*
MBFI*E
Each BAM File contains one animation group, each animation group
stores 5 Orientations though the files contain all 8 Cycles, the missing 3 are stored in East BAM Files in Cycle: Stance*8+ (5,6,7).
This is the standard IWD animation, but BG2 also has it.
See MMR
IE_ANI_TWO_FILES_3B: Animations using this type are stored using the following template:
[NAME][ACTIONTYPE][/E]
Example:
MBFI*
MBFI*E
This is a cut down version of IE_ANI_TWO_FILES_3. A2, CA and SP suffixes are missing.
This is the standard IWD animation, but BG2 also has it.
See MOR2
IE_ANI_TWO_FILES_4: This type stores animations in two files (G1 and G2) which each having only
one cycle. And both of those seem to be identical.
IE_ANI_TWO_PIECE: This is a modified IE_ANI_SIX_FILES with supporting still frames (using a
different palette) stored in a second set of files. Currently only used by MAKH
IE_ANI_FOUR_FRAMES: These animations are large, four bams make a frame.
IE_ANI_FOUR_FRAMES_2: Another variation of the four frames format with more files.
IE_ANI_NINE_FRAMES: These animations are huge, nine bams make a frame.
IE_ANI_FRAGMENT: These animations are used for projectile graphics.
A single file contains 5 cycles (code mirror for east animation)
IE_ANI_PST_ANIMATION_1:
IE_ANI_PST_ANIMATION_2:
IE_ANI_PST_ANIMATION_3:
Planescape: Torment Animations are stored in a different
way than the other games. This format uses the following template:
[C/D][ACTIONTYPE][NAME][B]
Example:
CAT1MRTB
Each Animation stores 5 Orientations, which are automatically mirrored
to form an 8 Orientation Animation. PST Animations have a different Palette
format. This Animation Type handles the PST Palette format too.
NOTE: Walking/Running animations store 9 Orientations.
The second variation is missing the resting stance (STD) and the transitions.
These creatures are always in combat stance (don't rest).
Animation_3 is without STC (combat stance), they are always standing
IE_ANI_PST_STAND: This is a modified PST animation, it contains only a
Standing image for every orientations, it follows the
[C/D]STD[NAME][B] standard.
IE_ANI_PST_GHOST: This is a special static animation with no standard
All armourlevels are drawn simultaneously. There is no orientation or stance.
WEST PART | EAST PART
|
NW NNW N NNE NE
NW 006 007 008 009 010 NE
WNW 005 | 011 ENE
W 004 xxx 012 E
WSW 003 | 013 ESE
SW 002 001 000 015 014 SE
SW SSW S SSE SE
|
|
*/
Animation** CharAnimations::GetAnimation(unsigned char Stance, unsigned char Orient)
{
if (Stance >= MAX_ANIMS) {
error("CharAnimation", "Illegal stance ID\n");
}
//for paletted dragon animations, we need the stance id
StanceID = nextStanceID = Stance;
int AnimType = GetAnimType();
//alter stance here if it is missing and you know a substitute
//probably we should feed this result back to the actor?
switch (AnimType) {
case -1: //invalid animation
return NULL;
case IE_ANI_PST_STAND:
StanceID=IE_ANI_AWAKE;
break;
case IE_ANI_PST_GHOST:
StanceID=IE_ANI_AWAKE;
Orient=0;
break;
case IE_ANI_PST_ANIMATION_3: //stc->std
if (StanceID==IE_ANI_READY) {
StanceID=IE_ANI_AWAKE;
}
break;
case IE_ANI_PST_ANIMATION_2: //std->stc
if (StanceID==IE_ANI_AWAKE) {
StanceID=IE_ANI_READY;
}
break;
}
//pst animations don't have separate animation for sleep/die
if (AnimType >= IE_ANI_PST_ANIMATION_1) {
if (StanceID==IE_ANI_DIE) {
StanceID=IE_ANI_TWITCH;
}
}
StanceID = MaybeOverrideStance(StanceID);
//TODO: Implement Auto Resource Loading
//setting up the sequencing of animation cycles
autoSwitchOnEnd = false;
switch (StanceID) {
case IE_ANI_DAMAGE:
nextStanceID = IE_ANI_READY;
autoSwitchOnEnd = true;
break;
case IE_ANI_SLEEP: //going to sleep
nextStanceID = IE_ANI_TWITCH;
autoSwitchOnEnd = true;
break;
case IE_ANI_TWITCH: //dead, sleeping
autoSwitchOnEnd = false;
break;
case IE_ANI_DIE: //going to die
nextStanceID = IE_ANI_TWITCH;
autoSwitchOnEnd = true;
break;
case IE_ANI_WALK:
case IE_ANI_RUN:
case IE_ANI_CAST: // looping
case IE_ANI_READY:
break;
case IE_ANI_AWAKE:
break;
case IE_ANI_EMERGE:
case IE_ANI_GET_UP:
case IE_ANI_HEAD_TURN:
case IE_ANI_PST_START:
nextStanceID = IE_ANI_AWAKE;
autoSwitchOnEnd = true;
break;
case IE_ANI_CONJURE: //ending
case IE_ANI_SHOOT:
case IE_ANI_ATTACK:
case IE_ANI_ATTACK_JAB:
case IE_ANI_ATTACK_SLASH:
case IE_ANI_ATTACK_BACKSLASH:
nextStanceID = IE_ANI_READY;
autoSwitchOnEnd = true;
break;
default:
Log(MESSAGE, "CharAnimation", "Invalid Stance: %d", StanceID);
break;
}
Animation** anims = Anims[StanceID][Orient];
if (anims) {
return anims;
}
int partCount = GetTotalPartCount();
int actorPartCount = GetActorPartCount();
if (partCount <= 0) return 0;
anims = new Animation*[partCount];
EquipResRefData* equipdat = 0;
for (int part = 0; part < partCount; ++part)
{
anims[part] = 0;
//newresref is based on the prefix (ResRef) and various
// other things.
//this is longer than expected so it won't overflow
char NewResRef[12];
unsigned char Cycle = 0;
if (part < actorPartCount) {
// Character animation parts
if (equipdat) delete equipdat;
//we need this long for special anims
strlcpy( NewResRef, ResRef, sizeof(ieResRef) );
GetAnimResRef( StanceID, Orient, NewResRef, Cycle, part, equipdat);
} else {
// Equipment animation parts
anims[part] = 0;
if (GetSize() == 0) continue;
if (part == actorPartCount) {
if (WeaponRef[0] == 0) continue;
// weapon
GetEquipmentResRef(WeaponRef,false,NewResRef,Cycle,equipdat);
} else if (part == actorPartCount+1) {
if (OffhandRef[0] == 0) continue;
if (WeaponType == IE_ANI_WEAPON_2H) continue;
// off-hand
if (WeaponType == IE_ANI_WEAPON_1H) {