1 //Copyright Paul Reiche, Fred Ford. 1992-2002
2
3 /*
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19 #include "weapon.h"
20
21 #include "colors.h"
22 #include "globdata.h"
23 #include "status.h"
24 #include "init.h"
25 #include "races.h"
26 #include "ship.h"
27 #include "setup.h"
28 #include "sounds.h"
29 #include "units.h"
30 #include "libs/mathlib.h"
31
32 #include <stdio.h>
33
34 // A wrapper function for weapon_collision that discards the return value.
35 // This makes its signature match ElementCollisionFunc.
36 static void
weapon_collision_cb(ELEMENT * WeaponElementPtr,POINT * pWPt,ELEMENT * HitElementPtr,POINT * pHPt)37 weapon_collision_cb (ELEMENT *WeaponElementPtr, POINT *pWPt,
38 ELEMENT *HitElementPtr, POINT *pHPt)
39 {
40 weapon_collision (WeaponElementPtr, pWPt, HitElementPtr, pHPt);
41 }
42
43
44 HELEMENT
initialize_laser(LASER_BLOCK * pLaserBlock)45 initialize_laser (LASER_BLOCK *pLaserBlock)
46 {
47 HELEMENT hLaserElement;
48
49 hLaserElement = AllocElement ();
50 if (hLaserElement)
51 {
52 #define LASER_LIFE 1
53 ELEMENT *LaserElementPtr;
54
55 LockElement (hLaserElement, &LaserElementPtr);
56 LaserElementPtr->playerNr = pLaserBlock->sender;
57 LaserElementPtr->hit_points = 1;
58 LaserElementPtr->mass_points = 1;
59 LaserElementPtr->state_flags = APPEARING | FINITE_LIFE
60 | pLaserBlock->flags;
61 LaserElementPtr->life_span = LASER_LIFE;
62 LaserElementPtr->collision_func = weapon_collision_cb;
63 LaserElementPtr->blast_offset = 1;
64
65 LaserElementPtr->current.location.x = pLaserBlock->cx
66 + COSINE (FACING_TO_ANGLE (pLaserBlock->face),
67 DISPLAY_TO_WORLD (pLaserBlock->pixoffs));
68 LaserElementPtr->current.location.y = pLaserBlock->cy
69 + SINE (FACING_TO_ANGLE (pLaserBlock->face),
70 DISPLAY_TO_WORLD (pLaserBlock->pixoffs));
71 SetPrimType (&DisplayArray[LaserElementPtr->PrimIndex], LINE_PRIM);
72 SetPrimColor (&DisplayArray[LaserElementPtr->PrimIndex],
73 pLaserBlock->color);
74 LaserElementPtr->current.image.frame = DecFrameIndex (stars_in_space);
75 LaserElementPtr->current.image.farray = &stars_in_space;
76 SetVelocityComponents (&LaserElementPtr->velocity,
77 WORLD_TO_VELOCITY ((pLaserBlock->cx + pLaserBlock->ex)
78 - LaserElementPtr->current.location.x),
79 WORLD_TO_VELOCITY ((pLaserBlock->cy + pLaserBlock->ey)
80 - LaserElementPtr->current.location.y));
81 UnlockElement (hLaserElement);
82 }
83
84 return (hLaserElement);
85 }
86
87 HELEMENT
initialize_missile(MISSILE_BLOCK * pMissileBlock)88 initialize_missile (MISSILE_BLOCK *pMissileBlock)
89 {
90 HELEMENT hMissileElement;
91
92 hMissileElement = AllocElement ();
93 if (hMissileElement)
94 {
95 SIZE delta_x, delta_y;
96 COUNT angle;
97 ELEMENT *MissileElementPtr;
98
99 LockElement (hMissileElement, &MissileElementPtr);
100 MissileElementPtr->hit_points = (BYTE)pMissileBlock->hit_points;
101 MissileElementPtr->mass_points = (BYTE)pMissileBlock->damage;
102 MissileElementPtr->playerNr = pMissileBlock->sender;
103 MissileElementPtr->state_flags = APPEARING | FINITE_LIFE
104 | pMissileBlock->flags;
105 MissileElementPtr->life_span = pMissileBlock->life;
106 SetPrimType (&DisplayArray[MissileElementPtr->PrimIndex], STAMP_PRIM);
107 MissileElementPtr->current.image.farray = pMissileBlock->farray;
108 MissileElementPtr->current.image.frame =
109 SetAbsFrameIndex (pMissileBlock->farray[0],
110 pMissileBlock->index);
111 MissileElementPtr->preprocess_func = pMissileBlock->preprocess_func;
112 MissileElementPtr->collision_func = weapon_collision_cb;
113 MissileElementPtr->blast_offset = (BYTE)pMissileBlock->blast_offs;
114
115 angle = FACING_TO_ANGLE (pMissileBlock->face);
116 MissileElementPtr->current.location.x = pMissileBlock->cx
117 + COSINE (angle, DISPLAY_TO_WORLD (pMissileBlock->pixoffs));
118 MissileElementPtr->current.location.y = pMissileBlock->cy
119 + SINE (angle, DISPLAY_TO_WORLD (pMissileBlock->pixoffs));
120
121 delta_x = COSINE (angle, WORLD_TO_VELOCITY (pMissileBlock->speed));
122 delta_y = SINE (angle, WORLD_TO_VELOCITY (pMissileBlock->speed));
123 SetVelocityComponents (&MissileElementPtr->velocity,
124 delta_x, delta_y);
125
126 MissileElementPtr->current.location.x -= VELOCITY_TO_WORLD (delta_x);
127 MissileElementPtr->current.location.y -= VELOCITY_TO_WORLD (delta_y);
128 UnlockElement (hMissileElement);
129 }
130
131 return (hMissileElement);
132 }
133
134 HELEMENT
weapon_collision(ELEMENT * WeaponElementPtr,POINT * pWPt,ELEMENT * HitElementPtr,POINT * pHPt)135 weapon_collision (ELEMENT *WeaponElementPtr, POINT *pWPt,
136 ELEMENT *HitElementPtr, POINT *pHPt)
137 {
138 SIZE damage;
139 HELEMENT hBlastElement;
140
141 if (WeaponElementPtr->state_flags & COLLISION) /* if already did effect */
142 return ((HELEMENT)0);
143
144 damage = (SIZE)WeaponElementPtr->mass_points;
145 if (damage
146 && ((HitElementPtr->state_flags & FINITE_LIFE)
147 || HitElementPtr->life_span == NORMAL_LIFE))
148 #ifdef NEVER
149 &&
150 /* lasers from the same ship can't hit each other */
151 (GetPrimType (&DisplayArray[HitElementPtr->PrimIndex]) != LINE_PRIM
152 || GetPrimType (&DisplayArray[WeaponElementPtr->PrimIndex]) != LINE_PRIM
153 || !elementsOfSamePlayer (HitElementPtr, WeaponElementPtr)))
154 #endif /* NEVER */
155 {
156 do_damage (HitElementPtr, damage);
157 if (HitElementPtr->hit_points)
158 WeaponElementPtr->state_flags |= COLLISION;
159 }
160
161 if (!(HitElementPtr->state_flags & FINITE_LIFE)
162 || (!(/* WeaponElementPtr->state_flags
163 & */ HitElementPtr->state_flags & COLLISION)
164 && WeaponElementPtr->hit_points <= HitElementPtr->mass_points))
165 {
166 if (damage)
167 {
168 damage = TARGET_DAMAGED_FOR_1_PT + (damage >> 1);
169 if (damage > TARGET_DAMAGED_FOR_6_PLUS_PT)
170 damage = TARGET_DAMAGED_FOR_6_PLUS_PT;
171 ProcessSound (SetAbsSoundIndex (GameSounds, damage),
172 HitElementPtr);
173 }
174
175 if (GetPrimType (&DisplayArray[WeaponElementPtr->PrimIndex])
176 != LINE_PRIM)
177 WeaponElementPtr->state_flags |= DISAPPEARING;
178
179 WeaponElementPtr->hit_points = 0;
180 WeaponElementPtr->life_span = 0;
181 WeaponElementPtr->state_flags |= COLLISION | NONSOLID;
182
183 hBlastElement = AllocElement ();
184 if (hBlastElement)
185 {
186 COUNT blast_index;
187 SIZE blast_offs;
188 COUNT angle, num_blast_frames;
189 ELEMENT *BlastElementPtr;
190 extern FRAME blast[];
191
192 PutElement (hBlastElement);
193 LockElement (hBlastElement, &BlastElementPtr);
194 BlastElementPtr->playerNr = WeaponElementPtr->playerNr;
195 BlastElementPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
196 SetPrimType (&DisplayArray[BlastElementPtr->PrimIndex], STAMP_PRIM);
197
198 BlastElementPtr->current.location.x = DISPLAY_TO_WORLD (pWPt->x);
199 BlastElementPtr->current.location.y = DISPLAY_TO_WORLD (pWPt->y);
200
201 angle = GetVelocityTravelAngle (&WeaponElementPtr->velocity);
202 if ((blast_offs = WeaponElementPtr->blast_offset) > 0)
203 {
204 BlastElementPtr->current.location.x +=
205 COSINE (angle, DISPLAY_TO_WORLD (blast_offs));
206 BlastElementPtr->current.location.y +=
207 SINE (angle, DISPLAY_TO_WORLD (blast_offs));
208 }
209
210 blast_index =
211 NORMALIZE_FACING (ANGLE_TO_FACING (angle + HALF_CIRCLE));
212 blast_index = ((blast_index >> 2) << 1) +
213 (blast_index & 0x3 ? 1 : 0);
214
215 num_blast_frames =
216 GetFrameCount (WeaponElementPtr->next.image.frame);
217 if (num_blast_frames <= ANGLE_TO_FACING (FULL_CIRCLE))
218 {
219 BlastElementPtr->life_span = 2;
220 BlastElementPtr->current.image.farray = blast;
221 BlastElementPtr->current.image.frame =
222 SetAbsFrameIndex (blast[0], blast_index);
223 }
224 else
225 {
226 BlastElementPtr->life_span = num_blast_frames
227 - ANGLE_TO_FACING (FULL_CIRCLE);
228 BlastElementPtr->turn_wait = BlastElementPtr->next_turn = 0;
229 BlastElementPtr->preprocess_func = animation_preprocess;
230 BlastElementPtr->current.image.farray =
231 WeaponElementPtr->next.image.farray;
232 BlastElementPtr->current.image.frame =
233 SetAbsFrameIndex (
234 BlastElementPtr->current.image.farray[0],
235 ANGLE_TO_FACING (FULL_CIRCLE));
236 }
237
238 UnlockElement (hBlastElement);
239
240 return (hBlastElement);
241 }
242 }
243
244 (void) pHPt; /* Satisfying compiler (unused parameter) */
245 return ((HELEMENT)0);
246 }
247
248 FRAME
ModifySilhouette(ELEMENT * ElementPtr,STAMP * modify_stamp,BYTE modify_flags)249 ModifySilhouette (ELEMENT *ElementPtr, STAMP *modify_stamp,
250 BYTE modify_flags)
251 {
252 FRAME f;
253 RECT r, or;
254 INTERSECT_CONTROL ShipIntersect, ObjectIntersect;
255 STARSHIP *StarShipPtr;
256 CONTEXT OldContext;
257
258 f = 0;
259 ObjectIntersect.IntersectStamp = *modify_stamp;
260 GetFrameRect (ObjectIntersect.IntersectStamp.frame, &or);
261
262 GetElementStarShip (ElementPtr, &StarShipPtr);
263 if (modify_flags & MODIFY_IMAGE)
264 {
265 ShipIntersect.IntersectStamp.frame = SetAbsFrameIndex (
266 StarShipPtr->RaceDescPtr->ship_info.icons, 1);
267 if (ShipIntersect.IntersectStamp.frame == 0)
268 return (0);
269
270 GetFrameRect (ShipIntersect.IntersectStamp.frame, &r);
271
272 ShipIntersect.IntersectStamp.origin.x = 0;
273 ShipIntersect.IntersectStamp.origin.y = 0;
274 ShipIntersect.EndPoint = ShipIntersect.IntersectStamp.origin;
275 do
276 {
277 ObjectIntersect.IntersectStamp.origin.x = ((COUNT)TFB_Random ()
278 % (r.extent.width - or.extent.width))
279 + ((or.extent.width - r.extent.width) >> 1);
280 ObjectIntersect.IntersectStamp.origin.y = ((COUNT)TFB_Random ()
281 % (r.extent.height - or.extent.height))
282 + ((or.extent.height - r.extent.height) >> 1);
283 ObjectIntersect.EndPoint = ObjectIntersect.IntersectStamp.origin;
284 } while (!DrawablesIntersect (&ObjectIntersect,
285 &ShipIntersect, MAX_TIME_VALUE));
286
287 ObjectIntersect.IntersectStamp.origin.x += STATUS_WIDTH >> 1;
288 ObjectIntersect.IntersectStamp.origin.y += 31;
289 }
290
291 ObjectIntersect.IntersectStamp.origin.y +=
292 status_y_offsets[ElementPtr->playerNr];
293
294 if (modify_flags & MODIFY_SWAP)
295 {
296 or.corner.x += ObjectIntersect.IntersectStamp.origin.x;
297 or.corner.y += ObjectIntersect.IntersectStamp.origin.y;
298 InitShipStatus (&StarShipPtr->RaceDescPtr->ship_info,
299 StarShipPtr, &or);
300 }
301 else
302 {
303 OldContext = SetContext (StatusContext);
304 DrawStamp (&ObjectIntersect.IntersectStamp);
305 SetContext (OldContext);
306 }
307
308 return (f);
309 }
310
311 // Find the closest possible target ship, to be set in Tracker->hTarget.
312 // *pfacing will be turned one angle unit into the direction towards the
313 // target.
314 // The return value will be the actual number of angle units to turn, or
315 // -1 if no target was found.
316 // Cloaked ships won't be detected, except when the APPEARING flag is
317 // set for the Tracker.
318 SIZE
TrackShip(ELEMENT * Tracker,COUNT * pfacing)319 TrackShip (ELEMENT *Tracker, COUNT *pfacing)
320 {
321 SIZE best_delta_facing, best_delta;
322 HELEMENT hShip, hNextShip;
323 ELEMENT *Trackee;
324
325 best_delta = 0;
326 best_delta_facing = -1;
327
328 hShip = Tracker->hTarget;
329 if (hShip)
330 {
331 LockElement (hShip, &Trackee);
332 Tracker->hTarget = hNextShip = 0;
333
334 goto CheckTracking;
335 }
336
337 for (hShip = GetHeadElement (); hShip != 0; hShip = hNextShip)
338 {
339 LockElement (hShip, &Trackee);
340 hNextShip = GetSuccElement (Trackee);
341 if ((Trackee->state_flags & PLAYER_SHIP)
342 && !elementsOfSamePlayer (Trackee, Tracker)
343 && (!OBJECT_CLOAKED (Trackee)
344 || ((Tracker->state_flags & PLAYER_SHIP)
345 && (Tracker->state_flags & APPEARING))
346 ))
347 {
348 STARSHIP *StarShipPtr;
349
350 CheckTracking:
351 GetElementStarShip (Trackee, &StarShipPtr);
352 if (Trackee->life_span
353 && StarShipPtr->RaceDescPtr->ship_info.crew_level)
354 {
355 SIZE delta_x, delta_y, delta_facing;
356
357 if (Tracker->state_flags & PRE_PROCESS)
358 {
359 delta_x = Trackee->next.location.x
360 - Tracker->next.location.x;
361 delta_y = Trackee->next.location.y
362 - Tracker->next.location.y;
363 }
364 else
365 {
366 delta_x = Trackee->current.location.x
367 - Tracker->current.location.x;
368 delta_y = Trackee->current.location.y
369 - Tracker->current.location.y;
370 }
371
372 delta_x = WRAP_DELTA_X (delta_x);
373 delta_y = WRAP_DELTA_Y (delta_y);
374 delta_facing = NORMALIZE_FACING (
375 ANGLE_TO_FACING (ARCTAN (delta_x, delta_y)) - *pfacing
376 );
377
378 if (delta_x < 0)
379 delta_x = -delta_x;
380 if (delta_y < 0)
381 delta_y = -delta_y;
382 delta_x += delta_y;
383 // 'delta_x + delta_y' is used as an approximation
384 // of the actual distance 'sqrt(sqr(delta_x) +
385 // sqr(delta_y))'.
386
387 if (best_delta == 0 || delta_x < best_delta)
388 {
389 best_delta = delta_x;
390 best_delta_facing = delta_facing;
391 Tracker->hTarget = hShip;
392 }
393 }
394 }
395 UnlockElement (hShip);
396 }
397
398 if (best_delta_facing > 0)
399 {
400 COUNT facing;
401
402 facing = *pfacing;
403 if (best_delta_facing == ANGLE_TO_FACING (HALF_CIRCLE))
404 facing += (((BYTE)TFB_Random () & 1) << 1) - 1;
405 else if (best_delta_facing < ANGLE_TO_FACING (HALF_CIRCLE))
406 ++facing;
407 else
408 --facing;
409 *pfacing = NORMALIZE_FACING (facing);
410 }
411
412 return (best_delta_facing);
413 }
414
415