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