1 /*
2 DEVICES.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 Sunday, December 5, 1993 2:48:44 PM
22 
23 Tuesday, December 7, 1993 11:12:25 PM
24 	changed to be Jason compatible, open/close doors, and nixed gratuitous enum.
25 Tuesday, January 4, 1994 10:36:08 AM
26 	opening doors can wake monsters.
27 Sunday, September 18, 1994 6:23:04 PM  (alain)
28 	much of control panel code has been rewritten. no longer use composite sides,
29 	but a flag in the side data structure. some control panels work over time (refueling)
30 	and there are on/off textures associated with each control panel. and sounds.
31 Friday, June 9, 1995 11:43:37 AM  (Jason')
32 	destroy-able switches.
33 Wednesday, June 21, 1995 8:31:57 AM  (Jason)
34 	tag switches.
35 
36 Jan 30, 2000 (Loren Petrich):
37 	Changed "class" to "_class" to make data structures more C++-friendly
38 	Removed some "static" declarations that conflict with "extern"
39 
40 Feb 3, 2000 (Loren Petrich):
41 	Added Jjaro control panels; they appear to be a clone of the sewage ones
42 
43 Feb. 4, 2000 (Loren Petrich):
44 	Changed halt() to assert(false) for better debugging
45 
46 May 26, 2000 (Loren Petrich):
47 	Added XML shapes support; had recently added XML configuration in general
48 
49 June 3, 2000 (Loren Petrich):
50 	Idiot-proofed the control-panels accessor; it now returns NULL if an index is out of range.
51 
52 Aug 10, 2000 (Loren Petrich):
53 	Added Chris Pruett's Pfhortran changes
54 
55 Feb 3, 2003 (Woody Zenfell):
56         Support for network saved-games
57 */
58 
59 #include "cseries.h"
60 
61 #include "map.h"
62 #include "monsters.h"
63 #include "interface.h"
64 #include "player.h"
65 #include "platforms.h"
66 #include "SoundManager.h"
67 #include "computer_interface.h"
68 //#include "music.h"
69 #include "lightsource.h"
70 #include "game_window.h"
71 #include "items.h"
72 #include "shell.h"	// screen_printf()
73 //MH: Lua scripting
74 #include "lua_script.h"
75 #include "InfoTree.h"
76 
77 #include <string.h>
78 #include <limits.h>
79 
80 /* ---------- constants */
81 
82 #define OXYGEN_RECHARGE_FREQUENCY 0
83 #define ENERGY_RECHARGE_FREQUENCY 0
84 
85 #define MAXIMUM_PLATFORM_ACTIVATION_RANGE (3*WORLD_ONE)
86 #define MAXIMUM_CONTROL_ACTIVATION_RANGE (WORLD_ONE+WORLD_ONE_HALF)
87 #define OBJECT_RADIUS 50
88 
89 #define MINIMUM_RESAVE_TICKS (2*TICKS_PER_SECOND)
90 
91 // For detecting double-save (overwrite) in a netgame
92 enum {
93         kDoubleClickTicks = 10
94 };
95 
96 /* ---------- structures */
97 
98 enum // control panel sounds
99 {
100 	_activating_sound,
101 	_deactivating_sound,
102 	_unusuable_sound,
103 
104 	NUMBER_OF_CONTROL_PANEL_SOUNDS
105 };
106 
107 
108 struct control_panel_definition
109 {
110 	int16 _class;
111 	uint16 flags;
112 
113 	int16 collection;
114 	int16 active_shape, inactive_shape;
115 
116 	int16 sounds[NUMBER_OF_CONTROL_PANEL_SOUNDS];
117 	_fixed sound_frequency;
118 
119 	int16 item;
120 };
121 
122 /* ---------- globals */
123 
124 #define NUMBER_OF_CONTROL_PANEL_DEFINITIONS (sizeof(control_panel_definitions)/sizeof(struct control_panel_definition))
125 
126 static struct control_panel_definition control_panel_definitions[]=
127 {
128 	// _collection_walls1 -- LP: water
129 	{_panel_is_oxygen_refuel, 0, _collection_walls1, 2, 3, {_snd_oxygen_refuel, NONE, NONE}, FIXED_ONE, NONE},
130 	{_panel_is_shield_refuel, 0, _collection_walls1, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE, NONE},
131 	{_panel_is_double_shield_refuel, 0, _collection_walls1, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/8, NONE},
132 	{_panel_is_tag_switch, 0, _collection_walls1, 0, 1, {_snd_chip_insertion, NONE, NONE}, FIXED_ONE, _i_uplink_chip},
133 	{_panel_is_light_switch, 0, _collection_walls1, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
134 	{_panel_is_platform_switch, 0, _collection_walls1, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
135 	{_panel_is_tag_switch, 0, _collection_walls1, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
136 	{_panel_is_pattern_buffer, 0, _collection_walls1, 4, 4, {_snd_pattern_buffer, NONE, NONE}, FIXED_ONE, NONE},
137 	{_panel_is_computer_terminal, 0, _collection_walls1, 4, 4, {NONE, NONE, NONE}, FIXED_ONE, NONE},
138 	{_panel_is_tag_switch, 0, _collection_walls1, 1, 0, {_snd_destroy_control_panel, NONE, NONE}, FIXED_ONE, NONE},
139 
140 	// _collection_walls2 -- LP: lava
141 	{_panel_is_shield_refuel, 0, _collection_walls2, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE, NONE},
142 	{_panel_is_double_shield_refuel, 0, _collection_walls2, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/8, NONE},
143 	{_panel_is_triple_shield_refuel, 0, _collection_walls2, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/4, NONE},
144 	{_panel_is_light_switch, 0, _collection_walls2, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
145 	{_panel_is_platform_switch, 0, _collection_walls2, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
146 	{_panel_is_tag_switch, 0, _collection_walls2, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
147 	{_panel_is_pattern_buffer, 0, _collection_walls2, 4, 4, {_snd_pattern_buffer, NONE, NONE}, FIXED_ONE, NONE},
148 	{_panel_is_computer_terminal, 0, _collection_walls2, 4, 4, {NONE, NONE, NONE}, FIXED_ONE, NONE},
149 	{_panel_is_oxygen_refuel, 0, _collection_walls2, 2, 3, {_snd_oxygen_refuel, NONE, NONE}, FIXED_ONE, NONE},
150 	{_panel_is_tag_switch, 0, _collection_walls2, 0, 1, {_snd_chip_insertion, NONE, NONE}, FIXED_ONE, _i_uplink_chip},
151 	{_panel_is_tag_switch, 0, _collection_walls2, 1, 0, {_snd_destroy_control_panel, NONE, NONE}, FIXED_ONE, NONE},
152 
153 	// _collection_walls3 -- LP: sewage
154 	{_panel_is_shield_refuel, 0, _collection_walls3, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE, NONE},
155 	{_panel_is_double_shield_refuel, 0, _collection_walls3, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/8, NONE},
156 	{_panel_is_triple_shield_refuel, 0, _collection_walls3, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/4, NONE},
157 	{_panel_is_light_switch, 0, _collection_walls3, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
158 	{_panel_is_platform_switch, 0, _collection_walls3, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
159 	{_panel_is_tag_switch, 0, _collection_walls3, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
160 	{_panel_is_pattern_buffer, 0, _collection_walls3, 4, 4, {_snd_pattern_buffer, NONE, NONE}, FIXED_ONE, NONE},
161 	{_panel_is_computer_terminal, 0, _collection_walls3, 4, 4, {NONE, NONE, NONE}, FIXED_ONE, NONE},
162 	{_panel_is_oxygen_refuel, 0, _collection_walls3, 2, 3, {_snd_oxygen_refuel, NONE, NONE}, FIXED_ONE, NONE},
163 	{_panel_is_tag_switch, 0, _collection_walls3, 0, 1, {_snd_chip_insertion, NONE, NONE}, FIXED_ONE, _i_uplink_chip},
164 	{_panel_is_tag_switch, 0, _collection_walls3, 1, 0, {_snd_destroy_control_panel, NONE, NONE}, FIXED_ONE, NONE},
165 
166 	// _collection_walls4 -- LP: really _collection_walls5 -- pfhor
167 	{_panel_is_shield_refuel, 0, _collection_walls5, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE, NONE},
168 	{_panel_is_double_shield_refuel, 0, _collection_walls5, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/8, NONE},
169 	{_panel_is_triple_shield_refuel, 0, _collection_walls5, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/4, NONE},
170 	{_panel_is_light_switch, 0, _collection_walls5, 0, 1, {_snd_pfhor_switch_on, _snd_pfhor_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
171 	{_panel_is_platform_switch, 0, _collection_walls5, 0, 1, {_snd_pfhor_switch_on, _snd_pfhor_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
172 	{_panel_is_tag_switch, 0, _collection_walls5, 0, 1, {_snd_pfhor_switch_on, _snd_pfhor_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
173 	{_panel_is_pattern_buffer, 0, _collection_walls5, 4, 4, {_snd_pattern_buffer, NONE, NONE}, FIXED_ONE, NONE},
174 	{_panel_is_computer_terminal, 0, _collection_walls5, 4, 4, {NONE, NONE, NONE}, FIXED_ONE, NONE},
175 	{_panel_is_oxygen_refuel, 0, _collection_walls5, 2, 3, {_snd_oxygen_refuel, NONE, NONE}, FIXED_ONE, NONE},
176 	{_panel_is_tag_switch, 0, _collection_walls5, 0, 1, {_snd_chip_insertion, NONE, NONE}, FIXED_ONE, _i_uplink_chip},
177 	{_panel_is_tag_switch, 0, _collection_walls5, 1, 0, {_snd_destroy_control_panel, NONE, NONE}, FIXED_ONE, NONE},
178 
179 	// LP addition: _collection_walls4 -- jjaro
180 	{_panel_is_shield_refuel, 0, _collection_walls4, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE, NONE},
181 	{_panel_is_double_shield_refuel, 0, _collection_walls4, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/8, NONE},
182 	{_panel_is_triple_shield_refuel, 0, _collection_walls4, 2, 3, {_snd_energy_refuel, NONE, NONE}, FIXED_ONE+FIXED_ONE/4, NONE},
183 	{_panel_is_light_switch, 0, _collection_walls4, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
184 	{_panel_is_platform_switch, 0, _collection_walls4, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
185 	{_panel_is_tag_switch, 0, _collection_walls4, 0, 1, {_snd_switch_on, _snd_switch_off, _snd_cant_toggle_switch}, FIXED_ONE, NONE},
186 	{_panel_is_pattern_buffer, 0, _collection_walls4, 4, 4, {_snd_pattern_buffer, NONE, NONE}, FIXED_ONE, NONE},
187 	{_panel_is_computer_terminal, 0, _collection_walls4, 4, 4, {NONE, NONE, NONE}, FIXED_ONE, NONE},
188 	{_panel_is_oxygen_refuel, 0, _collection_walls4, 2, 3, {_snd_oxygen_refuel, NONE, NONE}, FIXED_ONE, NONE},
189 	{_panel_is_tag_switch, 0, _collection_walls4, 0, 1, {_snd_chip_insertion, NONE, NONE}, FIXED_ONE, _i_uplink_chip},
190 	{_panel_is_tag_switch, 0, _collection_walls4, 1, 0, {_snd_destroy_control_panel, NONE, NONE}, FIXED_ONE, NONE},
191 };
192 
193 struct control_panel_settings_definition {
194 	// How far can one reach to activate the controls?
195 	short ReachDistance;
196 	short ReachHorizontal;
197 	// For recharging
198 	short SingleEnergy;
199 	short SingleEnergyRate;
200 	short DoubleEnergy;
201 	short DoubleEnergyRate;
202 	short TripleEnergy;
203 	short TripleEnergyRate;
204 };
205 
206 struct control_panel_settings_definition control_panel_settings = {
207 	MAXIMUM_CONTROL_ACTIVATION_RANGE, // ReachDistance
208 	2, // ReachHorizontal
209 	PLAYER_MAXIMUM_SUIT_ENERGY, // SingleEnergy
210 	1, // SingleEnergyRate
211 	2*PLAYER_MAXIMUM_SUIT_ENERGY, // DoubleEnergy
212 	2, // DoubleEnergyRate
213 	3*PLAYER_MAXIMUM_SUIT_ENERGY, // TripleEnergy
214 	3 // TripleEnergyRate
215 };
216 
217 /* ------------ private prototypes */
218 
219 control_panel_definition *get_control_panel_definition(
220 	const short control_panel_type);
221 
222 //static bool line_side_has_control_panel(short line_index, short polygon_index, short *side_index_with_panel);
223 static void	somebody_save_full_auto(player_data* inWhoSaved, bool inOverwrite);
224 static void	change_panel_state(short player_index, short panel_side_index);
225 void set_control_panel_texture(struct side_data *side);
226 
227 static bool line_is_within_range(short monster_index, short line_index, world_distance range);
228 
229 static bool switch_can_be_toggled(short line_index, bool player_hit, bool *should_destroy_switch);
230 
231 static void play_control_panel_sound(short side_index, short sound_index);
232 
233 static bool get_recharge_status(short side_index);
234 
235 /* ---------- code */
236 
237 /* set the initial states of all switches based on the objects they control */
initialize_control_panels_for_level(void)238 void initialize_control_panels_for_level(
239 	void)
240 {
241 	short side_index;
242 	struct side_data *side;
243 
244 	for (side_index= 0, side= map_sides; side_index<dynamic_world->side_count; ++side, ++side_index)
245 	{
246 		if (SIDE_IS_CONTROL_PANEL(side))
247 		{
248 			// LP change: modified previous fix so that it edits the side definition
249 			struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
250 			if (!definition)
251 			{
252 				SET_SIDE_CONTROL_PANEL(side,false);
253 				continue;
254 			}
255 			bool status= false;
256 
257 			switch (definition->_class)
258 			{
259 				case _panel_is_tag_switch:
260 					status= GET_CONTROL_PANEL_STATUS(side);
261 					// use default position
262 					break;
263 
264 				case _panel_is_light_switch:
265 					status= get_light_status(side->control_panel_permutation);
266 					break;
267 
268 				case _panel_is_platform_switch:
269 					if (platform_is_on(get_polygon_data(side->control_panel_permutation)->permutation)) status= true;
270 					break;
271 			}
272 
273 			SET_CONTROL_PANEL_STATUS(side, status);
274 			set_control_panel_texture(side);
275 		}
276 	}
277 }
278 
update_control_panels(void)279 void update_control_panels(
280 	void)
281 {
282 	short player_index;
283 	struct player_data *player;
284 
285 	for (player_index= 0, player= players; player_index<dynamic_world->player_count; ++player_index, ++player)
286 	{
287 		short side_index;
288 
289 		if ((side_index= player->control_panel_side_index)!=NONE)
290 		{
291 			struct side_data *side= get_side_data(player->control_panel_side_index);
292 			// LP change: idiot-proofing
293 			struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
294 			if (!definition) continue;
295 
296 			if(definition->_class == _panel_is_pattern_buffer)
297 			{	// Player was working on a full-auto save
298 				if(dynamic_world->tick_count - player->ticks_at_last_successful_save > kDoubleClickTicks)
299 				{	// no double-click - need to safe save
300 					somebody_save_full_auto(player, false);
301 				}
302 			}
303 			else
304 			{
305 				bool still_in_use= false;
306 
307 				if (player->variables.direction == player->variables.last_direction &&
308 					player->variables.last_position.x == player->variables.position.x &&
309 					player->variables.last_position.y == player->variables.position.y &&
310 					player->variables.last_position.z == player->variables.position.z)
311 				{
312 					switch (definition->_class)
313 					{
314 						case _panel_is_oxygen_refuel:
315 							if (!(dynamic_world->tick_count&OXYGEN_RECHARGE_FREQUENCY))
316 							{
317 								if (player->suit_oxygen<PLAYER_MAXIMUM_SUIT_OXYGEN)
318 								{
319 									player->suit_oxygen+= TICKS_PER_SECOND;
320 									mark_oxygen_display_as_dirty();
321 									still_in_use= true;
322 								}
323 							}
324 							break;
325 
326 						case _panel_is_shield_refuel:
327 						case _panel_is_double_shield_refuel:
328 						case _panel_is_triple_shield_refuel:
329 							if (!(dynamic_world->tick_count&ENERGY_RECHARGE_FREQUENCY))
330 							{
331 								short maximum, rate;
332 
333 								switch (definition->_class)
334 								{
335 									case _panel_is_shield_refuel:
336 										maximum= control_panel_settings.SingleEnergy;
337 										rate= control_panel_settings.SingleEnergyRate;
338 										break;
339 									case _panel_is_double_shield_refuel:
340 										maximum= control_panel_settings.DoubleEnergy;
341 										rate= control_panel_settings.DoubleEnergyRate;
342 										break;
343 									case _panel_is_triple_shield_refuel:
344 										maximum= control_panel_settings.TripleEnergy;
345 										rate= control_panel_settings.TripleEnergyRate;
346 										break;
347 									default:
348 										assert(false);
349 								}
350 								if (player->suit_energy<maximum)
351 								{
352 									player->suit_energy= CEILING(player->suit_energy+rate, maximum);
353 									mark_shield_display_as_dirty();
354 									still_in_use= true;
355 								}
356 							}
357 							break;
358 
359 						default:
360 							assert(false);
361 					}
362 				}
363 
364 				if (still_in_use)
365 				{
366 					set_control_panel_texture(side);
367 					play_control_panel_sound(side_index, _activating_sound);
368 				}
369 				else
370 				{
371 					change_panel_state(player_index, side_index);
372 					SoundManager::instance()->StopSound(NONE, definition->sounds[_activating_sound]);
373 				}
374 			}
375 		}
376 	}
377 }
378 
update_action_key(short player_index,bool triggered)379 void update_action_key(
380 	short player_index,
381 	bool triggered)
382 {
383 	short               object_index;
384 	short               target_type;
385 
386 	if(triggered)
387 	{
388 		object_index= find_action_key_target(player_index, MAXIMUM_ACTIVATION_RANGE, &target_type, true);
389 
390 		if(object_index != NONE)
391 		{
392 			switch(target_type)
393 			{
394 				case _target_is_platform:
395 					player_touch_platform_state(player_index, object_index);
396 					break;
397 				case _target_is_control_panel:
398 					change_panel_state(player_index, object_index);
399 					break;
400 
401 				case _target_is_unrecognized:
402 					break;
403 
404 				default:
405 					vhalt(csprintf(temporary, "%d is not a valid target type", target_type));
406 					break;
407 			}
408 		}
409 	}
410 }
411 
untoggled_repair_switches_on_level(bool only_last_switch)412 bool untoggled_repair_switches_on_level(
413 	bool only_last_switch)
414 {
415 	short side_index;
416 	struct side_data *side;
417 	bool untoggled_switch= false;
418 
419 	for (side_index= 0, side= map_sides; side_index<dynamic_world->side_count && (!untoggled_switch || only_last_switch); ++side_index, ++side)
420 	{
421 		if (SIDE_IS_CONTROL_PANEL(side) && SIDE_IS_REPAIR_SWITCH(side))
422 		{
423 			// LP change: idiot-proofing
424 			struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
425 			if (!definition) continue;
426 
427 			switch (definition->_class)
428 			{
429 				case _panel_is_platform_switch:
430 					untoggled_switch= platform_is_at_initial_state(get_polygon_data(side->control_panel_permutation)->permutation) ? true : false;
431 					break;
432 
433 				default:
434 					untoggled_switch= !GET_CONTROL_PANEL_STATUS(side);
435 					break;
436 			}
437 		}
438 	}
439 
440 	return untoggled_switch;
441 }
442 
assume_correct_switch_position(short switch_type,short permutation,bool new_state)443 void assume_correct_switch_position(
444 	short switch_type, /* platform or light */
445 	short permutation, /* platform or light index */ /* ghs: appears to be polygon, not platform */
446 	bool new_state)
447 {
448 	short side_index;
449 	struct side_data *side;
450 
451 	for (side_index= 0, side= map_sides; side_index<dynamic_world->side_count; ++side_index, ++side)
452 	{
453 		if (SIDE_IS_CONTROL_PANEL(side) && side->control_panel_permutation==permutation)
454 		{
455 			struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
456 			// LP change: idiot-proofing
457 			if (!definition) continue;
458 
459 			if (switch_type==definition->_class)
460 			{
461 				play_control_panel_sound(side_index, new_state ? _activating_sound : _deactivating_sound);
462 				SET_CONTROL_PANEL_STATUS(side, new_state);
463 				set_control_panel_texture(side);
464 			}
465 		}
466 	}
467 }
468 
try_and_toggle_control_panel(short polygon_index,short line_index,short projectile_index)469 void try_and_toggle_control_panel(
470 	short polygon_index,
471 	short line_index,
472 	short projectile_index)
473 {
474 	short side_index= find_adjacent_side(polygon_index, line_index);
475 
476 	if (side_index!=NONE)
477 	{
478 		struct side_data *side= get_side_data(side_index);
479 
480 		if (SIDE_IS_CONTROL_PANEL(side))
481 		{
482 			bool should_destroy_switch;
483 			if (switch_can_be_toggled(side_index, false, &should_destroy_switch))
484 			{
485 				if (should_destroy_switch) SET_SIDE_CONTROL_PANEL(side, false);
486 				bool make_sound = false, state= GET_CONTROL_PANEL_STATUS(side);
487 				struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
488 				// LP change: idiot-proofing
489 				if (!definition) return;
490 
491 				switch (definition->_class)
492 				{
493 					case _panel_is_tag_switch:
494 						state= !state;
495 						make_sound= set_tagged_light_statuses(side->control_panel_permutation, state);
496 						if (try_and_change_tagged_platform_states(side->control_panel_permutation, state)) make_sound= true;
497 						if (!side->control_panel_permutation) make_sound= true;
498 						if (make_sound)
499 						{
500 							SET_CONTROL_PANEL_STATUS(side, state);
501 							set_control_panel_texture(side);
502 						}
503 						L_Call_Projectile_Switch(side_index, projectile_index);
504 						break;
505 					case _panel_is_light_switch:
506 						state= !state;
507 						make_sound= set_light_status(side->control_panel_permutation, state);
508 
509 						L_Call_Projectile_Switch(side_index, projectile_index);
510 						break;
511 					case _panel_is_platform_switch:
512 						state= !state;
513 						make_sound= try_and_change_platform_state(get_polygon_data(side->control_panel_permutation)->permutation, state);
514 
515 						L_Call_Projectile_Switch(side_index, projectile_index);
516 						break;
517 				}
518 
519 				if (make_sound)
520 				{
521 					play_control_panel_sound(side_index, state ? _activating_sound : _deactivating_sound);
522 				}
523 			}
524 		}
525 	}
526 }
527 
get_panel_class(short panel_type)528 short get_panel_class(
529 	short panel_type)
530 {
531 	struct control_panel_definition *definition= get_control_panel_definition(panel_type);
532 
533 	return definition->_class;
534 }
535 
536 /* ---------- private code */
537 
get_control_panel_definition(const short control_panel_type)538 control_panel_definition *get_control_panel_definition(
539 	const short control_panel_type)
540 {
541 	return GetMemberWithBounds(control_panel_definitions,control_panel_type,NUMBER_OF_CONTROL_PANEL_DEFINITIONS);
542 }
543 
find_action_key_target(short player_index,world_distance range,short * target_type,bool perform_panel_actions)544 short find_action_key_target(
545 	short player_index,
546 	world_distance range,
547 	short *target_type,
548 	bool perform_panel_actions)
549 {
550 	struct player_data *player= get_player_data(player_index);
551 	short current_polygon= player->camera_polygon_index;
552 	world_point2d destination;
553 	bool done= false;
554 	short itemhit, line_index;
555 	struct polygon_data *polygon;
556 
557 	// In case we don't hit anything
558 	*target_type = _target_is_unrecognized;
559 
560 	/* Should we use this one, the physics one, or the object one? */
561 	ray_to_line_segment((world_point2d *) &player->location, &destination, player->facing, range);
562 
563 //	dprintf("#%d(#%d,#%d) --> (#%d,#%d) (#%d along #%d)", current_polygon, player->location.x, player->location.y, destination.x, destination.y, range, player->facing);
564 
565 	itemhit= NONE;
566 	while (!done)
567 	{
568 		line_index= find_line_crossed_leaving_polygon(current_polygon, (world_point2d *) &player->location, &destination);
569 
570 		if (line_index==NONE)
571 		{
572 			done= true;
573 		}
574 		else
575 		{
576 			short original_polygon;
577 
578 			(void)get_line_data(line_index); // verify line is valid
579 			original_polygon= current_polygon;
580 			current_polygon= find_adjacent_polygon(current_polygon, line_index);
581 
582 //			dprintf("leaving polygon #%d through line #%d to polygon #%d", original_polygon, line_index, current_polygon);
583 
584 			if (current_polygon!=NONE)
585 			{
586 				polygon= get_polygon_data(current_polygon);
587 
588 				/* We hit a platform */
589 				if (polygon->type==_polygon_is_platform && line_is_within_range(player->monster_index, line_index, MAXIMUM_PLATFORM_ACTIVATION_RANGE) &&
590 					platform_is_legal_player_target(polygon->permutation))
591 				{
592 //					dprintf("found platform #%d in %p", polygon->permutation, polygon);
593 					itemhit= polygon->permutation;
594 					*target_type= _target_is_platform;
595 					done= true;
596 				}
597 			}
598 			else
599 			{
600 				done= true;
601 			}
602 
603 			/* Slammed a wall */
604 			if (line_is_within_range(player->monster_index, line_index, control_panel_settings.ReachDistance))
605 			{
606 				if (line_side_has_control_panel(line_index, original_polygon, &itemhit))
607 				{
608 					bool should_destroy_panel;
609 					if (switch_can_be_toggled(itemhit, true, &should_destroy_panel))
610 					{
611 						if (should_destroy_panel && perform_panel_actions)
612 						{
613 							SET_SIDE_CONTROL_PANEL(get_side_data(itemhit), false);
614 						}
615 						*target_type= _target_is_control_panel;
616 						done= true;
617 					}
618 					else
619 					{
620 						if (perform_panel_actions)
621 						{
622 							play_control_panel_sound(itemhit, _unusuable_sound);
623 						}
624 						itemhit= NONE;
625 					}
626 				}
627 			}
628 		}
629 	}
630 
631 	return itemhit;
632 }
633 
line_is_within_range(short monster_index,short line_index,world_distance range)634 static bool line_is_within_range(
635 	short monster_index,
636 	short line_index,
637 	world_distance range)
638 {
639 	world_point3d monster_origin= get_object_data(get_monster_data(monster_index)->object_index)->location;
640 	world_point3d line_origin;
641 	world_distance radius, height;
642 	world_distance dx, dy, dz;
643 
644 	calculate_line_midpoint(line_index, &line_origin);
645 	get_monster_dimensions(monster_index, &radius, &height);
646 	monster_origin.z+= height>>1;
647 
648 	dx= monster_origin.x-line_origin.x;
649 	dy= monster_origin.y-line_origin.y;
650 	dz= control_panel_settings.ReachHorizontal*(monster_origin.z-line_origin.z); /* dz is weighted */
651 
652 	return isqrt(dx*dx + dy*dy + dz*dz)<range ? true : false;
653 }
654 
655 //tiennou: removed static for lua access
line_side_has_control_panel(short line_index,short polygon_index,short * side_index_with_panel)656 bool line_side_has_control_panel(
657 	short line_index,
658 	short polygon_index,
659 	short *side_index_with_panel)
660 {
661 	short             side_index = NONE;
662 	bool           has_panel = false;
663 	struct line_data  *line = get_line_data(line_index);
664 	struct side_data  *side = NULL;
665 
666 	if (line->clockwise_polygon_owner==polygon_index)
667 	{
668 		side_index = line->clockwise_polygon_side_index;
669 		if (side_index != NONE)
670 		{
671 			side = get_side_data(side_index);
672 		}
673 	}
674 	else
675 	{
676 		assert(line->counterclockwise_polygon_owner==polygon_index);
677 		side_index = line->counterclockwise_polygon_side_index;
678 		if (side_index != NONE)
679 		{
680 			side= get_side_data(side_index);
681 		}
682 	}
683 
684 	if (side != NULL && SIDE_IS_CONTROL_PANEL(side))
685 	{
686 		*side_index_with_panel = side_index;
687 		has_panel = true;
688 	}
689 
690 	return has_panel;
691 }
692 
693 static void
somebody_save_full_auto(player_data * inWhoSaved,bool inOverwrite)694 somebody_save_full_auto(player_data* inWhoSaved, bool inOverwrite)
695 {
696         play_control_panel_sound(inWhoSaved->control_panel_side_index, _activating_sound);
697 
698         // These need to happen before the save so that a freshly restored player
699         // doesn't end up automatically saving the game :)
700         inWhoSaved->control_panel_side_index = NONE;
701         inWhoSaved->ticks_at_last_successful_save = dynamic_world->tick_count;
702 
703         if(inWhoSaved == local_player)
704         {
705                 save_game_full_auto(inOverwrite);
706         }
707         else
708         {
709                 screen_printf("%s has saved the game", inWhoSaved->name);
710         }
711 }
712 
change_panel_state(short player_index,short panel_side_index)713 static void	change_panel_state(
714 	short player_index,
715 	short panel_side_index)
716 {
717 	bool state, make_sound= false;
718 	struct side_data *side= get_side_data(panel_side_index);
719 	struct player_data *player= get_player_data(player_index);
720 	struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
721 
722 	// LP change: idiot-proofing
723 	if (!definition) return;
724 
725 	state= GET_CONTROL_PANEL_STATUS(side);
726 
727 	/* Do the right thing, based on the panel type.. */
728 	switch (definition->_class)
729 	{
730 		case _panel_is_oxygen_refuel:
731 		case _panel_is_shield_refuel:
732 		case _panel_is_double_shield_refuel:
733 		case _panel_is_triple_shield_refuel:
734 			player->control_panel_side_index= player->control_panel_side_index==panel_side_index ? NONE : panel_side_index;
735 			state= get_recharge_status(panel_side_index);
736 			SET_CONTROL_PANEL_STATUS(side, state);
737 			if (!state) set_control_panel_texture(side);
738                                 // Lua script hook
739                                 if (player -> control_panel_side_index == panel_side_index)
740                                     L_Call_Start_Refuel (definition->_class, player_index, panel_side_index);
741                                 else
742                                     L_Call_End_Refuel (definition->_class, player_index, panel_side_index);
743 			break;
744 		case _panel_is_computer_terminal:
745 			if (get_game_state()==_game_in_progress && !PLAYER_HAS_CHEATED(player) && !PLAYER_HAS_MAP_OPEN(player))
746 			{
747                                 //MH: Lua script hook
748                                 L_Call_Terminal_Enter(side->control_panel_permutation,player_index);
749 
750 				/* this will handle changing levels, if necessary (i.e., if we�re finished) */
751 				enter_computer_interface(player_index, side->control_panel_permutation, calculate_level_completion_state());
752 			}
753 			break;
754 		case _panel_is_tag_switch:
755 			if (definition->item==NONE || (!state && try_and_subtract_player_item(player_index, definition->item)) || (!state && (side->flags & _side_item_is_optional)))
756 			{
757 				state= !state;
758 
759 				make_sound= set_tagged_light_statuses(side->control_panel_permutation, state);
760 				if (try_and_change_tagged_platform_states(side->control_panel_permutation, state)) make_sound= true;
761 				if (!side->control_panel_permutation) make_sound= true;
762 				if (make_sound)
763 				{
764 					SET_CONTROL_PANEL_STATUS(side, state);
765 					set_control_panel_texture(side);
766 				}
767                                 //MH: Lua script hook
768                                 L_Call_Tag_Switch(side->control_panel_permutation,player_index, panel_side_index);
769 
770 			}
771 			break;
772 		case _panel_is_light_switch:
773 			state= !state;
774 			make_sound= set_light_status(side->control_panel_permutation, state);
775 
776                         //MH: Lua script hook
777                         L_Call_Light_Switch(side->control_panel_permutation,player_index, panel_side_index);
778 
779 			break;
780 		case _panel_is_platform_switch:
781 			state= !state;
782 			make_sound= try_and_change_platform_state(get_polygon_data(side->control_panel_permutation)->permutation, state);
783 
784                         //MH: Lua script hook
785                         L_Call_Platform_Switch(side->control_panel_permutation,player_index, panel_side_index);
786 
787 			break;
788 		case _panel_is_pattern_buffer:
789                         if (player_controlling_game() && !PLAYER_HAS_CHEATED(local_player))
790                         {
791                                 if(game_is_networked)
792                                 {
793                                         if(player->control_panel_side_index != panel_side_index)
794                                         {
795                                                 if(dynamic_world->tick_count - player->ticks_at_last_successful_save > MINIMUM_RESAVE_TICKS)
796                                                 {	// User pressed "action" - we'll see if they're going to do it again.
797                                                         player->ticks_at_last_successful_save = dynamic_world->tick_count;
798                                                         player->control_panel_side_index = panel_side_index;
799                                                 }
800                                         }
801                                         else
802                                         {	// Double-press - overwrite recent saved game
803                                                 somebody_save_full_auto(player, true);
804                                         }
805                                 }
806                                 else
807                                 {	// game is not networked
808                                         if(dynamic_world->tick_count-player->ticks_at_last_successful_save>MINIMUM_RESAVE_TICKS)
809                                         {
810                                                 play_control_panel_sound(panel_side_index, _activating_sound);
811 
812                                                 //MH: Lua script hook
813                                                 L_Call_Pattern_Buffer(/*side->control_panel_permutation*/panel_side_index,player_index);
814 
815                 //				fade_out_background_music(30);
816 
817                                                 /* Assume a successful save- prevents vidding of the save game key.. */
818                                                 player->ticks_at_last_successful_save= dynamic_world->tick_count;
819                                                 if (!save_game())
820                                                 {
821                                                         player->ticks_at_last_successful_save= 0;
822                                                 }
823                 //				fade_in_background_music(30);
824                                         }
825                                 }
826                         }
827 			break;
828 	}
829 
830 	if (make_sound)
831 	{
832 		play_control_panel_sound(panel_side_index, state ? _activating_sound : _deactivating_sound);
833 	}
834 
835 	return;
836 }
837 
set_control_panel_texture(struct side_data * side)838 void set_control_panel_texture(
839 	struct side_data *side)
840 {
841 	struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
842 	// LP change: idiot-proofing
843 	if (!definition) return;
844 
845 	side->primary_texture.texture= BUILD_DESCRIPTOR(definition->collection,
846 		GET_CONTROL_PANEL_STATUS(side) ? definition->active_shape : definition->inactive_shape);
847 }
848 
849 
switch_can_be_toggled(short side_index,bool player_hit,bool * should_destroy_switch)850 static bool switch_can_be_toggled(
851 	short side_index,
852 	bool player_hit,
853 	bool *should_destroy_switch)
854 {
855 	bool valid_toggle= true;
856 	struct side_data *side= get_side_data(side_index);
857 	struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
858 	// LP change: idiot-proofing
859 	if (!definition) return false;
860 	if (should_destroy_switch) *should_destroy_switch = false;
861 
862 	if (side->flags&_side_is_lighted_switch)
863 	{
864 		valid_toggle= get_light_intensity(side->primary_lightsource_index)>(3*FIXED_ONE/4) ? true : false;
865 	}
866 
867 	if (definition->item!=NONE && !player_hit) valid_toggle= false;
868 	if (player_hit && (side->flags&_side_switch_can_only_be_hit_by_projectiles)) valid_toggle= false;
869 
870 	if (valid_toggle && (side->flags&_side_switch_can_be_destroyed) && should_destroy_switch)
871 	{
872 		*should_destroy_switch= true;
873 	}
874 
875 	return valid_toggle;
876 }
877 
play_control_panel_sound(short side_index,short sound_index)878 static void play_control_panel_sound(
879 	short side_index,
880 	short sound_index)
881 {
882 	struct side_data *side= get_side_data(side_index);
883 	struct control_panel_definition *definition= get_control_panel_definition(side->control_panel_type);
884 
885 	// LP change: idiot-proofing
886 	if (!definition) return;
887 
888 	if (!(sound_index>=0 && sound_index<NUMBER_OF_CONTROL_PANEL_SOUNDS)) return;
889 
890 	_play_side_sound(side_index, definition->sounds[sound_index], definition->sound_frequency);
891 }
892 
get_recharge_status(short side_index)893 static bool get_recharge_status(
894 	short side_index)
895 {
896 	short player_index;
897 	bool status= false;
898 
899 	for (player_index= 0; player_index<dynamic_world->player_count; ++player_index)
900 	{
901 		struct player_data *player= get_player_data(player_index);
902 
903 		if (player->control_panel_side_index==side_index) status= true;
904 	}
905 
906 	return status;
907 }
908 
909 struct control_panel_definition *original_control_panel_definitions = NULL;
910 struct control_panel_settings_definition *original_control_panel_settings = NULL;
911 
reset_mml_control_panels()912 void reset_mml_control_panels()
913 {
914 	if (original_control_panel_settings) {
915 		control_panel_settings = *original_control_panel_settings;
916 		free(original_control_panel_settings);
917 		original_control_panel_settings = NULL;
918 	}
919 
920 	if (original_control_panel_definitions) {
921 		for (unsigned i = 0; i < NUMBER_OF_CONTROL_PANEL_DEFINITIONS; i++)
922 			control_panel_definitions[i] = original_control_panel_definitions[i];
923 		free(original_control_panel_definitions);
924 		original_control_panel_definitions = NULL;
925 	}
926 }
927 
parse_mml_control_panels(const InfoTree & root)928 void parse_mml_control_panels(const InfoTree& root)
929 {
930 	// back up old values first
931 	if (!original_control_panel_settings) {
932 		original_control_panel_settings = (struct control_panel_settings_definition *) malloc(sizeof(struct control_panel_settings_definition));
933         assert(original_control_panel_settings);
934 		*original_control_panel_settings = control_panel_settings;
935 	}
936 
937 	if (!original_control_panel_definitions) {
938 		original_control_panel_definitions = (struct control_panel_definition *) malloc(sizeof(struct control_panel_definition) * NUMBER_OF_CONTROL_PANEL_DEFINITIONS);
939 		assert(original_control_panel_definitions);
940 		for (unsigned i = 0; i < NUMBER_OF_CONTROL_PANEL_DEFINITIONS; i++)
941 			original_control_panel_definitions[i] = control_panel_definitions[i];
942 	}
943 
944 	root.read_wu("reach", control_panel_settings.ReachDistance);
945 	root.read_attr("horiz", control_panel_settings.ReachHorizontal);
946 	root.read_attr("single_energy", control_panel_settings.SingleEnergy);
947 	root.read_attr("single_energy_rate", control_panel_settings.SingleEnergyRate);
948 	root.read_attr("double_energy", control_panel_settings.DoubleEnergy);
949 	root.read_attr("double_energy_rate", control_panel_settings.DoubleEnergyRate);
950 	root.read_attr("triple_energy", control_panel_settings.TripleEnergy);
951 	root.read_attr("triple_energy_rate", control_panel_settings.TripleEnergyRate);
952 
953 	BOOST_FOREACH(InfoTree panel, root.children_named("panel"))
954 	{
955 		int16 index;
956 		if (!panel.read_indexed("index", index, NUMBER_OF_CONTROL_PANEL_DEFINITIONS))
957 			continue;
958 
959 		control_panel_definition& def = control_panel_definitions[index];
960 		panel.read_indexed("type", def._class, NUMBER_OF_CONTROL_PANELS);
961 		panel.read_indexed("coll", def.collection, NUMBER_OF_COLLECTIONS);
962 		panel.read_indexed("active_frame", def.active_shape, MAXIMUM_SHAPES_PER_COLLECTION);
963 		panel.read_indexed("inactive_frame", def.inactive_shape, MAXIMUM_SHAPES_PER_COLLECTION);
964 		panel.read_indexed("item", def.item, NUMBER_OF_DEFINED_ITEMS, true);
965 		panel.read_fixed("pitch", def.sound_frequency, 0, SHRT_MAX+1);
966 
967 		BOOST_FOREACH(InfoTree sound, panel.children_named("sound"))
968 		{
969 			int16 type, which;
970 			if (!sound.read_indexed("type", type, NUMBER_OF_CONTROL_PANEL_SOUNDS) ||
971 				!sound.read_indexed("which", which, SHRT_MAX+1, true))
972 				continue;
973 			def.sounds[type] = which;
974 		}
975 	}
976 }
977