1 /*
2 MOTION_SENSOR.C
3 
4 	Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
5 	and the "Aleph One" developers.
6 
7 	This program is free software; you can redistribute it and/or modify
8 	it under the terms of the GNU General Public License as published by
9 	the Free Software Foundation; either version 3 of the License, or
10 	(at your option) any later version.
11 
12 	This program is distributed in the hope that it will be useful,
13 	but WITHOUT ANY WARRANTY; without even the implied warranty of
14 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 	GNU General Public License for more details.
16 
17 	This license is contained in the file "COPYING",
18 	which is included with this source code; it is available online at
19 	http://www.gnu.org/licenses/gpl.html
20 
21 Saturday, June 11, 1994 1:37:32 AM
22 
23 Friday, September 16, 1994 2:04:47 PM  (Jason')
24 	removed all get_shape_information() calls.
25 Monday, December 5, 1994 9:19:55 PM  (Jason)
26 	flickers in magnetic environments
27 
28 Feb 18, 2000 (Loren Petrich):
29 	Made VacBobs display properly
30 
31 Mar 23, 2000 (Loren Petrich):
32 	Made motion-sensor monster typing more generic;
33 	since it's now read off of a table, it can easily be changed
34 	without rebuilding the app.
35 
36 Sep 2, 2000 (Loren Petrich):
37 	Idiot-proofed the shapes display, since the shape accessor
38 	now returns NULL pointers for nonexistent bitmaps.
39 */
40 
41 #include "cseries.h"
42 #include "map.h"
43 #include "monsters.h"
44 #include "render.h"
45 #include "interface.h"
46 #include "motion_sensor.h"
47 #include "player.h"
48 #include "network_games.h"
49 #include "InfoTree.h"
50 
51 #include "HUDRenderer_SW.h"
52 #include "HUDRenderer_OGL.h"
53 #include "HUDRenderer_Lua.h"
54 
55 #include <math.h>
56 #include <string.h>
57 #include <stdlib.h>
58 
59 static short MonsterDisplays[NUMBER_OF_MONSTER_TYPES] =
60 {
61 	// Marine
62 	MType_Friend,
63 	// Ticks
64 	MType_Alien,
65 	MType_Alien,
66 	MType_Alien,
67 	// S'pht
68 	MType_Alien,
69 	MType_Alien,
70 	MType_Alien,
71 	MType_Alien,
72 	// Pfhor
73 	MType_Alien,
74 	MType_Alien,
75 	MType_Alien,
76 	MType_Alien,
77 	// Bob
78 	MType_Friend,
79 	MType_Friend,
80 	MType_Friend,
81 	MType_Friend,
82 	// Drone
83 	MType_Alien,
84 	MType_Alien,
85 	MType_Alien,
86 	MType_Alien,
87 	MType_Alien,
88 	// Cyborg
89 	MType_Alien,
90 	MType_Alien,
91 	MType_Alien,
92 	MType_Alien,
93 	// Enforcer
94 	MType_Alien,
95 	MType_Alien,
96 	// Hunter
97 	MType_Alien,
98 	MType_Alien,
99 	// Trooper
100 	MType_Alien,
101 	MType_Alien,
102 	// Big Cyborg, Hunter
103 	MType_Alien,
104 	MType_Alien,
105 	// F'lickta
106 	MType_Alien,
107 	MType_Alien,
108 	MType_Alien,
109 	// S'pht'Kr
110 	MType_Alien,
111 	MType_Alien,
112 	// Juggernauts
113 	MType_Alien,
114 	MType_Alien,
115 	// Tiny ones
116 	MType_Alien,
117 	MType_Alien,
118 	MType_Alien,
119 	// VacBobs
120 	MType_Friend,
121 	MType_Friend,
122 	MType_Friend,
123 	MType_Friend,
124 };
125 
126 /*
127 ??monsters which translate only on frame changes periodically drop off the motion sensor making it appear jumpy
128 
129 //when entities first appear on the sensor they are initially visible
130 //entities are drawn one at a time, so the dark spot from one entity can cover the light spot from another
131 //sometimes the motion sensor shapes are not masked correctly (fixed from SHAPES.C)
132 //must save old points in real world coordinates because entities get redrawn when the player turns
133 */
134 
135 #define MAXIMUM_MOTION_SENSOR_ENTITIES 12
136 
137 #define NUMBER_OF_PREVIOUS_LOCATIONS 6
138 
139 struct motion_sensor_definition {
140 	uint16 update_frequency;
141 	uint16 rescan_frequency;
142 	uint16 range;
143 	int16 scale;
144 };
145 
146 struct motion_sensor_definition motion_sensor_settings = {
147 	5,  // update_frequency
148 	15, // rescan_frequency
149 	(8 * WORLD_ONE), // range
150 	64 // scale
151 };
152 
153 #define MOTION_SENSOR_UPDATE_FREQUENCY motion_sensor_settings.update_frequency
154 #define MOTION_SENSOR_RESCAN_FREQUENCY motion_sensor_settings.rescan_frequency
155 #define MOTION_SENSOR_RANGE motion_sensor_settings.range
156 #define MOTION_SENSOR_SCALE motion_sensor_settings.scale
157 
158 #define OBJECT_IS_VISIBLE_TO_MOTION_SENSOR(o) true
159 
160 #define FLICKER_FREQUENCY 0xe
161 
162 /* ---------- structures */
163 
164 /* an entity can�t just be jerked from the array, because his signal should fade out, so we
165 	mark him as �being removed� and wait until his last signal fades away to actually remove
166 	him */
167 #define SLOT_IS_BEING_REMOVED_BIT 0x4000
168 #define SLOT_IS_BEING_REMOVED(e) ((e)->flags&(uint16)SLOT_IS_BEING_REMOVED_BIT)
169 #define MARK_SLOT_AS_BEING_REMOVED(e) ((e)->flags|=(uint16)SLOT_IS_BEING_REMOVED_BIT)
170 
171 struct entity_data
172 {
173 	uint16 flags; /* [slot_used.1] [slot_being_removed.1] [unused.14] */
174 
175 	short monster_index;
176 	shape_descriptor shape;
177 
178 	short remove_delay; /* only valid if this entity is being removed [0,NUMBER_OF_PREVIOUS_LOCATIONS) */
179 
180 	point2d previous_points[NUMBER_OF_PREVIOUS_LOCATIONS];
181 	bool visible_flags[NUMBER_OF_PREVIOUS_LOCATIONS];
182 
183 	world_point3d last_location;
184 	angle last_facing;
185 };
186 
187 struct region_data
188 {
189 	short x0, x1;
190 };
191 
192 /* ---------- globals */
193 
194 static short motion_sensor_player_index;
195 
196 static short motion_sensor_side_length;
197 
198 static struct region_data *sensor_region;
199 static struct entity_data *entities;
200 static short network_compass_state;
201 
202 static shape_descriptor mount_shape, virgin_mount_shapes, compass_shapes;
203 static shape_descriptor alien_shapes, friendly_shapes, enemy_shapes;
204 
205 static bool motion_sensor_changed;
206 static int32 ticks_since_last_update, ticks_since_last_rescan;
207 
208 /* ---------- private code */
209 
210 static void erase_all_entity_blips(void);
211 
212 static void precalculate_sensor_region(short side_length);
213 
214 static short find_or_add_motion_sensor_entity(short monster_index);
215 
216 static shape_descriptor get_motion_sensor_entity_shape(short monster_index);
217 
218 static void clipped_transparent_sprite_copy(struct bitmap_definition *source, struct bitmap_definition *destination,
219 	struct region_data *region, short x0, short y0);
220 static void bitmap_window_copy(struct bitmap_definition *source, struct bitmap_definition *destination,
221 	short x0, short y0, short x1, short y1);
222 static void unclipped_solid_sprite_copy(struct bitmap_definition *source,
223 	struct bitmap_definition *destination, short x0, short y0);
224 
225 /* ---------- code */
226 
initialize_motion_sensor(shape_descriptor mount,shape_descriptor virgin_mounts,shape_descriptor aliens,shape_descriptor friends,shape_descriptor enemies,shape_descriptor compasses,short side_length)227 void initialize_motion_sensor(
228 	shape_descriptor mount,
229 	shape_descriptor virgin_mounts,
230 	shape_descriptor aliens,
231 	shape_descriptor friends,
232 	shape_descriptor enemies,
233 	shape_descriptor compasses,
234 	short side_length)
235 {
236 	mount_shape= mount;
237 	virgin_mount_shapes= virgin_mounts;
238 	enemy_shapes= enemies;
239 	friendly_shapes= friends;
240 	alien_shapes= aliens;
241 	compass_shapes= compasses;
242 
243 	entities= new entity_data[MAXIMUM_MOTION_SENSOR_ENTITIES];
244 	assert(entities);
245 	for (int i = 0; i < MAXIMUM_MOTION_SENSOR_ENTITIES; i++) {
246 	  entities[i].flags = 0;
247 	}
248 
249 	sensor_region= new region_data[side_length];
250 	assert(sensor_region);
251 
252 	/* precalculate the sensor region */
253 	precalculate_sensor_region(side_length);
254 
255 	/* reset_motion_sensor() should be called before the motion sensor is used, but after it�s
256 		shapes are loaded (because it will do bitmap copying) */
257 }
258 
259 extern bool shapes_file_is_m1();
260 
reset_motion_sensor(short player_index)261 void reset_motion_sensor(
262 	short player_index)
263 {
264 	struct bitmap_definition *mount, *virgin_mount;
265 	short i;
266 
267 	motion_sensor_player_index= player_index;
268 	ticks_since_last_update= ticks_since_last_rescan= 0;
269 
270 	if (!shapes_file_is_m1())
271 	{
272 		get_shape_bitmap_and_shading_table(mount_shape, &mount, (void **) NULL, NONE);
273 		if (!mount) return;
274 		get_shape_bitmap_and_shading_table(virgin_mount_shapes, &virgin_mount, (void **) NULL, NONE);
275 		if (!virgin_mount) return;
276 
277 		assert(mount->width==virgin_mount->width);
278 		assert(mount->height==virgin_mount->height);
279 		bitmap_window_copy(virgin_mount, mount, 0, 0, mount->width, mount->height);
280 	}
281 
282 	for (i= 0; i<MAXIMUM_MOTION_SENSOR_ENTITIES; ++i) MARK_SLOT_AS_FREE(entities+i);
283 
284 	network_compass_state= _network_compass_all_off;
285 }
286 
287 extern bool MotionSensorActive;
288 
289 // regardless of frame rate, this will update the positions of the objects
290 // we are tracking (motion_sensor_scan() is called each tick)
motion_sensor_scan(void)291 void motion_sensor_scan(void)
292 {
293 	if ((GET_GAME_OPTIONS() & _motion_sensor_does_not_work) || !MotionSensorActive) {
294 		return;
295 	}
296 	if (m1_solo_player_in_terminal()) {
297 		return;
298 	}
299 
300 	struct object_data *owner_object= get_object_data(get_player_data(motion_sensor_player_index)->object_index);
301 
302 	/* if we need to scan for new objects, flood around the owner monster looking for other,
303 		visible monsters within our range */
304 	if ((--ticks_since_last_rescan) < 0)
305 	{
306 		struct monster_data *monster;
307 		short monster_index;
308 
309 		for (monster_index=0,monster=monsters;monster_index<MAXIMUM_MONSTERS_PER_MAP;++monster,++monster_index)
310 		{
311 			if (SLOT_IS_USED(monster)&&(MONSTER_IS_PLAYER(monster)||MONSTER_IS_ACTIVE(monster)))
312 			{
313 				struct object_data *object= get_object_data(monster->object_index);
314 				world_distance distance= guess_distance2d((world_point2d *) &object->location, (world_point2d *) &owner_object->location);
315 
316 				if (distance<MOTION_SENSOR_RANGE && OBJECT_IS_VISIBLE_TO_MOTION_SENSOR(object))
317 				{
318 //					dprintf("found valid monster #%d", monster_index);
319 					find_or_add_motion_sensor_entity(object->permutation);
320 					motion_sensor_changed = true;
321 				}
322 			}
323 		}
324 
325 		ticks_since_last_rescan= MOTION_SENSOR_RESCAN_FREQUENCY;
326 	}
327 
328 	if ((--ticks_since_last_update) < 0) {
329 		erase_all_entity_blips();
330 
331 		ticks_since_last_update = MOTION_SENSOR_UPDATE_FREQUENCY;
332 	}
333 }
334 
render_motion_sensor(short ticks_elapsed)335 void HUD_SW_Class::render_motion_sensor(short ticks_elapsed)
336 {
337 	// Clear the motion sensor and redraw blips
338 	struct bitmap_definition *mount, *virgin_mount;
339 
340 	get_shape_bitmap_and_shading_table(mount_shape, &mount, (void **) NULL, NONE);
341 	get_shape_bitmap_and_shading_table(virgin_mount_shapes, &virgin_mount, (void **) NULL, NONE);
342 	if (mount && virgin_mount)
343 		bitmap_window_copy(virgin_mount, mount, 0, 0, motion_sensor_side_length, motion_sensor_side_length);
344 
345 	draw_network_compass();
346 	draw_all_entity_blips();
347 }
348 
render_motion_sensor(short ticks_elapsed)349 void HUD_OGL_Class::render_motion_sensor(short ticks_elapsed)
350 {
351 	// Draw background
352 	screen_rectangle *r = get_interface_rectangle(_motion_sensor_rect);
353 	DrawShapeAtXY(BUILD_DESCRIPTOR(_collection_interface, _motion_sensor_virgin_mount), r->left, r->top);
354 
355 	// We allways draw all active entities because we have to update the
356 	// display on every frame
357 	/*if (dynamic_world->player_count > 1)*/
358 		draw_network_compass();
359 	draw_all_entity_blips();
360 }
361 
render_motion_sensor(short ticks_elapsed)362 void HUD_Lua_Class::render_motion_sensor(short ticks_elapsed)
363 {
364 	// If we need to update the motion sensor, draw all active entities
365 	draw_all_entity_blips();
366 }
367 
368 
369 /* the interface code will call this function and only draw the motion sensor if we return true */
motion_sensor_has_changed(void)370 bool motion_sensor_has_changed(void)
371 {
372 	bool changed = motion_sensor_changed;
373 	motion_sensor_changed = false;
374 	return changed;
375 }
376 
377 /* toggle through the ranges */
adjust_motion_sensor_range(void)378 void adjust_motion_sensor_range(void)
379 {
380 }
381 
382 /* ---------- private code */
383 
draw_network_compass(void)384 void HUD_Class::draw_network_compass(void)
385 {
386 #if !defined(DISABLE_NETWORKING)
387 	short new_state= get_network_compass_state(motion_sensor_player_index);
388 	short difference= (new_state^network_compass_state)|new_state;
389 
390 	if (difference&_network_compass_nw) draw_or_erase_unclipped_shape(36, 36, compass_shapes, (new_state&_network_compass_nw) != 0);
391 	if (difference&_network_compass_ne) draw_or_erase_unclipped_shape(61, 36, compass_shapes+1, (new_state&_network_compass_ne) != 0);
392 	if (difference&_network_compass_se) draw_or_erase_unclipped_shape(61, 61, compass_shapes+3, (new_state&_network_compass_se) != 0);
393 	if (difference&_network_compass_sw) draw_or_erase_unclipped_shape(36, 61, compass_shapes+2, (new_state&_network_compass_sw) != 0);
394 
395 	network_compass_state= new_state;
396 #endif // !defined(DISABLE_NETWORKING)
397 }
398 
erase_all_entity_blips(void)399 void erase_all_entity_blips(void)
400 {
401 	struct object_data *owner_object= get_object_data(get_player_data(motion_sensor_player_index)->object_index);
402 	struct entity_data *entity;
403 	short entity_index;
404 
405 	/* first erase all locations where the entity changed locations and then did not change
406 		locations, and erase it�s last location */
407 	for (entity_index=0,entity=entities;entity_index<MAXIMUM_MOTION_SENSOR_ENTITIES;++entity_index,++entity)
408 	{
409 		if (SLOT_IS_USED(entity))
410 		{
411 			motion_sensor_changed = true;
412 //			dprintf("entity #%d (%p) valid", entity_index, entity);
413 			/* see if our monster slot is free; if it is mark this entity as being removed; of
414 				course this isn�t wholly accurate and we might start tracking a new monster
415 				which has been placed in our old monster�s slot, but we eat that chance
416 				without remorse */
417 			if (SLOT_IS_USED(monsters+entity->monster_index))
418 			{
419 				struct object_data *object= get_object_data(get_monster_data(entity->monster_index)->object_index);
420 				world_distance distance= guess_distance2d((world_point2d *) &object->location, (world_point2d *) &owner_object->location);
421 
422 				/* verify that we�re still in range (and mark us as being removed if we�re not */
423 				if (distance>MOTION_SENSOR_RANGE || !OBJECT_IS_VISIBLE_TO_MOTION_SENSOR(object))
424 				{
425 //					dprintf("removed2");
426 					MARK_SLOT_AS_BEING_REMOVED(entity);
427 				}
428 			}
429 			else
430 			{
431 //				dprintf("removed1");
432 				MARK_SLOT_AS_BEING_REMOVED(entity);
433 			}
434 
435 			/* adjust the arrays to make room for new entries */
436 			memmove(entity->visible_flags+1, entity->visible_flags, (NUMBER_OF_PREVIOUS_LOCATIONS-1)*sizeof(bool));
437 			memmove(entity->previous_points+1, entity->previous_points, (NUMBER_OF_PREVIOUS_LOCATIONS-1)*sizeof(point2d));
438 			entity->visible_flags[0]= false;
439 
440 			/* if we�re not being removed, make room for a new location and calculate it */
441 			if (!SLOT_IS_BEING_REMOVED(entity))
442 			{
443 				struct monster_data *monster= get_monster_data(entity->monster_index);
444 				struct object_data *object= get_object_data(monster->object_index);
445 
446 				/* remember if this entity is visible or not */
447 				if (object->transfer_mode!=_xfer_invisibility && object->transfer_mode!=_xfer_subtle_invisibility &&
448 					(!(static_world->environment_flags&_environment_magnetic) || !((dynamic_world->tick_count+4*monster->object_index)&FLICKER_FREQUENCY)))
449 				{
450 					if (object->location.x!=entity->last_location.x || object->location.y!=entity->last_location.y ||
451 						object->location.z!=entity->last_location.z || object->facing!=entity->last_facing)
452 					{
453 						entity->visible_flags[0]= true;
454 
455 						entity->last_location= object->location;
456 						entity->last_facing= object->facing;
457 					}
458 				}
459 
460 				/* calculate the 2d position on the motion sensor */
461 				entity->previous_points[0].x= object->location.x;
462 				entity->previous_points[0].y= object->location.y;
463 				transform_point2d((world_point2d *)&entity->previous_points[0], (world_point2d *)&owner_object->location, NORMALIZE_ANGLE(owner_object->facing+QUARTER_CIRCLE));
464 				//entity->previous_points[0].x>>= MOTION_SENSOR_SCALE;
465 				entity->previous_points[0].x /= MOTION_SENSOR_SCALE;
466 			//	entity->previous_points[0].y>>= MOTION_SENSOR_SCALE;
467 				entity->previous_points[0].y /= MOTION_SENSOR_SCALE;
468 			}
469 			else
470 			{
471 				/* if this is the last point of an entity which was being removed; mark it as unused */
472 				if ((entity->remove_delay+= 1)>=NUMBER_OF_PREVIOUS_LOCATIONS)
473 				{
474 					MARK_SLOT_AS_FREE(entity);
475 				}
476 			}
477 		}
478 	}
479 }
480 
draw_all_entity_blips(void)481 void HUD_Class::draw_all_entity_blips(void)
482 {
483 	struct entity_data *entity;
484 	short entity_index, intensity;
485 
486 	for (intensity=NUMBER_OF_PREVIOUS_LOCATIONS-1;intensity>=0;--intensity)
487 	{
488 		for (entity_index=0,entity=entities;entity_index<MAXIMUM_MOTION_SENSOR_ENTITIES;++entity_index,++entity)
489 		{
490 			if (SLOT_IS_USED(entity))
491 			{
492 				if (entity->visible_flags[intensity])
493 				{
494 					draw_entity_blip(&entity->previous_points[intensity], entity->shape + intensity);
495 				}
496 			}
497 		}
498 	}
499 }
500 
draw_all_entity_blips(void)501 void HUD_Lua_Class::draw_all_entity_blips(void)
502 {
503 	struct entity_data *entity;
504 	short entity_index, intensity;
505 
506 	clear_entity_blips();
507 	for (intensity=NUMBER_OF_PREVIOUS_LOCATIONS-1;intensity>=0;--intensity)
508 	{
509 		for (entity_index=0,entity=entities;entity_index<MAXIMUM_MOTION_SENSOR_ENTITIES;++entity_index,++entity)
510 		{
511 			if (SLOT_IS_USED(entity))
512 			{
513 				if (entity->visible_flags[intensity])
514 				{
515 					short display_type = MType_Alien;
516 					if (entity->shape == friendly_shapes)
517 						display_type = MType_Friend;
518 					else if (entity->shape == enemy_shapes)
519 						display_type = MType_Enemy;
520 					add_entity_blip(display_type, intensity,
521 													entity->previous_points[intensity].x * MOTION_SENSOR_SCALE,
522 													entity->previous_points[intensity].y * MOTION_SENSOR_SCALE);
523 				}
524 			}
525 		}
526 	}
527 }
528 
draw_or_erase_unclipped_shape(short x,short y,shape_descriptor shape,bool draw)529 void HUD_SW_Class::draw_or_erase_unclipped_shape(short x, short y, shape_descriptor shape, bool draw)
530 {
531 	struct bitmap_definition *mount, *virgin_mount, *blip;
532 
533 	get_shape_bitmap_and_shading_table(mount_shape, &mount, (void **) NULL, NONE);
534 	if (!mount) return;
535 	get_shape_bitmap_and_shading_table(virgin_mount_shapes, &virgin_mount, (void **) NULL, NONE);
536 	if (!virgin_mount) return;
537 	get_shape_bitmap_and_shading_table(shape, &blip, (void **) NULL, NONE);
538 	if (!blip) return;
539 
540 	draw ?
541 		unclipped_solid_sprite_copy(blip, mount, x, y) :
542 		bitmap_window_copy(virgin_mount, mount, x, y, x+blip->width, y+blip->height);
543 }
544 
draw_or_erase_unclipped_shape(short x,short y,shape_descriptor shape,bool draw)545 void HUD_OGL_Class::draw_or_erase_unclipped_shape(short x, short y, shape_descriptor shape, bool draw)
546 {
547 	if (draw) {
548 		screen_rectangle *r = get_interface_rectangle(_motion_sensor_rect);
549 		DrawShapeAtXY(shape, x + r->left, y + r->top);
550 	}
551 }
552 
draw_entity_blip(point2d * location,shape_descriptor shape)553 void HUD_SW_Class::draw_entity_blip(point2d *location, shape_descriptor shape)
554 {
555 	bitmap_definition *mount, *blip;
556 
557 	get_shape_bitmap_and_shading_table(mount_shape, &mount, (void **) NULL, NONE);
558 	if (!mount) return;
559 	get_shape_bitmap_and_shading_table(shape, &blip, (void **) NULL, NONE);
560 	if (!blip) return;
561 
562 	clipped_transparent_sprite_copy(blip, mount, sensor_region,
563 		location->x + (motion_sensor_side_length>>1) - (blip->width>>1),
564 		location->y + (motion_sensor_side_length>>1) - (blip->height>>1));
565 }
566 
draw_entity_blip(point2d * location,shape_descriptor shape)567 void HUD_OGL_Class::draw_entity_blip(point2d *location, shape_descriptor shape)
568 {
569 	bitmap_definition *blip;
570 	get_shape_bitmap_and_shading_table(shape, &blip, (void **) NULL, NONE);
571 	if (!blip) return;
572 
573 	screen_rectangle *r = get_interface_rectangle(_motion_sensor_rect);
574 	int x = location->x, y = location->y;
575 	int c_x = r->left + (motion_sensor_side_length >> 1);
576 	int c_y = r->top + (motion_sensor_side_length >> 1);
577 	SetClipPlane(x, y, c_x, c_y, motion_sensor_side_length >> 1);
578 	DrawShapeAtXY(shape,
579 		x + c_x - (blip->width >> 1),
580 		y + c_y - (blip->height >> 1),
581 		true);
582 	DisableClipPlane();
583 }
584 
585 /* if we find an entity that is being removed, we continue with the removal process and ignore
586 	the new signal; the new entity will probably not be added to the sensor again for a full
587 	second or so (the range should be set so that this is reasonably hard to do) */
find_or_add_motion_sensor_entity(short monster_index)588 static short find_or_add_motion_sensor_entity(
589 	short monster_index)
590 {
591 	struct entity_data *entity;
592 	short entity_index, best_unused_index;
593 
594 	best_unused_index= NONE;
595 	for (entity_index=0,entity=entities;entity_index<MAXIMUM_MOTION_SENSOR_ENTITIES;++entity_index,++entity)
596 	{
597 		if (SLOT_IS_USED(entity))
598 		{
599 			if (entity->monster_index==monster_index) break;
600 		}
601 		else
602 		{
603 			if (best_unused_index==NONE) best_unused_index= entity_index;
604 		}
605 	}
606 
607 	if (entity_index==MAXIMUM_MOTION_SENSOR_ENTITIES)
608 	{
609 		/* not found; add new entity if we can */
610 
611 		if (best_unused_index!=NONE)
612 		{
613 			struct monster_data *monster= get_monster_data(monster_index);
614 			struct object_data *object= get_object_data(monster->object_index);
615 			short i;
616 
617 			entity= entities+best_unused_index;
618 
619 			entity->flags= 0;
620 			entity->monster_index= monster_index;
621 			entity->shape= get_motion_sensor_entity_shape(monster_index);
622 			for (i=0;i<NUMBER_OF_PREVIOUS_LOCATIONS;++i) entity->visible_flags[i]= false;
623 			entity->last_location= object->location;
624 			entity->last_facing= object->facing;
625 			entity->remove_delay= 0;
626 			MARK_SLOT_AS_USED(entity);
627 
628 //			dprintf("new index, pointer: %d, %p", best_unused_index, entity);
629 		}
630 
631 		entity_index= best_unused_index;
632 	}
633 
634 	return entity_index;
635 }
636 
precalculate_sensor_region(short side_length)637 static void precalculate_sensor_region(
638 	short side_length)
639 {
640 	double half_side_length= side_length/2.0;
641 	double r= half_side_length + 1.0;
642 	short i;
643 
644 	/* save length for assert() during rendering */
645 	motion_sensor_side_length= side_length;
646 
647 	/* precompute [x0,x1] clipping values for each y value in the circular sensor */
648 	for (i=0;i<side_length;++i)
649 	{
650 		double y= i - half_side_length;
651 		double x= sqrt(r*r-y*y);
652 
653 		if (x>=r) x= r-1.0;
654 		sensor_region[i].x0= int16(half_side_length-x);
655 		sensor_region[i].x1= int16(x+half_side_length);
656 	}
657 }
658 
659 /* (x0,y0) and (x1,y1) specify a window to be copied from the source to (x2,y2) in the destination.
660 	pixel index zero is transparent (handles clipping) */
bitmap_window_copy(struct bitmap_definition * source,struct bitmap_definition * destination,short x0,short y0,short x1,short y1)661 static void bitmap_window_copy(
662 	struct bitmap_definition *source,
663 	struct bitmap_definition *destination,
664 	short x0,
665 	short y0,
666 	short x1,
667 	short y1)
668 {
669 	short count;
670 	short y;
671 
672 	assert(x0<=x1&&y0<=y1);
673 
674 	if (x0<0) x0= 0;
675 	if (y0<0) y0= 0;
676 	if (x1>source->width) x1= source->width;
677 	if (y1>source->height) y1= source->height;
678 
679 	assert(source->width==destination->width);
680 	assert(source->height==destination->height);
681 	assert(destination->width==motion_sensor_side_length);
682 	assert(destination->height==motion_sensor_side_length);
683 
684 	for (y=y0;y<y1;++y)
685 	{
686 		pixel8 *read= source->row_addresses[y]+x0;
687 		pixel8 *write= destination->row_addresses[y]+x0;
688 
689 		for (count=x1-x0;count>0;--count) *write++= *read++;
690 	}
691 }
692 
clipped_transparent_sprite_copy(struct bitmap_definition * source,struct bitmap_definition * destination,struct region_data * region,short x0,short y0)693 static void clipped_transparent_sprite_copy(
694 	struct bitmap_definition *source,
695 	struct bitmap_definition *destination,
696 	struct region_data *region,
697 	short x0,
698 	short y0)
699 {
700 	short height, y;
701 
702 	assert(destination->width==motion_sensor_side_length);
703 	assert(destination->height==motion_sensor_side_length);
704 
705 	y= 0;
706 	height= source->height;
707 	if (y0+height>destination->height) height= destination->height-y0;
708 	if (y0<0)
709 	{
710 		y= -y0;
711 		height+= y0;
712 	}
713 
714 	while ((height-= 1)>=0)
715 	{
716 		pixel8 pixel, *read, *write;
717 		short width= source->width;
718 		short clip_left= region[y0+y].x0, clip_right= region[y0+y].x1;
719 		short offset= 0;
720 
721 		if (x0<clip_left) offset= clip_left-x0, width-= offset;
722 		if (x0+offset+width>clip_right) width= clip_right-x0-offset;
723 
724 		assert(y>=0&&y<source->height);
725 		assert(y0+y>=0&&y0+y<destination->height);
726 
727 		read= source->row_addresses[y]+offset;
728 		write= destination->row_addresses[y0+y]+x0+offset;
729 
730 		while ((width-= 1)>=0)
731 		{
732 			if ((pixel= *read++)!=0) {
733 				*write++= pixel;
734 			} else {
735 				write+= 1;
736 			}
737 		}
738 
739 		y+= 1;
740 	}
741 }
742 
unclipped_solid_sprite_copy(struct bitmap_definition * source,struct bitmap_definition * destination,short x0,short y0)743 static void unclipped_solid_sprite_copy(
744 	struct bitmap_definition *source,
745 	struct bitmap_definition *destination,
746 	short x0,
747 	short y0)
748 {
749 	short height, y;
750 
751 	y= 0;
752 	height= source->height;
753 
754 	while ((height-= 1)>=0)
755 	{
756 		pixel8 *read, *write;
757 		short width= source->width;
758 
759 		assert(y>=0&&y<source->height);
760 		assert(y0+y>=0&&y0+y<destination->height);
761 
762 		read= source->row_addresses[y];
763 		write= destination->row_addresses[y0+y]+x0;
764 
765 		while ((width-= 1)>=0) *write++= *read++;
766 
767 		y+= 1;
768 	}
769 }
770 
get_motion_sensor_entity_shape(short monster_index)771 static shape_descriptor get_motion_sensor_entity_shape(
772 	short monster_index)
773 {
774 	struct monster_data *monster= get_monster_data(monster_index);
775 	shape_descriptor shape;
776 
777 	if (MONSTER_IS_PLAYER(monster))
778 	{
779 		struct player_data *player= get_player_data(monster_index_to_player_index(monster_index));
780 		struct player_data *owner= get_player_data(motion_sensor_player_index);
781 
782 		shape= ((player->team==owner->team && !(GET_GAME_OPTIONS()&_force_unique_teams)) || GET_GAME_TYPE()==_game_of_cooperative_play) ?
783 			friendly_shapes : enemy_shapes;
784 	}
785 	else
786 	{
787 		/*
788 		switch (monster->type)
789 		{
790 			case _civilian_crew:
791 			case _civilian_science:
792 			case _civilian_security:
793 			case _civilian_assimilated:
794 			// LP additions: the VacBobs
795 			case _civilian_fusion_crew:
796 			case _civilian_fusion_science:
797 			case _civilian_fusion_security:
798 			case _civilian_fusion_assimilated:
799 				shape= friendly_shapes;
800 				break;
801 
802 			default:
803 				shape= alien_shapes;
804 				break;
805 		}
806 		*/
807 		switch(MonsterDisplays[monster->type])
808 		{
809 		case MType_Friend:
810 			shape = friendly_shapes;
811 			break;
812 
813 		case MType_Alien:
814 			shape = alien_shapes;
815 			break;
816 
817 		case MType_Enemy:
818 		default:
819 			shape = enemy_shapes;
820 			break;
821 		}
822 	}
823 
824 	return shape;
825 }
826 
827 // XML elements for parsing motion-sensor specification
828 short *OriginalMonsterDisplays = NULL;
829 struct motion_sensor_definition *original_motion_sensor_settings = NULL;
830 
reset_mml_motion_sensor()831 void reset_mml_motion_sensor()
832 {
833 	if (original_motion_sensor_settings) {
834 		motion_sensor_settings = *original_motion_sensor_settings;
835 		free(original_motion_sensor_settings);
836 		original_motion_sensor_settings = NULL;
837 	}
838 
839 	if (OriginalMonsterDisplays) {
840 		for (int i = 0; i < NUMBER_OF_MONSTER_TYPES; i++)
841 			MonsterDisplays[i] = OriginalMonsterDisplays[i];
842 		free(OriginalMonsterDisplays);
843 		OriginalMonsterDisplays = NULL;
844 	}
845 }
846 
parse_mml_motion_sensor(const InfoTree & root)847 void parse_mml_motion_sensor(const InfoTree& root)
848 {
849 	// back up old values first
850 	if (!original_motion_sensor_settings) {
851 		original_motion_sensor_settings = (struct motion_sensor_definition *) malloc(sizeof(struct motion_sensor_definition));
852 		assert(original_motion_sensor_settings);
853 		*original_motion_sensor_settings = motion_sensor_settings;
854 	}
855 
856 	if (!OriginalMonsterDisplays) {
857 		OriginalMonsterDisplays = (short *) malloc(sizeof(short) * NUMBER_OF_MONSTER_TYPES);
858 		assert(OriginalMonsterDisplays);
859 		for (int i = 0; i < NUMBER_OF_MONSTER_TYPES; i++)
860 			OriginalMonsterDisplays[i] = MonsterDisplays[i];
861 	}
862 
863 	root.read_attr("scale", MOTION_SENSOR_SCALE);
864 	short range;
865 	if (root.read_wu("range", range))
866 		MOTION_SENSOR_RANGE = range;
867 	root.read_attr("update_frequency", MOTION_SENSOR_UPDATE_FREQUENCY);
868 	root.read_attr("rescan_frequency", MOTION_SENSOR_RESCAN_FREQUENCY);
869 
870 	BOOST_FOREACH(InfoTree assign, root.children_named("assign"))
871 	{
872 		int16 index;
873 		if (!assign.read_indexed("monster", index, NUMBER_OF_MONSTER_TYPES))
874 			continue;
875 		assign.read_indexed("type", MonsterDisplays[index], NUMBER_OF_MDISPTYPES);
876 	}
877 }
878