-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCharacterClasses.ixx
1408 lines (1184 loc) · 43.2 KB
/
CharacterClasses.ixx
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
/*
*
* ____ _ _
* / ___| |__ __ _ _ __ __ _ ___| |_ ___ _ __ ___
* | | | '_ \ / _` | '__/ _` |/ __| __/ _ \ '__/ __|
* | |___| | | | (_| | | | (_| | (__| || __/ | \__ \
* \____|_| |_|\__,_|_| \__,_|\___|\__\___|_| |___/
* __ _ _ __ __| |
* / _` | '_ \ / _` |
* | (_| | | | | (_| |
* \__,_|_| |_|\__,_|_
* | | (_)_ __ ___ | |__ ___
* | | | | '_ ` _ \| '_ \/ __|
* | |___| | | | | | | |_) \__ \
* |_____|_|_| |_| |_|_.__/|___/
*
* CHARACTER and LIMB (abstract) objects to be extended in the modules that use them.
*
*
* TO DO:
* -- Create a master list, or some kind of reference, to find out which TYPE (label? slug?) of Limb we're using.
* ---- It should somehow be part of a Map.
* ---- Does a Map own the Limb? Or does the Limb hold a reference to its parent Map?
* ------ Certainly each Limb saved in the DB should reference its parent Map... or not. The Label or Slug
* ---- Should we use enum?
* ---- Should I get rid of the JSON and hard-code it all in a factory in this module?
*
*/
module;
export module CharacterClasses;
import "SDL.h";
import "SDL_image.h";
import <string>;
import <vector>;
import <tuple>;
import <cmath>;
import <limits>;
import TypeStorage;
import UI;
using namespace std;
export enum CharacterType { Player, Hostile, Friendly, None }; /* NOT a CLASS because we want to use it as int. */
/* Red beats Green (fire consumes life), Green beats Blue (life consumes water), Blue beats Red (water extinguishes fire) */
export enum class LimbState { Free, Owned, Equipped }; /* If it is OWNED or EQUIPPED, then there must be a character id. Every character should exist in the DB.*/
class Limb;
int normalizeAngle(int angle);
/* Where the limb image will be drawn onto the character surface. */
export struct SuitLimbPlacement {
string slug;
Point position;
};
export bool compareDrawOrder(Limb& limbA, Limb& limbB);
struct AvatarDimensionsStruct {
AvatarDimensionsStruct() {
leftmost = 0;
rightmost = 0;
topmost = 0;
bottommost = 0;
avatarWidth = 0;
avatarHeight = 0;
greaterDimension = 0;
}
/* Extreme reach of textures. */
int leftmost;
int rightmost;
int topmost;
int bottommost;
/* Final dimensions calcualted from extreme reach of textures. */
int avatarWidth;
int avatarHeight;
/* Greater dimension allows us to make a square. */
int greaterDimension;
};
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* ~ _____ ___ ____ __ __ ~
* ~ | ___/ _ \| _ \| \/ | ~
* ~ | |_ | | | | |_) | |\/| | ~
* ~ | _|| |_| | _ <| | | | ~
* ~ |_| \___/|_| \_\_| |_| ~
* ~ ____ _____ _____ _ _ ____ _____ ____ ~
* ~ / ___|_ _| _ \| | | |/ ___|_ _/ ___| ~
* ~ \___ \ | | | |_) | | | | | | | \___ \ ~
* ~ ___) || | | _ <| |_| | |___ | | ___) |~
* ~ |____/ |_| |_| \_\\___/ \____| |_| |____/ ~
* ~ ~
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* These are the basic types holding core data of what will be objects.
* Vanilla forms of objects, prior to being saved to (or retrieved from) the database.
*
* Every value for a form struct must be a const.
*/
export struct LimbForm {
string name;
string slug;
int hp;
int strength;
int speed;
int intelligence;
DominanceNode domNode;
vector<Point> jointPoints; /* Limb CLASS will have full Joint objects which hold references to other limbs. */
string texturePath;
/* CONSTRUCTOR */
LimbForm(
string name, string slug,
int hp, int strength, int speed, int intelligence,
DominanceNode domNode, string texturePath, vector<Point> jointPoints)
:
name(name), slug(slug),
hp(hp), strength(strength), speed(speed), intelligence(intelligence),
domNode(domNode), texturePath(texturePath), jointPoints(jointPoints) {
}
};
/* A suit is abstract. It is NOT a character. It holds information to build an abstract base character. */
export struct SuitForm {
const string name;
const string slug;
const vector<SuitLimbPlacement> limbPlacements;
bool unscrambled;
SuitForm(string name, string slug, vector<SuitLimbPlacement> limbPlacements, bool unscrambled = false) :
name(name), slug(slug), limbPlacements(limbPlacements), unscrambled(unscrambled) { }
};
export struct LandmarkForm {
const string name;
const int blocksWidth;
const int blocksHeight;
const vector<Point> blockPositions;
const SDL_Texture* texture;
const LandmarkType landmarkType;
LandmarkForm(
string name, int width, int height, vector<Point> blockPositions,
SDL_Texture* texture, LandmarkType landmarkType ) :
name(name), blocksWidth(width), blocksHeight(height), blockPositions(blockPositions),
texture(texture), landmarkType(landmarkType) { }
};
export struct MapForm {
MapLevel mapLevel;
MapType mapType;
string name;
string slug;
vector<LimbForm> nativeLimbs; /* We will need some limbs to be "free" and NOT part of a Suit. So the suits will simply refer to the slugs of the limbs, not contain the limbs. */
vector<SuitForm> suits;
int blocksWidth;
int blocksHeight;
vector<SDL_Texture*> floorTextures;
vector<SDL_Texture*> wallTextures;
vector<SDL_Texture*> pathTextures;
};
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* ~ ____ _ _ ____ ____ _____ ____ ~
* ~ / ___| | / \ / ___/ ___|| ____/ ___| ~
* ~ | | | | / _ \ \___ \___ \| _| \___ \ ~
* ~ | |___| |___ / ___ \ ___) |__) | |___ ___) |~
* ~ \____|_____/_/ \_\____/____/|_____|____/ ~
* ~ ~
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/* TO DO: add id (from database) */
export class Joint {
public:
/* Vanilla empty Limb. Possibly never use. */
Joint() {
pointForm = { 0, 0 };
isAnchor = false;
connectedLimbId = -1;
anchorJointIndex = -1;
id = -1;
}
/* When we create a new Joint for a new Limb. */
Joint(Point pointForm) : pointForm(pointForm), isAnchor(false), connectedLimbId(-1), anchorJointIndex(-1) {
resetModifiedPoint();
}
/* When we load a joint from the database. */
Joint(
Point pointForm,
Point modifiedPoint,
bool isAnchor,
int connectedLimbId,
int anchorJointIndex,
int id = -1
) :
pointForm(pointForm),
modifiedPoint(modifiedPoint),
isAnchor(isAnchor),
connectedLimbId(connectedLimbId),
anchorJointIndex(anchorJointIndex),
id(id)
{ }
void setId(int id) { this->id = id; }
void setAnchor(bool makeAnchor = true) { isAnchor = makeAnchor; }
void connectLimb(int limbId, int jointIndex) {
connectedLimbId = limbId;
anchorJointIndex = jointIndex;
}
void detachLimb() {
connectedLimbId = -1;
anchorJointIndex = -1; }
bool isFree() { return !isAnchor && connectedLimbId < 0; }
int getId() { return id; }
bool getIsAnchor() { return isAnchor; }
int getConnectedLimbId() { return connectedLimbId; }
int getChildLimbAnchorJointIndex() { return anchorJointIndex; }
Point getFormPoint() { return pointForm; }
Point getPoint() { return modifiedPoint; }
void resetModifiedPoint() {
modifiedPoint = { pointForm.x, pointForm.y }; }
void setModifiedPoint(Point newPoint) { modifiedPoint = newPoint; }
private:
Point pointForm; /* ORIGINAL point from LimbForm. */
Point modifiedPoint; /* NOT point MODIFIER. It's the fully modified point. Saved to DB. */
bool isAnchor;
/* Data about the CONNECTED limb. */
int connectedLimbId;
int anchorJointIndex; /* This is the anchor joint of the CHILD limb. */
int id;
};
Point getRotatedPoint(Point anchorPoint, Point pointToRotate, int rotationAngle) {
/* Convert to radians. */
double angleRad = rotationAngle * (M_PI / 180);
/* Translate point to rotate to origin. */
double translatedX = pointToRotate.x - anchorPoint.x;
double translatedY = pointToRotate.y - anchorPoint.y;
/* Apply rotation. */
double rotatedX = translatedX * cos(angleRad) - translatedY * sin(angleRad);
double rotatedY = translatedX * sin(angleRad) + translatedY * cos(angleRad);
Point newPoint = {
static_cast<int>(round(rotatedX + anchorPoint.x)),
static_cast<int>(round(rotatedY + anchorPoint.y))
};
return newPoint;
}
/*
* Minimalistic class from which useful Limb classes will derive for their objects.
* Every Limb object must be stored in the database. As soon as it exists it must have an ID.
*
* Maybe "attack" should be "strength".
* "Intelligence" can affect how precisely you hit a limb, vs how much the damage is spread around.
* Certain Low-intelligence limbs can create a special power which spreads damage around intentionally.
* Intelligence raises your chances of hitting at all. Or hitting the correct target.
* Intelligence also raises your chance of being missed (make whole limb twirl around on a joint-pivot or something?)
* Position is where the Limb is located in any Character or Suit that's holding it.
*
*
*
* Limb should have a LimbForm, and modifiers.
* Modifiers cannot be larger than the original.
*
* Let a Limb take a Form as its constructor... and a 2nd consrtructor which also takes an ID? Or only an ID (and gets the Form based on slug?)?
*
* Limb object should take a Form as its basic stats, and have modifiers.
* Only the modifiers are saved to the DB.
* The Form is retrieved fresh from the definition every time the Limb is constructed/instantiated.
*
* Limb textures will be destroyed by the screens handling them.
*/
export class Limb {
public:
/* constructor for NEW Limb. */
Limb(LimbForm form) : form(form) {
name = form.name; /* Name can POSSIBLE be changed (no plans for this yet) */
hpMod = 0;
strengthMod = 0;
speedMod = 0;
intelligenceMod = 0;
position = Point(50, 95);
lastPosition = Point(0, 0);
isAnchor = false;
rotationPointSDL = SDL_Point(0, 0);
/* get Limb texture */
UI& ui = UI::getInstance();
SDL_Surface* limbSurface = IMG_Load(form.texturePath.c_str());
texture = SDL_CreateTextureFromSurface(ui.getMainRenderer(), limbSurface);
SDL_FreeSurface(limbSurface);
/* TO DO: ERROR HANDLING FOR TEXTURE. */
/* Build actual Joints from the LimbForm. */
for (Point& jointPoint : form.jointPoints) {
/* new Limb constructor (must later accomadate existing joints from existing Limbs. */
joints.emplace_back(jointPoint);
}
drawRect = { 0, 0, 0, 0 };
rotationAngle = 0;
SDL_QueryTexture(texture, NULL, NULL, &textureWidth, &textureHeight);
setAnchorJointId();
drawOrder = -1;
}
/* Constructor to rebuild a ROAMING limb from DB.
*/
Limb(int id, LimbForm form, Point position, vector<Joint> joints, int drawOrder = -1) :
id(id), form(form), position(position), joints(joints), drawOrder(drawOrder)
{
lastPosition = Point(0, 0);
isAnchor = false;
name = form.name;
/*
* Get the TEXTURE.
* Get the JOINTS.
*/
/* get Limb texture */
UI& ui = UI::getInstance();
SDL_Surface* limbSurface = IMG_Load(form.texturePath.c_str());
texture = SDL_CreateTextureFromSurface(ui.getMainRenderer(), limbSurface);
SDL_FreeSurface(limbSurface);
/* TO DO: ERROR HANDLING FOR TEXTURE. */
drawRect = { 0, 0, 0, 0 };
rotationAngle = 0;
SDL_QueryTexture(texture, NULL, NULL, &textureWidth, &textureHeight);
setAnchorJointId();
}
/* */
void setFlipped(bool flip) { flipped = flip; }
void flip() { flipped = !flipped; }
Point getPosition() { return position; }
Point getLastPosition() { return lastPosition; }
LimbForm getForm() { return form; }
int getDrawOrder() { return drawOrder; }
string getName() { return name; }
string getTexturePath() { return form.texturePath; }
int getCharacterId() { return characterId; }
string getMapSlug() { return mapSlug; }
int getId() { return id; }
void setDrawOrder(int newDrawOrder) { this->drawOrder = newDrawOrder; }
void setId(int id) { this->id = id; }
void setName(string newName) { name = newName; }
void setCharacterId(int id) { characterId = id; }
void setMapSlug(string newSlug) { mapSlug = newSlug; }
void setPosition(Point newPosition) { position = newPosition; }
void setLastPosition(Point newPosition) { lastPosition = newPosition; }
/* GET the FORM (default) values PLUS the modifiers (which can be negative) */
int getHP() { return form.hp + hpMod; }
int getStrength() { return form.strength + strengthMod; }
int getSpeed() { return form.speed + speedMod; }
int getIntelligence() { return form.intelligence + intelligenceMod; }
int getRotationAngle() { return rotationAngle; }
int getHpMod() { return hpMod; }
int getStrengthMod() { return strengthMod; }
int getSpeedMod() { return speedMod; }
int getIntelligenceMod() { return intelligenceMod; }
SDL_Rect& getDrawRect() { return drawRect; }
SDL_Texture* getTexture() { return texture; }
bool getIsAnchor() { return isAnchor; }
bool getIsFlipped() { return flipped; }
/* SET the modifiers. */
int modifyStrength(int mod) {
modifyAttribute(strengthMod, form.strength, mod);
return getStrength(); }
int modifySpeed(int mod) {
modifyAttribute(speedMod, form.speed, mod);
return getSpeed(); }
int modifyIntelligence(int mod) {
modifyAttribute(intelligenceMod, form.intelligence, mod);
return getIntelligence(); }
/* HP is different. It can go down to 0, but can still only be boosted by 1/2. */
int modifyHP(int mod) {
hpMod += mod;
/* Check if updated attributeMod goes beyond limit */
if (hpMod > form.hp / 2) {
hpMod = form.hp / 2; }
return getHP();
}
void modifyAttribute(int& attributeMod, int formAttribute, int modMod) {
attributeMod += modMod;
/* Check if updated attributeMod goes beyond limit */
if (attributeMod > formAttribute / 2) {
attributeMod = formAttribute / 2; }
else if ((attributeMod * -1) > formAttribute / 2) { /* check limit for detriment too */
attributeMod = formAttribute / (-2); }
}
/* This can be MAP position or DRAW position. */
void move(Point newPosition) {
lastPosition = position;
position = newPosition; }
vector<Joint>& getJoints() { return joints; }
bool isEquipped() {
if (getIsAnchor()) {
return true; }
for (Joint& joint : joints) {
if (!joint.isFree()) { return true; } }
return false;
}
void setAnchor(bool isAnchor = true) {
this->isAnchor = isAnchor; }
/* TO DO: This must also remove the limb id from the character's drawLimbList. */
void unEquip() {
for (Joint& joint : joints) {
joint.setAnchor(false);
joint.detachLimb();
joint.resetModifiedPoint();
}
isAnchor = false;
rotationAngle = 0;
}
Joint& getAnchorJoint() {
for (Joint& joint : joints) {
if (joint.getIsAnchor()) {
return joint; }
}
/* THIS IS NOT SAFE. */
return joints[0];
}
void setAnchorJointId() {
for (int i = 0; i < joints.size(); ++i) {
Joint& joint = joints[i];
if (joint.getIsAnchor()) {
anchorJointId = i;
return;
}
}
anchorJointId = -1;
}
int getAnchorJointId() {
return anchorJointId;
}
void setDrawRect(SDL_Rect drawRect) {
this->drawRect = drawRect; }
//void setRotationAngle(int rotationAngle) { this->rotationAngle = normalizeAngle(rotationAngle); }
void resetRotationAngle() {
rotationAngle = 0;
for (Joint& joint : joints) {
joint.resetModifiedPoint();
}
}
int rotate(int angleIncrement) {
rotationAngle = normalizeAngle(rotationAngle + angleIncrement);
/* If this LIMB has no ANCHOR JOINT then it's the anchor limb, so rotate on the center instead of on a joint. */
Point anchorPoint =
//getAnchorJointId() < 0 ? joint.getPoint() : /* THIS is to test the getRotatedPoint */
getAnchorJointId() < 0 ? Point(textureWidth / 2, textureHeight / 2) :
getAnchorJoint().getFormPoint();
/* Now update all the joint points (except the anchor point). */
for (Joint& joint : joints) {
if (!joint.getIsAnchor()) {
joint.setModifiedPoint(getRotatedPoint(anchorPoint, joint.getFormPoint(), rotationAngle));
}
}
return rotationAngle;
}
bool hasFreeJoint() {
for (Joint& joint : joints) {
if (joint.isFree()) { return true; } }
return false;
}
int getNextFreeJointIndex(int startingIndex = 0) {
for (int i = startingIndex; i < joints.size(); ++i) {
if (joints[i].isFree()) {
return i;
}
}
return -1;
}
vector<int> getFreeJointIndexes() {
vector<int> indexes;
for (int i = 0; i < joints.size(); ++i) {
if (joints[i].isFree()) {
indexes.push_back(i);
}
}
return indexes;
}
/*
* When the player has loaded a limb which is the CHILD OF (connected to) this limb,
* we call this function on this limb and shift the location of the child (limbId).
*/
bool shiftJointOfLimb(int limbId) {
/* again, we're using the index but must replace with actual limbId. */
/* Make sure there is a free joint. (maybe useless?) */
if (!hasFreeJoint()) { return false; }
/* Get the current joint index. */
int oldJointIndex = -1;
int newJointIndex = -1;
int anchorJointIndex = -1;
for (int i = 0; i < joints.size(); i++) {
if (joints[i].getConnectedLimbId() == limbId) {
anchorJointIndex = joints[i].getChildLimbAnchorJointIndex();
oldJointIndex = i;
break; } }
if (oldJointIndex < 0) { return false; }
/* Find the next available joint index. Start ABOVE the current one. */
for (int i = oldJointIndex + 1; i < joints.size(); ++i) {
if (joints[i].isFree()) {
newJointIndex = i;
break; } }
/* If we didn't find a free joint above the old one, start at the beginning. */
if (newJointIndex < 0) {
for (int i = 0; i < oldJointIndex; ++i) {
if (joints[i].isFree()) {
newJointIndex = i;
break; } } }
if (newJointIndex >= 0 && oldJointIndex >= 0 && anchorJointIndex >= 0) {
/* make the switch. */
joints[oldJointIndex].detachLimb();
joints[newJointIndex].connectLimb(limbId, anchorJointIndex);
return true;
}
return false;
}
/*
* In the character creation screen, when the user wants to change which joint
* the limb uses to anchor itself to its parent limb, this function does that.
* Cycles through joints, and if one is available we do the switch.
* Returns boolean indicating whether a switch took place.
*/
bool shiftAnchorLimb() {
if (!isEquipped() || joints.size() == 1) { return false; }
int oldAnchorId = -1;
int newAnchorId = -1;
/* Get the old anchor id. */
for (int i = 0; i < joints.size(); ++i) {
if (joints[i].getIsAnchor()) {
oldAnchorId = i;
break;
}
}
if (oldAnchorId < 0) { return false; }
/* Cycle through joints ABOVE the oldAnchorId. */
if (oldAnchorId < joints.size() - 1) {
for (int i = oldAnchorId + 1; i < joints.size(); ++i) {
if (joints[i].isFree()) {
newAnchorId = i;
break;
}
}
}
/* If that didn't work, start from the beginning. */
if (newAnchorId < 0) {
for (int i = 0; i < oldAnchorId; ++i) {
if (joints[i].isFree()) {
newAnchorId = i;
break;
}
}
}
/* If we found both new and old anchor positions, do the switch. */
if (oldAnchorId >= 0 && newAnchorId >= 0) {
joints[newAnchorId].setAnchor(true);
joints[oldAnchorId].setAnchor(false);
/* Now that we did the switch, update the joint points based on the rotation angle on the NEW anchor joint. */
if (rotationAngle != 0) {
int totalAngle = rotationAngle;
resetRotationAngle();
rotate(totalAngle);
}
return true;
}
return false;
}
/*
* TO DO: Maybe we should replace ALL Point objects with SDL_Points to avoid using this function so often?
* Or just for the Joint Points.
*/
void setRotationPointSDL() {
setAnchorJointId();
if (getAnchorJointId() < 0) {
rotationPointSDL = SDL_Point(textureWidth/2, textureHeight/2); }
else {
Point anchorPoint = getJoints()[getAnchorJointId()].getPoint();
rotationPointSDL = SDL_Point(anchorPoint.x, anchorPoint.y); }
}
SDL_Point getRotationPointSDL() { return rotationPointSDL; }
void draw(UI& ui, bool drawJoints = false) {
/* rotationPoint should already be an SDL_Point in the Joint object.
* My Point object is redundant.
* WAIT... MAYBE NOT.
* There is no "rotation point", there are just joint points or else NULL.
* Further consideration is necessary.
* Maybe I do need a rotationPoint member which holds an SDL_Point pointer... GOOD IDEA.
*/
SDL_RenderCopyEx(
ui.getMainRenderer(),
getTexture(),
NULL, &getDrawRect(),
getRotationAngle(), &rotationPointSDL, SDL_FLIP_NONE
);
//if (drawJoints) { drawJoints(limb, ui); } /* For debugging. */
}
protected:
LimbForm form;
string name;
string mapSlug;
int hpMod;
int strengthMod;
int speedMod;
int intelligenceMod;
bool flipped = false;
SDL_Texture* texture = NULL;
Point position;
Point lastPosition;
vector<Joint> joints;
bool isAnchor;
SDL_Rect drawRect;
int rotationAngle;
int textureWidth;
int textureHeight;
int characterId;
int id;
SDL_Point rotationPointSDL;
int anchorJointId;
int drawOrder;
};
/*
* Very minimal parent class.
* Different Screen modules will extend this to be useful in their environments.
* We can't hold a vector of Limb objects here, because the derived Character classes must hold similarly derived Limb objects.
*
* Maybe Character should hold a vector of Limbs, and its derived object can have a function to turn those Limbs into derived Limbs.
*
* REMOVE TEXTURE.
* Only screen-specific derivatives should have textures, created from limbs.
* In fact, only the Map has a texture, since it's displayed as a collection of limbs everywhere else.
*
* I don't need MAP here, because MAP will only exist on the MapScreen screen, so it doesn't need derived classes.
* The factories will include a Map factory, just to retrieve the basic Map data and send it to the MapScreen screen.
*
* Maybe the limbs vector(s) must be smart pointers.
*/
export class Character {
public:
Character() : anchorLimbId(-1) {}
~Character() { }
Character(CharacterType characterType) :
characterType(characterType), anchorLimbId(-1) {}
string getName() { return name; }
void setName(string newName) { name = newName; }
void setType(CharacterType type) { characterType = type; }
int getType() { return characterType; }
void setId(int id) { this->id = id; }
int getId() { return id; }
vector<Limb>& getLimbs() { return limbs; }
bool equipLimb(int limbId);
void clearSuit();
void setRotationPointsSDL();
void setAnchorJointIDs();
Limb& getLimbById(int id);
void addLimb(Limb& newLimb) { limbs.emplace_back(newLimb); }
void unEquipLimb(int limbId);
int getParentLimbId(int childLimbId);
vector<tuple<int, int, bool>> getEquippedJointsData(int limbToSkipId);
int getAnchorLimbId() { return anchorLimbId; }
Limb& getAnchorLimb() { return getLimbById(anchorLimbId); }
tuple<int, int> getLimbIdAndJointIndexForConnection(int limbIdToSearch, int limbIdToExclude = -1);
void setAnchorLimbId(int newId) { anchorLimbId = newId; }
void setChildLimbDrawRects(Limb& parentLimb, UI& ui);
bool shiftChildLimb(int childLimbId);
vector<Limb> getEquippedLimbs();
vector<int>& getDrawLimbIDs() { return drawLimbListIDs; }
void getChildLimbsRecursively(Limb& parentLimb, vector<Limb>& childLimbs);
/*
* Ultimately builds the vector of the indexes of the limbs which are equipped,
* and therefore need to be drawn. Builds that vector according to their draw order.
* The order in which they are to be drawn.
* This allows any screen's draw function to get an easy list of indexes to
* quickly grab each limb to draw, during each frame.
*/
void buildDrawLimbList() {
drawLimbListIDs = {};
sort(limbs.begin(), limbs.end(), compareDrawOrder);
int drawOrder = 0;
for (Limb& limb : limbs) {
if (limb.isEquipped()) {
addToDrawLimbList(limb.getId());
limb.setDrawOrder(drawOrder);
++drawOrder;
}
else if(limb.getDrawOrder() >= 0) {
limb.setDrawOrder(-1);
}
}
setLimbDrawOrder(drawLimbListIDs);
}
/* Takes an ID (from DB) of a limb, adds it to the list of IDs to draw,
* then sets the list of vectors to draw.
*/
void addToDrawLimbList(int limbId) {
drawLimbListIDs.push_back(limbId);
setLimbDrawOrder(drawLimbListIDs);
}
/* Takes the list of Database IDs, sets the list of vectors which we use to actually draw. */
void setLimbDrawOrder(vector<int> limbIdsInDrawOrder) {
drawLimbListIDs = limbIdsInDrawOrder;
drawLimbListIndexes = {};
drawLimbListIndexes.resize(drawLimbListIDs.size());
for (int i = 0; i < drawLimbListIDs.size(); ++i) {
for (int k = 0; k < limbs.size(); ++k) {
if (limbs[k].getId() == drawLimbListIDs[i]) {
drawLimbListIndexes[i] = k;
}
}
}
}
void setLimbDrawOrder() { setLimbDrawOrder(drawLimbListIDs); }
vector<int> getDrawLimbIndexes() { return drawLimbListIndexes; }
void checkChildLimbsForAvatarBoundaries(Limb& parentLimb, AvatarDimensionsStruct& dimStruct);
SDL_Texture* createAvatar();
protected:
CharacterType characterType;
int id;
int anchorLimbId; /* Currently using INDEX for dev purposes... will replace with actual DB ID. */
vector<Limb> limbs;
string name;
vector<int> drawLimbListIDs;
vector<int> drawLimbListIndexes;
};
/*
* CHARACTER CLASS FUNCTIONS.
* ____ _ _
* / ___| |__ __ _ _ __ __ _ ___| |_ ___ _ __
* | | | '_ \ / _` | '__/ _` |/ __| __/ _ \ '__|
* | |___| | | | (_| | | | (_| | (__| || __/ |
* \____|_| |_|\__,_|_| \__,_|\___|\__\___|_|
* / ___| | __ _ ___ ___
* | | | |/ _` / __/ __|
* | |___| | (_| \__ \__ \
* \____|_|\__,_|___/___/_ _
* | ___| _ _ __ ___| |_(_) ___ _ __ ___
* | |_ | | | | '_ \ / __| __| |/ _ \| '_ \/ __|
* | _|| |_| | | | | (__| |_| | (_) | | | \__ \
* |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/
*/
/*
* When drawing the avatar, we call this function to see how far the limbs' textures reach in each direction.
* AnchorLimb must set its boundaries before calling this recursive function on its child limbs.
* AnchorLimb x,y must be set to 0,0.
*/
void Character::checkChildLimbsForAvatarBoundaries(Limb& parentLimb, AvatarDimensionsStruct& dimStruct) {
/* Check the dimensions of this limb. */
SDL_Rect drawRect = parentLimb.getDrawRect();
int tWidth;
int tHeight;
SDL_QueryTexture(parentLimb.getTexture(), NULL, NULL, &tWidth, &tHeight);
/* Now we actually check the dimensions. */
if (parentLimb.getRotationAngle() == 0) {
/* The limb is NOT rotated. Check the normal dimensions. */
if (drawRect.x < dimStruct.leftmost) {
dimStruct.leftmost = drawRect.x;
}
else if ((drawRect.x + tWidth) > dimStruct.rightmost) {
dimStruct.rightmost = drawRect.x + tWidth;
}
if (drawRect.y < dimStruct.topmost) {
dimStruct.topmost = drawRect.y;
}
else if ((drawRect.y + tHeight) > dimStruct.bottommost) {
dimStruct.bottommost = drawRect.y + tHeight;
}
}
else {
/* Limb is rotated. We must rotate each corner and check to see if it extends the bounds. */
Point anchorPoint = parentLimb.getAnchorJoint().getPoint();
int rotationAngle = parentLimb.getRotationAngle();
Point topLeftPoint = Point(drawRect.x, drawRect.y);
Point topRightPoint = Point(drawRect.x + drawRect.w, drawRect.y);
Point bottomLeftPoint = Point(drawRect.x, drawRect.y + drawRect.h);
Point bottomRightPoint = Point(drawRect.x + drawRect.w, drawRect.y + drawRect.h);
/* Make a vector of the points so we can do a loop to check if they extend the boundaries. */
vector<Point> rotatedPoints = {
getRotatedPoint(anchorPoint, topLeftPoint, rotationAngle),
getRotatedPoint(anchorPoint, topRightPoint, rotationAngle),
getRotatedPoint(anchorPoint, bottomLeftPoint, rotationAngle),
getRotatedPoint(anchorPoint, bottomRightPoint, rotationAngle)
};
for (Point point : rotatedPoints) {
if (point.x < dimStruct.leftmost) {
dimStruct.leftmost = point.x;
}
else if (point.x > dimStruct.rightmost) {
dimStruct.rightmost = point.x;
}
if (point.y < dimStruct.topmost) {
dimStruct.topmost = point.y;
}
else if (point.y > dimStruct.bottommost) {
dimStruct.bottommost = point.y;
}
}
}
/* Now check each connected limb recursively. */
for (Joint& joint : parentLimb.getJoints()) {
int connectedLimbId = joint.getConnectedLimbId();
if (connectedLimbId >= 0) {
checkChildLimbsForAvatarBoundaries(getLimbById(connectedLimbId), dimStruct);
}
}
}
/*
* This function draws the limbs to an offscreen texture as an avatar, which it returns.
* First we cycle through the equipped limbs and find out how high and wide the drawn avatar will be.
* We take into account the fact that rotating a texture will extend the boundaries.
* We use that information to build a texture, then draw onto it and return it.
*/
SDL_Texture* Character::createAvatar() {
UI& ui = UI::getInstance();
Limb& anchorLimb = getAnchorLimb();
int tWidth, tHeight;
SDL_QueryTexture(anchorLimb.getTexture(), NULL, NULL, &tWidth, &tHeight);
/* Set drawRects of all equipped limbs from starting point of 0 for calculating. */
anchorLimb.setDrawRect({ 0, 0, tWidth, tHeight });
setChildLimbDrawRects(anchorLimb, ui);
/* AvatarDimensionsStruct will hold the information about the avatar dimensions as we
* recursively cycle through the limbs and read the coordinates of their textures. */
AvatarDimensionsStruct dimStruct = AvatarDimensionsStruct();
checkChildLimbsForAvatarBoundaries(anchorLimb, dimStruct);
/* The search is finished. Set the final dimensions based on the boundaries. */
dimStruct.avatarWidth = dimStruct.rightmost - dimStruct.leftmost;
dimStruct.avatarHeight = dimStruct.bottommost - dimStruct.topmost;
dimStruct.greaterDimension = dimStruct.avatarHeight > dimStruct.avatarWidth ? dimStruct.avatarHeight : dimStruct.avatarWidth;
/* Now build the actual avatar.
* We will draw (render) the limb textures to an off-screen texture,
* then return that texture as the avatar.
* Some work is done to ensure transparency (setting the blend mode, using the right pixel format, clearing with 0 alpha).
*/
SDL_Renderer* renderer = ui.getMainRenderer();
/* Create offscreen texture where we will draw the avatar. */
SDL_Texture* offscreenTexture = SDL_CreateTexture(
renderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET,
dimStruct.greaterDimension, dimStruct.greaterDimension);
if (offscreenTexture == NULL) {
cout << "TEXTURE ERROR\n";
SDL_Log("Failed to create offscreen texture: %s", SDL_GetError());
return NULL;
}
/* Set the blend mode of the offscreen texture (to allow transparency). */
SDL_SetTextureBlendMode(offscreenTexture, SDL_BLENDMODE_BLEND);
/* Set the render target to the off-screen texture, and clear with transparent color. */
SDL_SetRenderTarget(renderer, offscreenTexture);
/* Enable blending for the renderer. */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); /* 0 alpha means transparent. */
SDL_RenderClear(renderer);
/* Reset the drawRects (giving anchorLimb the correct offset) and draw limbs for the avatar . */
anchorLimb.setDrawRect({
0 - dimStruct.leftmost,
0 - dimStruct.topmost,
tWidth,
tHeight
});
/* Prepare points, rects, and draw order for drawing. */
setRotationPointsSDL();
setChildLimbDrawRects(anchorLimb, ui);
buildDrawLimbList();
for (int index : getDrawLimbIndexes()) {
Limb& limbToDraw = getLimbs()[index];
/* Ensure limb texture supports blending. */
SDL_SetTextureBlendMode(limbToDraw.getTexture(), SDL_BLENDMODE_BLEND);
limbToDraw.draw(ui);
}