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