Simple tower defense tutorial, part 16: Adding new enemey types.
Originally I wanted to make this a post about balancing the game, but there is still a lack of enemies and rules to provide a good challenge. So, let's add some enemies first that have some special rules.
- ENEMY_TYPE_MINION - basic enemy we have right now. No special rules, just has hitpoints.
- ENEMY_TYPE_RUNNER - faster than the minion, but has less hitpoints.
- ENEMY_TYPE_SHIELD - enemy with a shield that does damage reduction. The shield has hitpoints and can be destroyed, which converts the enemy to a shieldless unit.
- ENEMY_TYPE_BOSS - a big enemy with a lot of hitpoints.
The first change we do is to add new ids to the game as well as defining a weapon for the minion.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 #include <stdlib.h>
  5 #include <math.h>
  6 
  7 //# Variables
  8 GUIState guiState = {0};
  9 GameTime gameTime = {
 10   .fixedDeltaTime = 1.0f / 60.0f,
 11 };
 12 
 13 Model floorTileAModel = {0};
 14 Model floorTileBModel = {0};
 15 Model treeModel[2] = {0};
 16 Model firTreeModel[2] = {0};
 17 Model rockModels[5] = {0};
 18 Model grassPatchModel[1] = {0};
 19 
 20 Model pathArrowModel = {0};
 21 Model greenArrowModel = {0};
 22 
 23 Texture2D palette, spriteSheet;
 24 
 25 Level levels[] = {
 26   [0] = {
 27     .state = LEVEL_STATE_BUILDING,
 28     .initialGold = 20,
 29     .waves[0] = {
 30       .enemyType = ENEMY_TYPE_MINION,
 31       .wave = 0,
 32       .count = 5,
 33       .interval = 2.5f,
 34       .delay = 1.0f,
 35       .spawnPosition = {2, 6},
 36     },
 37     .waves[1] = {
 38       .enemyType = ENEMY_TYPE_MINION,
 39       .wave = 0,
 40       .count = 5,
 41       .interval = 2.5f,
 42       .delay = 1.0f,
 43       .spawnPosition = {-2, 6},
 44     },
 45     .waves[2] = {
 46       .enemyType = ENEMY_TYPE_MINION,
 47       .wave = 1,
 48       .count = 20,
 49       .interval = 1.5f,
 50       .delay = 1.0f,
 51       .spawnPosition = {0, 6},
 52     },
 53     .waves[3] = {
 54       .enemyType = ENEMY_TYPE_MINION,
 55       .wave = 2,
 56       .count = 30,
 57       .interval = 1.2f,
 58       .delay = 1.0f,
 59       .spawnPosition = {0, 6},
 60     }
 61   },
 62 };
 63 
 64 Level *currentLevel = levels;
 65 
 66 //# Game
 67 
 68 static Model LoadGLBModel(char *filename)
 69 {
 70   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 71   for (int i = 0; i < model.materialCount; i++)
 72   {
 73     model.materials[i].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 74   }
 75   return model;
 76 }
 77 
 78 void LoadAssets()
 79 {
 80   // load a sprite sheet that contains all units
 81   spriteSheet = LoadTexture("data/spritesheet.png");
 82   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 83 
 84   // we'll use a palette texture to colorize the all buildings and environment art
 85   palette = LoadTexture("data/palette.png");
 86   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 87   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 88 
 89   floorTileAModel = LoadGLBModel("floor-tile-a");
 90   floorTileBModel = LoadGLBModel("floor-tile-b");
 91   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
 92   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
 93   firTreeModel[0] = LoadGLBModel("firtree-1-a");
 94   firTreeModel[1] = LoadGLBModel("firtree-1-b");
 95   rockModels[0] = LoadGLBModel("rock-1");
 96   rockModels[1] = LoadGLBModel("rock-2");
 97   rockModels[2] = LoadGLBModel("rock-3");
 98   rockModels[3] = LoadGLBModel("rock-4");
 99   rockModels[4] = LoadGLBModel("rock-5");
100   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
101 
102   pathArrowModel = LoadGLBModel("direction-arrow-x");
103   greenArrowModel = LoadGLBModel("green-arrow");
104 }
105 
106 void InitLevel(Level *level)
107 {
108   level->seed = (int)(GetTime() * 100.0f);
109 
110   TowerInit();
111   EnemyInit();
112   ProjectileInit();
113   ParticleInit();
114   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
115 
116   level->placementMode = 0;
117   level->state = LEVEL_STATE_BUILDING;
118   level->nextState = LEVEL_STATE_NONE;
119   level->playerGold = level->initialGold;
120   level->currentWave = 0;
121   level->placementX = -1;
122   level->placementY = 0;
123 
124   Camera *camera = &level->camera;
125   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
126   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
127   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
128   camera->fovy = 10.0f;
129   camera->projection = CAMERA_ORTHOGRAPHIC;
130 }
131 
132 void DrawLevelHud(Level *level)
133 {
134   const char *text = TextFormat("Gold: %d", level->playerGold);
135   Font font = GetFontDefault();
136   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
137   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
138 }
139 
140 void DrawLevelReportLostWave(Level *level)
141 {
142   BeginMode3D(level->camera);
143   DrawLevelGround(level);
144   TowerDraw();
145   EnemyDraw();
146   ProjectileDraw();
147   ParticleDraw();
148   guiState.isBlocked = 0;
149   EndMode3D();
150 
151   TowerDrawHealthBars(level->camera);
152 
153   const char *text = "Wave lost";
154   int textWidth = MeasureText(text, 20);
155   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
156 
157   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
158   {
159     level->nextState = LEVEL_STATE_RESET;
160   }
161 }
162 
163 int HasLevelNextWave(Level *level)
164 {
165   for (int i = 0; i < 10; i++)
166   {
167     EnemyWave *wave = &level->waves[i];
168     if (wave->wave == level->currentWave)
169     {
170       return 1;
171     }
172   }
173   return 0;
174 }
175 
176 void DrawLevelReportWonWave(Level *level)
177 {
178   BeginMode3D(level->camera);
179   DrawLevelGround(level);
180   TowerDraw();
181   EnemyDraw();
182   ProjectileDraw();
183   ParticleDraw();
184   guiState.isBlocked = 0;
185   EndMode3D();
186 
187   TowerDrawHealthBars(level->camera);
188 
189   const char *text = "Wave won";
190   int textWidth = MeasureText(text, 20);
191   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
192 
193 
194   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
195   {
196     level->nextState = LEVEL_STATE_RESET;
197   }
198 
199   if (HasLevelNextWave(level))
200   {
201     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
202     {
203       level->nextState = LEVEL_STATE_BUILDING;
204     }
205   }
206   else {
207     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
208     {
209       level->nextState = LEVEL_STATE_WON_LEVEL;
210     }
211   }
212 }
213 
214 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
215 {
216   static ButtonState buttonStates[8] = {0};
217   int cost = GetTowerCosts(towerType);
218   const char *text = TextFormat("%s: %d", name, cost);
219   buttonStates[towerType].isSelected = level->placementMode == towerType;
220   buttonStates[towerType].isDisabled = level->playerGold < cost;
221   if (Button(text, x, y, width, height, &buttonStates[towerType]))
222   {
223     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
224     level->nextState = LEVEL_STATE_BUILDING_PLACEMENT;
225   }
226 }
227 
228 float GetRandomFloat(float min, float max)
229 {
230   int random = GetRandomValue(0, 0xfffffff);
231   return ((float)random / (float)0xfffffff) * (max - min) + min;
232 }
233 
234 void DrawLevelGround(Level *level)
235 {
236   // draw checkerboard ground pattern
237   for (int x = -5; x <= 5; x += 1)
238   {
239     for (int y = -5; y <= 5; y += 1)
240     {
241       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
242       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
243     }
244   }
245 
246   int oldSeed = GetRandomValue(0, 0xfffffff);
247   SetRandomSeed(level->seed);
248   // increase probability for trees via duplicated entries
249   Model borderModels[64];
250   int maxRockCount = GetRandomValue(2, 6);
251   int maxTreeCount = GetRandomValue(10, 20);
252   int maxFirTreeCount = GetRandomValue(5, 10);
253   int maxLeafTreeCount = maxTreeCount - maxFirTreeCount;
254   int grassPatchCount = GetRandomValue(5, 30);
255 
256   int modelCount = 0;
257   for (int i = 0; i < maxRockCount && modelCount < 63; i++)
258   {
259     borderModels[modelCount++] = rockModels[GetRandomValue(0, 5)];
260   }
261   for (int i = 0; i < maxLeafTreeCount && modelCount < 63; i++)
262   {
263     borderModels[modelCount++] = treeModel[GetRandomValue(0, 1)];
264   }
265   for (int i = 0; i < maxFirTreeCount && modelCount < 63; i++)
266   {
267     borderModels[modelCount++] = firTreeModel[GetRandomValue(0, 1)];
268   }
269   for (int i = 0; i < grassPatchCount && modelCount < 63; i++)
270   {
271     borderModels[modelCount++] = grassPatchModel[0];
272   }
273 
274   // draw some objects around the border of the map
275   Vector3 up = {0, 1, 0};
276   // a pseudo random number generator to get the same result every time
277   const float wiggle = 0.75f;
278   const int layerCount = 3;
279   for (int layer = 0; layer < layerCount; layer++)
280   {
281     int layerPos = 6 + layer;
282     for (int x = -6 + layer; x <= 6 + layer; x += 1)
283     {
284       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
285         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, -layerPos + GetRandomFloat(0.0f, wiggle)}, 
286         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
287       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
288         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, layerPos + GetRandomFloat(0.0f, wiggle)}, 
289         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
290     }
291 
292     for (int z = -5 + layer; z <= 5 + layer; z += 1)
293     {
294       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
295         (Vector3){-layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
296         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
297       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
298         (Vector3){layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
299         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
300     }
301   }
302 
303   SetRandomSeed(oldSeed);
304 }
305 
306 void DrawEnemyPath(Level *level, Color arrowColor)
307 {
308   const int castleX = 0, castleY = 0;
309   const int maxWaypointCount = 200;
310   const float timeStep = 1.0f;
311   Vector3 arrowScale = {0.75f, 0.75f, 0.75f};
312 
313   // we start with a time offset to simulate the path, 
314   // this way the arrows are animated in a forward moving direction
315   // The time is wrapped around the time step to get a smooth animation
316   float timeOffset = fmodf(GetTime(), timeStep);
317 
318   for (int i = 0; i < ENEMY_MAX_WAVE_COUNT; i++)
319   {
320     EnemyWave *wave = &level->waves[i];
321     if (wave->wave != level->currentWave)
322     {
323       continue;
324     }
325 
326     // use this dummy enemy to simulate the path
327     Enemy dummy = {
328       .enemyType = ENEMY_TYPE_MINION,
329       .simPosition = (Vector2){wave->spawnPosition.x, wave->spawnPosition.y},
330       .nextX = wave->spawnPosition.x,
331       .nextY = wave->spawnPosition.y,
332       .currentX = wave->spawnPosition.x,
333       .currentY = wave->spawnPosition.y,
334     };
335 
336     float deltaTime = timeOffset;
337     for (int j = 0; j < maxWaypointCount; j++)
338     {
339       int waypointPassedCount = 0;
340       Vector2 pos = EnemyGetPosition(&dummy, deltaTime, &dummy.simVelocity, &waypointPassedCount);
341       // after the initial variable starting offset, we use a fixed time step
342       deltaTime = timeStep;
343       dummy.simPosition = pos;
344 
345       // Update the dummy's position just like we do in the regular enemy update loop
346       for (int k = 0; k < waypointPassedCount; k++)
347       {
348         dummy.currentX = dummy.nextX;
349         dummy.currentY = dummy.nextY;
350         if (EnemyGetNextPosition(dummy.currentX, dummy.currentY, &dummy.nextX, &dummy.nextY) &&
351           Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
352         {
353           break;
354         }
355       }
356       if (Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
357       {
358         break;
359       }
360       
361       // get the angle we need to rotate the arrow model. The velocity is just fine for this.
362       float angle = atan2f(dummy.simVelocity.x, dummy.simVelocity.y) * RAD2DEG - 90.0f;
363       DrawModelEx(pathArrowModel, (Vector3){pos.x, 0.15f, pos.y}, (Vector3){0, 1, 0}, angle, arrowScale, arrowColor);
364     }
365   }
366 }
367 
368 void DrawEnemyPaths(Level *level)
369 {
370   // disable depth testing for the path arrows
371   // flush the 3D batch to draw the arrows on top of everything
372   rlDrawRenderBatchActive();
373   rlDisableDepthTest();
374   DrawEnemyPath(level, (Color){64, 64, 64, 160});
375 
376   rlDrawRenderBatchActive();
377   rlEnableDepthTest();
378   DrawEnemyPath(level, WHITE);
379 }
380 
381 static void SimulateTowerPlacementBehavior(Level *level, float towerFloatHeight, float mapX, float mapY)
382 {
383   float dt = gameTime.fixedDeltaTime;
384   // smooth transition for the placement position using exponential decay
385   const float lambda = 15.0f;
386   float factor = 1.0f - expf(-lambda * dt);
387 
388   float damping = 0.5f;
389   float springStiffness = 300.0f;
390   float springDecay = 95.0f;
391   float minHeight = 0.35f;
392 
393   if (level->placementPhase == PLACEMENT_PHASE_STARTING)
394   {
395     damping = 1.0f;
396     springDecay = 90.0f;
397     springStiffness = 100.0f;
398     minHeight = 0.70f;
399   }
400 
401   for (int i = 0; i < gameTime.fixedStepCount; i++)
402   {
403     level->placementTransitionPosition = 
404       Vector2Lerp(
405         level->placementTransitionPosition, 
406         (Vector2){mapX, mapY}, factor);
407 
408     // draw the spring position for debugging the spring simulation
409     // first step: stiff spring, no simulation
410     Vector3 worldPlacementPosition = (Vector3){
411       level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
412     Vector3 springTargetPosition = (Vector3){
413       worldPlacementPosition.x, 1.0f + towerFloatHeight, worldPlacementPosition.z};
414     // consider the current velocity to predict the future position in order to dampen
415     // the spring simulation. Longer prediction times will result in more damping
416     Vector3 predictedSpringPosition = Vector3Add(level->placementTowerSpring.position, 
417       Vector3Scale(level->placementTowerSpring.velocity, dt * damping));
418     Vector3 springPointDelta = Vector3Subtract(springTargetPosition, predictedSpringPosition);
419     Vector3 velocityChange = Vector3Scale(springPointDelta, dt * springStiffness);
420     // decay velocity of the upright forcing spring
421     // This force acts like a 2nd spring that pulls the tip upright into the air above the
422     // base position
423     level->placementTowerSpring.velocity = Vector3Scale(level->placementTowerSpring.velocity, 1.0f - expf(-springDecay * dt));
424     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, velocityChange);
425 
426     // calculate length of the spring to calculate the force to apply to the placementTowerSpring position
427     // we use a simple spring model with a rest length of 1.0f
428     Vector3 springDelta = Vector3Subtract(worldPlacementPosition, level->placementTowerSpring.position);
429     float springLength = Vector3Length(springDelta);
430     float springForce = (springLength - 1.0f) * springStiffness;
431     Vector3 springForceVector = Vector3Normalize(springDelta);
432     springForceVector = Vector3Scale(springForceVector, springForce);
433     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, 
434       Vector3Scale(springForceVector, dt));
435 
436     level->placementTowerSpring.position = Vector3Add(level->placementTowerSpring.position, 
437       Vector3Scale(level->placementTowerSpring.velocity, dt));
438     if (level->placementTowerSpring.position.y < minHeight + towerFloatHeight)
439     {
440       level->placementTowerSpring.velocity.y *= -1.0f;
441       level->placementTowerSpring.position.y = fmaxf(level->placementTowerSpring.position.y, minHeight + towerFloatHeight);
442     }
443   }
444 }
445 
446 void DrawLevelBuildingPlacementState(Level *level)
447 {
448   const float placementDuration = 0.5f;
449 
450   level->placementTimer += gameTime.deltaTime;
451   if (level->placementTimer > 1.0f && level->placementPhase == PLACEMENT_PHASE_STARTING)
452   {
453     level->placementPhase = PLACEMENT_PHASE_MOVING;
454     level->placementTimer = 0.0f;
455   }
456 
457   BeginMode3D(level->camera);
458   DrawLevelGround(level);
459 
460   int blockedCellCount = 0;
461   Vector2 blockedCells[1];
462   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
463   float planeDistance = ray.position.y / -ray.direction.y;
464   float planeX = ray.direction.x * planeDistance + ray.position.x;
465   float planeY = ray.direction.z * planeDistance + ray.position.z;
466   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
467   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
468   if (level->placementPhase == PLACEMENT_PHASE_MOVING && 
469     level->placementMode && !guiState.isBlocked && 
470     mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5 && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
471   {
472     level->placementX = mapX;
473     level->placementY = mapY;
474   }
475   else
476   {
477     mapX = level->placementX;
478     mapY = level->placementY;
479   }
480   blockedCells[blockedCellCount++] = (Vector2){mapX, mapY};
481   PathFindingMapUpdate(blockedCellCount, blockedCells);
482 
483   TowerDraw();
484   EnemyDraw();
485   ProjectileDraw();
486   ParticleDraw();
487   DrawEnemyPaths(level);
488 
489   // let the tower float up and down. Consider this height in the spring simulation as well
490   float towerFloatHeight = sinf(gameTime.time * 4.0f) * 0.2f + 0.3f;
491 
492   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
493   {
494     // The bouncing spring needs a bit of outro time to look nice and complete. 
495     // So we scale the time so that the first 2/3rd of the placing phase handles the motion
496     // and the last 1/3rd is the outro physics (bouncing)
497     float t = fminf(1.0f, level->placementTimer / placementDuration * 1.5f);
498     // let towerFloatHeight describe parabola curve, starting at towerFloatHeight and ending at 0
499     float linearBlendHeight = (1.0f - t) * towerFloatHeight;
500     float parabola = (1.0f - ((t - 0.5f) * (t - 0.5f) * 4.0f)) * 2.0f;
501     towerFloatHeight = linearBlendHeight + parabola;
502   }
503 
504   SimulateTowerPlacementBehavior(level, towerFloatHeight, mapX, mapY);
505   
506   rlPushMatrix();
507   rlTranslatef(level->placementTransitionPosition.x, 0, level->placementTransitionPosition.y);
508 
509   rlPushMatrix();
510   rlTranslatef(0.0f, towerFloatHeight, 0.0f);
511   // calculate x and z rotation to align the model with the spring
512   Vector3 position = {level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
513   Vector3 towerUp = Vector3Subtract(level->placementTowerSpring.position, position);
514   Vector3 rotationAxis = Vector3CrossProduct(towerUp, (Vector3){0, 1, 0});
515   float angle = acosf(Vector3DotProduct(towerUp, (Vector3){0, 1, 0}) / Vector3Length(towerUp)) * RAD2DEG;
516   float springLength = Vector3Length(towerUp);
517   float towerStretch = fminf(fmaxf(springLength, 0.5f), 4.5f);
518   float towerSquash = 1.0f / towerStretch;
519   rlRotatef(-angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
520   rlScalef(towerSquash, towerStretch, towerSquash);
521   Tower dummy = {
522     .towerType = level->placementMode,
523   };
524   TowerDrawSingle(dummy);
525   rlPopMatrix();
526 
527   // draw a shadow for the tower
528   float umbrasize = 0.8 + sqrtf(towerFloatHeight);
529   DrawCube((Vector3){0.0f, 0.05f, 0.0f}, umbrasize, 0.0f, umbrasize, (Color){0, 0, 0, 32});
530   DrawCube((Vector3){0.0f, 0.075f, 0.0f}, 0.85f, 0.0f, 0.85f, (Color){0, 0, 0, 64});
531 
532 
533   float bounce = sinf(gameTime.time * 8.0f) * 0.5f + 0.5f;
534   float offset = fmaxf(0.0f, bounce - 0.4f) * 0.35f + 0.7f;
535   float squeeze = -fminf(0.0f, bounce - 0.4f) * 0.7f + 0.7f;
536   float stretch = fmaxf(0.0f, bounce - 0.3f) * 0.5f + 0.8f;
537   
538   DrawModelEx(greenArrowModel, (Vector3){ offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 90, (Vector3){squeeze, 1.0f, stretch}, WHITE);
539   DrawModelEx(greenArrowModel, (Vector3){-offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 270, (Vector3){squeeze, 1.0f, stretch}, WHITE);
540   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f,  offset}, (Vector3){0, 1, 0}, 0, (Vector3){squeeze, 1.0f, stretch}, WHITE);
541   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f, -offset}, (Vector3){0, 1, 0}, 180, (Vector3){squeeze, 1.0f, stretch}, WHITE);
542   rlPopMatrix();
543 
544   guiState.isBlocked = 0;
545 
546   EndMode3D();
547 
548   TowerDrawHealthBars(level->camera);
549 
550   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
551   {
552     if (level->placementTimer > placementDuration)
553     {
554         TowerTryAdd(level->placementMode, mapX, mapY);
555         level->playerGold -= GetTowerCosts(level->placementMode);
556         level->nextState = LEVEL_STATE_BUILDING;
557         level->placementMode = TOWER_TYPE_NONE;
558     }
559   }
560   else
561   {   
562     if (Button("Cancel", 20, GetScreenHeight() - 40, 160, 30, 0))
563     {
564       level->nextState = LEVEL_STATE_BUILDING;
565       level->placementMode = TOWER_TYPE_NONE;
566       TraceLog(LOG_INFO, "Cancel building");
567     }
568     
569     if (TowerGetAt(mapX, mapY) == 0 &&  Button("Build", GetScreenWidth() - 180, GetScreenHeight() - 40, 160, 30, 0))
570     {
571       level->placementPhase = PLACEMENT_PHASE_PLACING;
572       level->placementTimer = 0.0f;
573     }
574   }
575 }
576 
577 void DrawLevelBuildingState(Level *level)
578 {
579   BeginMode3D(level->camera);
580   DrawLevelGround(level);
581 
582   PathFindingMapUpdate(0, 0);
583   TowerDraw();
584   EnemyDraw();
585   ProjectileDraw();
586   ParticleDraw();
587   DrawEnemyPaths(level);
588 
589   guiState.isBlocked = 0;
590 
591   EndMode3D();
592 
593   TowerDrawHealthBars(level->camera);
594 
595   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
596   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_ARCHER, "Archer");
597   DrawBuildingBuildButton(level, 10, 90, 110, 30, TOWER_TYPE_BALLISTA, "Ballista");
598   DrawBuildingBuildButton(level, 10, 130, 110, 30, TOWER_TYPE_CATAPULT, "Catapult");
599 
600   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
601   {
602     level->nextState = LEVEL_STATE_RESET;
603   }
604   
605   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
606   {
607     level->nextState = LEVEL_STATE_BATTLE;
608   }
609 
610   const char *text = "Building phase";
611   int textWidth = MeasureText(text, 20);
612   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
613 }
614 
615 void InitBattleStateConditions(Level *level)
616 {
617   level->state = LEVEL_STATE_BATTLE;
618   level->nextState = LEVEL_STATE_NONE;
619   level->waveEndTimer = 0.0f;
620   for (int i = 0; i < 10; i++)
621   {
622     EnemyWave *wave = &level->waves[i];
623     wave->spawned = 0;
624     wave->timeToSpawnNext = wave->delay;
625   }
626 }
627 
628 void DrawLevelBattleState(Level *level)
629 {
630   BeginMode3D(level->camera);
631   DrawLevelGround(level);
632   TowerDraw();
633   EnemyDraw();
634   ProjectileDraw();
635   ParticleDraw();
636   guiState.isBlocked = 0;
637   EndMode3D();
638 
639   EnemyDrawHealthbars(level->camera);
640   TowerDrawHealthBars(level->camera);
641 
642   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
643   {
644     level->nextState = LEVEL_STATE_RESET;
645   }
646 
647   int maxCount = 0;
648   int remainingCount = 0;
649   for (int i = 0; i < 10; i++)
650   {
651     EnemyWave *wave = &level->waves[i];
652     if (wave->wave != level->currentWave)
653     {
654       continue;
655     }
656     maxCount += wave->count;
657     remainingCount += wave->count - wave->spawned;
658   }
659   int aliveCount = EnemyCount();
660   remainingCount += aliveCount;
661 
662   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
663   int textWidth = MeasureText(text, 20);
664   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
665 }
666 
667 void DrawLevel(Level *level)
668 {
669   switch (level->state)
670   {
671     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
672     case LEVEL_STATE_BUILDING_PLACEMENT: DrawLevelBuildingPlacementState(level); break;
673     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
674     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
675     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
676     default: break;
677   }
678 
679   DrawLevelHud(level);
680 }
681 
682 void UpdateLevel(Level *level)
683 {
684   if (level->state == LEVEL_STATE_BATTLE)
685   {
686     int activeWaves = 0;
687     for (int i = 0; i < 10; i++)
688     {
689       EnemyWave *wave = &level->waves[i];
690       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
691       {
692         continue;
693       }
694       activeWaves++;
695       wave->timeToSpawnNext -= gameTime.deltaTime;
696       if (wave->timeToSpawnNext <= 0.0f)
697       {
698         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
699         if (enemy)
700         {
701           wave->timeToSpawnNext = wave->interval;
702           wave->spawned++;
703         }
704       }
705     }
706     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
707       level->waveEndTimer += gameTime.deltaTime;
708       if (level->waveEndTimer >= 2.0f)
709       {
710         level->nextState = LEVEL_STATE_LOST_WAVE;
711       }
712     }
713     else if (activeWaves == 0 && EnemyCount() == 0)
714     {
715       level->waveEndTimer += gameTime.deltaTime;
716       if (level->waveEndTimer >= 2.0f)
717       {
718         level->nextState = LEVEL_STATE_WON_WAVE;
719       }
720     }
721   }
722 
723   PathFindingMapUpdate(0, 0);
724   EnemyUpdate();
725   TowerUpdate();
726   ProjectileUpdate();
727   ParticleUpdate();
728 
729   if (level->nextState == LEVEL_STATE_RESET)
730   {
731     InitLevel(level);
732   }
733   
734   if (level->nextState == LEVEL_STATE_BATTLE)
735   {
736     InitBattleStateConditions(level);
737   }
738   
739   if (level->nextState == LEVEL_STATE_WON_WAVE)
740   {
741     level->currentWave++;
742     level->state = LEVEL_STATE_WON_WAVE;
743   }
744   
745   if (level->nextState == LEVEL_STATE_LOST_WAVE)
746   {
747     level->state = LEVEL_STATE_LOST_WAVE;
748   }
749 
750   if (level->nextState == LEVEL_STATE_BUILDING)
751   {
752     level->state = LEVEL_STATE_BUILDING;
753   }
754 
755   if (level->nextState == LEVEL_STATE_BUILDING_PLACEMENT)
756   {
757     level->state = LEVEL_STATE_BUILDING_PLACEMENT;
758     level->placementTransitionPosition = (Vector2){
759       level->placementX, level->placementY};
760     // initialize the spring to the current position
761     level->placementTowerSpring = (PhysicsPoint){
762       .position = (Vector3){level->placementX, 8.0f, level->placementY},
763       .velocity = (Vector3){0.0f, 0.0f, 0.0f},
764     };
765     level->placementPhase = PLACEMENT_PHASE_STARTING;
766     level->placementTimer = 0.0f;
767   }
768 
769   if (level->nextState == LEVEL_STATE_WON_LEVEL)
770   {
771     // make something of this later
772     InitLevel(level);
773   }
774 
775   level->nextState = LEVEL_STATE_NONE;
776 }
777 
778 float nextSpawnTime = 0.0f;
779 
780 void ResetGame()
781 {
782   InitLevel(currentLevel);
783 }
784 
785 void InitGame()
786 {
787   TowerInit();
788   EnemyInit();
789   ProjectileInit();
790   ParticleInit();
791   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
792 
793   currentLevel = levels;
794   InitLevel(currentLevel);
795 }
796 
797 //# Immediate GUI functions
798 
799 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
800 {
801   const float healthBarHeight = 6.0f;
802   const float healthBarOffset = 15.0f;
803   const float inset = 2.0f;
804   const float innerWidth = healthBarWidth - inset * 2;
805   const float innerHeight = healthBarHeight - inset * 2;
806 
807   Vector2 screenPos = GetWorldToScreen(position, camera);
808   float centerX = screenPos.x - healthBarWidth * 0.5f;
809   float topY = screenPos.y - healthBarOffset;
810   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
811   float healthWidth = innerWidth * healthRatio;
812   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
813 }
814 
815 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
816 {
817   Rectangle bounds = {x, y, width, height};
818   int isPressed = 0;
819   int isSelected = state && state->isSelected;
820   int isDisabled = state && state->isDisabled;
821   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
822   {
823     Color color = isSelected ? DARKGRAY : GRAY;
824     DrawRectangle(x, y, width, height, color);
825     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
826     {
827       isPressed = 1;
828     }
829     guiState.isBlocked = 1;
830   }
831   else
832   {
833     Color color = isSelected ? WHITE : LIGHTGRAY;
834     DrawRectangle(x, y, width, height, color);
835   }
836   Font font = GetFontDefault();
837   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
838   Color textColor = isDisabled ? GRAY : BLACK;
839   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
840   return isPressed;
841 }
842 
843 //# Main game loop
844 
845 void GameUpdate()
846 {
847   UpdateLevel(currentLevel);
848 }
849 
850 int main(void)
851 {
852   int screenWidth, screenHeight;
853   GetPreferredSize(&screenWidth, &screenHeight);
854   InitWindow(screenWidth, screenHeight, "Tower defense");
855   float gamespeed = 1.0f;
856   SetTargetFPS(30);
857 
858   LoadAssets();
859   InitGame();
860 
861   float pause = 1.0f;
862 
863   while (!WindowShouldClose())
864   {
865     if (IsPaused()) {
866       // canvas is not visible in browser - do nothing
867       continue;
868     }
869 
870     if (IsKeyPressed(KEY_T))
871     {
872       gamespeed += 0.1f;
873       if (gamespeed > 1.05f) gamespeed = 0.1f;
874     }
875 
876     if (IsKeyPressed(KEY_P))
877     {
878       pause = pause > 0.5f ? 0.0f : 1.0f;
879     }
880 
881     float dt = GetFrameTime() * gamespeed * pause;
882     // cap maximum delta time to 0.1 seconds to prevent large time steps
883     if (dt > 0.1f) dt = 0.1f;
884     gameTime.time += dt;
885     gameTime.deltaTime = dt;
886     gameTime.frameCount += 1;
887 
888     float fixedTimeDiff = gameTime.time - gameTime.fixedTimeStart;
889     gameTime.fixedStepCount = (uint8_t)(fixedTimeDiff / gameTime.fixedDeltaTime);
890 
891     BeginDrawing();
892     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
893 
894     GameUpdate();
895     DrawLevel(currentLevel);
896 
897     DrawText(TextFormat("Speed: %.1f", gamespeed), GetScreenWidth() - 180, 60, 20, WHITE);
898     EndDrawing();
899 
900     gameTime.fixedTimeStart += gameTime.fixedStepCount * gameTime.fixedDeltaTime;
901   }
902 
903   CloseWindow();
904 
905   return 0;
906 }  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 //# Declarations
 10 
 11 typedef struct PhysicsPoint
 12 {
 13   Vector3 position;
 14   Vector3 velocity;
 15 } PhysicsPoint;
 16 
 17 #define ENEMY_MAX_PATH_COUNT 8
 18 #define ENEMY_MAX_COUNT 400
 19 #define ENEMY_TYPE_NONE 0
 20 
 21 #define ENEMY_TYPE_MINION 1
 22 #define ENEMY_TYPE_RUNNER 2
 23 #define ENEMY_TYPE_SHIELD 3
 24 #define ENEMY_TYPE_BOSS 3
 25 
 26 #define PARTICLE_MAX_COUNT 400
 27 #define PARTICLE_TYPE_NONE 0
 28 #define PARTICLE_TYPE_EXPLOSION 1
 29 
 30 typedef struct Particle
 31 {
 32   uint8_t particleType;
 33   float spawnTime;
 34   float lifetime;
 35   Vector3 position;
 36   Vector3 velocity;
 37   Vector3 scale;
 38 } Particle;
 39 
 40 #define TOWER_MAX_COUNT 400
 41 enum TowerType
 42 {
 43   TOWER_TYPE_NONE,
 44   TOWER_TYPE_BASE,
 45   TOWER_TYPE_ARCHER,
 46   TOWER_TYPE_BALLISTA,
 47   TOWER_TYPE_CATAPULT,
 48   TOWER_TYPE_WALL,
 49   TOWER_TYPE_COUNT
 50 };
 51 
 52 typedef struct HitEffectConfig
 53 {
 54   float damage;
 55   float areaDamageRadius;
 56   float pushbackPowerDistance;
 57 } HitEffectConfig;
 58 
 59 typedef struct TowerTypeConfig
 60 {
 61   float cooldown;
 62   float range;
 63   float projectileSpeed;
 64   
 65   uint8_t cost;
 66   uint8_t projectileType;
 67   uint16_t maxHealth;
 68 
 69   HitEffectConfig hitEffect;
 70 } TowerTypeConfig;
 71 
 72 typedef struct Tower
 73 {
 74   int16_t x, y;
 75   uint8_t towerType;
 76   Vector2 lastTargetPosition;
 77   float cooldown;
 78   float damage;
 79 } Tower;
 80 
 81 typedef struct GameTime
 82 {
 83   float time;
 84   float deltaTime;
 85   uint32_t frameCount;
 86 
 87   float fixedDeltaTime;
 88   // leaving the fixed time stepping to the update functions,
 89   // we need to know the fixed time at the start of the frame
 90   float fixedTimeStart;
 91   // and the number of fixed steps that we have to make this frame
 92   // The fixedTime is fixedTimeStart + n * fixedStepCount
 93   uint8_t fixedStepCount;
 94 } GameTime;
 95 
 96 typedef struct ButtonState {
 97   char isSelected;
 98   char isDisabled;
 99 } ButtonState;
100 
101 typedef struct GUIState {
102   int isBlocked;
103 } GUIState;
104 
105 typedef enum LevelState
106 {
107   LEVEL_STATE_NONE,
108   LEVEL_STATE_BUILDING,
109   LEVEL_STATE_BUILDING_PLACEMENT,
110   LEVEL_STATE_BATTLE,
111   LEVEL_STATE_WON_WAVE,
112   LEVEL_STATE_LOST_WAVE,
113   LEVEL_STATE_WON_LEVEL,
114   LEVEL_STATE_RESET,
115 } LevelState;
116 
117 typedef struct EnemyWave {
118   uint8_t enemyType;
119   uint8_t wave;
120   uint16_t count;
121   float interval;
122   float delay;
123   Vector2 spawnPosition;
124 
125   uint16_t spawned;
126   float timeToSpawnNext;
127 } EnemyWave;
128 
129 #define ENEMY_MAX_WAVE_COUNT 10
130 
131 typedef enum PlacementPhase
132 {
133   PLACEMENT_PHASE_STARTING,
134   PLACEMENT_PHASE_MOVING,
135   PLACEMENT_PHASE_PLACING,
136 } PlacementPhase;
137 
138 typedef struct Level
139 {
140   int seed;
141   LevelState state;
142   LevelState nextState;
143   Camera3D camera;
144   int placementMode;
145   PlacementPhase placementPhase;
146   float placementTimer;
147   int16_t placementX;
148   int16_t placementY;
149   Vector2 placementTransitionPosition;
150   PhysicsPoint placementTowerSpring;
151 
152   int initialGold;
153   int playerGold;
154 
155   EnemyWave waves[ENEMY_MAX_WAVE_COUNT];
156   int currentWave;
157   float waveEndTimer;
158 } Level;
159 
160 typedef struct DeltaSrc
161 {
162   char x, y;
163 } DeltaSrc;
164 
165 typedef struct PathfindingMap
166 {
167   int width, height;
168   float scale;
169   float *distances;
170   long *towerIndex; 
171   DeltaSrc *deltaSrc;
172   float maxDistance;
173   Matrix toMapSpace;
174   Matrix toWorldSpace;
175 } PathfindingMap;
176 
177 // when we execute the pathfinding algorithm, we need to store the active nodes
178 // in a queue. Each node has a position, a distance from the start, and the
179 // position of the node that we came from.
180 typedef struct PathfindingNode
181 {
182   int16_t x, y, fromX, fromY;
183   float distance;
184 } PathfindingNode;
185 
186 typedef struct EnemyId
187 {
188   uint16_t index;
189   uint16_t generation;
190 } EnemyId;
191 
192 typedef struct EnemyClassConfig
193 {
194   float speed;
195   float health;
196   float radius;
197   float maxAcceleration;
198   float requiredContactTime;
199   float explosionDamage;
200   float explosionRange;
201   float explosionPushbackPower;
202   int goldValue;
203 } EnemyClassConfig;
204 
205 typedef struct Enemy
206 {
207   int16_t currentX, currentY;
208   int16_t nextX, nextY;
209   Vector2 simPosition;
210   Vector2 simVelocity;
211   uint16_t generation;
212   float walkedDistance;
213   float startMovingTime;
214   float damage, futureDamage;
215   float contactTime;
216   uint8_t enemyType;
217   uint8_t movePathCount;
218   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
219 } Enemy;
220 
221 // a unit that uses sprites to be drawn
222 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
223 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
224 typedef struct SpriteUnit
225 {
226   Rectangle srcRect;
227   Vector2 offset;
228   int frameCount;
229   float frameDuration;
230   Rectangle srcWeaponIdleRect;
231   Vector2 srcWeaponIdleOffset;
232   Rectangle srcWeaponCooldownRect;
233   Vector2 srcWeaponCooldownOffset;
234 } SpriteUnit;
235 
236 #define PROJECTILE_MAX_COUNT 1200
237 #define PROJECTILE_TYPE_NONE 0
238 #define PROJECTILE_TYPE_ARROW 1
239 #define PROJECTILE_TYPE_CATAPULT 2
240 #define PROJECTILE_TYPE_BALLISTA 3
241 
242 typedef struct Projectile
243 {
244   uint8_t projectileType;
245   float shootTime;
246   float arrivalTime;
247   float distance;
248   Vector3 position;
249   Vector3 target;
250   Vector3 directionNormal;
251   EnemyId targetEnemy;
252   HitEffectConfig hitEffectConfig;
253 } Projectile;
254 
255 //# Function declarations
256 float TowerGetMaxHealth(Tower *tower);
257 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
258 int EnemyAddDamageRange(Vector2 position, float range, float damage);
259 int EnemyAddDamage(Enemy *enemy, float damage);
260 
261 //# Enemy functions
262 void EnemyInit();
263 void EnemyDraw();
264 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
265 void EnemyUpdate();
266 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
267 float EnemyGetMaxHealth(Enemy *enemy);
268 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
269 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
270 EnemyId EnemyGetId(Enemy *enemy);
271 Enemy *EnemyTryResolve(EnemyId enemyId);
272 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
273 int EnemyAddDamage(Enemy *enemy, float damage);
274 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
275 int EnemyCount();
276 void EnemyDrawHealthbars(Camera3D camera);
277 
278 //# Tower functions
279 void TowerInit();
280 Tower *TowerGetAt(int16_t x, int16_t y);
281 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
282 Tower *GetTowerByType(uint8_t towerType);
283 int GetTowerCosts(uint8_t towerType);
284 float TowerGetMaxHealth(Tower *tower);
285 void TowerDraw();
286 void TowerDrawSingle(Tower tower);
287 void TowerUpdate();
288 void TowerDrawHealthBars(Camera3D camera);
289 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
290 
291 //# Particles
292 void ParticleInit();
293 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime);
294 void ParticleUpdate();
295 void ParticleDraw();
296 
297 //# Projectiles
298 void ProjectileInit();
299 void ProjectileDraw();
300 void ProjectileUpdate();
301 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig);
302 
303 //# Pathfinding map
304 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
305 float PathFindingGetDistance(int mapX, int mapY);
306 Vector2 PathFindingGetGradient(Vector3 world);
307 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
308 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells);
309 void PathFindingMapDraw();
310 
311 //# UI
312 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
313 
314 //# Level
315 void DrawLevelGround(Level *level);
316 void DrawEnemyPath(Level *level, Color arrowColor);
317 
318 //# variables
319 extern Level *currentLevel;
320 extern Enemy enemies[ENEMY_MAX_COUNT];
321 extern int enemyCount;
322 extern EnemyClassConfig enemyClassConfigs[];
323 
324 extern GUIState guiState;
325 extern GameTime gameTime;
326 extern Tower towers[TOWER_MAX_COUNT];
327 extern int towerCount;
328 
329 extern Texture2D palette, spriteSheet;
330 
331 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 EnemyClassConfig enemyClassConfigs[] = {
  7     [ENEMY_TYPE_MINION] = {
  8       .health = 10.0f, 
  9       .speed = 0.6f, 
 10       .radius = 0.25f, 
 11       .maxAcceleration = 1.0f,
 12       .explosionDamage = 1.0f,
 13       .requiredContactTime = 0.5f,
 14       .explosionRange = 1.0f,
 15       .explosionPushbackPower = 0.25f,
 16       .goldValue = 1,
 17     },
 18     [ENEMY_TYPE_RUNNER] = {
 19       .health = 5.0f, 
 20       .speed = 1.0f, 
 21       .radius = 0.25f, 
 22       .maxAcceleration = 2.0f,
 23       .explosionDamage = 1.0f,
 24       .requiredContactTime = 0.5f,
 25       .explosionRange = 1.0f,
 26       .explosionPushbackPower = 0.25f,
 27       .goldValue = 2,
 28     },
 29 };
 30 
 31 Enemy enemies[ENEMY_MAX_COUNT];
 32 int enemyCount = 0;
 33 
 34 SpriteUnit enemySprites[] = {
 35     [ENEMY_TYPE_MINION] = {
 36       .srcRect = {0, 16, 16, 16},
 37       .offset = {8.0f, 0.0f},
 38       .frameCount = 6,
 39       .frameDuration = 0.1f,
 40       .srcWeaponIdleRect = {0, 32, 16, 16},
 41     },
 42     [ENEMY_TYPE_RUNNER] = {
 43       .srcRect = {0, 16, 16, 16},
 44       .offset = {8.0f, 0.0f},
 45       .frameCount = 6,
 46       .frameDuration = 0.1f,
 47     },
 48 };
 49 
 50 void EnemyInit()
 51 {
 52   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 53   {
 54     enemies[i] = (Enemy){0};
 55   }
 56   enemyCount = 0;
 57 }
 58 
 59 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 60 {
 61   return enemyClassConfigs[enemy->enemyType].speed;
 62 }
 63 
 64 float EnemyGetMaxHealth(Enemy *enemy)
 65 {
 66   return enemyClassConfigs[enemy->enemyType].health;
 67 }
 68 
 69 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 70 {
 71   int16_t castleX = 0;
 72   int16_t castleY = 0;
 73   int16_t dx = castleX - currentX;
 74   int16_t dy = castleY - currentY;
 75   if (dx == 0 && dy == 0)
 76   {
 77     *nextX = currentX;
 78     *nextY = currentY;
 79     return 1;
 80   }
 81   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 82 
 83   if (gradient.x == 0 && gradient.y == 0)
 84   {
 85     *nextX = currentX;
 86     *nextY = currentY;
 87     return 1;
 88   }
 89 
 90   if (fabsf(gradient.x) > fabsf(gradient.y))
 91   {
 92     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
 93     *nextY = currentY;
 94     return 0;
 95   }
 96   *nextX = currentX;
 97   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
 98   return 0;
 99 }
100 
101 
102 // this function predicts the movement of the unit for the next deltaT seconds
103 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
104 {
105   const float pointReachedDistance = 0.25f;
106   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
107   const float maxSimStepTime = 0.015625f;
108   
109   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
110   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
111   int16_t nextX = enemy->nextX;
112   int16_t nextY = enemy->nextY;
113   Vector2 position = enemy->simPosition;
114   int passedCount = 0;
115   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
116   {
117     float stepTime = fminf(deltaT - t, maxSimStepTime);
118     Vector2 target = (Vector2){nextX, nextY};
119     float speed = Vector2Length(*velocity);
120     // draw the target position for debugging
121     //DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
122     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
123     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
124     {
125       // we reached the target position, let's move to the next waypoint
126       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
127       target = (Vector2){nextX, nextY};
128       // track how many waypoints we passed
129       passedCount++;
130     }
131     
132     // acceleration towards the target
133     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
134     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
135     *velocity = Vector2Add(*velocity, acceleration);
136 
137     // limit the speed to the maximum speed
138     if (speed > maxSpeed)
139     {
140       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
141     }
142 
143     // move the enemy
144     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
145   }
146 
147   if (waypointPassedCount)
148   {
149     (*waypointPassedCount) = passedCount;
150   }
151 
152   return position;
153 }
154 
155 void EnemyDraw()
156 {
157   for (int i = 0; i < enemyCount; i++)
158   {
159     Enemy enemy = enemies[i];
160     if (enemy.enemyType == ENEMY_TYPE_NONE)
161     {
162       continue;
163     }
164 
165     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
166     
167     // don't draw any trails for now; might replace this with footprints later
168     // if (enemy.movePathCount > 0)
169     // {
170     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
171     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
172     // }
173     // for (int j = 1; j < enemy.movePathCount; j++)
174     // {
175     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
176     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
177     //   DrawLine3D(p, q, GREEN);
178     // }
179 
180     switch (enemy.enemyType)
181     {
182     case ENEMY_TYPE_MINION:
183       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y}, 
184         enemy.walkedDistance, 0, 0);
185       break;
186     }
187   }
188 }
189 
190 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
191 {
192   // damage the tower
193   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
194   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
195   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
196   float explosionRange2 = explosionRange * explosionRange;
197   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
198   // explode the enemy
199   if (tower->damage >= TowerGetMaxHealth(tower))
200   {
201     tower->towerType = TOWER_TYPE_NONE;
202   }
203 
204   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
205     explosionSource, 
206     (Vector3){0, 0.1f, 0}, (Vector3){1.0f, 1.0f, 1.0f}, 1.0f);
207 
208   enemy->enemyType = ENEMY_TYPE_NONE;
209 
210   // push back enemies & dealing damage
211   for (int i = 0; i < enemyCount; i++)
212   {
213     Enemy *other = &enemies[i];
214     if (other->enemyType == ENEMY_TYPE_NONE)
215     {
216       continue;
217     }
218     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
219     if (distanceSqr > 0 && distanceSqr < explosionRange2)
220     {
221       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
222       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
223       EnemyAddDamage(other, explosionDamge);
224     }
225   }
226 }
227 
228 void EnemyUpdate()
229 {
230   const float castleX = 0;
231   const float castleY = 0;
232   const float maxPathDistance2 = 0.25f * 0.25f;
233   
234   for (int i = 0; i < enemyCount; i++)
235   {
236     Enemy *enemy = &enemies[i];
237     if (enemy->enemyType == ENEMY_TYPE_NONE)
238     {
239       continue;
240     }
241 
242     int waypointPassedCount = 0;
243     Vector2 prevPosition = enemy->simPosition;
244     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
245     enemy->startMovingTime = gameTime.time;
246     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
247     // track path of unit
248     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
249     {
250       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
251       {
252         enemy->movePath[j] = enemy->movePath[j - 1];
253       }
254       enemy->movePath[0] = enemy->simPosition;
255       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
256       {
257         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
258       }
259     }
260 
261     if (waypointPassedCount > 0)
262     {
263       enemy->currentX = enemy->nextX;
264       enemy->currentY = enemy->nextY;
265       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
266         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
267       {
268         // enemy reached the castle; remove it
269         enemy->enemyType = ENEMY_TYPE_NONE;
270         continue;
271       }
272     }
273   }
274 
275   // handle collisions between enemies
276   for (int i = 0; i < enemyCount - 1; i++)
277   {
278     Enemy *enemyA = &enemies[i];
279     if (enemyA->enemyType == ENEMY_TYPE_NONE)
280     {
281       continue;
282     }
283     for (int j = i + 1; j < enemyCount; j++)
284     {
285       Enemy *enemyB = &enemies[j];
286       if (enemyB->enemyType == ENEMY_TYPE_NONE)
287       {
288         continue;
289       }
290       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
291       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
292       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
293       float radiusSum = radiusA + radiusB;
294       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
295       {
296         // collision
297         float distance = sqrtf(distanceSqr);
298         float overlap = radiusSum - distance;
299         // move the enemies apart, but softly; if we have a clog of enemies,
300         // moving them perfectly apart can cause them to jitter
301         float positionCorrection = overlap / 5.0f;
302         Vector2 direction = (Vector2){
303             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
304             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
305         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
306         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
307       }
308     }
309   }
310 
311   // handle collisions between enemies and towers
312   for (int i = 0; i < enemyCount; i++)
313   {
314     Enemy *enemy = &enemies[i];
315     if (enemy->enemyType == ENEMY_TYPE_NONE)
316     {
317       continue;
318     }
319     enemy->contactTime -= gameTime.deltaTime;
320     if (enemy->contactTime < 0.0f)
321     {
322       enemy->contactTime = 0.0f;
323     }
324 
325     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
326     // linear search over towers; could be optimized by using path finding tower map,
327     // but for now, we keep it simple
328     for (int j = 0; j < towerCount; j++)
329     {
330       Tower *tower = &towers[j];
331       if (tower->towerType == TOWER_TYPE_NONE)
332       {
333         continue;
334       }
335       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
336       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
337       if (distanceSqr > combinedRadius * combinedRadius)
338       {
339         continue;
340       }
341       // potential collision; square / circle intersection
342       float dx = tower->x - enemy->simPosition.x;
343       float dy = tower->y - enemy->simPosition.y;
344       float absDx = fabsf(dx);
345       float absDy = fabsf(dy);
346       Vector3 contactPoint = {0};
347       if (absDx <= 0.5f && absDx <= absDy) {
348         // vertical collision; push the enemy out horizontally
349         float overlap = enemyRadius + 0.5f - absDy;
350         if (overlap < 0.0f)
351         {
352           continue;
353         }
354         float direction = dy > 0.0f ? -1.0f : 1.0f;
355         enemy->simPosition.y += direction * overlap;
356         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
357       }
358       else if (absDy <= 0.5f && absDy <= absDx)
359       {
360         // horizontal collision; push the enemy out vertically
361         float overlap = enemyRadius + 0.5f - absDx;
362         if (overlap < 0.0f)
363         {
364           continue;
365         }
366         float direction = dx > 0.0f ? -1.0f : 1.0f;
367         enemy->simPosition.x += direction * overlap;
368         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
369       }
370       else
371       {
372         // possible collision with a corner
373         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
374         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
375         float cornerX = tower->x + cornerDX;
376         float cornerY = tower->y + cornerDY;
377         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
378         if (cornerDistanceSqr > enemyRadius * enemyRadius)
379         {
380           continue;
381         }
382         // push the enemy out along the diagonal
383         float cornerDistance = sqrtf(cornerDistanceSqr);
384         float overlap = enemyRadius - cornerDistance;
385         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
386         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
387         enemy->simPosition.x -= directionX * overlap;
388         enemy->simPosition.y -= directionY * overlap;
389         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
390       }
391 
392       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
393       {
394         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
395         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
396         {
397           EnemyTriggerExplode(enemy, tower, contactPoint);
398         }
399       }
400     }
401   }
402 }
403 
404 EnemyId EnemyGetId(Enemy *enemy)
405 {
406   return (EnemyId){enemy - enemies, enemy->generation};
407 }
408 
409 Enemy *EnemyTryResolve(EnemyId enemyId)
410 {
411   if (enemyId.index >= ENEMY_MAX_COUNT)
412   {
413     return 0;
414   }
415   Enemy *enemy = &enemies[enemyId.index];
416   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
417   {
418     return 0;
419   }
420   return enemy;
421 }
422 
423 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
424 {
425   Enemy *spawn = 0;
426   for (int i = 0; i < enemyCount; i++)
427   {
428     Enemy *enemy = &enemies[i];
429     if (enemy->enemyType == ENEMY_TYPE_NONE)
430     {
431       spawn = enemy;
432       break;
433     }
434   }
435 
436   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
437   {
438     spawn = &enemies[enemyCount++];
439   }
440 
441   if (spawn)
442   {
443     spawn->currentX = currentX;
444     spawn->currentY = currentY;
445     spawn->nextX = currentX;
446     spawn->nextY = currentY;
447     spawn->simPosition = (Vector2){currentX, currentY};
448     spawn->simVelocity = (Vector2){0, 0};
449     spawn->enemyType = enemyType;
450     spawn->startMovingTime = gameTime.time;
451     spawn->damage = 0.0f;
452     spawn->futureDamage = 0.0f;
453     spawn->generation++;
454     spawn->movePathCount = 0;
455     spawn->walkedDistance = 0.0f;
456   }
457 
458   return spawn;
459 }
460 
461 int EnemyAddDamageRange(Vector2 position, float range, float damage)
462 {
463   int count = 0;
464   float range2 = range * range;
465   for (int i = 0; i < enemyCount; i++)
466   {
467     Enemy *enemy = &enemies[i];
468     if (enemy->enemyType == ENEMY_TYPE_NONE)
469     {
470       continue;
471     }
472     float distance2 = Vector2DistanceSqr(position, enemy->simPosition);
473     if (distance2 <= range2)
474     {
475       EnemyAddDamage(enemy, damage);
476       count++;
477     }
478   }
479   return count;
480 }
481 
482 int EnemyAddDamage(Enemy *enemy, float damage)
483 {
484   enemy->damage += damage;
485   if (enemy->damage >= EnemyGetMaxHealth(enemy))
486   {
487     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
488     enemy->enemyType = ENEMY_TYPE_NONE;
489     return 1;
490   }
491 
492   return 0;
493 }
494 
495 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
496 {
497   int16_t castleX = 0;
498   int16_t castleY = 0;
499   Enemy* closest = 0;
500   int16_t closestDistance = 0;
501   float range2 = range * range;
502   for (int i = 0; i < enemyCount; i++)
503   {
504     Enemy* enemy = &enemies[i];
505     if (enemy->enemyType == ENEMY_TYPE_NONE)
506     {
507       continue;
508     }
509     float maxHealth = EnemyGetMaxHealth(enemy);
510     if (enemy->futureDamage >= maxHealth)
511     {
512       // ignore enemies that will die soon
513       continue;
514     }
515     int16_t dx = castleX - enemy->currentX;
516     int16_t dy = castleY - enemy->currentY;
517     int16_t distance = abs(dx) + abs(dy);
518     if (!closest || distance < closestDistance)
519     {
520       float tdx = towerX - enemy->currentX;
521       float tdy = towerY - enemy->currentY;
522       float tdistance2 = tdx * tdx + tdy * tdy;
523       if (tdistance2 <= range2)
524       {
525         closest = enemy;
526         closestDistance = distance;
527       }
528     }
529   }
530   return closest;
531 }
532 
533 int EnemyCount()
534 {
535   int count = 0;
536   for (int i = 0; i < enemyCount; i++)
537   {
538     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
539     {
540       count++;
541     }
542   }
543   return count;
544 }
545 
546 void EnemyDrawHealthbars(Camera3D camera)
547 {
548   for (int i = 0; i < enemyCount; i++)
549   {
550     Enemy *enemy = &enemies[i];
551     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
552     {
553       continue;
554     }
555     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
556     float maxHealth = EnemyGetMaxHealth(enemy);
557     float health = maxHealth - enemy->damage;
558     float healthRatio = health / maxHealth;
559     
560     DrawHealthBar(camera, position, healthRatio, GREEN, 15.0f);
561   }
562 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells)
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < blockedCellCount; i++)
131   {
132     int16_t mapX, mapY;
133     if (!PathFindingFromWorldToMapPosition((Vector3){blockedCells[i].x, 0.0f, blockedCells[i].y}, &mapX, &mapY))
134     {
135       continue;
136     }
137     int index = mapY * width + mapX;
138     pathfindingMap.towerIndex[index] = -2;
139   }
140 
141   for (int i = 0; i < towerCount; i++)
142   {
143     Tower *tower = &towers[i];
144     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
145     {
146       continue;
147     }
148     int16_t mapX, mapY;
149     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
150     // this would not work correctly and needs to be refined to allow towers covering multiple cells
151     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
152     // one cell. For now.
153     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
154     {
155       continue;
156     }
157     int index = mapY * width + mapX;
158     pathfindingMap.towerIndex[index] = i;
159   }
160 
161   // we start at the castle and add the castle to the queue
162   pathfindingMap.maxDistance = 0.0f;
163   pathfindingNodeQueueCount = 0;
164   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
165   PathfindingNode *node = 0;
166   while ((node = PathFindingNodePop()))
167   {
168     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
169     {
170       continue;
171     }
172     int index = node->y * width + node->x;
173     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
174     {
175       continue;
176     }
177 
178     int deltaX = node->x - node->fromX;
179     int deltaY = node->y - node->fromY;
180     // even if the cell is blocked by a tower, we still may want to store the direction
181     // (though this might not be needed, IDK right now)
182     pathfindingMap.deltaSrc[index].x = (char) deltaX;
183     pathfindingMap.deltaSrc[index].y = (char) deltaY;
184 
185     // we skip nodes that are blocked by towers or by the provided blocked cells
186     if (pathfindingMap.towerIndex[index] != -1)
187     {
188       node->distance += 8.0f;
189     }
190     pathfindingMap.distances[index] = node->distance;
191     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
192     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
193     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
194     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
195     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
196   }
197 }
198 
199 void PathFindingMapDraw()
200 {
201   float cellSize = pathfindingMap.scale * 0.9f;
202   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
203   for (int x = 0; x < pathfindingMap.width; x++)
204   {
205     for (int y = 0; y < pathfindingMap.height; y++)
206     {
207       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
208       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
209       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
210       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
211       // animate the distance "wave" to show how the pathfinding algorithm expands
212       // from the castle
213       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
214       {
215         color = BLACK;
216       }
217       DrawCube(position, cellSize, 0.1f, cellSize, color);
218     }
219   }
220 }
221 
222 Vector2 PathFindingGetGradient(Vector3 world)
223 {
224   int16_t mapX, mapY;
225   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
226   {
227     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
228     return (Vector2){(float)-delta.x, (float)-delta.y};
229   }
230   // fallback to a simple gradient calculation
231   float n = PathFindingGetDistance(mapX, mapY - 1);
232   float s = PathFindingGetDistance(mapX, mapY + 1);
233   float w = PathFindingGetDistance(mapX - 1, mapY);
234   float e = PathFindingGetDistance(mapX + 1, mapY);
235   return (Vector2){w - e + 0.25f, n - s + 0.125f};
236 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static TowerTypeConfig towerTypeConfigs[TOWER_TYPE_COUNT] = {
  5     [TOWER_TYPE_BASE] = {
  6         .maxHealth = 10,
  7     },
  8     [TOWER_TYPE_ARCHER] = {
  9         .cooldown = 0.5f,
 10         .range = 3.0f,
 11         .cost = 6,
 12         .maxHealth = 10,
 13         .projectileSpeed = 4.0f,
 14         .projectileType = PROJECTILE_TYPE_ARROW,
 15         .hitEffect = {
 16           .damage = 3.0f,
 17         }
 18     },
 19     [TOWER_TYPE_BALLISTA] = {
 20         .cooldown = 1.5f,
 21         .range = 6.0f,
 22         .cost = 9,
 23         .maxHealth = 10,
 24         .projectileSpeed = 10.0f,
 25         .projectileType = PROJECTILE_TYPE_BALLISTA,
 26         .hitEffect = {
 27           .damage = 6.0f,
 28           .pushbackPowerDistance = 0.25f,
 29         }
 30     },
 31     [TOWER_TYPE_CATAPULT] = {
 32         .cooldown = 1.7f,
 33         .range = 5.0f,
 34         .cost = 10,
 35         .maxHealth = 10,
 36         .projectileSpeed = 3.0f,
 37         .projectileType = PROJECTILE_TYPE_CATAPULT,
 38         .hitEffect = {
 39           .damage = 2.0f,
 40           .areaDamageRadius = 1.75f,
 41         }
 42     },
 43     [TOWER_TYPE_WALL] = {
 44         .cost = 2,
 45         .maxHealth = 10,
 46     },
 47 };
 48 
 49 Tower towers[TOWER_MAX_COUNT];
 50 int towerCount = 0;
 51 
 52 Model towerModels[TOWER_TYPE_COUNT];
 53 
 54 // definition of our archer unit
 55 SpriteUnit archerUnit = {
 56     .srcRect = {0, 0, 16, 16},
 57     .offset = {7, 1},
 58     .frameCount = 1,
 59     .frameDuration = 0.0f,
 60     .srcWeaponIdleRect = {16, 0, 6, 16},
 61     .srcWeaponIdleOffset = {8, 0},
 62     .srcWeaponCooldownRect = {22, 0, 11, 16},
 63     .srcWeaponCooldownOffset = {10, 0},
 64 };
 65 
 66 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 67 {
 68   float xScale = flip ? -1.0f : 1.0f;
 69   Camera3D camera = currentLevel->camera;
 70   float size = 0.5f;
 71   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size * xScale };
 72   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
 73   // we want the sprite to face the camera, so we need to calculate the up vector
 74   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 75   Vector3 up = {0, 1, 0};
 76   Vector3 right = Vector3CrossProduct(forward, up);
 77   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 78 
 79   Rectangle srcRect = unit.srcRect;
 80   if (unit.frameCount > 1)
 81   {
 82     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 83   }
 84   if (flip)
 85   {
 86     srcRect.x += srcRect.width;
 87     srcRect.width = -srcRect.width;
 88   }
 89   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 90   // move the sprite slightly towards the camera to avoid z-fighting
 91   position = Vector3Add(position, Vector3Scale(Vector3Normalize(forward), -0.01f));
 92 
 93   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 94   {
 95     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 96     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 97     srcRect = unit.srcWeaponCooldownRect;
 98     if (flip)
 99     {
100       // position.x = flip * scale.x * 0.5f;
101       srcRect.x += srcRect.width;
102       srcRect.width = -srcRect.width;
103       offset.x = scale.x - offset.x;
104     }
105     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
106   }
107   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
108   {
109     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
110     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
111     srcRect = unit.srcWeaponIdleRect;
112     if (flip)
113     {
114       // position.x = flip * scale.x * 0.5f;
115       srcRect.x += srcRect.width;
116       srcRect.width = -srcRect.width;
117       offset.x = scale.x - offset.x;
118     }
119     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
120   }
121 }
122 
123 void TowerInit()
124 {
125   for (int i = 0; i < TOWER_MAX_COUNT; i++)
126   {
127     towers[i] = (Tower){0};
128   }
129   towerCount = 0;
130 
131   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
132   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
133 
134   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
135   {
136     if (towerModels[i].materials)
137     {
138       // assign the palette texture to the material of the model (0 is not used afaik)
139       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
140     }
141   }
142 }
143 
144 static void TowerGunUpdate(Tower *tower)
145 {
146   TowerTypeConfig config = towerTypeConfigs[tower->towerType];
147   if (tower->cooldown <= 0.0f)
148   {
149     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, config.range);
150     if (enemy)
151     {
152       tower->cooldown = config.cooldown;
153       // shoot the enemy; determine future position of the enemy
154       float bulletSpeed = config.projectileSpeed;
155       Vector2 velocity = enemy->simVelocity;
156       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
157       Vector2 towerPosition = {tower->x, tower->y};
158       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
159       for (int i = 0; i < 8; i++) {
160         velocity = enemy->simVelocity;
161         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
162         float distance = Vector2Distance(towerPosition, futurePosition);
163         float eta2 = distance / bulletSpeed;
164         if (fabs(eta - eta2) < 0.01f) {
165           break;
166         }
167         eta = (eta2 + eta) * 0.5f;
168       }
169 
170       ProjectileTryAdd(config.projectileType, enemy, 
171         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
172         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
173         bulletSpeed, config.hitEffect);
174       enemy->futureDamage += config.hitEffect.damage;
175       tower->lastTargetPosition = futurePosition;
176     }
177   }
178   else
179   {
180     tower->cooldown -= gameTime.deltaTime;
181   }
182 }
183 
184 Tower *TowerGetAt(int16_t x, int16_t y)
185 {
186   for (int i = 0; i < towerCount; i++)
187   {
188     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
189     {
190       return &towers[i];
191     }
192   }
193   return 0;
194 }
195 
196 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
197 {
198   if (towerCount >= TOWER_MAX_COUNT)
199   {
200     return 0;
201   }
202 
203   Tower *tower = TowerGetAt(x, y);
204   if (tower)
205   {
206     return 0;
207   }
208 
209   tower = &towers[towerCount++];
210   tower->x = x;
211   tower->y = y;
212   tower->towerType = towerType;
213   tower->cooldown = 0.0f;
214   tower->damage = 0.0f;
215   return tower;
216 }
217 
218 Tower *GetTowerByType(uint8_t towerType)
219 {
220   for (int i = 0; i < towerCount; i++)
221   {
222     if (towers[i].towerType == towerType)
223     {
224       return &towers[i];
225     }
226   }
227   return 0;
228 }
229 
230 int GetTowerCosts(uint8_t towerType)
231 {
232   return towerTypeConfigs[towerType].cost;
233 }
234 
235 float TowerGetMaxHealth(Tower *tower)
236 {
237   return towerTypeConfigs[tower->towerType].maxHealth;
238 }
239 
240 void TowerDrawSingle(Tower tower)
241 {
242   if (tower.towerType == TOWER_TYPE_NONE)
243   {
244     return;
245   }
246 
247   switch (tower.towerType)
248   {
249   case TOWER_TYPE_ARCHER:
250     {
251       Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
252       Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
253       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
254       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
255         tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
256     }
257     break;
258   case TOWER_TYPE_BALLISTA:
259     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, BROWN);
260     break;
261   case TOWER_TYPE_CATAPULT:
262     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, DARKGRAY);
263     break;
264   default:
265     if (towerModels[tower.towerType].materials)
266     {
267       DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
268     } else {
269       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
270     }
271     break;
272   }
273 }
274 
275 void TowerDraw()
276 {
277   for (int i = 0; i < towerCount; i++)
278   {
279     TowerDrawSingle(towers[i]);
280   }
281 }
282 
283 void TowerUpdate()
284 {
285   for (int i = 0; i < towerCount; i++)
286   {
287     Tower *tower = &towers[i];
288     switch (tower->towerType)
289     {
290     case TOWER_TYPE_CATAPULT:
291     case TOWER_TYPE_BALLISTA:
292     case TOWER_TYPE_ARCHER:
293       TowerGunUpdate(tower);
294       break;
295     }
296   }
297 }
298 
299 void TowerDrawHealthBars(Camera3D camera)
300 {
301   for (int i = 0; i < towerCount; i++)
302   {
303     Tower *tower = &towers[i];
304     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
305     {
306       continue;
307     }
308     
309     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
310     float maxHealth = TowerGetMaxHealth(tower);
311     float health = maxHealth - tower->damage;
312     float healthRatio = health / maxHealth;
313     
314     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
315   }
316 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 typedef struct ProjectileConfig
  8 {
  9   float arcFactor;
 10   Color color;
 11   Color trailColor;
 12 } ProjectileConfig;
 13 
 14 ProjectileConfig projectileConfigs[] = {
 15     [PROJECTILE_TYPE_ARROW] = {
 16         .arcFactor = 0.15f,
 17         .color = RED,
 18         .trailColor = BROWN,
 19     },
 20     [PROJECTILE_TYPE_CATAPULT] = {
 21         .arcFactor = 0.5f,
 22         .color = RED,
 23         .trailColor = GRAY,
 24     },
 25     [PROJECTILE_TYPE_BALLISTA] = {
 26         .arcFactor = 0.025f,
 27         .color = RED,
 28         .trailColor = BROWN,
 29     },
 30 };
 31 
 32 void ProjectileInit()
 33 {
 34   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 35   {
 36     projectiles[i] = (Projectile){0};
 37   }
 38 }
 39 
 40 void ProjectileDraw()
 41 {
 42   for (int i = 0; i < projectileCount; i++)
 43   {
 44     Projectile projectile = projectiles[i];
 45     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 46     {
 47       continue;
 48     }
 49     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 50     if (transition >= 1.0f)
 51     {
 52       continue;
 53     }
 54 
 55     ProjectileConfig config = projectileConfigs[projectile.projectileType];
 56     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 57     {
 58       float t = transition + transitionOffset * 0.3f;
 59       if (t > 1.0f)
 60       {
 61         break;
 62       }
 63       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 64       Color color = config.color;
 65       color = ColorLerp(config.trailColor, config.color, transitionOffset * transitionOffset);
 66       // fake a ballista flight path using parabola equation
 67       float parabolaT = t - 0.5f;
 68       parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 69       position.y += config.arcFactor * parabolaT * projectile.distance;
 70       
 71       float size = 0.06f * (transitionOffset + 0.25f);
 72       DrawCube(position, size, size, size, color);
 73     }
 74   }
 75 }
 76 
 77 void ProjectileUpdate()
 78 {
 79   for (int i = 0; i < projectileCount; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 87     if (transition >= 1.0f)
 88     {
 89       projectile->projectileType = PROJECTILE_TYPE_NONE;
 90       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 91       if (enemy && projectile->hitEffectConfig.pushbackPowerDistance > 0.0f)
 92       {
 93           Vector2 direction = Vector2Normalize(Vector2Subtract((Vector2){projectile->target.x, projectile->target.z}, enemy->simPosition));
 94           enemy->simPosition = Vector2Add(enemy->simPosition, Vector2Scale(direction, projectile->hitEffectConfig.pushbackPowerDistance));
 95       }
 96       
 97       if (projectile->hitEffectConfig.areaDamageRadius > 0.0f)
 98       {
 99         EnemyAddDamageRange((Vector2){projectile->target.x, projectile->target.z}, projectile->hitEffectConfig.areaDamageRadius, projectile->hitEffectConfig.damage);
100         // pancaked sphere explosion
101         float r = projectile->hitEffectConfig.areaDamageRadius;
102         ParticleAdd(PARTICLE_TYPE_EXPLOSION, projectile->target, (Vector3){0}, (Vector3){r, r * 0.2f, r}, 0.33f);
103       }
104       else if (projectile->hitEffectConfig.damage > 0.0f && enemy)
105       {
106         EnemyAddDamage(enemy, projectile->hitEffectConfig.damage);
107       }
108       continue;
109     }
110   }
111 }
112 
113 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig)
114 {
115   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
116   {
117     Projectile *projectile = &projectiles[i];
118     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
119     {
120       projectile->projectileType = projectileType;
121       projectile->shootTime = gameTime.time;
122       float distance = Vector3Distance(position, target);
123       projectile->arrivalTime = gameTime.time + distance / speed;
124       projectile->position = position;
125       projectile->target = target;
126       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
127       projectile->distance = distance;
128       projectile->targetEnemy = EnemyGetId(enemy);
129       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
130       projectile->hitEffectConfig = hitEffectConfig;
131       return projectile;
132     }
133   }
134   return 0;
135 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 
  5 static Particle particles[PARTICLE_MAX_COUNT];
  6 static int particleCount = 0;
  7 
  8 void ParticleInit()
  9 {
 10   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 11   {
 12     particles[i] = (Particle){0};
 13   }
 14   particleCount = 0;
 15 }
 16 
 17 static void DrawExplosionParticle(Particle *particle, float transition)
 18 {
 19   Vector3 scale = particle->scale;
 20   float size = 1.0f * (1.0f - transition);
 21   Color startColor = WHITE;
 22   Color endColor = RED;
 23   Color color = ColorLerp(startColor, endColor, transition);
 24 
 25   rlPushMatrix();
 26   rlTranslatef(particle->position.x, particle->position.y, particle->position.z);
 27   rlScalef(scale.x, scale.y, scale.z);
 28   DrawSphere(Vector3Zero(), size, color);
 29   rlPopMatrix();
 30 }
 31 
 32 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime)
 33 {
 34   if (particleCount >= PARTICLE_MAX_COUNT)
 35   {
 36     return;
 37   }
 38 
 39   int index = -1;
 40   for (int i = 0; i < particleCount; i++)
 41   {
 42     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 43     {
 44       index = i;
 45       break;
 46     }
 47   }
 48 
 49   if (index == -1)
 50   {
 51     index = particleCount++;
 52   }
 53 
 54   Particle *particle = &particles[index];
 55   particle->particleType = particleType;
 56   particle->spawnTime = gameTime.time;
 57   particle->lifetime = lifetime;
 58   particle->position = position;
 59   particle->velocity = velocity;
 60   particle->scale = scale;
 61 }
 62 
 63 void ParticleUpdate()
 64 {
 65   for (int i = 0; i < particleCount; i++)
 66   {
 67     Particle *particle = &particles[i];
 68     if (particle->particleType == PARTICLE_TYPE_NONE)
 69     {
 70       continue;
 71     }
 72 
 73     float age = gameTime.time - particle->spawnTime;
 74 
 75     if (particle->lifetime > age)
 76     {
 77       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 78     }
 79     else {
 80       particle->particleType = PARTICLE_TYPE_NONE;
 81     }
 82   }
 83 }
 84 
 85 void ParticleDraw()
 86 {
 87   for (int i = 0; i < particleCount; i++)
 88   {
 89     Particle particle = particles[i];
 90     if (particle.particleType == PARTICLE_TYPE_NONE)
 91     {
 92       continue;
 93     }
 94 
 95     float age = gameTime.time - particle.spawnTime;
 96     float transition = age / particle.lifetime;
 97     switch (particle.particleType)
 98     {
 99     case PARTICLE_TYPE_EXPLOSION:
100       DrawExplosionParticle(&particle, transition);
101       break;
102     default:
103       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
104       break;
105     }
106   }
107 }  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endifJust adding the weapon to the minion to make it more unique reveals two rendering issues:
The weapon disappears behind another enemy sprite: This happens because the sprite is drawn in whatever order the enemies are in the list and the sprites draw to the z-buffer. When now another sprite is rendered behind, it will be drawn over the weapon.
There are multiple ways to fix this:
- Draw the sprites back to front: This way, they will be drawn in the correct order, never blocking each other.
- Use depth testing: The shader can discard fragments that are invisible due to the alpha value. Normally, the entire quad is drawn into the z-buffer, but the shader can discard the fragments that are invisible, so the z-buffer will not be updated for these fragments. For blurred sprites, this does not work so well.
- Just ignore the problem and draw without depth writing: We can just draw the sprites without writing to the z-buffer. This way, the sprites will be drawn in the order they are in the list, but the z-buffer will not be updated. This is the easiest way to fix the problem, but it can cause other issues, like sprites that should be behind another sprite being drawn in front.
Since we focus here on simplicity, I will first try the last option and see if it works. Sorting the sprites would be my second choice, but that would require a few more changes in the code.
The second problem is that there are lines on the edges of the sprites. This is due to the texture filtering and is called "bleeding" (see here).
Here is a picture of the sprite sheet and how the sprites are drawn:
The source rectangles from our atlas texture have not enough space between the sprites (padding), so the texture filtering is interpolating the colors of the adjacent pixels belonging to other sprites. Texture bleeding can be observed in may different situations. Light mapping is often suffers from the same problem.
To solve this probleme here, we only have to add a small margin to the sprites in the atlas texture. In this specific case, I can simply select a smaller rectangle from the sprite, since the sprites we use here are not using the entire space.
But in the future when we're adding new sprites, we have to keep this in mind.
Since we are not using mipmaps, a distance of 1 pixel is enough. As always, the topic can be quite complex; there are various different solutions to this problem each with different trade-offs. Luckily, we can ignore this for now.
In case you don't know what mipmaps are, you can read it up here.
Anyway, let's try out how the enemies look without depth writing and with a margin around the sprites.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 #include <stdlib.h>
  5 #include <math.h>
  6 
  7 //# Variables
  8 GUIState guiState = {0};
  9 GameTime gameTime = {
 10   .fixedDeltaTime = 1.0f / 60.0f,
 11 };
 12 
 13 Model floorTileAModel = {0};
 14 Model floorTileBModel = {0};
 15 Model treeModel[2] = {0};
 16 Model firTreeModel[2] = {0};
 17 Model rockModels[5] = {0};
 18 Model grassPatchModel[1] = {0};
 19 
 20 Model pathArrowModel = {0};
 21 Model greenArrowModel = {0};
 22 
 23 Texture2D palette, spriteSheet;
 24 
 25 Level levels[] = {
 26   [0] = {
 27     .state = LEVEL_STATE_BUILDING,
 28     .initialGold = 20,
 29     .waves[0] = {
 30       .enemyType = ENEMY_TYPE_MINION,
 31       .wave = 0,
 32       .count = 5,
 33       .interval = 2.5f,
 34       .delay = 1.0f,
 35       .spawnPosition = {2, 6},
 36     },
 37     .waves[1] = {
 38       .enemyType = ENEMY_TYPE_MINION,
 39       .wave = 0,
 40       .count = 5,
 41       .interval = 2.5f,
 42       .delay = 1.0f,
 43       .spawnPosition = {-2, 6},
 44     },
 45     .waves[2] = {
 46       .enemyType = ENEMY_TYPE_MINION,
 47       .wave = 1,
 48       .count = 20,
 49       .interval = 1.5f,
 50       .delay = 1.0f,
 51       .spawnPosition = {0, 6},
 52     },
 53     .waves[3] = {
 54       .enemyType = ENEMY_TYPE_MINION,
 55       .wave = 2,
 56       .count = 30,
 57       .interval = 1.2f,
 58       .delay = 1.0f,
 59       .spawnPosition = {0, 6},
 60     }
 61   },
 62 };
 63 
 64 Level *currentLevel = levels;
 65 
 66 //# Game
 67 
 68 static Model LoadGLBModel(char *filename)
 69 {
 70   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 71   for (int i = 0; i < model.materialCount; i++)
 72   {
 73     model.materials[i].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 74   }
 75   return model;
 76 }
 77 
 78 void LoadAssets()
 79 {
 80   // load a sprite sheet that contains all units
 81   spriteSheet = LoadTexture("data/spritesheet.png");
 82   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 83 
 84   // we'll use a palette texture to colorize the all buildings and environment art
 85   palette = LoadTexture("data/palette.png");
 86   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 87   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 88 
 89   floorTileAModel = LoadGLBModel("floor-tile-a");
 90   floorTileBModel = LoadGLBModel("floor-tile-b");
 91   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
 92   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
 93   firTreeModel[0] = LoadGLBModel("firtree-1-a");
 94   firTreeModel[1] = LoadGLBModel("firtree-1-b");
 95   rockModels[0] = LoadGLBModel("rock-1");
 96   rockModels[1] = LoadGLBModel("rock-2");
 97   rockModels[2] = LoadGLBModel("rock-3");
 98   rockModels[3] = LoadGLBModel("rock-4");
 99   rockModels[4] = LoadGLBModel("rock-5");
100   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
101 
102   pathArrowModel = LoadGLBModel("direction-arrow-x");
103   greenArrowModel = LoadGLBModel("green-arrow");
104 }
105 
106 void InitLevel(Level *level)
107 {
108   level->seed = (int)(GetTime() * 100.0f);
109 
110   TowerInit();
111   EnemyInit();
112   ProjectileInit();
113   ParticleInit();
114   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
115 
116   level->placementMode = 0;
117   level->state = LEVEL_STATE_BUILDING;
118   level->nextState = LEVEL_STATE_NONE;
119   level->playerGold = level->initialGold;
120   level->currentWave = 0;
121   level->placementX = -1;
122   level->placementY = 0;
123 
124   Camera *camera = &level->camera;
125   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
126   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
127   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
128   camera->fovy = 10.0f;
129   camera->projection = CAMERA_ORTHOGRAPHIC;
130 }
131 
132 void DrawLevelHud(Level *level)
133 {
134   const char *text = TextFormat("Gold: %d", level->playerGold);
135   Font font = GetFontDefault();
136   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
137   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
138 }
139 
140 void DrawLevelReportLostWave(Level *level)
141 {
142   BeginMode3D(level->camera);
143   DrawLevelGround(level);
144   TowerDraw();
145   EnemyDraw();
146   ProjectileDraw();
147   ParticleDraw();
148   guiState.isBlocked = 0;
149   EndMode3D();
150 
151   TowerDrawHealthBars(level->camera);
152 
153   const char *text = "Wave lost";
154   int textWidth = MeasureText(text, 20);
155   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
156 
157   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
158   {
159     level->nextState = LEVEL_STATE_RESET;
160   }
161 }
162 
163 int HasLevelNextWave(Level *level)
164 {
165   for (int i = 0; i < 10; i++)
166   {
167     EnemyWave *wave = &level->waves[i];
168     if (wave->wave == level->currentWave)
169     {
170       return 1;
171     }
172   }
173   return 0;
174 }
175 
176 void DrawLevelReportWonWave(Level *level)
177 {
178   BeginMode3D(level->camera);
179   DrawLevelGround(level);
180   TowerDraw();
181   EnemyDraw();
182   ProjectileDraw();
183   ParticleDraw();
184   guiState.isBlocked = 0;
185   EndMode3D();
186 
187   TowerDrawHealthBars(level->camera);
188 
189   const char *text = "Wave won";
190   int textWidth = MeasureText(text, 20);
191   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
192 
193 
194   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
195   {
196     level->nextState = LEVEL_STATE_RESET;
197   }
198 
199   if (HasLevelNextWave(level))
200   {
201     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
202     {
203       level->nextState = LEVEL_STATE_BUILDING;
204     }
205   }
206   else {
207     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
208     {
209       level->nextState = LEVEL_STATE_WON_LEVEL;
210     }
211   }
212 }
213 
214 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
215 {
216   static ButtonState buttonStates[8] = {0};
217   int cost = GetTowerCosts(towerType);
218   const char *text = TextFormat("%s: %d", name, cost);
219   buttonStates[towerType].isSelected = level->placementMode == towerType;
220   buttonStates[towerType].isDisabled = level->playerGold < cost;
221   if (Button(text, x, y, width, height, &buttonStates[towerType]))
222   {
223     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
224     level->nextState = LEVEL_STATE_BUILDING_PLACEMENT;
225   }
226 }
227 
228 float GetRandomFloat(float min, float max)
229 {
230   int random = GetRandomValue(0, 0xfffffff);
231   return ((float)random / (float)0xfffffff) * (max - min) + min;
232 }
233 
234 void DrawLevelGround(Level *level)
235 {
236   // draw checkerboard ground pattern
237   for (int x = -5; x <= 5; x += 1)
238   {
239     for (int y = -5; y <= 5; y += 1)
240     {
241       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
242       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
243     }
244   }
245 
246   int oldSeed = GetRandomValue(0, 0xfffffff);
247   SetRandomSeed(level->seed);
248   // increase probability for trees via duplicated entries
249   Model borderModels[64];
250   int maxRockCount = GetRandomValue(2, 6);
251   int maxTreeCount = GetRandomValue(10, 20);
252   int maxFirTreeCount = GetRandomValue(5, 10);
253   int maxLeafTreeCount = maxTreeCount - maxFirTreeCount;
254   int grassPatchCount = GetRandomValue(5, 30);
255 
256   int modelCount = 0;
257   for (int i = 0; i < maxRockCount && modelCount < 63; i++)
258   {
259     borderModels[modelCount++] = rockModels[GetRandomValue(0, 5)];
260   }
261   for (int i = 0; i < maxLeafTreeCount && modelCount < 63; i++)
262   {
263     borderModels[modelCount++] = treeModel[GetRandomValue(0, 1)];
264   }
265   for (int i = 0; i < maxFirTreeCount && modelCount < 63; i++)
266   {
267     borderModels[modelCount++] = firTreeModel[GetRandomValue(0, 1)];
268   }
269   for (int i = 0; i < grassPatchCount && modelCount < 63; i++)
270   {
271     borderModels[modelCount++] = grassPatchModel[0];
272   }
273 
274   // draw some objects around the border of the map
275   Vector3 up = {0, 1, 0};
276   // a pseudo random number generator to get the same result every time
277   const float wiggle = 0.75f;
278   const int layerCount = 3;
279   for (int layer = 0; layer < layerCount; layer++)
280   {
281     int layerPos = 6 + layer;
282     for (int x = -6 + layer; x <= 6 + layer; x += 1)
283     {
284       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
285         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, -layerPos + GetRandomFloat(0.0f, wiggle)}, 
286         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
287       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
288         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, layerPos + GetRandomFloat(0.0f, wiggle)}, 
289         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
290     }
291 
292     for (int z = -5 + layer; z <= 5 + layer; z += 1)
293     {
294       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
295         (Vector3){-layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
296         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
297       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
298         (Vector3){layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
299         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
300     }
301   }
302 
303   SetRandomSeed(oldSeed);
304 }
305 
306 void DrawEnemyPath(Level *level, Color arrowColor)
307 {
308   const int castleX = 0, castleY = 0;
309   const int maxWaypointCount = 200;
310   const float timeStep = 1.0f;
311   Vector3 arrowScale = {0.75f, 0.75f, 0.75f};
312 
313   // we start with a time offset to simulate the path, 
314   // this way the arrows are animated in a forward moving direction
315   // The time is wrapped around the time step to get a smooth animation
316   float timeOffset = fmodf(GetTime(), timeStep);
317 
318   for (int i = 0; i < ENEMY_MAX_WAVE_COUNT; i++)
319   {
320     EnemyWave *wave = &level->waves[i];
321     if (wave->wave != level->currentWave)
322     {
323       continue;
324     }
325 
326     // use this dummy enemy to simulate the path
327     Enemy dummy = {
328       .enemyType = ENEMY_TYPE_MINION,
329       .simPosition = (Vector2){wave->spawnPosition.x, wave->spawnPosition.y},
330       .nextX = wave->spawnPosition.x,
331       .nextY = wave->spawnPosition.y,
332       .currentX = wave->spawnPosition.x,
333       .currentY = wave->spawnPosition.y,
334     };
335 
336     float deltaTime = timeOffset;
337     for (int j = 0; j < maxWaypointCount; j++)
338     {
339       int waypointPassedCount = 0;
340       Vector2 pos = EnemyGetPosition(&dummy, deltaTime, &dummy.simVelocity, &waypointPassedCount);
341       // after the initial variable starting offset, we use a fixed time step
342       deltaTime = timeStep;
343       dummy.simPosition = pos;
344 
345       // Update the dummy's position just like we do in the regular enemy update loop
346       for (int k = 0; k < waypointPassedCount; k++)
347       {
348         dummy.currentX = dummy.nextX;
349         dummy.currentY = dummy.nextY;
350         if (EnemyGetNextPosition(dummy.currentX, dummy.currentY, &dummy.nextX, &dummy.nextY) &&
351           Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
352         {
353           break;
354         }
355       }
356       if (Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
357       {
358         break;
359       }
360       
361       // get the angle we need to rotate the arrow model. The velocity is just fine for this.
362       float angle = atan2f(dummy.simVelocity.x, dummy.simVelocity.y) * RAD2DEG - 90.0f;
363       DrawModelEx(pathArrowModel, (Vector3){pos.x, 0.15f, pos.y}, (Vector3){0, 1, 0}, angle, arrowScale, arrowColor);
364     }
365   }
366 }
367 
368 void DrawEnemyPaths(Level *level)
369 {
370   // disable depth testing for the path arrows
371   // flush the 3D batch to draw the arrows on top of everything
372   rlDrawRenderBatchActive();
373   rlDisableDepthTest();
374   DrawEnemyPath(level, (Color){64, 64, 64, 160});
375 
376   rlDrawRenderBatchActive();
377   rlEnableDepthTest();
378   DrawEnemyPath(level, WHITE);
379 }
380 
381 static void SimulateTowerPlacementBehavior(Level *level, float towerFloatHeight, float mapX, float mapY)
382 {
383   float dt = gameTime.fixedDeltaTime;
384   // smooth transition for the placement position using exponential decay
385   const float lambda = 15.0f;
386   float factor = 1.0f - expf(-lambda * dt);
387 
388   float damping = 0.5f;
389   float springStiffness = 300.0f;
390   float springDecay = 95.0f;
391   float minHeight = 0.35f;
392 
393   if (level->placementPhase == PLACEMENT_PHASE_STARTING)
394   {
395     damping = 1.0f;
396     springDecay = 90.0f;
397     springStiffness = 100.0f;
398     minHeight = 0.70f;
399   }
400 
401   for (int i = 0; i < gameTime.fixedStepCount; i++)
402   {
403     level->placementTransitionPosition = 
404       Vector2Lerp(
405         level->placementTransitionPosition, 
406         (Vector2){mapX, mapY}, factor);
407 
408     // draw the spring position for debugging the spring simulation
409     // first step: stiff spring, no simulation
410     Vector3 worldPlacementPosition = (Vector3){
411       level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
412     Vector3 springTargetPosition = (Vector3){
413       worldPlacementPosition.x, 1.0f + towerFloatHeight, worldPlacementPosition.z};
414     // consider the current velocity to predict the future position in order to dampen
415     // the spring simulation. Longer prediction times will result in more damping
416     Vector3 predictedSpringPosition = Vector3Add(level->placementTowerSpring.position, 
417       Vector3Scale(level->placementTowerSpring.velocity, dt * damping));
418     Vector3 springPointDelta = Vector3Subtract(springTargetPosition, predictedSpringPosition);
419     Vector3 velocityChange = Vector3Scale(springPointDelta, dt * springStiffness);
420     // decay velocity of the upright forcing spring
421     // This force acts like a 2nd spring that pulls the tip upright into the air above the
422     // base position
423     level->placementTowerSpring.velocity = Vector3Scale(level->placementTowerSpring.velocity, 1.0f - expf(-springDecay * dt));
424     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, velocityChange);
425 
426     // calculate length of the spring to calculate the force to apply to the placementTowerSpring position
427     // we use a simple spring model with a rest length of 1.0f
428     Vector3 springDelta = Vector3Subtract(worldPlacementPosition, level->placementTowerSpring.position);
429     float springLength = Vector3Length(springDelta);
430     float springForce = (springLength - 1.0f) * springStiffness;
431     Vector3 springForceVector = Vector3Normalize(springDelta);
432     springForceVector = Vector3Scale(springForceVector, springForce);
433     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, 
434       Vector3Scale(springForceVector, dt));
435 
436     level->placementTowerSpring.position = Vector3Add(level->placementTowerSpring.position, 
437       Vector3Scale(level->placementTowerSpring.velocity, dt));
438     if (level->placementTowerSpring.position.y < minHeight + towerFloatHeight)
439     {
440       level->placementTowerSpring.velocity.y *= -1.0f;
441       level->placementTowerSpring.position.y = fmaxf(level->placementTowerSpring.position.y, minHeight + towerFloatHeight);
442     }
443   }
444 }
445 
446 void DrawLevelBuildingPlacementState(Level *level)
447 {
448   const float placementDuration = 0.5f;
449 
450   level->placementTimer += gameTime.deltaTime;
451   if (level->placementTimer > 1.0f && level->placementPhase == PLACEMENT_PHASE_STARTING)
452   {
453     level->placementPhase = PLACEMENT_PHASE_MOVING;
454     level->placementTimer = 0.0f;
455   }
456 
457   BeginMode3D(level->camera);
458   DrawLevelGround(level);
459 
460   int blockedCellCount = 0;
461   Vector2 blockedCells[1];
462   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
463   float planeDistance = ray.position.y / -ray.direction.y;
464   float planeX = ray.direction.x * planeDistance + ray.position.x;
465   float planeY = ray.direction.z * planeDistance + ray.position.z;
466   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
467   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
468   if (level->placementPhase == PLACEMENT_PHASE_MOVING && 
469     level->placementMode && !guiState.isBlocked && 
470     mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5 && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
471   {
472     level->placementX = mapX;
473     level->placementY = mapY;
474   }
475   else
476   {
477     mapX = level->placementX;
478     mapY = level->placementY;
479   }
480   blockedCells[blockedCellCount++] = (Vector2){mapX, mapY};
481   PathFindingMapUpdate(blockedCellCount, blockedCells);
482 
483   TowerDraw();
484   EnemyDraw();
485   ProjectileDraw();
486   ParticleDraw();
487   DrawEnemyPaths(level);
488 
489   // let the tower float up and down. Consider this height in the spring simulation as well
490   float towerFloatHeight = sinf(gameTime.time * 4.0f) * 0.2f + 0.3f;
491 
492   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
493   {
494     // The bouncing spring needs a bit of outro time to look nice and complete. 
495     // So we scale the time so that the first 2/3rd of the placing phase handles the motion
496     // and the last 1/3rd is the outro physics (bouncing)
497     float t = fminf(1.0f, level->placementTimer / placementDuration * 1.5f);
498     // let towerFloatHeight describe parabola curve, starting at towerFloatHeight and ending at 0
499     float linearBlendHeight = (1.0f - t) * towerFloatHeight;
500     float parabola = (1.0f - ((t - 0.5f) * (t - 0.5f) * 4.0f)) * 2.0f;
501     towerFloatHeight = linearBlendHeight + parabola;
502   }
503 
504   SimulateTowerPlacementBehavior(level, towerFloatHeight, mapX, mapY);
505   
506   rlPushMatrix();
507   rlTranslatef(level->placementTransitionPosition.x, 0, level->placementTransitionPosition.y);
508 
509   rlPushMatrix();
510   rlTranslatef(0.0f, towerFloatHeight, 0.0f);
511   // calculate x and z rotation to align the model with the spring
512   Vector3 position = {level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
513   Vector3 towerUp = Vector3Subtract(level->placementTowerSpring.position, position);
514   Vector3 rotationAxis = Vector3CrossProduct(towerUp, (Vector3){0, 1, 0});
515   float angle = acosf(Vector3DotProduct(towerUp, (Vector3){0, 1, 0}) / Vector3Length(towerUp)) * RAD2DEG;
516   float springLength = Vector3Length(towerUp);
517   float towerStretch = fminf(fmaxf(springLength, 0.5f), 4.5f);
518   float towerSquash = 1.0f / towerStretch;
519   rlRotatef(-angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
520   rlScalef(towerSquash, towerStretch, towerSquash);
521   Tower dummy = {
522     .towerType = level->placementMode,
523   };
524   TowerDrawSingle(dummy);
525   rlPopMatrix();
526 
527   // draw a shadow for the tower
528   float umbrasize = 0.8 + sqrtf(towerFloatHeight);
529   DrawCube((Vector3){0.0f, 0.05f, 0.0f}, umbrasize, 0.0f, umbrasize, (Color){0, 0, 0, 32});
530   DrawCube((Vector3){0.0f, 0.075f, 0.0f}, 0.85f, 0.0f, 0.85f, (Color){0, 0, 0, 64});
531 
532 
533   float bounce = sinf(gameTime.time * 8.0f) * 0.5f + 0.5f;
534   float offset = fmaxf(0.0f, bounce - 0.4f) * 0.35f + 0.7f;
535   float squeeze = -fminf(0.0f, bounce - 0.4f) * 0.7f + 0.7f;
536   float stretch = fmaxf(0.0f, bounce - 0.3f) * 0.5f + 0.8f;
537   
538   DrawModelEx(greenArrowModel, (Vector3){ offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 90, (Vector3){squeeze, 1.0f, stretch}, WHITE);
539   DrawModelEx(greenArrowModel, (Vector3){-offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 270, (Vector3){squeeze, 1.0f, stretch}, WHITE);
540   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f,  offset}, (Vector3){0, 1, 0}, 0, (Vector3){squeeze, 1.0f, stretch}, WHITE);
541   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f, -offset}, (Vector3){0, 1, 0}, 180, (Vector3){squeeze, 1.0f, stretch}, WHITE);
542   rlPopMatrix();
543 
544   guiState.isBlocked = 0;
545 
546   EndMode3D();
547 
548   TowerDrawHealthBars(level->camera);
549 
550   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
551   {
552     if (level->placementTimer > placementDuration)
553     {
554         TowerTryAdd(level->placementMode, mapX, mapY);
555         level->playerGold -= GetTowerCosts(level->placementMode);
556         level->nextState = LEVEL_STATE_BUILDING;
557         level->placementMode = TOWER_TYPE_NONE;
558     }
559   }
560   else
561   {   
562     if (Button("Cancel", 20, GetScreenHeight() - 40, 160, 30, 0))
563     {
564       level->nextState = LEVEL_STATE_BUILDING;
565       level->placementMode = TOWER_TYPE_NONE;
566       TraceLog(LOG_INFO, "Cancel building");
567     }
568     
569     if (TowerGetAt(mapX, mapY) == 0 &&  Button("Build", GetScreenWidth() - 180, GetScreenHeight() - 40, 160, 30, 0))
570     {
571       level->placementPhase = PLACEMENT_PHASE_PLACING;
572       level->placementTimer = 0.0f;
573     }
574   }
575 }
576 
577 void DrawLevelBuildingState(Level *level)
578 {
579   BeginMode3D(level->camera);
580   DrawLevelGround(level);
581 
582   PathFindingMapUpdate(0, 0);
583   TowerDraw();
584   EnemyDraw();
585   ProjectileDraw();
586   ParticleDraw();
587   DrawEnemyPaths(level);
588 
589   guiState.isBlocked = 0;
590 
591   EndMode3D();
592 
593   TowerDrawHealthBars(level->camera);
594 
595   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
596   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_ARCHER, "Archer");
597   DrawBuildingBuildButton(level, 10, 90, 110, 30, TOWER_TYPE_BALLISTA, "Ballista");
598   DrawBuildingBuildButton(level, 10, 130, 110, 30, TOWER_TYPE_CATAPULT, "Catapult");
599 
600   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
601   {
602     level->nextState = LEVEL_STATE_RESET;
603   }
604   
605   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
606   {
607     level->nextState = LEVEL_STATE_BATTLE;
608   }
609 
610   const char *text = "Building phase";
611   int textWidth = MeasureText(text, 20);
612   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
613 }
614 
615 void InitBattleStateConditions(Level *level)
616 {
617   level->state = LEVEL_STATE_BATTLE;
618   level->nextState = LEVEL_STATE_NONE;
619   level->waveEndTimer = 0.0f;
620   for (int i = 0; i < 10; i++)
621   {
622     EnemyWave *wave = &level->waves[i];
623     wave->spawned = 0;
624     wave->timeToSpawnNext = wave->delay;
625   }
626 }
627 
628 void DrawLevelBattleState(Level *level)
629 {
630   BeginMode3D(level->camera);
631   DrawLevelGround(level);
632   TowerDraw();
633   EnemyDraw();
634   ProjectileDraw();
635   ParticleDraw();
636   guiState.isBlocked = 0;
637   EndMode3D();
638 
639   EnemyDrawHealthbars(level->camera);
640   TowerDrawHealthBars(level->camera);
641 
642   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
643   {
644     level->nextState = LEVEL_STATE_RESET;
645   }
646 
647   int maxCount = 0;
648   int remainingCount = 0;
649   for (int i = 0; i < 10; i++)
650   {
651     EnemyWave *wave = &level->waves[i];
652     if (wave->wave != level->currentWave)
653     {
654       continue;
655     }
656     maxCount += wave->count;
657     remainingCount += wave->count - wave->spawned;
658   }
659   int aliveCount = EnemyCount();
660   remainingCount += aliveCount;
661 
662   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
663   int textWidth = MeasureText(text, 20);
664   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
665 }
666 
667 void DrawLevel(Level *level)
668 {
669   switch (level->state)
670   {
671     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
672     case LEVEL_STATE_BUILDING_PLACEMENT: DrawLevelBuildingPlacementState(level); break;
673     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
674     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
675     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
676     default: break;
677   }
678 
679   DrawLevelHud(level);
680 }
681 
682 void UpdateLevel(Level *level)
683 {
684   if (level->state == LEVEL_STATE_BATTLE)
685   {
686     int activeWaves = 0;
687     for (int i = 0; i < 10; i++)
688     {
689       EnemyWave *wave = &level->waves[i];
690       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
691       {
692         continue;
693       }
694       activeWaves++;
695       wave->timeToSpawnNext -= gameTime.deltaTime;
696       if (wave->timeToSpawnNext <= 0.0f)
697       {
698         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
699         if (enemy)
700         {
701           wave->timeToSpawnNext = wave->interval;
702           wave->spawned++;
703         }
704       }
705     }
706     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
707       level->waveEndTimer += gameTime.deltaTime;
708       if (level->waveEndTimer >= 2.0f)
709       {
710         level->nextState = LEVEL_STATE_LOST_WAVE;
711       }
712     }
713     else if (activeWaves == 0 && EnemyCount() == 0)
714     {
715       level->waveEndTimer += gameTime.deltaTime;
716       if (level->waveEndTimer >= 2.0f)
717       {
718         level->nextState = LEVEL_STATE_WON_WAVE;
719       }
720     }
721   }
722 
723   PathFindingMapUpdate(0, 0);
724   EnemyUpdate();
725   TowerUpdate();
726   ProjectileUpdate();
727   ParticleUpdate();
728 
729   if (level->nextState == LEVEL_STATE_RESET)
730   {
731     InitLevel(level);
732   }
733   
734   if (level->nextState == LEVEL_STATE_BATTLE)
735   {
736     InitBattleStateConditions(level);
737   }
738   
739   if (level->nextState == LEVEL_STATE_WON_WAVE)
740   {
741     level->currentWave++;
742     level->state = LEVEL_STATE_WON_WAVE;
743   }
744   
745   if (level->nextState == LEVEL_STATE_LOST_WAVE)
746   {
747     level->state = LEVEL_STATE_LOST_WAVE;
748   }
749 
750   if (level->nextState == LEVEL_STATE_BUILDING)
751   {
752     level->state = LEVEL_STATE_BUILDING;
753   }
754 
755   if (level->nextState == LEVEL_STATE_BUILDING_PLACEMENT)
756   {
757     level->state = LEVEL_STATE_BUILDING_PLACEMENT;
758     level->placementTransitionPosition = (Vector2){
759       level->placementX, level->placementY};
760     // initialize the spring to the current position
761     level->placementTowerSpring = (PhysicsPoint){
762       .position = (Vector3){level->placementX, 8.0f, level->placementY},
763       .velocity = (Vector3){0.0f, 0.0f, 0.0f},
764     };
765     level->placementPhase = PLACEMENT_PHASE_STARTING;
766     level->placementTimer = 0.0f;
767   }
768 
769   if (level->nextState == LEVEL_STATE_WON_LEVEL)
770   {
771     // make something of this later
772     InitLevel(level);
773   }
774 
775   level->nextState = LEVEL_STATE_NONE;
776 }
777 
778 float nextSpawnTime = 0.0f;
779 
780 void ResetGame()
781 {
782   InitLevel(currentLevel);
783 }
784 
785 void InitGame()
786 {
787   TowerInit();
788   EnemyInit();
789   ProjectileInit();
790   ParticleInit();
791   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
792 
793   currentLevel = levels;
794   InitLevel(currentLevel);
795 }
796 
797 //# Immediate GUI functions
798 
799 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
800 {
801   const float healthBarHeight = 6.0f;
802   const float healthBarOffset = 15.0f;
803   const float inset = 2.0f;
804   const float innerWidth = healthBarWidth - inset * 2;
805   const float innerHeight = healthBarHeight - inset * 2;
806 
807   Vector2 screenPos = GetWorldToScreen(position, camera);
808   float centerX = screenPos.x - healthBarWidth * 0.5f;
809   float topY = screenPos.y - healthBarOffset;
810   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
811   float healthWidth = innerWidth * healthRatio;
812   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
813 }
814 
815 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
816 {
817   Rectangle bounds = {x, y, width, height};
818   int isPressed = 0;
819   int isSelected = state && state->isSelected;
820   int isDisabled = state && state->isDisabled;
821   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
822   {
823     Color color = isSelected ? DARKGRAY : GRAY;
824     DrawRectangle(x, y, width, height, color);
825     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
826     {
827       isPressed = 1;
828     }
829     guiState.isBlocked = 1;
830   }
831   else
832   {
833     Color color = isSelected ? WHITE : LIGHTGRAY;
834     DrawRectangle(x, y, width, height, color);
835   }
836   Font font = GetFontDefault();
837   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
838   Color textColor = isDisabled ? GRAY : BLACK;
839   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
840   return isPressed;
841 }
842 
843 //# Main game loop
844 
845 void GameUpdate()
846 {
847   UpdateLevel(currentLevel);
848 }
849 
850 int main(void)
851 {
852   int screenWidth, screenHeight;
853   GetPreferredSize(&screenWidth, &screenHeight);
854   InitWindow(screenWidth, screenHeight, "Tower defense");
855   float gamespeed = 1.0f;
856   SetTargetFPS(30);
857 
858   LoadAssets();
859   InitGame();
860 
861   float pause = 1.0f;
862 
863   while (!WindowShouldClose())
864   {
865     if (IsPaused()) {
866       // canvas is not visible in browser - do nothing
867       continue;
868     }
869 
870     if (IsKeyPressed(KEY_T))
871     {
872       gamespeed += 0.1f;
873       if (gamespeed > 1.05f) gamespeed = 0.1f;
874     }
875 
876     if (IsKeyPressed(KEY_P))
877     {
878       pause = pause > 0.5f ? 0.0f : 1.0f;
879     }
880 
881     float dt = GetFrameTime() * gamespeed * pause;
882     // cap maximum delta time to 0.1 seconds to prevent large time steps
883     if (dt > 0.1f) dt = 0.1f;
884     gameTime.time += dt;
885     gameTime.deltaTime = dt;
886     gameTime.frameCount += 1;
887 
888     float fixedTimeDiff = gameTime.time - gameTime.fixedTimeStart;
889     gameTime.fixedStepCount = (uint8_t)(fixedTimeDiff / gameTime.fixedDeltaTime);
890 
891     BeginDrawing();
892     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
893 
894     GameUpdate();
895     DrawLevel(currentLevel);
896 
897     DrawText(TextFormat("Speed: %.1f", gamespeed), GetScreenWidth() - 180, 60, 20, WHITE);
898     EndDrawing();
899 
900     gameTime.fixedTimeStart += gameTime.fixedStepCount * gameTime.fixedDeltaTime;
901   }
902 
903   CloseWindow();
904 
905   return 0;
906 }  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 //# Declarations
 10 
 11 typedef struct PhysicsPoint
 12 {
 13   Vector3 position;
 14   Vector3 velocity;
 15 } PhysicsPoint;
 16 
 17 #define ENEMY_MAX_PATH_COUNT 8
 18 #define ENEMY_MAX_COUNT 400
 19 #define ENEMY_TYPE_NONE 0
 20 
 21 #define ENEMY_TYPE_MINION 1
 22 #define ENEMY_TYPE_RUNNER 2
 23 #define ENEMY_TYPE_SHIELD 3
 24 #define ENEMY_TYPE_BOSS 3
 25 
 26 #define PARTICLE_MAX_COUNT 400
 27 #define PARTICLE_TYPE_NONE 0
 28 #define PARTICLE_TYPE_EXPLOSION 1
 29 
 30 typedef struct Particle
 31 {
 32   uint8_t particleType;
 33   float spawnTime;
 34   float lifetime;
 35   Vector3 position;
 36   Vector3 velocity;
 37   Vector3 scale;
 38 } Particle;
 39 
 40 #define TOWER_MAX_COUNT 400
 41 enum TowerType
 42 {
 43   TOWER_TYPE_NONE,
 44   TOWER_TYPE_BASE,
 45   TOWER_TYPE_ARCHER,
 46   TOWER_TYPE_BALLISTA,
 47   TOWER_TYPE_CATAPULT,
 48   TOWER_TYPE_WALL,
 49   TOWER_TYPE_COUNT
 50 };
 51 
 52 typedef struct HitEffectConfig
 53 {
 54   float damage;
 55   float areaDamageRadius;
 56   float pushbackPowerDistance;
 57 } HitEffectConfig;
 58 
 59 typedef struct TowerTypeConfig
 60 {
 61   float cooldown;
 62   float range;
 63   float projectileSpeed;
 64   
 65   uint8_t cost;
 66   uint8_t projectileType;
 67   uint16_t maxHealth;
 68 
 69   HitEffectConfig hitEffect;
 70 } TowerTypeConfig;
 71 
 72 typedef struct Tower
 73 {
 74   int16_t x, y;
 75   uint8_t towerType;
 76   Vector2 lastTargetPosition;
 77   float cooldown;
 78   float damage;
 79 } Tower;
 80 
 81 typedef struct GameTime
 82 {
 83   float time;
 84   float deltaTime;
 85   uint32_t frameCount;
 86 
 87   float fixedDeltaTime;
 88   // leaving the fixed time stepping to the update functions,
 89   // we need to know the fixed time at the start of the frame
 90   float fixedTimeStart;
 91   // and the number of fixed steps that we have to make this frame
 92   // The fixedTime is fixedTimeStart + n * fixedStepCount
 93   uint8_t fixedStepCount;
 94 } GameTime;
 95 
 96 typedef struct ButtonState {
 97   char isSelected;
 98   char isDisabled;
 99 } ButtonState;
100 
101 typedef struct GUIState {
102   int isBlocked;
103 } GUIState;
104 
105 typedef enum LevelState
106 {
107   LEVEL_STATE_NONE,
108   LEVEL_STATE_BUILDING,
109   LEVEL_STATE_BUILDING_PLACEMENT,
110   LEVEL_STATE_BATTLE,
111   LEVEL_STATE_WON_WAVE,
112   LEVEL_STATE_LOST_WAVE,
113   LEVEL_STATE_WON_LEVEL,
114   LEVEL_STATE_RESET,
115 } LevelState;
116 
117 typedef struct EnemyWave {
118   uint8_t enemyType;
119   uint8_t wave;
120   uint16_t count;
121   float interval;
122   float delay;
123   Vector2 spawnPosition;
124 
125   uint16_t spawned;
126   float timeToSpawnNext;
127 } EnemyWave;
128 
129 #define ENEMY_MAX_WAVE_COUNT 10
130 
131 typedef enum PlacementPhase
132 {
133   PLACEMENT_PHASE_STARTING,
134   PLACEMENT_PHASE_MOVING,
135   PLACEMENT_PHASE_PLACING,
136 } PlacementPhase;
137 
138 typedef struct Level
139 {
140   int seed;
141   LevelState state;
142   LevelState nextState;
143   Camera3D camera;
144   int placementMode;
145   PlacementPhase placementPhase;
146   float placementTimer;
147   int16_t placementX;
148   int16_t placementY;
149   Vector2 placementTransitionPosition;
150   PhysicsPoint placementTowerSpring;
151 
152   int initialGold;
153   int playerGold;
154 
155   EnemyWave waves[ENEMY_MAX_WAVE_COUNT];
156   int currentWave;
157   float waveEndTimer;
158 } Level;
159 
160 typedef struct DeltaSrc
161 {
162   char x, y;
163 } DeltaSrc;
164 
165 typedef struct PathfindingMap
166 {
167   int width, height;
168   float scale;
169   float *distances;
170   long *towerIndex; 
171   DeltaSrc *deltaSrc;
172   float maxDistance;
173   Matrix toMapSpace;
174   Matrix toWorldSpace;
175 } PathfindingMap;
176 
177 // when we execute the pathfinding algorithm, we need to store the active nodes
178 // in a queue. Each node has a position, a distance from the start, and the
179 // position of the node that we came from.
180 typedef struct PathfindingNode
181 {
182   int16_t x, y, fromX, fromY;
183   float distance;
184 } PathfindingNode;
185 
186 typedef struct EnemyId
187 {
188   uint16_t index;
189   uint16_t generation;
190 } EnemyId;
191 
192 typedef struct EnemyClassConfig
193 {
194   float speed;
195   float health;
196   float radius;
197   float maxAcceleration;
198   float requiredContactTime;
199   float explosionDamage;
200   float explosionRange;
201   float explosionPushbackPower;
202   int goldValue;
203 } EnemyClassConfig;
204 
205 typedef struct Enemy
206 {
207   int16_t currentX, currentY;
208   int16_t nextX, nextY;
209   Vector2 simPosition;
210   Vector2 simVelocity;
211   uint16_t generation;
212   float walkedDistance;
213   float startMovingTime;
214   float damage, futureDamage;
215   float contactTime;
216   uint8_t enemyType;
217   uint8_t movePathCount;
218   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
219 } Enemy;
220 
221 // a unit that uses sprites to be drawn
222 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
223 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
224 typedef struct SpriteUnit
225 {
226   Rectangle srcRect;
227   Vector2 offset;
228   int frameCount;
229   float frameDuration;
230   Rectangle srcWeaponIdleRect;
231   Vector2 srcWeaponIdleOffset;
232   Rectangle srcWeaponCooldownRect;
233   Vector2 srcWeaponCooldownOffset;
234 } SpriteUnit;
235 
236 #define PROJECTILE_MAX_COUNT 1200
237 #define PROJECTILE_TYPE_NONE 0
238 #define PROJECTILE_TYPE_ARROW 1
239 #define PROJECTILE_TYPE_CATAPULT 2
240 #define PROJECTILE_TYPE_BALLISTA 3
241 
242 typedef struct Projectile
243 {
244   uint8_t projectileType;
245   float shootTime;
246   float arrivalTime;
247   float distance;
248   Vector3 position;
249   Vector3 target;
250   Vector3 directionNormal;
251   EnemyId targetEnemy;
252   HitEffectConfig hitEffectConfig;
253 } Projectile;
254 
255 //# Function declarations
256 float TowerGetMaxHealth(Tower *tower);
257 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
258 int EnemyAddDamageRange(Vector2 position, float range, float damage);
259 int EnemyAddDamage(Enemy *enemy, float damage);
260 
261 //# Enemy functions
262 void EnemyInit();
263 void EnemyDraw();
264 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
265 void EnemyUpdate();
266 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
267 float EnemyGetMaxHealth(Enemy *enemy);
268 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
269 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
270 EnemyId EnemyGetId(Enemy *enemy);
271 Enemy *EnemyTryResolve(EnemyId enemyId);
272 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
273 int EnemyAddDamage(Enemy *enemy, float damage);
274 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
275 int EnemyCount();
276 void EnemyDrawHealthbars(Camera3D camera);
277 
278 //# Tower functions
279 void TowerInit();
280 Tower *TowerGetAt(int16_t x, int16_t y);
281 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
282 Tower *GetTowerByType(uint8_t towerType);
283 int GetTowerCosts(uint8_t towerType);
284 float TowerGetMaxHealth(Tower *tower);
285 void TowerDraw();
286 void TowerDrawSingle(Tower tower);
287 void TowerUpdate();
288 void TowerDrawHealthBars(Camera3D camera);
289 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
290 
291 //# Particles
292 void ParticleInit();
293 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime);
294 void ParticleUpdate();
295 void ParticleDraw();
296 
297 //# Projectiles
298 void ProjectileInit();
299 void ProjectileDraw();
300 void ProjectileUpdate();
301 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig);
302 
303 //# Pathfinding map
304 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
305 float PathFindingGetDistance(int mapX, int mapY);
306 Vector2 PathFindingGetGradient(Vector3 world);
307 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
308 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells);
309 void PathFindingMapDraw();
310 
311 //# UI
312 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
313 
314 //# Level
315 void DrawLevelGround(Level *level);
316 void DrawEnemyPath(Level *level, Color arrowColor);
317 
318 //# variables
319 extern Level *currentLevel;
320 extern Enemy enemies[ENEMY_MAX_COUNT];
321 extern int enemyCount;
322 extern EnemyClassConfig enemyClassConfigs[];
323 
324 extern GUIState guiState;
325 extern GameTime gameTime;
326 extern Tower towers[TOWER_MAX_COUNT];
327 extern int towerCount;
328 
329 extern Texture2D palette, spriteSheet;
330 
331 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 #include <rlgl.h>
  6 
  7 EnemyClassConfig enemyClassConfigs[] = {
  8     [ENEMY_TYPE_MINION] = {
  9       .health = 10.0f, 
 10       .speed = 0.6f, 
 11       .radius = 0.25f, 
 12       .maxAcceleration = 1.0f,
 13       .explosionDamage = 1.0f,
 14       .requiredContactTime = 0.5f,
 15       .explosionRange = 1.0f,
 16       .explosionPushbackPower = 0.25f,
 17       .goldValue = 1,
 18     },
 19     [ENEMY_TYPE_RUNNER] = {
 20       .health = 5.0f, 
 21       .speed = 1.0f, 
 22       .radius = 0.25f, 
 23       .maxAcceleration = 2.0f,
 24       .explosionDamage = 1.0f,
 25       .requiredContactTime = 0.5f,
 26       .explosionRange = 1.0f,
 27       .explosionPushbackPower = 0.25f,
 28       .goldValue = 2,
 29     },
 30 };
 31 
 32 Enemy enemies[ENEMY_MAX_COUNT];
 33 int enemyCount = 0;
 34 
 35 SpriteUnit enemySprites[] = {
 36     [ENEMY_TYPE_MINION] = {
 37       .srcRect = {0, 17, 16, 15},
 38       .offset = {8.0f, 0.0f},
 39       .frameCount = 6,
 40       .frameDuration = 0.1f,
 41       .srcWeaponIdleRect = {1, 33, 14, 14},
 42       .srcWeaponIdleOffset = {7.0f, 0.0f},
 43     },
 44     [ENEMY_TYPE_RUNNER] = {
 45       .srcRect = {0, 16, 16, 16},
 46       .offset = {8.0f, 0.0f},
 47       .frameCount = 6,
 48       .frameDuration = 0.1f,
 49     },
 50 };
 51 
 52 void EnemyInit()
 53 {
 54   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 55   {
 56     enemies[i] = (Enemy){0};
 57   }
 58   enemyCount = 0;
 59 }
 60 
 61 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 62 {
 63   return enemyClassConfigs[enemy->enemyType].speed;
 64 }
 65 
 66 float EnemyGetMaxHealth(Enemy *enemy)
 67 {
 68   return enemyClassConfigs[enemy->enemyType].health;
 69 }
 70 
 71 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 72 {
 73   int16_t castleX = 0;
 74   int16_t castleY = 0;
 75   int16_t dx = castleX - currentX;
 76   int16_t dy = castleY - currentY;
 77   if (dx == 0 && dy == 0)
 78   {
 79     *nextX = currentX;
 80     *nextY = currentY;
 81     return 1;
 82   }
 83   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 84 
 85   if (gradient.x == 0 && gradient.y == 0)
 86   {
 87     *nextX = currentX;
 88     *nextY = currentY;
 89     return 1;
 90   }
 91 
 92   if (fabsf(gradient.x) > fabsf(gradient.y))
 93   {
 94     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
 95     *nextY = currentY;
 96     return 0;
 97   }
 98   *nextX = currentX;
 99   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
100   return 0;
101 }
102 
103 
104 // this function predicts the movement of the unit for the next deltaT seconds
105 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
106 {
107   const float pointReachedDistance = 0.25f;
108   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
109   const float maxSimStepTime = 0.015625f;
110   
111   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
112   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
113   int16_t nextX = enemy->nextX;
114   int16_t nextY = enemy->nextY;
115   Vector2 position = enemy->simPosition;
116   int passedCount = 0;
117   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
118   {
119     float stepTime = fminf(deltaT - t, maxSimStepTime);
120     Vector2 target = (Vector2){nextX, nextY};
121     float speed = Vector2Length(*velocity);
122     // draw the target position for debugging
123     //DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
124     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
125     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
126     {
127       // we reached the target position, let's move to the next waypoint
128       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
129       target = (Vector2){nextX, nextY};
130       // track how many waypoints we passed
131       passedCount++;
132     }
133     
134     // acceleration towards the target
135     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
136     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
137     *velocity = Vector2Add(*velocity, acceleration);
138 
139     // limit the speed to the maximum speed
140     if (speed > maxSpeed)
141     {
142       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
143     }
144 
145     // move the enemy
146     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
147   }
148 
149   if (waypointPassedCount)
150   {
151     (*waypointPassedCount) = passedCount;
152   }
153 
154   return position;
155 }
156 
157 void EnemyDraw()
158 {
159   rlDrawRenderBatchActive();
160   rlDisableDepthMask();
161   for (int i = 0; i < enemyCount; i++)
162   {
163     Enemy enemy = enemies[i];
164     if (enemy.enemyType == ENEMY_TYPE_NONE)
165     {
166       continue;
167     }
168 
169     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
170     
171     // don't draw any trails for now; might replace this with footprints later
172     // if (enemy.movePathCount > 0)
173     // {
174     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
175     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
176     // }
177     // for (int j = 1; j < enemy.movePathCount; j++)
178     // {
179     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
180     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
181     //   DrawLine3D(p, q, GREEN);
182     // }
183 
184     switch (enemy.enemyType)
185     {
186     case ENEMY_TYPE_MINION:
187       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y}, 
188         enemy.walkedDistance, 0, 0);
189       break;
190     }
191   }
192   rlDrawRenderBatchActive();
193   rlEnableDepthMask();
194 }
195 
196 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
197 {
198   // damage the tower
199   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
200   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
201   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
202   float explosionRange2 = explosionRange * explosionRange;
203   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
204   // explode the enemy
205   if (tower->damage >= TowerGetMaxHealth(tower))
206   {
207     tower->towerType = TOWER_TYPE_NONE;
208   }
209 
210   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
211     explosionSource, 
212     (Vector3){0, 0.1f, 0}, (Vector3){1.0f, 1.0f, 1.0f}, 1.0f);
213 
214   enemy->enemyType = ENEMY_TYPE_NONE;
215 
216   // push back enemies & dealing damage
217   for (int i = 0; i < enemyCount; i++)
218   {
219     Enemy *other = &enemies[i];
220     if (other->enemyType == ENEMY_TYPE_NONE)
221     {
222       continue;
223     }
224     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
225     if (distanceSqr > 0 && distanceSqr < explosionRange2)
226     {
227       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
228       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
229       EnemyAddDamage(other, explosionDamge);
230     }
231   }
232 }
233 
234 void EnemyUpdate()
235 {
236   const float castleX = 0;
237   const float castleY = 0;
238   const float maxPathDistance2 = 0.25f * 0.25f;
239   
240   for (int i = 0; i < enemyCount; i++)
241   {
242     Enemy *enemy = &enemies[i];
243     if (enemy->enemyType == ENEMY_TYPE_NONE)
244     {
245       continue;
246     }
247 
248     int waypointPassedCount = 0;
249     Vector2 prevPosition = enemy->simPosition;
250     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
251     enemy->startMovingTime = gameTime.time;
252     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
253     // track path of unit
254     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
255     {
256       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
257       {
258         enemy->movePath[j] = enemy->movePath[j - 1];
259       }
260       enemy->movePath[0] = enemy->simPosition;
261       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
262       {
263         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
264       }
265     }
266 
267     if (waypointPassedCount > 0)
268     {
269       enemy->currentX = enemy->nextX;
270       enemy->currentY = enemy->nextY;
271       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
272         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
273       {
274         // enemy reached the castle; remove it
275         enemy->enemyType = ENEMY_TYPE_NONE;
276         continue;
277       }
278     }
279   }
280 
281   // handle collisions between enemies
282   for (int i = 0; i < enemyCount - 1; i++)
283   {
284     Enemy *enemyA = &enemies[i];
285     if (enemyA->enemyType == ENEMY_TYPE_NONE)
286     {
287       continue;
288     }
289     for (int j = i + 1; j < enemyCount; j++)
290     {
291       Enemy *enemyB = &enemies[j];
292       if (enemyB->enemyType == ENEMY_TYPE_NONE)
293       {
294         continue;
295       }
296       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
297       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
298       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
299       float radiusSum = radiusA + radiusB;
300       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
301       {
302         // collision
303         float distance = sqrtf(distanceSqr);
304         float overlap = radiusSum - distance;
305         // move the enemies apart, but softly; if we have a clog of enemies,
306         // moving them perfectly apart can cause them to jitter
307         float positionCorrection = overlap / 5.0f;
308         Vector2 direction = (Vector2){
309             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
310             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
311         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
312         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
313       }
314     }
315   }
316 
317   // handle collisions between enemies and towers
318   for (int i = 0; i < enemyCount; i++)
319   {
320     Enemy *enemy = &enemies[i];
321     if (enemy->enemyType == ENEMY_TYPE_NONE)
322     {
323       continue;
324     }
325     enemy->contactTime -= gameTime.deltaTime;
326     if (enemy->contactTime < 0.0f)
327     {
328       enemy->contactTime = 0.0f;
329     }
330 
331     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
332     // linear search over towers; could be optimized by using path finding tower map,
333     // but for now, we keep it simple
334     for (int j = 0; j < towerCount; j++)
335     {
336       Tower *tower = &towers[j];
337       if (tower->towerType == TOWER_TYPE_NONE)
338       {
339         continue;
340       }
341       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
342       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
343       if (distanceSqr > combinedRadius * combinedRadius)
344       {
345         continue;
346       }
347       // potential collision; square / circle intersection
348       float dx = tower->x - enemy->simPosition.x;
349       float dy = tower->y - enemy->simPosition.y;
350       float absDx = fabsf(dx);
351       float absDy = fabsf(dy);
352       Vector3 contactPoint = {0};
353       if (absDx <= 0.5f && absDx <= absDy) {
354         // vertical collision; push the enemy out horizontally
355         float overlap = enemyRadius + 0.5f - absDy;
356         if (overlap < 0.0f)
357         {
358           continue;
359         }
360         float direction = dy > 0.0f ? -1.0f : 1.0f;
361         enemy->simPosition.y += direction * overlap;
362         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
363       }
364       else if (absDy <= 0.5f && absDy <= absDx)
365       {
366         // horizontal collision; push the enemy out vertically
367         float overlap = enemyRadius + 0.5f - absDx;
368         if (overlap < 0.0f)
369         {
370           continue;
371         }
372         float direction = dx > 0.0f ? -1.0f : 1.0f;
373         enemy->simPosition.x += direction * overlap;
374         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
375       }
376       else
377       {
378         // possible collision with a corner
379         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
380         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
381         float cornerX = tower->x + cornerDX;
382         float cornerY = tower->y + cornerDY;
383         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
384         if (cornerDistanceSqr > enemyRadius * enemyRadius)
385         {
386           continue;
387         }
388         // push the enemy out along the diagonal
389         float cornerDistance = sqrtf(cornerDistanceSqr);
390         float overlap = enemyRadius - cornerDistance;
391         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
392         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
393         enemy->simPosition.x -= directionX * overlap;
394         enemy->simPosition.y -= directionY * overlap;
395         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
396       }
397 
398       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
399       {
400         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
401         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
402         {
403           EnemyTriggerExplode(enemy, tower, contactPoint);
404         }
405       }
406     }
407   }
408 }
409 
410 EnemyId EnemyGetId(Enemy *enemy)
411 {
412   return (EnemyId){enemy - enemies, enemy->generation};
413 }
414 
415 Enemy *EnemyTryResolve(EnemyId enemyId)
416 {
417   if (enemyId.index >= ENEMY_MAX_COUNT)
418   {
419     return 0;
420   }
421   Enemy *enemy = &enemies[enemyId.index];
422   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
423   {
424     return 0;
425   }
426   return enemy;
427 }
428 
429 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
430 {
431   Enemy *spawn = 0;
432   for (int i = 0; i < enemyCount; i++)
433   {
434     Enemy *enemy = &enemies[i];
435     if (enemy->enemyType == ENEMY_TYPE_NONE)
436     {
437       spawn = enemy;
438       break;
439     }
440   }
441 
442   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
443   {
444     spawn = &enemies[enemyCount++];
445   }
446 
447   if (spawn)
448   {
449     spawn->currentX = currentX;
450     spawn->currentY = currentY;
451     spawn->nextX = currentX;
452     spawn->nextY = currentY;
453     spawn->simPosition = (Vector2){currentX, currentY};
454     spawn->simVelocity = (Vector2){0, 0};
455     spawn->enemyType = enemyType;
456     spawn->startMovingTime = gameTime.time;
457     spawn->damage = 0.0f;
458     spawn->futureDamage = 0.0f;
459     spawn->generation++;
460     spawn->movePathCount = 0;
461     spawn->walkedDistance = 0.0f;
462   }
463 
464   return spawn;
465 }
466 
467 int EnemyAddDamageRange(Vector2 position, float range, float damage)
468 {
469   int count = 0;
470   float range2 = range * range;
471   for (int i = 0; i < enemyCount; i++)
472   {
473     Enemy *enemy = &enemies[i];
474     if (enemy->enemyType == ENEMY_TYPE_NONE)
475     {
476       continue;
477     }
478     float distance2 = Vector2DistanceSqr(position, enemy->simPosition);
479     if (distance2 <= range2)
480     {
481       EnemyAddDamage(enemy, damage);
482       count++;
483     }
484   }
485   return count;
486 }
487 
488 int EnemyAddDamage(Enemy *enemy, float damage)
489 {
490   enemy->damage += damage;
491   if (enemy->damage >= EnemyGetMaxHealth(enemy))
492   {
493     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
494     enemy->enemyType = ENEMY_TYPE_NONE;
495     return 1;
496   }
497 
498   return 0;
499 }
500 
501 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
502 {
503   int16_t castleX = 0;
504   int16_t castleY = 0;
505   Enemy* closest = 0;
506   int16_t closestDistance = 0;
507   float range2 = range * range;
508   for (int i = 0; i < enemyCount; i++)
509   {
510     Enemy* enemy = &enemies[i];
511     if (enemy->enemyType == ENEMY_TYPE_NONE)
512     {
513       continue;
514     }
515     float maxHealth = EnemyGetMaxHealth(enemy);
516     if (enemy->futureDamage >= maxHealth)
517     {
518       // ignore enemies that will die soon
519       continue;
520     }
521     int16_t dx = castleX - enemy->currentX;
522     int16_t dy = castleY - enemy->currentY;
523     int16_t distance = abs(dx) + abs(dy);
524     if (!closest || distance < closestDistance)
525     {
526       float tdx = towerX - enemy->currentX;
527       float tdy = towerY - enemy->currentY;
528       float tdistance2 = tdx * tdx + tdy * tdy;
529       if (tdistance2 <= range2)
530       {
531         closest = enemy;
532         closestDistance = distance;
533       }
534     }
535   }
536   return closest;
537 }
538 
539 int EnemyCount()
540 {
541   int count = 0;
542   for (int i = 0; i < enemyCount; i++)
543   {
544     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
545     {
546       count++;
547     }
548   }
549   return count;
550 }
551 
552 void EnemyDrawHealthbars(Camera3D camera)
553 {
554   for (int i = 0; i < enemyCount; i++)
555   {
556     Enemy *enemy = &enemies[i];
557     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
558     {
559       continue;
560     }
561     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
562     float maxHealth = EnemyGetMaxHealth(enemy);
563     float health = maxHealth - enemy->damage;
564     float healthRatio = health / maxHealth;
565     
566     DrawHealthBar(camera, position, healthRatio, GREEN, 15.0f);
567   }
568 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells)
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < blockedCellCount; i++)
131   {
132     int16_t mapX, mapY;
133     if (!PathFindingFromWorldToMapPosition((Vector3){blockedCells[i].x, 0.0f, blockedCells[i].y}, &mapX, &mapY))
134     {
135       continue;
136     }
137     int index = mapY * width + mapX;
138     pathfindingMap.towerIndex[index] = -2;
139   }
140 
141   for (int i = 0; i < towerCount; i++)
142   {
143     Tower *tower = &towers[i];
144     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
145     {
146       continue;
147     }
148     int16_t mapX, mapY;
149     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
150     // this would not work correctly and needs to be refined to allow towers covering multiple cells
151     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
152     // one cell. For now.
153     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
154     {
155       continue;
156     }
157     int index = mapY * width + mapX;
158     pathfindingMap.towerIndex[index] = i;
159   }
160 
161   // we start at the castle and add the castle to the queue
162   pathfindingMap.maxDistance = 0.0f;
163   pathfindingNodeQueueCount = 0;
164   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
165   PathfindingNode *node = 0;
166   while ((node = PathFindingNodePop()))
167   {
168     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
169     {
170       continue;
171     }
172     int index = node->y * width + node->x;
173     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
174     {
175       continue;
176     }
177 
178     int deltaX = node->x - node->fromX;
179     int deltaY = node->y - node->fromY;
180     // even if the cell is blocked by a tower, we still may want to store the direction
181     // (though this might not be needed, IDK right now)
182     pathfindingMap.deltaSrc[index].x = (char) deltaX;
183     pathfindingMap.deltaSrc[index].y = (char) deltaY;
184 
185     // we skip nodes that are blocked by towers or by the provided blocked cells
186     if (pathfindingMap.towerIndex[index] != -1)
187     {
188       node->distance += 8.0f;
189     }
190     pathfindingMap.distances[index] = node->distance;
191     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
192     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
193     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
194     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
195     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
196   }
197 }
198 
199 void PathFindingMapDraw()
200 {
201   float cellSize = pathfindingMap.scale * 0.9f;
202   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
203   for (int x = 0; x < pathfindingMap.width; x++)
204   {
205     for (int y = 0; y < pathfindingMap.height; y++)
206     {
207       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
208       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
209       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
210       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
211       // animate the distance "wave" to show how the pathfinding algorithm expands
212       // from the castle
213       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
214       {
215         color = BLACK;
216       }
217       DrawCube(position, cellSize, 0.1f, cellSize, color);
218     }
219   }
220 }
221 
222 Vector2 PathFindingGetGradient(Vector3 world)
223 {
224   int16_t mapX, mapY;
225   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
226   {
227     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
228     return (Vector2){(float)-delta.x, (float)-delta.y};
229   }
230   // fallback to a simple gradient calculation
231   float n = PathFindingGetDistance(mapX, mapY - 1);
232   float s = PathFindingGetDistance(mapX, mapY + 1);
233   float w = PathFindingGetDistance(mapX - 1, mapY);
234   float e = PathFindingGetDistance(mapX + 1, mapY);
235   return (Vector2){w - e + 0.25f, n - s + 0.125f};
236 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static TowerTypeConfig towerTypeConfigs[TOWER_TYPE_COUNT] = {
  5     [TOWER_TYPE_BASE] = {
  6         .maxHealth = 10,
  7     },
  8     [TOWER_TYPE_ARCHER] = {
  9         .cooldown = 0.5f,
 10         .range = 3.0f,
 11         .cost = 6,
 12         .maxHealth = 10,
 13         .projectileSpeed = 4.0f,
 14         .projectileType = PROJECTILE_TYPE_ARROW,
 15         .hitEffect = {
 16           .damage = 3.0f,
 17         }
 18     },
 19     [TOWER_TYPE_BALLISTA] = {
 20         .cooldown = 1.5f,
 21         .range = 6.0f,
 22         .cost = 9,
 23         .maxHealth = 10,
 24         .projectileSpeed = 10.0f,
 25         .projectileType = PROJECTILE_TYPE_BALLISTA,
 26         .hitEffect = {
 27           .damage = 6.0f,
 28           .pushbackPowerDistance = 0.25f,
 29         }
 30     },
 31     [TOWER_TYPE_CATAPULT] = {
 32         .cooldown = 1.7f,
 33         .range = 5.0f,
 34         .cost = 10,
 35         .maxHealth = 10,
 36         .projectileSpeed = 3.0f,
 37         .projectileType = PROJECTILE_TYPE_CATAPULT,
 38         .hitEffect = {
 39           .damage = 2.0f,
 40           .areaDamageRadius = 1.75f,
 41         }
 42     },
 43     [TOWER_TYPE_WALL] = {
 44         .cost = 2,
 45         .maxHealth = 10,
 46     },
 47 };
 48 
 49 Tower towers[TOWER_MAX_COUNT];
 50 int towerCount = 0;
 51 
 52 Model towerModels[TOWER_TYPE_COUNT];
 53 
 54 // definition of our archer unit
 55 SpriteUnit archerUnit = {
 56     .srcRect = {0, 0, 16, 16},
 57     .offset = {7, 1},
 58     .frameCount = 1,
 59     .frameDuration = 0.0f,
 60     .srcWeaponIdleRect = {16, 0, 6, 16},
 61     .srcWeaponIdleOffset = {8, 0},
 62     .srcWeaponCooldownRect = {22, 0, 11, 16},
 63     .srcWeaponCooldownOffset = {10, 0},
 64 };
 65 
 66 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 67 {
 68   float xScale = flip ? -1.0f : 1.0f;
 69   Camera3D camera = currentLevel->camera;
 70   float size = 0.5f;
 71   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size * xScale };
 72   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
 73   // we want the sprite to face the camera, so we need to calculate the up vector
 74   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 75   Vector3 up = {0, 1, 0};
 76   Vector3 right = Vector3CrossProduct(forward, up);
 77   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 78 
 79   Rectangle srcRect = unit.srcRect;
 80   if (unit.frameCount > 1)
 81   {
 82     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 83   }
 84   if (flip)
 85   {
 86     srcRect.x += srcRect.width;
 87     srcRect.width = -srcRect.width;
 88   }
 89   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 90   // move the sprite slightly towards the camera to avoid z-fighting
 91   position = Vector3Add(position, Vector3Scale(Vector3Normalize(forward), -0.01f));
 92 
 93   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 94   {
 95     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 96     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 97     srcRect = unit.srcWeaponCooldownRect;
 98     if (flip)
 99     {
100       // position.x = flip * scale.x * 0.5f;
101       srcRect.x += srcRect.width;
102       srcRect.width = -srcRect.width;
103       offset.x = scale.x - offset.x;
104     }
105     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
106   }
107   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
108   {
109     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
110     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
111     srcRect = unit.srcWeaponIdleRect;
112     if (flip)
113     {
114       // position.x = flip * scale.x * 0.5f;
115       srcRect.x += srcRect.width;
116       srcRect.width = -srcRect.width;
117       offset.x = scale.x - offset.x;
118     }
119     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
120   }
121 }
122 
123 void TowerInit()
124 {
125   for (int i = 0; i < TOWER_MAX_COUNT; i++)
126   {
127     towers[i] = (Tower){0};
128   }
129   towerCount = 0;
130 
131   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
132   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
133 
134   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
135   {
136     if (towerModels[i].materials)
137     {
138       // assign the palette texture to the material of the model (0 is not used afaik)
139       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
140     }
141   }
142 }
143 
144 static void TowerGunUpdate(Tower *tower)
145 {
146   TowerTypeConfig config = towerTypeConfigs[tower->towerType];
147   if (tower->cooldown <= 0.0f)
148   {
149     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, config.range);
150     if (enemy)
151     {
152       tower->cooldown = config.cooldown;
153       // shoot the enemy; determine future position of the enemy
154       float bulletSpeed = config.projectileSpeed;
155       Vector2 velocity = enemy->simVelocity;
156       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
157       Vector2 towerPosition = {tower->x, tower->y};
158       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
159       for (int i = 0; i < 8; i++) {
160         velocity = enemy->simVelocity;
161         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
162         float distance = Vector2Distance(towerPosition, futurePosition);
163         float eta2 = distance / bulletSpeed;
164         if (fabs(eta - eta2) < 0.01f) {
165           break;
166         }
167         eta = (eta2 + eta) * 0.5f;
168       }
169 
170       ProjectileTryAdd(config.projectileType, enemy, 
171         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
172         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
173         bulletSpeed, config.hitEffect);
174       enemy->futureDamage += config.hitEffect.damage;
175       tower->lastTargetPosition = futurePosition;
176     }
177   }
178   else
179   {
180     tower->cooldown -= gameTime.deltaTime;
181   }
182 }
183 
184 Tower *TowerGetAt(int16_t x, int16_t y)
185 {
186   for (int i = 0; i < towerCount; i++)
187   {
188     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
189     {
190       return &towers[i];
191     }
192   }
193   return 0;
194 }
195 
196 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
197 {
198   if (towerCount >= TOWER_MAX_COUNT)
199   {
200     return 0;
201   }
202 
203   Tower *tower = TowerGetAt(x, y);
204   if (tower)
205   {
206     return 0;
207   }
208 
209   tower = &towers[towerCount++];
210   tower->x = x;
211   tower->y = y;
212   tower->towerType = towerType;
213   tower->cooldown = 0.0f;
214   tower->damage = 0.0f;
215   return tower;
216 }
217 
218 Tower *GetTowerByType(uint8_t towerType)
219 {
220   for (int i = 0; i < towerCount; i++)
221   {
222     if (towers[i].towerType == towerType)
223     {
224       return &towers[i];
225     }
226   }
227   return 0;
228 }
229 
230 int GetTowerCosts(uint8_t towerType)
231 {
232   return towerTypeConfigs[towerType].cost;
233 }
234 
235 float TowerGetMaxHealth(Tower *tower)
236 {
237   return towerTypeConfigs[tower->towerType].maxHealth;
238 }
239 
240 void TowerDrawSingle(Tower tower)
241 {
242   if (tower.towerType == TOWER_TYPE_NONE)
243   {
244     return;
245   }
246 
247   switch (tower.towerType)
248   {
249   case TOWER_TYPE_ARCHER:
250     {
251       Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
252       Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
253       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
254       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
255         tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
256     }
257     break;
258   case TOWER_TYPE_BALLISTA:
259     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, BROWN);
260     break;
261   case TOWER_TYPE_CATAPULT:
262     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, DARKGRAY);
263     break;
264   default:
265     if (towerModels[tower.towerType].materials)
266     {
267       DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
268     } else {
269       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
270     }
271     break;
272   }
273 }
274 
275 void TowerDraw()
276 {
277   for (int i = 0; i < towerCount; i++)
278   {
279     TowerDrawSingle(towers[i]);
280   }
281 }
282 
283 void TowerUpdate()
284 {
285   for (int i = 0; i < towerCount; i++)
286   {
287     Tower *tower = &towers[i];
288     switch (tower->towerType)
289     {
290     case TOWER_TYPE_CATAPULT:
291     case TOWER_TYPE_BALLISTA:
292     case TOWER_TYPE_ARCHER:
293       TowerGunUpdate(tower);
294       break;
295     }
296   }
297 }
298 
299 void TowerDrawHealthBars(Camera3D camera)
300 {
301   for (int i = 0; i < towerCount; i++)
302   {
303     Tower *tower = &towers[i];
304     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
305     {
306       continue;
307     }
308     
309     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
310     float maxHealth = TowerGetMaxHealth(tower);
311     float health = maxHealth - tower->damage;
312     float healthRatio = health / maxHealth;
313     
314     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
315   }
316 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 typedef struct ProjectileConfig
  8 {
  9   float arcFactor;
 10   Color color;
 11   Color trailColor;
 12 } ProjectileConfig;
 13 
 14 ProjectileConfig projectileConfigs[] = {
 15     [PROJECTILE_TYPE_ARROW] = {
 16         .arcFactor = 0.15f,
 17         .color = RED,
 18         .trailColor = BROWN,
 19     },
 20     [PROJECTILE_TYPE_CATAPULT] = {
 21         .arcFactor = 0.5f,
 22         .color = RED,
 23         .trailColor = GRAY,
 24     },
 25     [PROJECTILE_TYPE_BALLISTA] = {
 26         .arcFactor = 0.025f,
 27         .color = RED,
 28         .trailColor = BROWN,
 29     },
 30 };
 31 
 32 void ProjectileInit()
 33 {
 34   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 35   {
 36     projectiles[i] = (Projectile){0};
 37   }
 38 }
 39 
 40 void ProjectileDraw()
 41 {
 42   for (int i = 0; i < projectileCount; i++)
 43   {
 44     Projectile projectile = projectiles[i];
 45     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 46     {
 47       continue;
 48     }
 49     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 50     if (transition >= 1.0f)
 51     {
 52       continue;
 53     }
 54 
 55     ProjectileConfig config = projectileConfigs[projectile.projectileType];
 56     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 57     {
 58       float t = transition + transitionOffset * 0.3f;
 59       if (t > 1.0f)
 60       {
 61         break;
 62       }
 63       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 64       Color color = config.color;
 65       color = ColorLerp(config.trailColor, config.color, transitionOffset * transitionOffset);
 66       // fake a ballista flight path using parabola equation
 67       float parabolaT = t - 0.5f;
 68       parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 69       position.y += config.arcFactor * parabolaT * projectile.distance;
 70       
 71       float size = 0.06f * (transitionOffset + 0.25f);
 72       DrawCube(position, size, size, size, color);
 73     }
 74   }
 75 }
 76 
 77 void ProjectileUpdate()
 78 {
 79   for (int i = 0; i < projectileCount; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 87     if (transition >= 1.0f)
 88     {
 89       projectile->projectileType = PROJECTILE_TYPE_NONE;
 90       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 91       if (enemy && projectile->hitEffectConfig.pushbackPowerDistance > 0.0f)
 92       {
 93           Vector2 direction = Vector2Normalize(Vector2Subtract((Vector2){projectile->target.x, projectile->target.z}, enemy->simPosition));
 94           enemy->simPosition = Vector2Add(enemy->simPosition, Vector2Scale(direction, projectile->hitEffectConfig.pushbackPowerDistance));
 95       }
 96       
 97       if (projectile->hitEffectConfig.areaDamageRadius > 0.0f)
 98       {
 99         EnemyAddDamageRange((Vector2){projectile->target.x, projectile->target.z}, projectile->hitEffectConfig.areaDamageRadius, projectile->hitEffectConfig.damage);
100         // pancaked sphere explosion
101         float r = projectile->hitEffectConfig.areaDamageRadius;
102         ParticleAdd(PARTICLE_TYPE_EXPLOSION, projectile->target, (Vector3){0}, (Vector3){r, r * 0.2f, r}, 0.33f);
103       }
104       else if (projectile->hitEffectConfig.damage > 0.0f && enemy)
105       {
106         EnemyAddDamage(enemy, projectile->hitEffectConfig.damage);
107       }
108       continue;
109     }
110   }
111 }
112 
113 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig)
114 {
115   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
116   {
117     Projectile *projectile = &projectiles[i];
118     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
119     {
120       projectile->projectileType = projectileType;
121       projectile->shootTime = gameTime.time;
122       float distance = Vector3Distance(position, target);
123       projectile->arrivalTime = gameTime.time + distance / speed;
124       projectile->position = position;
125       projectile->target = target;
126       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
127       projectile->distance = distance;
128       projectile->targetEnemy = EnemyGetId(enemy);
129       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
130       projectile->hitEffectConfig = hitEffectConfig;
131       return projectile;
132     }
133   }
134   return 0;
135 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 
  5 static Particle particles[PARTICLE_MAX_COUNT];
  6 static int particleCount = 0;
  7 
  8 void ParticleInit()
  9 {
 10   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 11   {
 12     particles[i] = (Particle){0};
 13   }
 14   particleCount = 0;
 15 }
 16 
 17 static void DrawExplosionParticle(Particle *particle, float transition)
 18 {
 19   Vector3 scale = particle->scale;
 20   float size = 1.0f * (1.0f - transition);
 21   Color startColor = WHITE;
 22   Color endColor = RED;
 23   Color color = ColorLerp(startColor, endColor, transition);
 24 
 25   rlPushMatrix();
 26   rlTranslatef(particle->position.x, particle->position.y, particle->position.z);
 27   rlScalef(scale.x, scale.y, scale.z);
 28   DrawSphere(Vector3Zero(), size, color);
 29   rlPopMatrix();
 30 }
 31 
 32 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime)
 33 {
 34   if (particleCount >= PARTICLE_MAX_COUNT)
 35   {
 36     return;
 37   }
 38 
 39   int index = -1;
 40   for (int i = 0; i < particleCount; i++)
 41   {
 42     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 43     {
 44       index = i;
 45       break;
 46     }
 47   }
 48 
 49   if (index == -1)
 50   {
 51     index = particleCount++;
 52   }
 53 
 54   Particle *particle = &particles[index];
 55   particle->particleType = particleType;
 56   particle->spawnTime = gameTime.time;
 57   particle->lifetime = lifetime;
 58   particle->position = position;
 59   particle->velocity = velocity;
 60   particle->scale = scale;
 61 }
 62 
 63 void ParticleUpdate()
 64 {
 65   for (int i = 0; i < particleCount; i++)
 66   {
 67     Particle *particle = &particles[i];
 68     if (particle->particleType == PARTICLE_TYPE_NONE)
 69     {
 70       continue;
 71     }
 72 
 73     float age = gameTime.time - particle->spawnTime;
 74 
 75     if (particle->lifetime > age)
 76     {
 77       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 78     }
 79     else {
 80       particle->particleType = PARTICLE_TYPE_NONE;
 81     }
 82   }
 83 }
 84 
 85 void ParticleDraw()
 86 {
 87   for (int i = 0; i < particleCount; i++)
 88   {
 89     Particle particle = particles[i];
 90     if (particle.particleType == PARTICLE_TYPE_NONE)
 91     {
 92       continue;
 93     }
 94 
 95     float age = gameTime.time - particle.spawnTime;
 96     float transition = age / particle.lifetime;
 97     switch (particle.particleType)
 98     {
 99     case PARTICLE_TYPE_EXPLOSION:
100       DrawExplosionParticle(&particle, transition);
101       break;
102     default:
103       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
104       break;
105     }
106   }
107 }  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endifZooming in, we can see the difference:
The bleeding is gone and the render order looks good - moving the sword from the left to the right hand helps to reduce the overlapping. Since the enemies have a radius and push each other away, overlapping is therefore only happen in rare cases, so this should be good enough for now.
One important bit: When changing the rendering state (calling rlDisableDepthMask()), we have to first flush the batch to make sure the sprites are drawn in the correct order. This is what the rlDrawRenderBatchActive(); call does. If we don't do this, the render state change may not be applied until the batch is flushed (raylib is internally batching the draw calls).
One more missing thing is, that the weapon has also an animation, so let's add that as well.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 #include <stdlib.h>
  5 #include <math.h>
  6 
  7 //# Variables
  8 GUIState guiState = {0};
  9 GameTime gameTime = {
 10   .fixedDeltaTime = 1.0f / 60.0f,
 11 };
 12 
 13 Model floorTileAModel = {0};
 14 Model floorTileBModel = {0};
 15 Model treeModel[2] = {0};
 16 Model firTreeModel[2] = {0};
 17 Model rockModels[5] = {0};
 18 Model grassPatchModel[1] = {0};
 19 
 20 Model pathArrowModel = {0};
 21 Model greenArrowModel = {0};
 22 
 23 Texture2D palette, spriteSheet;
 24 
 25 Level levels[] = {
 26   [0] = {
 27     .state = LEVEL_STATE_BUILDING,
 28     .initialGold = 20,
 29     .waves[0] = {
 30       .enemyType = ENEMY_TYPE_MINION,
 31       .wave = 0,
 32       .count = 5,
 33       .interval = 2.5f,
 34       .delay = 1.0f,
 35       .spawnPosition = {2, 6},
 36     },
 37     .waves[1] = {
 38       .enemyType = ENEMY_TYPE_MINION,
 39       .wave = 0,
 40       .count = 5,
 41       .interval = 2.5f,
 42       .delay = 1.0f,
 43       .spawnPosition = {-2, 6},
 44     },
 45     .waves[2] = {
 46       .enemyType = ENEMY_TYPE_MINION,
 47       .wave = 1,
 48       .count = 20,
 49       .interval = 1.5f,
 50       .delay = 1.0f,
 51       .spawnPosition = {0, 6},
 52     },
 53     .waves[3] = {
 54       .enemyType = ENEMY_TYPE_MINION,
 55       .wave = 2,
 56       .count = 30,
 57       .interval = 1.2f,
 58       .delay = 1.0f,
 59       .spawnPosition = {0, 6},
 60     }
 61   },
 62 };
 63 
 64 Level *currentLevel = levels;
 65 
 66 //# Game
 67 
 68 static Model LoadGLBModel(char *filename)
 69 {
 70   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 71   for (int i = 0; i < model.materialCount; i++)
 72   {
 73     model.materials[i].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 74   }
 75   return model;
 76 }
 77 
 78 void LoadAssets()
 79 {
 80   // load a sprite sheet that contains all units
 81   spriteSheet = LoadTexture("data/spritesheet.png");
 82   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 83 
 84   // we'll use a palette texture to colorize the all buildings and environment art
 85   palette = LoadTexture("data/palette.png");
 86   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 87   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 88 
 89   floorTileAModel = LoadGLBModel("floor-tile-a");
 90   floorTileBModel = LoadGLBModel("floor-tile-b");
 91   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
 92   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
 93   firTreeModel[0] = LoadGLBModel("firtree-1-a");
 94   firTreeModel[1] = LoadGLBModel("firtree-1-b");
 95   rockModels[0] = LoadGLBModel("rock-1");
 96   rockModels[1] = LoadGLBModel("rock-2");
 97   rockModels[2] = LoadGLBModel("rock-3");
 98   rockModels[3] = LoadGLBModel("rock-4");
 99   rockModels[4] = LoadGLBModel("rock-5");
100   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
101 
102   pathArrowModel = LoadGLBModel("direction-arrow-x");
103   greenArrowModel = LoadGLBModel("green-arrow");
104 }
105 
106 void InitLevel(Level *level)
107 {
108   level->seed = (int)(GetTime() * 100.0f);
109 
110   TowerInit();
111   EnemyInit();
112   ProjectileInit();
113   ParticleInit();
114   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
115 
116   level->placementMode = 0;
117   level->state = LEVEL_STATE_BUILDING;
118   level->nextState = LEVEL_STATE_NONE;
119   level->playerGold = level->initialGold;
120   level->currentWave = 0;
121   level->placementX = -1;
122   level->placementY = 0;
123 
124   Camera *camera = &level->camera;
125   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
126   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
127   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
128   camera->fovy = 10.0f;
129   camera->projection = CAMERA_ORTHOGRAPHIC;
130 }
131 
132 void DrawLevelHud(Level *level)
133 {
134   const char *text = TextFormat("Gold: %d", level->playerGold);
135   Font font = GetFontDefault();
136   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
137   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
138 }
139 
140 void DrawLevelReportLostWave(Level *level)
141 {
142   BeginMode3D(level->camera);
143   DrawLevelGround(level);
144   TowerDraw();
145   EnemyDraw();
146   ProjectileDraw();
147   ParticleDraw();
148   guiState.isBlocked = 0;
149   EndMode3D();
150 
151   TowerDrawHealthBars(level->camera);
152 
153   const char *text = "Wave lost";
154   int textWidth = MeasureText(text, 20);
155   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
156 
157   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
158   {
159     level->nextState = LEVEL_STATE_RESET;
160   }
161 }
162 
163 int HasLevelNextWave(Level *level)
164 {
165   for (int i = 0; i < 10; i++)
166   {
167     EnemyWave *wave = &level->waves[i];
168     if (wave->wave == level->currentWave)
169     {
170       return 1;
171     }
172   }
173   return 0;
174 }
175 
176 void DrawLevelReportWonWave(Level *level)
177 {
178   BeginMode3D(level->camera);
179   DrawLevelGround(level);
180   TowerDraw();
181   EnemyDraw();
182   ProjectileDraw();
183   ParticleDraw();
184   guiState.isBlocked = 0;
185   EndMode3D();
186 
187   TowerDrawHealthBars(level->camera);
188 
189   const char *text = "Wave won";
190   int textWidth = MeasureText(text, 20);
191   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
192 
193 
194   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
195   {
196     level->nextState = LEVEL_STATE_RESET;
197   }
198 
199   if (HasLevelNextWave(level))
200   {
201     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
202     {
203       level->nextState = LEVEL_STATE_BUILDING;
204     }
205   }
206   else {
207     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
208     {
209       level->nextState = LEVEL_STATE_WON_LEVEL;
210     }
211   }
212 }
213 
214 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
215 {
216   static ButtonState buttonStates[8] = {0};
217   int cost = GetTowerCosts(towerType);
218   const char *text = TextFormat("%s: %d", name, cost);
219   buttonStates[towerType].isSelected = level->placementMode == towerType;
220   buttonStates[towerType].isDisabled = level->playerGold < cost;
221   if (Button(text, x, y, width, height, &buttonStates[towerType]))
222   {
223     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
224     level->nextState = LEVEL_STATE_BUILDING_PLACEMENT;
225   }
226 }
227 
228 float GetRandomFloat(float min, float max)
229 {
230   int random = GetRandomValue(0, 0xfffffff);
231   return ((float)random / (float)0xfffffff) * (max - min) + min;
232 }
233 
234 void DrawLevelGround(Level *level)
235 {
236   // draw checkerboard ground pattern
237   for (int x = -5; x <= 5; x += 1)
238   {
239     for (int y = -5; y <= 5; y += 1)
240     {
241       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
242       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
243     }
244   }
245 
246   int oldSeed = GetRandomValue(0, 0xfffffff);
247   SetRandomSeed(level->seed);
248   // increase probability for trees via duplicated entries
249   Model borderModels[64];
250   int maxRockCount = GetRandomValue(2, 6);
251   int maxTreeCount = GetRandomValue(10, 20);
252   int maxFirTreeCount = GetRandomValue(5, 10);
253   int maxLeafTreeCount = maxTreeCount - maxFirTreeCount;
254   int grassPatchCount = GetRandomValue(5, 30);
255 
256   int modelCount = 0;
257   for (int i = 0; i < maxRockCount && modelCount < 63; i++)
258   {
259     borderModels[modelCount++] = rockModels[GetRandomValue(0, 5)];
260   }
261   for (int i = 0; i < maxLeafTreeCount && modelCount < 63; i++)
262   {
263     borderModels[modelCount++] = treeModel[GetRandomValue(0, 1)];
264   }
265   for (int i = 0; i < maxFirTreeCount && modelCount < 63; i++)
266   {
267     borderModels[modelCount++] = firTreeModel[GetRandomValue(0, 1)];
268   }
269   for (int i = 0; i < grassPatchCount && modelCount < 63; i++)
270   {
271     borderModels[modelCount++] = grassPatchModel[0];
272   }
273 
274   // draw some objects around the border of the map
275   Vector3 up = {0, 1, 0};
276   // a pseudo random number generator to get the same result every time
277   const float wiggle = 0.75f;
278   const int layerCount = 3;
279   for (int layer = 0; layer < layerCount; layer++)
280   {
281     int layerPos = 6 + layer;
282     for (int x = -6 + layer; x <= 6 + layer; x += 1)
283     {
284       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
285         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, -layerPos + GetRandomFloat(0.0f, wiggle)}, 
286         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
287       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
288         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, layerPos + GetRandomFloat(0.0f, wiggle)}, 
289         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
290     }
291 
292     for (int z = -5 + layer; z <= 5 + layer; z += 1)
293     {
294       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
295         (Vector3){-layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
296         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
297       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
298         (Vector3){layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
299         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
300     }
301   }
302 
303   SetRandomSeed(oldSeed);
304 }
305 
306 void DrawEnemyPath(Level *level, Color arrowColor)
307 {
308   const int castleX = 0, castleY = 0;
309   const int maxWaypointCount = 200;
310   const float timeStep = 1.0f;
311   Vector3 arrowScale = {0.75f, 0.75f, 0.75f};
312 
313   // we start with a time offset to simulate the path, 
314   // this way the arrows are animated in a forward moving direction
315   // The time is wrapped around the time step to get a smooth animation
316   float timeOffset = fmodf(GetTime(), timeStep);
317 
318   for (int i = 0; i < ENEMY_MAX_WAVE_COUNT; i++)
319   {
320     EnemyWave *wave = &level->waves[i];
321     if (wave->wave != level->currentWave)
322     {
323       continue;
324     }
325 
326     // use this dummy enemy to simulate the path
327     Enemy dummy = {
328       .enemyType = ENEMY_TYPE_MINION,
329       .simPosition = (Vector2){wave->spawnPosition.x, wave->spawnPosition.y},
330       .nextX = wave->spawnPosition.x,
331       .nextY = wave->spawnPosition.y,
332       .currentX = wave->spawnPosition.x,
333       .currentY = wave->spawnPosition.y,
334     };
335 
336     float deltaTime = timeOffset;
337     for (int j = 0; j < maxWaypointCount; j++)
338     {
339       int waypointPassedCount = 0;
340       Vector2 pos = EnemyGetPosition(&dummy, deltaTime, &dummy.simVelocity, &waypointPassedCount);
341       // after the initial variable starting offset, we use a fixed time step
342       deltaTime = timeStep;
343       dummy.simPosition = pos;
344 
345       // Update the dummy's position just like we do in the regular enemy update loop
346       for (int k = 0; k < waypointPassedCount; k++)
347       {
348         dummy.currentX = dummy.nextX;
349         dummy.currentY = dummy.nextY;
350         if (EnemyGetNextPosition(dummy.currentX, dummy.currentY, &dummy.nextX, &dummy.nextY) &&
351           Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
352         {
353           break;
354         }
355       }
356       if (Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
357       {
358         break;
359       }
360       
361       // get the angle we need to rotate the arrow model. The velocity is just fine for this.
362       float angle = atan2f(dummy.simVelocity.x, dummy.simVelocity.y) * RAD2DEG - 90.0f;
363       DrawModelEx(pathArrowModel, (Vector3){pos.x, 0.15f, pos.y}, (Vector3){0, 1, 0}, angle, arrowScale, arrowColor);
364     }
365   }
366 }
367 
368 void DrawEnemyPaths(Level *level)
369 {
370   // disable depth testing for the path arrows
371   // flush the 3D batch to draw the arrows on top of everything
372   rlDrawRenderBatchActive();
373   rlDisableDepthTest();
374   DrawEnemyPath(level, (Color){64, 64, 64, 160});
375 
376   rlDrawRenderBatchActive();
377   rlEnableDepthTest();
378   DrawEnemyPath(level, WHITE);
379 }
380 
381 static void SimulateTowerPlacementBehavior(Level *level, float towerFloatHeight, float mapX, float mapY)
382 {
383   float dt = gameTime.fixedDeltaTime;
384   // smooth transition for the placement position using exponential decay
385   const float lambda = 15.0f;
386   float factor = 1.0f - expf(-lambda * dt);
387 
388   float damping = 0.5f;
389   float springStiffness = 300.0f;
390   float springDecay = 95.0f;
391   float minHeight = 0.35f;
392 
393   if (level->placementPhase == PLACEMENT_PHASE_STARTING)
394   {
395     damping = 1.0f;
396     springDecay = 90.0f;
397     springStiffness = 100.0f;
398     minHeight = 0.70f;
399   }
400 
401   for (int i = 0; i < gameTime.fixedStepCount; i++)
402   {
403     level->placementTransitionPosition = 
404       Vector2Lerp(
405         level->placementTransitionPosition, 
406         (Vector2){mapX, mapY}, factor);
407 
408     // draw the spring position for debugging the spring simulation
409     // first step: stiff spring, no simulation
410     Vector3 worldPlacementPosition = (Vector3){
411       level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
412     Vector3 springTargetPosition = (Vector3){
413       worldPlacementPosition.x, 1.0f + towerFloatHeight, worldPlacementPosition.z};
414     // consider the current velocity to predict the future position in order to dampen
415     // the spring simulation. Longer prediction times will result in more damping
416     Vector3 predictedSpringPosition = Vector3Add(level->placementTowerSpring.position, 
417       Vector3Scale(level->placementTowerSpring.velocity, dt * damping));
418     Vector3 springPointDelta = Vector3Subtract(springTargetPosition, predictedSpringPosition);
419     Vector3 velocityChange = Vector3Scale(springPointDelta, dt * springStiffness);
420     // decay velocity of the upright forcing spring
421     // This force acts like a 2nd spring that pulls the tip upright into the air above the
422     // base position
423     level->placementTowerSpring.velocity = Vector3Scale(level->placementTowerSpring.velocity, 1.0f - expf(-springDecay * dt));
424     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, velocityChange);
425 
426     // calculate length of the spring to calculate the force to apply to the placementTowerSpring position
427     // we use a simple spring model with a rest length of 1.0f
428     Vector3 springDelta = Vector3Subtract(worldPlacementPosition, level->placementTowerSpring.position);
429     float springLength = Vector3Length(springDelta);
430     float springForce = (springLength - 1.0f) * springStiffness;
431     Vector3 springForceVector = Vector3Normalize(springDelta);
432     springForceVector = Vector3Scale(springForceVector, springForce);
433     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, 
434       Vector3Scale(springForceVector, dt));
435 
436     level->placementTowerSpring.position = Vector3Add(level->placementTowerSpring.position, 
437       Vector3Scale(level->placementTowerSpring.velocity, dt));
438     if (level->placementTowerSpring.position.y < minHeight + towerFloatHeight)
439     {
440       level->placementTowerSpring.velocity.y *= -1.0f;
441       level->placementTowerSpring.position.y = fmaxf(level->placementTowerSpring.position.y, minHeight + towerFloatHeight);
442     }
443   }
444 }
445 
446 void DrawLevelBuildingPlacementState(Level *level)
447 {
448   const float placementDuration = 0.5f;
449 
450   level->placementTimer += gameTime.deltaTime;
451   if (level->placementTimer > 1.0f && level->placementPhase == PLACEMENT_PHASE_STARTING)
452   {
453     level->placementPhase = PLACEMENT_PHASE_MOVING;
454     level->placementTimer = 0.0f;
455   }
456 
457   BeginMode3D(level->camera);
458   DrawLevelGround(level);
459 
460   int blockedCellCount = 0;
461   Vector2 blockedCells[1];
462   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
463   float planeDistance = ray.position.y / -ray.direction.y;
464   float planeX = ray.direction.x * planeDistance + ray.position.x;
465   float planeY = ray.direction.z * planeDistance + ray.position.z;
466   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
467   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
468   if (level->placementPhase == PLACEMENT_PHASE_MOVING && 
469     level->placementMode && !guiState.isBlocked && 
470     mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5 && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
471   {
472     level->placementX = mapX;
473     level->placementY = mapY;
474   }
475   else
476   {
477     mapX = level->placementX;
478     mapY = level->placementY;
479   }
480   blockedCells[blockedCellCount++] = (Vector2){mapX, mapY};
481   PathFindingMapUpdate(blockedCellCount, blockedCells);
482 
483   TowerDraw();
484   EnemyDraw();
485   ProjectileDraw();
486   ParticleDraw();
487   DrawEnemyPaths(level);
488 
489   // let the tower float up and down. Consider this height in the spring simulation as well
490   float towerFloatHeight = sinf(gameTime.time * 4.0f) * 0.2f + 0.3f;
491 
492   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
493   {
494     // The bouncing spring needs a bit of outro time to look nice and complete. 
495     // So we scale the time so that the first 2/3rd of the placing phase handles the motion
496     // and the last 1/3rd is the outro physics (bouncing)
497     float t = fminf(1.0f, level->placementTimer / placementDuration * 1.5f);
498     // let towerFloatHeight describe parabola curve, starting at towerFloatHeight and ending at 0
499     float linearBlendHeight = (1.0f - t) * towerFloatHeight;
500     float parabola = (1.0f - ((t - 0.5f) * (t - 0.5f) * 4.0f)) * 2.0f;
501     towerFloatHeight = linearBlendHeight + parabola;
502   }
503 
504   SimulateTowerPlacementBehavior(level, towerFloatHeight, mapX, mapY);
505   
506   rlPushMatrix();
507   rlTranslatef(level->placementTransitionPosition.x, 0, level->placementTransitionPosition.y);
508 
509   rlPushMatrix();
510   rlTranslatef(0.0f, towerFloatHeight, 0.0f);
511   // calculate x and z rotation to align the model with the spring
512   Vector3 position = {level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
513   Vector3 towerUp = Vector3Subtract(level->placementTowerSpring.position, position);
514   Vector3 rotationAxis = Vector3CrossProduct(towerUp, (Vector3){0, 1, 0});
515   float angle = acosf(Vector3DotProduct(towerUp, (Vector3){0, 1, 0}) / Vector3Length(towerUp)) * RAD2DEG;
516   float springLength = Vector3Length(towerUp);
517   float towerStretch = fminf(fmaxf(springLength, 0.5f), 4.5f);
518   float towerSquash = 1.0f / towerStretch;
519   rlRotatef(-angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
520   rlScalef(towerSquash, towerStretch, towerSquash);
521   Tower dummy = {
522     .towerType = level->placementMode,
523   };
524   TowerDrawSingle(dummy);
525   rlPopMatrix();
526 
527   // draw a shadow for the tower
528   float umbrasize = 0.8 + sqrtf(towerFloatHeight);
529   DrawCube((Vector3){0.0f, 0.05f, 0.0f}, umbrasize, 0.0f, umbrasize, (Color){0, 0, 0, 32});
530   DrawCube((Vector3){0.0f, 0.075f, 0.0f}, 0.85f, 0.0f, 0.85f, (Color){0, 0, 0, 64});
531 
532 
533   float bounce = sinf(gameTime.time * 8.0f) * 0.5f + 0.5f;
534   float offset = fmaxf(0.0f, bounce - 0.4f) * 0.35f + 0.7f;
535   float squeeze = -fminf(0.0f, bounce - 0.4f) * 0.7f + 0.7f;
536   float stretch = fmaxf(0.0f, bounce - 0.3f) * 0.5f + 0.8f;
537   
538   DrawModelEx(greenArrowModel, (Vector3){ offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 90, (Vector3){squeeze, 1.0f, stretch}, WHITE);
539   DrawModelEx(greenArrowModel, (Vector3){-offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 270, (Vector3){squeeze, 1.0f, stretch}, WHITE);
540   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f,  offset}, (Vector3){0, 1, 0}, 0, (Vector3){squeeze, 1.0f, stretch}, WHITE);
541   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f, -offset}, (Vector3){0, 1, 0}, 180, (Vector3){squeeze, 1.0f, stretch}, WHITE);
542   rlPopMatrix();
543 
544   guiState.isBlocked = 0;
545 
546   EndMode3D();
547 
548   TowerDrawHealthBars(level->camera);
549 
550   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
551   {
552     if (level->placementTimer > placementDuration)
553     {
554         TowerTryAdd(level->placementMode, mapX, mapY);
555         level->playerGold -= GetTowerCosts(level->placementMode);
556         level->nextState = LEVEL_STATE_BUILDING;
557         level->placementMode = TOWER_TYPE_NONE;
558     }
559   }
560   else
561   {   
562     if (Button("Cancel", 20, GetScreenHeight() - 40, 160, 30, 0))
563     {
564       level->nextState = LEVEL_STATE_BUILDING;
565       level->placementMode = TOWER_TYPE_NONE;
566       TraceLog(LOG_INFO, "Cancel building");
567     }
568     
569     if (TowerGetAt(mapX, mapY) == 0 &&  Button("Build", GetScreenWidth() - 180, GetScreenHeight() - 40, 160, 30, 0))
570     {
571       level->placementPhase = PLACEMENT_PHASE_PLACING;
572       level->placementTimer = 0.0f;
573     }
574   }
575 }
576 
577 void DrawLevelBuildingState(Level *level)
578 {
579   BeginMode3D(level->camera);
580   DrawLevelGround(level);
581 
582   PathFindingMapUpdate(0, 0);
583   TowerDraw();
584   EnemyDraw();
585   ProjectileDraw();
586   ParticleDraw();
587   DrawEnemyPaths(level);
588 
589   guiState.isBlocked = 0;
590 
591   EndMode3D();
592 
593   TowerDrawHealthBars(level->camera);
594 
595   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
596   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_ARCHER, "Archer");
597   DrawBuildingBuildButton(level, 10, 90, 110, 30, TOWER_TYPE_BALLISTA, "Ballista");
598   DrawBuildingBuildButton(level, 10, 130, 110, 30, TOWER_TYPE_CATAPULT, "Catapult");
599 
600   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
601   {
602     level->nextState = LEVEL_STATE_RESET;
603   }
604   
605   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
606   {
607     level->nextState = LEVEL_STATE_BATTLE;
608   }
609 
610   const char *text = "Building phase";
611   int textWidth = MeasureText(text, 20);
612   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
613 }
614 
615 void InitBattleStateConditions(Level *level)
616 {
617   level->state = LEVEL_STATE_BATTLE;
618   level->nextState = LEVEL_STATE_NONE;
619   level->waveEndTimer = 0.0f;
620   for (int i = 0; i < 10; i++)
621   {
622     EnemyWave *wave = &level->waves[i];
623     wave->spawned = 0;
624     wave->timeToSpawnNext = wave->delay;
625   }
626 }
627 
628 void DrawLevelBattleState(Level *level)
629 {
630   BeginMode3D(level->camera);
631   DrawLevelGround(level);
632   TowerDraw();
633   EnemyDraw();
634   ProjectileDraw();
635   ParticleDraw();
636   guiState.isBlocked = 0;
637   EndMode3D();
638 
639   EnemyDrawHealthbars(level->camera);
640   TowerDrawHealthBars(level->camera);
641 
642   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
643   {
644     level->nextState = LEVEL_STATE_RESET;
645   }
646 
647   int maxCount = 0;
648   int remainingCount = 0;
649   for (int i = 0; i < 10; i++)
650   {
651     EnemyWave *wave = &level->waves[i];
652     if (wave->wave != level->currentWave)
653     {
654       continue;
655     }
656     maxCount += wave->count;
657     remainingCount += wave->count - wave->spawned;
658   }
659   int aliveCount = EnemyCount();
660   remainingCount += aliveCount;
661 
662   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
663   int textWidth = MeasureText(text, 20);
664   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
665 }
666 
667 void DrawLevel(Level *level)
668 {
669   switch (level->state)
670   {
671     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
672     case LEVEL_STATE_BUILDING_PLACEMENT: DrawLevelBuildingPlacementState(level); break;
673     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
674     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
675     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
676     default: break;
677   }
678 
679   DrawLevelHud(level);
680 }
681 
682 void UpdateLevel(Level *level)
683 {
684   if (level->state == LEVEL_STATE_BATTLE)
685   {
686     int activeWaves = 0;
687     for (int i = 0; i < 10; i++)
688     {
689       EnemyWave *wave = &level->waves[i];
690       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
691       {
692         continue;
693       }
694       activeWaves++;
695       wave->timeToSpawnNext -= gameTime.deltaTime;
696       if (wave->timeToSpawnNext <= 0.0f)
697       {
698         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
699         if (enemy)
700         {
701           wave->timeToSpawnNext = wave->interval;
702           wave->spawned++;
703         }
704       }
705     }
706     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
707       level->waveEndTimer += gameTime.deltaTime;
708       if (level->waveEndTimer >= 2.0f)
709       {
710         level->nextState = LEVEL_STATE_LOST_WAVE;
711       }
712     }
713     else if (activeWaves == 0 && EnemyCount() == 0)
714     {
715       level->waveEndTimer += gameTime.deltaTime;
716       if (level->waveEndTimer >= 2.0f)
717       {
718         level->nextState = LEVEL_STATE_WON_WAVE;
719       }
720     }
721   }
722 
723   PathFindingMapUpdate(0, 0);
724   EnemyUpdate();
725   TowerUpdate();
726   ProjectileUpdate();
727   ParticleUpdate();
728 
729   if (level->nextState == LEVEL_STATE_RESET)
730   {
731     InitLevel(level);
732   }
733   
734   if (level->nextState == LEVEL_STATE_BATTLE)
735   {
736     InitBattleStateConditions(level);
737   }
738   
739   if (level->nextState == LEVEL_STATE_WON_WAVE)
740   {
741     level->currentWave++;
742     level->state = LEVEL_STATE_WON_WAVE;
743   }
744   
745   if (level->nextState == LEVEL_STATE_LOST_WAVE)
746   {
747     level->state = LEVEL_STATE_LOST_WAVE;
748   }
749 
750   if (level->nextState == LEVEL_STATE_BUILDING)
751   {
752     level->state = LEVEL_STATE_BUILDING;
753   }
754 
755   if (level->nextState == LEVEL_STATE_BUILDING_PLACEMENT)
756   {
757     level->state = LEVEL_STATE_BUILDING_PLACEMENT;
758     level->placementTransitionPosition = (Vector2){
759       level->placementX, level->placementY};
760     // initialize the spring to the current position
761     level->placementTowerSpring = (PhysicsPoint){
762       .position = (Vector3){level->placementX, 8.0f, level->placementY},
763       .velocity = (Vector3){0.0f, 0.0f, 0.0f},
764     };
765     level->placementPhase = PLACEMENT_PHASE_STARTING;
766     level->placementTimer = 0.0f;
767   }
768 
769   if (level->nextState == LEVEL_STATE_WON_LEVEL)
770   {
771     // make something of this later
772     InitLevel(level);
773   }
774 
775   level->nextState = LEVEL_STATE_NONE;
776 }
777 
778 float nextSpawnTime = 0.0f;
779 
780 void ResetGame()
781 {
782   InitLevel(currentLevel);
783 }
784 
785 void InitGame()
786 {
787   TowerInit();
788   EnemyInit();
789   ProjectileInit();
790   ParticleInit();
791   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
792 
793   currentLevel = levels;
794   InitLevel(currentLevel);
795 }
796 
797 //# Immediate GUI functions
798 
799 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
800 {
801   const float healthBarHeight = 6.0f;
802   const float healthBarOffset = 15.0f;
803   const float inset = 2.0f;
804   const float innerWidth = healthBarWidth - inset * 2;
805   const float innerHeight = healthBarHeight - inset * 2;
806 
807   Vector2 screenPos = GetWorldToScreen(position, camera);
808   float centerX = screenPos.x - healthBarWidth * 0.5f;
809   float topY = screenPos.y - healthBarOffset;
810   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
811   float healthWidth = innerWidth * healthRatio;
812   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
813 }
814 
815 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
816 {
817   Rectangle bounds = {x, y, width, height};
818   int isPressed = 0;
819   int isSelected = state && state->isSelected;
820   int isDisabled = state && state->isDisabled;
821   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
822   {
823     Color color = isSelected ? DARKGRAY : GRAY;
824     DrawRectangle(x, y, width, height, color);
825     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
826     {
827       isPressed = 1;
828     }
829     guiState.isBlocked = 1;
830   }
831   else
832   {
833     Color color = isSelected ? WHITE : LIGHTGRAY;
834     DrawRectangle(x, y, width, height, color);
835   }
836   Font font = GetFontDefault();
837   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
838   Color textColor = isDisabled ? GRAY : BLACK;
839   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
840   return isPressed;
841 }
842 
843 //# Main game loop
844 
845 void GameUpdate()
846 {
847   UpdateLevel(currentLevel);
848 }
849 
850 int main(void)
851 {
852   int screenWidth, screenHeight;
853   GetPreferredSize(&screenWidth, &screenHeight);
854   InitWindow(screenWidth, screenHeight, "Tower defense");
855   float gamespeed = 1.0f;
856   SetTargetFPS(30);
857 
858   LoadAssets();
859   InitGame();
860 
861   float pause = 1.0f;
862 
863   while (!WindowShouldClose())
864   {
865     if (IsPaused()) {
866       // canvas is not visible in browser - do nothing
867       continue;
868     }
869 
870     if (IsKeyPressed(KEY_T))
871     {
872       gamespeed += 0.1f;
873       if (gamespeed > 1.05f) gamespeed = 0.1f;
874     }
875 
876     if (IsKeyPressed(KEY_P))
877     {
878       pause = pause > 0.5f ? 0.0f : 1.0f;
879     }
880 
881     float dt = GetFrameTime() * gamespeed * pause;
882     // cap maximum delta time to 0.1 seconds to prevent large time steps
883     if (dt > 0.1f) dt = 0.1f;
884     gameTime.time += dt;
885     gameTime.deltaTime = dt;
886     gameTime.frameCount += 1;
887 
888     float fixedTimeDiff = gameTime.time - gameTime.fixedTimeStart;
889     gameTime.fixedStepCount = (uint8_t)(fixedTimeDiff / gameTime.fixedDeltaTime);
890 
891     BeginDrawing();
892     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
893 
894     GameUpdate();
895     DrawLevel(currentLevel);
896 
897     DrawText(TextFormat("Speed: %.1f", gamespeed), GetScreenWidth() - 180, 60, 20, WHITE);
898     EndDrawing();
899 
900     gameTime.fixedTimeStart += gameTime.fixedStepCount * gameTime.fixedDeltaTime;
901   }
902 
903   CloseWindow();
904 
905   return 0;
906 }  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 //# Declarations
 10 
 11 typedef struct PhysicsPoint
 12 {
 13   Vector3 position;
 14   Vector3 velocity;
 15 } PhysicsPoint;
 16 
 17 #define ENEMY_MAX_PATH_COUNT 8
 18 #define ENEMY_MAX_COUNT 400
 19 #define ENEMY_TYPE_NONE 0
 20 
 21 #define ENEMY_TYPE_MINION 1
 22 #define ENEMY_TYPE_RUNNER 2
 23 #define ENEMY_TYPE_SHIELD 3
 24 #define ENEMY_TYPE_BOSS 3
 25 
 26 #define PARTICLE_MAX_COUNT 400
 27 #define PARTICLE_TYPE_NONE 0
 28 #define PARTICLE_TYPE_EXPLOSION 1
 29 
 30 typedef struct Particle
 31 {
 32   uint8_t particleType;
 33   float spawnTime;
 34   float lifetime;
 35   Vector3 position;
 36   Vector3 velocity;
 37   Vector3 scale;
 38 } Particle;
 39 
 40 #define TOWER_MAX_COUNT 400
 41 enum TowerType
 42 {
 43   TOWER_TYPE_NONE,
 44   TOWER_TYPE_BASE,
 45   TOWER_TYPE_ARCHER,
 46   TOWER_TYPE_BALLISTA,
 47   TOWER_TYPE_CATAPULT,
 48   TOWER_TYPE_WALL,
 49   TOWER_TYPE_COUNT
 50 };
 51 
 52 typedef struct HitEffectConfig
 53 {
 54   float damage;
 55   float areaDamageRadius;
 56   float pushbackPowerDistance;
 57 } HitEffectConfig;
 58 
 59 typedef struct TowerTypeConfig
 60 {
 61   float cooldown;
 62   float range;
 63   float projectileSpeed;
 64   
 65   uint8_t cost;
 66   uint8_t projectileType;
 67   uint16_t maxHealth;
 68 
 69   HitEffectConfig hitEffect;
 70 } TowerTypeConfig;
 71 
 72 typedef struct Tower
 73 {
 74   int16_t x, y;
 75   uint8_t towerType;
 76   Vector2 lastTargetPosition;
 77   float cooldown;
 78   float damage;
 79 } Tower;
 80 
 81 typedef struct GameTime
 82 {
 83   float time;
 84   float deltaTime;
 85   uint32_t frameCount;
 86 
 87   float fixedDeltaTime;
 88   // leaving the fixed time stepping to the update functions,
 89   // we need to know the fixed time at the start of the frame
 90   float fixedTimeStart;
 91   // and the number of fixed steps that we have to make this frame
 92   // The fixedTime is fixedTimeStart + n * fixedStepCount
 93   uint8_t fixedStepCount;
 94 } GameTime;
 95 
 96 typedef struct ButtonState {
 97   char isSelected;
 98   char isDisabled;
 99 } ButtonState;
100 
101 typedef struct GUIState {
102   int isBlocked;
103 } GUIState;
104 
105 typedef enum LevelState
106 {
107   LEVEL_STATE_NONE,
108   LEVEL_STATE_BUILDING,
109   LEVEL_STATE_BUILDING_PLACEMENT,
110   LEVEL_STATE_BATTLE,
111   LEVEL_STATE_WON_WAVE,
112   LEVEL_STATE_LOST_WAVE,
113   LEVEL_STATE_WON_LEVEL,
114   LEVEL_STATE_RESET,
115 } LevelState;
116 
117 typedef struct EnemyWave {
118   uint8_t enemyType;
119   uint8_t wave;
120   uint16_t count;
121   float interval;
122   float delay;
123   Vector2 spawnPosition;
124 
125   uint16_t spawned;
126   float timeToSpawnNext;
127 } EnemyWave;
128 
129 #define ENEMY_MAX_WAVE_COUNT 10
130 
131 typedef enum PlacementPhase
132 {
133   PLACEMENT_PHASE_STARTING,
134   PLACEMENT_PHASE_MOVING,
135   PLACEMENT_PHASE_PLACING,
136 } PlacementPhase;
137 
138 typedef struct Level
139 {
140   int seed;
141   LevelState state;
142   LevelState nextState;
143   Camera3D camera;
144   int placementMode;
145   PlacementPhase placementPhase;
146   float placementTimer;
147   int16_t placementX;
148   int16_t placementY;
149   Vector2 placementTransitionPosition;
150   PhysicsPoint placementTowerSpring;
151 
152   int initialGold;
153   int playerGold;
154 
155   EnemyWave waves[ENEMY_MAX_WAVE_COUNT];
156   int currentWave;
157   float waveEndTimer;
158 } Level;
159 
160 typedef struct DeltaSrc
161 {
162   char x, y;
163 } DeltaSrc;
164 
165 typedef struct PathfindingMap
166 {
167   int width, height;
168   float scale;
169   float *distances;
170   long *towerIndex; 
171   DeltaSrc *deltaSrc;
172   float maxDistance;
173   Matrix toMapSpace;
174   Matrix toWorldSpace;
175 } PathfindingMap;
176 
177 // when we execute the pathfinding algorithm, we need to store the active nodes
178 // in a queue. Each node has a position, a distance from the start, and the
179 // position of the node that we came from.
180 typedef struct PathfindingNode
181 {
182   int16_t x, y, fromX, fromY;
183   float distance;
184 } PathfindingNode;
185 
186 typedef struct EnemyId
187 {
188   uint16_t index;
189   uint16_t generation;
190 } EnemyId;
191 
192 typedef struct EnemyClassConfig
193 {
194   float speed;
195   float health;
196   float radius;
197   float maxAcceleration;
198   float requiredContactTime;
199   float explosionDamage;
200   float explosionRange;
201   float explosionPushbackPower;
202   int goldValue;
203 } EnemyClassConfig;
204 
205 typedef struct Enemy
206 {
207   int16_t currentX, currentY;
208   int16_t nextX, nextY;
209   Vector2 simPosition;
210   Vector2 simVelocity;
211   uint16_t generation;
212   float walkedDistance;
213   float startMovingTime;
214   float damage, futureDamage;
215   float contactTime;
216   uint8_t enemyType;
217   uint8_t movePathCount;
218   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
219 } Enemy;
220 
221 // a unit that uses sprites to be drawn
222 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
223 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
224 typedef struct SpriteUnit
225 {
226   Rectangle srcRect;
227   Vector2 offset;
228   int frameCount;
229   float frameDuration;
230   Rectangle srcWeaponIdleRect;
231   Vector2 srcWeaponIdleOffset;
232   int srcWeaponIdleFrameCount;
233   int srcWeaponIdleFrameWidth;
234   float srcWeaponIdleFrameDuration;
235   Rectangle srcWeaponCooldownRect;
236   Vector2 srcWeaponCooldownOffset;
237 } SpriteUnit;
238 
239 #define PROJECTILE_MAX_COUNT 1200
240 #define PROJECTILE_TYPE_NONE 0
241 #define PROJECTILE_TYPE_ARROW 1
242 #define PROJECTILE_TYPE_CATAPULT 2
243 #define PROJECTILE_TYPE_BALLISTA 3
244 
245 typedef struct Projectile
246 {
247   uint8_t projectileType;
248   float shootTime;
249   float arrivalTime;
250   float distance;
251   Vector3 position;
252   Vector3 target;
253   Vector3 directionNormal;
254   EnemyId targetEnemy;
255   HitEffectConfig hitEffectConfig;
256 } Projectile;
257 
258 //# Function declarations
259 float TowerGetMaxHealth(Tower *tower);
260 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
261 int EnemyAddDamageRange(Vector2 position, float range, float damage);
262 int EnemyAddDamage(Enemy *enemy, float damage);
263 
264 //# Enemy functions
265 void EnemyInit();
266 void EnemyDraw();
267 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
268 void EnemyUpdate();
269 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
270 float EnemyGetMaxHealth(Enemy *enemy);
271 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
272 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
273 EnemyId EnemyGetId(Enemy *enemy);
274 Enemy *EnemyTryResolve(EnemyId enemyId);
275 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
276 int EnemyAddDamage(Enemy *enemy, float damage);
277 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
278 int EnemyCount();
279 void EnemyDrawHealthbars(Camera3D camera);
280 
281 //# Tower functions
282 void TowerInit();
283 Tower *TowerGetAt(int16_t x, int16_t y);
284 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
285 Tower *GetTowerByType(uint8_t towerType);
286 int GetTowerCosts(uint8_t towerType);
287 float TowerGetMaxHealth(Tower *tower);
288 void TowerDraw();
289 void TowerDrawSingle(Tower tower);
290 void TowerUpdate();
291 void TowerDrawHealthBars(Camera3D camera);
292 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
293 
294 //# Particles
295 void ParticleInit();
296 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime);
297 void ParticleUpdate();
298 void ParticleDraw();
299 
300 //# Projectiles
301 void ProjectileInit();
302 void ProjectileDraw();
303 void ProjectileUpdate();
304 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig);
305 
306 //# Pathfinding map
307 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
308 float PathFindingGetDistance(int mapX, int mapY);
309 Vector2 PathFindingGetGradient(Vector3 world);
310 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
311 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells);
312 void PathFindingMapDraw();
313 
314 //# UI
315 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
316 
317 //# Level
318 void DrawLevelGround(Level *level);
319 void DrawEnemyPath(Level *level, Color arrowColor);
320 
321 //# variables
322 extern Level *currentLevel;
323 extern Enemy enemies[ENEMY_MAX_COUNT];
324 extern int enemyCount;
325 extern EnemyClassConfig enemyClassConfigs[];
326 
327 extern GUIState guiState;
328 extern GameTime gameTime;
329 extern Tower towers[TOWER_MAX_COUNT];
330 extern int towerCount;
331 
332 extern Texture2D palette, spriteSheet;
333 
334 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 #include <rlgl.h>
  6 
  7 EnemyClassConfig enemyClassConfigs[] = {
  8     [ENEMY_TYPE_MINION] = {
  9       .health = 10.0f, 
 10       .speed = 0.6f, 
 11       .radius = 0.25f, 
 12       .maxAcceleration = 1.0f,
 13       .explosionDamage = 1.0f,
 14       .requiredContactTime = 0.5f,
 15       .explosionRange = 1.0f,
 16       .explosionPushbackPower = 0.25f,
 17       .goldValue = 1,
 18     },
 19     [ENEMY_TYPE_RUNNER] = {
 20       .health = 5.0f, 
 21       .speed = 1.0f, 
 22       .radius = 0.25f, 
 23       .maxAcceleration = 2.0f,
 24       .explosionDamage = 1.0f,
 25       .requiredContactTime = 0.5f,
 26       .explosionRange = 1.0f,
 27       .explosionPushbackPower = 0.25f,
 28       .goldValue = 2,
 29     },
 30 };
 31 
 32 Enemy enemies[ENEMY_MAX_COUNT];
 33 int enemyCount = 0;
 34 
 35 SpriteUnit enemySprites[] = {
 36     [ENEMY_TYPE_MINION] = {
 37       .srcRect = {0, 17, 16, 15},
 38       .offset = {8.0f, 0.0f},
 39       .frameCount = 6,
 40       .frameDuration = 0.1f,
 41       .srcWeaponIdleRect = {1, 33, 15, 14},
 42       .srcWeaponIdleFrameCount = 6,
 43       .srcWeaponIdleFrameWidth = 16,
 44       .srcWeaponIdleFrameDuration = 0.1f,
 45       .srcWeaponIdleOffset = {7.0f, 0.0f},
 46     },
 47     [ENEMY_TYPE_RUNNER] = {
 48       .srcRect = {0, 16, 16, 16},
 49       .offset = {8.0f, 0.0f},
 50       .frameCount = 6,
 51       .frameDuration = 0.1f,
 52     },
 53 };
 54 
 55 void EnemyInit()
 56 {
 57   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 58   {
 59     enemies[i] = (Enemy){0};
 60   }
 61   enemyCount = 0;
 62 }
 63 
 64 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 65 {
 66   return enemyClassConfigs[enemy->enemyType].speed;
 67 }
 68 
 69 float EnemyGetMaxHealth(Enemy *enemy)
 70 {
 71   return enemyClassConfigs[enemy->enemyType].health;
 72 }
 73 
 74 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 75 {
 76   int16_t castleX = 0;
 77   int16_t castleY = 0;
 78   int16_t dx = castleX - currentX;
 79   int16_t dy = castleY - currentY;
 80   if (dx == 0 && dy == 0)
 81   {
 82     *nextX = currentX;
 83     *nextY = currentY;
 84     return 1;
 85   }
 86   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 87 
 88   if (gradient.x == 0 && gradient.y == 0)
 89   {
 90     *nextX = currentX;
 91     *nextY = currentY;
 92     return 1;
 93   }
 94 
 95   if (fabsf(gradient.x) > fabsf(gradient.y))
 96   {
 97     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
 98     *nextY = currentY;
 99     return 0;
100   }
101   *nextX = currentX;
102   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
103   return 0;
104 }
105 
106 
107 // this function predicts the movement of the unit for the next deltaT seconds
108 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
109 {
110   const float pointReachedDistance = 0.25f;
111   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
112   const float maxSimStepTime = 0.015625f;
113   
114   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
115   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
116   int16_t nextX = enemy->nextX;
117   int16_t nextY = enemy->nextY;
118   Vector2 position = enemy->simPosition;
119   int passedCount = 0;
120   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
121   {
122     float stepTime = fminf(deltaT - t, maxSimStepTime);
123     Vector2 target = (Vector2){nextX, nextY};
124     float speed = Vector2Length(*velocity);
125     // draw the target position for debugging
126     //DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
127     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
128     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
129     {
130       // we reached the target position, let's move to the next waypoint
131       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
132       target = (Vector2){nextX, nextY};
133       // track how many waypoints we passed
134       passedCount++;
135     }
136     
137     // acceleration towards the target
138     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
139     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
140     *velocity = Vector2Add(*velocity, acceleration);
141 
142     // limit the speed to the maximum speed
143     if (speed > maxSpeed)
144     {
145       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
146     }
147 
148     // move the enemy
149     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
150   }
151 
152   if (waypointPassedCount)
153   {
154     (*waypointPassedCount) = passedCount;
155   }
156 
157   return position;
158 }
159 
160 void EnemyDraw()
161 {
162   rlDrawRenderBatchActive();
163   rlDisableDepthMask();
164   for (int i = 0; i < enemyCount; i++)
165   {
166     Enemy enemy = enemies[i];
167     if (enemy.enemyType == ENEMY_TYPE_NONE)
168     {
169       continue;
170     }
171 
172     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
173     
174     // don't draw any trails for now; might replace this with footprints later
175     // if (enemy.movePathCount > 0)
176     // {
177     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
178     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
179     // }
180     // for (int j = 1; j < enemy.movePathCount; j++)
181     // {
182     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
183     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
184     //   DrawLine3D(p, q, GREEN);
185     // }
186 
187     switch (enemy.enemyType)
188     {
189     case ENEMY_TYPE_MINION:
190       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y}, 
191         enemy.walkedDistance, 0, 0);
192       break;
193     }
194   }
195   rlDrawRenderBatchActive();
196   rlEnableDepthMask();
197 }
198 
199 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
200 {
201   // damage the tower
202   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
203   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
204   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
205   float explosionRange2 = explosionRange * explosionRange;
206   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
207   // explode the enemy
208   if (tower->damage >= TowerGetMaxHealth(tower))
209   {
210     tower->towerType = TOWER_TYPE_NONE;
211   }
212 
213   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
214     explosionSource, 
215     (Vector3){0, 0.1f, 0}, (Vector3){1.0f, 1.0f, 1.0f}, 1.0f);
216 
217   enemy->enemyType = ENEMY_TYPE_NONE;
218 
219   // push back enemies & dealing damage
220   for (int i = 0; i < enemyCount; i++)
221   {
222     Enemy *other = &enemies[i];
223     if (other->enemyType == ENEMY_TYPE_NONE)
224     {
225       continue;
226     }
227     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
228     if (distanceSqr > 0 && distanceSqr < explosionRange2)
229     {
230       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
231       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
232       EnemyAddDamage(other, explosionDamge);
233     }
234   }
235 }
236 
237 void EnemyUpdate()
238 {
239   const float castleX = 0;
240   const float castleY = 0;
241   const float maxPathDistance2 = 0.25f * 0.25f;
242   
243   for (int i = 0; i < enemyCount; i++)
244   {
245     Enemy *enemy = &enemies[i];
246     if (enemy->enemyType == ENEMY_TYPE_NONE)
247     {
248       continue;
249     }
250 
251     int waypointPassedCount = 0;
252     Vector2 prevPosition = enemy->simPosition;
253     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
254     enemy->startMovingTime = gameTime.time;
255     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
256     // track path of unit
257     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
258     {
259       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
260       {
261         enemy->movePath[j] = enemy->movePath[j - 1];
262       }
263       enemy->movePath[0] = enemy->simPosition;
264       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
265       {
266         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
267       }
268     }
269 
270     if (waypointPassedCount > 0)
271     {
272       enemy->currentX = enemy->nextX;
273       enemy->currentY = enemy->nextY;
274       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
275         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
276       {
277         // enemy reached the castle; remove it
278         enemy->enemyType = ENEMY_TYPE_NONE;
279         continue;
280       }
281     }
282   }
283 
284   // handle collisions between enemies
285   for (int i = 0; i < enemyCount - 1; i++)
286   {
287     Enemy *enemyA = &enemies[i];
288     if (enemyA->enemyType == ENEMY_TYPE_NONE)
289     {
290       continue;
291     }
292     for (int j = i + 1; j < enemyCount; j++)
293     {
294       Enemy *enemyB = &enemies[j];
295       if (enemyB->enemyType == ENEMY_TYPE_NONE)
296       {
297         continue;
298       }
299       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
300       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
301       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
302       float radiusSum = radiusA + radiusB;
303       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
304       {
305         // collision
306         float distance = sqrtf(distanceSqr);
307         float overlap = radiusSum - distance;
308         // move the enemies apart, but softly; if we have a clog of enemies,
309         // moving them perfectly apart can cause them to jitter
310         float positionCorrection = overlap / 5.0f;
311         Vector2 direction = (Vector2){
312             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
313             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
314         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
315         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
316       }
317     }
318   }
319 
320   // handle collisions between enemies and towers
321   for (int i = 0; i < enemyCount; i++)
322   {
323     Enemy *enemy = &enemies[i];
324     if (enemy->enemyType == ENEMY_TYPE_NONE)
325     {
326       continue;
327     }
328     enemy->contactTime -= gameTime.deltaTime;
329     if (enemy->contactTime < 0.0f)
330     {
331       enemy->contactTime = 0.0f;
332     }
333 
334     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
335     // linear search over towers; could be optimized by using path finding tower map,
336     // but for now, we keep it simple
337     for (int j = 0; j < towerCount; j++)
338     {
339       Tower *tower = &towers[j];
340       if (tower->towerType == TOWER_TYPE_NONE)
341       {
342         continue;
343       }
344       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
345       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
346       if (distanceSqr > combinedRadius * combinedRadius)
347       {
348         continue;
349       }
350       // potential collision; square / circle intersection
351       float dx = tower->x - enemy->simPosition.x;
352       float dy = tower->y - enemy->simPosition.y;
353       float absDx = fabsf(dx);
354       float absDy = fabsf(dy);
355       Vector3 contactPoint = {0};
356       if (absDx <= 0.5f && absDx <= absDy) {
357         // vertical collision; push the enemy out horizontally
358         float overlap = enemyRadius + 0.5f - absDy;
359         if (overlap < 0.0f)
360         {
361           continue;
362         }
363         float direction = dy > 0.0f ? -1.0f : 1.0f;
364         enemy->simPosition.y += direction * overlap;
365         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
366       }
367       else if (absDy <= 0.5f && absDy <= absDx)
368       {
369         // horizontal collision; push the enemy out vertically
370         float overlap = enemyRadius + 0.5f - absDx;
371         if (overlap < 0.0f)
372         {
373           continue;
374         }
375         float direction = dx > 0.0f ? -1.0f : 1.0f;
376         enemy->simPosition.x += direction * overlap;
377         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
378       }
379       else
380       {
381         // possible collision with a corner
382         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
383         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
384         float cornerX = tower->x + cornerDX;
385         float cornerY = tower->y + cornerDY;
386         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
387         if (cornerDistanceSqr > enemyRadius * enemyRadius)
388         {
389           continue;
390         }
391         // push the enemy out along the diagonal
392         float cornerDistance = sqrtf(cornerDistanceSqr);
393         float overlap = enemyRadius - cornerDistance;
394         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
395         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
396         enemy->simPosition.x -= directionX * overlap;
397         enemy->simPosition.y -= directionY * overlap;
398         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
399       }
400 
401       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
402       {
403         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
404         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
405         {
406           EnemyTriggerExplode(enemy, tower, contactPoint);
407         }
408       }
409     }
410   }
411 }
412 
413 EnemyId EnemyGetId(Enemy *enemy)
414 {
415   return (EnemyId){enemy - enemies, enemy->generation};
416 }
417 
418 Enemy *EnemyTryResolve(EnemyId enemyId)
419 {
420   if (enemyId.index >= ENEMY_MAX_COUNT)
421   {
422     return 0;
423   }
424   Enemy *enemy = &enemies[enemyId.index];
425   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
426   {
427     return 0;
428   }
429   return enemy;
430 }
431 
432 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
433 {
434   Enemy *spawn = 0;
435   for (int i = 0; i < enemyCount; i++)
436   {
437     Enemy *enemy = &enemies[i];
438     if (enemy->enemyType == ENEMY_TYPE_NONE)
439     {
440       spawn = enemy;
441       break;
442     }
443   }
444 
445   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
446   {
447     spawn = &enemies[enemyCount++];
448   }
449 
450   if (spawn)
451   {
452     spawn->currentX = currentX;
453     spawn->currentY = currentY;
454     spawn->nextX = currentX;
455     spawn->nextY = currentY;
456     spawn->simPosition = (Vector2){currentX, currentY};
457     spawn->simVelocity = (Vector2){0, 0};
458     spawn->enemyType = enemyType;
459     spawn->startMovingTime = gameTime.time;
460     spawn->damage = 0.0f;
461     spawn->futureDamage = 0.0f;
462     spawn->generation++;
463     spawn->movePathCount = 0;
464     spawn->walkedDistance = 0.0f;
465   }
466 
467   return spawn;
468 }
469 
470 int EnemyAddDamageRange(Vector2 position, float range, float damage)
471 {
472   int count = 0;
473   float range2 = range * range;
474   for (int i = 0; i < enemyCount; i++)
475   {
476     Enemy *enemy = &enemies[i];
477     if (enemy->enemyType == ENEMY_TYPE_NONE)
478     {
479       continue;
480     }
481     float distance2 = Vector2DistanceSqr(position, enemy->simPosition);
482     if (distance2 <= range2)
483     {
484       EnemyAddDamage(enemy, damage);
485       count++;
486     }
487   }
488   return count;
489 }
490 
491 int EnemyAddDamage(Enemy *enemy, float damage)
492 {
493   enemy->damage += damage;
494   if (enemy->damage >= EnemyGetMaxHealth(enemy))
495   {
496     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
497     enemy->enemyType = ENEMY_TYPE_NONE;
498     return 1;
499   }
500 
501   return 0;
502 }
503 
504 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
505 {
506   int16_t castleX = 0;
507   int16_t castleY = 0;
508   Enemy* closest = 0;
509   int16_t closestDistance = 0;
510   float range2 = range * range;
511   for (int i = 0; i < enemyCount; i++)
512   {
513     Enemy* enemy = &enemies[i];
514     if (enemy->enemyType == ENEMY_TYPE_NONE)
515     {
516       continue;
517     }
518     float maxHealth = EnemyGetMaxHealth(enemy);
519     if (enemy->futureDamage >= maxHealth)
520     {
521       // ignore enemies that will die soon
522       continue;
523     }
524     int16_t dx = castleX - enemy->currentX;
525     int16_t dy = castleY - enemy->currentY;
526     int16_t distance = abs(dx) + abs(dy);
527     if (!closest || distance < closestDistance)
528     {
529       float tdx = towerX - enemy->currentX;
530       float tdy = towerY - enemy->currentY;
531       float tdistance2 = tdx * tdx + tdy * tdy;
532       if (tdistance2 <= range2)
533       {
534         closest = enemy;
535         closestDistance = distance;
536       }
537     }
538   }
539   return closest;
540 }
541 
542 int EnemyCount()
543 {
544   int count = 0;
545   for (int i = 0; i < enemyCount; i++)
546   {
547     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
548     {
549       count++;
550     }
551   }
552   return count;
553 }
554 
555 void EnemyDrawHealthbars(Camera3D camera)
556 {
557   for (int i = 0; i < enemyCount; i++)
558   {
559     Enemy *enemy = &enemies[i];
560     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
561     {
562       continue;
563     }
564     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
565     float maxHealth = EnemyGetMaxHealth(enemy);
566     float health = maxHealth - enemy->damage;
567     float healthRatio = health / maxHealth;
568     
569     DrawHealthBar(camera, position, healthRatio, GREEN, 15.0f);
570   }
571 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static TowerTypeConfig towerTypeConfigs[TOWER_TYPE_COUNT] = {
  5     [TOWER_TYPE_BASE] = {
  6         .maxHealth = 10,
  7     },
  8     [TOWER_TYPE_ARCHER] = {
  9         .cooldown = 0.5f,
 10         .range = 3.0f,
 11         .cost = 6,
 12         .maxHealth = 10,
 13         .projectileSpeed = 4.0f,
 14         .projectileType = PROJECTILE_TYPE_ARROW,
 15         .hitEffect = {
 16           .damage = 3.0f,
 17         }
 18     },
 19     [TOWER_TYPE_BALLISTA] = {
 20         .cooldown = 1.5f,
 21         .range = 6.0f,
 22         .cost = 9,
 23         .maxHealth = 10,
 24         .projectileSpeed = 10.0f,
 25         .projectileType = PROJECTILE_TYPE_BALLISTA,
 26         .hitEffect = {
 27           .damage = 6.0f,
 28           .pushbackPowerDistance = 0.25f,
 29         }
 30     },
 31     [TOWER_TYPE_CATAPULT] = {
 32         .cooldown = 1.7f,
 33         .range = 5.0f,
 34         .cost = 10,
 35         .maxHealth = 10,
 36         .projectileSpeed = 3.0f,
 37         .projectileType = PROJECTILE_TYPE_CATAPULT,
 38         .hitEffect = {
 39           .damage = 2.0f,
 40           .areaDamageRadius = 1.75f,
 41         }
 42     },
 43     [TOWER_TYPE_WALL] = {
 44         .cost = 2,
 45         .maxHealth = 10,
 46     },
 47 };
 48 
 49 Tower towers[TOWER_MAX_COUNT];
 50 int towerCount = 0;
 51 
 52 Model towerModels[TOWER_TYPE_COUNT];
 53 
 54 // definition of our archer unit
 55 SpriteUnit archerUnit = {
 56     .srcRect = {0, 0, 16, 16},
 57     .offset = {7, 1},
 58     .frameCount = 1,
 59     .frameDuration = 0.0f,
 60     .srcWeaponIdleRect = {16, 0, 6, 16},
 61     .srcWeaponIdleOffset = {8, 0},
 62     .srcWeaponCooldownRect = {22, 0, 11, 16},
 63     .srcWeaponCooldownOffset = {10, 0},
 64 };
 65 
 66 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 67 {
 68   float xScale = flip ? -1.0f : 1.0f;
 69   Camera3D camera = currentLevel->camera;
 70   float size = 0.5f;
 71   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size * xScale };
 72   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
 73   // we want the sprite to face the camera, so we need to calculate the up vector
 74   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 75   Vector3 up = {0, 1, 0};
 76   Vector3 right = Vector3CrossProduct(forward, up);
 77   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 78 
 79   Rectangle srcRect = unit.srcRect;
 80   if (unit.frameCount > 1)
 81   {
 82     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 83   }
 84   if (flip)
 85   {
 86     srcRect.x += srcRect.width;
 87     srcRect.width = -srcRect.width;
 88   }
 89   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 90   // move the sprite slightly towards the camera to avoid z-fighting
 91   position = Vector3Add(position, Vector3Scale(Vector3Normalize(forward), -0.01f));
 92 
 93   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 94   {
 95     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 96     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 97     srcRect = unit.srcWeaponCooldownRect;
 98     if (flip)
 99     {
100       // position.x = flip * scale.x * 0.5f;
101       srcRect.x += srcRect.width;
102       srcRect.width = -srcRect.width;
103       offset.x = scale.x - offset.x;
104     }
105     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
106   }
107   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
108   {
109     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
110     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
111     srcRect = unit.srcWeaponIdleRect;
112     if (flip)
113     {
114       // position.x = flip * scale.x * 0.5f;
115       srcRect.x += srcRect.width;
116       srcRect.width = -srcRect.width;
117       offset.x = scale.x - offset.x;
118     }
119     if (unit.srcWeaponIdleFrameCount > 1)
120     {
121       int w = unit.srcWeaponIdleFrameWidth > 0 ? unit.srcWeaponIdleFrameWidth : srcRect.width;
122       srcRect.x += (int)(t / unit.srcWeaponIdleFrameDuration) % unit.srcWeaponIdleFrameCount * w;
123     }
124     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
125   }
126 }
127 
128 void TowerInit()
129 {
130   for (int i = 0; i < TOWER_MAX_COUNT; i++)
131   {
132     towers[i] = (Tower){0};
133   }
134   towerCount = 0;
135 
136   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
137   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
138 
139   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
140   {
141     if (towerModels[i].materials)
142     {
143       // assign the palette texture to the material of the model (0 is not used afaik)
144       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
145     }
146   }
147 }
148 
149 static void TowerGunUpdate(Tower *tower)
150 {
151   TowerTypeConfig config = towerTypeConfigs[tower->towerType];
152   if (tower->cooldown <= 0.0f)
153   {
154     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, config.range);
155     if (enemy)
156     {
157       tower->cooldown = config.cooldown;
158       // shoot the enemy; determine future position of the enemy
159       float bulletSpeed = config.projectileSpeed;
160       Vector2 velocity = enemy->simVelocity;
161       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
162       Vector2 towerPosition = {tower->x, tower->y};
163       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
164       for (int i = 0; i < 8; i++) {
165         velocity = enemy->simVelocity;
166         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
167         float distance = Vector2Distance(towerPosition, futurePosition);
168         float eta2 = distance / bulletSpeed;
169         if (fabs(eta - eta2) < 0.01f) {
170           break;
171         }
172         eta = (eta2 + eta) * 0.5f;
173       }
174 
175       ProjectileTryAdd(config.projectileType, enemy, 
176         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
177         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
178         bulletSpeed, config.hitEffect);
179       enemy->futureDamage += config.hitEffect.damage;
180       tower->lastTargetPosition = futurePosition;
181     }
182   }
183   else
184   {
185     tower->cooldown -= gameTime.deltaTime;
186   }
187 }
188 
189 Tower *TowerGetAt(int16_t x, int16_t y)
190 {
191   for (int i = 0; i < towerCount; i++)
192   {
193     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
194     {
195       return &towers[i];
196     }
197   }
198   return 0;
199 }
200 
201 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
202 {
203   if (towerCount >= TOWER_MAX_COUNT)
204   {
205     return 0;
206   }
207 
208   Tower *tower = TowerGetAt(x, y);
209   if (tower)
210   {
211     return 0;
212   }
213 
214   tower = &towers[towerCount++];
215   tower->x = x;
216   tower->y = y;
217   tower->towerType = towerType;
218   tower->cooldown = 0.0f;
219   tower->damage = 0.0f;
220   return tower;
221 }
222 
223 Tower *GetTowerByType(uint8_t towerType)
224 {
225   for (int i = 0; i < towerCount; i++)
226   {
227     if (towers[i].towerType == towerType)
228     {
229       return &towers[i];
230     }
231   }
232   return 0;
233 }
234 
235 int GetTowerCosts(uint8_t towerType)
236 {
237   return towerTypeConfigs[towerType].cost;
238 }
239 
240 float TowerGetMaxHealth(Tower *tower)
241 {
242   return towerTypeConfigs[tower->towerType].maxHealth;
243 }
244 
245 void TowerDrawSingle(Tower tower)
246 {
247   if (tower.towerType == TOWER_TYPE_NONE)
248   {
249     return;
250   }
251 
252   switch (tower.towerType)
253   {
254   case TOWER_TYPE_ARCHER:
255     {
256       Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
257       Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
258       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
259       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
260         tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
261     }
262     break;
263   case TOWER_TYPE_BALLISTA:
264     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, BROWN);
265     break;
266   case TOWER_TYPE_CATAPULT:
267     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, DARKGRAY);
268     break;
269   default:
270     if (towerModels[tower.towerType].materials)
271     {
272       DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
273     } else {
274       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
275     }
276     break;
277   }
278 }
279 
280 void TowerDraw()
281 {
282   for (int i = 0; i < towerCount; i++)
283   {
284     TowerDrawSingle(towers[i]);
285   }
286 }
287 
288 void TowerUpdate()
289 {
290   for (int i = 0; i < towerCount; i++)
291   {
292     Tower *tower = &towers[i];
293     switch (tower->towerType)
294     {
295     case TOWER_TYPE_CATAPULT:
296     case TOWER_TYPE_BALLISTA:
297     case TOWER_TYPE_ARCHER:
298       TowerGunUpdate(tower);
299       break;
300     }
301   }
302 }
303 
304 void TowerDrawHealthBars(Camera3D camera)
305 {
306   for (int i = 0; i < towerCount; i++)
307   {
308     Tower *tower = &towers[i];
309     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
310     {
311       continue;
312     }
313     
314     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
315     float maxHealth = TowerGetMaxHealth(tower);
316     float health = maxHealth - tower->damage;
317     float healthRatio = health / maxHealth;
318     
319     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
320   }
321 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells)
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < blockedCellCount; i++)
131   {
132     int16_t mapX, mapY;
133     if (!PathFindingFromWorldToMapPosition((Vector3){blockedCells[i].x, 0.0f, blockedCells[i].y}, &mapX, &mapY))
134     {
135       continue;
136     }
137     int index = mapY * width + mapX;
138     pathfindingMap.towerIndex[index] = -2;
139   }
140 
141   for (int i = 0; i < towerCount; i++)
142   {
143     Tower *tower = &towers[i];
144     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
145     {
146       continue;
147     }
148     int16_t mapX, mapY;
149     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
150     // this would not work correctly and needs to be refined to allow towers covering multiple cells
151     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
152     // one cell. For now.
153     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
154     {
155       continue;
156     }
157     int index = mapY * width + mapX;
158     pathfindingMap.towerIndex[index] = i;
159   }
160 
161   // we start at the castle and add the castle to the queue
162   pathfindingMap.maxDistance = 0.0f;
163   pathfindingNodeQueueCount = 0;
164   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
165   PathfindingNode *node = 0;
166   while ((node = PathFindingNodePop()))
167   {
168     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
169     {
170       continue;
171     }
172     int index = node->y * width + node->x;
173     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
174     {
175       continue;
176     }
177 
178     int deltaX = node->x - node->fromX;
179     int deltaY = node->y - node->fromY;
180     // even if the cell is blocked by a tower, we still may want to store the direction
181     // (though this might not be needed, IDK right now)
182     pathfindingMap.deltaSrc[index].x = (char) deltaX;
183     pathfindingMap.deltaSrc[index].y = (char) deltaY;
184 
185     // we skip nodes that are blocked by towers or by the provided blocked cells
186     if (pathfindingMap.towerIndex[index] != -1)
187     {
188       node->distance += 8.0f;
189     }
190     pathfindingMap.distances[index] = node->distance;
191     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
192     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
193     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
194     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
195     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
196   }
197 }
198 
199 void PathFindingMapDraw()
200 {
201   float cellSize = pathfindingMap.scale * 0.9f;
202   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
203   for (int x = 0; x < pathfindingMap.width; x++)
204   {
205     for (int y = 0; y < pathfindingMap.height; y++)
206     {
207       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
208       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
209       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
210       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
211       // animate the distance "wave" to show how the pathfinding algorithm expands
212       // from the castle
213       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
214       {
215         color = BLACK;
216       }
217       DrawCube(position, cellSize, 0.1f, cellSize, color);
218     }
219   }
220 }
221 
222 Vector2 PathFindingGetGradient(Vector3 world)
223 {
224   int16_t mapX, mapY;
225   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
226   {
227     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
228     return (Vector2){(float)-delta.x, (float)-delta.y};
229   }
230   // fallback to a simple gradient calculation
231   float n = PathFindingGetDistance(mapX, mapY - 1);
232   float s = PathFindingGetDistance(mapX, mapY + 1);
233   float w = PathFindingGetDistance(mapX - 1, mapY);
234   float e = PathFindingGetDistance(mapX + 1, mapY);
235   return (Vector2){w - e + 0.25f, n - s + 0.125f};
236 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 typedef struct ProjectileConfig
  8 {
  9   float arcFactor;
 10   Color color;
 11   Color trailColor;
 12 } ProjectileConfig;
 13 
 14 ProjectileConfig projectileConfigs[] = {
 15     [PROJECTILE_TYPE_ARROW] = {
 16         .arcFactor = 0.15f,
 17         .color = RED,
 18         .trailColor = BROWN,
 19     },
 20     [PROJECTILE_TYPE_CATAPULT] = {
 21         .arcFactor = 0.5f,
 22         .color = RED,
 23         .trailColor = GRAY,
 24     },
 25     [PROJECTILE_TYPE_BALLISTA] = {
 26         .arcFactor = 0.025f,
 27         .color = RED,
 28         .trailColor = BROWN,
 29     },
 30 };
 31 
 32 void ProjectileInit()
 33 {
 34   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 35   {
 36     projectiles[i] = (Projectile){0};
 37   }
 38 }
 39 
 40 void ProjectileDraw()
 41 {
 42   for (int i = 0; i < projectileCount; i++)
 43   {
 44     Projectile projectile = projectiles[i];
 45     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 46     {
 47       continue;
 48     }
 49     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 50     if (transition >= 1.0f)
 51     {
 52       continue;
 53     }
 54 
 55     ProjectileConfig config = projectileConfigs[projectile.projectileType];
 56     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 57     {
 58       float t = transition + transitionOffset * 0.3f;
 59       if (t > 1.0f)
 60       {
 61         break;
 62       }
 63       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 64       Color color = config.color;
 65       color = ColorLerp(config.trailColor, config.color, transitionOffset * transitionOffset);
 66       // fake a ballista flight path using parabola equation
 67       float parabolaT = t - 0.5f;
 68       parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 69       position.y += config.arcFactor * parabolaT * projectile.distance;
 70       
 71       float size = 0.06f * (transitionOffset + 0.25f);
 72       DrawCube(position, size, size, size, color);
 73     }
 74   }
 75 }
 76 
 77 void ProjectileUpdate()
 78 {
 79   for (int i = 0; i < projectileCount; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 87     if (transition >= 1.0f)
 88     {
 89       projectile->projectileType = PROJECTILE_TYPE_NONE;
 90       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 91       if (enemy && projectile->hitEffectConfig.pushbackPowerDistance > 0.0f)
 92       {
 93           Vector2 direction = Vector2Normalize(Vector2Subtract((Vector2){projectile->target.x, projectile->target.z}, enemy->simPosition));
 94           enemy->simPosition = Vector2Add(enemy->simPosition, Vector2Scale(direction, projectile->hitEffectConfig.pushbackPowerDistance));
 95       }
 96       
 97       if (projectile->hitEffectConfig.areaDamageRadius > 0.0f)
 98       {
 99         EnemyAddDamageRange((Vector2){projectile->target.x, projectile->target.z}, projectile->hitEffectConfig.areaDamageRadius, projectile->hitEffectConfig.damage);
100         // pancaked sphere explosion
101         float r = projectile->hitEffectConfig.areaDamageRadius;
102         ParticleAdd(PARTICLE_TYPE_EXPLOSION, projectile->target, (Vector3){0}, (Vector3){r, r * 0.2f, r}, 0.33f);
103       }
104       else if (projectile->hitEffectConfig.damage > 0.0f && enemy)
105       {
106         EnemyAddDamage(enemy, projectile->hitEffectConfig.damage);
107       }
108       continue;
109     }
110   }
111 }
112 
113 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig)
114 {
115   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
116   {
117     Projectile *projectile = &projectiles[i];
118     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
119     {
120       projectile->projectileType = projectileType;
121       projectile->shootTime = gameTime.time;
122       float distance = Vector3Distance(position, target);
123       projectile->arrivalTime = gameTime.time + distance / speed;
124       projectile->position = position;
125       projectile->target = target;
126       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
127       projectile->distance = distance;
128       projectile->targetEnemy = EnemyGetId(enemy);
129       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
130       projectile->hitEffectConfig = hitEffectConfig;
131       return projectile;
132     }
133   }
134   return 0;
135 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 
  5 static Particle particles[PARTICLE_MAX_COUNT];
  6 static int particleCount = 0;
  7 
  8 void ParticleInit()
  9 {
 10   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 11   {
 12     particles[i] = (Particle){0};
 13   }
 14   particleCount = 0;
 15 }
 16 
 17 static void DrawExplosionParticle(Particle *particle, float transition)
 18 {
 19   Vector3 scale = particle->scale;
 20   float size = 1.0f * (1.0f - transition);
 21   Color startColor = WHITE;
 22   Color endColor = RED;
 23   Color color = ColorLerp(startColor, endColor, transition);
 24 
 25   rlPushMatrix();
 26   rlTranslatef(particle->position.x, particle->position.y, particle->position.z);
 27   rlScalef(scale.x, scale.y, scale.z);
 28   DrawSphere(Vector3Zero(), size, color);
 29   rlPopMatrix();
 30 }
 31 
 32 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime)
 33 {
 34   if (particleCount >= PARTICLE_MAX_COUNT)
 35   {
 36     return;
 37   }
 38 
 39   int index = -1;
 40   for (int i = 0; i < particleCount; i++)
 41   {
 42     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 43     {
 44       index = i;
 45       break;
 46     }
 47   }
 48 
 49   if (index == -1)
 50   {
 51     index = particleCount++;
 52   }
 53 
 54   Particle *particle = &particles[index];
 55   particle->particleType = particleType;
 56   particle->spawnTime = gameTime.time;
 57   particle->lifetime = lifetime;
 58   particle->position = position;
 59   particle->velocity = velocity;
 60   particle->scale = scale;
 61 }
 62 
 63 void ParticleUpdate()
 64 {
 65   for (int i = 0; i < particleCount; i++)
 66   {
 67     Particle *particle = &particles[i];
 68     if (particle->particleType == PARTICLE_TYPE_NONE)
 69     {
 70       continue;
 71     }
 72 
 73     float age = gameTime.time - particle->spawnTime;
 74 
 75     if (particle->lifetime > age)
 76     {
 77       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 78     }
 79     else {
 80       particle->particleType = PARTICLE_TYPE_NONE;
 81     }
 82   }
 83 }
 84 
 85 void ParticleDraw()
 86 {
 87   for (int i = 0; i < particleCount; i++)
 88   {
 89     Particle particle = particles[i];
 90     if (particle.particleType == PARTICLE_TYPE_NONE)
 91     {
 92       continue;
 93     }
 94 
 95     float age = gameTime.time - particle.spawnTime;
 96     float transition = age / particle.lifetime;
 97     switch (particle.particleType)
 98     {
 99     case PARTICLE_TYPE_EXPLOSION:
100       DrawExplosionParticle(&particle, transition);
101       break;
102     default:
103       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
104       break;
105     }
106   }
107 }  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endifThe weapon now has a simple animation in sync with the walking animation of the minion:
We can now use the minion without the weapon as the runner enemy. But maybe, we should refine the approach to describe sprite animations for the units. Just look at how the struct has grown:
  1 typedef struct SpriteUnit
  2 {
  3   Rectangle srcRect;
  4   Vector2 offset;
  5   int frameCount;
  6   float frameDuration;
  7   Rectangle srcWeaponIdleRect;
  8   Vector2 srcWeaponIdleOffset;
  9   int srcWeaponIdleFrameCount;
 10   int srcWeaponIdleFrameWidth;
 11   float srcWeaponIdleFrameDuration;
 12   Rectangle srcWeaponCooldownRect;
 13   Vector2 srcWeaponCooldownOffset;
 14 } SpriteUnit;It isn't difficult to spot that there is a lot of repetition in the struct. It is basically a bunch of rectangles and offsets. Adding more and more such custom variables will make this struct and the code using it needlessly complex. So let's refactor this into a more generic struct that can handle multiple overlays and animations. A fixed number of overlays is good enough for our game:
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 #include <stdlib.h>
  5 #include <math.h>
  6 
  7 //# Variables
  8 GUIState guiState = {0};
  9 GameTime gameTime = {
 10   .fixedDeltaTime = 1.0f / 60.0f,
 11 };
 12 
 13 Model floorTileAModel = {0};
 14 Model floorTileBModel = {0};
 15 Model treeModel[2] = {0};
 16 Model firTreeModel[2] = {0};
 17 Model rockModels[5] = {0};
 18 Model grassPatchModel[1] = {0};
 19 
 20 Model pathArrowModel = {0};
 21 Model greenArrowModel = {0};
 22 
 23 Texture2D palette, spriteSheet;
 24 
 25 Level levels[] = {
 26   [0] = {
 27     .state = LEVEL_STATE_BUILDING,
 28     .initialGold = 20,
 29     .waves[0] = {
 30       .enemyType = ENEMY_TYPE_MINION,
 31       .wave = 0,
 32       .count = 5,
 33       .interval = 2.5f,
 34       .delay = 1.0f,
 35       .spawnPosition = {2, 6},
 36     },
 37     .waves[1] = {
 38       .enemyType = ENEMY_TYPE_MINION,
 39       .wave = 0,
 40       .count = 5,
 41       .interval = 2.5f,
 42       .delay = 1.0f,
 43       .spawnPosition = {-2, 6},
 44     },
 45     .waves[2] = {
 46       .enemyType = ENEMY_TYPE_MINION,
 47       .wave = 1,
 48       .count = 20,
 49       .interval = 1.5f,
 50       .delay = 1.0f,
 51       .spawnPosition = {0, 6},
 52     },
 53     .waves[3] = {
 54       .enemyType = ENEMY_TYPE_MINION,
 55       .wave = 2,
 56       .count = 30,
 57       .interval = 1.2f,
 58       .delay = 1.0f,
 59       .spawnPosition = {0, 6},
 60     }
 61   },
 62 };
 63 
 64 Level *currentLevel = levels;
 65 
 66 //# Game
 67 
 68 static Model LoadGLBModel(char *filename)
 69 {
 70   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 71   for (int i = 0; i < model.materialCount; i++)
 72   {
 73     model.materials[i].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 74   }
 75   return model;
 76 }
 77 
 78 void LoadAssets()
 79 {
 80   // load a sprite sheet that contains all units
 81   spriteSheet = LoadTexture("data/spritesheet.png");
 82   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 83 
 84   // we'll use a palette texture to colorize the all buildings and environment art
 85   palette = LoadTexture("data/palette.png");
 86   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 87   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 88 
 89   floorTileAModel = LoadGLBModel("floor-tile-a");
 90   floorTileBModel = LoadGLBModel("floor-tile-b");
 91   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
 92   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
 93   firTreeModel[0] = LoadGLBModel("firtree-1-a");
 94   firTreeModel[1] = LoadGLBModel("firtree-1-b");
 95   rockModels[0] = LoadGLBModel("rock-1");
 96   rockModels[1] = LoadGLBModel("rock-2");
 97   rockModels[2] = LoadGLBModel("rock-3");
 98   rockModels[3] = LoadGLBModel("rock-4");
 99   rockModels[4] = LoadGLBModel("rock-5");
100   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
101 
102   pathArrowModel = LoadGLBModel("direction-arrow-x");
103   greenArrowModel = LoadGLBModel("green-arrow");
104 }
105 
106 void InitLevel(Level *level)
107 {
108   level->seed = (int)(GetTime() * 100.0f);
109 
110   TowerInit();
111   EnemyInit();
112   ProjectileInit();
113   ParticleInit();
114   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
115 
116   level->placementMode = 0;
117   level->state = LEVEL_STATE_BUILDING;
118   level->nextState = LEVEL_STATE_NONE;
119   level->playerGold = level->initialGold;
120   level->currentWave = 0;
121   level->placementX = -1;
122   level->placementY = 0;
123 
124   Camera *camera = &level->camera;
125   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
126   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
127   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
128   camera->fovy = 10.0f;
129   camera->projection = CAMERA_ORTHOGRAPHIC;
130 }
131 
132 void DrawLevelHud(Level *level)
133 {
134   const char *text = TextFormat("Gold: %d", level->playerGold);
135   Font font = GetFontDefault();
136   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
137   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
138 }
139 
140 void DrawLevelReportLostWave(Level *level)
141 {
142   BeginMode3D(level->camera);
143   DrawLevelGround(level);
144   TowerDraw();
145   EnemyDraw();
146   ProjectileDraw();
147   ParticleDraw();
148   guiState.isBlocked = 0;
149   EndMode3D();
150 
151   TowerDrawHealthBars(level->camera);
152 
153   const char *text = "Wave lost";
154   int textWidth = MeasureText(text, 20);
155   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
156 
157   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
158   {
159     level->nextState = LEVEL_STATE_RESET;
160   }
161 }
162 
163 int HasLevelNextWave(Level *level)
164 {
165   for (int i = 0; i < 10; i++)
166   {
167     EnemyWave *wave = &level->waves[i];
168     if (wave->wave == level->currentWave)
169     {
170       return 1;
171     }
172   }
173   return 0;
174 }
175 
176 void DrawLevelReportWonWave(Level *level)
177 {
178   BeginMode3D(level->camera);
179   DrawLevelGround(level);
180   TowerDraw();
181   EnemyDraw();
182   ProjectileDraw();
183   ParticleDraw();
184   guiState.isBlocked = 0;
185   EndMode3D();
186 
187   TowerDrawHealthBars(level->camera);
188 
189   const char *text = "Wave won";
190   int textWidth = MeasureText(text, 20);
191   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
192 
193 
194   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
195   {
196     level->nextState = LEVEL_STATE_RESET;
197   }
198 
199   if (HasLevelNextWave(level))
200   {
201     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
202     {
203       level->nextState = LEVEL_STATE_BUILDING;
204     }
205   }
206   else {
207     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
208     {
209       level->nextState = LEVEL_STATE_WON_LEVEL;
210     }
211   }
212 }
213 
214 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
215 {
216   static ButtonState buttonStates[8] = {0};
217   int cost = GetTowerCosts(towerType);
218   const char *text = TextFormat("%s: %d", name, cost);
219   buttonStates[towerType].isSelected = level->placementMode == towerType;
220   buttonStates[towerType].isDisabled = level->playerGold < cost;
221   if (Button(text, x, y, width, height, &buttonStates[towerType]))
222   {
223     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
224     level->nextState = LEVEL_STATE_BUILDING_PLACEMENT;
225   }
226 }
227 
228 float GetRandomFloat(float min, float max)
229 {
230   int random = GetRandomValue(0, 0xfffffff);
231   return ((float)random / (float)0xfffffff) * (max - min) + min;
232 }
233 
234 void DrawLevelGround(Level *level)
235 {
236   // draw checkerboard ground pattern
237   for (int x = -5; x <= 5; x += 1)
238   {
239     for (int y = -5; y <= 5; y += 1)
240     {
241       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
242       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
243     }
244   }
245 
246   int oldSeed = GetRandomValue(0, 0xfffffff);
247   SetRandomSeed(level->seed);
248   // increase probability for trees via duplicated entries
249   Model borderModels[64];
250   int maxRockCount = GetRandomValue(2, 6);
251   int maxTreeCount = GetRandomValue(10, 20);
252   int maxFirTreeCount = GetRandomValue(5, 10);
253   int maxLeafTreeCount = maxTreeCount - maxFirTreeCount;
254   int grassPatchCount = GetRandomValue(5, 30);
255 
256   int modelCount = 0;
257   for (int i = 0; i < maxRockCount && modelCount < 63; i++)
258   {
259     borderModels[modelCount++] = rockModels[GetRandomValue(0, 5)];
260   }
261   for (int i = 0; i < maxLeafTreeCount && modelCount < 63; i++)
262   {
263     borderModels[modelCount++] = treeModel[GetRandomValue(0, 1)];
264   }
265   for (int i = 0; i < maxFirTreeCount && modelCount < 63; i++)
266   {
267     borderModels[modelCount++] = firTreeModel[GetRandomValue(0, 1)];
268   }
269   for (int i = 0; i < grassPatchCount && modelCount < 63; i++)
270   {
271     borderModels[modelCount++] = grassPatchModel[0];
272   }
273 
274   // draw some objects around the border of the map
275   Vector3 up = {0, 1, 0};
276   // a pseudo random number generator to get the same result every time
277   const float wiggle = 0.75f;
278   const int layerCount = 3;
279   for (int layer = 0; layer < layerCount; layer++)
280   {
281     int layerPos = 6 + layer;
282     for (int x = -6 + layer; x <= 6 + layer; x += 1)
283     {
284       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
285         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, -layerPos + GetRandomFloat(0.0f, wiggle)}, 
286         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
287       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
288         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, layerPos + GetRandomFloat(0.0f, wiggle)}, 
289         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
290     }
291 
292     for (int z = -5 + layer; z <= 5 + layer; z += 1)
293     {
294       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
295         (Vector3){-layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
296         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
297       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
298         (Vector3){layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
299         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
300     }
301   }
302 
303   SetRandomSeed(oldSeed);
304 }
305 
306 void DrawEnemyPath(Level *level, Color arrowColor)
307 {
308   const int castleX = 0, castleY = 0;
309   const int maxWaypointCount = 200;
310   const float timeStep = 1.0f;
311   Vector3 arrowScale = {0.75f, 0.75f, 0.75f};
312 
313   // we start with a time offset to simulate the path, 
314   // this way the arrows are animated in a forward moving direction
315   // The time is wrapped around the time step to get a smooth animation
316   float timeOffset = fmodf(GetTime(), timeStep);
317 
318   for (int i = 0; i < ENEMY_MAX_WAVE_COUNT; i++)
319   {
320     EnemyWave *wave = &level->waves[i];
321     if (wave->wave != level->currentWave)
322     {
323       continue;
324     }
325 
326     // use this dummy enemy to simulate the path
327     Enemy dummy = {
328       .enemyType = ENEMY_TYPE_MINION,
329       .simPosition = (Vector2){wave->spawnPosition.x, wave->spawnPosition.y},
330       .nextX = wave->spawnPosition.x,
331       .nextY = wave->spawnPosition.y,
332       .currentX = wave->spawnPosition.x,
333       .currentY = wave->spawnPosition.y,
334     };
335 
336     float deltaTime = timeOffset;
337     for (int j = 0; j < maxWaypointCount; j++)
338     {
339       int waypointPassedCount = 0;
340       Vector2 pos = EnemyGetPosition(&dummy, deltaTime, &dummy.simVelocity, &waypointPassedCount);
341       // after the initial variable starting offset, we use a fixed time step
342       deltaTime = timeStep;
343       dummy.simPosition = pos;
344 
345       // Update the dummy's position just like we do in the regular enemy update loop
346       for (int k = 0; k < waypointPassedCount; k++)
347       {
348         dummy.currentX = dummy.nextX;
349         dummy.currentY = dummy.nextY;
350         if (EnemyGetNextPosition(dummy.currentX, dummy.currentY, &dummy.nextX, &dummy.nextY) &&
351           Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
352         {
353           break;
354         }
355       }
356       if (Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
357       {
358         break;
359       }
360       
361       // get the angle we need to rotate the arrow model. The velocity is just fine for this.
362       float angle = atan2f(dummy.simVelocity.x, dummy.simVelocity.y) * RAD2DEG - 90.0f;
363       DrawModelEx(pathArrowModel, (Vector3){pos.x, 0.15f, pos.y}, (Vector3){0, 1, 0}, angle, arrowScale, arrowColor);
364     }
365   }
366 }
367 
368 void DrawEnemyPaths(Level *level)
369 {
370   // disable depth testing for the path arrows
371   // flush the 3D batch to draw the arrows on top of everything
372   rlDrawRenderBatchActive();
373   rlDisableDepthTest();
374   DrawEnemyPath(level, (Color){64, 64, 64, 160});
375 
376   rlDrawRenderBatchActive();
377   rlEnableDepthTest();
378   DrawEnemyPath(level, WHITE);
379 }
380 
381 static void SimulateTowerPlacementBehavior(Level *level, float towerFloatHeight, float mapX, float mapY)
382 {
383   float dt = gameTime.fixedDeltaTime;
384   // smooth transition for the placement position using exponential decay
385   const float lambda = 15.0f;
386   float factor = 1.0f - expf(-lambda * dt);
387 
388   float damping = 0.5f;
389   float springStiffness = 300.0f;
390   float springDecay = 95.0f;
391   float minHeight = 0.35f;
392 
393   if (level->placementPhase == PLACEMENT_PHASE_STARTING)
394   {
395     damping = 1.0f;
396     springDecay = 90.0f;
397     springStiffness = 100.0f;
398     minHeight = 0.70f;
399   }
400 
401   for (int i = 0; i < gameTime.fixedStepCount; i++)
402   {
403     level->placementTransitionPosition = 
404       Vector2Lerp(
405         level->placementTransitionPosition, 
406         (Vector2){mapX, mapY}, factor);
407 
408     // draw the spring position for debugging the spring simulation
409     // first step: stiff spring, no simulation
410     Vector3 worldPlacementPosition = (Vector3){
411       level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
412     Vector3 springTargetPosition = (Vector3){
413       worldPlacementPosition.x, 1.0f + towerFloatHeight, worldPlacementPosition.z};
414     // consider the current velocity to predict the future position in order to dampen
415     // the spring simulation. Longer prediction times will result in more damping
416     Vector3 predictedSpringPosition = Vector3Add(level->placementTowerSpring.position, 
417       Vector3Scale(level->placementTowerSpring.velocity, dt * damping));
418     Vector3 springPointDelta = Vector3Subtract(springTargetPosition, predictedSpringPosition);
419     Vector3 velocityChange = Vector3Scale(springPointDelta, dt * springStiffness);
420     // decay velocity of the upright forcing spring
421     // This force acts like a 2nd spring that pulls the tip upright into the air above the
422     // base position
423     level->placementTowerSpring.velocity = Vector3Scale(level->placementTowerSpring.velocity, 1.0f - expf(-springDecay * dt));
424     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, velocityChange);
425 
426     // calculate length of the spring to calculate the force to apply to the placementTowerSpring position
427     // we use a simple spring model with a rest length of 1.0f
428     Vector3 springDelta = Vector3Subtract(worldPlacementPosition, level->placementTowerSpring.position);
429     float springLength = Vector3Length(springDelta);
430     float springForce = (springLength - 1.0f) * springStiffness;
431     Vector3 springForceVector = Vector3Normalize(springDelta);
432     springForceVector = Vector3Scale(springForceVector, springForce);
433     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, 
434       Vector3Scale(springForceVector, dt));
435 
436     level->placementTowerSpring.position = Vector3Add(level->placementTowerSpring.position, 
437       Vector3Scale(level->placementTowerSpring.velocity, dt));
438     if (level->placementTowerSpring.position.y < minHeight + towerFloatHeight)
439     {
440       level->placementTowerSpring.velocity.y *= -1.0f;
441       level->placementTowerSpring.position.y = fmaxf(level->placementTowerSpring.position.y, minHeight + towerFloatHeight);
442     }
443   }
444 }
445 
446 void DrawLevelBuildingPlacementState(Level *level)
447 {
448   const float placementDuration = 0.5f;
449 
450   level->placementTimer += gameTime.deltaTime;
451   if (level->placementTimer > 1.0f && level->placementPhase == PLACEMENT_PHASE_STARTING)
452   {
453     level->placementPhase = PLACEMENT_PHASE_MOVING;
454     level->placementTimer = 0.0f;
455   }
456 
457   BeginMode3D(level->camera);
458   DrawLevelGround(level);
459 
460   int blockedCellCount = 0;
461   Vector2 blockedCells[1];
462   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
463   float planeDistance = ray.position.y / -ray.direction.y;
464   float planeX = ray.direction.x * planeDistance + ray.position.x;
465   float planeY = ray.direction.z * planeDistance + ray.position.z;
466   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
467   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
468   if (level->placementPhase == PLACEMENT_PHASE_MOVING && 
469     level->placementMode && !guiState.isBlocked && 
470     mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5 && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
471   {
472     level->placementX = mapX;
473     level->placementY = mapY;
474   }
475   else
476   {
477     mapX = level->placementX;
478     mapY = level->placementY;
479   }
480   blockedCells[blockedCellCount++] = (Vector2){mapX, mapY};
481   PathFindingMapUpdate(blockedCellCount, blockedCells);
482 
483   TowerDraw();
484   EnemyDraw();
485   ProjectileDraw();
486   ParticleDraw();
487   DrawEnemyPaths(level);
488 
489   // let the tower float up and down. Consider this height in the spring simulation as well
490   float towerFloatHeight = sinf(gameTime.time * 4.0f) * 0.2f + 0.3f;
491 
492   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
493   {
494     // The bouncing spring needs a bit of outro time to look nice and complete. 
495     // So we scale the time so that the first 2/3rd of the placing phase handles the motion
496     // and the last 1/3rd is the outro physics (bouncing)
497     float t = fminf(1.0f, level->placementTimer / placementDuration * 1.5f);
498     // let towerFloatHeight describe parabola curve, starting at towerFloatHeight and ending at 0
499     float linearBlendHeight = (1.0f - t) * towerFloatHeight;
500     float parabola = (1.0f - ((t - 0.5f) * (t - 0.5f) * 4.0f)) * 2.0f;
501     towerFloatHeight = linearBlendHeight + parabola;
502   }
503 
504   SimulateTowerPlacementBehavior(level, towerFloatHeight, mapX, mapY);
505   
506   rlPushMatrix();
507   rlTranslatef(level->placementTransitionPosition.x, 0, level->placementTransitionPosition.y);
508 
509   rlPushMatrix();
510   rlTranslatef(0.0f, towerFloatHeight, 0.0f);
511   // calculate x and z rotation to align the model with the spring
512   Vector3 position = {level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
513   Vector3 towerUp = Vector3Subtract(level->placementTowerSpring.position, position);
514   Vector3 rotationAxis = Vector3CrossProduct(towerUp, (Vector3){0, 1, 0});
515   float angle = acosf(Vector3DotProduct(towerUp, (Vector3){0, 1, 0}) / Vector3Length(towerUp)) * RAD2DEG;
516   float springLength = Vector3Length(towerUp);
517   float towerStretch = fminf(fmaxf(springLength, 0.5f), 4.5f);
518   float towerSquash = 1.0f / towerStretch;
519   rlRotatef(-angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
520   rlScalef(towerSquash, towerStretch, towerSquash);
521   Tower dummy = {
522     .towerType = level->placementMode,
523   };
524   TowerDrawSingle(dummy);
525   rlPopMatrix();
526 
527   // draw a shadow for the tower
528   float umbrasize = 0.8 + sqrtf(towerFloatHeight);
529   DrawCube((Vector3){0.0f, 0.05f, 0.0f}, umbrasize, 0.0f, umbrasize, (Color){0, 0, 0, 32});
530   DrawCube((Vector3){0.0f, 0.075f, 0.0f}, 0.85f, 0.0f, 0.85f, (Color){0, 0, 0, 64});
531 
532 
533   float bounce = sinf(gameTime.time * 8.0f) * 0.5f + 0.5f;
534   float offset = fmaxf(0.0f, bounce - 0.4f) * 0.35f + 0.7f;
535   float squeeze = -fminf(0.0f, bounce - 0.4f) * 0.7f + 0.7f;
536   float stretch = fmaxf(0.0f, bounce - 0.3f) * 0.5f + 0.8f;
537   
538   DrawModelEx(greenArrowModel, (Vector3){ offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 90, (Vector3){squeeze, 1.0f, stretch}, WHITE);
539   DrawModelEx(greenArrowModel, (Vector3){-offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 270, (Vector3){squeeze, 1.0f, stretch}, WHITE);
540   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f,  offset}, (Vector3){0, 1, 0}, 0, (Vector3){squeeze, 1.0f, stretch}, WHITE);
541   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f, -offset}, (Vector3){0, 1, 0}, 180, (Vector3){squeeze, 1.0f, stretch}, WHITE);
542   rlPopMatrix();
543 
544   guiState.isBlocked = 0;
545 
546   EndMode3D();
547 
548   TowerDrawHealthBars(level->camera);
549 
550   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
551   {
552     if (level->placementTimer > placementDuration)
553     {
554         TowerTryAdd(level->placementMode, mapX, mapY);
555         level->playerGold -= GetTowerCosts(level->placementMode);
556         level->nextState = LEVEL_STATE_BUILDING;
557         level->placementMode = TOWER_TYPE_NONE;
558     }
559   }
560   else
561   {   
562     if (Button("Cancel", 20, GetScreenHeight() - 40, 160, 30, 0))
563     {
564       level->nextState = LEVEL_STATE_BUILDING;
565       level->placementMode = TOWER_TYPE_NONE;
566       TraceLog(LOG_INFO, "Cancel building");
567     }
568     
569     if (TowerGetAt(mapX, mapY) == 0 &&  Button("Build", GetScreenWidth() - 180, GetScreenHeight() - 40, 160, 30, 0))
570     {
571       level->placementPhase = PLACEMENT_PHASE_PLACING;
572       level->placementTimer = 0.0f;
573     }
574   }
575 }
576 
577 void DrawLevelBuildingState(Level *level)
578 {
579   BeginMode3D(level->camera);
580   DrawLevelGround(level);
581 
582   PathFindingMapUpdate(0, 0);
583   TowerDraw();
584   EnemyDraw();
585   ProjectileDraw();
586   ParticleDraw();
587   DrawEnemyPaths(level);
588 
589   guiState.isBlocked = 0;
590 
591   EndMode3D();
592 
593   TowerDrawHealthBars(level->camera);
594 
595   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
596   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_ARCHER, "Archer");
597   DrawBuildingBuildButton(level, 10, 90, 110, 30, TOWER_TYPE_BALLISTA, "Ballista");
598   DrawBuildingBuildButton(level, 10, 130, 110, 30, TOWER_TYPE_CATAPULT, "Catapult");
599 
600   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
601   {
602     level->nextState = LEVEL_STATE_RESET;
603   }
604   
605   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
606   {
607     level->nextState = LEVEL_STATE_BATTLE;
608   }
609 
610   const char *text = "Building phase";
611   int textWidth = MeasureText(text, 20);
612   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
613 }
614 
615 void InitBattleStateConditions(Level *level)
616 {
617   level->state = LEVEL_STATE_BATTLE;
618   level->nextState = LEVEL_STATE_NONE;
619   level->waveEndTimer = 0.0f;
620   for (int i = 0; i < 10; i++)
621   {
622     EnemyWave *wave = &level->waves[i];
623     wave->spawned = 0;
624     wave->timeToSpawnNext = wave->delay;
625   }
626 }
627 
628 void DrawLevelBattleState(Level *level)
629 {
630   BeginMode3D(level->camera);
631   DrawLevelGround(level);
632   TowerDraw();
633   EnemyDraw();
634   ProjectileDraw();
635   ParticleDraw();
636   guiState.isBlocked = 0;
637   EndMode3D();
638 
639   EnemyDrawHealthbars(level->camera);
640   TowerDrawHealthBars(level->camera);
641 
642   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
643   {
644     level->nextState = LEVEL_STATE_RESET;
645   }
646 
647   int maxCount = 0;
648   int remainingCount = 0;
649   for (int i = 0; i < 10; i++)
650   {
651     EnemyWave *wave = &level->waves[i];
652     if (wave->wave != level->currentWave)
653     {
654       continue;
655     }
656     maxCount += wave->count;
657     remainingCount += wave->count - wave->spawned;
658   }
659   int aliveCount = EnemyCount();
660   remainingCount += aliveCount;
661 
662   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
663   int textWidth = MeasureText(text, 20);
664   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
665 }
666 
667 void DrawLevel(Level *level)
668 {
669   switch (level->state)
670   {
671     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
672     case LEVEL_STATE_BUILDING_PLACEMENT: DrawLevelBuildingPlacementState(level); break;
673     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
674     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
675     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
676     default: break;
677   }
678 
679   DrawLevelHud(level);
680 }
681 
682 void UpdateLevel(Level *level)
683 {
684   if (level->state == LEVEL_STATE_BATTLE)
685   {
686     int activeWaves = 0;
687     for (int i = 0; i < 10; i++)
688     {
689       EnemyWave *wave = &level->waves[i];
690       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
691       {
692         continue;
693       }
694       activeWaves++;
695       wave->timeToSpawnNext -= gameTime.deltaTime;
696       if (wave->timeToSpawnNext <= 0.0f)
697       {
698         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
699         if (enemy)
700         {
701           wave->timeToSpawnNext = wave->interval;
702           wave->spawned++;
703         }
704       }
705     }
706     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
707       level->waveEndTimer += gameTime.deltaTime;
708       if (level->waveEndTimer >= 2.0f)
709       {
710         level->nextState = LEVEL_STATE_LOST_WAVE;
711       }
712     }
713     else if (activeWaves == 0 && EnemyCount() == 0)
714     {
715       level->waveEndTimer += gameTime.deltaTime;
716       if (level->waveEndTimer >= 2.0f)
717       {
718         level->nextState = LEVEL_STATE_WON_WAVE;
719       }
720     }
721   }
722 
723   PathFindingMapUpdate(0, 0);
724   EnemyUpdate();
725   TowerUpdate();
726   ProjectileUpdate();
727   ParticleUpdate();
728 
729   if (level->nextState == LEVEL_STATE_RESET)
730   {
731     InitLevel(level);
732   }
733   
734   if (level->nextState == LEVEL_STATE_BATTLE)
735   {
736     InitBattleStateConditions(level);
737   }
738   
739   if (level->nextState == LEVEL_STATE_WON_WAVE)
740   {
741     level->currentWave++;
742     level->state = LEVEL_STATE_WON_WAVE;
743   }
744   
745   if (level->nextState == LEVEL_STATE_LOST_WAVE)
746   {
747     level->state = LEVEL_STATE_LOST_WAVE;
748   }
749 
750   if (level->nextState == LEVEL_STATE_BUILDING)
751   {
752     level->state = LEVEL_STATE_BUILDING;
753   }
754 
755   if (level->nextState == LEVEL_STATE_BUILDING_PLACEMENT)
756   {
757     level->state = LEVEL_STATE_BUILDING_PLACEMENT;
758     level->placementTransitionPosition = (Vector2){
759       level->placementX, level->placementY};
760     // initialize the spring to the current position
761     level->placementTowerSpring = (PhysicsPoint){
762       .position = (Vector3){level->placementX, 8.0f, level->placementY},
763       .velocity = (Vector3){0.0f, 0.0f, 0.0f},
764     };
765     level->placementPhase = PLACEMENT_PHASE_STARTING;
766     level->placementTimer = 0.0f;
767   }
768 
769   if (level->nextState == LEVEL_STATE_WON_LEVEL)
770   {
771     // make something of this later
772     InitLevel(level);
773   }
774 
775   level->nextState = LEVEL_STATE_NONE;
776 }
777 
778 float nextSpawnTime = 0.0f;
779 
780 void ResetGame()
781 {
782   InitLevel(currentLevel);
783 }
784 
785 void InitGame()
786 {
787   TowerInit();
788   EnemyInit();
789   ProjectileInit();
790   ParticleInit();
791   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
792 
793   currentLevel = levels;
794   InitLevel(currentLevel);
795 }
796 
797 //# Immediate GUI functions
798 
799 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
800 {
801   const float healthBarHeight = 6.0f;
802   const float healthBarOffset = 15.0f;
803   const float inset = 2.0f;
804   const float innerWidth = healthBarWidth - inset * 2;
805   const float innerHeight = healthBarHeight - inset * 2;
806 
807   Vector2 screenPos = GetWorldToScreen(position, camera);
808   float centerX = screenPos.x - healthBarWidth * 0.5f;
809   float topY = screenPos.y - healthBarOffset;
810   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
811   float healthWidth = innerWidth * healthRatio;
812   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
813 }
814 
815 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
816 {
817   Rectangle bounds = {x, y, width, height};
818   int isPressed = 0;
819   int isSelected = state && state->isSelected;
820   int isDisabled = state && state->isDisabled;
821   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
822   {
823     Color color = isSelected ? DARKGRAY : GRAY;
824     DrawRectangle(x, y, width, height, color);
825     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
826     {
827       isPressed = 1;
828     }
829     guiState.isBlocked = 1;
830   }
831   else
832   {
833     Color color = isSelected ? WHITE : LIGHTGRAY;
834     DrawRectangle(x, y, width, height, color);
835   }
836   Font font = GetFontDefault();
837   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
838   Color textColor = isDisabled ? GRAY : BLACK;
839   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
840   return isPressed;
841 }
842 
843 //# Main game loop
844 
845 void GameUpdate()
846 {
847   UpdateLevel(currentLevel);
848 }
849 
850 int main(void)
851 {
852   int screenWidth, screenHeight;
853   GetPreferredSize(&screenWidth, &screenHeight);
854   InitWindow(screenWidth, screenHeight, "Tower defense");
855   float gamespeed = 1.0f;
856   SetTargetFPS(30);
857 
858   LoadAssets();
859   InitGame();
860 
861   float pause = 1.0f;
862 
863   while (!WindowShouldClose())
864   {
865     if (IsPaused()) {
866       // canvas is not visible in browser - do nothing
867       continue;
868     }
869 
870     if (IsKeyPressed(KEY_T))
871     {
872       gamespeed += 0.1f;
873       if (gamespeed > 1.05f) gamespeed = 0.1f;
874     }
875 
876     if (IsKeyPressed(KEY_P))
877     {
878       pause = pause > 0.5f ? 0.0f : 1.0f;
879     }
880 
881     float dt = GetFrameTime() * gamespeed * pause;
882     // cap maximum delta time to 0.1 seconds to prevent large time steps
883     if (dt > 0.1f) dt = 0.1f;
884     gameTime.time += dt;
885     gameTime.deltaTime = dt;
886     gameTime.frameCount += 1;
887 
888     float fixedTimeDiff = gameTime.time - gameTime.fixedTimeStart;
889     gameTime.fixedStepCount = (uint8_t)(fixedTimeDiff / gameTime.fixedDeltaTime);
890 
891     BeginDrawing();
892     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
893 
894     GameUpdate();
895     DrawLevel(currentLevel);
896 
897     DrawText(TextFormat("Speed: %.1f", gamespeed), GetScreenWidth() - 180, 60, 20, WHITE);
898     EndDrawing();
899 
900     gameTime.fixedTimeStart += gameTime.fixedStepCount * gameTime.fixedDeltaTime;
901   }
902 
903   CloseWindow();
904 
905   return 0;
906 }  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 //# Declarations
 10 
 11 typedef struct PhysicsPoint
 12 {
 13   Vector3 position;
 14   Vector3 velocity;
 15 } PhysicsPoint;
 16 
 17 #define ENEMY_MAX_PATH_COUNT 8
 18 #define ENEMY_MAX_COUNT 400
 19 #define ENEMY_TYPE_NONE 0
 20 
 21 #define ENEMY_TYPE_MINION 1
 22 #define ENEMY_TYPE_RUNNER 2
 23 #define ENEMY_TYPE_SHIELD 3
 24 #define ENEMY_TYPE_BOSS 3
 25 
 26 #define PARTICLE_MAX_COUNT 400
 27 #define PARTICLE_TYPE_NONE 0
 28 #define PARTICLE_TYPE_EXPLOSION 1
 29 
 30 typedef struct Particle
 31 {
 32   uint8_t particleType;
 33   float spawnTime;
 34   float lifetime;
 35   Vector3 position;
 36   Vector3 velocity;
 37   Vector3 scale;
 38 } Particle;
 39 
 40 #define TOWER_MAX_COUNT 400
 41 enum TowerType
 42 {
 43   TOWER_TYPE_NONE,
 44   TOWER_TYPE_BASE,
 45   TOWER_TYPE_ARCHER,
 46   TOWER_TYPE_BALLISTA,
 47   TOWER_TYPE_CATAPULT,
 48   TOWER_TYPE_WALL,
 49   TOWER_TYPE_COUNT
 50 };
 51 
 52 typedef struct HitEffectConfig
 53 {
 54   float damage;
 55   float areaDamageRadius;
 56   float pushbackPowerDistance;
 57 } HitEffectConfig;
 58 
 59 typedef struct TowerTypeConfig
 60 {
 61   float cooldown;
 62   float range;
 63   float projectileSpeed;
 64   
 65   uint8_t cost;
 66   uint8_t projectileType;
 67   uint16_t maxHealth;
 68 
 69   HitEffectConfig hitEffect;
 70 } TowerTypeConfig;
 71 
 72 typedef struct Tower
 73 {
 74   int16_t x, y;
 75   uint8_t towerType;
 76   Vector2 lastTargetPosition;
 77   float cooldown;
 78   float damage;
 79 } Tower;
 80 
 81 typedef struct GameTime
 82 {
 83   float time;
 84   float deltaTime;
 85   uint32_t frameCount;
 86 
 87   float fixedDeltaTime;
 88   // leaving the fixed time stepping to the update functions,
 89   // we need to know the fixed time at the start of the frame
 90   float fixedTimeStart;
 91   // and the number of fixed steps that we have to make this frame
 92   // The fixedTime is fixedTimeStart + n * fixedStepCount
 93   uint8_t fixedStepCount;
 94 } GameTime;
 95 
 96 typedef struct ButtonState {
 97   char isSelected;
 98   char isDisabled;
 99 } ButtonState;
100 
101 typedef struct GUIState {
102   int isBlocked;
103 } GUIState;
104 
105 typedef enum LevelState
106 {
107   LEVEL_STATE_NONE,
108   LEVEL_STATE_BUILDING,
109   LEVEL_STATE_BUILDING_PLACEMENT,
110   LEVEL_STATE_BATTLE,
111   LEVEL_STATE_WON_WAVE,
112   LEVEL_STATE_LOST_WAVE,
113   LEVEL_STATE_WON_LEVEL,
114   LEVEL_STATE_RESET,
115 } LevelState;
116 
117 typedef struct EnemyWave {
118   uint8_t enemyType;
119   uint8_t wave;
120   uint16_t count;
121   float interval;
122   float delay;
123   Vector2 spawnPosition;
124 
125   uint16_t spawned;
126   float timeToSpawnNext;
127 } EnemyWave;
128 
129 #define ENEMY_MAX_WAVE_COUNT 10
130 
131 typedef enum PlacementPhase
132 {
133   PLACEMENT_PHASE_STARTING,
134   PLACEMENT_PHASE_MOVING,
135   PLACEMENT_PHASE_PLACING,
136 } PlacementPhase;
137 
138 typedef struct Level
139 {
140   int seed;
141   LevelState state;
142   LevelState nextState;
143   Camera3D camera;
144   int placementMode;
145   PlacementPhase placementPhase;
146   float placementTimer;
147   int16_t placementX;
148   int16_t placementY;
149   Vector2 placementTransitionPosition;
150   PhysicsPoint placementTowerSpring;
151 
152   int initialGold;
153   int playerGold;
154 
155   EnemyWave waves[ENEMY_MAX_WAVE_COUNT];
156   int currentWave;
157   float waveEndTimer;
158 } Level;
159 
160 typedef struct DeltaSrc
161 {
162   char x, y;
163 } DeltaSrc;
164 
165 typedef struct PathfindingMap
166 {
167   int width, height;
168   float scale;
169   float *distances;
170   long *towerIndex; 
171   DeltaSrc *deltaSrc;
172   float maxDistance;
173   Matrix toMapSpace;
174   Matrix toWorldSpace;
175 } PathfindingMap;
176 
177 // when we execute the pathfinding algorithm, we need to store the active nodes
178 // in a queue. Each node has a position, a distance from the start, and the
179 // position of the node that we came from.
180 typedef struct PathfindingNode
181 {
182   int16_t x, y, fromX, fromY;
183   float distance;
184 } PathfindingNode;
185 
186 typedef struct EnemyId
187 {
188   uint16_t index;
189   uint16_t generation;
190 } EnemyId;
191 
192 typedef struct EnemyClassConfig
193 {
194   float speed;
195   float health;
196   float radius;
197   float maxAcceleration;
198   float requiredContactTime;
199   float explosionDamage;
200   float explosionRange;
201   float explosionPushbackPower;
202   int goldValue;
203 } EnemyClassConfig;
204 
205 typedef struct Enemy
206 {
207   int16_t currentX, currentY;
208   int16_t nextX, nextY;
209   Vector2 simPosition;
210   Vector2 simVelocity;
211   uint16_t generation;
212   float walkedDistance;
213   float startMovingTime;
214   float damage, futureDamage;
215   float contactTime;
216   uint8_t enemyType;
217   uint8_t movePathCount;
218   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
219 } Enemy;
220 
221 // a unit that uses sprites to be drawn
222 #define SPRITE_UNIT_ANIMATION_COUNT 6
223 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 1
224 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 2
225 
226 typedef struct SpriteAnimation
227 {
228   Rectangle srcRect;
229   Vector2 offset;
230   uint8_t animationId;
231   uint8_t frameCount;
232   uint8_t frameWidth;
233   float frameDuration;
234 } SpriteAnimation;
235 
236 typedef struct SpriteUnit
237 {
238   SpriteAnimation animations[SPRITE_UNIT_ANIMATION_COUNT];
239 } SpriteUnit;
240 
241 #define PROJECTILE_MAX_COUNT 1200
242 #define PROJECTILE_TYPE_NONE 0
243 #define PROJECTILE_TYPE_ARROW 1
244 #define PROJECTILE_TYPE_CATAPULT 2
245 #define PROJECTILE_TYPE_BALLISTA 3
246 
247 typedef struct Projectile
248 {
249   uint8_t projectileType;
250   float shootTime;
251   float arrivalTime;
252   float distance;
253   Vector3 position;
254   Vector3 target;
255   Vector3 directionNormal;
256   EnemyId targetEnemy;
257   HitEffectConfig hitEffectConfig;
258 } Projectile;
259 
260 //# Function declarations
261 float TowerGetMaxHealth(Tower *tower);
262 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
263 int EnemyAddDamageRange(Vector2 position, float range, float damage);
264 int EnemyAddDamage(Enemy *enemy, float damage);
265 
266 //# Enemy functions
267 void EnemyInit();
268 void EnemyDraw();
269 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
270 void EnemyUpdate();
271 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
272 float EnemyGetMaxHealth(Enemy *enemy);
273 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
274 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
275 EnemyId EnemyGetId(Enemy *enemy);
276 Enemy *EnemyTryResolve(EnemyId enemyId);
277 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
278 int EnemyAddDamage(Enemy *enemy, float damage);
279 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
280 int EnemyCount();
281 void EnemyDrawHealthbars(Camera3D camera);
282 
283 //# Tower functions
284 void TowerInit();
285 Tower *TowerGetAt(int16_t x, int16_t y);
286 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
287 Tower *GetTowerByType(uint8_t towerType);
288 int GetTowerCosts(uint8_t towerType);
289 float TowerGetMaxHealth(Tower *tower);
290 void TowerDraw();
291 void TowerDrawSingle(Tower tower);
292 void TowerUpdate();
293 void TowerDrawHealthBars(Camera3D camera);
294 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
295 
296 //# Particles
297 void ParticleInit();
298 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime);
299 void ParticleUpdate();
300 void ParticleDraw();
301 
302 //# Projectiles
303 void ProjectileInit();
304 void ProjectileDraw();
305 void ProjectileUpdate();
306 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig);
307 
308 //# Pathfinding map
309 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
310 float PathFindingGetDistance(int mapX, int mapY);
311 Vector2 PathFindingGetGradient(Vector3 world);
312 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
313 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells);
314 void PathFindingMapDraw();
315 
316 //# UI
317 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
318 
319 //# Level
320 void DrawLevelGround(Level *level);
321 void DrawEnemyPath(Level *level, Color arrowColor);
322 
323 //# variables
324 extern Level *currentLevel;
325 extern Enemy enemies[ENEMY_MAX_COUNT];
326 extern int enemyCount;
327 extern EnemyClassConfig enemyClassConfigs[];
328 
329 extern GUIState guiState;
330 extern GameTime gameTime;
331 extern Tower towers[TOWER_MAX_COUNT];
332 extern int towerCount;
333 
334 extern Texture2D palette, spriteSheet;
335 
336 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 #include <rlgl.h>
  6 
  7 EnemyClassConfig enemyClassConfigs[] = {
  8     [ENEMY_TYPE_MINION] = {
  9       .health = 10.0f, 
 10       .speed = 0.6f, 
 11       .radius = 0.25f, 
 12       .maxAcceleration = 1.0f,
 13       .explosionDamage = 1.0f,
 14       .requiredContactTime = 0.5f,
 15       .explosionRange = 1.0f,
 16       .explosionPushbackPower = 0.25f,
 17       .goldValue = 1,
 18     },
 19     [ENEMY_TYPE_RUNNER] = {
 20       .health = 5.0f, 
 21       .speed = 1.0f, 
 22       .radius = 0.25f, 
 23       .maxAcceleration = 2.0f,
 24       .explosionDamage = 1.0f,
 25       .requiredContactTime = 0.5f,
 26       .explosionRange = 1.0f,
 27       .explosionPushbackPower = 0.25f,
 28       .goldValue = 2,
 29     },
 30 };
 31 
 32 Enemy enemies[ENEMY_MAX_COUNT];
 33 int enemyCount = 0;
 34 
 35 SpriteUnit enemySprites[] = {
 36     [ENEMY_TYPE_MINION] = {
 37       .animations[0] = {
 38         .srcRect = {0, 17, 16, 15},
 39         .offset = {8.0f, 0.0f},
 40         .frameCount = 6,
 41         .frameDuration = 0.1f,
 42       },
 43       .animations[1] = {
 44         .srcRect = {1, 33, 15, 14},
 45         .offset = {7.0f, 0.0f},
 46         .frameCount = 6,
 47         .frameWidth = 16,
 48         .frameDuration = 0.1f,
 49       },
 50     },
 51     [ENEMY_TYPE_RUNNER] = {
 52       .animations[0] = {
 53         .srcRect = {0, 17, 16, 15},
 54         .offset = {8.0f, 0.0f},
 55         .frameCount = 6,
 56         .frameDuration = 0.1f,
 57       },
 58     },
 59 };
 60 
 61 void EnemyInit()
 62 {
 63   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 64   {
 65     enemies[i] = (Enemy){0};
 66   }
 67   enemyCount = 0;
 68 }
 69 
 70 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 71 {
 72   return enemyClassConfigs[enemy->enemyType].speed;
 73 }
 74 
 75 float EnemyGetMaxHealth(Enemy *enemy)
 76 {
 77   return enemyClassConfigs[enemy->enemyType].health;
 78 }
 79 
 80 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 81 {
 82   int16_t castleX = 0;
 83   int16_t castleY = 0;
 84   int16_t dx = castleX - currentX;
 85   int16_t dy = castleY - currentY;
 86   if (dx == 0 && dy == 0)
 87   {
 88     *nextX = currentX;
 89     *nextY = currentY;
 90     return 1;
 91   }
 92   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 93 
 94   if (gradient.x == 0 && gradient.y == 0)
 95   {
 96     *nextX = currentX;
 97     *nextY = currentY;
 98     return 1;
 99   }
100 
101   if (fabsf(gradient.x) > fabsf(gradient.y))
102   {
103     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
104     *nextY = currentY;
105     return 0;
106   }
107   *nextX = currentX;
108   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
109   return 0;
110 }
111 
112 
113 // this function predicts the movement of the unit for the next deltaT seconds
114 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
115 {
116   const float pointReachedDistance = 0.25f;
117   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
118   const float maxSimStepTime = 0.015625f;
119   
120   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
121   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
122   int16_t nextX = enemy->nextX;
123   int16_t nextY = enemy->nextY;
124   Vector2 position = enemy->simPosition;
125   int passedCount = 0;
126   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
127   {
128     float stepTime = fminf(deltaT - t, maxSimStepTime);
129     Vector2 target = (Vector2){nextX, nextY};
130     float speed = Vector2Length(*velocity);
131     // draw the target position for debugging
132     //DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
133     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
134     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
135     {
136       // we reached the target position, let's move to the next waypoint
137       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
138       target = (Vector2){nextX, nextY};
139       // track how many waypoints we passed
140       passedCount++;
141     }
142     
143     // acceleration towards the target
144     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
145     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
146     *velocity = Vector2Add(*velocity, acceleration);
147 
148     // limit the speed to the maximum speed
149     if (speed > maxSpeed)
150     {
151       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
152     }
153 
154     // move the enemy
155     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
156   }
157 
158   if (waypointPassedCount)
159   {
160     (*waypointPassedCount) = passedCount;
161   }
162 
163   return position;
164 }
165 
166 void EnemyDraw()
167 {
168   rlDrawRenderBatchActive();
169   rlDisableDepthMask();
170   for (int i = 0; i < enemyCount; i++)
171   {
172     Enemy enemy = enemies[i];
173     if (enemy.enemyType == ENEMY_TYPE_NONE)
174     {
175       continue;
176     }
177 
178     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
179     
180     // don't draw any trails for now; might replace this with footprints later
181     // if (enemy.movePathCount > 0)
182     // {
183     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
184     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
185     // }
186     // for (int j = 1; j < enemy.movePathCount; j++)
187     // {
188     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
189     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
190     //   DrawLine3D(p, q, GREEN);
191     // }
192 
193     switch (enemy.enemyType)
194     {
195     case ENEMY_TYPE_MINION:
196       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y}, 
197         enemy.walkedDistance, 0, 0);
198       break;
199     }
200   }
201   rlDrawRenderBatchActive();
202   rlEnableDepthMask();
203 }
204 
205 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
206 {
207   // damage the tower
208   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
209   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
210   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
211   float explosionRange2 = explosionRange * explosionRange;
212   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
213   // explode the enemy
214   if (tower->damage >= TowerGetMaxHealth(tower))
215   {
216     tower->towerType = TOWER_TYPE_NONE;
217   }
218 
219   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
220     explosionSource, 
221     (Vector3){0, 0.1f, 0}, (Vector3){1.0f, 1.0f, 1.0f}, 1.0f);
222 
223   enemy->enemyType = ENEMY_TYPE_NONE;
224 
225   // push back enemies & dealing damage
226   for (int i = 0; i < enemyCount; i++)
227   {
228     Enemy *other = &enemies[i];
229     if (other->enemyType == ENEMY_TYPE_NONE)
230     {
231       continue;
232     }
233     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
234     if (distanceSqr > 0 && distanceSqr < explosionRange2)
235     {
236       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
237       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
238       EnemyAddDamage(other, explosionDamge);
239     }
240   }
241 }
242 
243 void EnemyUpdate()
244 {
245   const float castleX = 0;
246   const float castleY = 0;
247   const float maxPathDistance2 = 0.25f * 0.25f;
248   
249   for (int i = 0; i < enemyCount; i++)
250   {
251     Enemy *enemy = &enemies[i];
252     if (enemy->enemyType == ENEMY_TYPE_NONE)
253     {
254       continue;
255     }
256 
257     int waypointPassedCount = 0;
258     Vector2 prevPosition = enemy->simPosition;
259     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
260     enemy->startMovingTime = gameTime.time;
261     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
262     // track path of unit
263     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
264     {
265       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
266       {
267         enemy->movePath[j] = enemy->movePath[j - 1];
268       }
269       enemy->movePath[0] = enemy->simPosition;
270       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
271       {
272         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
273       }
274     }
275 
276     if (waypointPassedCount > 0)
277     {
278       enemy->currentX = enemy->nextX;
279       enemy->currentY = enemy->nextY;
280       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
281         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
282       {
283         // enemy reached the castle; remove it
284         enemy->enemyType = ENEMY_TYPE_NONE;
285         continue;
286       }
287     }
288   }
289 
290   // handle collisions between enemies
291   for (int i = 0; i < enemyCount - 1; i++)
292   {
293     Enemy *enemyA = &enemies[i];
294     if (enemyA->enemyType == ENEMY_TYPE_NONE)
295     {
296       continue;
297     }
298     for (int j = i + 1; j < enemyCount; j++)
299     {
300       Enemy *enemyB = &enemies[j];
301       if (enemyB->enemyType == ENEMY_TYPE_NONE)
302       {
303         continue;
304       }
305       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
306       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
307       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
308       float radiusSum = radiusA + radiusB;
309       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
310       {
311         // collision
312         float distance = sqrtf(distanceSqr);
313         float overlap = radiusSum - distance;
314         // move the enemies apart, but softly; if we have a clog of enemies,
315         // moving them perfectly apart can cause them to jitter
316         float positionCorrection = overlap / 5.0f;
317         Vector2 direction = (Vector2){
318             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
319             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
320         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
321         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
322       }
323     }
324   }
325 
326   // handle collisions between enemies and towers
327   for (int i = 0; i < enemyCount; i++)
328   {
329     Enemy *enemy = &enemies[i];
330     if (enemy->enemyType == ENEMY_TYPE_NONE)
331     {
332       continue;
333     }
334     enemy->contactTime -= gameTime.deltaTime;
335     if (enemy->contactTime < 0.0f)
336     {
337       enemy->contactTime = 0.0f;
338     }
339 
340     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
341     // linear search over towers; could be optimized by using path finding tower map,
342     // but for now, we keep it simple
343     for (int j = 0; j < towerCount; j++)
344     {
345       Tower *tower = &towers[j];
346       if (tower->towerType == TOWER_TYPE_NONE)
347       {
348         continue;
349       }
350       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
351       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
352       if (distanceSqr > combinedRadius * combinedRadius)
353       {
354         continue;
355       }
356       // potential collision; square / circle intersection
357       float dx = tower->x - enemy->simPosition.x;
358       float dy = tower->y - enemy->simPosition.y;
359       float absDx = fabsf(dx);
360       float absDy = fabsf(dy);
361       Vector3 contactPoint = {0};
362       if (absDx <= 0.5f && absDx <= absDy) {
363         // vertical collision; push the enemy out horizontally
364         float overlap = enemyRadius + 0.5f - absDy;
365         if (overlap < 0.0f)
366         {
367           continue;
368         }
369         float direction = dy > 0.0f ? -1.0f : 1.0f;
370         enemy->simPosition.y += direction * overlap;
371         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
372       }
373       else if (absDy <= 0.5f && absDy <= absDx)
374       {
375         // horizontal collision; push the enemy out vertically
376         float overlap = enemyRadius + 0.5f - absDx;
377         if (overlap < 0.0f)
378         {
379           continue;
380         }
381         float direction = dx > 0.0f ? -1.0f : 1.0f;
382         enemy->simPosition.x += direction * overlap;
383         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
384       }
385       else
386       {
387         // possible collision with a corner
388         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
389         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
390         float cornerX = tower->x + cornerDX;
391         float cornerY = tower->y + cornerDY;
392         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
393         if (cornerDistanceSqr > enemyRadius * enemyRadius)
394         {
395           continue;
396         }
397         // push the enemy out along the diagonal
398         float cornerDistance = sqrtf(cornerDistanceSqr);
399         float overlap = enemyRadius - cornerDistance;
400         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
401         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
402         enemy->simPosition.x -= directionX * overlap;
403         enemy->simPosition.y -= directionY * overlap;
404         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
405       }
406 
407       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
408       {
409         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
410         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
411         {
412           EnemyTriggerExplode(enemy, tower, contactPoint);
413         }
414       }
415     }
416   }
417 }
418 
419 EnemyId EnemyGetId(Enemy *enemy)
420 {
421   return (EnemyId){enemy - enemies, enemy->generation};
422 }
423 
424 Enemy *EnemyTryResolve(EnemyId enemyId)
425 {
426   if (enemyId.index >= ENEMY_MAX_COUNT)
427   {
428     return 0;
429   }
430   Enemy *enemy = &enemies[enemyId.index];
431   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
432   {
433     return 0;
434   }
435   return enemy;
436 }
437 
438 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
439 {
440   Enemy *spawn = 0;
441   for (int i = 0; i < enemyCount; i++)
442   {
443     Enemy *enemy = &enemies[i];
444     if (enemy->enemyType == ENEMY_TYPE_NONE)
445     {
446       spawn = enemy;
447       break;
448     }
449   }
450 
451   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
452   {
453     spawn = &enemies[enemyCount++];
454   }
455 
456   if (spawn)
457   {
458     spawn->currentX = currentX;
459     spawn->currentY = currentY;
460     spawn->nextX = currentX;
461     spawn->nextY = currentY;
462     spawn->simPosition = (Vector2){currentX, currentY};
463     spawn->simVelocity = (Vector2){0, 0};
464     spawn->enemyType = enemyType;
465     spawn->startMovingTime = gameTime.time;
466     spawn->damage = 0.0f;
467     spawn->futureDamage = 0.0f;
468     spawn->generation++;
469     spawn->movePathCount = 0;
470     spawn->walkedDistance = 0.0f;
471   }
472 
473   return spawn;
474 }
475 
476 int EnemyAddDamageRange(Vector2 position, float range, float damage)
477 {
478   int count = 0;
479   float range2 = range * range;
480   for (int i = 0; i < enemyCount; i++)
481   {
482     Enemy *enemy = &enemies[i];
483     if (enemy->enemyType == ENEMY_TYPE_NONE)
484     {
485       continue;
486     }
487     float distance2 = Vector2DistanceSqr(position, enemy->simPosition);
488     if (distance2 <= range2)
489     {
490       EnemyAddDamage(enemy, damage);
491       count++;
492     }
493   }
494   return count;
495 }
496 
497 int EnemyAddDamage(Enemy *enemy, float damage)
498 {
499   enemy->damage += damage;
500   if (enemy->damage >= EnemyGetMaxHealth(enemy))
501   {
502     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
503     enemy->enemyType = ENEMY_TYPE_NONE;
504     return 1;
505   }
506 
507   return 0;
508 }
509 
510 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
511 {
512   int16_t castleX = 0;
513   int16_t castleY = 0;
514   Enemy* closest = 0;
515   int16_t closestDistance = 0;
516   float range2 = range * range;
517   for (int i = 0; i < enemyCount; i++)
518   {
519     Enemy* enemy = &enemies[i];
520     if (enemy->enemyType == ENEMY_TYPE_NONE)
521     {
522       continue;
523     }
524     float maxHealth = EnemyGetMaxHealth(enemy);
525     if (enemy->futureDamage >= maxHealth)
526     {
527       // ignore enemies that will die soon
528       continue;
529     }
530     int16_t dx = castleX - enemy->currentX;
531     int16_t dy = castleY - enemy->currentY;
532     int16_t distance = abs(dx) + abs(dy);
533     if (!closest || distance < closestDistance)
534     {
535       float tdx = towerX - enemy->currentX;
536       float tdy = towerY - enemy->currentY;
537       float tdistance2 = tdx * tdx + tdy * tdy;
538       if (tdistance2 <= range2)
539       {
540         closest = enemy;
541         closestDistance = distance;
542       }
543     }
544   }
545   return closest;
546 }
547 
548 int EnemyCount()
549 {
550   int count = 0;
551   for (int i = 0; i < enemyCount; i++)
552   {
553     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
554     {
555       count++;
556     }
557   }
558   return count;
559 }
560 
561 void EnemyDrawHealthbars(Camera3D camera)
562 {
563   for (int i = 0; i < enemyCount; i++)
564   {
565     Enemy *enemy = &enemies[i];
566     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
567     {
568       continue;
569     }
570     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
571     float maxHealth = EnemyGetMaxHealth(enemy);
572     float health = maxHealth - enemy->damage;
573     float healthRatio = health / maxHealth;
574     
575     DrawHealthBar(camera, position, healthRatio, GREEN, 15.0f);
576   }
577 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static TowerTypeConfig towerTypeConfigs[TOWER_TYPE_COUNT] = {
  5     [TOWER_TYPE_BASE] = {
  6         .maxHealth = 10,
  7     },
  8     [TOWER_TYPE_ARCHER] = {
  9         .cooldown = 0.5f,
 10         .range = 3.0f,
 11         .cost = 6,
 12         .maxHealth = 10,
 13         .projectileSpeed = 4.0f,
 14         .projectileType = PROJECTILE_TYPE_ARROW,
 15         .hitEffect = {
 16           .damage = 3.0f,
 17         }
 18     },
 19     [TOWER_TYPE_BALLISTA] = {
 20         .cooldown = 1.5f,
 21         .range = 6.0f,
 22         .cost = 9,
 23         .maxHealth = 10,
 24         .projectileSpeed = 10.0f,
 25         .projectileType = PROJECTILE_TYPE_BALLISTA,
 26         .hitEffect = {
 27           .damage = 6.0f,
 28           .pushbackPowerDistance = 0.25f,
 29         }
 30     },
 31     [TOWER_TYPE_CATAPULT] = {
 32         .cooldown = 1.7f,
 33         .range = 5.0f,
 34         .cost = 10,
 35         .maxHealth = 10,
 36         .projectileSpeed = 3.0f,
 37         .projectileType = PROJECTILE_TYPE_CATAPULT,
 38         .hitEffect = {
 39           .damage = 2.0f,
 40           .areaDamageRadius = 1.75f,
 41         }
 42     },
 43     [TOWER_TYPE_WALL] = {
 44         .cost = 2,
 45         .maxHealth = 10,
 46     },
 47 };
 48 
 49 Tower towers[TOWER_MAX_COUNT];
 50 int towerCount = 0;
 51 
 52 Model towerModels[TOWER_TYPE_COUNT];
 53 
 54 // definition of our archer unit
 55 SpriteUnit archerUnit = {
 56   .animations[0] = {
 57     .srcRect = {0, 0, 16, 16},
 58     .offset = {7, 1},
 59     .frameCount = 1,
 60     .frameDuration = 0.0f,
 61   },
 62   .animations[1] = {
 63     .animationId = SPRITE_UNIT_PHASE_WEAPON_COOLDOWN,
 64     .srcRect = {16, 0, 6, 16},
 65     .offset = {8, 0},
 66   },
 67   .animations[2] = {
 68     .animationId = SPRITE_UNIT_PHASE_WEAPON_IDLE,
 69     .srcRect = {22, 0, 11, 16},
 70     .offset = {10, 0},
 71   },
 72 };
 73 
 74 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 75 {
 76   float xScale = flip ? -1.0f : 1.0f;
 77   Camera3D camera = currentLevel->camera;
 78   float size = 0.5f;
 79   // we want the sprite to face the camera, so we need to calculate the up vector
 80   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 81   Vector3 up = {0, 1, 0};
 82   Vector3 right = Vector3CrossProduct(forward, up);
 83   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 84   
 85   for (int i=0;i<SPRITE_UNIT_ANIMATION_COUNT;i++)
 86   {
 87     SpriteAnimation anim = unit.animations[i];
 88     if (anim.animationId != phase && anim.animationId != 0)
 89     {
 90       continue;
 91     }
 92     Rectangle srcRect = anim.srcRect;
 93     if (anim.frameCount > 1)
 94     {
 95       int w = anim.frameWidth > 0 ? anim.frameWidth : srcRect.width;
 96       srcRect.x += (int)(t / anim.frameDuration) % anim.frameCount * w;
 97     }
 98     Vector2 offset = { anim.offset.x / 16.0f * size, anim.offset.y / 16.0f * size * xScale };
 99     Vector2 scale = { srcRect.width / 16.0f * size, srcRect.height / 16.0f * size };
100     
101     if (flip)
102     {
103       srcRect.x += srcRect.width;
104       srcRect.width = -srcRect.width;
105       offset.x = scale.x - offset.x;
106     }
107     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
108     // move the sprite slightly towards the camera to avoid z-fighting
109     position = Vector3Add(position, Vector3Scale(Vector3Normalize(forward), -0.01f));  
110   }
111 }
112 
113 void TowerInit()
114 {
115   for (int i = 0; i < TOWER_MAX_COUNT; i++)
116   {
117     towers[i] = (Tower){0};
118   }
119   towerCount = 0;
120 
121   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
122   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
123 
124   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
125   {
126     if (towerModels[i].materials)
127     {
128       // assign the palette texture to the material of the model (0 is not used afaik)
129       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
130     }
131   }
132 }
133 
134 static void TowerGunUpdate(Tower *tower)
135 {
136   TowerTypeConfig config = towerTypeConfigs[tower->towerType];
137   if (tower->cooldown <= 0.0f)
138   {
139     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, config.range);
140     if (enemy)
141     {
142       tower->cooldown = config.cooldown;
143       // shoot the enemy; determine future position of the enemy
144       float bulletSpeed = config.projectileSpeed;
145       Vector2 velocity = enemy->simVelocity;
146       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
147       Vector2 towerPosition = {tower->x, tower->y};
148       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
149       for (int i = 0; i < 8; i++) {
150         velocity = enemy->simVelocity;
151         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
152         float distance = Vector2Distance(towerPosition, futurePosition);
153         float eta2 = distance / bulletSpeed;
154         if (fabs(eta - eta2) < 0.01f) {
155           break;
156         }
157         eta = (eta2 + eta) * 0.5f;
158       }
159 
160       ProjectileTryAdd(config.projectileType, enemy, 
161         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
162         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
163         bulletSpeed, config.hitEffect);
164       enemy->futureDamage += config.hitEffect.damage;
165       tower->lastTargetPosition = futurePosition;
166     }
167   }
168   else
169   {
170     tower->cooldown -= gameTime.deltaTime;
171   }
172 }
173 
174 Tower *TowerGetAt(int16_t x, int16_t y)
175 {
176   for (int i = 0; i < towerCount; i++)
177   {
178     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
179     {
180       return &towers[i];
181     }
182   }
183   return 0;
184 }
185 
186 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
187 {
188   if (towerCount >= TOWER_MAX_COUNT)
189   {
190     return 0;
191   }
192 
193   Tower *tower = TowerGetAt(x, y);
194   if (tower)
195   {
196     return 0;
197   }
198 
199   tower = &towers[towerCount++];
200   tower->x = x;
201   tower->y = y;
202   tower->towerType = towerType;
203   tower->cooldown = 0.0f;
204   tower->damage = 0.0f;
205   return tower;
206 }
207 
208 Tower *GetTowerByType(uint8_t towerType)
209 {
210   for (int i = 0; i < towerCount; i++)
211   {
212     if (towers[i].towerType == towerType)
213     {
214       return &towers[i];
215     }
216   }
217   return 0;
218 }
219 
220 int GetTowerCosts(uint8_t towerType)
221 {
222   return towerTypeConfigs[towerType].cost;
223 }
224 
225 float TowerGetMaxHealth(Tower *tower)
226 {
227   return towerTypeConfigs[tower->towerType].maxHealth;
228 }
229 
230 void TowerDrawSingle(Tower tower)
231 {
232   if (tower.towerType == TOWER_TYPE_NONE)
233   {
234     return;
235   }
236 
237   switch (tower.towerType)
238   {
239   case TOWER_TYPE_ARCHER:
240     {
241       Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
242       Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
243       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
244       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
245         tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
246     }
247     break;
248   case TOWER_TYPE_BALLISTA:
249     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, BROWN);
250     break;
251   case TOWER_TYPE_CATAPULT:
252     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, DARKGRAY);
253     break;
254   default:
255     if (towerModels[tower.towerType].materials)
256     {
257       DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
258     } else {
259       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
260     }
261     break;
262   }
263 }
264 
265 void TowerDraw()
266 {
267   for (int i = 0; i < towerCount; i++)
268   {
269     TowerDrawSingle(towers[i]);
270   }
271 }
272 
273 void TowerUpdate()
274 {
275   for (int i = 0; i < towerCount; i++)
276   {
277     Tower *tower = &towers[i];
278     switch (tower->towerType)
279     {
280     case TOWER_TYPE_CATAPULT:
281     case TOWER_TYPE_BALLISTA:
282     case TOWER_TYPE_ARCHER:
283       TowerGunUpdate(tower);
284       break;
285     }
286   }
287 }
288 
289 void TowerDrawHealthBars(Camera3D camera)
290 {
291   for (int i = 0; i < towerCount; i++)
292   {
293     Tower *tower = &towers[i];
294     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
295     {
296       continue;
297     }
298     
299     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
300     float maxHealth = TowerGetMaxHealth(tower);
301     float health = maxHealth - tower->damage;
302     float healthRatio = health / maxHealth;
303     
304     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
305   }
306 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells)
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < blockedCellCount; i++)
131   {
132     int16_t mapX, mapY;
133     if (!PathFindingFromWorldToMapPosition((Vector3){blockedCells[i].x, 0.0f, blockedCells[i].y}, &mapX, &mapY))
134     {
135       continue;
136     }
137     int index = mapY * width + mapX;
138     pathfindingMap.towerIndex[index] = -2;
139   }
140 
141   for (int i = 0; i < towerCount; i++)
142   {
143     Tower *tower = &towers[i];
144     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
145     {
146       continue;
147     }
148     int16_t mapX, mapY;
149     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
150     // this would not work correctly and needs to be refined to allow towers covering multiple cells
151     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
152     // one cell. For now.
153     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
154     {
155       continue;
156     }
157     int index = mapY * width + mapX;
158     pathfindingMap.towerIndex[index] = i;
159   }
160 
161   // we start at the castle and add the castle to the queue
162   pathfindingMap.maxDistance = 0.0f;
163   pathfindingNodeQueueCount = 0;
164   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
165   PathfindingNode *node = 0;
166   while ((node = PathFindingNodePop()))
167   {
168     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
169     {
170       continue;
171     }
172     int index = node->y * width + node->x;
173     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
174     {
175       continue;
176     }
177 
178     int deltaX = node->x - node->fromX;
179     int deltaY = node->y - node->fromY;
180     // even if the cell is blocked by a tower, we still may want to store the direction
181     // (though this might not be needed, IDK right now)
182     pathfindingMap.deltaSrc[index].x = (char) deltaX;
183     pathfindingMap.deltaSrc[index].y = (char) deltaY;
184 
185     // we skip nodes that are blocked by towers or by the provided blocked cells
186     if (pathfindingMap.towerIndex[index] != -1)
187     {
188       node->distance += 8.0f;
189     }
190     pathfindingMap.distances[index] = node->distance;
191     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
192     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
193     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
194     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
195     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
196   }
197 }
198 
199 void PathFindingMapDraw()
200 {
201   float cellSize = pathfindingMap.scale * 0.9f;
202   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
203   for (int x = 0; x < pathfindingMap.width; x++)
204   {
205     for (int y = 0; y < pathfindingMap.height; y++)
206     {
207       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
208       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
209       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
210       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
211       // animate the distance "wave" to show how the pathfinding algorithm expands
212       // from the castle
213       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
214       {
215         color = BLACK;
216       }
217       DrawCube(position, cellSize, 0.1f, cellSize, color);
218     }
219   }
220 }
221 
222 Vector2 PathFindingGetGradient(Vector3 world)
223 {
224   int16_t mapX, mapY;
225   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
226   {
227     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
228     return (Vector2){(float)-delta.x, (float)-delta.y};
229   }
230   // fallback to a simple gradient calculation
231   float n = PathFindingGetDistance(mapX, mapY - 1);
232   float s = PathFindingGetDistance(mapX, mapY + 1);
233   float w = PathFindingGetDistance(mapX - 1, mapY);
234   float e = PathFindingGetDistance(mapX + 1, mapY);
235   return (Vector2){w - e + 0.25f, n - s + 0.125f};
236 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 typedef struct ProjectileConfig
  8 {
  9   float arcFactor;
 10   Color color;
 11   Color trailColor;
 12 } ProjectileConfig;
 13 
 14 ProjectileConfig projectileConfigs[] = {
 15     [PROJECTILE_TYPE_ARROW] = {
 16         .arcFactor = 0.15f,
 17         .color = RED,
 18         .trailColor = BROWN,
 19     },
 20     [PROJECTILE_TYPE_CATAPULT] = {
 21         .arcFactor = 0.5f,
 22         .color = RED,
 23         .trailColor = GRAY,
 24     },
 25     [PROJECTILE_TYPE_BALLISTA] = {
 26         .arcFactor = 0.025f,
 27         .color = RED,
 28         .trailColor = BROWN,
 29     },
 30 };
 31 
 32 void ProjectileInit()
 33 {
 34   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 35   {
 36     projectiles[i] = (Projectile){0};
 37   }
 38 }
 39 
 40 void ProjectileDraw()
 41 {
 42   for (int i = 0; i < projectileCount; i++)
 43   {
 44     Projectile projectile = projectiles[i];
 45     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 46     {
 47       continue;
 48     }
 49     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 50     if (transition >= 1.0f)
 51     {
 52       continue;
 53     }
 54 
 55     ProjectileConfig config = projectileConfigs[projectile.projectileType];
 56     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 57     {
 58       float t = transition + transitionOffset * 0.3f;
 59       if (t > 1.0f)
 60       {
 61         break;
 62       }
 63       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 64       Color color = config.color;
 65       color = ColorLerp(config.trailColor, config.color, transitionOffset * transitionOffset);
 66       // fake a ballista flight path using parabola equation
 67       float parabolaT = t - 0.5f;
 68       parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 69       position.y += config.arcFactor * parabolaT * projectile.distance;
 70       
 71       float size = 0.06f * (transitionOffset + 0.25f);
 72       DrawCube(position, size, size, size, color);
 73     }
 74   }
 75 }
 76 
 77 void ProjectileUpdate()
 78 {
 79   for (int i = 0; i < projectileCount; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 87     if (transition >= 1.0f)
 88     {
 89       projectile->projectileType = PROJECTILE_TYPE_NONE;
 90       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 91       if (enemy && projectile->hitEffectConfig.pushbackPowerDistance > 0.0f)
 92       {
 93           Vector2 direction = Vector2Normalize(Vector2Subtract((Vector2){projectile->target.x, projectile->target.z}, enemy->simPosition));
 94           enemy->simPosition = Vector2Add(enemy->simPosition, Vector2Scale(direction, projectile->hitEffectConfig.pushbackPowerDistance));
 95       }
 96       
 97       if (projectile->hitEffectConfig.areaDamageRadius > 0.0f)
 98       {
 99         EnemyAddDamageRange((Vector2){projectile->target.x, projectile->target.z}, projectile->hitEffectConfig.areaDamageRadius, projectile->hitEffectConfig.damage);
100         // pancaked sphere explosion
101         float r = projectile->hitEffectConfig.areaDamageRadius;
102         ParticleAdd(PARTICLE_TYPE_EXPLOSION, projectile->target, (Vector3){0}, (Vector3){r, r * 0.2f, r}, 0.33f);
103       }
104       else if (projectile->hitEffectConfig.damage > 0.0f && enemy)
105       {
106         EnemyAddDamage(enemy, projectile->hitEffectConfig.damage);
107       }
108       continue;
109     }
110   }
111 }
112 
113 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig)
114 {
115   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
116   {
117     Projectile *projectile = &projectiles[i];
118     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
119     {
120       projectile->projectileType = projectileType;
121       projectile->shootTime = gameTime.time;
122       float distance = Vector3Distance(position, target);
123       projectile->arrivalTime = gameTime.time + distance / speed;
124       projectile->position = position;
125       projectile->target = target;
126       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
127       projectile->distance = distance;
128       projectile->targetEnemy = EnemyGetId(enemy);
129       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
130       projectile->hitEffectConfig = hitEffectConfig;
131       return projectile;
132     }
133   }
134   return 0;
135 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 
  5 static Particle particles[PARTICLE_MAX_COUNT];
  6 static int particleCount = 0;
  7 
  8 void ParticleInit()
  9 {
 10   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 11   {
 12     particles[i] = (Particle){0};
 13   }
 14   particleCount = 0;
 15 }
 16 
 17 static void DrawExplosionParticle(Particle *particle, float transition)
 18 {
 19   Vector3 scale = particle->scale;
 20   float size = 1.0f * (1.0f - transition);
 21   Color startColor = WHITE;
 22   Color endColor = RED;
 23   Color color = ColorLerp(startColor, endColor, transition);
 24 
 25   rlPushMatrix();
 26   rlTranslatef(particle->position.x, particle->position.y, particle->position.z);
 27   rlScalef(scale.x, scale.y, scale.z);
 28   DrawSphere(Vector3Zero(), size, color);
 29   rlPopMatrix();
 30 }
 31 
 32 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime)
 33 {
 34   if (particleCount >= PARTICLE_MAX_COUNT)
 35   {
 36     return;
 37   }
 38 
 39   int index = -1;
 40   for (int i = 0; i < particleCount; i++)
 41   {
 42     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 43     {
 44       index = i;
 45       break;
 46     }
 47   }
 48 
 49   if (index == -1)
 50   {
 51     index = particleCount++;
 52   }
 53 
 54   Particle *particle = &particles[index];
 55   particle->particleType = particleType;
 56   particle->spawnTime = gameTime.time;
 57   particle->lifetime = lifetime;
 58   particle->position = position;
 59   particle->velocity = velocity;
 60   particle->scale = scale;
 61 }
 62 
 63 void ParticleUpdate()
 64 {
 65   for (int i = 0; i < particleCount; i++)
 66   {
 67     Particle *particle = &particles[i];
 68     if (particle->particleType == PARTICLE_TYPE_NONE)
 69     {
 70       continue;
 71     }
 72 
 73     float age = gameTime.time - particle->spawnTime;
 74 
 75     if (particle->lifetime > age)
 76     {
 77       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 78     }
 79     else {
 80       particle->particleType = PARTICLE_TYPE_NONE;
 81     }
 82   }
 83 }
 84 
 85 void ParticleDraw()
 86 {
 87   for (int i = 0; i < particleCount; i++)
 88   {
 89     Particle particle = particles[i];
 90     if (particle.particleType == PARTICLE_TYPE_NONE)
 91     {
 92       continue;
 93     }
 94 
 95     float age = gameTime.time - particle.spawnTime;
 96     float transition = age / particle.lifetime;
 97     switch (particle.particleType)
 98     {
 99     case PARTICLE_TYPE_EXPLOSION:
100       DrawExplosionParticle(&particle, transition);
101       break;
102     default:
103       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
104       break;
105     }
106   }
107 }  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endifThere is now a new SpriteAnimation struct that describes a single sprite animation source:
  1 typedef struct SpriteAnimation
  2 {
  3   Rectangle srcRect;
  4   Vector2 offset;
  5   uint8_t animationId;
  6   uint8_t frameCount;
  7   uint8_t frameWidth;
  8   float frameDuration;
  9 } SpriteAnimation;This used to be a part of the SpriteUnit - which looks now very simple:
  1 typedef struct SpriteUnit
  2 {
  3   SpriteAnimation animations[SPRITE_UNIT_ANIMATION_COUNT];
  4 } SpriteUnit;It is now greatly simplified and is just a number of animations. Creating animations for a unit is now a description of the different animations that are drawn on top of each other:
  1 SpriteUnit enemySprites[] = {
  2   [ENEMY_TYPE_MINION] = {
  3     .animations[0] = { // the minion body
  4       .srcRect = {0, 17, 16, 15},
  5       .offset = {8.0f, 0.0f},
  6       .frameCount = 6,
  7       .frameDuration = 0.1f,
  8     },
  9     .animations[1] = { // the sword
 10       .srcRect = {1, 33, 15, 14},
 11       .offset = {7.0f, 0.0f},
 12       .frameCount = 6,
 13       .frameWidth = 16,
 14       .frameDuration = 0.1f,
 15     },
 16   },
 17   ...
 18 }It is simple to see that with this approach that we can make up enemies with multiple overlays and animations. But the best part is how the code looks like that draws the unit sprite - let's compare it - first the new version:
  1 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
  2 {
  3   float xScale = flip ? -1.0f : 1.0f;
  4   Camera3D camera = currentLevel->camera;
  5   float size = 0.5f;
  6   // we want the sprite to face the camera, so we need to calculate the up vector
  7   Vector3 forward = Vector3Subtract(camera.target, camera.position);
  8   Vector3 up = {0, 1, 0};
  9   Vector3 right = Vector3CrossProduct(forward, up);
 10   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 11   
 12   for (int i=0;i<SPRITE_UNIT_ANIMATION_COUNT;i++)
 13   {
 14     SpriteAnimation anim = unit.animations[i];
 15     if (anim.animationId != phase && anim.animationId != 0)
 16     {
 17       continue;
 18     }
 19     Rectangle srcRect = anim.srcRect;
 20     if (anim.frameCount > 1)
 21     {
 22       int w = anim.frameWidth > 0 ? anim.frameWidth : srcRect.width;
 23       srcRect.x += (int)(t / anim.frameDuration) % anim.frameCount * w;
 24     }
 25     Vector2 offset = { anim.offset.x / 16.0f * size, anim.offset.y / 16.0f * size * xScale };
 26     Vector2 scale = { srcRect.width / 16.0f * size, srcRect.height / 16.0f * size };
 27     
 28     if (flip)
 29     {
 30       srcRect.x += srcRect.width;
 31       srcRect.width = -srcRect.width;
 32       offset.x = scale.x - offset.x;
 33     }
 34     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 35     // move the sprite slightly towards the camera to avoid z-fighting
 36     position = Vector3Add(position, Vector3Scale(Vector3Normalize(forward), -0.01f));  
 37   }
 38 }Noteworthy is this part:
if (anim.animationId != phase && anim.animationId != 0) { continue; }
... which is ignoring SpriteAnimation elements that have an animationId set and that does not match the current phase. This is how we can have units that draw different animations depending on the phase of the unit - e.g. for the archers. Not the most elegant approach, but it works for now!
And the old version:
  1 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
  2 {
  3   float xScale = flip ? -1.0f : 1.0f;
  4   Camera3D camera = currentLevel->camera;
  5   float size = 0.5f;
  6   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size * xScale };
  7   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
  8   // we want the sprite to face the camera, so we need to calculate the up vector
  9   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 10   Vector3 up = {0, 1, 0};
 11   Vector3 right = Vector3CrossProduct(forward, up);
 12   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 13 
 14   Rectangle srcRect = unit.srcRect;
 15   if (unit.frameCount > 1)
 16   {
 17     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 18   }
 19   if (flip)
 20   {
 21     srcRect.x += srcRect.width;
 22     srcRect.width = -srcRect.width;
 23   }
 24   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 25   // move the sprite slightly towards the camera to avoid z-fighting
 26   position = Vector3Add(position, Vector3Scale(Vector3Normalize(forward), -0.01f));
 27 
 28   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 29   {
 30     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 31     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 32     srcRect = unit.srcWeaponCooldownRect;
 33     if (flip)
 34     {
 35       // position.x = flip * scale.x * 0.5f;
 36       srcRect.x += srcRect.width;
 37       srcRect.width = -srcRect.width;
 38       offset.x = scale.x - offset.x;
 39     }
 40     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 41   }
 42   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 43   {
 44     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 45     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 46     srcRect = unit.srcWeaponIdleRect;
 47     if (flip)
 48     {
 49       // position.x = flip * scale.x * 0.5f;
 50       srcRect.x += srcRect.width;
 51       srcRect.width = -srcRect.width;
 52       offset.x = scale.x - offset.x;
 53     }
 54     if (unit.srcWeaponIdleFrameCount > 1)
 55     {
 56       int w = unit.srcWeaponIdleFrameWidth > 0 ? unit.srcWeaponIdleFrameWidth : srcRect.width;
 57       srcRect.x += (int)(t / unit.srcWeaponIdleFrameDuration) % unit.srcWeaponIdleFrameCount * w;
 58     }
 59     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 60   }
Comparing the changes, we can see that setting up a unit sprite is more structured now and the code to draw it has become significantly shorter and simpler.
Both should be useful when adding more units and features to the game. We might later want to add more effects to the sprite animations, like scaling, rotation, or color changes and with this approach, this should be easier to do. Later.
Now, let's add the new enemies and let them spawn to see how they look like.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 #include <stdlib.h>
  5 #include <math.h>
  6 
  7 //# Variables
  8 GUIState guiState = {0};
  9 GameTime gameTime = {
 10   .fixedDeltaTime = 1.0f / 60.0f,
 11 };
 12 
 13 Model floorTileAModel = {0};
 14 Model floorTileBModel = {0};
 15 Model treeModel[2] = {0};
 16 Model firTreeModel[2] = {0};
 17 Model rockModels[5] = {0};
 18 Model grassPatchModel[1] = {0};
 19 
 20 Model pathArrowModel = {0};
 21 Model greenArrowModel = {0};
 22 
 23 Texture2D palette, spriteSheet;
 24 
 25 Level levels[] = {
 26   [0] = {
 27     .state = LEVEL_STATE_BUILDING,
 28     .initialGold = 20,
 29     .waves[0] = {
 30       .enemyType = ENEMY_TYPE_MINION,
 31       .wave = 0,
 32       .count = 5,
 33       .interval = 2.5f,
 34       .delay = 1.0f,
 35       .spawnPosition = {2, 6},
 36     },
 37     .waves[1] = {
 38       .enemyType = ENEMY_TYPE_RUNNER,
 39       .wave = 0,
 40       .count = 5,
 41       .interval = 2.5f,
 42       .delay = 1.0f,
 43       .spawnPosition = {-2, 6},
 44     },
 45     .waves[2] = {
 46       .enemyType = ENEMY_TYPE_SHIELD,
 47       .wave = 1,
 48       .count = 20,
 49       .interval = 1.5f,
 50       .delay = 1.0f,
 51       .spawnPosition = {0, 6},
 52     },
 53     .waves[3] = {
 54       .enemyType = ENEMY_TYPE_MINION,
 55       .wave = 2,
 56       .count = 30,
 57       .interval = 1.2f,
 58       .delay = 1.0f,
 59       .spawnPosition = {2, 6},
 60     },
 61     .waves[4] = {
 62       .enemyType = ENEMY_TYPE_BOSS,
 63       .wave = 0,
 64       .count = 2,
 65       .interval = 5.0f,
 66       .delay = 2.0f,
 67       .spawnPosition = {-2, 4},
 68     }
 69   },
 70 };
 71 
 72 Level *currentLevel = levels;
 73 
 74 //# Game
 75 
 76 static Model LoadGLBModel(char *filename)
 77 {
 78   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 79   for (int i = 0; i < model.materialCount; i++)
 80   {
 81     model.materials[i].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 82   }
 83   return model;
 84 }
 85 
 86 void LoadAssets()
 87 {
 88   // load a sprite sheet that contains all units
 89   spriteSheet = LoadTexture("data/spritesheet.png");
 90   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 91 
 92   // we'll use a palette texture to colorize the all buildings and environment art
 93   palette = LoadTexture("data/palette.png");
 94   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 95   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 96 
 97   floorTileAModel = LoadGLBModel("floor-tile-a");
 98   floorTileBModel = LoadGLBModel("floor-tile-b");
 99   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
100   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
101   firTreeModel[0] = LoadGLBModel("firtree-1-a");
102   firTreeModel[1] = LoadGLBModel("firtree-1-b");
103   rockModels[0] = LoadGLBModel("rock-1");
104   rockModels[1] = LoadGLBModel("rock-2");
105   rockModels[2] = LoadGLBModel("rock-3");
106   rockModels[3] = LoadGLBModel("rock-4");
107   rockModels[4] = LoadGLBModel("rock-5");
108   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
109 
110   pathArrowModel = LoadGLBModel("direction-arrow-x");
111   greenArrowModel = LoadGLBModel("green-arrow");
112 }
113 
114 void InitLevel(Level *level)
115 {
116   level->seed = (int)(GetTime() * 100.0f);
117 
118   TowerInit();
119   EnemyInit();
120   ProjectileInit();
121   ParticleInit();
122   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
123 
124   level->placementMode = 0;
125   level->state = LEVEL_STATE_BUILDING;
126   level->nextState = LEVEL_STATE_NONE;
127   level->playerGold = level->initialGold;
128   level->currentWave = 0;
129   level->placementX = -1;
130   level->placementY = 0;
131 
132   Camera *camera = &level->camera;
133   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
134   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
135   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
136   camera->fovy = 10.0f;
137   camera->projection = CAMERA_ORTHOGRAPHIC;
138 }
139 
140 void DrawLevelHud(Level *level)
141 {
142   const char *text = TextFormat("Gold: %d", level->playerGold);
143   Font font = GetFontDefault();
144   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
145   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
146 }
147 
148 void DrawLevelReportLostWave(Level *level)
149 {
150   BeginMode3D(level->camera);
151   DrawLevelGround(level);
152   TowerDraw();
153   EnemyDraw();
154   ProjectileDraw();
155   ParticleDraw();
156   guiState.isBlocked = 0;
157   EndMode3D();
158 
159   TowerDrawHealthBars(level->camera);
160 
161   const char *text = "Wave lost";
162   int textWidth = MeasureText(text, 20);
163   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
164 
165   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
166   {
167     level->nextState = LEVEL_STATE_RESET;
168   }
169 }
170 
171 int HasLevelNextWave(Level *level)
172 {
173   for (int i = 0; i < 10; i++)
174   {
175     EnemyWave *wave = &level->waves[i];
176     if (wave->wave == level->currentWave)
177     {
178       return 1;
179     }
180   }
181   return 0;
182 }
183 
184 void DrawLevelReportWonWave(Level *level)
185 {
186   BeginMode3D(level->camera);
187   DrawLevelGround(level);
188   TowerDraw();
189   EnemyDraw();
190   ProjectileDraw();
191   ParticleDraw();
192   guiState.isBlocked = 0;
193   EndMode3D();
194 
195   TowerDrawHealthBars(level->camera);
196 
197   const char *text = "Wave won";
198   int textWidth = MeasureText(text, 20);
199   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
200 
201 
202   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
203   {
204     level->nextState = LEVEL_STATE_RESET;
205   }
206 
207   if (HasLevelNextWave(level))
208   {
209     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
210     {
211       level->nextState = LEVEL_STATE_BUILDING;
212     }
213   }
214   else {
215     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
216     {
217       level->nextState = LEVEL_STATE_WON_LEVEL;
218     }
219   }
220 }
221 
222 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
223 {
224   static ButtonState buttonStates[8] = {0};
225   int cost = GetTowerCosts(towerType);
226   const char *text = TextFormat("%s: %d", name, cost);
227   buttonStates[towerType].isSelected = level->placementMode == towerType;
228   buttonStates[towerType].isDisabled = level->playerGold < cost;
229   if (Button(text, x, y, width, height, &buttonStates[towerType]))
230   {
231     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
232     level->nextState = LEVEL_STATE_BUILDING_PLACEMENT;
233   }
234 }
235 
236 float GetRandomFloat(float min, float max)
237 {
238   int random = GetRandomValue(0, 0xfffffff);
239   return ((float)random / (float)0xfffffff) * (max - min) + min;
240 }
241 
242 void DrawLevelGround(Level *level)
243 {
244   // draw checkerboard ground pattern
245   for (int x = -5; x <= 5; x += 1)
246   {
247     for (int y = -5; y <= 5; y += 1)
248     {
249       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
250       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
251     }
252   }
253 
254   int oldSeed = GetRandomValue(0, 0xfffffff);
255   SetRandomSeed(level->seed);
256   // increase probability for trees via duplicated entries
257   Model borderModels[64];
258   int maxRockCount = GetRandomValue(2, 6);
259   int maxTreeCount = GetRandomValue(10, 20);
260   int maxFirTreeCount = GetRandomValue(5, 10);
261   int maxLeafTreeCount = maxTreeCount - maxFirTreeCount;
262   int grassPatchCount = GetRandomValue(5, 30);
263 
264   int modelCount = 0;
265   for (int i = 0; i < maxRockCount && modelCount < 63; i++)
266   {
267     borderModels[modelCount++] = rockModels[GetRandomValue(0, 5)];
268   }
269   for (int i = 0; i < maxLeafTreeCount && modelCount < 63; i++)
270   {
271     borderModels[modelCount++] = treeModel[GetRandomValue(0, 1)];
272   }
273   for (int i = 0; i < maxFirTreeCount && modelCount < 63; i++)
274   {
275     borderModels[modelCount++] = firTreeModel[GetRandomValue(0, 1)];
276   }
277   for (int i = 0; i < grassPatchCount && modelCount < 63; i++)
278   {
279     borderModels[modelCount++] = grassPatchModel[0];
280   }
281 
282   // draw some objects around the border of the map
283   Vector3 up = {0, 1, 0};
284   // a pseudo random number generator to get the same result every time
285   const float wiggle = 0.75f;
286   const int layerCount = 3;
287   for (int layer = 0; layer < layerCount; layer++)
288   {
289     int layerPos = 6 + layer;
290     for (int x = -6 + layer; x <= 6 + layer; x += 1)
291     {
292       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
293         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, -layerPos + GetRandomFloat(0.0f, wiggle)}, 
294         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
295       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
296         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, layerPos + GetRandomFloat(0.0f, wiggle)}, 
297         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
298     }
299 
300     for (int z = -5 + layer; z <= 5 + layer; z += 1)
301     {
302       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
303         (Vector3){-layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
304         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
305       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
306         (Vector3){layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
307         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
308     }
309   }
310 
311   SetRandomSeed(oldSeed);
312 }
313 
314 void DrawEnemyPath(Level *level, Color arrowColor)
315 {
316   const int castleX = 0, castleY = 0;
317   const int maxWaypointCount = 200;
318   const float timeStep = 1.0f;
319   Vector3 arrowScale = {0.75f, 0.75f, 0.75f};
320 
321   // we start with a time offset to simulate the path, 
322   // this way the arrows are animated in a forward moving direction
323   // The time is wrapped around the time step to get a smooth animation
324   float timeOffset = fmodf(GetTime(), timeStep);
325 
326   for (int i = 0; i < ENEMY_MAX_WAVE_COUNT; i++)
327   {
328     EnemyWave *wave = &level->waves[i];
329     if (wave->wave != level->currentWave)
330     {
331       continue;
332     }
333 
334     // use this dummy enemy to simulate the path
335     Enemy dummy = {
336       .enemyType = ENEMY_TYPE_MINION,
337       .simPosition = (Vector2){wave->spawnPosition.x, wave->spawnPosition.y},
338       .nextX = wave->spawnPosition.x,
339       .nextY = wave->spawnPosition.y,
340       .currentX = wave->spawnPosition.x,
341       .currentY = wave->spawnPosition.y,
342     };
343 
344     float deltaTime = timeOffset;
345     for (int j = 0; j < maxWaypointCount; j++)
346     {
347       int waypointPassedCount = 0;
348       Vector2 pos = EnemyGetPosition(&dummy, deltaTime, &dummy.simVelocity, &waypointPassedCount);
349       // after the initial variable starting offset, we use a fixed time step
350       deltaTime = timeStep;
351       dummy.simPosition = pos;
352 
353       // Update the dummy's position just like we do in the regular enemy update loop
354       for (int k = 0; k < waypointPassedCount; k++)
355       {
356         dummy.currentX = dummy.nextX;
357         dummy.currentY = dummy.nextY;
358         if (EnemyGetNextPosition(dummy.currentX, dummy.currentY, &dummy.nextX, &dummy.nextY) &&
359           Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
360         {
361           break;
362         }
363       }
364       if (Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
365       {
366         break;
367       }
368       
369       // get the angle we need to rotate the arrow model. The velocity is just fine for this.
370       float angle = atan2f(dummy.simVelocity.x, dummy.simVelocity.y) * RAD2DEG - 90.0f;
371       DrawModelEx(pathArrowModel, (Vector3){pos.x, 0.15f, pos.y}, (Vector3){0, 1, 0}, angle, arrowScale, arrowColor);
372     }
373   }
374 }
375 
376 void DrawEnemyPaths(Level *level)
377 {
378   // disable depth testing for the path arrows
379   // flush the 3D batch to draw the arrows on top of everything
380   rlDrawRenderBatchActive();
381   rlDisableDepthTest();
382   DrawEnemyPath(level, (Color){64, 64, 64, 160});
383 
384   rlDrawRenderBatchActive();
385   rlEnableDepthTest();
386   DrawEnemyPath(level, WHITE);
387 }
388 
389 static void SimulateTowerPlacementBehavior(Level *level, float towerFloatHeight, float mapX, float mapY)
390 {
391   float dt = gameTime.fixedDeltaTime;
392   // smooth transition for the placement position using exponential decay
393   const float lambda = 15.0f;
394   float factor = 1.0f - expf(-lambda * dt);
395 
396   float damping = 0.5f;
397   float springStiffness = 300.0f;
398   float springDecay = 95.0f;
399   float minHeight = 0.35f;
400 
401   if (level->placementPhase == PLACEMENT_PHASE_STARTING)
402   {
403     damping = 1.0f;
404     springDecay = 90.0f;
405     springStiffness = 100.0f;
406     minHeight = 0.70f;
407   }
408 
409   for (int i = 0; i < gameTime.fixedStepCount; i++)
410   {
411     level->placementTransitionPosition = 
412       Vector2Lerp(
413         level->placementTransitionPosition, 
414         (Vector2){mapX, mapY}, factor);
415 
416     // draw the spring position for debugging the spring simulation
417     // first step: stiff spring, no simulation
418     Vector3 worldPlacementPosition = (Vector3){
419       level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
420     Vector3 springTargetPosition = (Vector3){
421       worldPlacementPosition.x, 1.0f + towerFloatHeight, worldPlacementPosition.z};
422     // consider the current velocity to predict the future position in order to dampen
423     // the spring simulation. Longer prediction times will result in more damping
424     Vector3 predictedSpringPosition = Vector3Add(level->placementTowerSpring.position, 
425       Vector3Scale(level->placementTowerSpring.velocity, dt * damping));
426     Vector3 springPointDelta = Vector3Subtract(springTargetPosition, predictedSpringPosition);
427     Vector3 velocityChange = Vector3Scale(springPointDelta, dt * springStiffness);
428     // decay velocity of the upright forcing spring
429     // This force acts like a 2nd spring that pulls the tip upright into the air above the
430     // base position
431     level->placementTowerSpring.velocity = Vector3Scale(level->placementTowerSpring.velocity, 1.0f - expf(-springDecay * dt));
432     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, velocityChange);
433 
434     // calculate length of the spring to calculate the force to apply to the placementTowerSpring position
435     // we use a simple spring model with a rest length of 1.0f
436     Vector3 springDelta = Vector3Subtract(worldPlacementPosition, level->placementTowerSpring.position);
437     float springLength = Vector3Length(springDelta);
438     float springForce = (springLength - 1.0f) * springStiffness;
439     Vector3 springForceVector = Vector3Normalize(springDelta);
440     springForceVector = Vector3Scale(springForceVector, springForce);
441     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, 
442       Vector3Scale(springForceVector, dt));
443 
444     level->placementTowerSpring.position = Vector3Add(level->placementTowerSpring.position, 
445       Vector3Scale(level->placementTowerSpring.velocity, dt));
446     if (level->placementTowerSpring.position.y < minHeight + towerFloatHeight)
447     {
448       level->placementTowerSpring.velocity.y *= -1.0f;
449       level->placementTowerSpring.position.y = fmaxf(level->placementTowerSpring.position.y, minHeight + towerFloatHeight);
450     }
451   }
452 }
453 
454 void DrawLevelBuildingPlacementState(Level *level)
455 {
456   const float placementDuration = 0.5f;
457 
458   level->placementTimer += gameTime.deltaTime;
459   if (level->placementTimer > 1.0f && level->placementPhase == PLACEMENT_PHASE_STARTING)
460   {
461     level->placementPhase = PLACEMENT_PHASE_MOVING;
462     level->placementTimer = 0.0f;
463   }
464 
465   BeginMode3D(level->camera);
466   DrawLevelGround(level);
467 
468   int blockedCellCount = 0;
469   Vector2 blockedCells[1];
470   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
471   float planeDistance = ray.position.y / -ray.direction.y;
472   float planeX = ray.direction.x * planeDistance + ray.position.x;
473   float planeY = ray.direction.z * planeDistance + ray.position.z;
474   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
475   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
476   if (level->placementPhase == PLACEMENT_PHASE_MOVING && 
477     level->placementMode && !guiState.isBlocked && 
478     mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5 && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
479   {
480     level->placementX = mapX;
481     level->placementY = mapY;
482   }
483   else
484   {
485     mapX = level->placementX;
486     mapY = level->placementY;
487   }
488   blockedCells[blockedCellCount++] = (Vector2){mapX, mapY};
489   PathFindingMapUpdate(blockedCellCount, blockedCells);
490 
491   TowerDraw();
492   EnemyDraw();
493   ProjectileDraw();
494   ParticleDraw();
495   DrawEnemyPaths(level);
496 
497   // let the tower float up and down. Consider this height in the spring simulation as well
498   float towerFloatHeight = sinf(gameTime.time * 4.0f) * 0.2f + 0.3f;
499 
500   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
501   {
502     // The bouncing spring needs a bit of outro time to look nice and complete. 
503     // So we scale the time so that the first 2/3rd of the placing phase handles the motion
504     // and the last 1/3rd is the outro physics (bouncing)
505     float t = fminf(1.0f, level->placementTimer / placementDuration * 1.5f);
506     // let towerFloatHeight describe parabola curve, starting at towerFloatHeight and ending at 0
507     float linearBlendHeight = (1.0f - t) * towerFloatHeight;
508     float parabola = (1.0f - ((t - 0.5f) * (t - 0.5f) * 4.0f)) * 2.0f;
509     towerFloatHeight = linearBlendHeight + parabola;
510   }
511 
512   SimulateTowerPlacementBehavior(level, towerFloatHeight, mapX, mapY);
513   
514   rlPushMatrix();
515   rlTranslatef(level->placementTransitionPosition.x, 0, level->placementTransitionPosition.y);
516 
517   rlPushMatrix();
518   rlTranslatef(0.0f, towerFloatHeight, 0.0f);
519   // calculate x and z rotation to align the model with the spring
520   Vector3 position = {level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
521   Vector3 towerUp = Vector3Subtract(level->placementTowerSpring.position, position);
522   Vector3 rotationAxis = Vector3CrossProduct(towerUp, (Vector3){0, 1, 0});
523   float angle = acosf(Vector3DotProduct(towerUp, (Vector3){0, 1, 0}) / Vector3Length(towerUp)) * RAD2DEG;
524   float springLength = Vector3Length(towerUp);
525   float towerStretch = fminf(fmaxf(springLength, 0.5f), 4.5f);
526   float towerSquash = 1.0f / towerStretch;
527   rlRotatef(-angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
528   rlScalef(towerSquash, towerStretch, towerSquash);
529   Tower dummy = {
530     .towerType = level->placementMode,
531   };
532   TowerDrawSingle(dummy);
533   rlPopMatrix();
534 
535   // draw a shadow for the tower
536   float umbrasize = 0.8 + sqrtf(towerFloatHeight);
537   DrawCube((Vector3){0.0f, 0.05f, 0.0f}, umbrasize, 0.0f, umbrasize, (Color){0, 0, 0, 32});
538   DrawCube((Vector3){0.0f, 0.075f, 0.0f}, 0.85f, 0.0f, 0.85f, (Color){0, 0, 0, 64});
539 
540 
541   float bounce = sinf(gameTime.time * 8.0f) * 0.5f + 0.5f;
542   float offset = fmaxf(0.0f, bounce - 0.4f) * 0.35f + 0.7f;
543   float squeeze = -fminf(0.0f, bounce - 0.4f) * 0.7f + 0.7f;
544   float stretch = fmaxf(0.0f, bounce - 0.3f) * 0.5f + 0.8f;
545   
546   DrawModelEx(greenArrowModel, (Vector3){ offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 90, (Vector3){squeeze, 1.0f, stretch}, WHITE);
547   DrawModelEx(greenArrowModel, (Vector3){-offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 270, (Vector3){squeeze, 1.0f, stretch}, WHITE);
548   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f,  offset}, (Vector3){0, 1, 0}, 0, (Vector3){squeeze, 1.0f, stretch}, WHITE);
549   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f, -offset}, (Vector3){0, 1, 0}, 180, (Vector3){squeeze, 1.0f, stretch}, WHITE);
550   rlPopMatrix();
551 
552   guiState.isBlocked = 0;
553 
554   EndMode3D();
555 
556   TowerDrawHealthBars(level->camera);
557 
558   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
559   {
560     if (level->placementTimer > placementDuration)
561     {
562         TowerTryAdd(level->placementMode, mapX, mapY);
563         level->playerGold -= GetTowerCosts(level->placementMode);
564         level->nextState = LEVEL_STATE_BUILDING;
565         level->placementMode = TOWER_TYPE_NONE;
566     }
567   }
568   else
569   {   
570     if (Button("Cancel", 20, GetScreenHeight() - 40, 160, 30, 0))
571     {
572       level->nextState = LEVEL_STATE_BUILDING;
573       level->placementMode = TOWER_TYPE_NONE;
574       TraceLog(LOG_INFO, "Cancel building");
575     }
576     
577     if (TowerGetAt(mapX, mapY) == 0 &&  Button("Build", GetScreenWidth() - 180, GetScreenHeight() - 40, 160, 30, 0))
578     {
579       level->placementPhase = PLACEMENT_PHASE_PLACING;
580       level->placementTimer = 0.0f;
581     }
582   }
583 }
584 
585 void DrawLevelBuildingState(Level *level)
586 {
587   BeginMode3D(level->camera);
588   DrawLevelGround(level);
589 
590   PathFindingMapUpdate(0, 0);
591   TowerDraw();
592   EnemyDraw();
593   ProjectileDraw();
594   ParticleDraw();
595   DrawEnemyPaths(level);
596 
597   guiState.isBlocked = 0;
598 
599   EndMode3D();
600 
601   TowerDrawHealthBars(level->camera);
602 
603   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
604   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_ARCHER, "Archer");
605   DrawBuildingBuildButton(level, 10, 90, 110, 30, TOWER_TYPE_BALLISTA, "Ballista");
606   DrawBuildingBuildButton(level, 10, 130, 110, 30, TOWER_TYPE_CATAPULT, "Catapult");
607 
608   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
609   {
610     level->nextState = LEVEL_STATE_RESET;
611   }
612   
613   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
614   {
615     level->nextState = LEVEL_STATE_BATTLE;
616   }
617 
618   const char *text = "Building phase";
619   int textWidth = MeasureText(text, 20);
620   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
621 }
622 
623 void InitBattleStateConditions(Level *level)
624 {
625   level->state = LEVEL_STATE_BATTLE;
626   level->nextState = LEVEL_STATE_NONE;
627   level->waveEndTimer = 0.0f;
628   for (int i = 0; i < 10; i++)
629   {
630     EnemyWave *wave = &level->waves[i];
631     wave->spawned = 0;
632     wave->timeToSpawnNext = wave->delay;
633   }
634 }
635 
636 void DrawLevelBattleState(Level *level)
637 {
638   BeginMode3D(level->camera);
639   DrawLevelGround(level);
640   TowerDraw();
641   EnemyDraw();
642   ProjectileDraw();
643   ParticleDraw();
644   guiState.isBlocked = 0;
645   EndMode3D();
646 
647   EnemyDrawHealthbars(level->camera);
648   TowerDrawHealthBars(level->camera);
649 
650   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
651   {
652     level->nextState = LEVEL_STATE_RESET;
653   }
654 
655   int maxCount = 0;
656   int remainingCount = 0;
657   for (int i = 0; i < 10; i++)
658   {
659     EnemyWave *wave = &level->waves[i];
660     if (wave->wave != level->currentWave)
661     {
662       continue;
663     }
664     maxCount += wave->count;
665     remainingCount += wave->count - wave->spawned;
666   }
667   int aliveCount = EnemyCount();
668   remainingCount += aliveCount;
669 
670   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
671   int textWidth = MeasureText(text, 20);
672   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
673 }
674 
675 void DrawLevel(Level *level)
676 {
677   switch (level->state)
678   {
679     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
680     case LEVEL_STATE_BUILDING_PLACEMENT: DrawLevelBuildingPlacementState(level); break;
681     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
682     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
683     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
684     default: break;
685   }
686 
687   DrawLevelHud(level);
688 }
689 
690 void UpdateLevel(Level *level)
691 {
692   if (level->state == LEVEL_STATE_BATTLE)
693   {
694     int activeWaves = 0;
695     for (int i = 0; i < 10; i++)
696     {
697       EnemyWave *wave = &level->waves[i];
698       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
699       {
700         continue;
701       }
702       activeWaves++;
703       wave->timeToSpawnNext -= gameTime.deltaTime;
704       if (wave->timeToSpawnNext <= 0.0f)
705       {
706         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
707         if (enemy)
708         {
709           wave->timeToSpawnNext = wave->interval;
710           wave->spawned++;
711         }
712       }
713     }
714     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
715       level->waveEndTimer += gameTime.deltaTime;
716       if (level->waveEndTimer >= 2.0f)
717       {
718         level->nextState = LEVEL_STATE_LOST_WAVE;
719       }
720     }
721     else if (activeWaves == 0 && EnemyCount() == 0)
722     {
723       level->waveEndTimer += gameTime.deltaTime;
724       if (level->waveEndTimer >= 2.0f)
725       {
726         level->nextState = LEVEL_STATE_WON_WAVE;
727       }
728     }
729   }
730 
731   PathFindingMapUpdate(0, 0);
732   EnemyUpdate();
733   TowerUpdate();
734   ProjectileUpdate();
735   ParticleUpdate();
736 
737   if (level->nextState == LEVEL_STATE_RESET)
738   {
739     InitLevel(level);
740   }
741   
742   if (level->nextState == LEVEL_STATE_BATTLE)
743   {
744     InitBattleStateConditions(level);
745   }
746   
747   if (level->nextState == LEVEL_STATE_WON_WAVE)
748   {
749     level->currentWave++;
750     level->state = LEVEL_STATE_WON_WAVE;
751   }
752   
753   if (level->nextState == LEVEL_STATE_LOST_WAVE)
754   {
755     level->state = LEVEL_STATE_LOST_WAVE;
756   }
757 
758   if (level->nextState == LEVEL_STATE_BUILDING)
759   {
760     level->state = LEVEL_STATE_BUILDING;
761   }
762 
763   if (level->nextState == LEVEL_STATE_BUILDING_PLACEMENT)
764   {
765     level->state = LEVEL_STATE_BUILDING_PLACEMENT;
766     level->placementTransitionPosition = (Vector2){
767       level->placementX, level->placementY};
768     // initialize the spring to the current position
769     level->placementTowerSpring = (PhysicsPoint){
770       .position = (Vector3){level->placementX, 8.0f, level->placementY},
771       .velocity = (Vector3){0.0f, 0.0f, 0.0f},
772     };
773     level->placementPhase = PLACEMENT_PHASE_STARTING;
774     level->placementTimer = 0.0f;
775   }
776 
777   if (level->nextState == LEVEL_STATE_WON_LEVEL)
778   {
779     // make something of this later
780     InitLevel(level);
781   }
782 
783   level->nextState = LEVEL_STATE_NONE;
784 }
785 
786 float nextSpawnTime = 0.0f;
787 
788 void ResetGame()
789 {
790   InitLevel(currentLevel);
791 }
792 
793 void InitGame()
794 {
795   TowerInit();
796   EnemyInit();
797   ProjectileInit();
798   ParticleInit();
799   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
800 
801   currentLevel = levels;
802   InitLevel(currentLevel);
803 }
804 
805 //# Immediate GUI functions
806 
807 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
808 {
809   const float healthBarHeight = 6.0f;
810   const float healthBarOffset = 15.0f;
811   const float inset = 2.0f;
812   const float innerWidth = healthBarWidth - inset * 2;
813   const float innerHeight = healthBarHeight - inset * 2;
814 
815   Vector2 screenPos = GetWorldToScreen(position, camera);
816   float centerX = screenPos.x - healthBarWidth * 0.5f;
817   float topY = screenPos.y - healthBarOffset;
818   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
819   float healthWidth = innerWidth * healthRatio;
820   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
821 }
822 
823 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
824 {
825   Rectangle bounds = {x, y, width, height};
826   int isPressed = 0;
827   int isSelected = state && state->isSelected;
828   int isDisabled = state && state->isDisabled;
829   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
830   {
831     Color color = isSelected ? DARKGRAY : GRAY;
832     DrawRectangle(x, y, width, height, color);
833     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
834     {
835       isPressed = 1;
836     }
837     guiState.isBlocked = 1;
838   }
839   else
840   {
841     Color color = isSelected ? WHITE : LIGHTGRAY;
842     DrawRectangle(x, y, width, height, color);
843   }
844   Font font = GetFontDefault();
845   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
846   Color textColor = isDisabled ? GRAY : BLACK;
847   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
848   return isPressed;
849 }
850 
851 //# Main game loop
852 
853 void GameUpdate()
854 {
855   UpdateLevel(currentLevel);
856 }
857 
858 int main(void)
859 {
860   int screenWidth, screenHeight;
861   GetPreferredSize(&screenWidth, &screenHeight);
862   InitWindow(screenWidth, screenHeight, "Tower defense");
863   float gamespeed = 1.0f;
864   SetTargetFPS(30);
865 
866   LoadAssets();
867   InitGame();
868 
869   float pause = 1.0f;
870 
871   while (!WindowShouldClose())
872   {
873     if (IsPaused()) {
874       // canvas is not visible in browser - do nothing
875       continue;
876     }
877 
878     if (IsKeyPressed(KEY_T))
879     {
880       gamespeed += 0.1f;
881       if (gamespeed > 1.05f) gamespeed = 0.1f;
882     }
883 
884     if (IsKeyPressed(KEY_P))
885     {
886       pause = pause > 0.5f ? 0.0f : 1.0f;
887     }
888 
889     float dt = GetFrameTime() * gamespeed * pause;
890     // cap maximum delta time to 0.1 seconds to prevent large time steps
891     if (dt > 0.1f) dt = 0.1f;
892     gameTime.time += dt;
893     gameTime.deltaTime = dt;
894     gameTime.frameCount += 1;
895 
896     float fixedTimeDiff = gameTime.time - gameTime.fixedTimeStart;
897     gameTime.fixedStepCount = (uint8_t)(fixedTimeDiff / gameTime.fixedDeltaTime);
898 
899     BeginDrawing();
900     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
901 
902     GameUpdate();
903     DrawLevel(currentLevel);
904 
905     DrawText(TextFormat("Speed: %.1f", gamespeed), GetScreenWidth() - 180, 60, 20, WHITE);
906     EndDrawing();
907 
908     gameTime.fixedTimeStart += gameTime.fixedStepCount * gameTime.fixedDeltaTime;
909   }
910 
911   CloseWindow();
912 
913   return 0;
914 }  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 //# Declarations
 10 
 11 typedef struct PhysicsPoint
 12 {
 13   Vector3 position;
 14   Vector3 velocity;
 15 } PhysicsPoint;
 16 
 17 #define ENEMY_MAX_PATH_COUNT 8
 18 #define ENEMY_MAX_COUNT 400
 19 #define ENEMY_TYPE_NONE 0
 20 
 21 #define ENEMY_TYPE_MINION 1
 22 #define ENEMY_TYPE_RUNNER 2
 23 #define ENEMY_TYPE_SHIELD 3
 24 #define ENEMY_TYPE_BOSS 4
 25 
 26 #define PARTICLE_MAX_COUNT 400
 27 #define PARTICLE_TYPE_NONE 0
 28 #define PARTICLE_TYPE_EXPLOSION 1
 29 
 30 typedef struct Particle
 31 {
 32   uint8_t particleType;
 33   float spawnTime;
 34   float lifetime;
 35   Vector3 position;
 36   Vector3 velocity;
 37   Vector3 scale;
 38 } Particle;
 39 
 40 #define TOWER_MAX_COUNT 400
 41 enum TowerType
 42 {
 43   TOWER_TYPE_NONE,
 44   TOWER_TYPE_BASE,
 45   TOWER_TYPE_ARCHER,
 46   TOWER_TYPE_BALLISTA,
 47   TOWER_TYPE_CATAPULT,
 48   TOWER_TYPE_WALL,
 49   TOWER_TYPE_COUNT
 50 };
 51 
 52 typedef struct HitEffectConfig
 53 {
 54   float damage;
 55   float areaDamageRadius;
 56   float pushbackPowerDistance;
 57 } HitEffectConfig;
 58 
 59 typedef struct TowerTypeConfig
 60 {
 61   float cooldown;
 62   float range;
 63   float projectileSpeed;
 64   
 65   uint8_t cost;
 66   uint8_t projectileType;
 67   uint16_t maxHealth;
 68 
 69   HitEffectConfig hitEffect;
 70 } TowerTypeConfig;
 71 
 72 typedef struct Tower
 73 {
 74   int16_t x, y;
 75   uint8_t towerType;
 76   Vector2 lastTargetPosition;
 77   float cooldown;
 78   float damage;
 79 } Tower;
 80 
 81 typedef struct GameTime
 82 {
 83   float time;
 84   float deltaTime;
 85   uint32_t frameCount;
 86 
 87   float fixedDeltaTime;
 88   // leaving the fixed time stepping to the update functions,
 89   // we need to know the fixed time at the start of the frame
 90   float fixedTimeStart;
 91   // and the number of fixed steps that we have to make this frame
 92   // The fixedTime is fixedTimeStart + n * fixedStepCount
 93   uint8_t fixedStepCount;
 94 } GameTime;
 95 
 96 typedef struct ButtonState {
 97   char isSelected;
 98   char isDisabled;
 99 } ButtonState;
100 
101 typedef struct GUIState {
102   int isBlocked;
103 } GUIState;
104 
105 typedef enum LevelState
106 {
107   LEVEL_STATE_NONE,
108   LEVEL_STATE_BUILDING,
109   LEVEL_STATE_BUILDING_PLACEMENT,
110   LEVEL_STATE_BATTLE,
111   LEVEL_STATE_WON_WAVE,
112   LEVEL_STATE_LOST_WAVE,
113   LEVEL_STATE_WON_LEVEL,
114   LEVEL_STATE_RESET,
115 } LevelState;
116 
117 typedef struct EnemyWave {
118   uint8_t enemyType;
119   uint8_t wave;
120   uint16_t count;
121   float interval;
122   float delay;
123   Vector2 spawnPosition;
124 
125   uint16_t spawned;
126   float timeToSpawnNext;
127 } EnemyWave;
128 
129 #define ENEMY_MAX_WAVE_COUNT 10
130 
131 typedef enum PlacementPhase
132 {
133   PLACEMENT_PHASE_STARTING,
134   PLACEMENT_PHASE_MOVING,
135   PLACEMENT_PHASE_PLACING,
136 } PlacementPhase;
137 
138 typedef struct Level
139 {
140   int seed;
141   LevelState state;
142   LevelState nextState;
143   Camera3D camera;
144   int placementMode;
145   PlacementPhase placementPhase;
146   float placementTimer;
147   int16_t placementX;
148   int16_t placementY;
149   Vector2 placementTransitionPosition;
150   PhysicsPoint placementTowerSpring;
151 
152   int initialGold;
153   int playerGold;
154 
155   EnemyWave waves[ENEMY_MAX_WAVE_COUNT];
156   int currentWave;
157   float waveEndTimer;
158 } Level;
159 
160 typedef struct DeltaSrc
161 {
162   char x, y;
163 } DeltaSrc;
164 
165 typedef struct PathfindingMap
166 {
167   int width, height;
168   float scale;
169   float *distances;
170   long *towerIndex; 
171   DeltaSrc *deltaSrc;
172   float maxDistance;
173   Matrix toMapSpace;
174   Matrix toWorldSpace;
175 } PathfindingMap;
176 
177 // when we execute the pathfinding algorithm, we need to store the active nodes
178 // in a queue. Each node has a position, a distance from the start, and the
179 // position of the node that we came from.
180 typedef struct PathfindingNode
181 {
182   int16_t x, y, fromX, fromY;
183   float distance;
184 } PathfindingNode;
185 
186 typedef struct EnemyId
187 {
188   uint16_t index;
189   uint16_t generation;
190 } EnemyId;
191 
192 typedef struct EnemyClassConfig
193 {
194   float speed;
195   float health;
196   float radius;
197   float maxAcceleration;
198   float requiredContactTime;
199   float explosionDamage;
200   float explosionRange;
201   float explosionPushbackPower;
202   int goldValue;
203 } EnemyClassConfig;
204 
205 typedef struct Enemy
206 {
207   int16_t currentX, currentY;
208   int16_t nextX, nextY;
209   Vector2 simPosition;
210   Vector2 simVelocity;
211   uint16_t generation;
212   float walkedDistance;
213   float startMovingTime;
214   float damage, futureDamage;
215   float contactTime;
216   uint8_t enemyType;
217   uint8_t movePathCount;
218   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
219 } Enemy;
220 
221 // a unit that uses sprites to be drawn
222 #define SPRITE_UNIT_ANIMATION_COUNT 6
223 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 1
224 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 2
225 
226 typedef struct SpriteAnimation
227 {
228   Rectangle srcRect;
229   Vector2 offset;
230   uint8_t animationId;
231   uint8_t frameCount;
232   uint8_t frameWidth;
233   float frameDuration;
234 } SpriteAnimation;
235 
236 typedef struct SpriteUnit
237 {
238   float scale;
239   SpriteAnimation animations[SPRITE_UNIT_ANIMATION_COUNT];
240 } SpriteUnit;
241 
242 #define PROJECTILE_MAX_COUNT 1200
243 #define PROJECTILE_TYPE_NONE 0
244 #define PROJECTILE_TYPE_ARROW 1
245 #define PROJECTILE_TYPE_CATAPULT 2
246 #define PROJECTILE_TYPE_BALLISTA 3
247 
248 typedef struct Projectile
249 {
250   uint8_t projectileType;
251   float shootTime;
252   float arrivalTime;
253   float distance;
254   Vector3 position;
255   Vector3 target;
256   Vector3 directionNormal;
257   EnemyId targetEnemy;
258   HitEffectConfig hitEffectConfig;
259 } Projectile;
260 
261 //# Function declarations
262 float TowerGetMaxHealth(Tower *tower);
263 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
264 int EnemyAddDamageRange(Vector2 position, float range, float damage);
265 int EnemyAddDamage(Enemy *enemy, float damage);
266 
267 //# Enemy functions
268 void EnemyInit();
269 void EnemyDraw();
270 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
271 void EnemyUpdate();
272 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
273 float EnemyGetMaxHealth(Enemy *enemy);
274 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
275 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
276 EnemyId EnemyGetId(Enemy *enemy);
277 Enemy *EnemyTryResolve(EnemyId enemyId);
278 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
279 int EnemyAddDamage(Enemy *enemy, float damage);
280 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
281 int EnemyCount();
282 void EnemyDrawHealthbars(Camera3D camera);
283 
284 //# Tower functions
285 void TowerInit();
286 Tower *TowerGetAt(int16_t x, int16_t y);
287 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
288 Tower *GetTowerByType(uint8_t towerType);
289 int GetTowerCosts(uint8_t towerType);
290 float TowerGetMaxHealth(Tower *tower);
291 void TowerDraw();
292 void TowerDrawSingle(Tower tower);
293 void TowerUpdate();
294 void TowerDrawHealthBars(Camera3D camera);
295 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
296 
297 //# Particles
298 void ParticleInit();
299 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime);
300 void ParticleUpdate();
301 void ParticleDraw();
302 
303 //# Projectiles
304 void ProjectileInit();
305 void ProjectileDraw();
306 void ProjectileUpdate();
307 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig);
308 
309 //# Pathfinding map
310 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
311 float PathFindingGetDistance(int mapX, int mapY);
312 Vector2 PathFindingGetGradient(Vector3 world);
313 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
314 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells);
315 void PathFindingMapDraw();
316 
317 //# UI
318 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
319 
320 //# Level
321 void DrawLevelGround(Level *level);
322 void DrawEnemyPath(Level *level, Color arrowColor);
323 
324 //# variables
325 extern Level *currentLevel;
326 extern Enemy enemies[ENEMY_MAX_COUNT];
327 extern int enemyCount;
328 extern EnemyClassConfig enemyClassConfigs[];
329 
330 extern GUIState guiState;
331 extern GameTime gameTime;
332 extern Tower towers[TOWER_MAX_COUNT];
333 extern int towerCount;
334 
335 extern Texture2D palette, spriteSheet;
336 
337 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static TowerTypeConfig towerTypeConfigs[TOWER_TYPE_COUNT] = {
  5     [TOWER_TYPE_BASE] = {
  6         .maxHealth = 10,
  7     },
  8     [TOWER_TYPE_ARCHER] = {
  9         .cooldown = 0.5f,
 10         .range = 3.0f,
 11         .cost = 6,
 12         .maxHealth = 10,
 13         .projectileSpeed = 4.0f,
 14         .projectileType = PROJECTILE_TYPE_ARROW,
 15         .hitEffect = {
 16           .damage = 3.0f,
 17         }
 18     },
 19     [TOWER_TYPE_BALLISTA] = {
 20         .cooldown = 1.5f,
 21         .range = 6.0f,
 22         .cost = 9,
 23         .maxHealth = 10,
 24         .projectileSpeed = 10.0f,
 25         .projectileType = PROJECTILE_TYPE_BALLISTA,
 26         .hitEffect = {
 27           .damage = 6.0f,
 28           .pushbackPowerDistance = 0.25f,
 29         }
 30     },
 31     [TOWER_TYPE_CATAPULT] = {
 32         .cooldown = 1.7f,
 33         .range = 5.0f,
 34         .cost = 10,
 35         .maxHealth = 10,
 36         .projectileSpeed = 3.0f,
 37         .projectileType = PROJECTILE_TYPE_CATAPULT,
 38         .hitEffect = {
 39           .damage = 2.0f,
 40           .areaDamageRadius = 1.75f,
 41         }
 42     },
 43     [TOWER_TYPE_WALL] = {
 44         .cost = 2,
 45         .maxHealth = 10,
 46     },
 47 };
 48 
 49 Tower towers[TOWER_MAX_COUNT];
 50 int towerCount = 0;
 51 
 52 Model towerModels[TOWER_TYPE_COUNT];
 53 
 54 // definition of our archer unit
 55 SpriteUnit archerUnit = {
 56   .animations[0] = {
 57     .srcRect = {0, 0, 16, 16},
 58     .offset = {7, 1},
 59     .frameCount = 1,
 60     .frameDuration = 0.0f,
 61   },
 62   .animations[1] = {
 63     .animationId = SPRITE_UNIT_PHASE_WEAPON_COOLDOWN,
 64     .srcRect = {16, 0, 6, 16},
 65     .offset = {8, 0},
 66   },
 67   .animations[2] = {
 68     .animationId = SPRITE_UNIT_PHASE_WEAPON_IDLE,
 69     .srcRect = {22, 0, 11, 16},
 70     .offset = {10, 0},
 71   },
 72 };
 73 
 74 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 75 {
 76   float unitScale = unit.scale == 0 ? 1.0f : unit.scale;
 77   float xScale = flip ? -1.0f : 1.0f;
 78   Camera3D camera = currentLevel->camera;
 79   float size = 0.5f * unitScale;
 80   // we want the sprite to face the camera, so we need to calculate the up vector
 81   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 82   Vector3 up = {0, 1, 0};
 83   Vector3 right = Vector3CrossProduct(forward, up);
 84   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 85   
 86   for (int i=0;i<SPRITE_UNIT_ANIMATION_COUNT;i++)
 87   {
 88     SpriteAnimation anim = unit.animations[i];
 89     if (anim.animationId != phase && anim.animationId != 0)
 90     {
 91       continue;
 92     }
 93     Rectangle srcRect = anim.srcRect;
 94     if (anim.frameCount > 1)
 95     {
 96       int w = anim.frameWidth > 0 ? anim.frameWidth : srcRect.width;
 97       srcRect.x += (int)(t / anim.frameDuration) % anim.frameCount * w;
 98     }
 99     Vector2 offset = { anim.offset.x / 16.0f * size, anim.offset.y / 16.0f * size * xScale };
100     Vector2 scale = { srcRect.width / 16.0f * size, srcRect.height / 16.0f * size };
101     
102     if (flip)
103     {
104       srcRect.x += srcRect.width;
105       srcRect.width = -srcRect.width;
106       offset.x = scale.x - offset.x;
107     }
108     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
109     // move the sprite slightly towards the camera to avoid z-fighting
110     position = Vector3Add(position, Vector3Scale(Vector3Normalize(forward), -0.01f));  
111   }
112 }
113 
114 void TowerInit()
115 {
116   for (int i = 0; i < TOWER_MAX_COUNT; i++)
117   {
118     towers[i] = (Tower){0};
119   }
120   towerCount = 0;
121 
122   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
123   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
124 
125   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
126   {
127     if (towerModels[i].materials)
128     {
129       // assign the palette texture to the material of the model (0 is not used afaik)
130       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
131     }
132   }
133 }
134 
135 static void TowerGunUpdate(Tower *tower)
136 {
137   TowerTypeConfig config = towerTypeConfigs[tower->towerType];
138   if (tower->cooldown <= 0.0f)
139   {
140     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, config.range);
141     if (enemy)
142     {
143       tower->cooldown = config.cooldown;
144       // shoot the enemy; determine future position of the enemy
145       float bulletSpeed = config.projectileSpeed;
146       Vector2 velocity = enemy->simVelocity;
147       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
148       Vector2 towerPosition = {tower->x, tower->y};
149       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
150       for (int i = 0; i < 8; i++) {
151         velocity = enemy->simVelocity;
152         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
153         float distance = Vector2Distance(towerPosition, futurePosition);
154         float eta2 = distance / bulletSpeed;
155         if (fabs(eta - eta2) < 0.01f) {
156           break;
157         }
158         eta = (eta2 + eta) * 0.5f;
159       }
160 
161       ProjectileTryAdd(config.projectileType, enemy, 
162         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
163         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
164         bulletSpeed, config.hitEffect);
165       enemy->futureDamage += config.hitEffect.damage;
166       tower->lastTargetPosition = futurePosition;
167     }
168   }
169   else
170   {
171     tower->cooldown -= gameTime.deltaTime;
172   }
173 }
174 
175 Tower *TowerGetAt(int16_t x, int16_t y)
176 {
177   for (int i = 0; i < towerCount; i++)
178   {
179     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
180     {
181       return &towers[i];
182     }
183   }
184   return 0;
185 }
186 
187 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
188 {
189   if (towerCount >= TOWER_MAX_COUNT)
190   {
191     return 0;
192   }
193 
194   Tower *tower = TowerGetAt(x, y);
195   if (tower)
196   {
197     return 0;
198   }
199 
200   tower = &towers[towerCount++];
201   tower->x = x;
202   tower->y = y;
203   tower->towerType = towerType;
204   tower->cooldown = 0.0f;
205   tower->damage = 0.0f;
206   return tower;
207 }
208 
209 Tower *GetTowerByType(uint8_t towerType)
210 {
211   for (int i = 0; i < towerCount; i++)
212   {
213     if (towers[i].towerType == towerType)
214     {
215       return &towers[i];
216     }
217   }
218   return 0;
219 }
220 
221 int GetTowerCosts(uint8_t towerType)
222 {
223   return towerTypeConfigs[towerType].cost;
224 }
225 
226 float TowerGetMaxHealth(Tower *tower)
227 {
228   return towerTypeConfigs[tower->towerType].maxHealth;
229 }
230 
231 void TowerDrawSingle(Tower tower)
232 {
233   if (tower.towerType == TOWER_TYPE_NONE)
234   {
235     return;
236   }
237 
238   switch (tower.towerType)
239   {
240   case TOWER_TYPE_ARCHER:
241     {
242       Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
243       Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
244       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
245       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
246         tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
247     }
248     break;
249   case TOWER_TYPE_BALLISTA:
250     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, BROWN);
251     break;
252   case TOWER_TYPE_CATAPULT:
253     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, DARKGRAY);
254     break;
255   default:
256     if (towerModels[tower.towerType].materials)
257     {
258       DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
259     } else {
260       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
261     }
262     break;
263   }
264 }
265 
266 void TowerDraw()
267 {
268   for (int i = 0; i < towerCount; i++)
269   {
270     TowerDrawSingle(towers[i]);
271   }
272 }
273 
274 void TowerUpdate()
275 {
276   for (int i = 0; i < towerCount; i++)
277   {
278     Tower *tower = &towers[i];
279     switch (tower->towerType)
280     {
281     case TOWER_TYPE_CATAPULT:
282     case TOWER_TYPE_BALLISTA:
283     case TOWER_TYPE_ARCHER:
284       TowerGunUpdate(tower);
285       break;
286     }
287   }
288 }
289 
290 void TowerDrawHealthBars(Camera3D camera)
291 {
292   for (int i = 0; i < towerCount; i++)
293   {
294     Tower *tower = &towers[i];
295     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
296     {
297       continue;
298     }
299     
300     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
301     float maxHealth = TowerGetMaxHealth(tower);
302     float health = maxHealth - tower->damage;
303     float healthRatio = health / maxHealth;
304     
305     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
306   }
307 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells)
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < blockedCellCount; i++)
131   {
132     int16_t mapX, mapY;
133     if (!PathFindingFromWorldToMapPosition((Vector3){blockedCells[i].x, 0.0f, blockedCells[i].y}, &mapX, &mapY))
134     {
135       continue;
136     }
137     int index = mapY * width + mapX;
138     pathfindingMap.towerIndex[index] = -2;
139   }
140 
141   for (int i = 0; i < towerCount; i++)
142   {
143     Tower *tower = &towers[i];
144     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
145     {
146       continue;
147     }
148     int16_t mapX, mapY;
149     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
150     // this would not work correctly and needs to be refined to allow towers covering multiple cells
151     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
152     // one cell. For now.
153     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
154     {
155       continue;
156     }
157     int index = mapY * width + mapX;
158     pathfindingMap.towerIndex[index] = i;
159   }
160 
161   // we start at the castle and add the castle to the queue
162   pathfindingMap.maxDistance = 0.0f;
163   pathfindingNodeQueueCount = 0;
164   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
165   PathfindingNode *node = 0;
166   while ((node = PathFindingNodePop()))
167   {
168     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
169     {
170       continue;
171     }
172     int index = node->y * width + node->x;
173     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
174     {
175       continue;
176     }
177 
178     int deltaX = node->x - node->fromX;
179     int deltaY = node->y - node->fromY;
180     // even if the cell is blocked by a tower, we still may want to store the direction
181     // (though this might not be needed, IDK right now)
182     pathfindingMap.deltaSrc[index].x = (char) deltaX;
183     pathfindingMap.deltaSrc[index].y = (char) deltaY;
184 
185     // we skip nodes that are blocked by towers or by the provided blocked cells
186     if (pathfindingMap.towerIndex[index] != -1)
187     {
188       node->distance += 8.0f;
189     }
190     pathfindingMap.distances[index] = node->distance;
191     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
192     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
193     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
194     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
195     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
196   }
197 }
198 
199 void PathFindingMapDraw()
200 {
201   float cellSize = pathfindingMap.scale * 0.9f;
202   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
203   for (int x = 0; x < pathfindingMap.width; x++)
204   {
205     for (int y = 0; y < pathfindingMap.height; y++)
206     {
207       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
208       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
209       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
210       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
211       // animate the distance "wave" to show how the pathfinding algorithm expands
212       // from the castle
213       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
214       {
215         color = BLACK;
216       }
217       DrawCube(position, cellSize, 0.1f, cellSize, color);
218     }
219   }
220 }
221 
222 Vector2 PathFindingGetGradient(Vector3 world)
223 {
224   int16_t mapX, mapY;
225   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
226   {
227     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
228     return (Vector2){(float)-delta.x, (float)-delta.y};
229   }
230   // fallback to a simple gradient calculation
231   float n = PathFindingGetDistance(mapX, mapY - 1);
232   float s = PathFindingGetDistance(mapX, mapY + 1);
233   float w = PathFindingGetDistance(mapX - 1, mapY);
234   float e = PathFindingGetDistance(mapX + 1, mapY);
235   return (Vector2){w - e + 0.25f, n - s + 0.125f};
236 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 #include <rlgl.h>
  6 
  7 EnemyClassConfig enemyClassConfigs[] = {
  8     [ENEMY_TYPE_MINION] = {
  9       .health = 10.0f, 
 10       .speed = 0.6f, 
 11       .radius = 0.25f, 
 12       .maxAcceleration = 1.0f,
 13       .explosionDamage = 1.0f,
 14       .requiredContactTime = 0.5f,
 15       .explosionRange = 1.0f,
 16       .explosionPushbackPower = 0.25f,
 17       .goldValue = 1,
 18     },
 19     [ENEMY_TYPE_RUNNER] = {
 20       .health = 5.0f, 
 21       .speed = 1.0f, 
 22       .radius = 0.25f, 
 23       .maxAcceleration = 2.0f,
 24       .explosionDamage = 1.0f,
 25       .requiredContactTime = 0.5f,
 26       .explosionRange = 1.0f,
 27       .explosionPushbackPower = 0.25f,
 28       .goldValue = 2,
 29     },
 30     [ENEMY_TYPE_SHIELD] = {
 31       .health = 20.0f, 
 32       .speed = 0.5f, 
 33       .radius = 0.25f, 
 34       .maxAcceleration = 1.0f,
 35       .explosionDamage = 2.0f,
 36       .requiredContactTime = 0.5f,
 37       .explosionRange = 1.0f,
 38       .explosionPushbackPower = 0.25f,
 39       .goldValue = 3,
 40     },
 41     [ENEMY_TYPE_BOSS] = {
 42       .health = 50.0f, 
 43       .speed = 0.4f, 
 44       .radius = 0.25f, 
 45       .maxAcceleration = 1.0f,
 46       .explosionDamage = 5.0f,
 47       .requiredContactTime = 0.5f,
 48       .explosionRange = 1.0f,
 49       .explosionPushbackPower = 0.25f,
 50       .goldValue = 10,
 51     },
 52 };
 53 
 54 Enemy enemies[ENEMY_MAX_COUNT];
 55 int enemyCount = 0;
 56 
 57 SpriteUnit enemySprites[] = {
 58     [ENEMY_TYPE_MINION] = {
 59       .animations[0] = {
 60         .srcRect = {0, 17, 16, 15},
 61         .offset = {8.0f, 0.0f},
 62         .frameCount = 6,
 63         .frameDuration = 0.1f,
 64       },
 65       .animations[1] = {
 66         .srcRect = {1, 33, 15, 14},
 67         .offset = {7.0f, 0.0f},
 68         .frameCount = 6,
 69         .frameWidth = 16,
 70         .frameDuration = 0.1f,
 71       },
 72     },
 73     [ENEMY_TYPE_RUNNER] = {
 74       .scale = 0.75f,
 75       .animations[0] = {
 76         .srcRect = {0, 17, 16, 15},
 77         .offset = {8.0f, 0.0f},
 78         .frameCount = 6,
 79         .frameDuration = 0.1f,
 80       },
 81     },
 82     [ENEMY_TYPE_SHIELD] = {
 83       .animations[0] = {
 84         .srcRect = {0, 17, 16, 15},
 85         .offset = {8.0f, 0.0f},
 86         .frameCount = 6,
 87         .frameDuration = 0.1f,
 88       },
 89       .animations[1] = {
 90         .srcRect = {99, 17, 10, 11},
 91         .offset = {7.0f, 0.0f},
 92       },
 93     },
 94     [ENEMY_TYPE_BOSS] = {
 95       .scale = 1.5f,
 96       .animations[0] = {
 97         .srcRect = {0, 17, 16, 15},
 98         .offset = {8.0f, 0.0f},
 99         .frameCount = 6,
100         .frameDuration = 0.1f,
101       },
102       .animations[1] = {
103         .srcRect = {97, 29, 14, 7},
104         .offset = {7.0f, -9.0f},
105       },
106     },
107 };
108 
109 void EnemyInit()
110 {
111   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
112   {
113     enemies[i] = (Enemy){0};
114   }
115   enemyCount = 0;
116 }
117 
118 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
119 {
120   return enemyClassConfigs[enemy->enemyType].speed;
121 }
122 
123 float EnemyGetMaxHealth(Enemy *enemy)
124 {
125   return enemyClassConfigs[enemy->enemyType].health;
126 }
127 
128 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
129 {
130   int16_t castleX = 0;
131   int16_t castleY = 0;
132   int16_t dx = castleX - currentX;
133   int16_t dy = castleY - currentY;
134   if (dx == 0 && dy == 0)
135   {
136     *nextX = currentX;
137     *nextY = currentY;
138     return 1;
139   }
140   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
141 
142   if (gradient.x == 0 && gradient.y == 0)
143   {
144     *nextX = currentX;
145     *nextY = currentY;
146     return 1;
147   }
148 
149   if (fabsf(gradient.x) > fabsf(gradient.y))
150   {
151     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
152     *nextY = currentY;
153     return 0;
154   }
155   *nextX = currentX;
156   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
157   return 0;
158 }
159 
160 
161 // this function predicts the movement of the unit for the next deltaT seconds
162 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
163 {
164   const float pointReachedDistance = 0.25f;
165   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
166   const float maxSimStepTime = 0.015625f;
167   
168   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
169   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
170   int16_t nextX = enemy->nextX;
171   int16_t nextY = enemy->nextY;
172   Vector2 position = enemy->simPosition;
173   int passedCount = 0;
174   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
175   {
176     float stepTime = fminf(deltaT - t, maxSimStepTime);
177     Vector2 target = (Vector2){nextX, nextY};
178     float speed = Vector2Length(*velocity);
179     // draw the target position for debugging
180     //DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
181     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
182     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
183     {
184       // we reached the target position, let's move to the next waypoint
185       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
186       target = (Vector2){nextX, nextY};
187       // track how many waypoints we passed
188       passedCount++;
189     }
190     
191     // acceleration towards the target
192     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
193     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
194     *velocity = Vector2Add(*velocity, acceleration);
195 
196     // limit the speed to the maximum speed
197     if (speed > maxSpeed)
198     {
199       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
200     }
201 
202     // move the enemy
203     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
204   }
205 
206   if (waypointPassedCount)
207   {
208     (*waypointPassedCount) = passedCount;
209   }
210 
211   return position;
212 }
213 
214 void EnemyDraw()
215 {
216   rlDrawRenderBatchActive();
217   rlDisableDepthMask();
218   for (int i = 0; i < enemyCount; i++)
219   {
220     Enemy enemy = enemies[i];
221     if (enemy.enemyType == ENEMY_TYPE_NONE)
222     {
223       continue;
224     }
225 
226     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
227     
228     // don't draw any trails for now; might replace this with footprints later
229     // if (enemy.movePathCount > 0)
230     // {
231     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
232     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
233     // }
234     // for (int j = 1; j < enemy.movePathCount; j++)
235     // {
236     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
237     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
238     //   DrawLine3D(p, q, GREEN);
239     // }
240 
241     switch (enemy.enemyType)
242     {
243     case ENEMY_TYPE_MINION:
244     case ENEMY_TYPE_RUNNER:
245     case ENEMY_TYPE_SHIELD:
246     case ENEMY_TYPE_BOSS:
247       DrawSpriteUnit(enemySprites[enemy.enemyType], (Vector3){position.x, 0.0f, position.y}, 
248         enemy.walkedDistance, 0, 0);
249       break;
250     }
251   }
252   rlDrawRenderBatchActive();
253   rlEnableDepthMask();
254 }
255 
256 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
257 {
258   // damage the tower
259   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
260   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
261   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
262   float explosionRange2 = explosionRange * explosionRange;
263   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
264   // explode the enemy
265   if (tower->damage >= TowerGetMaxHealth(tower))
266   {
267     tower->towerType = TOWER_TYPE_NONE;
268   }
269 
270   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
271     explosionSource, 
272     (Vector3){0, 0.1f, 0}, (Vector3){1.0f, 1.0f, 1.0f}, 1.0f);
273 
274   enemy->enemyType = ENEMY_TYPE_NONE;
275 
276   // push back enemies & dealing damage
277   for (int i = 0; i < enemyCount; i++)
278   {
279     Enemy *other = &enemies[i];
280     if (other->enemyType == ENEMY_TYPE_NONE)
281     {
282       continue;
283     }
284     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
285     if (distanceSqr > 0 && distanceSqr < explosionRange2)
286     {
287       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
288       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
289       EnemyAddDamage(other, explosionDamge);
290     }
291   }
292 }
293 
294 void EnemyUpdate()
295 {
296   const float castleX = 0;
297   const float castleY = 0;
298   const float maxPathDistance2 = 0.25f * 0.25f;
299   
300   for (int i = 0; i < enemyCount; i++)
301   {
302     Enemy *enemy = &enemies[i];
303     if (enemy->enemyType == ENEMY_TYPE_NONE)
304     {
305       continue;
306     }
307 
308     int waypointPassedCount = 0;
309     Vector2 prevPosition = enemy->simPosition;
310     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
311     enemy->startMovingTime = gameTime.time;
312     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
313     // track path of unit
314     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
315     {
316       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
317       {
318         enemy->movePath[j] = enemy->movePath[j - 1];
319       }
320       enemy->movePath[0] = enemy->simPosition;
321       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
322       {
323         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
324       }
325     }
326 
327     if (waypointPassedCount > 0)
328     {
329       enemy->currentX = enemy->nextX;
330       enemy->currentY = enemy->nextY;
331       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
332         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
333       {
334         // enemy reached the castle; remove it
335         enemy->enemyType = ENEMY_TYPE_NONE;
336         continue;
337       }
338     }
339   }
340 
341   // handle collisions between enemies
342   for (int i = 0; i < enemyCount - 1; i++)
343   {
344     Enemy *enemyA = &enemies[i];
345     if (enemyA->enemyType == ENEMY_TYPE_NONE)
346     {
347       continue;
348     }
349     for (int j = i + 1; j < enemyCount; j++)
350     {
351       Enemy *enemyB = &enemies[j];
352       if (enemyB->enemyType == ENEMY_TYPE_NONE)
353       {
354         continue;
355       }
356       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
357       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
358       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
359       float radiusSum = radiusA + radiusB;
360       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
361       {
362         // collision
363         float distance = sqrtf(distanceSqr);
364         float overlap = radiusSum - distance;
365         // move the enemies apart, but softly; if we have a clog of enemies,
366         // moving them perfectly apart can cause them to jitter
367         float positionCorrection = overlap / 5.0f;
368         Vector2 direction = (Vector2){
369             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
370             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
371         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
372         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
373       }
374     }
375   }
376 
377   // handle collisions between enemies and towers
378   for (int i = 0; i < enemyCount; i++)
379   {
380     Enemy *enemy = &enemies[i];
381     if (enemy->enemyType == ENEMY_TYPE_NONE)
382     {
383       continue;
384     }
385     enemy->contactTime -= gameTime.deltaTime;
386     if (enemy->contactTime < 0.0f)
387     {
388       enemy->contactTime = 0.0f;
389     }
390 
391     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
392     // linear search over towers; could be optimized by using path finding tower map,
393     // but for now, we keep it simple
394     for (int j = 0; j < towerCount; j++)
395     {
396       Tower *tower = &towers[j];
397       if (tower->towerType == TOWER_TYPE_NONE)
398       {
399         continue;
400       }
401       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
402       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
403       if (distanceSqr > combinedRadius * combinedRadius)
404       {
405         continue;
406       }
407       // potential collision; square / circle intersection
408       float dx = tower->x - enemy->simPosition.x;
409       float dy = tower->y - enemy->simPosition.y;
410       float absDx = fabsf(dx);
411       float absDy = fabsf(dy);
412       Vector3 contactPoint = {0};
413       if (absDx <= 0.5f && absDx <= absDy) {
414         // vertical collision; push the enemy out horizontally
415         float overlap = enemyRadius + 0.5f - absDy;
416         if (overlap < 0.0f)
417         {
418           continue;
419         }
420         float direction = dy > 0.0f ? -1.0f : 1.0f;
421         enemy->simPosition.y += direction * overlap;
422         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
423       }
424       else if (absDy <= 0.5f && absDy <= absDx)
425       {
426         // horizontal collision; push the enemy out vertically
427         float overlap = enemyRadius + 0.5f - absDx;
428         if (overlap < 0.0f)
429         {
430           continue;
431         }
432         float direction = dx > 0.0f ? -1.0f : 1.0f;
433         enemy->simPosition.x += direction * overlap;
434         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
435       }
436       else
437       {
438         // possible collision with a corner
439         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
440         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
441         float cornerX = tower->x + cornerDX;
442         float cornerY = tower->y + cornerDY;
443         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
444         if (cornerDistanceSqr > enemyRadius * enemyRadius)
445         {
446           continue;
447         }
448         // push the enemy out along the diagonal
449         float cornerDistance = sqrtf(cornerDistanceSqr);
450         float overlap = enemyRadius - cornerDistance;
451         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
452         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
453         enemy->simPosition.x -= directionX * overlap;
454         enemy->simPosition.y -= directionY * overlap;
455         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
456       }
457 
458       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
459       {
460         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
461         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
462         {
463           EnemyTriggerExplode(enemy, tower, contactPoint);
464         }
465       }
466     }
467   }
468 }
469 
470 EnemyId EnemyGetId(Enemy *enemy)
471 {
472   return (EnemyId){enemy - enemies, enemy->generation};
473 }
474 
475 Enemy *EnemyTryResolve(EnemyId enemyId)
476 {
477   if (enemyId.index >= ENEMY_MAX_COUNT)
478   {
479     return 0;
480   }
481   Enemy *enemy = &enemies[enemyId.index];
482   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
483   {
484     return 0;
485   }
486   return enemy;
487 }
488 
489 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
490 {
491   Enemy *spawn = 0;
492   for (int i = 0; i < enemyCount; i++)
493   {
494     Enemy *enemy = &enemies[i];
495     if (enemy->enemyType == ENEMY_TYPE_NONE)
496     {
497       spawn = enemy;
498       break;
499     }
500   }
501 
502   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
503   {
504     spawn = &enemies[enemyCount++];
505   }
506 
507   if (spawn)
508   {
509     spawn->currentX = currentX;
510     spawn->currentY = currentY;
511     spawn->nextX = currentX;
512     spawn->nextY = currentY;
513     spawn->simPosition = (Vector2){currentX, currentY};
514     spawn->simVelocity = (Vector2){0, 0};
515     spawn->enemyType = enemyType;
516     spawn->startMovingTime = gameTime.time;
517     spawn->damage = 0.0f;
518     spawn->futureDamage = 0.0f;
519     spawn->generation++;
520     spawn->movePathCount = 0;
521     spawn->walkedDistance = 0.0f;
522   }
523 
524   return spawn;
525 }
526 
527 int EnemyAddDamageRange(Vector2 position, float range, float damage)
528 {
529   int count = 0;
530   float range2 = range * range;
531   for (int i = 0; i < enemyCount; i++)
532   {
533     Enemy *enemy = &enemies[i];
534     if (enemy->enemyType == ENEMY_TYPE_NONE)
535     {
536       continue;
537     }
538     float distance2 = Vector2DistanceSqr(position, enemy->simPosition);
539     if (distance2 <= range2)
540     {
541       EnemyAddDamage(enemy, damage);
542       count++;
543     }
544   }
545   return count;
546 }
547 
548 int EnemyAddDamage(Enemy *enemy, float damage)
549 {
550   enemy->damage += damage;
551   if (enemy->damage >= EnemyGetMaxHealth(enemy))
552   {
553     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
554     enemy->enemyType = ENEMY_TYPE_NONE;
555     return 1;
556   }
557 
558   return 0;
559 }
560 
561 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
562 {
563   int16_t castleX = 0;
564   int16_t castleY = 0;
565   Enemy* closest = 0;
566   int16_t closestDistance = 0;
567   float range2 = range * range;
568   for (int i = 0; i < enemyCount; i++)
569   {
570     Enemy* enemy = &enemies[i];
571     if (enemy->enemyType == ENEMY_TYPE_NONE)
572     {
573       continue;
574     }
575     float maxHealth = EnemyGetMaxHealth(enemy);
576     if (enemy->futureDamage >= maxHealth)
577     {
578       // ignore enemies that will die soon
579       continue;
580     }
581     int16_t dx = castleX - enemy->currentX;
582     int16_t dy = castleY - enemy->currentY;
583     int16_t distance = abs(dx) + abs(dy);
584     if (!closest || distance < closestDistance)
585     {
586       float tdx = towerX - enemy->currentX;
587       float tdy = towerY - enemy->currentY;
588       float tdistance2 = tdx * tdx + tdy * tdy;
589       if (tdistance2 <= range2)
590       {
591         closest = enemy;
592         closestDistance = distance;
593       }
594     }
595   }
596   return closest;
597 }
598 
599 int EnemyCount()
600 {
601   int count = 0;
602   for (int i = 0; i < enemyCount; i++)
603   {
604     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
605     {
606       count++;
607     }
608   }
609   return count;
610 }
611 
612 void EnemyDrawHealthbars(Camera3D camera)
613 {
614   for (int i = 0; i < enemyCount; i++)
615   {
616     Enemy *enemy = &enemies[i];
617     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
618     {
619       continue;
620     }
621     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
622     float maxHealth = EnemyGetMaxHealth(enemy);
623     float health = maxHealth - enemy->damage;
624     float healthRatio = health / maxHealth;
625     
626     DrawHealthBar(camera, position, healthRatio, GREEN, 15.0f);
627   }
628 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 typedef struct ProjectileConfig
  8 {
  9   float arcFactor;
 10   Color color;
 11   Color trailColor;
 12 } ProjectileConfig;
 13 
 14 ProjectileConfig projectileConfigs[] = {
 15     [PROJECTILE_TYPE_ARROW] = {
 16         .arcFactor = 0.15f,
 17         .color = RED,
 18         .trailColor = BROWN,
 19     },
 20     [PROJECTILE_TYPE_CATAPULT] = {
 21         .arcFactor = 0.5f,
 22         .color = RED,
 23         .trailColor = GRAY,
 24     },
 25     [PROJECTILE_TYPE_BALLISTA] = {
 26         .arcFactor = 0.025f,
 27         .color = RED,
 28         .trailColor = BROWN,
 29     },
 30 };
 31 
 32 void ProjectileInit()
 33 {
 34   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 35   {
 36     projectiles[i] = (Projectile){0};
 37   }
 38 }
 39 
 40 void ProjectileDraw()
 41 {
 42   for (int i = 0; i < projectileCount; i++)
 43   {
 44     Projectile projectile = projectiles[i];
 45     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 46     {
 47       continue;
 48     }
 49     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 50     if (transition >= 1.0f)
 51     {
 52       continue;
 53     }
 54 
 55     ProjectileConfig config = projectileConfigs[projectile.projectileType];
 56     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 57     {
 58       float t = transition + transitionOffset * 0.3f;
 59       if (t > 1.0f)
 60       {
 61         break;
 62       }
 63       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 64       Color color = config.color;
 65       color = ColorLerp(config.trailColor, config.color, transitionOffset * transitionOffset);
 66       // fake a ballista flight path using parabola equation
 67       float parabolaT = t - 0.5f;
 68       parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 69       position.y += config.arcFactor * parabolaT * projectile.distance;
 70       
 71       float size = 0.06f * (transitionOffset + 0.25f);
 72       DrawCube(position, size, size, size, color);
 73     }
 74   }
 75 }
 76 
 77 void ProjectileUpdate()
 78 {
 79   for (int i = 0; i < projectileCount; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 87     if (transition >= 1.0f)
 88     {
 89       projectile->projectileType = PROJECTILE_TYPE_NONE;
 90       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 91       if (enemy && projectile->hitEffectConfig.pushbackPowerDistance > 0.0f)
 92       {
 93           Vector2 direction = Vector2Normalize(Vector2Subtract((Vector2){projectile->target.x, projectile->target.z}, enemy->simPosition));
 94           enemy->simPosition = Vector2Add(enemy->simPosition, Vector2Scale(direction, projectile->hitEffectConfig.pushbackPowerDistance));
 95       }
 96       
 97       if (projectile->hitEffectConfig.areaDamageRadius > 0.0f)
 98       {
 99         EnemyAddDamageRange((Vector2){projectile->target.x, projectile->target.z}, projectile->hitEffectConfig.areaDamageRadius, projectile->hitEffectConfig.damage);
100         // pancaked sphere explosion
101         float r = projectile->hitEffectConfig.areaDamageRadius;
102         ParticleAdd(PARTICLE_TYPE_EXPLOSION, projectile->target, (Vector3){0}, (Vector3){r, r * 0.2f, r}, 0.33f);
103       }
104       else if (projectile->hitEffectConfig.damage > 0.0f && enemy)
105       {
106         EnemyAddDamage(enemy, projectile->hitEffectConfig.damage);
107       }
108       continue;
109     }
110   }
111 }
112 
113 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig)
114 {
115   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
116   {
117     Projectile *projectile = &projectiles[i];
118     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
119     {
120       projectile->projectileType = projectileType;
121       projectile->shootTime = gameTime.time;
122       float distance = Vector3Distance(position, target);
123       projectile->arrivalTime = gameTime.time + distance / speed;
124       projectile->position = position;
125       projectile->target = target;
126       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
127       projectile->distance = distance;
128       projectile->targetEnemy = EnemyGetId(enemy);
129       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
130       projectile->hitEffectConfig = hitEffectConfig;
131       return projectile;
132     }
133   }
134   return 0;
135 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 
  5 static Particle particles[PARTICLE_MAX_COUNT];
  6 static int particleCount = 0;
  7 
  8 void ParticleInit()
  9 {
 10   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 11   {
 12     particles[i] = (Particle){0};
 13   }
 14   particleCount = 0;
 15 }
 16 
 17 static void DrawExplosionParticle(Particle *particle, float transition)
 18 {
 19   Vector3 scale = particle->scale;
 20   float size = 1.0f * (1.0f - transition);
 21   Color startColor = WHITE;
 22   Color endColor = RED;
 23   Color color = ColorLerp(startColor, endColor, transition);
 24 
 25   rlPushMatrix();
 26   rlTranslatef(particle->position.x, particle->position.y, particle->position.z);
 27   rlScalef(scale.x, scale.y, scale.z);
 28   DrawSphere(Vector3Zero(), size, color);
 29   rlPopMatrix();
 30 }
 31 
 32 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime)
 33 {
 34   if (particleCount >= PARTICLE_MAX_COUNT)
 35   {
 36     return;
 37   }
 38 
 39   int index = -1;
 40   for (int i = 0; i < particleCount; i++)
 41   {
 42     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 43     {
 44       index = i;
 45       break;
 46     }
 47   }
 48 
 49   if (index == -1)
 50   {
 51     index = particleCount++;
 52   }
 53 
 54   Particle *particle = &particles[index];
 55   particle->particleType = particleType;
 56   particle->spawnTime = gameTime.time;
 57   particle->lifetime = lifetime;
 58   particle->position = position;
 59   particle->velocity = velocity;
 60   particle->scale = scale;
 61 }
 62 
 63 void ParticleUpdate()
 64 {
 65   for (int i = 0; i < particleCount; i++)
 66   {
 67     Particle *particle = &particles[i];
 68     if (particle->particleType == PARTICLE_TYPE_NONE)
 69     {
 70       continue;
 71     }
 72 
 73     float age = gameTime.time - particle->spawnTime;
 74 
 75     if (particle->lifetime > age)
 76     {
 77       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 78     }
 79     else {
 80       particle->particleType = PARTICLE_TYPE_NONE;
 81     }
 82   }
 83 }
 84 
 85 void ParticleDraw()
 86 {
 87   for (int i = 0; i < particleCount; i++)
 88   {
 89     Particle particle = particles[i];
 90     if (particle.particleType == PARTICLE_TYPE_NONE)
 91     {
 92       continue;
 93     }
 94 
 95     float age = gameTime.time - particle.spawnTime;
 96     float transition = age / particle.lifetime;
 97     switch (particle.particleType)
 98     {
 99     case PARTICLE_TYPE_EXPLOSION:
100       DrawExplosionParticle(&particle, transition);
101       break;
102     default:
103       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
104       break;
105     }
106   }
107 }  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endifThe different enemies are now spawning in the game in the 1st and 2nd wave for testing purposes. A new scaling factor allows us to make the enemies bigger or smaller:
The graphics are now good enough for current state. The next rule to implement is to give the shield its own hitpoints and apply a damage reduction. The idea is that the the ballista's high damage is piercing the shield and the minion behind it while arrow attacks only damage the shield. When the shield is destroyed, the shield should now longer be drawn.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 #include <stdlib.h>
  5 #include <math.h>
  6 
  7 //# Variables
  8 GUIState guiState = {0};
  9 GameTime gameTime = {
 10   .fixedDeltaTime = 1.0f / 60.0f,
 11 };
 12 
 13 Model floorTileAModel = {0};
 14 Model floorTileBModel = {0};
 15 Model treeModel[2] = {0};
 16 Model firTreeModel[2] = {0};
 17 Model rockModels[5] = {0};
 18 Model grassPatchModel[1] = {0};
 19 
 20 Model pathArrowModel = {0};
 21 Model greenArrowModel = {0};
 22 
 23 Texture2D palette, spriteSheet;
 24 
 25 Level levels[] = {
 26   [0] = {
 27     .state = LEVEL_STATE_BUILDING,
 28     .initialGold = 20,
 29     .waves[0] = {
 30       .enemyType = ENEMY_TYPE_SHIELD,
 31       .wave = 0,
 32       .count = 5,
 33       .interval = 2.5f,
 34       .delay = 1.0f,
 35       .spawnPosition = {2, 6},
 36     },
 37     .waves[1] = {
 38       .enemyType = ENEMY_TYPE_RUNNER,
 39       .wave = 0,
 40       .count = 5,
 41       .interval = 2.5f,
 42       .delay = 1.0f,
 43       .spawnPosition = {-2, 6},
 44     },
 45     .waves[2] = {
 46       .enemyType = ENEMY_TYPE_SHIELD,
 47       .wave = 1,
 48       .count = 20,
 49       .interval = 1.5f,
 50       .delay = 1.0f,
 51       .spawnPosition = {0, 6},
 52     },
 53     .waves[3] = {
 54       .enemyType = ENEMY_TYPE_MINION,
 55       .wave = 2,
 56       .count = 30,
 57       .interval = 1.2f,
 58       .delay = 1.0f,
 59       .spawnPosition = {2, 6},
 60     },
 61     .waves[4] = {
 62       .enemyType = ENEMY_TYPE_BOSS,
 63       .wave = 2,
 64       .count = 2,
 65       .interval = 5.0f,
 66       .delay = 2.0f,
 67       .spawnPosition = {-2, 4},
 68     }
 69   },
 70 };
 71 
 72 Level *currentLevel = levels;
 73 
 74 //# Game
 75 
 76 static Model LoadGLBModel(char *filename)
 77 {
 78   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 79   for (int i = 0; i < model.materialCount; i++)
 80   {
 81     model.materials[i].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 82   }
 83   return model;
 84 }
 85 
 86 void LoadAssets()
 87 {
 88   // load a sprite sheet that contains all units
 89   spriteSheet = LoadTexture("data/spritesheet.png");
 90   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 91 
 92   // we'll use a palette texture to colorize the all buildings and environment art
 93   palette = LoadTexture("data/palette.png");
 94   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 95   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 96 
 97   floorTileAModel = LoadGLBModel("floor-tile-a");
 98   floorTileBModel = LoadGLBModel("floor-tile-b");
 99   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
100   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
101   firTreeModel[0] = LoadGLBModel("firtree-1-a");
102   firTreeModel[1] = LoadGLBModel("firtree-1-b");
103   rockModels[0] = LoadGLBModel("rock-1");
104   rockModels[1] = LoadGLBModel("rock-2");
105   rockModels[2] = LoadGLBModel("rock-3");
106   rockModels[3] = LoadGLBModel("rock-4");
107   rockModels[4] = LoadGLBModel("rock-5");
108   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
109 
110   pathArrowModel = LoadGLBModel("direction-arrow-x");
111   greenArrowModel = LoadGLBModel("green-arrow");
112 }
113 
114 void InitLevel(Level *level)
115 {
116   level->seed = (int)(GetTime() * 100.0f);
117 
118   TowerInit();
119   EnemyInit();
120   ProjectileInit();
121   ParticleInit();
122   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
123 
124   level->placementMode = 0;
125   level->state = LEVEL_STATE_BUILDING;
126   level->nextState = LEVEL_STATE_NONE;
127   level->playerGold = level->initialGold;
128   level->currentWave = 0;
129   level->placementX = -1;
130   level->placementY = 0;
131 
132   Camera *camera = &level->camera;
133   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
134   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
135   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
136   camera->fovy = 10.0f;
137   camera->projection = CAMERA_ORTHOGRAPHIC;
138 }
139 
140 void DrawLevelHud(Level *level)
141 {
142   const char *text = TextFormat("Gold: %d", level->playerGold);
143   Font font = GetFontDefault();
144   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
145   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
146 }
147 
148 void DrawLevelReportLostWave(Level *level)
149 {
150   BeginMode3D(level->camera);
151   DrawLevelGround(level);
152   TowerDraw();
153   EnemyDraw();
154   ProjectileDraw();
155   ParticleDraw();
156   guiState.isBlocked = 0;
157   EndMode3D();
158 
159   TowerDrawHealthBars(level->camera);
160 
161   const char *text = "Wave lost";
162   int textWidth = MeasureText(text, 20);
163   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
164 
165   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
166   {
167     level->nextState = LEVEL_STATE_RESET;
168   }
169 }
170 
171 int HasLevelNextWave(Level *level)
172 {
173   for (int i = 0; i < 10; i++)
174   {
175     EnemyWave *wave = &level->waves[i];
176     if (wave->wave == level->currentWave)
177     {
178       return 1;
179     }
180   }
181   return 0;
182 }
183 
184 void DrawLevelReportWonWave(Level *level)
185 {
186   BeginMode3D(level->camera);
187   DrawLevelGround(level);
188   TowerDraw();
189   EnemyDraw();
190   ProjectileDraw();
191   ParticleDraw();
192   guiState.isBlocked = 0;
193   EndMode3D();
194 
195   TowerDrawHealthBars(level->camera);
196 
197   const char *text = "Wave won";
198   int textWidth = MeasureText(text, 20);
199   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
200 
201 
202   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
203   {
204     level->nextState = LEVEL_STATE_RESET;
205   }
206 
207   if (HasLevelNextWave(level))
208   {
209     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
210     {
211       level->nextState = LEVEL_STATE_BUILDING;
212     }
213   }
214   else {
215     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
216     {
217       level->nextState = LEVEL_STATE_WON_LEVEL;
218     }
219   }
220 }
221 
222 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
223 {
224   static ButtonState buttonStates[8] = {0};
225   int cost = GetTowerCosts(towerType);
226   const char *text = TextFormat("%s: %d", name, cost);
227   buttonStates[towerType].isSelected = level->placementMode == towerType;
228   buttonStates[towerType].isDisabled = level->playerGold < cost;
229   if (Button(text, x, y, width, height, &buttonStates[towerType]))
230   {
231     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
232     level->nextState = LEVEL_STATE_BUILDING_PLACEMENT;
233   }
234 }
235 
236 float GetRandomFloat(float min, float max)
237 {
238   int random = GetRandomValue(0, 0xfffffff);
239   return ((float)random / (float)0xfffffff) * (max - min) + min;
240 }
241 
242 void DrawLevelGround(Level *level)
243 {
244   // draw checkerboard ground pattern
245   for (int x = -5; x <= 5; x += 1)
246   {
247     for (int y = -5; y <= 5; y += 1)
248     {
249       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
250       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
251     }
252   }
253 
254   int oldSeed = GetRandomValue(0, 0xfffffff);
255   SetRandomSeed(level->seed);
256   // increase probability for trees via duplicated entries
257   Model borderModels[64];
258   int maxRockCount = GetRandomValue(2, 6);
259   int maxTreeCount = GetRandomValue(10, 20);
260   int maxFirTreeCount = GetRandomValue(5, 10);
261   int maxLeafTreeCount = maxTreeCount - maxFirTreeCount;
262   int grassPatchCount = GetRandomValue(5, 30);
263 
264   int modelCount = 0;
265   for (int i = 0; i < maxRockCount && modelCount < 63; i++)
266   {
267     borderModels[modelCount++] = rockModels[GetRandomValue(0, 5)];
268   }
269   for (int i = 0; i < maxLeafTreeCount && modelCount < 63; i++)
270   {
271     borderModels[modelCount++] = treeModel[GetRandomValue(0, 1)];
272   }
273   for (int i = 0; i < maxFirTreeCount && modelCount < 63; i++)
274   {
275     borderModels[modelCount++] = firTreeModel[GetRandomValue(0, 1)];
276   }
277   for (int i = 0; i < grassPatchCount && modelCount < 63; i++)
278   {
279     borderModels[modelCount++] = grassPatchModel[0];
280   }
281 
282   // draw some objects around the border of the map
283   Vector3 up = {0, 1, 0};
284   // a pseudo random number generator to get the same result every time
285   const float wiggle = 0.75f;
286   const int layerCount = 3;
287   for (int layer = 0; layer <= layerCount; layer++)
288   {
289     int layerPos = 6 + layer;
290     Model *selectedModels = borderModels;
291     int selectedModelCount = modelCount;
292     if (layer == 0)
293     {
294       selectedModels = grassPatchModel;
295       selectedModelCount = 1;
296     }
297     for (int x = -6 + layer; x <= 6 + layer; x += 1)
298     {
299       DrawModelEx(selectedModels[GetRandomValue(0, selectedModelCount - 1)], 
300         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, -layerPos + GetRandomFloat(0.0f, wiggle)}, 
301         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
302       DrawModelEx(selectedModels[GetRandomValue(0, selectedModelCount - 1)], 
303         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, layerPos + GetRandomFloat(0.0f, wiggle)}, 
304         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
305     }
306 
307     for (int z = -5 + layer; z <= 5 + layer; z += 1)
308     {
309       DrawModelEx(selectedModels[GetRandomValue(0, selectedModelCount - 1)], 
310         (Vector3){-layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
311         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
312       DrawModelEx(selectedModels[GetRandomValue(0, selectedModelCount - 1)], 
313         (Vector3){layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
314         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
315     }
316   }
317 
318   SetRandomSeed(oldSeed);
319 }
320 
321 void DrawEnemyPath(Level *level, Color arrowColor)
322 {
323   const int castleX = 0, castleY = 0;
324   const int maxWaypointCount = 200;
325   const float timeStep = 1.0f;
326   Vector3 arrowScale = {0.75f, 0.75f, 0.75f};
327 
328   // we start with a time offset to simulate the path, 
329   // this way the arrows are animated in a forward moving direction
330   // The time is wrapped around the time step to get a smooth animation
331   float timeOffset = fmodf(GetTime(), timeStep);
332 
333   for (int i = 0; i < ENEMY_MAX_WAVE_COUNT; i++)
334   {
335     EnemyWave *wave = &level->waves[i];
336     if (wave->wave != level->currentWave)
337     {
338       continue;
339     }
340 
341     // use this dummy enemy to simulate the path
342     Enemy dummy = {
343       .enemyType = ENEMY_TYPE_MINION,
344       .simPosition = (Vector2){wave->spawnPosition.x, wave->spawnPosition.y},
345       .nextX = wave->spawnPosition.x,
346       .nextY = wave->spawnPosition.y,
347       .currentX = wave->spawnPosition.x,
348       .currentY = wave->spawnPosition.y,
349     };
350 
351     float deltaTime = timeOffset;
352     for (int j = 0; j < maxWaypointCount; j++)
353     {
354       int waypointPassedCount = 0;
355       Vector2 pos = EnemyGetPosition(&dummy, deltaTime, &dummy.simVelocity, &waypointPassedCount);
356       // after the initial variable starting offset, we use a fixed time step
357       deltaTime = timeStep;
358       dummy.simPosition = pos;
359 
360       // Update the dummy's position just like we do in the regular enemy update loop
361       for (int k = 0; k < waypointPassedCount; k++)
362       {
363         dummy.currentX = dummy.nextX;
364         dummy.currentY = dummy.nextY;
365         if (EnemyGetNextPosition(dummy.currentX, dummy.currentY, &dummy.nextX, &dummy.nextY) &&
366           Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
367         {
368           break;
369         }
370       }
371       if (Vector2DistanceSqr(dummy.simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
372       {
373         break;
374       }
375       
376       // get the angle we need to rotate the arrow model. The velocity is just fine for this.
377       float angle = atan2f(dummy.simVelocity.x, dummy.simVelocity.y) * RAD2DEG - 90.0f;
378       DrawModelEx(pathArrowModel, (Vector3){pos.x, 0.15f, pos.y}, (Vector3){0, 1, 0}, angle, arrowScale, arrowColor);
379     }
380   }
381 }
382 
383 void DrawEnemyPaths(Level *level)
384 {
385   // disable depth testing for the path arrows
386   // flush the 3D batch to draw the arrows on top of everything
387   rlDrawRenderBatchActive();
388   rlDisableDepthTest();
389   DrawEnemyPath(level, (Color){64, 64, 64, 160});
390 
391   rlDrawRenderBatchActive();
392   rlEnableDepthTest();
393   DrawEnemyPath(level, WHITE);
394 }
395 
396 static void SimulateTowerPlacementBehavior(Level *level, float towerFloatHeight, float mapX, float mapY)
397 {
398   float dt = gameTime.fixedDeltaTime;
399   // smooth transition for the placement position using exponential decay
400   const float lambda = 15.0f;
401   float factor = 1.0f - expf(-lambda * dt);
402 
403   float damping = 0.5f;
404   float springStiffness = 300.0f;
405   float springDecay = 95.0f;
406   float minHeight = 0.35f;
407 
408   if (level->placementPhase == PLACEMENT_PHASE_STARTING)
409   {
410     damping = 1.0f;
411     springDecay = 90.0f;
412     springStiffness = 100.0f;
413     minHeight = 0.70f;
414   }
415 
416   for (int i = 0; i < gameTime.fixedStepCount; i++)
417   {
418     level->placementTransitionPosition = 
419       Vector2Lerp(
420         level->placementTransitionPosition, 
421         (Vector2){mapX, mapY}, factor);
422 
423     // draw the spring position for debugging the spring simulation
424     // first step: stiff spring, no simulation
425     Vector3 worldPlacementPosition = (Vector3){
426       level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
427     Vector3 springTargetPosition = (Vector3){
428       worldPlacementPosition.x, 1.0f + towerFloatHeight, worldPlacementPosition.z};
429     // consider the current velocity to predict the future position in order to dampen
430     // the spring simulation. Longer prediction times will result in more damping
431     Vector3 predictedSpringPosition = Vector3Add(level->placementTowerSpring.position, 
432       Vector3Scale(level->placementTowerSpring.velocity, dt * damping));
433     Vector3 springPointDelta = Vector3Subtract(springTargetPosition, predictedSpringPosition);
434     Vector3 velocityChange = Vector3Scale(springPointDelta, dt * springStiffness);
435     // decay velocity of the upright forcing spring
436     // This force acts like a 2nd spring that pulls the tip upright into the air above the
437     // base position
438     level->placementTowerSpring.velocity = Vector3Scale(level->placementTowerSpring.velocity, 1.0f - expf(-springDecay * dt));
439     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, velocityChange);
440 
441     // calculate length of the spring to calculate the force to apply to the placementTowerSpring position
442     // we use a simple spring model with a rest length of 1.0f
443     Vector3 springDelta = Vector3Subtract(worldPlacementPosition, level->placementTowerSpring.position);
444     float springLength = Vector3Length(springDelta);
445     float springForce = (springLength - 1.0f) * springStiffness;
446     Vector3 springForceVector = Vector3Normalize(springDelta);
447     springForceVector = Vector3Scale(springForceVector, springForce);
448     level->placementTowerSpring.velocity = Vector3Add(level->placementTowerSpring.velocity, 
449       Vector3Scale(springForceVector, dt));
450 
451     level->placementTowerSpring.position = Vector3Add(level->placementTowerSpring.position, 
452       Vector3Scale(level->placementTowerSpring.velocity, dt));
453     if (level->placementTowerSpring.position.y < minHeight + towerFloatHeight)
454     {
455       level->placementTowerSpring.velocity.y *= -1.0f;
456       level->placementTowerSpring.position.y = fmaxf(level->placementTowerSpring.position.y, minHeight + towerFloatHeight);
457     }
458   }
459 }
460 
461 void DrawLevelBuildingPlacementState(Level *level)
462 {
463   const float placementDuration = 0.5f;
464 
465   level->placementTimer += gameTime.deltaTime;
466   if (level->placementTimer > 1.0f && level->placementPhase == PLACEMENT_PHASE_STARTING)
467   {
468     level->placementPhase = PLACEMENT_PHASE_MOVING;
469     level->placementTimer = 0.0f;
470   }
471 
472   BeginMode3D(level->camera);
473   DrawLevelGround(level);
474 
475   int blockedCellCount = 0;
476   Vector2 blockedCells[1];
477   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
478   float planeDistance = ray.position.y / -ray.direction.y;
479   float planeX = ray.direction.x * planeDistance + ray.position.x;
480   float planeY = ray.direction.z * planeDistance + ray.position.z;
481   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
482   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
483   if (level->placementPhase == PLACEMENT_PHASE_MOVING && 
484     level->placementMode && !guiState.isBlocked && 
485     mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5 && IsMouseButtonDown(MOUSE_LEFT_BUTTON))
486   {
487     level->placementX = mapX;
488     level->placementY = mapY;
489   }
490   else
491   {
492     mapX = level->placementX;
493     mapY = level->placementY;
494   }
495   blockedCells[blockedCellCount++] = (Vector2){mapX, mapY};
496   PathFindingMapUpdate(blockedCellCount, blockedCells);
497 
498   TowerDraw();
499   EnemyDraw();
500   ProjectileDraw();
501   ParticleDraw();
502   DrawEnemyPaths(level);
503 
504   // let the tower float up and down. Consider this height in the spring simulation as well
505   float towerFloatHeight = sinf(gameTime.time * 4.0f) * 0.2f + 0.3f;
506 
507   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
508   {
509     // The bouncing spring needs a bit of outro time to look nice and complete. 
510     // So we scale the time so that the first 2/3rd of the placing phase handles the motion
511     // and the last 1/3rd is the outro physics (bouncing)
512     float t = fminf(1.0f, level->placementTimer / placementDuration * 1.5f);
513     // let towerFloatHeight describe parabola curve, starting at towerFloatHeight and ending at 0
514     float linearBlendHeight = (1.0f - t) * towerFloatHeight;
515     float parabola = (1.0f - ((t - 0.5f) * (t - 0.5f) * 4.0f)) * 2.0f;
516     towerFloatHeight = linearBlendHeight + parabola;
517   }
518 
519   SimulateTowerPlacementBehavior(level, towerFloatHeight, mapX, mapY);
520   
521   rlPushMatrix();
522   rlTranslatef(level->placementTransitionPosition.x, 0, level->placementTransitionPosition.y);
523 
524   rlPushMatrix();
525   rlTranslatef(0.0f, towerFloatHeight, 0.0f);
526   // calculate x and z rotation to align the model with the spring
527   Vector3 position = {level->placementTransitionPosition.x, towerFloatHeight, level->placementTransitionPosition.y};
528   Vector3 towerUp = Vector3Subtract(level->placementTowerSpring.position, position);
529   Vector3 rotationAxis = Vector3CrossProduct(towerUp, (Vector3){0, 1, 0});
530   float angle = acosf(Vector3DotProduct(towerUp, (Vector3){0, 1, 0}) / Vector3Length(towerUp)) * RAD2DEG;
531   float springLength = Vector3Length(towerUp);
532   float towerStretch = fminf(fmaxf(springLength, 0.5f), 4.5f);
533   float towerSquash = 1.0f / towerStretch;
534   rlRotatef(-angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
535   rlScalef(towerSquash, towerStretch, towerSquash);
536   Tower dummy = {
537     .towerType = level->placementMode,
538   };
539   TowerDrawSingle(dummy);
540   rlPopMatrix();
541 
542   // draw a shadow for the tower
543   float umbrasize = 0.8 + sqrtf(towerFloatHeight);
544   DrawCube((Vector3){0.0f, 0.05f, 0.0f}, umbrasize, 0.0f, umbrasize, (Color){0, 0, 0, 32});
545   DrawCube((Vector3){0.0f, 0.075f, 0.0f}, 0.85f, 0.0f, 0.85f, (Color){0, 0, 0, 64});
546 
547 
548   float bounce = sinf(gameTime.time * 8.0f) * 0.5f + 0.5f;
549   float offset = fmaxf(0.0f, bounce - 0.4f) * 0.35f + 0.7f;
550   float squeeze = -fminf(0.0f, bounce - 0.4f) * 0.7f + 0.7f;
551   float stretch = fmaxf(0.0f, bounce - 0.3f) * 0.5f + 0.8f;
552   
553   DrawModelEx(greenArrowModel, (Vector3){ offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 90, (Vector3){squeeze, 1.0f, stretch}, WHITE);
554   DrawModelEx(greenArrowModel, (Vector3){-offset, 0.0f,  0.0f}, (Vector3){0, 1, 0}, 270, (Vector3){squeeze, 1.0f, stretch}, WHITE);
555   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f,  offset}, (Vector3){0, 1, 0}, 0, (Vector3){squeeze, 1.0f, stretch}, WHITE);
556   DrawModelEx(greenArrowModel, (Vector3){ 0.0f, 0.0f, -offset}, (Vector3){0, 1, 0}, 180, (Vector3){squeeze, 1.0f, stretch}, WHITE);
557   rlPopMatrix();
558 
559   guiState.isBlocked = 0;
560 
561   EndMode3D();
562 
563   TowerDrawHealthBars(level->camera);
564 
565   if (level->placementPhase == PLACEMENT_PHASE_PLACING)
566   {
567     if (level->placementTimer > placementDuration)
568     {
569         TowerTryAdd(level->placementMode, mapX, mapY);
570         level->playerGold -= GetTowerCosts(level->placementMode);
571         level->nextState = LEVEL_STATE_BUILDING;
572         level->placementMode = TOWER_TYPE_NONE;
573     }
574   }
575   else
576   {   
577     if (Button("Cancel", 20, GetScreenHeight() - 40, 160, 30, 0))
578     {
579       level->nextState = LEVEL_STATE_BUILDING;
580       level->placementMode = TOWER_TYPE_NONE;
581       TraceLog(LOG_INFO, "Cancel building");
582     }
583     
584     if (TowerGetAt(mapX, mapY) == 0 &&  Button("Build", GetScreenWidth() - 180, GetScreenHeight() - 40, 160, 30, 0))
585     {
586       level->placementPhase = PLACEMENT_PHASE_PLACING;
587       level->placementTimer = 0.0f;
588     }
589   }
590 }
591 
592 void DrawLevelBuildingState(Level *level)
593 {
594   BeginMode3D(level->camera);
595   DrawLevelGround(level);
596 
597   PathFindingMapUpdate(0, 0);
598   TowerDraw();
599   EnemyDraw();
600   ProjectileDraw();
601   ParticleDraw();
602   DrawEnemyPaths(level);
603 
604   guiState.isBlocked = 0;
605 
606   EndMode3D();
607 
608   TowerDrawHealthBars(level->camera);
609 
610   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
611   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_ARCHER, "Archer");
612   DrawBuildingBuildButton(level, 10, 90, 110, 30, TOWER_TYPE_BALLISTA, "Ballista");
613   DrawBuildingBuildButton(level, 10, 130, 110, 30, TOWER_TYPE_CATAPULT, "Catapult");
614 
615   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
616   {
617     level->nextState = LEVEL_STATE_RESET;
618   }
619   
620   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
621   {
622     level->nextState = LEVEL_STATE_BATTLE;
623   }
624 
625   const char *text = "Building phase";
626   int textWidth = MeasureText(text, 20);
627   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
628 }
629 
630 void InitBattleStateConditions(Level *level)
631 {
632   level->state = LEVEL_STATE_BATTLE;
633   level->nextState = LEVEL_STATE_NONE;
634   level->waveEndTimer = 0.0f;
635   for (int i = 0; i < 10; i++)
636   {
637     EnemyWave *wave = &level->waves[i];
638     wave->spawned = 0;
639     wave->timeToSpawnNext = wave->delay;
640   }
641 }
642 
643 void DrawLevelBattleState(Level *level)
644 {
645   BeginMode3D(level->camera);
646   DrawLevelGround(level);
647   TowerDraw();
648   EnemyDraw();
649   ProjectileDraw();
650   ParticleDraw();
651   guiState.isBlocked = 0;
652   EndMode3D();
653 
654   EnemyDrawHealthbars(level->camera);
655   TowerDrawHealthBars(level->camera);
656 
657   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
658   {
659     level->nextState = LEVEL_STATE_RESET;
660   }
661 
662   int maxCount = 0;
663   int remainingCount = 0;
664   for (int i = 0; i < 10; i++)
665   {
666     EnemyWave *wave = &level->waves[i];
667     if (wave->wave != level->currentWave)
668     {
669       continue;
670     }
671     maxCount += wave->count;
672     remainingCount += wave->count - wave->spawned;
673   }
674   int aliveCount = EnemyCount();
675   remainingCount += aliveCount;
676 
677   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
678   int textWidth = MeasureText(text, 20);
679   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
680 }
681 
682 void DrawLevel(Level *level)
683 {
684   switch (level->state)
685   {
686     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
687     case LEVEL_STATE_BUILDING_PLACEMENT: DrawLevelBuildingPlacementState(level); break;
688     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
689     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
690     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
691     default: break;
692   }
693 
694   DrawLevelHud(level);
695 }
696 
697 void UpdateLevel(Level *level)
698 {
699   if (level->state == LEVEL_STATE_BATTLE)
700   {
701     int activeWaves = 0;
702     for (int i = 0; i < 10; i++)
703     {
704       EnemyWave *wave = &level->waves[i];
705       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
706       {
707         continue;
708       }
709       activeWaves++;
710       wave->timeToSpawnNext -= gameTime.deltaTime;
711       if (wave->timeToSpawnNext <= 0.0f)
712       {
713         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
714         if (enemy)
715         {
716           wave->timeToSpawnNext = wave->interval;
717           wave->spawned++;
718         }
719       }
720     }
721     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
722       level->waveEndTimer += gameTime.deltaTime;
723       if (level->waveEndTimer >= 2.0f)
724       {
725         level->nextState = LEVEL_STATE_LOST_WAVE;
726       }
727     }
728     else if (activeWaves == 0 && EnemyCount() == 0)
729     {
730       level->waveEndTimer += gameTime.deltaTime;
731       if (level->waveEndTimer >= 2.0f)
732       {
733         level->nextState = LEVEL_STATE_WON_WAVE;
734       }
735     }
736   }
737 
738   PathFindingMapUpdate(0, 0);
739   EnemyUpdate();
740   TowerUpdate();
741   ProjectileUpdate();
742   ParticleUpdate();
743 
744   if (level->nextState == LEVEL_STATE_RESET)
745   {
746     InitLevel(level);
747   }
748   
749   if (level->nextState == LEVEL_STATE_BATTLE)
750   {
751     InitBattleStateConditions(level);
752   }
753   
754   if (level->nextState == LEVEL_STATE_WON_WAVE)
755   {
756     level->currentWave++;
757     level->state = LEVEL_STATE_WON_WAVE;
758   }
759   
760   if (level->nextState == LEVEL_STATE_LOST_WAVE)
761   {
762     level->state = LEVEL_STATE_LOST_WAVE;
763   }
764 
765   if (level->nextState == LEVEL_STATE_BUILDING)
766   {
767     level->state = LEVEL_STATE_BUILDING;
768   }
769 
770   if (level->nextState == LEVEL_STATE_BUILDING_PLACEMENT)
771   {
772     level->state = LEVEL_STATE_BUILDING_PLACEMENT;
773     level->placementTransitionPosition = (Vector2){
774       level->placementX, level->placementY};
775     // initialize the spring to the current position
776     level->placementTowerSpring = (PhysicsPoint){
777       .position = (Vector3){level->placementX, 8.0f, level->placementY},
778       .velocity = (Vector3){0.0f, 0.0f, 0.0f},
779     };
780     level->placementPhase = PLACEMENT_PHASE_STARTING;
781     level->placementTimer = 0.0f;
782   }
783 
784   if (level->nextState == LEVEL_STATE_WON_LEVEL)
785   {
786     // make something of this later
787     InitLevel(level);
788   }
789 
790   level->nextState = LEVEL_STATE_NONE;
791 }
792 
793 float nextSpawnTime = 0.0f;
794 
795 void ResetGame()
796 {
797   InitLevel(currentLevel);
798 }
799 
800 void InitGame()
801 {
802   TowerInit();
803   EnemyInit();
804   ProjectileInit();
805   ParticleInit();
806   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
807 
808   currentLevel = levels;
809   InitLevel(currentLevel);
810 }
811 
812 //# Immediate GUI functions
813 
814 void DrawHealthBar(Camera3D camera, Vector3 position, Vector2 screenOffset, float healthRatio, Color barColor, float healthBarWidth)
815 {
816   const float healthBarHeight = 6.0f;
817   const float healthBarOffset = 15.0f;
818   const float inset = 2.0f;
819   const float innerWidth = healthBarWidth - inset * 2;
820   const float innerHeight = healthBarHeight - inset * 2;
821 
822   Vector2 screenPos = GetWorldToScreen(position, camera);
823   screenPos = Vector2Add(screenPos, screenOffset);
824   float centerX = screenPos.x - healthBarWidth * 0.5f;
825   float topY = screenPos.y - healthBarOffset;
826   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
827   float healthWidth = innerWidth * healthRatio;
828   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
829 }
830 
831 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
832 {
833   Rectangle bounds = {x, y, width, height};
834   int isPressed = 0;
835   int isSelected = state && state->isSelected;
836   int isDisabled = state && state->isDisabled;
837   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
838   {
839     Color color = isSelected ? DARKGRAY : GRAY;
840     DrawRectangle(x, y, width, height, color);
841     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
842     {
843       isPressed = 1;
844     }
845     guiState.isBlocked = 1;
846   }
847   else
848   {
849     Color color = isSelected ? WHITE : LIGHTGRAY;
850     DrawRectangle(x, y, width, height, color);
851   }
852   Font font = GetFontDefault();
853   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
854   Color textColor = isDisabled ? GRAY : BLACK;
855   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
856   return isPressed;
857 }
858 
859 //# Main game loop
860 
861 void GameUpdate()
862 {
863   UpdateLevel(currentLevel);
864 }
865 
866 int main(void)
867 {
868   int screenWidth, screenHeight;
869   GetPreferredSize(&screenWidth, &screenHeight);
870   InitWindow(screenWidth, screenHeight, "Tower defense");
871   float gamespeed = 1.0f;
872   SetTargetFPS(30);
873 
874   LoadAssets();
875   InitGame();
876 
877   float pause = 1.0f;
878 
879   while (!WindowShouldClose())
880   {
881     if (IsPaused()) {
882       // canvas is not visible in browser - do nothing
883       continue;
884     }
885 
886     if (IsKeyPressed(KEY_T))
887     {
888       gamespeed += 0.1f;
889       if (gamespeed > 1.05f) gamespeed = 0.1f;
890     }
891 
892     if (IsKeyPressed(KEY_P))
893     {
894       pause = pause > 0.5f ? 0.0f : 1.0f;
895     }
896 
897     float dt = GetFrameTime() * gamespeed * pause;
898     // cap maximum delta time to 0.1 seconds to prevent large time steps
899     if (dt > 0.1f) dt = 0.1f;
900     gameTime.time += dt;
901     gameTime.deltaTime = dt;
902     gameTime.frameCount += 1;
903 
904     float fixedTimeDiff = gameTime.time - gameTime.fixedTimeStart;
905     gameTime.fixedStepCount = (uint8_t)(fixedTimeDiff / gameTime.fixedDeltaTime);
906 
907     BeginDrawing();
908     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
909 
910     GameUpdate();
911     DrawLevel(currentLevel);
912 
913     DrawText(TextFormat("Speed: %.1f", gamespeed), GetScreenWidth() - 180, 60, 20, WHITE);
914     EndDrawing();
915 
916     gameTime.fixedTimeStart += gameTime.fixedStepCount * gameTime.fixedDeltaTime;
917   }
918 
919   CloseWindow();
920 
921   return 0;
922 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 #include <rlgl.h>
  6 
  7 EnemyClassConfig enemyClassConfigs[] = {
  8     [ENEMY_TYPE_MINION] = {
  9       .health = 10.0f, 
 10       .speed = 0.6f, 
 11       .radius = 0.25f, 
 12       .maxAcceleration = 1.0f,
 13       .explosionDamage = 1.0f,
 14       .requiredContactTime = 0.5f,
 15       .explosionRange = 1.0f,
 16       .explosionPushbackPower = 0.25f,
 17       .goldValue = 1,
 18     },
 19     [ENEMY_TYPE_RUNNER] = {
 20       .health = 5.0f, 
 21       .speed = 1.0f, 
 22       .radius = 0.25f, 
 23       .maxAcceleration = 2.0f,
 24       .explosionDamage = 1.0f,
 25       .requiredContactTime = 0.5f,
 26       .explosionRange = 1.0f,
 27       .explosionPushbackPower = 0.25f,
 28       .goldValue = 2,
 29     },
 30     [ENEMY_TYPE_SHIELD] = {
 31       .health = 8.0f, 
 32       .speed = 0.5f, 
 33       .radius = 0.25f, 
 34       .maxAcceleration = 1.0f,
 35       .explosionDamage = 2.0f,
 36       .requiredContactTime = 0.5f,
 37       .explosionRange = 1.0f,
 38       .explosionPushbackPower = 0.25f,
 39       .goldValue = 3,
 40       .shieldDamageAbsorption = 4.0f,
 41       .shieldHealth = 25.0f,
 42     },
 43     [ENEMY_TYPE_BOSS] = {
 44       .health = 50.0f, 
 45       .speed = 0.4f, 
 46       .radius = 0.25f, 
 47       .maxAcceleration = 1.0f,
 48       .explosionDamage = 5.0f,
 49       .requiredContactTime = 0.5f,
 50       .explosionRange = 1.0f,
 51       .explosionPushbackPower = 0.25f,
 52       .goldValue = 10,
 53     },
 54 };
 55 
 56 Enemy enemies[ENEMY_MAX_COUNT];
 57 int enemyCount = 0;
 58 
 59 SpriteUnit enemySprites[] = {
 60     [ENEMY_TYPE_MINION] = {
 61       .animations[0] = {
 62         .srcRect = {0, 17, 16, 15},
 63         .offset = {8.0f, 0.0f},
 64         .frameCount = 6,
 65         .frameDuration = 0.1f,
 66       },
 67       .animations[1] = {
 68         .srcRect = {1, 33, 15, 14},
 69         .offset = {7.0f, 0.0f},
 70         .frameCount = 6,
 71         .frameWidth = 16,
 72         .frameDuration = 0.1f,
 73       },
 74     },
 75     [ENEMY_TYPE_RUNNER] = {
 76       .scale = 0.75f,
 77       .animations[0] = {
 78         .srcRect = {0, 17, 16, 15},
 79         .offset = {8.0f, 0.0f},
 80         .frameCount = 6,
 81         .frameDuration = 0.1f,
 82       },
 83     },
 84     [ENEMY_TYPE_SHIELD] = {
 85       .animations[0] = {
 86         .srcRect = {0, 17, 16, 15},
 87         .offset = {8.0f, 0.0f},
 88         .frameCount = 6,
 89         .frameDuration = 0.1f,
 90       },
 91       .animations[1] = {
 92         .animationId = SPRITE_UNIT_PHASE_WEAPON_SHIELD,
 93         .srcRect = {99, 17, 10, 11},
 94         .offset = {7.0f, 0.0f},
 95       },
 96     },
 97     [ENEMY_TYPE_BOSS] = {
 98       .scale = 1.5f,
 99       .animations[0] = {
100         .srcRect = {0, 17, 16, 15},
101         .offset = {8.0f, 0.0f},
102         .frameCount = 6,
103         .frameDuration = 0.1f,
104       },
105       .animations[1] = {
106         .srcRect = {97, 29, 14, 7},
107         .offset = {7.0f, -9.0f},
108       },
109     },
110 };
111 
112 void EnemyInit()
113 {
114   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
115   {
116     enemies[i] = (Enemy){0};
117   }
118   enemyCount = 0;
119 }
120 
121 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
122 {
123   return enemyClassConfigs[enemy->enemyType].speed;
124 }
125 
126 float EnemyGetMaxHealth(Enemy *enemy)
127 {
128   return enemyClassConfigs[enemy->enemyType].health;
129 }
130 
131 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
132 {
133   int16_t castleX = 0;
134   int16_t castleY = 0;
135   int16_t dx = castleX - currentX;
136   int16_t dy = castleY - currentY;
137   if (dx == 0 && dy == 0)
138   {
139     *nextX = currentX;
140     *nextY = currentY;
141     return 1;
142   }
143   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
144 
145   if (gradient.x == 0 && gradient.y == 0)
146   {
147     *nextX = currentX;
148     *nextY = currentY;
149     return 1;
150   }
151 
152   if (fabsf(gradient.x) > fabsf(gradient.y))
153   {
154     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
155     *nextY = currentY;
156     return 0;
157   }
158   *nextX = currentX;
159   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
160   return 0;
161 }
162 
163 
164 // this function predicts the movement of the unit for the next deltaT seconds
165 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
166 {
167   const float pointReachedDistance = 0.25f;
168   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
169   const float maxSimStepTime = 0.015625f;
170   
171   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
172   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
173   int16_t nextX = enemy->nextX;
174   int16_t nextY = enemy->nextY;
175   Vector2 position = enemy->simPosition;
176   int passedCount = 0;
177   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
178   {
179     float stepTime = fminf(deltaT - t, maxSimStepTime);
180     Vector2 target = (Vector2){nextX, nextY};
181     float speed = Vector2Length(*velocity);
182     // draw the target position for debugging
183     //DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
184     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
185     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
186     {
187       // we reached the target position, let's move to the next waypoint
188       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
189       target = (Vector2){nextX, nextY};
190       // track how many waypoints we passed
191       passedCount++;
192     }
193     
194     // acceleration towards the target
195     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
196     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
197     *velocity = Vector2Add(*velocity, acceleration);
198 
199     // limit the speed to the maximum speed
200     if (speed > maxSpeed)
201     {
202       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
203     }
204 
205     // move the enemy
206     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
207   }
208 
209   if (waypointPassedCount)
210   {
211     (*waypointPassedCount) = passedCount;
212   }
213 
214   return position;
215 }
216 
217 void EnemyDraw()
218 {
219   rlDrawRenderBatchActive();
220   rlDisableDepthMask();
221   for (int i = 0; i < enemyCount; i++)
222   {
223     Enemy enemy = enemies[i];
224     if (enemy.enemyType == ENEMY_TYPE_NONE)
225     {
226       continue;
227     }
228 
229     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
230     
231     // don't draw any trails for now; might replace this with footprints later
232     // if (enemy.movePathCount > 0)
233     // {
234     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
235     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
236     // }
237     // for (int j = 1; j < enemy.movePathCount; j++)
238     // {
239     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
240     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
241     //   DrawLine3D(p, q, GREEN);
242     // }
243 
244     float shieldHealth = enemyClassConfigs[enemy.enemyType].shieldHealth;
245     int phase = 0;
246     if (shieldHealth > 0 && enemy.shieldDamage < shieldHealth)
247     {
248       phase = SPRITE_UNIT_PHASE_WEAPON_SHIELD;
249     }
250 
251     switch (enemy.enemyType)
252     {
253     case ENEMY_TYPE_MINION:
254     case ENEMY_TYPE_RUNNER:
255     case ENEMY_TYPE_SHIELD:
256     case ENEMY_TYPE_BOSS:
257       DrawSpriteUnit(enemySprites[enemy.enemyType], (Vector3){position.x, 0.0f, position.y}, 
258         enemy.walkedDistance, 0, phase);
259       break;
260     }
261   }
262   rlDrawRenderBatchActive();
263   rlEnableDepthMask();
264 }
265 
266 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
267 {
268   // damage the tower
269   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
270   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
271   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
272   float explosionRange2 = explosionRange * explosionRange;
273   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
274   // explode the enemy
275   if (tower->damage >= TowerGetMaxHealth(tower))
276   {
277     tower->towerType = TOWER_TYPE_NONE;
278   }
279 
280   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
281     explosionSource, 
282     (Vector3){0, 0.1f, 0}, (Vector3){1.0f, 1.0f, 1.0f}, 1.0f);
283 
284   enemy->enemyType = ENEMY_TYPE_NONE;
285 
286   // push back enemies & dealing damage
287   for (int i = 0; i < enemyCount; i++)
288   {
289     Enemy *other = &enemies[i];
290     if (other->enemyType == ENEMY_TYPE_NONE)
291     {
292       continue;
293     }
294     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
295     if (distanceSqr > 0 && distanceSqr < explosionRange2)
296     {
297       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
298       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
299       EnemyAddDamage(other, explosionDamge);
300     }
301   }
302 }
303 
304 void EnemyUpdate()
305 {
306   const float castleX = 0;
307   const float castleY = 0;
308   const float maxPathDistance2 = 0.25f * 0.25f;
309   
310   for (int i = 0; i < enemyCount; i++)
311   {
312     Enemy *enemy = &enemies[i];
313     if (enemy->enemyType == ENEMY_TYPE_NONE)
314     {
315       continue;
316     }
317 
318     int waypointPassedCount = 0;
319     Vector2 prevPosition = enemy->simPosition;
320     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
321     enemy->startMovingTime = gameTime.time;
322     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
323     // track path of unit
324     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
325     {
326       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
327       {
328         enemy->movePath[j] = enemy->movePath[j - 1];
329       }
330       enemy->movePath[0] = enemy->simPosition;
331       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
332       {
333         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
334       }
335     }
336 
337     if (waypointPassedCount > 0)
338     {
339       enemy->currentX = enemy->nextX;
340       enemy->currentY = enemy->nextY;
341       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
342         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
343       {
344         // enemy reached the castle; remove it
345         enemy->enemyType = ENEMY_TYPE_NONE;
346         continue;
347       }
348     }
349   }
350 
351   // handle collisions between enemies
352   for (int i = 0; i < enemyCount - 1; i++)
353   {
354     Enemy *enemyA = &enemies[i];
355     if (enemyA->enemyType == ENEMY_TYPE_NONE)
356     {
357       continue;
358     }
359     for (int j = i + 1; j < enemyCount; j++)
360     {
361       Enemy *enemyB = &enemies[j];
362       if (enemyB->enemyType == ENEMY_TYPE_NONE)
363       {
364         continue;
365       }
366       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
367       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
368       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
369       float radiusSum = radiusA + radiusB;
370       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
371       {
372         // collision
373         float distance = sqrtf(distanceSqr);
374         float overlap = radiusSum - distance;
375         // move the enemies apart, but softly; if we have a clog of enemies,
376         // moving them perfectly apart can cause them to jitter
377         float positionCorrection = overlap / 5.0f;
378         Vector2 direction = (Vector2){
379             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
380             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
381         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
382         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
383       }
384     }
385   }
386 
387   // handle collisions between enemies and towers
388   for (int i = 0; i < enemyCount; i++)
389   {
390     Enemy *enemy = &enemies[i];
391     if (enemy->enemyType == ENEMY_TYPE_NONE)
392     {
393       continue;
394     }
395     enemy->contactTime -= gameTime.deltaTime;
396     if (enemy->contactTime < 0.0f)
397     {
398       enemy->contactTime = 0.0f;
399     }
400 
401     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
402     // linear search over towers; could be optimized by using path finding tower map,
403     // but for now, we keep it simple
404     for (int j = 0; j < towerCount; j++)
405     {
406       Tower *tower = &towers[j];
407       if (tower->towerType == TOWER_TYPE_NONE)
408       {
409         continue;
410       }
411       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
412       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
413       if (distanceSqr > combinedRadius * combinedRadius)
414       {
415         continue;
416       }
417       // potential collision; square / circle intersection
418       float dx = tower->x - enemy->simPosition.x;
419       float dy = tower->y - enemy->simPosition.y;
420       float absDx = fabsf(dx);
421       float absDy = fabsf(dy);
422       Vector3 contactPoint = {0};
423       if (absDx <= 0.5f && absDx <= absDy) {
424         // vertical collision; push the enemy out horizontally
425         float overlap = enemyRadius + 0.5f - absDy;
426         if (overlap < 0.0f)
427         {
428           continue;
429         }
430         float direction = dy > 0.0f ? -1.0f : 1.0f;
431         enemy->simPosition.y += direction * overlap;
432         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
433       }
434       else if (absDy <= 0.5f && absDy <= absDx)
435       {
436         // horizontal collision; push the enemy out vertically
437         float overlap = enemyRadius + 0.5f - absDx;
438         if (overlap < 0.0f)
439         {
440           continue;
441         }
442         float direction = dx > 0.0f ? -1.0f : 1.0f;
443         enemy->simPosition.x += direction * overlap;
444         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
445       }
446       else
447       {
448         // possible collision with a corner
449         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
450         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
451         float cornerX = tower->x + cornerDX;
452         float cornerY = tower->y + cornerDY;
453         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
454         if (cornerDistanceSqr > enemyRadius * enemyRadius)
455         {
456           continue;
457         }
458         // push the enemy out along the diagonal
459         float cornerDistance = sqrtf(cornerDistanceSqr);
460         float overlap = enemyRadius - cornerDistance;
461         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
462         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
463         enemy->simPosition.x -= directionX * overlap;
464         enemy->simPosition.y -= directionY * overlap;
465         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
466       }
467 
468       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
469       {
470         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
471         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
472         {
473           EnemyTriggerExplode(enemy, tower, contactPoint);
474         }
475       }
476     }
477   }
478 }
479 
480 EnemyId EnemyGetId(Enemy *enemy)
481 {
482   return (EnemyId){enemy - enemies, enemy->generation};
483 }
484 
485 Enemy *EnemyTryResolve(EnemyId enemyId)
486 {
487   if (enemyId.index >= ENEMY_MAX_COUNT)
488   {
489     return 0;
490   }
491   Enemy *enemy = &enemies[enemyId.index];
492   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
493   {
494     return 0;
495   }
496   return enemy;
497 }
498 
499 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
500 {
501   Enemy *spawn = 0;
502   for (int i = 0; i < enemyCount; i++)
503   {
504     Enemy *enemy = &enemies[i];
505     if (enemy->enemyType == ENEMY_TYPE_NONE)
506     {
507       spawn = enemy;
508       break;
509     }
510   }
511 
512   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
513   {
514     spawn = &enemies[enemyCount++];
515   }
516 
517   if (spawn)
518   {
519     *spawn = (Enemy){0};
520     spawn->currentX = currentX;
521     spawn->currentY = currentY;
522     spawn->nextX = currentX;
523     spawn->nextY = currentY;
524     spawn->simPosition = (Vector2){currentX, currentY};
525     spawn->simVelocity = (Vector2){0, 0};
526     spawn->enemyType = enemyType;
527     spawn->startMovingTime = gameTime.time;
528     spawn->damage = 0.0f;
529     spawn->futureDamage = 0.0f;
530     spawn->generation++;
531     spawn->movePathCount = 0;
532     spawn->walkedDistance = 0.0f;
533   }
534 
535   return spawn;
536 }
537 
538 int EnemyAddDamageRange(Vector2 position, float range, float damage)
539 {
540   int count = 0;
541   float range2 = range * range;
542   for (int i = 0; i < enemyCount; i++)
543   {
544     Enemy *enemy = &enemies[i];
545     if (enemy->enemyType == ENEMY_TYPE_NONE)
546     {
547       continue;
548     }
549     float distance2 = Vector2DistanceSqr(position, enemy->simPosition);
550     if (distance2 <= range2)
551     {
552       EnemyAddDamage(enemy, damage);
553       count++;
554     }
555   }
556   return count;
557 }
558 
559 int EnemyAddDamage(Enemy *enemy, float damage)
560 {
561   float shieldHealth = enemyClassConfigs[enemy->enemyType].shieldHealth;
562   if (shieldHealth > 0.0f && enemy->shieldDamage < shieldHealth)
563   {
564     float shieldDamageAbsorption = enemyClassConfigs[enemy->enemyType].shieldDamageAbsorption;
565     float shieldDamage = fminf(fminf(shieldDamageAbsorption, damage), shieldHealth - enemy->shieldDamage);
566     enemy->shieldDamage += shieldDamage;
567     damage -= shieldDamage;
568   }
569   enemy->damage += damage;
570   if (enemy->damage >= EnemyGetMaxHealth(enemy))
571   {
572     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
573     enemy->enemyType = ENEMY_TYPE_NONE;
574     return 1;
575   }
576 
577   return 0;
578 }
579 
580 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
581 {
582   int16_t castleX = 0;
583   int16_t castleY = 0;
584   Enemy* closest = 0;
585   int16_t closestDistance = 0;
586   float range2 = range * range;
587   for (int i = 0; i < enemyCount; i++)
588   {
589     Enemy* enemy = &enemies[i];
590     if (enemy->enemyType == ENEMY_TYPE_NONE)
591     {
592       continue;
593     }
594     float maxHealth = EnemyGetMaxHealth(enemy) + enemyClassConfigs[enemy->enemyType].shieldHealth;
595     if (enemy->futureDamage >= maxHealth)
596     {
597       // ignore enemies that will die soon
598       continue;
599     }
600     int16_t dx = castleX - enemy->currentX;
601     int16_t dy = castleY - enemy->currentY;
602     int16_t distance = abs(dx) + abs(dy);
603     if (!closest || distance < closestDistance)
604     {
605       float tdx = towerX - enemy->currentX;
606       float tdy = towerY - enemy->currentY;
607       float tdistance2 = tdx * tdx + tdy * tdy;
608       if (tdistance2 <= range2)
609       {
610         closest = enemy;
611         closestDistance = distance;
612       }
613     }
614   }
615   return closest;
616 }
617 
618 int EnemyCount()
619 {
620   int count = 0;
621   for (int i = 0; i < enemyCount; i++)
622   {
623     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
624     {
625       count++;
626     }
627   }
628   return count;
629 }
630 
631 void EnemyDrawHealthbars(Camera3D camera)
632 {
633   for (int i = 0; i < enemyCount; i++)
634   {
635     Enemy *enemy = &enemies[i];
636     
637     float maxShieldHealth = enemyClassConfigs[enemy->enemyType].shieldHealth;
638     if (maxShieldHealth > 0.0f && enemy->shieldDamage < maxShieldHealth && enemy->shieldDamage > 0.0f)
639     {
640       float shieldHealth = maxShieldHealth - enemy->shieldDamage;
641       float shieldHealthRatio = shieldHealth / maxShieldHealth;
642       Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
643       DrawHealthBar(camera, position, (Vector2) {.y = -4}, shieldHealthRatio, BLUE, 20.0f);
644     }
645 
646     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
647     {
648       continue;
649     }
650     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
651     float maxHealth = EnemyGetMaxHealth(enemy);
652     float health = maxHealth - enemy->damage;
653     float healthRatio = health / maxHealth;
654     
655     DrawHealthBar(camera, position, Vector2Zero(), healthRatio, GREEN, 15.0f);
656   }
657 }  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 //# Declarations
 10 
 11 typedef struct PhysicsPoint
 12 {
 13   Vector3 position;
 14   Vector3 velocity;
 15 } PhysicsPoint;
 16 
 17 #define ENEMY_MAX_PATH_COUNT 8
 18 #define ENEMY_MAX_COUNT 400
 19 #define ENEMY_TYPE_NONE 0
 20 
 21 #define ENEMY_TYPE_MINION 1
 22 #define ENEMY_TYPE_RUNNER 2
 23 #define ENEMY_TYPE_SHIELD 3
 24 #define ENEMY_TYPE_BOSS 4
 25 
 26 #define PARTICLE_MAX_COUNT 400
 27 #define PARTICLE_TYPE_NONE 0
 28 #define PARTICLE_TYPE_EXPLOSION 1
 29 
 30 typedef struct Particle
 31 {
 32   uint8_t particleType;
 33   float spawnTime;
 34   float lifetime;
 35   Vector3 position;
 36   Vector3 velocity;
 37   Vector3 scale;
 38 } Particle;
 39 
 40 #define TOWER_MAX_COUNT 400
 41 enum TowerType
 42 {
 43   TOWER_TYPE_NONE,
 44   TOWER_TYPE_BASE,
 45   TOWER_TYPE_ARCHER,
 46   TOWER_TYPE_BALLISTA,
 47   TOWER_TYPE_CATAPULT,
 48   TOWER_TYPE_WALL,
 49   TOWER_TYPE_COUNT
 50 };
 51 
 52 typedef struct HitEffectConfig
 53 {
 54   float damage;
 55   float areaDamageRadius;
 56   float pushbackPowerDistance;
 57 } HitEffectConfig;
 58 
 59 typedef struct TowerTypeConfig
 60 {
 61   float cooldown;
 62   float range;
 63   float projectileSpeed;
 64   
 65   uint8_t cost;
 66   uint8_t projectileType;
 67   uint16_t maxHealth;
 68 
 69   HitEffectConfig hitEffect;
 70 } TowerTypeConfig;
 71 
 72 typedef struct Tower
 73 {
 74   int16_t x, y;
 75   uint8_t towerType;
 76   Vector2 lastTargetPosition;
 77   float cooldown;
 78   float damage;
 79 } Tower;
 80 
 81 typedef struct GameTime
 82 {
 83   float time;
 84   float deltaTime;
 85   uint32_t frameCount;
 86 
 87   float fixedDeltaTime;
 88   // leaving the fixed time stepping to the update functions,
 89   // we need to know the fixed time at the start of the frame
 90   float fixedTimeStart;
 91   // and the number of fixed steps that we have to make this frame
 92   // The fixedTime is fixedTimeStart + n * fixedStepCount
 93   uint8_t fixedStepCount;
 94 } GameTime;
 95 
 96 typedef struct ButtonState {
 97   char isSelected;
 98   char isDisabled;
 99 } ButtonState;
100 
101 typedef struct GUIState {
102   int isBlocked;
103 } GUIState;
104 
105 typedef enum LevelState
106 {
107   LEVEL_STATE_NONE,
108   LEVEL_STATE_BUILDING,
109   LEVEL_STATE_BUILDING_PLACEMENT,
110   LEVEL_STATE_BATTLE,
111   LEVEL_STATE_WON_WAVE,
112   LEVEL_STATE_LOST_WAVE,
113   LEVEL_STATE_WON_LEVEL,
114   LEVEL_STATE_RESET,
115 } LevelState;
116 
117 typedef struct EnemyWave {
118   uint8_t enemyType;
119   uint8_t wave;
120   uint16_t count;
121   float interval;
122   float delay;
123   Vector2 spawnPosition;
124 
125   uint16_t spawned;
126   float timeToSpawnNext;
127 } EnemyWave;
128 
129 #define ENEMY_MAX_WAVE_COUNT 10
130 
131 typedef enum PlacementPhase
132 {
133   PLACEMENT_PHASE_STARTING,
134   PLACEMENT_PHASE_MOVING,
135   PLACEMENT_PHASE_PLACING,
136 } PlacementPhase;
137 
138 typedef struct Level
139 {
140   int seed;
141   LevelState state;
142   LevelState nextState;
143   Camera3D camera;
144   int placementMode;
145   PlacementPhase placementPhase;
146   float placementTimer;
147   int16_t placementX;
148   int16_t placementY;
149   Vector2 placementTransitionPosition;
150   PhysicsPoint placementTowerSpring;
151 
152   int initialGold;
153   int playerGold;
154 
155   EnemyWave waves[ENEMY_MAX_WAVE_COUNT];
156   int currentWave;
157   float waveEndTimer;
158 } Level;
159 
160 typedef struct DeltaSrc
161 {
162   char x, y;
163 } DeltaSrc;
164 
165 typedef struct PathfindingMap
166 {
167   int width, height;
168   float scale;
169   float *distances;
170   long *towerIndex; 
171   DeltaSrc *deltaSrc;
172   float maxDistance;
173   Matrix toMapSpace;
174   Matrix toWorldSpace;
175 } PathfindingMap;
176 
177 // when we execute the pathfinding algorithm, we need to store the active nodes
178 // in a queue. Each node has a position, a distance from the start, and the
179 // position of the node that we came from.
180 typedef struct PathfindingNode
181 {
182   int16_t x, y, fromX, fromY;
183   float distance;
184 } PathfindingNode;
185 
186 typedef struct EnemyId
187 {
188   uint16_t index;
189   uint16_t generation;
190 } EnemyId;
191 
192 typedef struct EnemyClassConfig
193 {
194   float speed;
195   float health;
196   float shieldHealth;
197   float shieldDamageAbsorption;
198   float radius;
199   float maxAcceleration;
200   float requiredContactTime;
201   float explosionDamage;
202   float explosionRange;
203   float explosionPushbackPower;
204   int goldValue;
205 } EnemyClassConfig;
206 
207 typedef struct Enemy
208 {
209   int16_t currentX, currentY;
210   int16_t nextX, nextY;
211   Vector2 simPosition;
212   Vector2 simVelocity;
213   uint16_t generation;
214   float walkedDistance;
215   float startMovingTime;
216   float damage, futureDamage;
217   float shieldDamage;
218   float contactTime;
219   uint8_t enemyType;
220   uint8_t movePathCount;
221   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
222 } Enemy;
223 
224 // a unit that uses sprites to be drawn
225 #define SPRITE_UNIT_ANIMATION_COUNT 6
226 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 1
227 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 2
228 #define SPRITE_UNIT_PHASE_WEAPON_SHIELD 3
229 
230 typedef struct SpriteAnimation
231 {
232   Rectangle srcRect;
233   Vector2 offset;
234   uint8_t animationId;
235   uint8_t frameCount;
236   uint8_t frameWidth;
237   float frameDuration;
238 } SpriteAnimation;
239 
240 typedef struct SpriteUnit
241 {
242   float scale;
243   SpriteAnimation animations[SPRITE_UNIT_ANIMATION_COUNT];
244 } SpriteUnit;
245 
246 #define PROJECTILE_MAX_COUNT 1200
247 #define PROJECTILE_TYPE_NONE 0
248 #define PROJECTILE_TYPE_ARROW 1
249 #define PROJECTILE_TYPE_CATAPULT 2
250 #define PROJECTILE_TYPE_BALLISTA 3
251 
252 typedef struct Projectile
253 {
254   uint8_t projectileType;
255   float shootTime;
256   float arrivalTime;
257   float distance;
258   Vector3 position;
259   Vector3 target;
260   Vector3 directionNormal;
261   EnemyId targetEnemy;
262   HitEffectConfig hitEffectConfig;
263 } Projectile;
264 
265 //# Function declarations
266 float TowerGetMaxHealth(Tower *tower);
267 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
268 int EnemyAddDamageRange(Vector2 position, float range, float damage);
269 int EnemyAddDamage(Enemy *enemy, float damage);
270 
271 //# Enemy functions
272 void EnemyInit();
273 void EnemyDraw();
274 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
275 void EnemyUpdate();
276 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
277 float EnemyGetMaxHealth(Enemy *enemy);
278 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
279 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
280 EnemyId EnemyGetId(Enemy *enemy);
281 Enemy *EnemyTryResolve(EnemyId enemyId);
282 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
283 int EnemyAddDamage(Enemy *enemy, float damage);
284 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
285 int EnemyCount();
286 void EnemyDrawHealthbars(Camera3D camera);
287 
288 //# Tower functions
289 void TowerInit();
290 Tower *TowerGetAt(int16_t x, int16_t y);
291 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
292 Tower *GetTowerByType(uint8_t towerType);
293 int GetTowerCosts(uint8_t towerType);
294 float TowerGetMaxHealth(Tower *tower);
295 void TowerDraw();
296 void TowerDrawSingle(Tower tower);
297 void TowerUpdate();
298 void TowerDrawHealthBars(Camera3D camera);
299 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
300 
301 //# Particles
302 void ParticleInit();
303 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime);
304 void ParticleUpdate();
305 void ParticleDraw();
306 
307 //# Projectiles
308 void ProjectileInit();
309 void ProjectileDraw();
310 void ProjectileUpdate();
311 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig);
312 
313 //# Pathfinding map
314 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
315 float PathFindingGetDistance(int mapX, int mapY);
316 Vector2 PathFindingGetGradient(Vector3 world);
317 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
318 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells);
319 void PathFindingMapDraw();
320 
321 //# UI
322 void DrawHealthBar(Camera3D camera, Vector3 position, Vector2 screenOffset, float healthRatio, Color barColor, float healthBarWidth);
323 
324 //# Level
325 void DrawLevelGround(Level *level);
326 void DrawEnemyPath(Level *level, Color arrowColor);
327 
328 //# variables
329 extern Level *currentLevel;
330 extern Enemy enemies[ENEMY_MAX_COUNT];
331 extern int enemyCount;
332 extern EnemyClassConfig enemyClassConfigs[];
333 
334 extern GUIState guiState;
335 extern GameTime gameTime;
336 extern Tower towers[TOWER_MAX_COUNT];
337 extern int towerCount;
338 
339 extern Texture2D palette, spriteSheet;
340 
341 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static TowerTypeConfig towerTypeConfigs[TOWER_TYPE_COUNT] = {
  5     [TOWER_TYPE_BASE] = {
  6         .maxHealth = 10,
  7     },
  8     [TOWER_TYPE_ARCHER] = {
  9         .cooldown = 0.5f,
 10         .range = 3.0f,
 11         .cost = 6,
 12         .maxHealth = 10,
 13         .projectileSpeed = 4.0f,
 14         .projectileType = PROJECTILE_TYPE_ARROW,
 15         .hitEffect = {
 16           .damage = 3.0f,
 17         }
 18     },
 19     [TOWER_TYPE_BALLISTA] = {
 20         .cooldown = 1.5f,
 21         .range = 6.0f,
 22         .cost = 9,
 23         .maxHealth = 10,
 24         .projectileSpeed = 10.0f,
 25         .projectileType = PROJECTILE_TYPE_BALLISTA,
 26         .hitEffect = {
 27           .damage = 8.0f,
 28           .pushbackPowerDistance = 0.25f,
 29         }
 30     },
 31     [TOWER_TYPE_CATAPULT] = {
 32         .cooldown = 1.7f,
 33         .range = 5.0f,
 34         .cost = 10,
 35         .maxHealth = 10,
 36         .projectileSpeed = 3.0f,
 37         .projectileType = PROJECTILE_TYPE_CATAPULT,
 38         .hitEffect = {
 39           .damage = 2.0f,
 40           .areaDamageRadius = 1.75f,
 41         }
 42     },
 43     [TOWER_TYPE_WALL] = {
 44         .cost = 2,
 45         .maxHealth = 10,
 46     },
 47 };
 48 
 49 Tower towers[TOWER_MAX_COUNT];
 50 int towerCount = 0;
 51 
 52 Model towerModels[TOWER_TYPE_COUNT];
 53 
 54 // definition of our archer unit
 55 SpriteUnit archerUnit = {
 56   .animations[0] = {
 57     .srcRect = {0, 0, 16, 16},
 58     .offset = {7, 1},
 59     .frameCount = 1,
 60     .frameDuration = 0.0f,
 61   },
 62   .animations[1] = {
 63     .animationId = SPRITE_UNIT_PHASE_WEAPON_COOLDOWN,
 64     .srcRect = {16, 0, 6, 16},
 65     .offset = {8, 0},
 66   },
 67   .animations[2] = {
 68     .animationId = SPRITE_UNIT_PHASE_WEAPON_IDLE,
 69     .srcRect = {22, 0, 11, 16},
 70     .offset = {10, 0},
 71   },
 72 };
 73 
 74 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 75 {
 76   float unitScale = unit.scale == 0 ? 1.0f : unit.scale;
 77   float xScale = flip ? -1.0f : 1.0f;
 78   Camera3D camera = currentLevel->camera;
 79   float size = 0.5f * unitScale;
 80   // we want the sprite to face the camera, so we need to calculate the up vector
 81   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 82   Vector3 up = {0, 1, 0};
 83   Vector3 right = Vector3CrossProduct(forward, up);
 84   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 85   
 86   for (int i=0;i<SPRITE_UNIT_ANIMATION_COUNT;i++)
 87   {
 88     SpriteAnimation anim = unit.animations[i];
 89     if (anim.animationId != phase && anim.animationId != 0)
 90     {
 91       continue;
 92     }
 93     Rectangle srcRect = anim.srcRect;
 94     if (anim.frameCount > 1)
 95     {
 96       int w = anim.frameWidth > 0 ? anim.frameWidth : srcRect.width;
 97       srcRect.x += (int)(t / anim.frameDuration) % anim.frameCount * w;
 98     }
 99     Vector2 offset = { anim.offset.x / 16.0f * size, anim.offset.y / 16.0f * size * xScale };
100     Vector2 scale = { srcRect.width / 16.0f * size, srcRect.height / 16.0f * size };
101     
102     if (flip)
103     {
104       srcRect.x += srcRect.width;
105       srcRect.width = -srcRect.width;
106       offset.x = scale.x - offset.x;
107     }
108     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
109     // move the sprite slightly towards the camera to avoid z-fighting
110     position = Vector3Add(position, Vector3Scale(Vector3Normalize(forward), -0.01f));  
111   }
112 }
113 
114 void TowerInit()
115 {
116   for (int i = 0; i < TOWER_MAX_COUNT; i++)
117   {
118     towers[i] = (Tower){0};
119   }
120   towerCount = 0;
121 
122   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
123   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
124 
125   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
126   {
127     if (towerModels[i].materials)
128     {
129       // assign the palette texture to the material of the model (0 is not used afaik)
130       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
131     }
132   }
133 }
134 
135 static void TowerGunUpdate(Tower *tower)
136 {
137   TowerTypeConfig config = towerTypeConfigs[tower->towerType];
138   if (tower->cooldown <= 0.0f)
139   {
140     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, config.range);
141     if (enemy)
142     {
143       tower->cooldown = config.cooldown;
144       // shoot the enemy; determine future position of the enemy
145       float bulletSpeed = config.projectileSpeed;
146       Vector2 velocity = enemy->simVelocity;
147       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
148       Vector2 towerPosition = {tower->x, tower->y};
149       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
150       for (int i = 0; i < 8; i++) {
151         velocity = enemy->simVelocity;
152         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
153         float distance = Vector2Distance(towerPosition, futurePosition);
154         float eta2 = distance / bulletSpeed;
155         if (fabs(eta - eta2) < 0.01f) {
156           break;
157         }
158         eta = (eta2 + eta) * 0.5f;
159       }
160 
161       ProjectileTryAdd(config.projectileType, enemy, 
162         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
163         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
164         bulletSpeed, config.hitEffect);
165       enemy->futureDamage += config.hitEffect.damage;
166       tower->lastTargetPosition = futurePosition;
167     }
168   }
169   else
170   {
171     tower->cooldown -= gameTime.deltaTime;
172   }
173 }
174 
175 Tower *TowerGetAt(int16_t x, int16_t y)
176 {
177   for (int i = 0; i < towerCount; i++)
178   {
179     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
180     {
181       return &towers[i];
182     }
183   }
184   return 0;
185 }
186 
187 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
188 {
189   if (towerCount >= TOWER_MAX_COUNT)
190   {
191     return 0;
192   }
193 
194   Tower *tower = TowerGetAt(x, y);
195   if (tower)
196   {
197     return 0;
198   }
199 
200   tower = &towers[towerCount++];
201   tower->x = x;
202   tower->y = y;
203   tower->towerType = towerType;
204   tower->cooldown = 0.0f;
205   tower->damage = 0.0f;
206   return tower;
207 }
208 
209 Tower *GetTowerByType(uint8_t towerType)
210 {
211   for (int i = 0; i < towerCount; i++)
212   {
213     if (towers[i].towerType == towerType)
214     {
215       return &towers[i];
216     }
217   }
218   return 0;
219 }
220 
221 int GetTowerCosts(uint8_t towerType)
222 {
223   return towerTypeConfigs[towerType].cost;
224 }
225 
226 float TowerGetMaxHealth(Tower *tower)
227 {
228   return towerTypeConfigs[tower->towerType].maxHealth;
229 }
230 
231 void TowerDrawSingle(Tower tower)
232 {
233   if (tower.towerType == TOWER_TYPE_NONE)
234   {
235     return;
236   }
237 
238   switch (tower.towerType)
239   {
240   case TOWER_TYPE_ARCHER:
241     {
242       Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
243       Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
244       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
245       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
246         tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
247     }
248     break;
249   case TOWER_TYPE_BALLISTA:
250     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, BROWN);
251     break;
252   case TOWER_TYPE_CATAPULT:
253     DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, DARKGRAY);
254     break;
255   default:
256     if (towerModels[tower.towerType].materials)
257     {
258       DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
259     } else {
260       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
261     }
262     break;
263   }
264 }
265 
266 void TowerDraw()
267 {
268   for (int i = 0; i < towerCount; i++)
269   {
270     TowerDrawSingle(towers[i]);
271   }
272 }
273 
274 void TowerUpdate()
275 {
276   for (int i = 0; i < towerCount; i++)
277   {
278     Tower *tower = &towers[i];
279     switch (tower->towerType)
280     {
281     case TOWER_TYPE_CATAPULT:
282     case TOWER_TYPE_BALLISTA:
283     case TOWER_TYPE_ARCHER:
284       TowerGunUpdate(tower);
285       break;
286     }
287   }
288 }
289 
290 void TowerDrawHealthBars(Camera3D camera)
291 {
292   for (int i = 0; i < towerCount; i++)
293   {
294     Tower *tower = &towers[i];
295     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
296     {
297       continue;
298     }
299     
300     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
301     float maxHealth = TowerGetMaxHealth(tower);
302     float health = maxHealth - tower->damage;
303     float healthRatio = health / maxHealth;
304     
305     DrawHealthBar(camera, position, Vector2Zero(), healthRatio, GREEN, 35.0f);
306   }
307 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate(int blockedCellCount, Vector2 *blockedCells)
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < blockedCellCount; i++)
131   {
132     int16_t mapX, mapY;
133     if (!PathFindingFromWorldToMapPosition((Vector3){blockedCells[i].x, 0.0f, blockedCells[i].y}, &mapX, &mapY))
134     {
135       continue;
136     }
137     int index = mapY * width + mapX;
138     pathfindingMap.towerIndex[index] = -2;
139   }
140 
141   for (int i = 0; i < towerCount; i++)
142   {
143     Tower *tower = &towers[i];
144     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
145     {
146       continue;
147     }
148     int16_t mapX, mapY;
149     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
150     // this would not work correctly and needs to be refined to allow towers covering multiple cells
151     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
152     // one cell. For now.
153     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
154     {
155       continue;
156     }
157     int index = mapY * width + mapX;
158     pathfindingMap.towerIndex[index] = i;
159   }
160 
161   // we start at the castle and add the castle to the queue
162   pathfindingMap.maxDistance = 0.0f;
163   pathfindingNodeQueueCount = 0;
164   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
165   PathfindingNode *node = 0;
166   while ((node = PathFindingNodePop()))
167   {
168     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
169     {
170       continue;
171     }
172     int index = node->y * width + node->x;
173     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
174     {
175       continue;
176     }
177 
178     int deltaX = node->x - node->fromX;
179     int deltaY = node->y - node->fromY;
180     // even if the cell is blocked by a tower, we still may want to store the direction
181     // (though this might not be needed, IDK right now)
182     pathfindingMap.deltaSrc[index].x = (char) deltaX;
183     pathfindingMap.deltaSrc[index].y = (char) deltaY;
184 
185     // we skip nodes that are blocked by towers or by the provided blocked cells
186     if (pathfindingMap.towerIndex[index] != -1)
187     {
188       node->distance += 8.0f;
189     }
190     pathfindingMap.distances[index] = node->distance;
191     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
192     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
193     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
194     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
195     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
196   }
197 }
198 
199 void PathFindingMapDraw()
200 {
201   float cellSize = pathfindingMap.scale * 0.9f;
202   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
203   for (int x = 0; x < pathfindingMap.width; x++)
204   {
205     for (int y = 0; y < pathfindingMap.height; y++)
206     {
207       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
208       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
209       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
210       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
211       // animate the distance "wave" to show how the pathfinding algorithm expands
212       // from the castle
213       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
214       {
215         color = BLACK;
216       }
217       DrawCube(position, cellSize, 0.1f, cellSize, color);
218     }
219   }
220 }
221 
222 Vector2 PathFindingGetGradient(Vector3 world)
223 {
224   int16_t mapX, mapY;
225   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
226   {
227     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
228     return (Vector2){(float)-delta.x, (float)-delta.y};
229   }
230   // fallback to a simple gradient calculation
231   float n = PathFindingGetDistance(mapX, mapY - 1);
232   float s = PathFindingGetDistance(mapX, mapY + 1);
233   float w = PathFindingGetDistance(mapX - 1, mapY);
234   float e = PathFindingGetDistance(mapX + 1, mapY);
235   return (Vector2){w - e + 0.25f, n - s + 0.125f};
236 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 typedef struct ProjectileConfig
  8 {
  9   float arcFactor;
 10   Color color;
 11   Color trailColor;
 12 } ProjectileConfig;
 13 
 14 ProjectileConfig projectileConfigs[] = {
 15     [PROJECTILE_TYPE_ARROW] = {
 16         .arcFactor = 0.15f,
 17         .color = RED,
 18         .trailColor = BROWN,
 19     },
 20     [PROJECTILE_TYPE_CATAPULT] = {
 21         .arcFactor = 0.5f,
 22         .color = RED,
 23         .trailColor = GRAY,
 24     },
 25     [PROJECTILE_TYPE_BALLISTA] = {
 26         .arcFactor = 0.025f,
 27         .color = RED,
 28         .trailColor = BROWN,
 29     },
 30 };
 31 
 32 void ProjectileInit()
 33 {
 34   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 35   {
 36     projectiles[i] = (Projectile){0};
 37   }
 38 }
 39 
 40 void ProjectileDraw()
 41 {
 42   for (int i = 0; i < projectileCount; i++)
 43   {
 44     Projectile projectile = projectiles[i];
 45     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 46     {
 47       continue;
 48     }
 49     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 50     if (transition >= 1.0f)
 51     {
 52       continue;
 53     }
 54 
 55     ProjectileConfig config = projectileConfigs[projectile.projectileType];
 56     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 57     {
 58       float t = transition + transitionOffset * 0.3f;
 59       if (t > 1.0f)
 60       {
 61         break;
 62       }
 63       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 64       Color color = config.color;
 65       color = ColorLerp(config.trailColor, config.color, transitionOffset * transitionOffset);
 66       // fake a ballista flight path using parabola equation
 67       float parabolaT = t - 0.5f;
 68       parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 69       position.y += config.arcFactor * parabolaT * projectile.distance;
 70       
 71       float size = 0.06f * (transitionOffset + 0.25f);
 72       DrawCube(position, size, size, size, color);
 73     }
 74   }
 75 }
 76 
 77 void ProjectileUpdate()
 78 {
 79   for (int i = 0; i < projectileCount; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 87     if (transition >= 1.0f)
 88     {
 89       projectile->projectileType = PROJECTILE_TYPE_NONE;
 90       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 91       if (enemy && projectile->hitEffectConfig.pushbackPowerDistance > 0.0f)
 92       {
 93           Vector2 direction = Vector2Normalize(Vector2Subtract((Vector2){projectile->target.x, projectile->target.z}, enemy->simPosition));
 94           enemy->simPosition = Vector2Add(enemy->simPosition, Vector2Scale(direction, projectile->hitEffectConfig.pushbackPowerDistance));
 95       }
 96       
 97       if (projectile->hitEffectConfig.areaDamageRadius > 0.0f)
 98       {
 99         EnemyAddDamageRange((Vector2){projectile->target.x, projectile->target.z}, projectile->hitEffectConfig.areaDamageRadius, projectile->hitEffectConfig.damage);
100         // pancaked sphere explosion
101         float r = projectile->hitEffectConfig.areaDamageRadius;
102         ParticleAdd(PARTICLE_TYPE_EXPLOSION, projectile->target, (Vector3){0}, (Vector3){r, r * 0.2f, r}, 0.33f);
103       }
104       else if (projectile->hitEffectConfig.damage > 0.0f && enemy)
105       {
106         EnemyAddDamage(enemy, projectile->hitEffectConfig.damage);
107       }
108       continue;
109     }
110   }
111 }
112 
113 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, HitEffectConfig hitEffectConfig)
114 {
115   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
116   {
117     Projectile *projectile = &projectiles[i];
118     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
119     {
120       projectile->projectileType = projectileType;
121       projectile->shootTime = gameTime.time;
122       float distance = Vector3Distance(position, target);
123       projectile->arrivalTime = gameTime.time + distance / speed;
124       projectile->position = position;
125       projectile->target = target;
126       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
127       projectile->distance = distance;
128       projectile->targetEnemy = EnemyGetId(enemy);
129       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
130       projectile->hitEffectConfig = hitEffectConfig;
131       return projectile;
132     }
133   }
134   return 0;
135 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <rlgl.h>
  4 
  5 static Particle particles[PARTICLE_MAX_COUNT];
  6 static int particleCount = 0;
  7 
  8 void ParticleInit()
  9 {
 10   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 11   {
 12     particles[i] = (Particle){0};
 13   }
 14   particleCount = 0;
 15 }
 16 
 17 static void DrawExplosionParticle(Particle *particle, float transition)
 18 {
 19   Vector3 scale = particle->scale;
 20   float size = 1.0f * (1.0f - transition);
 21   Color startColor = WHITE;
 22   Color endColor = RED;
 23   Color color = ColorLerp(startColor, endColor, transition);
 24 
 25   rlPushMatrix();
 26   rlTranslatef(particle->position.x, particle->position.y, particle->position.z);
 27   rlScalef(scale.x, scale.y, scale.z);
 28   DrawSphere(Vector3Zero(), size, color);
 29   rlPopMatrix();
 30 }
 31 
 32 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, Vector3 scale, float lifetime)
 33 {
 34   if (particleCount >= PARTICLE_MAX_COUNT)
 35   {
 36     return;
 37   }
 38 
 39   int index = -1;
 40   for (int i = 0; i < particleCount; i++)
 41   {
 42     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 43     {
 44       index = i;
 45       break;
 46     }
 47   }
 48 
 49   if (index == -1)
 50   {
 51     index = particleCount++;
 52   }
 53 
 54   Particle *particle = &particles[index];
 55   particle->particleType = particleType;
 56   particle->spawnTime = gameTime.time;
 57   particle->lifetime = lifetime;
 58   particle->position = position;
 59   particle->velocity = velocity;
 60   particle->scale = scale;
 61 }
 62 
 63 void ParticleUpdate()
 64 {
 65   for (int i = 0; i < particleCount; i++)
 66   {
 67     Particle *particle = &particles[i];
 68     if (particle->particleType == PARTICLE_TYPE_NONE)
 69     {
 70       continue;
 71     }
 72 
 73     float age = gameTime.time - particle->spawnTime;
 74 
 75     if (particle->lifetime > age)
 76     {
 77       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 78     }
 79     else {
 80       particle->particleType = PARTICLE_TYPE_NONE;
 81     }
 82   }
 83 }
 84 
 85 void ParticleDraw()
 86 {
 87   for (int i = 0; i < particleCount; i++)
 88   {
 89     Particle particle = particles[i];
 90     if (particle.particleType == PARTICLE_TYPE_NONE)
 91     {
 92       continue;
 93     }
 94 
 95     float age = gameTime.time - particle.spawnTime;
 96     float transition = age / particle.lifetime;
 97     switch (particle.particleType)
 98     {
 99     case PARTICLE_TYPE_EXPLOSION:
100       DrawExplosionParticle(&particle, transition);
101       break;
102     default:
103       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
104       break;
105     }
106   }
107 }  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endifThe first wave uses now the shield enemy so it is easy to test, how it behaves. The shield is absorbing much of the damage and has quite some amount of hitpoints. The ballista is however dealing more damage than the shield can absorb, so the remaining damage is applied to the minion behind the shield. This way, the ballista can take out shielded enemies much quicker than the arrow tower. The additional health bar for the shield is also working as intended:
Conclusion
We added 3 different new types of enemies in this part of the tutorial. The shield enemy adds a first new flavor that tastes like rock-paper-scissors.
The catapult tower could play out its area damage against groups of enemies, but the current wave configuration does not allow for this.
In the next part, we'll add a tower upgrade system and refine the UI a bit.