particle_effects.diff 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. diff --git a/game.h b/game.h
  2. index 991058d..328ed68 100755
  3. --- a/game.h
  4. +++ b/game.h
  5. @@ -339,6 +339,15 @@ typedef struct
  6. #define SFG_MENU_ITEM_NONE 255
  7. +typedef struct
  8. +{
  9. + uint8_t dieIn; ///< Double frames left to live.
  10. + uint8_t color;
  11. + uint8_t height; ///< Floor h. under part., in RCL_UNITS_PER_SQUARE / 4s.
  12. + uint16_t position[3];
  13. + int8_t velocity[3]; ///< Velocity, simply added to position each frame.
  14. +} SFG_Particle;
  15. +
  16. /*
  17. GLOBAL VARIABLES
  18. ===============================================================================
  19. @@ -453,6 +462,9 @@ struct
  20. uint8_t floorColor;
  21. uint8_t ceilingColor;
  22. + SFG_Particle particles[SFG_MAX_PARTICLES];
  23. + uint16_t particleCount;
  24. +
  25. SFG_DoorRecord doorRecords[SFG_MAX_DOORS];
  26. uint8_t doorRecordCount;
  27. uint8_t checkedDoorIndex; ///< Says which door are currently being checked.
  28. @@ -1312,6 +1324,79 @@ RCL_Unit SFG_floorHeightAt(int16_t x, int16_t y)
  29. return SFG_TILE_FLOOR_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP - doorHeight;
  30. }
  31. +/**
  32. + Emits new particle in the particle system, velocity is in 1/16ths of a square
  33. + per second.
  34. +*/
  35. +void SFG_emitParticle(uint8_t timeToLive, uint8_t color, RCL_Unit position[3],
  36. + int8_t velocity[3])
  37. +{
  38. + if (timeToLive == 0)
  39. + return;
  40. +
  41. + SFG_Particle *p = SFG_currentLevel.particles;
  42. +
  43. + if (SFG_currentLevel.particleCount >= SFG_MAX_PARTICLES)
  44. + p += SFG_game.frame % SFG_currentLevel.particleCount; // replace random
  45. + else
  46. + { // append new particle
  47. + p += SFG_currentLevel.particleCount;
  48. + SFG_currentLevel.particleCount++;
  49. + }
  50. +
  51. + p->height = SFG_floorHeightAt(
  52. + position[0] / RCL_UNITS_PER_SQUARE,
  53. + position[1] / RCL_UNITS_PER_SQUARE) / (RCL_UNITS_PER_SQUARE / 4);
  54. +
  55. + p->dieIn = timeToLive;
  56. + p->color = color;
  57. +
  58. + for (uint8_t i = 0; i < 3; ++i)
  59. + {
  60. + p->position[i] = position[i];
  61. +
  62. + int16_t v = velocity[i];
  63. +
  64. + v = (v * (RCL_UNITS_PER_SQUARE / 16)) / SFG_FPS;
  65. +
  66. + if (v < -128)
  67. + v = -128;
  68. + else if (v > 127)
  69. + v = 127;
  70. +
  71. + p->velocity[i] = v;
  72. + }
  73. +}
  74. +
  75. +void SFG_explodeParticles(RCL_Unit position[3], uint8_t speed, uint8_t count,
  76. + uint16_t randomSeed, uint8_t color, uint8_t timeToLive, uint8_t allowDown)
  77. +{
  78. + while (count)
  79. + {
  80. + int8_t v[3];
  81. + uint8_t randomAxis = randomSeed % 3;
  82. + uint8_t randomizedSpeed = speed - 2 + count % 4;
  83. +
  84. + // select random point on a speed x speed x speed cube:
  85. + for (uint8_t i = 0; i < 3; ++i)
  86. + {
  87. + randomSeed = randomSeed * 123 + 10;
  88. +
  89. + v[i] = (i == randomAxis) ?
  90. + (((randomSeed / 32) % 2) ? -1 * randomizedSpeed : randomizedSpeed) :
  91. + ((randomSeed % (randomizedSpeed * 2)) - randomizedSpeed);
  92. + }
  93. +
  94. + if (!allowDown && v[2] < 0)
  95. + v[2] *= -1;
  96. +
  97. + SFG_emitParticle(timeToLive + (timeToLive > 127 ? -1 * (randomSeed % 32) :
  98. + (randomSeed % 32)),color,position,v);
  99. +
  100. + count--;
  101. + }
  102. +}
  103. +
  104. /**
  105. Like SFG_floorCollisionHeightAt, but takes into account colliding items on
  106. the map, so the squares that have these items are higher. The former function
  107. @@ -1662,6 +1747,8 @@ void SFG_setAndInitLevel(uint8_t levelNumber)
  108. }
  109. }
  110. + SFG_currentLevel.particleCount = 0;
  111. +
  112. SFG_currentLevel.timeStart = SFG_game.frameTime;
  113. SFG_currentLevel.frameStart = SFG_game.frame;
  114. @@ -2000,18 +2087,24 @@ void SFG_monsterChangeHealth(SFG_MonsterRecord *monster, int8_t healthAdd)
  115. if (healthAdd < 0)
  116. {
  117. // play hurt sound
  118. +
  119. + RCL_Unit pos[3];
  120. +
  121. + pos[0] = SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]);
  122. + pos[1] = SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]);
  123. + pos[2] = SFG_floorHeightAt(SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
  124. + SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1]));
  125. +
  126. + uint8_t volume = SFG_distantSoundVolume(pos[0],pos[1],pos[2]);
  127. - uint8_t volume = SFG_distantSoundVolume(
  128. - SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
  129. - SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),
  130. - SFG_floorHeightAt(
  131. - SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
  132. - SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1])));
  133. -
  134. - SFG_playGameSound(5,volume);
  135. + SFG_playGameSound(5,volume);
  136. if (monster->health == 0)
  137. + {
  138. SFG_playGameSound(2,volume);
  139. + SFG_explodeParticles(pos,25,SFG_MAX_PARTICLES / 3,SFG_game.frame + pos[0],
  140. + 52,255,0);
  141. + }
  142. }
  143. }
  144. @@ -2222,6 +2315,14 @@ void SFG_createExplosion(RCL_Unit x, RCL_Unit y, RCL_Unit z)
  145. i--;
  146. }
  147. }
  148. +
  149. + RCL_Unit pos[3];
  150. + pos[0] = x;
  151. + pos[1] = y;
  152. + pos[2] = z;
  153. +
  154. + SFG_explodeParticles(pos,43,(SFG_MAX_PARTICLES * 3) / 2,
  155. + SFG_game.frame + x + y,191,150,1);
  156. }
  157. void SFG_createDust(RCL_Unit x, RCL_Unit y, RCL_Unit z)
  158. @@ -2242,6 +2343,14 @@ void SFG_createDust(RCL_Unit x, RCL_Unit y, RCL_Unit z)
  159. RCL_nonZero(SFG_GET_PROJECTILE_FRAMES_TO_LIVE(SFG_PROJECTILE_DUST) / 2);
  160. SFG_createProjectile(dust);
  161. +
  162. + RCL_Unit pos[3];
  163. + pos[0] = x;
  164. + pos[1] = y;
  165. + pos[2] = z;
  166. +
  167. + SFG_explodeParticles(pos,30,SFG_MAX_PARTICLES / 3,SFG_game.frame + x + y,5,
  168. + 5,1);
  169. }
  170. void SFG_getMonsterWorldPosition(SFG_MonsterRecord *monster, RCL_Unit *x,
  171. @@ -2645,6 +2754,31 @@ void SFG_updateLevel(void)
  172. }
  173. }
  174. + if (!eliminate && ((SFG_game.frame % (SFG_FPS / 20 + 1)) == 0) &&
  175. + (p->type == SFG_PROJECTILE_FIREBALL || p->type == SFG_PROJECTILE_PLASMA))
  176. + {
  177. + // rocket and plasma emit particles as they fly
  178. + int8_t vel[3];
  179. + uint8_t col = 95;
  180. +
  181. + vel[0] = 0;
  182. + vel[1] = 0;
  183. +
  184. + if (p->type == SFG_PROJECTILE_PLASMA)
  185. + {
  186. + col = 207;
  187. +
  188. + vel[0] = p->direction[1] / 32;
  189. + vel[1] = p->direction[0] / 32;
  190. + vel[(SFG_game.frame % 8) == 0] *= -1;
  191. + vel[2] = (pos[0] % 3 + pos[1] % 2) * 4;
  192. + }
  193. + else
  194. + vel[2] = 8;
  195. +
  196. + SFG_emitParticle(30,col - p->doubleFramesToLive / 4 ,pos,vel);
  197. + }
  198. +
  199. if (p->doubleFramesToLive == 0) // no more time to live?
  200. {
  201. eliminate = 1;
  202. @@ -2974,6 +3108,93 @@ void SFG_updateLevel(void)
  203. }
  204. }
  205. }
  206. +
  207. + // update particles:
  208. +
  209. + SFG_Particle *p = SFG_currentLevel.particles;
  210. + uint8_t evenFrame = SFG_game.frame % 2;
  211. +
  212. + if (SFG_currentLevel.particleCount > 0)
  213. + {
  214. + // check visibility of one random particle
  215. + SFG_Particle *p = SFG_currentLevel.particles +
  216. + (SFG_game.frame % SFG_currentLevel.particleCount);
  217. +
  218. + RCL_Vector2D particlePos;
  219. +
  220. + particlePos.x = p->position[0];
  221. + particlePos.y = p->position[1];
  222. +
  223. + if (RCL_castRay3D(
  224. + SFG_player.camera.position,
  225. + SFG_player.camera.height,
  226. + particlePos,
  227. + p->position[2],
  228. + SFG_floorHeightAt,
  229. + SFG_ceilingHeightAt,
  230. + SFG_game.visibilityRayConstraints
  231. + ) < (7 * RCL_UNITS_PER_SQUARE) / 8)
  232. + p->dieIn = 0; // particle out of sight: just kill it
  233. + }
  234. +
  235. + for (int i = 0; i < SFG_currentLevel.particleCount; ++i)
  236. + {
  237. + if (p->dieIn != 0)
  238. + {
  239. + if (evenFrame)
  240. + p->dieIn--;
  241. +
  242. + RCL_Unit height = p->height;
  243. + height *= RCL_UNITS_PER_SQUARE / 4;
  244. +
  245. + uint8_t squareX = p->position[0] / RCL_UNITS_PER_SQUARE,
  246. + squareY = p->position[1] / RCL_UNITS_PER_SQUARE;
  247. +
  248. + p->position[0] += p->velocity[0];
  249. + p->position[1] += p->velocity[1];
  250. +
  251. + if (squareX != p->position[0] / RCL_UNITS_PER_SQUARE ||
  252. + squareY != p->position[1] / RCL_UNITS_PER_SQUARE)
  253. + {
  254. + // particle changed square, handle change in floor height
  255. +
  256. + RCL_Unit newHeight = SFG_floorHeightAt(
  257. + p->position[0] / RCL_UNITS_PER_SQUARE,
  258. + p->position[1] / RCL_UNITS_PER_SQUARE);
  259. +
  260. + if (p->position[2] >= newHeight)
  261. + {
  262. + p->height = newHeight / (RCL_UNITS_PER_SQUARE / 4);
  263. + height = newHeight;
  264. + }
  265. + else
  266. + {
  267. + p->position[0] -= p->velocity[0];
  268. + p->position[1] -= p->velocity[1];
  269. + p->velocity[0] = 0;
  270. + p->velocity[1] = 0;
  271. + }
  272. + }
  273. +
  274. + if (p->velocity[2] > 0 || p->position[2] >= -1 * p->velocity[2])
  275. + p->position[2] += p->velocity[2];
  276. +
  277. + if (p->position[2] < height)
  278. + p->position[2] = height;
  279. +
  280. + if (evenFrame && p->velocity[2] > -30)
  281. + p->velocity[2] -= (RCL_UNITS_PER_SQUARE / 16) / SFG_FPS ; // gravity
  282. + }
  283. + else
  284. + {
  285. + SFG_currentLevel.particleCount--;
  286. +
  287. + if (SFG_currentLevel.particleCount > 0)
  288. + *p = SFG_currentLevel.particles[SFG_currentLevel.particleCount];
  289. + }
  290. +
  291. + p++;
  292. + }
  293. }
  294. /**
  295. @@ -3591,6 +3812,24 @@ void SFG_gameStepPlaying(void)
  296. default: sound = 0; break;
  297. }
  298. + if (SFG_player.weapon == SFG_WEAPON_SHOTGUN ||
  299. + SFG_player.weapon == SFG_WEAPON_MACHINE_GUN)
  300. + {
  301. + // spawn bullet shell:
  302. + RCL_Unit shellP[3];
  303. + int8_t shellV[3];
  304. +
  305. + shellP[0] = SFG_player.camera.position.x;
  306. + shellP[1] = SFG_player.camera.position.y;
  307. + shellP[2] = SFG_player.camera.height;
  308. +
  309. + shellV[0] = SFG_player.direction.x / 16 - SFG_player.direction.y / 32;
  310. + shellV[1] = SFG_player.direction.y / 16 + SFG_player.direction.x / 32;
  311. + shellV[2] = 2;
  312. +
  313. + SFG_emitParticle(255,20,shellP,shellV);
  314. + }
  315. +
  316. if (sound != 255)
  317. SFG_playGameSound(sound,255);
  318. @@ -3769,6 +4008,14 @@ void SFG_gameStepPlaying(void)
  319. SFG_processEvent(SFG_EVENT_VIBRATE,0);
  320. SFG_processEvent(SFG_EVENT_PLAYER_DIES,0);
  321. SFG_setGameState(SFG_GAME_STATE_LOSE);
  322. +
  323. + RCL_Unit pos[3];
  324. + pos[0] = SFG_player.camera.position.x + (SFG_player.direction.x * 2) / 3;
  325. + pos[1] = SFG_player.camera.position.y + (SFG_player.direction.y * 2) / 3;
  326. + pos[2] = SFG_player.camera.height;
  327. +
  328. + // blood:
  329. + SFG_explodeParticles(pos,8,(SFG_MAX_PARTICLES * 2) / 3,123,175,255,0);
  330. }
  331. #endif
  332. }
  333. @@ -4860,6 +5107,36 @@ void SFG_draw(void)
  334. p.depth);
  335. }
  336. + // particles:
  337. +
  338. + SFG_Particle *particle = SFG_currentLevel.particles;
  339. +
  340. + for (int i = 0; i < SFG_currentLevel.particleCount; ++i)
  341. + {
  342. + RCL_Vector2D worldPosition;
  343. +
  344. + worldPosition.x = particle->position[0];
  345. + worldPosition.y = particle->position[1];
  346. +
  347. + RCL_PixelInfo p =
  348. + RCL_mapToScreen(worldPosition,particle->position[2],SFG_player.camera);
  349. +
  350. + p.position.x *= SFG_RAYCASTING_SUBSAMPLE;
  351. +
  352. + if (p.depth > 127 &&
  353. + p.position.x >= 0 &&
  354. + p.position.x < (SFG_GAME_RESOLUTION_X - SFG_PARTICLE_SIZE) &&
  355. + p.position.y >= 0 &&
  356. + p.position.y < (SFG_GAME_RESOLUTION_Y - SFG_PARTICLE_SIZE))
  357. + {
  358. + for (int y = p.position.y; y < p.position.y + SFG_PARTICLE_SIZE; ++y)
  359. + for (int x = p.position.x; x < p.position.x + SFG_PARTICLE_SIZE; ++x)
  360. + SFG_setGamePixel(x,y,particle->color);
  361. + }
  362. +
  363. + particle++;
  364. + }
  365. +
  366. #if SFG_HEADBOB_ENABLED
  367. // after rendering sprites subtract back the head bob offset
  368. SFG_player.camera.height -= headBobOffset;
  369. diff --git a/settings.h b/settings.h
  370. index a0d8c1f..2155369 100644
  371. --- a/settings.h
  372. +++ b/settings.h
  373. @@ -457,6 +457,20 @@
  374. #define SFG_FORCE_SINGLE_ITEM_MENU 0
  375. #endif
  376. +/**
  377. + Maximum number of particles for the game's particle system.
  378. +*/
  379. +#ifndef SFG_MAX_PARTICLES
  380. + #define SFG_MAX_PARTICLES 64
  381. +#endif
  382. +
  383. +/**
  384. + Particle size in pixels, for particle system.
  385. +*/
  386. +#ifndef SFG_PARTICLE_SIZE
  387. + #define SFG_PARTICLE_SIZE 4
  388. +#endif
  389. +
  390. //------ developer/debug settings ------
  391. /**