1 #include "weapons.h"
2 
3 #include "../../ObjManager.h"
4 #include "../../caret.h"
5 #include "../../Utils/Logger.h"
6 #include "../../game.h"
7 #include "../../graphics/Renderer.h"
8 #include "../../map.h"
9 #include "../../player.h"
10 #include "../../sound/SoundManager.h"
11 #include "../sym/smoke.h"
12 
13 using namespace NXE::Graphics;
14 
15 // a convenience function which does some stuff common to a basic weapon--
16 // damage enemies, hit walls, and ttl dispersion.
17 // if it deletes the shot, returns nonzero.
run_shot(Object * o,bool destroys_blocks)18 uint8_t run_shot(Object *o, bool destroys_blocks)
19 {
20   if (damage_enemies(o))
21   {
22     o->Delete();
23     return RS_HIT_ENEMY;
24   }
25 
26   if (IsBlockedInShotDir(o))
27   {
28     shot_spawn_effect(o, EFFECT_STARSOLID);
29 
30     if (destroys_blocks)
31     {
32       if (!shot_destroy_blocks(o))
33         NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_SHOT_HIT);
34     }
35     else
36     {
37       NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_SHOT_HIT);
38     }
39 
40     o->Delete();
41     return RS_HIT_WALL;
42   }
43 
44   if (--o->shot.ttl < 0)
45   {
46     shot_spawn_effect(o, EFFECT_STARPOOF);
47     o->Delete();
48     return RS_TTL_EXPIRED;
49   }
50 
51   return 0;
52 }
53 
54 /*
55 void c------------------------------() {}
56 */
57 
58 // checks if the shot passed in has struck an enemy. if so, returns the enemy.
59 // optional parameter flags_to_exclude lets you pass through enemies with
60 // certain flags set (such as if you don't want to try to hurt invulnerable enemies).
check_hit_enemy(Object * shot,uint32_t flags_to_exclude)61 Object *check_hit_enemy(Object *shot, uint32_t flags_to_exclude)
62 {
63   Object *enemy;
64   FOREACH_OBJECT(enemy)
65   {
66     if (enemy->flags & (FLAG_SHOOTABLE | FLAG_INVULNERABLE))
67     {
68       if ((enemy->flags & flags_to_exclude) == 0)
69       {
70         if (hitdetect_shot(enemy, shot))
71         {
72           // can't hit an enemy by shooting up when standing on it
73           // (added for omega battle but good probably in other times too)
74           if (player->riding != enemy || shot->yinertia >= 0)
75           {
76             return enemy;
77           }
78         }
79       }
80     }
81   }
82 
83   return NULL;
84 }
85 
86 // checks if the player shot "o" has hit an enemy and handles damaging the enemy if so.
87 // also, if an enemy was damaged, returns it's handle.
88 // multi is for weapons such as the spur and fireball that can "plow through" enemies
89 // until their damage is exhausted.
damage_enemies(Object * o,uint32_t flags_to_exclude)90 Object *damage_enemies(Object *o, uint32_t flags_to_exclude)
91 {
92   Object *enemy;
93 
94   // first check if we hit an enemy
95   if ((enemy = check_hit_enemy(o, flags_to_exclude)))
96   {
97     if (enemy->flags & FLAG_INVULNERABLE)
98     {
99       shot_spawn_effect(o, EFFECT_STARSOLID);
100       NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_TINK);
101     }
102     else
103     {
104       enemy->DealDelayedDamage(o->shot.damage, o);
105     }
106 
107     return enemy;
108   }
109 
110   return NULL;
111 }
112 
113 // used by AoE weapons, damages all enemies within the bounding box,
114 // not just the first one found. Returns the number of enemies hit.
damage_all_enemies_in_bb(Object * o,uint32_t flags_to_exclude,int x,int y,int range)115 int damage_all_enemies_in_bb(Object *o, uint32_t flags_to_exclude, int x, int y, int range)
116 {
117   Object *enemy;
118   int count = 0;
119 
120   FOREACH_OBJECT(enemy)
121   {
122     if (enemy->flags & (FLAG_SHOOTABLE | FLAG_INVULNERABLE))
123     {
124       if ((enemy->flags & flags_to_exclude) == 0)
125       {
126         if (hitdetect_area(enemy, x, y, range))
127         {
128           if (enemy->flags & FLAG_INVULNERABLE)
129           {
130             shot_spawn_effect(o, EFFECT_STARSOLID);
131             NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_TINK);
132           }
133           else
134           {
135             enemy->DealDelayedDamage(o->shot.damage, o);
136           }
137 
138           count++;
139         }
140       }
141     }
142   }
143 
144   return count;
145 }
146 
147 /*
148 void c------------------------------() {}
149 */
150 
151 // spawn an effect at a shot's center point
shot_spawn_effect(Object * o,int effectno)152 void shot_spawn_effect(Object *o, int effectno)
153 {
154   int x = 0;
155   int y = 0;
156 
157   // Nemesis shots are very long so just centering the star doesn't look right.
158   // I could have gone off of aspect ratio but wanted to keep missiles the same
159   // and not risk breaking any older weapons.
160   if ((o->type == OBJ_NEMESIS_SHOT && o->shot.level != 2) || (o->type == OBJ_MGUN_LEADER))
161   {
162     switch (o->shot.dir)
163     {
164       case LEFT:
165         x = o->x;
166         y = o->CenterY();
167         break;
168 
169       case RIGHT:
170         x = (o->x + o->Width());
171         y = o->CenterY();
172         break;
173 
174       case UP:
175         x = o->CenterX();
176         y = o->y;
177         break;
178 
179       case DOWN:
180         x = o->CenterX();
181         y = (o->y + o->Height());
182         break;
183     }
184   }
185   else
186   {
187     x = o->CenterX();
188     y = o->CenterY();
189   }
190 
191   if (effectno == EFFECT_STARSOLID || effectno == EFFECT_SPUR_HIT)
192   { // embed it in the wall, instead of the spot where we hit at
193     switch (o->shot.dir)
194     {
195       case RIGHT:
196         x += 0x400;
197         break;
198       case LEFT:
199         x -= 0x400;
200         break;
201       case UP:
202         y -= 0x400;
203         break;
204       case DOWN:
205         y += 0x400;
206         break;
207     }
208   }
209 
210   effect(x, y, effectno);
211 }
212 
shot_dissipate(Object * o,int effectno)213 void shot_dissipate(Object *o, int effectno)
214 {
215   shot_spawn_effect(o, effectno);
216   o->Delete();
217 }
218 
219 /*
220 void c------------------------------() {}
221 */
222 // called when certain kinds of shots hit a wall to see if it was a
223 // destroyable brick that they hit.
224 // this destroys star-blocks which are touching the shot.
225 // returns nonzero if any were destroyed.
shot_destroy_blocks(Object * o)226 bool shot_destroy_blocks(Object *o)
227 {
228   int x, y;
229   SIFPointList *plist;
230 
231   // select which pointlist to check based on which direction shot is traveling
232   switch (o->shot.dir)
233   {
234     case LEFT:
235       plist = &Renderer::getInstance()->sprites.sprites[o->sprite].block_l;
236       break;
237     case RIGHT:
238       plist = &Renderer::getInstance()->sprites.sprites[o->sprite].block_r;
239       break;
240     case UP:
241       plist = &Renderer::getInstance()->sprites.sprites[o->sprite].block_u;
242       break;
243     case DOWN:
244       plist = &Renderer::getInstance()->sprites.sprites[o->sprite].block_d;
245       break;
246     default:
247       return 0;
248   }
249 
250   // see if we've hit a destroyable block
251   if (o->CheckAttribute(plist, TA_DESTROYABLE, &x, &y))
252   {
253     map.tiles[x][y]--;
254     SmokeCloudsSlow(((x * TILE_W) + (TILE_W / 2)) * CSFI, ((y * TILE_H) + (TILE_H / 2)) * CSFI, 4);
255 
256     NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_BLOCK_DESTROY);
257     shot_spawn_effect(o, EFFECT_FISHY);
258     return 1;
259   }
260 
261   return 0;
262 }
263 
264 /*
265 void c------------------------------() {}
266 */
267 
268 // returns nonzero if a shot is blocked in the direction it's traveling
IsBlockedInShotDir(Object * o)269 bool IsBlockedInShotDir(Object *o)
270 {
271   switch (o->shot.dir)
272   {
273     case LEFT:
274       return o->blockl;
275     case RIGHT:
276       return o->blockr;
277     case UP:
278       return o->blocku;
279     case DOWN:
280       return o->blockd;
281   }
282 
283   LOG_ERROR("IsBlockedInShotDir({:#x}): invalid direction {}", (intptr_t)o, o->shot.dir);
284   return 0;
285 }
286