-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMapScreen.ixx
1366 lines (1161 loc) · 44.8 KB
/
MapScreen.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
/*
*
* __ __ ____
* | \/ | __ _ _ __ / ___| ___ _ __ ___ ___ _ __
* | |\/| |/ _` | '_ \ \___ \ / __| '__/ _ \/ _ \ '_ \
* | | | | (_| | |_) | ___) | (__| | | __/ __/ | | |
* |_| |_|\__,_| .__/ |____/ \___|_| \___|\___|_| |_|
* |_|
*
* Where the character roams the open world (or a dungeon or building) seeking limbs, fighting NPCs, and meeting friendly NPCs.
*
*
*
* The MAP has a vector of LANDMARK objects.
* We'll create an ENTRANCE and EXIT... but we need some way to access them explicitly
*
*
* Buildings will turn their underlying Block objects into WALL objects... EXCEPT for the entrance.
* So when a player is ON a building block, they "enter" the building (load the building map)
* But this will only be possible on the entrance block, because it's the only FLOOR.
*
*
* MAP vs MAP SCREEN
*
* The MAP contains grid data about the map itself, its landmarks, and characters and limbs on the map.
* None of this should be affected by changes to the display (zoom in/out, or which location we're viewing).
*
* The MAP SCREEN controls the user's interaction with the map, and our view onto the map.
*
*
* ANIMATING MOVES:
*
* -- Player Move Animation is different from NPC Move Animation
* ----- Animation Lock
* ----- enum AnimationTurnType { Player, NPC }
* ----- Poll_Event removes the events from the que... so we should STILL POLL, but don't handle the event during animation
* ----- set a countdown of 10 (10 frames) which also moves player's DRAW position and each block's DRAW position 10% per tick
* ---------- At the END of the countdown, THEN we move the player
* ---------- LIMBS and CHARACTERS have "previousBlock" X and Y and we do "increment percent" * countdown FROM the PREVIOUS to the current XY
*
* ----- I only need to animate the CHARACTER when they're close to the edge. Otherwise I simply animate the map.
* ----- When animating the map, I need to add extra blocks and rows.
*
* -- Extra SPEED will allow SOME NPCs to move multiple blocks at a time!
* -- This is made possible by storing PREVIOUS BLOCK (instead of just letting the draw functions increment by one block automatically)
* -- The PLAYER CHARACTER can ALSO move in multiple blocks IF they have enough speed.
* ----- They can SET how many moves they WANT to make just by pressing that number... and it stays until they change it!
* ----- They can also click on the block they want to go to, and they'll move as far in that direction as their speed allows.
*
* --- When the PLAYER has that speed, they just get MULTIPLE TURNS before the NPCs get to move.
* THAT WAY we only have to animate ONE BLOCK at a time.
*
*
* NEXT:
*
* ----- LIMBS roaming around.
* ---------- Get a list of LimbForms from the FormFactory
* ---------- Turn them into a vector of Limb Objects
* ---------- Make them MapLimb objects actually, with an x/y position
* --------------- the Position attribute can be used for Position in the MAP, when NOT part of a character!
* --------------- ...or maybe not... because we need to animate with lastPosition...
* --------------- ...or maybe we just add lastPosition to the main class anyway, since it will be useful in battles when you lose a limb and have to travel to New Position?
* ----- Introduce TURNS
* ---------- After the player moves, then we check if they hit (A) NPCs (B) Limbs (C) Landmarks.
* ---------- Each landmark has a vector of int pairs (Point struct... not actually a point)
* ----- JSON for other landmarks.
* ----- Paths between other landmarks.
* ---------- Side-paths.
* ----- NPCs roaming around, defined in the JSON
* ----- SQLite.
* ----- Move Character to its own module and make a Character Decorator here to add map-specific attributes.
*
*
* TO DO: when we implement ZOOM functionality.
* Animation increments are BETTER but not necessarily precise enough.
* They're good for every screen width & xViewRes I've tried.
* But there might be some combos where it goes to quick or too fast.
*
* setViewResAndBlockWidth() function decides this. RETURN TO IT LATER and make it better.
*
* TO DO: After drawing all the tiles on a map (the first time) we can add VARIATION tiles by drawing NEW PATHS
* with wall_002 and wall_003.
* Every map should have three walls and three floors, plus a path (floor) and a border (wall).
* The BLOCK objects will SAVE (in the DB) which tile they own so we don't need to calculate it again.
*
* TO DO: Wall tiles should be a little bigger, so the wall-thing (tree, or whatever) stick ABOVE the higher tile.
* ALSO randomly paint them as FLIP (horizontal) or NO_FLIP ( use rand % 2 to get each FLIP or NO FLIP... save it somewhere... maybe the DB)
*
*
*
* COLLISION ANIMATIONS:
*
* --- There can be a GENERIC COLLISION ANIMATION.
* --- Whenever there is a collision with NPCs or Limbs, we can just mark the Point position and draw those animations
*
*
* Destroy all textures for PANELS and LIMBS (on every screen).
*/
module;
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_ttf.h"
export module MapScreen;
import <stdio.h>;
import <string>;
import <iostream>;
import <vector>;
import <cstdlib>;
import <time.h>;
import <unordered_map>;
import FormFactory;
import TypeStorage;
import GameState;
import Resources;
import CharacterClasses;
import LimbFormMasterList;
import UI;
import MapClasses;
import Database;
using namespace std;
enum class AnimationType { Player, NPC, Collision, None }; /* This is about whose TURN it is. */
struct AcquiredLimb {
SDL_Texture* texture;
int countdown;
int rotationAngle;
SDL_Rect diffRect;
AcquiredLimb(SDL_Texture* texture, int countdown, int rotationAngle, SDL_Rect diffRect) :
texture(texture), countdown(countdown), rotationAngle(rotationAngle), diffRect(diffRect) {}
};
/* Map Screen class: where we navigate worlds, dungeons, and buildings. */
export class MapScreen {
public:
/*
* constructor:
*
* For now we are sending in the WIDTH.
* Later we'll send in the ID of the database object
* and/or the reference to a JSON file.
* mapWidth refers to the number of blocks to CREATE.
*/
MapScreen(string mapSlug) {
UI& ui = UI::getInstance();
/* TEMPORARY MAP-CREATION SCHEME
*
* PHASE 1:
* 1) Get vanilla Forest Map every time.
* 2) Save the map immediately after creation.
*
* PHASE 2:
* 1) Check for EXISTING Forest Map.
* 2) LOAD that map from the database if it exists.
* 3) If it does NOT exist, do PHASE 1 steps.
*
* PHASE 3 comes after creating the BATTLE screen.
*/
mapForm = getMapFormFromSlug(mapSlug);
if (mapObjectExists(mapSlug)) {
/* Load existing map. */
cout << "TRYING to load MAP SCREEN 11111\n";
map = loadMap(mapSlug);
cout << "TRYING to load MAP SCREEN 22222\n";
map.setPlayerCharacter(loadPlayerMapCharacter());
updatePlayerMap(mapSlug);
}
else {
/* Create new map. */
map = Map(mapForm);
createNewMap(map);
updatePlayerMap(mapSlug);
/* get character texture (MUST DELETE AFTER WE START DRAWING RAW LIMBS ONTO THE MAP INSTEAD.) */
SDL_Surface* characterSurface = IMG_Load("assets/player_character.png");
SDL_Texture* characterTexture = SDL_CreateTextureFromSurface(ui.getMainRenderer(), characterSurface);
SDL_FreeSurface(characterSurface);
map.getPlayerCharacter().setTexture(characterTexture);
}
screenType = ScreenType::Map;
screenToLoadStruct = ScreenStruct(ScreenType::Menu, 0);
hBlocksTotal = mapForm.blocksWidth;
vBlocksTotal = mapForm.blocksHeight;
animationType = AnimationType::None;
showTitle = true;
titleCountdown = 140;
setViewResAndBlockWidth(ui);
setMaxDrawBlock();
setDrawStartBlock();
buildMapDisplay();
createTitleTexture(ui);
limbAngle = 0;
cout << "There are " << map.getRoamingLimbs().size() << " roaming limbs\n";
cout << "Player has " << map.getPlayerCharacter().getLimbs().size() << " limbs\n";
}
/* Destructor */
~MapScreen() {
SDL_DestroyTexture(floorTexture);
SDL_DestroyTexture(wallTexture);
SDL_DestroyTexture(titleTexture);
/* Destroy all the textures in the Landmarks */
for (int i = 0; i < map.getLandmarks().size(); i++) {
SDL_Texture* textureToDestroy = map.getLandmarks()[i].getTexture();
if (textureToDestroy) { SDL_DestroyTexture(textureToDestroy); } }
/* Destroy all the textures in the roamingLimbs */
vector<Limb>& roamingLimbs = map.getRoamingLimbs();
for (int i = 0; i < roamingLimbs.size(); i++) {
SDL_Texture* textureToDestroy = roamingLimbs[i].getTexture();
if (textureToDestroy) { SDL_DestroyTexture(textureToDestroy); } }
/* Destroy all the textures in the Characters */
SDL_DestroyTexture(map.getPlayerCharacter().getTexture());
SDL_DestroyTexture(wallTexture);
SDL_DestroyTexture(floorTexture);
}
int getAnimationIncrementPercent() { return animationIncrementFraction; }
int getAnimationCountdown() { return animationCountdown; }
void startAnimationCountdown(AnimationType iType);
void decrementCountdown();
ScreenType getScreenType() { return screenType; }
MapType getMapType() { return mapForm.mapType; }
MapForm mapForm;
void run();
bool checkLimbCollision();
vector<Limb> acquiredLimbs;
private:
ScreenType screenType;
ScreenStruct screenToLoadStruct;
void drawMap(UI& ui);
void drawBlock(UI& ui, Block& block, SDL_Rect targetRect);
void drawLandmarks(UI& ui);
void drawCharacters(UI& ui);
void drawPlayerCharacter(UI& ui);
void drawRoamingLimbs(UI& ui);
void drawAcquiredLimbs(UI& ui, int playerX, int playerY);
void draw(UI& ui, Panel& settingsPanel, Panel& gameMenuPanel);
bool moveLimb(Limb& roamingLimb);
void animateMapBlockDuringPlayerMove(SDL_Rect& rect, int blockPositionX, int blockPositionY);
void animateMovingObject(SDL_Rect& rect, int blockPositionX, int blockPositionY, Point lastPosition);
void handleEvent(SDL_Event& e, bool& running, Panel& settingsPanel, Panel& gameMenuPanel, GameState& gameState);
void handleMousedown(SDL_Event& e, bool& running, Panel& settingsPanel, Panel& gameMenuPanel);
void handleKeydown(SDL_Event& e);
void checkMouseLocation(SDL_Event& e, Panel& settingsPanel, Panel& gameMenuPanel);
void buildMapDisplay();
void rebuildDisplay(Panel& settingsPanel, Panel& gameMenuPanel);
void setDrawStartBlock();
void setMaxDrawBlock();
void setViewResAndBlockWidth(UI& ui);
void setScrollLimits();
int xViewRes; /* Horizontal Resolution of the screen ( # of blocks displayed across the top) */
int yViewRes; /* Vertical Resolution of the screen ( # of vertical blocks, depends on xViewRes) */
int blockWidth; /* actual pixel dimensions of the block. depends on horizontal resolution */
int vBlocksVisible;
int rightLimit;
int leftLimit;
int topLimit;
int bottomLimit;
int hBlocksTotal;
int vBlocksTotal;
bool animate;
int animationIncrementFraction = 20;
int animationCountdown;
int limbCollisionCountdown = 70;
int blockAnimationIncrement;
AnimationType animationType;
bool showTitle;
int titleCountdown;
Map map;
SDL_Texture* floorTexture = NULL;
SDL_Texture* wallTexture = NULL;
SDL_Texture* getWallTexture(int index) { return map.getWallTexture(index); }
SDL_Texture* getFloorTexture(int index) { return map.getFloorTexture(index); }
void createTitleTexture(UI& ui);
SDL_Texture* titleTexture;
SDL_Rect titleRect;
void raiseTitleRect() {
--titleRect.y; }
int getTitleBottomPosition() {
return titleRect.y + titleRect.h; }
void requestUp();
void requestDown();
void requestLeft();
void requestRight();
void moveCharacter(MapDirection direction);
int drawStartX = 0;
int drawStartY = 0;
int lastDrawStartX = 0;
int lastDrawStartY = 0;
int maxDrawStartX = 0;
int maxDrawStartY = 0;
int limbAngle;
vector<AcquiredLimb> acquiredLimbStructs;
/* still need looted wall texture, looted floor texture, character texture (this actually will be in character object).
* The NPCs (in a vactor) will each have their own textures, and x/y locations.
*/
};
/*
*
*
*
*
* MapScreen Functions
*
*
*
*
*/
/* Create the texture with the name of the game */
void MapScreen::createTitleTexture(UI& ui) {
Resources& resources = Resources::getInstance();
auto [incomingTitleTexture, incomingTitleRect] = ui.createTitleTexture(map.getName());
titleTexture = incomingTitleTexture;
titleRect = incomingTitleRect;
}
void MapScreen::buildMapDisplay() {
UI& ui = UI::getInstance();
SDL_Surface* mainSurface = ui.getWindowSurface();
setViewResAndBlockWidth(ui);
setMaxDrawBlock();
setDrawStartBlock();
vBlocksVisible = (mainSurface->h / blockWidth) + 1; /* adding one to fill any gap on the bottom. (Will always add 1 to xViewRes when drawing) */
/* make wall and floor textures (move into function(s) later) */
unordered_map<string, SDL_Color> colorsByFunction = ui.getColorsByFunction();
SDL_Surface* wallSurface = IMG_Load("assets/wall.png");
SDL_Surface* floorSurface = IMG_Load("assets/floor.png");
wallTexture = SDL_CreateTextureFromSurface(ui.getMainRenderer(), wallSurface);
floorTexture = SDL_CreateTextureFromSurface(ui.getMainRenderer(), floorSurface);
if (!floorTexture || !wallTexture) { cout << "\n\n ERROR! \n\n"; }
SDL_FreeSurface(wallSurface);
SDL_FreeSurface(floorSurface);
setMaxDrawBlock();
setScrollLimits();
}
/* get the maximum allowed map position of top left block on-screen. */
void MapScreen::setMaxDrawBlock() {
maxDrawStartX = static_cast<int>(map.getRows().size()) - xViewRes;
maxDrawStartY = static_cast<int>(map.getRows().size()) - yViewRes;
}
/* set when screen loads or resizes */
void MapScreen::setViewResAndBlockWidth(UI& ui) {
xViewRes = 18; /* LATER user can update this to zoom in or out. Function to update must also updated yViewRes */
/* get and set y resolution... must be updated whenever xViewRes is updated. PUT THIS IN FUNCTION LATER. */
blockWidth = ui.getWindowWidth() / xViewRes;
yViewRes = (ui.getWindowHeight() / blockWidth) + 1;
++xViewRes; /* give xViewRes an extra block so there's never blank space on the side of the screen (when half blocks get cut off. */
/* make animationIncrementFraction work based on blockWidth (preferably something fully divisible) */
if (animationIncrementFraction > blockWidth) {
animationIncrementFraction = blockWidth; }
else if (blockWidth % animationIncrementFraction > 3) {
/* do an algorithm to find something ALMOST divisible (it would be nice to prefer zero remainder) */
int fractionFinderIncrement = 1;
while (fractionFinderIncrement < blockWidth) {
int divisibleFractionFinder = animationIncrementFraction + fractionFinderIncrement;
if (blockWidth % (animationIncrementFraction + fractionFinderIncrement) <= 3) {
animationIncrementFraction = animationIncrementFraction + fractionFinderIncrement;
break;
} else if (blockWidth % (animationIncrementFraction - fractionFinderIncrement) <= 3) {
animationIncrementFraction = animationIncrementFraction - fractionFinderIncrement;
break;
}
++fractionFinderIncrement;
}
}
}
/*
* Beyond these limits of the map, the character scrolls across the screen, not the map.
*/
void MapScreen::setScrollLimits() {
rightLimit = maxDrawStartX + (xViewRes / 2);
leftLimit = xViewRes / 2;
topLimit = yViewRes / 2;
bottomLimit = maxDrawStartY + (yViewRes / 2);
}
/* Screen has been resized. Rebuild! */
void MapScreen::rebuildDisplay(Panel& settingsPanel, Panel& gameMenuPanel) {
UI& ui = UI::getInstance();
ui.rebuildSettingsPanel(settingsPanel, ScreenType::Map);
ui.rebuildGameMenuPanel(gameMenuPanel);
buildMapDisplay();
createTitleTexture(ui);
}
/* The main function of the module, containing the game loop.
* Multiple kinds of animations happen during game loop.
* MOVE animation is controlled by the animate boolean.
* Whenever the player moves, the map and/or characters are shifted, and this is called an animation.
* When the player has finished moving, the NPCs and LIMBs also move.
This is also controlled by the animate boolean, but differentiated by the AnimationType enum value.
*
* SPRITE ANIM is different. It ALWAYS happens.
* Limbs rotate back and forth.
* NPCs bounce up and down.
* These are controlled by spriteAnim ints, which go up and down.
*/
export void MapScreen::run() {
/* singletons */
GameState& gameState = GameState::getInstance();
UI& ui = UI::getInstance();
/* panels */
Panel settingsPanel = ui.createSettingsPanel(ScreenType::Map);
Panel gameMenuPanel = ui.createGameMenuPanel();
settingsPanel.setShow(false);
gameMenuPanel.setShow(true);
cout << "TRYING to load MAP 11111\n";
/*
* PANELS TO COME:
* * navigation panel
* * small horizontal panel with buttons to toggle settings panel and nav panel
* * mini-map
* * stats
*/
/* Timeout data */
const int TARGET_FPS = 120;
const int FRAME_DELAY = 1200 / TARGET_FPS; // milliseconds per frame
Uint32 frameStartTime; // Tick count when this particular frame began
int frameTimeElapsed; // how much time has elapsed during this frame
/* loop and event control */
SDL_Event e;
bool running = true;
animate = false;
/* Making the Limbs rotate; */
int spriteAnimMax = 15;
bool reverseSpriteAnimation = false;
vector<int> collidedLimbIDs; /* Contains the database IDs, not the vector indexes. */
cout << "TRYING to load MAP 55555\n";
while (running) {
/* Get the total running time(tick count) at the beginning of the frame, for the frame timeout at the end */
frameStartTime = SDL_GetTicks();
bool startNpcAnimation = false;
if (animate && animationCountdown < 1) {
/* counter has run out but animate is still true. */
bool landmarkCollided = false;
bool npcCollided = false;
bool limbCollided = false;
if (animationType == AnimationType::Player) {
/*
* Player animation has finished.
* Reset lastDrawStart values and check for collisions.
* If no collisions happened, start NPC animation.
*/
animate = false;
animationType = AnimationType::None;
lastDrawStartX = drawStartX;
lastDrawStartY = drawStartY;
/* check for collisions (animation is done, player is ON new block and/or NPCs have moved ONTO new blocks */
/* collisions with LANDMARK: */
MapCharacter& playerCharacter = map.getPlayerCharacter();
for (Landmark landmark : map.getLandmarks()) {
LandmarkCollisionInfo collisionInfo = landmark.checkCollision(playerCharacter.getPosition());
if (collisionInfo.hasCollided) {
cout << "HIT LANDMARK\n";
landmarkCollided = true;
if (collisionInfo.type == LandmarkType::Exit) {
cout << "EXITING\n";
running = false;
}
else if (collisionInfo.type == LandmarkType::Entrance) {
cout << "YOU CANNOT LEAVE THIS WAY\n";
/* TO DO: animate PUSHING the character OFF the entrance??? */
}
}
}
/* Collisions with NPCs */
/* Collisions with LIMBs */
checkLimbCollision();
if (!landmarkCollided && !npcCollided) {
/* start the NPC animation. */
startNpcAnimation = true;
}
}
else if (animationType == AnimationType::NPC) {
/* Deal with wrapping up the NPC animation. */
animate = false;
animationType = AnimationType::None;
/*
* Check for collisions again.
* This time check for collisions between Limbs, so they can become NPCs.
* But also check for Limbs or NPCs who moved onto the Player's block.
*/
checkLimbCollision();
}
}
/* Check for events in queue, and handle them(really just checking for X close now */
while (SDL_PollEvent(&e) != 0) {
if (
!animate && !startNpcAnimation && /* Drop any events during the animations. */
(e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_KEYDOWN) /* Actual events to respond to. */
) {
handleEvent(e, running, settingsPanel, gameMenuPanel, gameState);
}
}
/* Deal with showing the title. */
if (showTitle) {
if (titleCountdown > 0) {
--titleCountdown; }
else {
raiseTitleRect(); }
if (getTitleBottomPosition() < -1) {
showTitle = false; }
}
checkMouseLocation(e, settingsPanel, gameMenuPanel);
draw(ui, settingsPanel, gameMenuPanel);
/* Delay so the app doesn't just crash */
frameTimeElapsed = SDL_GetTicks() - frameStartTime; /* Calculate how long the frame took to process. */
/* Delay loop */
if (frameTimeElapsed < FRAME_DELAY) {
SDL_Delay(FRAME_DELAY - frameTimeElapsed); }
if (animate) { decrementCountdown(); }
if (startNpcAnimation) {
/* First move the NPCs and the Limbs */
// Move NPCs
// Move LIMBs
/*
*
*
* MOVE LIMBS
*
*
*/
for (Limb& limb : map.getRoamingLimbs()) {
moveLimb(limb); }
/* Save the new locations. */
updateLimbsLocation(map.getRoamingLimbs());
/* Then start the Animation of the movement. */
startAnimationCountdown(AnimationType::NPC);
startNpcAnimation = false;
}
/* check the sprite anim situation (wiggling Roaming Limbs, bouncing NPCs.). */
/* What is reverse sprint animation? Probably supposed to be reversed SPRITE animation. */
if (reverseSpriteAnimation) {
if (limbAngle > spriteAnimMax * -1) {
--limbAngle; }
else {
reverseSpriteAnimation = !reverseSpriteAnimation;
}
}
else {
if (limbAngle < spriteAnimMax) {
++limbAngle;
}
else {
reverseSpriteAnimation = !reverseSpriteAnimation; }
}
}
/* set the next screen to load */
gameState.setScreenStruct(screenToLoadStruct);
}
bool MapScreen::moveLimb(Limb& roamingLimb) {
bool moved = false;
Point currentPosition = roamingLimb.getPosition();
int pointX = currentPosition.x;
int pointY = currentPosition.y;
/* Get list of available blocks */
vector<Point> availablePositions;
vector<vector<Block>>& rows = map.getRows();
/* Check up. */
if (pointY > 0) {
if (rows[pointY - 1][pointX].getIsFloor()) {
availablePositions.push_back(Point(pointX, pointY - 1)); } }
/* Check down. */
if ( pointY < vBlocksTotal) {
if (rows[pointY + 1][pointX].getIsFloor()) {
availablePositions.push_back(Point(pointX, pointY + 1)); } }
/* Check left. */
if (pointX > 0) {
if (rows[pointY][pointX - 1].getIsFloor()) {
availablePositions.push_back(Point(pointX - 1, pointY)); } }
if (pointX < hBlocksTotal) {
if (rows[pointY][pointX + 1].getIsFloor()) {
availablePositions.push_back(Point(pointX + 1, pointY)); } }
if (availablePositions.size() > 0) {
Point& newPoint = availablePositions[rand() % availablePositions.size()];
roamingLimb.move(newPoint);
moved = true;
}
return moved;
}
/* The main DRAW function, which calls the more-specific Draw functions. */
void MapScreen::draw(UI& ui, Panel& settingsPanel, Panel& gameMenuPanel) {
unordered_map<string, SDL_Color> colorsByFunction = ui.getColorsByFunction();
/* draw panel(make this a function of the UI object which takes a panel as a parameter) */
SDL_SetRenderDrawColor(ui.getMainRenderer(), 0, 0, 0, 1);
SDL_RenderClear(ui.getMainRenderer());
if (!animate) { blockAnimationIncrement = 0; }
else {
/* Get the distance for the camera to travel in this iteration of the animation sequence. (used in multiple draw functions) */
int baseIncrement = (blockWidth / animationIncrementFraction);
blockAnimationIncrement = (baseIncrement * (animationIncrementFraction - animationCountdown)) + baseIncrement; }
drawMap(ui);
drawLandmarks(ui);
drawCharacters(ui);
if (showTitle) {
SDL_RenderCopyEx(ui.getMainRenderer(), titleTexture, NULL, &titleRect, 0, NULL, SDL_FLIP_NONE); }
gameMenuPanel.draw(ui);
settingsPanel.draw(ui);
SDL_RenderPresent(ui.getMainRenderer()); /* update window */
}
/* Draw buildings, exits, shrines, and loot boxes. */
void MapScreen::drawLandmarks(UI& ui) {
/* NOW draw all LANDMARKS */
/* This is SIMPLISTIC FOR NOW. Real buildings come LATER. (new algorithms for each building TYPE!! */
SDL_Rect targetRect = { 0, 0, blockWidth, blockWidth };
vector<Landmark>& landmarks = map.getLandmarks();
int lX = 0;
int lY = 0;
/* Draw any landmarks that are in bounds */
for (int i = 0; i < landmarks.size(); i++) {
Landmark& landmark = landmarks[i];
lX = landmark.getDrawX();
lY = landmark.getDrawY();
targetRect.w = blockWidth;
targetRect.h = blockWidth;
if (
lX >= drawStartX &&
lX <= (drawStartX + xViewRes) &&
lY >= drawStartY &&
lY <= (drawStartY + yViewRes)
) {
targetRect.x = (lX - drawStartX) * blockWidth;
targetRect.y = (lY - drawStartY) * blockWidth;
if (animate && animationType == AnimationType::Player) {
animateMapBlockDuringPlayerMove(targetRect, lX, lY); }
SDL_RenderCopyEx(
ui.getMainRenderer(),
landmark.getTexture(),
NULL, &targetRect,
0, NULL, SDL_FLIP_NONE);
}
}
}
/* Draw NPCs and player character */
void MapScreen::drawCharacters(UI& ui) {
/* cycle through the characters list and draw them */
/* ONLY draw the ones in the screen */
/*
* Draw Player Character last
* --- ACTUALLY... if the NPC moves onto YOU then they should be drawn OVER the player.
* (figure that out later)
*/
drawRoamingLimbs(ui);
drawPlayerCharacter(ui);
}
void MapScreen::drawPlayerCharacter(UI& ui) {
SDL_Rect characterRect = { 0, 0, blockWidth, blockWidth };
MapCharacter& playerCharacter = map.getPlayerCharacter();
int blockX = playerCharacter.getBlockX();
int blockY = playerCharacter.getBlockY();
characterRect.x = (blockX - drawStartX) * blockWidth;
characterRect.y = (blockY - drawStartY) * blockWidth;
/* Check if we are animating AND close to an edge.
* If close to a vertical edge, and moving vertically, animate the character.
* If close to a horizontal edge, and moving horizontally, animate the character.
* Player has different animation dynamics from the map blocks.
*/
if (animate && animationType == AnimationType::Player) {
animateMovingObject(characterRect, blockX, blockY, playerCharacter.getLastPosition()); }
SDL_RenderCopyEx(
ui.getMainRenderer(),
playerCharacter.getTexture(),
NULL, &characterRect,
0, NULL, SDL_FLIP_NONE
);
drawAcquiredLimbs(ui, characterRect.x, characterRect.y);
}
/*
* Call this within the drawPlayerCharacter function so we don't have to calculate the location again.
* Also, the acquired limbs can be considered part of the player character.
*/
void MapScreen::drawAcquiredLimbs(UI& ui, int playerX, int playerY) {
for (int i = acquiredLimbStructs.size() - 1; i >= 0; --i) {
AcquiredLimb& aLimbStruct = acquiredLimbStructs[i];
/* If the countdown is finished, or if the draw rect will be too small, remove this limb from the queue. */
if (aLimbStruct.countdown < 1 || aLimbStruct.diffRect.w < ((blockWidth - 3) * -1)) {
acquiredLimbStructs.erase(acquiredLimbStructs.begin() + i);
continue;
}
/*
* Increment the angle so it spins during the whole animation.
* Make the limb grow large quickly for the first part of the animation countdown.
* Then make the limb shrink more quickly.
* Size differentials are stored in the acquired limb struct.
* The actual rect is based on the character's current location block, altered by those differentials.
*/
aLimbStruct.rotationAngle += 7;
if (aLimbStruct.countdown > 25) {
/* Growing BIGGER slowly. */
aLimbStruct.diffRect.x -= 1;
aLimbStruct.diffRect.y -= 1;
aLimbStruct.diffRect.w += 2;
aLimbStruct.diffRect.h += 2;
}
else {
/* Growing SMALLER quickly. */
aLimbStruct.diffRect.x += 3;
aLimbStruct.diffRect.y += 3;
aLimbStruct.diffRect.w -= 6;
aLimbStruct.diffRect.h -= 6;
}
SDL_Rect limbRect = {
playerX + aLimbStruct.diffRect.x,
playerY + aLimbStruct.diffRect.y,
blockWidth + aLimbStruct.diffRect.w,
blockWidth + aLimbStruct.diffRect.h
};
SDL_RenderCopyEx(
ui.getMainRenderer(),
aLimbStruct.texture,
NULL, &limbRect,
aLimbStruct.rotationAngle, NULL, SDL_FLIP_NONE
);
--aLimbStruct.countdown;
}
}
/*
* During the animation of the Player Character moving from one block to another,
* if the map has to move, it must move in increments defined by blockAnimationIncrement,
* which is set during every frame of the animation.
* This function accepts a reference to the rect where we will draw the block (or limb, or NPC, or landmark texture)
* onto the screen, and the positions of the block, use those positions to tell the rect where it must draw the block (or &etc).
*/
void MapScreen::animateMapBlockDuringPlayerMove(SDL_Rect& rect, int blockPositionX, int blockPositionY) {
/* Shifting DOWN or UP. */
if (drawStartY > lastDrawStartY) {
rect.y = ((blockPositionY - lastDrawStartY) * blockWidth) - blockAnimationIncrement; }
else if (drawStartY < lastDrawStartY) {
rect.y = ((blockPositionY - lastDrawStartY) * blockWidth) + blockAnimationIncrement; }
/* Shifting RIGHT or LEFT. */
if (drawStartX > lastDrawStartX) {
rect.x = ((blockPositionX - lastDrawStartX) * blockWidth) - blockAnimationIncrement; }
else if (drawStartX < lastDrawStartX) {
rect.x = ((blockPositionX - lastDrawStartX) * blockWidth) + blockAnimationIncrement; }
}
/*
* When a character or limb moves while the map stays still, use this function to set the incremented position
* of the rect for each frame of the animation.
*/
void MapScreen::animateMovingObject(SDL_Rect& rect, int blockPositionX, int blockPositionY, Point lastPosition) {
int lastBlockX = lastPosition.x;
int lastBlockY = lastPosition.y;
/* are we close to a horizontal edge and moving horizontally? */
if (
blockPositionX > rightLimit ||
animationType == AnimationType::NPC || /* only check limits if the Player is being animated. */
(lastBlockX > rightLimit || blockPositionX < leftLimit || lastBlockX < leftLimit)
) {
/* are we moving left or moving right? */
if (blockPositionX > lastBlockX) {
/* we are moving right */
rect.x = ((lastBlockX - drawStartX) * blockWidth) + blockAnimationIncrement; }
else if (blockPositionX < lastBlockX) {
/* we are moving left */
rect.x = ((lastBlockX - drawStartX) * blockWidth) - blockAnimationIncrement; }
}
/* are we close to a vertical edge and moving vertically? */
if (
blockPositionY > bottomLimit ||
animationType == AnimationType::NPC || /* only check limits if the Player is being animated. */
(lastBlockY > bottomLimit || blockPositionY < topLimit || lastBlockY < topLimit)
) {
/* are we moving up or down? */
if (blockPositionY > lastBlockY) {
/* we are moving down */
rect.y = ((lastBlockY - drawStartY) * blockWidth) + blockAnimationIncrement; }
else if (blockPositionY < lastBlockY) {
/* we're moving up */
rect.y = ((lastBlockY - drawStartY) * blockWidth) - blockAnimationIncrement; }
}
}
/* The limbs must move. */
void MapScreen::drawRoamingLimbs(UI& ui) {
SDL_Rect limbRect = { 0, 0, blockWidth, blockWidth };
for (Limb& limb : map.getRoamingLimbs()) {
Point position = limb.getPosition();
int posX = position.x;
int posY = position.y;
/* skip limbs that are too far outside of the frame. (still draw them if they might fly onto the frame. */
if (posX < drawStartX - 5 || posX > drawStartX + xViewRes + 5 ||
posY < drawStartY - 5 || posY > drawStartY + yViewRes + 5
) { continue; }
limbRect.x = (posX - drawStartX) * blockWidth;
limbRect.y = (posY - drawStartY) * blockWidth;
if (animate) {
if (animationType == AnimationType::Player) {
animateMapBlockDuringPlayerMove(limbRect, posX, posY); }
else if (animationType == AnimationType::NPC) {
animateMovingObject(limbRect, posX, posY, limb.getLastPosition()); } }
SDL_RenderCopyEx(
ui.getMainRenderer(),
limb.getTexture(),
NULL, &limbRect,
limbAngle, NULL, SDL_FLIP_NONE
);
}
}
void MapScreen::drawBlock(UI& ui, Block& block, SDL_Rect targetRect) {
/* always draw floor. */
SDL_RenderCopyEx(
ui.getMainRenderer(),
getFloorTexture(block.getFloorTextureIndex()),
NULL, &targetRect,
0, NULL, SDL_FLIP_NONE);
/* Draw wall if it's not a floor. */
if (!block.getIsFloor()) {
SDL_RenderCopyEx(
ui.getMainRenderer(),
getWallTexture(block.getWallTextureIndex()),
NULL, &targetRect,
0, NULL,
block.getWallIsFlipped() ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE
);
}
else if (block.getIsPath()) {
int pathFlipOption = block.getPathFlipOption();
SDL_RenderCopyEx(
ui.getMainRenderer(),
map.getPathTexture(block.getPathTextureIndex()),
NULL, &targetRect,
block.getPathRotateAngle(),
NULL,
pathFlipOption == 2 ? SDL_FLIP_VERTICAL : pathFlipOption == 1 ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE
);
}
}
void MapScreen::drawMap(UI& ui) {
/*
* FOR EACH FRAME:
*
* Keep textures for each block type.
* Redraw them all each frame, but only the ones visible on-screen.
* This holds less data in memory (better than holding the entire map).
* So there is no TEXTURE of the whole MAP.
*
* During transition animations, I can start drawing the blocks off-screen,
* and only parts of them will be displayed. This will NOT give errors.
*/
vector<vector<Block>>& rows = map.getRows();
SDL_Rect targetRect = { 0, 0, blockWidth, blockWidth };
/* start with the TOP ROW that we want to draw, then work our way down. */
for (int y = drawStartY; y < drawStartY + yViewRes; ++y) {
vector<Block>& blocks = rows[y];
/* Start with the left-most BLOCK that we want to draw, then work our way across. */
for (int x = drawStartX; x < drawStartX + xViewRes; ++x) {
if (animate && animationType == AnimationType::Player) {