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