1 /*
2 computer_interface.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 Monday, May 8, 1995 7:18:55 PM- rdm created.
22
23 Feb 4, 2000 (Loren Petrich):
24 Changed halt() to assert(false) for better debugging
25
26 Feb 16, 2000 (Loren Petrich):
27 Set up for more graceful degradation when
28 there is no terminal data in a map;
29 also had get_indexed_terminal_data() return NULL
30 if terminal data could not be found.
31
32 Feb 18, 2000 (Loren Petrich):
33 Suppressed all the dprintf warnings, such as one for a picture being
34 too large for a terminal.
35
36 Feb 19, 2000 (Loren Petrich):
37 Had a frustrating time getting get_date_string() to work properly;
38 SecondsToDate() seems to be broken.
39
40 Feb 21, 2000 (Loren Petrich):
41 Corrected a troublesome assert in find_checkpoint_location()
42 Moved drawing of borders to after drawing of other stuff,
43 to fix checkpoint-overdraw bug.
44
45 Feb 26, 2000 (Loren Petrich):
46 Fixed level-0 teleportation bug; the hack is to move the destination
47 down by 1.
48
49 Set font in PICT-error-message code to "systemFont".
50
51 Mar 5, 2000 (Loren Petrich):
52 Improved handling of clipping in _render_computer_interface();
53 it is trimmed to the terminal window when rendering there, and restored
54 to the screen's full size when leaving.
55
56 May 11, 2000 (Loren Petrich):
57 Suppressed vhalts here; these were for unrecognized terminal states.
58 Changed them into vwarns.
59
60 May 20, 2000 (Loren Petrich):
61 Put in more graceful degradation for
62 get_indexed_grouping()
63 get_indexed_font_changes()
64 These will return NULL for an invalid index
65
66 July 5, 2000 (Loren Petrich):
67 Using world_pixels instead of screen_window
68
69 Aug 10, 2000 (Loren Petrich):
70 Added Chris Pruett's Pfhortran changes
71
72 Aug 22, 2000 (Loren Petrich):
73 Added object-oriented resource handling
74
75 Sep 24, 2000 (Loren Petrich):
76 Banished OverallBounds as unnecessary; world_pixels->portRect does fine here
77
78 Jun 23, 2001 (Loren Petrich):
79 Suppressed some of the asserts in the code; tried to make degradation more graceful
80
81 Jan 25, 2002 (Br'fin (Jeremy Parsons)):
82 Added accessors for datafields now opaque in Carbon
83 */
84
85 // add logon/logoff keywords. (& make terminal display them)
86 // cameras
87 // static
88 // delayed teleport
89
90 // activate tags
91 // picture with scrolling text.
92 // checkpoint with scrolling text
93 // don't use charwidth
94
95 #include <stdlib.h>
96 #include <stdio.h>
97 #include <string.h>
98 #include <time.h>
99 #include <limits.h>
100
101 #include <vector>
102
103 #include "cseries.h"
104 #include "FileHandler.h"
105
106 #include "world.h"
107 #include "map.h"
108 #include "player.h"
109 #include "computer_interface.h"
110 #include "screen_drawing.h"
111 #include "overhead_map.h"
112 #include "SoundManager.h"
113 #include "interface.h" // for the error strings.
114 #include "shell.h"
115 #include "platforms.h" // for tagged platforms
116 #include "lightsource.h" // for tagged lightsources
117 #include "screen.h"
118
119 #include "images.h"
120 #include "Packing.h"
121
122 // MH: Lua scripting
123 #include "lua_script.h"
124
125 #include "Logging.h"
126
127 #include <boost/algorithm/string/predicate.hpp>
128 #include <boost/iostreams/device/array.hpp>
129 #include <boost/iostreams/stream_buffer.hpp>
130
131 #include <sstream>
132
133 namespace algo = boost::algorithm;
134 namespace io = boost::iostreams;
135
136 #define LABEL_INSET 3
137 #define LOG_DURATION_BEFORE_TIMEOUT (2*TICKS_PER_SECOND)
138 #define BORDER_HEIGHT 18
139 #define BORDER_INSET 9
140 #define FUDGE_FACTOR 1
141
142 #define MAC_LINE_END 13
143
144 enum {
145 _reading_terminal,
146 _no_terminal_state,
147 NUMBER_OF_TERMINAL_STATES
148 };
149
150 enum {
151 _terminal_is_dirty= 0x01
152 };
153
154 enum {
155 _any_abort_key_mask= _action_trigger_state,
156 _terminal_up_arrow= _moving_forward,
157 _terminal_down_arrow= _moving_backward,
158 _terminal_page_down= _turning_right,
159 _terminal_page_up= _turning_left,
160 _terminal_next_state= _left_trigger_state
161 };
162
163 #define strCOMPUTER_LABELS 135
164 enum
165 {
166 _marathon_name,
167 _computer_starting_up,
168 _computer_manufacturer,
169 _computer_address,
170 _computer_terminal,
171 _scrolling_message,
172 _acknowledgement_message,
173 _disconnecting_message,
174 _connection_terminated_message,
175 _date_format
176 };
177
178 #define TERMINAL_IS_DIRTY(term) ((term)->flags & _terminal_is_dirty)
179 #define SET_TERMINAL_IS_DIRTY(term, v) ((void)((v)? ((term)->flags |= _terminal_is_dirty) : ((term)->flags &= ~_terminal_is_dirty)))
180
181 /* Maximum face changes per text grouping.. */
182 #define MAXIMUM_FACE_CHANGES_PER_TEXT_GROUPING (128)
183
184 enum {
185 _text_is_encoded_flag= 0x0001
186 };
187
188 enum {
189 _logon_group,
190 _unfinished_group,
191 _success_group,
192 _failure_group,
193 _information_group,
194 _end_group,
195 _interlevel_teleport_group, // permutation is level to go to
196 _intralevel_teleport_group, // permutation is polygon to go to.
197 _checkpoint_group, // permutation is the goal to show
198 _sound_group, // permutation is the sound id to play
199 _movie_group, // permutation is the movie id to play
200 _track_group, // permutation is the track to play
201 _pict_group, // permutation is the pict to display
202 _logoff_group,
203 _camera_group, // permutation is the object index
204 _static_group, // permutation is the duration of static.
205 _tag_group, // permutation is the tag to activate
206
207 NUMBER_OF_GROUP_TYPES
208 };
209
210 enum // flags to indicate text styles for paragraphs
211 {
212 _plain_text = 0x00,
213 _bold_text = 0x01,
214 _italic_text = 0x02,
215 _underline_text = 0x04
216 };
217
218 enum { /* terminal grouping flags */
219 _draw_object_on_right= 0x01, // for drawing checkpoints, picts, movies.
220 _center_object= 0x02,
221 _group_is_marathon_1 = 0x100
222 };
223
224 struct terminal_groupings {
225 int16 flags; /* varies.. */
226 int16 type; /* _information_text, _checkpoint_text, _briefing_text, _movie, _sound_bite, _soundtrack */
227 int16 permutation; /* checkpoint id for chkpt, level id for _briefing, movie id for movie, sound id for sound, soundtrack id for soundtrack */
228 int16 start_index;
229 int16 length;
230 int16 maximum_line_count;
231 };
232 const int SIZEOF_terminal_groupings = 12;
233
234 struct text_face_data {
235 int16 index;
236 int16 face;
237 int16 color;
238 };
239 const int SIZEOF_text_face_data = 6;
240
241 // This is externally visible, so its external size is defined in the header file
242 struct player_terminal_data
243 {
244 int16 flags;
245 int16 phase;
246 int16 state;
247 int16 current_group;
248 int16 level_completion_state;
249 int16 current_line;
250 int16 maximum_line;
251 int16 terminal_id;
252 int32 last_action_flag;
253 };
254
255 struct terminal_key {
256 int16 keycode;
257 int16 offset;
258 int16 mask;
259 int32 action_flag;
260 };
261
262 struct font_dimensions {
263 int16 lines_per_screen;
264 int16 character_width;
265 };
266
267 /* Terminal data loaded from map (maintained by computer_interface.cpp) */
268 struct terminal_text_t { // Object describing one terminal
terminal_text_tterminal_text_t269 terminal_text_t() {}
270 uint16 flags = 0;
271 int16 lines_per_page = 0;
272 vector<terminal_groupings> groupings;
273 vector<text_face_data> font_changes;
274 vector<uint8> text;
275 };
276
277 static vector<terminal_text_t> map_terminal_text;
278
279 // ghs: for Lua
number_of_terminal_texts()280 short number_of_terminal_texts() { return map_terminal_text.size(); }
281
282 /* internal global structure */
283 static struct player_terminal_data *player_terminals;
284
285 #define NUMBER_OF_TERMINAL_KEYS (sizeof(terminal_keys)/sizeof(struct terminal_key))
286
287 // Get the interface font to use from screen_drawing_<platform>.cpp
288 extern TextSpec *_get_font_spec(short font_index);
289 extern font_info *GetInterfaceFont(short font_index);
290 extern uint16 GetInterfaceStyle(short font_index);
291
292 /* ------------ private prototypes */
293 static player_terminal_data *get_player_terminal_data(
294 short player_index);
295
296 static void draw_logon_text(terminal_text_t *terminal_text,
297 short current_group_index, short logon_shape_id);
298 static void draw_computer_text(Rect *bounds,
299 terminal_text_t *terminal_text, short current_group_index, short current_line);
300 static void _draw_computer_text(char *base_text, short start_index, Rect *bounds,
301 terminal_text_t *terminal_text, short current_line);
302 static short find_group_type(terminal_text_t *data,
303 short group_type);
304 static void teleport_to_level(short level_number);
305 static void teleport_to_polygon(short player_index, short polygon_index);
306 static struct terminal_groupings *get_indexed_grouping(
307 terminal_text_t *data, short index);
308 static struct text_face_data *get_indexed_font_changes(
309 terminal_text_t *data, short index);
310 static char *get_text_base(terminal_text_t *data);
311 // LP change: added a flag to indicate whether stuff after the other
312 // terminal stuff is to be drawn; if not, then draw the stuff before the
313 // other terminal stuff.
314 static void draw_terminal_borders(struct player_terminal_data *terminal_data,
315 bool after_other_terminal_stuff);
316 static void next_terminal_state(short player_index);
317 static void next_terminal_group(short player_index, terminal_text_t *terminal_text);
318 static void get_date_string(char *date_string, short flags);
319 static void present_checkpoint_text(terminal_text_t *terminal_text, short current_group_index,
320 short current_line);
321 static bool find_checkpoint_location(short checkpoint_index, world_point2d *location,
322 short *polygon_index);
323 static void set_text_face(struct text_face_data *text_face);
324 static void draw_line(char *base_text, short start_index, short end_index, Rect *bounds,
325 terminal_text_t *terminal_text, short *text_face_start_index,
326 short line_number);
327 static bool calculate_line(char *base_text, short width, short start_index,
328 short text_end_index, short *end_index);
329 static void handle_reading_terminal_keys(short player_index, int32 action_flags);
330 static void calculate_bounds_for_object(short flags, Rect *bounds, Rect *source);
331 static void display_picture(short picture_id, Rect *frame, short flags);
332 static void display_shape(short shape, Rect* frame);
333 static void display_picture_with_text(struct player_terminal_data *terminal_data,
334 Rect *bounds, terminal_text_t *terminal_text, short current_lien);
335 static short count_total_lines(char *base_text, short width, short start_index, short end_index);
336 static void calculate_bounds_for_text_box(short flags, Rect *bounds);
337 static void goto_terminal_group(short player_index, terminal_text_t *terminal_text,
338 short new_group_index);
339 static bool previous_terminal_group(short player_index, terminal_text_t *terminal_text);
340 static void fill_terminal_with_static(Rect *bounds);
341 static short calculate_lines_per_page(void);
342 static Rect get_term_rectangle(short index);
343
344 static terminal_text_t *get_indexed_terminal_data(short id);
345 static void encode_text(terminal_text_t *terminal_text);
346 static void decode_text(terminal_text_t *terminal_text);
347
348 #include "sdl_fonts.h"
349 #include "joystick.h" // for AO_SCANCODE_BASE_JOYSTICK_BUTTON
350
351
352 // Global variables
353 // static const sdl_font_info *terminal_font = NULL;
354 static uint32 current_pixel; // Current color pixel value
355 static uint16 current_style = styleNormal; // Current style flags
356
357 // From screen_sdl.cpp
358 extern SDL_Surface *world_pixels;
359
360
361 // Terminal key definitions
362 static struct terminal_key terminal_keys[]= {
363 {SDL_SCANCODE_UP, 0, 0, _terminal_page_up}, // arrow up
364 {SDL_SCANCODE_DOWN, 0, 0, _terminal_page_down}, // arrow down
365 {SDL_SCANCODE_PAGEUP, 0, 0, _terminal_page_up}, // page up
366 {SDL_SCANCODE_PAGEDOWN, 0, 0, _terminal_page_down}, // page down
367 {SDL_SCANCODE_TAB, 0, 0, _terminal_next_state}, // tab
368 {SDL_SCANCODE_KP_ENTER, 0, 0, _terminal_next_state}, // enter
369 {SDL_SCANCODE_RETURN, 0, 0, _terminal_next_state}, // return
370 {SDL_SCANCODE_SPACE, 0, 0, _terminal_next_state}, // space
371 {SDL_SCANCODE_ESCAPE, 0, 0, _any_abort_key_mask}, // escape
372 {AO_SCANCODE_JOYSTICK_ESCAPE, 0, 0, _any_abort_key_mask},
373 {AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_UP, 0, 0, _terminal_page_up},
374 {AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_DOWN, 0, 0, _terminal_page_down},
375 {AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_A, 0, 0, _terminal_next_state},
376 {AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_X, 0, 0, _terminal_next_state},
377 {AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_Y, 0, 0, _terminal_next_state},
378 {AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_B, 0, 0, _any_abort_key_mask}
379 };
380
381
382 // Emulation of MacOS functions
InsetRect(Rect * r,int dx,int dy)383 static void InsetRect(Rect *r, int dx, int dy)
384 {
385 r->top += dy;
386 r->left += dx;
387 r->bottom -= dy;
388 r->right -= dx;
389 }
390
OffsetRect(Rect * r,int dx,int dy)391 static void OffsetRect(Rect *r, int dx, int dy)
392 {
393 r->top += dy;
394 r->left += dx;
395 r->bottom += dy;
396 r->right += dx;
397 }
398
399 extern SDL_Surface *draw_surface;
400
set_text_face(struct text_face_data * text_face)401 static void set_text_face(struct text_face_data *text_face)
402 {
403 current_style = styleNormal;
404
405 // Set style
406 if (text_face->face & _bold_text)
407 current_style |= styleBold;
408 if (text_face->face & _italic_text)
409 current_style |= styleItalic;
410 if (text_face->face & _underline_text)
411 current_style |= styleUnderline;
412
413 // Set color
414 SDL_Color color;
415 _get_interface_color(text_face->color + _computer_interface_text_color, &color);
416 current_pixel = SDL_MapRGB(/*world_pixels*/draw_surface->format, color.r, color.g, color.b);
417 }
418
419
calculate_line(char * base_text,short width,short start_index,short text_end_index,short * end_index)420 static bool calculate_line(char *base_text, short width, short start_index, short text_end_index, short *end_index)
421 {
422 bool done = false;
423
424 if (base_text[start_index]) {
425 int index = start_index, running_width = 0;
426
427 // terminal_font no longer a global, since it may change
428 font_info *terminal_font = GetInterfaceFont(_computer_interface_font);
429
430 while (running_width < width && base_text[index] && base_text[index] != MAC_LINE_END) {
431 running_width += char_width(base_text[index], terminal_font, current_style);
432 index++;
433 }
434
435 // Now go backwards, looking for whitespace to split on
436 if (base_text[index] == MAC_LINE_END)
437 index++;
438 else if (base_text[index]) {
439 int break_point = index;
440
441 while (break_point>start_index) {
442 if (base_text[break_point] == ' ')
443 break; // Non printing
444 break_point--; // this needs to be in front of the test
445 }
446
447 if (break_point != start_index)
448 index = break_point+1; // Space at the end of the line
449 }
450
451 *end_index= index;
452 } else
453 done = true;
454
455 return done;
456 }
457
458 /* ------------ code begins */
459
get_player_terminal_data(short player_index)460 player_terminal_data *get_player_terminal_data(
461 short player_index)
462 {
463 struct player_terminal_data *player_terminal = GetMemberWithBounds(player_terminals,player_index,MAXIMUM_NUMBER_OF_PLAYERS);
464
465 vassert(player_terminal, csprintf(temporary, "player index #%d is out of range", player_index));
466
467 return player_terminal;
468 }
469
initialize_terminal_manager(void)470 void initialize_terminal_manager(
471 void)
472 {
473 player_terminals= new player_terminal_data[MAXIMUM_NUMBER_OF_PLAYERS];
474 assert(player_terminals);
475 objlist_clear(player_terminals, MAXIMUM_NUMBER_OF_PLAYERS);
476
477 /*
478 terminal_font = load_font(*_get_font_spec(_computer_interface_font));
479 */
480 }
481
initialize_player_terminal_info(short player_index)482 void initialize_player_terminal_info(
483 short player_index)
484 {
485 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
486
487 //CP Addition: trap for logout!
488 if (terminal->state != _no_terminal_state)
489 {
490 L_Call_Terminal_Exit(terminal->terminal_id, player_index);
491 }
492
493 terminal->flags= 0;
494 terminal->phase = NONE; // not using a control panel.
495 terminal->state= _no_terminal_state; // And there is no line..
496 terminal->current_group= NONE;
497 terminal->level_completion_state= 0;
498 terminal->current_line= 0;
499 terminal->maximum_line= 0;
500 terminal->terminal_id= 0;
501 terminal->last_action_flag= -1l; /* Eat the first key */
502 }
503
enter_computer_interface(short player_index,short text_number,short completion_flag)504 void enter_computer_interface(
505 short player_index,
506 short text_number,
507 short completion_flag)
508 {
509 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
510 struct player_data *player= get_player_data(player_index);
511
512 // LP addition: if there is no terminal-data chunk, then just make the logon sound and quit
513 /* if (map_terminal_text.size() == 0)
514 {
515 play_object_sound(player->object_index, Sound_TerminalLogon());
516 return;
517 }
518 */
519 struct terminal_text_t *terminal_text = get_indexed_terminal_data(text_number);
520 if (terminal_text == NULL)
521 {
522 play_object_sound(player->object_index, Sound_TerminalLogon());
523 return;
524 }
525
526 if(dynamic_world->player_count==1)
527 {
528 short lines_per_page;
529
530 /* Reset the lines per page to the actual value for whatever fucked up font that they have */
531 lines_per_page= calculate_lines_per_page();
532 if(lines_per_page != terminal_text->lines_per_page)
533 {
534 // dprintf("You have one confused font.");
535 terminal_text->lines_per_page= lines_per_page;
536 }
537 }
538
539 /* Tell everyone that this player is in the computer interface.. */
540 terminal->state= _reading_terminal;
541 terminal->phase= NONE;
542 terminal->current_group= NONE;
543 terminal->level_completion_state= completion_flag;
544 terminal->current_line= 0;
545 terminal->maximum_line= 1; // any click or keypress will get us out.
546 terminal->terminal_id= text_number;
547 terminal->last_action_flag= -1l; /* Eat the first key */
548
549 /* And select the first one. */
550 next_terminal_group(player_index, terminal_text);
551 }
552
553 /* Assumes �t==1 tick */
update_player_for_terminal_mode(short player_index)554 void update_player_for_terminal_mode(
555 short player_index)
556 {
557 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
558 if (!terminal) return;
559
560 if(terminal->state != _no_terminal_state)
561 {
562 /* Phase is a timer for logging in and out only.. */
563 switch(terminal->state)
564 {
565 case _reading_terminal:
566 if(terminal->phase != NONE)
567 {
568 if(--terminal->phase<=0)
569 {
570 terminal_text_t *terminal_text = get_indexed_terminal_data(terminal->terminal_id);
571 if (terminal_text == NULL)
572 break;
573 next_terminal_group(player_index, terminal_text);
574 }
575 }
576 break;
577
578 default:
579 break;
580 }
581 }
582 }
583
update_player_keys_for_terminal(short player_index,uint32 action_flags)584 void update_player_keys_for_terminal(
585 short player_index,
586 uint32 action_flags)
587 {
588 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
589 if (!terminal) return;
590
591 switch(terminal->state)
592 {
593 case _reading_terminal:
594 handle_reading_terminal_keys(player_index, action_flags);
595 break;
596
597 case _no_terminal_state:
598 default:
599 break;
600 }
601 }
602
player_in_terminal_mode(short player_index)603 bool player_in_terminal_mode(
604 short player_index)
605 {
606 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
607 bool in_terminal_mode;
608
609 if(terminal->state==_no_terminal_state)
610 {
611 in_terminal_mode= false;
612 } else {
613 in_terminal_mode= true;
614 }
615
616 return in_terminal_mode;
617 }
618
_render_computer_interface(void)619 void _render_computer_interface(void)
620 {
621 struct player_terminal_data *terminal_data= get_player_terminal_data(current_player_index);
622
623 if (terminal_data->state == _no_terminal_state) return;
624 // assert(terminal_data->state != _no_terminal_state);
625 if(TERMINAL_IS_DIRTY(terminal_data))
626 {
627 SET_TERMINAL_IS_DIRTY(terminal_data, false);
628
629 terminal_text_t *terminal_text;
630 struct terminal_groupings *current_group;
631 Rect bounds;
632
633 /* Get the terminal text.. */
634 terminal_text = get_indexed_terminal_data(terminal_data->terminal_id);
635 // LP addition: quit if none
636 if (terminal_text == NULL) return;
637
638 switch(terminal_data->state)
639 {
640 case _reading_terminal:
641 /* Initialize it if it hasn't been done.. */
642 current_group= get_indexed_grouping(terminal_text, terminal_data->current_group);
643 // LP change: just in case...
644 if (!current_group) return;
645
646 /* Draw the borders! */
647 // LP change: draw these after, so as to avoid overdraw bug
648 draw_terminal_borders(terminal_data, false);
649 switch(current_group->type)
650 {
651 case _logon_group:
652 case _logoff_group:
653 draw_logon_text(terminal_text, terminal_data->current_group,
654 current_group->permutation);
655 break;
656
657 case _unfinished_group:
658 case _success_group:
659 case _failure_group:
660 // dprintf("You shouldn't try to render this view.;g");
661 break;
662
663 case _information_group:
664 /* Draw as normal... */
665 bounds= get_term_rectangle(_terminal_full_text_rect);
666 draw_computer_text(&bounds, terminal_text, terminal_data->current_group,
667 terminal_data->current_line);
668 break;
669
670 case _checkpoint_group: // permutation is the goal to show
671 /* note that checkpoints can only be equal to one screenful.. */
672 present_checkpoint_text(terminal_text, terminal_data->current_group,
673 terminal_data->current_line);
674 break;
675
676 case _end_group:
677 case _interlevel_teleport_group: // permutation is level to go to
678 case _intralevel_teleport_group: // permutation is polygon to go to.
679 case _sound_group: // permutation is the sound id to play
680 case _tag_group:
681 // These are all handled elsewhere
682 break;
683
684 case _movie_group:
685 case _track_group:
686 if(!game_is_networked)
687 {
688 // dprintf("Movies/Music Tracks not supported on playback (yet);g");
689 } else {
690 // dprintf("On networked games, should we display a PICT here?;g");
691 }
692 break;
693
694 case _pict_group:
695 display_picture_with_text(terminal_data, &bounds, terminal_text,
696 terminal_data->current_line);
697 break;
698
699 case _static_group:
700 bounds = get_term_rectangle(_terminal_screen_rect);
701 fill_terminal_with_static(&bounds);
702 SET_TERMINAL_IS_DIRTY(terminal_data, true);
703 break;
704
705 case _camera_group:
706 break;
707
708 default:
709 break;
710 }
711 // Moved down here, so they'd overdraw the other stuff
712 draw_terminal_borders(terminal_data, true);
713 break;
714
715 default:
716 break;
717 }
718
719 RequestDrawingTerm();
720 }
721 }
722
723 /* Only care about local_player_index */
build_terminal_action_flags(char * keymap)724 uint32 build_terminal_action_flags(
725 char *keymap)
726 {
727 uint32 flags, raw_flags;
728 struct terminal_key *key= terminal_keys;
729 struct player_terminal_data *terminal= get_player_terminal_data(local_player_index);
730
731 raw_flags= 0;
732 for(unsigned index= 0; index<NUMBER_OF_TERMINAL_KEYS; ++index)
733 {
734 if (keymap[key->keycode]) raw_flags |= key->action_flag;
735 key++;
736 }
737
738 /* Only catch the key the first time. */
739 flags= raw_flags^terminal->last_action_flag;
740 flags&= raw_flags;
741 terminal->last_action_flag= raw_flags;
742
743 //dprintf("Flags: %x (Raw: %x Mask: %x);g", flags, raw_flags, terminal->last_action_flag);
744 return flags;
745 }
746
747 /* called from walk_player_list.. */
dirty_terminal_view(short player_index)748 void dirty_terminal_view(
749 short player_index)
750 {
751 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
752
753 if(terminal->state != _no_terminal_state)
754 {
755 SET_TERMINAL_IS_DIRTY(terminal, true);
756 }
757 }
758
759 /* Take damage, abort.. */
abort_terminal_mode(short player_index)760 void abort_terminal_mode(
761 short player_index)
762 {
763 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
764
765 if(terminal->state != _no_terminal_state)
766 {
767 terminal->state= _no_terminal_state;
768 L_Call_Terminal_Exit(terminal->terminal_id, player_index);
769 }
770 }
771
772 static uint16_t MARATHON_LOGON_SHAPE = 44;
773
774 /* --------- local code */
draw_logon_text(terminal_text_t * terminal_text,short current_group_index,short logon_shape_id)775 static void draw_logon_text(
776 terminal_text_t *terminal_text,
777 short current_group_index,
778 short logon_shape_id)
779 {
780 Rect picture_bounds= get_term_rectangle(_terminal_logon_graphic_rect);
781 char *base_text= get_text_base(terminal_text);
782 short width;
783 struct terminal_groupings *current_group= get_indexed_grouping(terminal_text,
784 current_group_index);
785 // LP change: just in case...
786 if (!current_group) return;
787
788 if (current_group->flags & _group_is_marathon_1)
789 {
790 display_shape(MARATHON_LOGON_SHAPE, &picture_bounds);
791
792 // draw static title below logo
793 picture_bounds= get_term_rectangle(_terminal_logon_title_rect);
794 getcstr(temporary, strCOMPUTER_LABELS, _marathon_name);
795 _draw_screen_text(temporary, (screen_rectangle *) &picture_bounds, _center_vertical | _center_horizontal, _computer_interface_title_font, _computer_interface_text_color);
796
797 picture_bounds= get_term_rectangle(_terminal_logon_location_rect);
798 }
799 else
800 {
801 Rect bounds= picture_bounds;
802 display_picture(logon_shape_id, &picture_bounds, _center_object);
803
804 /* Use the picture bounds to create the logon text crap . */
805 picture_bounds.top= picture_bounds.bottom;
806 picture_bounds.bottom= bounds.bottom;
807 picture_bounds.left= bounds.left;
808 picture_bounds.right= bounds.right;
809 }
810
811 /* This is always just a line, so we can do this here.. */
812 font_info *terminal_font = GetInterfaceFont(_computer_interface_font);
813 uint16 terminal_style = GetInterfaceStyle(_computer_interface_font);
814 width = text_width(base_text + current_group->start_index, current_group->length, terminal_font, terminal_style);
815 // width = text_width(base_text + current_group->start_index, current_group->length, terminal_font, _get_font_spec(_computer_interface_font)->style);
816 picture_bounds.left += (RECTANGLE_WIDTH(&picture_bounds) - width) / 2;
817
818 _draw_computer_text(base_text, current_group_index, &picture_bounds, terminal_text, 0);
819 }
820
821 /* returns true for phase chagne */
draw_computer_text(Rect * bounds,terminal_text_t * terminal_text,short current_group_index,short current_line)822 static void draw_computer_text(
823 Rect *bounds,
824 terminal_text_t *terminal_text,
825 short current_group_index,
826 short current_line)
827 {
828 char *base_text= get_text_base(terminal_text);
829
830 _draw_computer_text(base_text, current_group_index, bounds,
831 terminal_text, current_line);
832 }
833
834 /* Returns true if current_line> end of text.. (for phase change) */
_draw_computer_text(char * base_text,short group_index,Rect * bounds,terminal_text_t * terminal_text,short current_line)835 static void _draw_computer_text(
836 char *base_text,
837 short group_index,
838 Rect *bounds,
839 terminal_text_t *terminal_text,
840 short current_line)
841 {
842 bool done= false;
843 short start_index;
844 struct terminal_groupings *current_group= get_indexed_grouping(terminal_text, group_index);
845 // LP change: just in case...
846 if (!current_group) return;
847 struct text_face_data text_face;
848 short index, last_index, last_text_index, end_index;
849 unsigned text_index;
850
851 uint16 old_style = current_style;
852 current_style = GetInterfaceStyle(_computer_interface_font);
853 // current_style = _get_font_spec(_computer_interface_font)->style;
854
855 start_index= current_group->start_index;
856 end_index= current_group->length+current_group->start_index;
857
858 /* eat the previous lines */
859 for(index= 0; index<current_line; ++index)
860 {
861 /* Calculate one line.. */
862 if(!calculate_line(base_text, RECTANGLE_WIDTH(bounds), start_index, current_group->start_index+current_group->length,
863 &end_index))
864 {
865 if(end_index>current_group->start_index+current_group->length)
866 {
867 // dprintf("Start: %d Length: %d End: %d;g", current_group->start_index, current_group->length, end_index);
868 // dprintf("Width: %d", RECTANGLE_WIDTH(bounds));
869 end_index= current_group->start_index+current_group->length;
870 }
871 //dprintf("calculate line: %d start: %d end: %d", index, start_index, end_index);
872 assert(end_index<=current_group->start_index+current_group->length);
873
874 start_index= end_index;
875 } else {
876 /* End of text.. */
877 done= true;
878 }
879 }
880
881 if(!done)
882 {
883 /* Go backwards, and see if there were any other face changes... */
884 last_index= current_group->start_index;
885 last_text_index= NONE;
886 for(text_index= 0; text_index<terminal_text->font_changes.size(); ++text_index)
887 {
888 struct text_face_data *font_face= get_indexed_font_changes(terminal_text, text_index);
889 // LP change: just in case...
890 if (!font_face) return;
891
892 /* Go backwards from the scrolled starting location.. */
893 if(font_face->index>last_index && font_face->index<start_index)
894 {
895 // dprintf("ff index: %d last: %d end: %d", font_face->index, last_index, start_index);
896 last_index= font_face->index;
897 last_text_index= text_index;
898 }
899 }
900
901 // dprintf("last index: %d", last_text_index);
902 /* Figure out the font.. */
903 if(last_text_index==NONE)
904 {
905 /* Default-> plain, etc.. */
906 text_face.color= 0;
907 text_face.face= 0;
908 } else {
909 struct text_face_data *font_face= get_indexed_font_changes(terminal_text,
910 last_text_index);
911 // LP change: just in case...
912 if (!font_face) return;
913 text_face= *font_face;
914 }
915
916 /* Set the font.. */
917 set_text_face(&text_face);
918
919 /* Draw what is one the screen */
920 for(index= 0; !done && index<terminal_text->lines_per_page; ++index)
921 {
922 //dprintf("calculating the line");
923 /* Calculate one line.. */
924 if(!calculate_line(base_text, RECTANGLE_WIDTH(bounds), start_index, current_group->start_index+current_group->length, &end_index))
925 {
926 //dprintf("draw calculate line: %d start: %d end: %d text: %x length: %d lti: %d", index, start_index, end_index, base_text, current_group->length, last_text_index);
927 if(end_index>current_group->start_index+current_group->length)
928 {
929 // dprintf("Start: %d Length: %d End: %d;g", current_group->start_index, current_group->length, end_index);
930 // dprintf("Width: %d", RECTANGLE_WIDTH(bounds));
931 end_index= current_group->start_index+current_group->length;
932 }
933 assert(end_index<=current_group->start_index+current_group->length);
934 draw_line(base_text, start_index, end_index, bounds, terminal_text, &last_text_index,
935 index);
936 start_index= end_index;
937 } else {
938 /* End of text. */
939 // done= true;
940 }
941 }
942 }
943
944 current_style = old_style;
945 }
946
count_total_lines(char * base_text,short width,short start_index,short end_index)947 static short count_total_lines(
948 char *base_text,
949 short width,
950 short start_index,
951 short end_index)
952 {
953 short total_line_count= 0;
954 short text_end_index= end_index;
955
956 uint16 old_style = current_style;
957 current_style = GetInterfaceStyle(_computer_interface_font);
958 // current_style = _get_font_spec(_computer_interface_font)->style;
959
960 while(!calculate_line(base_text, width, start_index, text_end_index, &end_index))
961 {
962 total_line_count++;
963 start_index= end_index;
964 }
965
966 current_style = old_style;
967
968 return total_line_count;
969 }
970
draw_line(char * base_text,short start_index,short end_index,Rect * bounds,terminal_text_t * terminal_text,short * text_face_start_index,short line_number)971 static void draw_line(
972 char *base_text,
973 short start_index,
974 short end_index,
975 Rect *bounds,
976 terminal_text_t *terminal_text,
977 short *text_face_start_index,
978 short line_number)
979 {
980 short line_height= _get_font_line_height(_computer_interface_font);
981 bool done= false;
982 short current_start, current_end= end_index;
983 struct text_face_data *face_data= NULL;
984 unsigned text_index;
985
986 if((*text_face_start_index)==NONE)
987 {
988 text_index= 0;
989 } else {
990 text_index= (*text_face_start_index);
991 }
992
993 /* Get to the first one that concerns us.. */
994 if(text_index<terminal_text->font_changes.size())
995 {
996 do {
997 face_data= get_indexed_font_changes(terminal_text, text_index);
998 // LP change: just in case...
999 if (!face_data) return;
1000 if(face_data->index<start_index) text_index++;
1001 } while(face_data->index<start_index && text_index<terminal_text->font_changes.size());
1002 }
1003
1004 current_start= start_index;
1005 font_info *terminal_font = GetInterfaceFont(_computer_interface_font);
1006 int xpos = bounds->left;
1007
1008 while(!done)
1009 {
1010 if(text_index<terminal_text->font_changes.size())
1011 {
1012 face_data= get_indexed_font_changes(terminal_text, text_index);
1013 // LP change: just in case...
1014 if (!face_data) return;
1015
1016 if(face_data->index>=current_start && face_data->index<current_end)
1017 {
1018 current_end= face_data->index;
1019 text_index++;
1020 (*text_face_start_index)= text_index;
1021 }
1022 }
1023
1024 xpos += draw_text(/*world_pixels*/ draw_surface, base_text + current_start, current_end - current_start,
1025 xpos, bounds->top + line_height * (line_number + FUDGE_FACTOR),
1026 current_pixel, terminal_font, current_style);
1027 if(current_end!=end_index)
1028 {
1029 current_start= current_end;
1030 current_end= end_index;
1031 assert(face_data);
1032 set_text_face(face_data);
1033 } else {
1034 done= true;
1035 }
1036 }
1037 }
1038
find_group_type(terminal_text_t * data,short group_type)1039 static short find_group_type(
1040 terminal_text_t *data,
1041 short group_type)
1042 {
1043 unsigned index;
1044
1045 for(index= 0; index<data->groupings.size(); index++)
1046 {
1047 struct terminal_groupings *group= get_indexed_grouping(data, index);
1048 // LP change: just in case...
1049 if (!group) return NONE;
1050 if(group->type==group_type) break;
1051 }
1052
1053 return index;
1054 }
1055
teleport_to_level(short level_number)1056 static void teleport_to_level(
1057 short level_number)
1058 {
1059 /* It doesn't matter which player we get. */
1060 struct player_data *player= get_player_data(0);
1061
1062 // LP change: moved down by 1 so that level 0 will be valid
1063 player->teleporting_destination= -level_number - 1;
1064 player->delay_before_teleport= TICKS_PER_SECOND/2; // delay before we teleport.
1065 }
1066
teleport_to_polygon(short player_index,short polygon_index)1067 static void teleport_to_polygon(
1068 short player_index,
1069 short polygon_index)
1070 {
1071 struct player_data *player= get_player_data(player_index);
1072
1073 player->teleporting_destination= polygon_index;
1074 assert(!player->delay_before_teleport);
1075 }
1076
calculate_bounds_for_text_box(short flags,Rect * bounds)1077 static void calculate_bounds_for_text_box(
1078 short flags,
1079 Rect *bounds)
1080 {
1081 if(flags & _center_object)
1082 {
1083 // dprintf("splitting text not supported!");
1084 calculate_bounds_for_object(_draw_object_on_right, bounds, NULL);
1085 }
1086 else if(flags & _draw_object_on_right)
1087 {
1088 calculate_bounds_for_object(0, bounds, NULL);
1089 }
1090 else
1091 {
1092 calculate_bounds_for_object(_draw_object_on_right, bounds, NULL);
1093 }
1094 if (flags & _group_is_marathon_1)
1095 bounds->top += _get_font_line_height(_computer_interface_font);
1096 }
1097
display_picture_with_text(struct player_terminal_data * terminal_data,Rect * bounds,terminal_text_t * terminal_text,short current_line)1098 static void display_picture_with_text(
1099 struct player_terminal_data *terminal_data,
1100 Rect *bounds,
1101 terminal_text_t *terminal_text,
1102 short current_line)
1103 {
1104 struct terminal_groupings *current_group= get_indexed_grouping(terminal_text, terminal_data->current_group);
1105 // LP change: just in case...
1106 if (!current_group) return;
1107 Rect text_bounds;
1108 Rect picture_bounds;
1109
1110 assert(current_group->type==_pict_group);
1111 calculate_bounds_for_object(current_group->flags, &picture_bounds, NULL);
1112 display_picture(current_group->permutation, &picture_bounds, current_group->flags);
1113
1114 /* Display the text */
1115 calculate_bounds_for_text_box(current_group->flags, &text_bounds);
1116 draw_computer_text(&text_bounds, terminal_text, terminal_data->current_group, current_line);
1117 }
1118
display_shape(short shape,Rect * frame)1119 static void display_shape(short shape, Rect* frame)
1120 {
1121 SDL_Surface* s = get_shape_surface(shape);
1122 if (s)
1123 {
1124 Rect bounds;
1125 Rect screen_bounds;
1126
1127 bounds.left = bounds.top = 0;
1128 bounds.right = s->w;
1129 bounds.bottom = s->h;
1130
1131 OffsetRect(&bounds, -bounds.left, -bounds.top);
1132 calculate_bounds_for_object(_center_object, &screen_bounds, &bounds);
1133
1134 OffsetRect(&bounds, screen_bounds.left + (RECTANGLE_WIDTH(&screen_bounds)-RECTANGLE_WIDTH(&bounds))/2, screen_bounds.top + (RECTANGLE_HEIGHT(&screen_bounds)-RECTANGLE_HEIGHT(&bounds))/2);
1135
1136 SDL_Rect r = { bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top };
1137 SDL_SetSurfaceAlphaMod(s, 255);
1138 SDL_BlitSurface(s, NULL, draw_surface, &r);
1139
1140 SDL_FreeSurface(s);
1141 *frame = bounds;
1142 }
1143 }
1144
1145 extern int get_pict_header_width(LoadedResource &);
1146
display_picture(short picture_id,Rect * frame,short flags)1147 static void display_picture(
1148 short picture_id,
1149 Rect *frame,
1150 short flags)
1151 {
1152 LoadedResource PictRsrc;
1153 SDL_Surface *s = NULL;
1154
1155 if (get_picture_resource_from_scenario(picture_id, PictRsrc))
1156 {
1157 s = picture_to_surface(PictRsrc);
1158 }
1159 if (s)
1160 {
1161 Rect bounds;
1162 Rect screen_bounds;
1163
1164 bounds.left = bounds.top = 0;
1165 bounds.right = s->w;
1166 bounds.bottom = s->h;
1167
1168 int pict_header_width = get_pict_header_width(PictRsrc);
1169 bool cinemascopeHack = false;
1170 if (bounds.right != pict_header_width)
1171 {
1172 cinemascopeHack = true;
1173 bounds.right = pict_header_width;
1174 }
1175 OffsetRect(&bounds, -bounds.left, -bounds.top);
1176 calculate_bounds_for_object(flags, &screen_bounds, &bounds);
1177
1178 if(RECTANGLE_WIDTH(&bounds)<=RECTANGLE_WIDTH(&screen_bounds) &&
1179 RECTANGLE_HEIGHT(&bounds)<=RECTANGLE_HEIGHT(&screen_bounds))
1180 {
1181 /* It fits-> center it. */
1182 OffsetRect(&bounds, screen_bounds.left+(RECTANGLE_WIDTH(&screen_bounds)-RECTANGLE_WIDTH(&bounds))/2,
1183 screen_bounds.top+(RECTANGLE_HEIGHT(&screen_bounds)-RECTANGLE_HEIGHT(&bounds))/2);
1184 } else {
1185 /* Doesn't fit. Make it, but preserve the aspect ratio like a good little boy */
1186 if(RECTANGLE_HEIGHT(&bounds)-RECTANGLE_HEIGHT(&screen_bounds)>=
1187 RECTANGLE_WIDTH(&bounds)-RECTANGLE_WIDTH(&screen_bounds))
1188 {
1189 short adjusted_width;
1190
1191 adjusted_width= RECTANGLE_HEIGHT(&screen_bounds)*RECTANGLE_WIDTH(&bounds)/RECTANGLE_HEIGHT(&bounds);
1192 bounds= screen_bounds;
1193 InsetRect(&bounds, (RECTANGLE_WIDTH(&screen_bounds)-adjusted_width)/2, 0);
1194 // dprintf("Warning: Not large enough for pict: %d (height);g", picture_id);
1195 } else {
1196 /* Width is the predominant factor */
1197 short adjusted_height;
1198
1199 adjusted_height= RECTANGLE_WIDTH(&screen_bounds)*RECTANGLE_HEIGHT(&bounds)/RECTANGLE_WIDTH(&bounds);
1200 bounds= screen_bounds;
1201 InsetRect(&bounds, 0, (RECTANGLE_HEIGHT(&screen_bounds)-adjusted_height)/2);
1202 // dprintf("Warning: Not large enough for pict: %d (width);g", picture_id);
1203 }
1204 }
1205
1206 // warn(HGetState((Handle) picture) & 0x40); // assert it is purgable.
1207
1208 SDL_Rect r = {bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top};
1209 if ((s->w == r.w && s->h == r.h) || cinemascopeHack)
1210 SDL_BlitSurface(s, NULL, /*world_pixels*/draw_surface, &r);
1211 else {
1212 // Rescale picture
1213 SDL_Surface *s2 = rescale_surface(s, r.w, r.h);
1214 if (s2) {
1215 SDL_BlitSurface(s2, NULL, /*world_pixels*/draw_surface, &r);
1216 SDL_FreeSurface(s2);
1217 }
1218 }
1219 SDL_FreeSurface(s);
1220 /* And let the caller know where we drew the picture */
1221 *frame= bounds;
1222 } else {
1223 Rect bounds;
1224 char format_string[128];
1225
1226 calculate_bounds_for_object(flags, &bounds, NULL);
1227
1228 SDL_Rect r = {bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top};
1229 SDL_FillRect(/*world_pixels*/draw_surface, &r, SDL_MapRGB(/*world_pixels*/draw_surface->format, 0, 0, 0));
1230
1231 getcstr(format_string, strERRORS, pictureNotFound);
1232 sprintf(temporary, format_string, picture_id);
1233
1234 const font_info *font = GetInterfaceFont(_computer_interface_title_font);
1235 int width = text_width(temporary, font, styleNormal);
1236 draw_text(/*world_pixels*/draw_surface, temporary,
1237 bounds.left + (RECTANGLE_WIDTH(&bounds) - width) / 2,
1238 bounds.top + RECTANGLE_HEIGHT(&bounds) / 2,
1239 SDL_MapRGB(/*world_pixels*/draw_surface->format, 0xff, 0xff, 0xff),
1240 font, styleNormal);
1241 }
1242 }
1243
1244 template <typename T>
randomize_pixel(uint16 pixel)1245 static inline T randomize_pixel(uint16 pixel)
1246 {
1247 return static_cast<T>(pixel);
1248 }
1249
1250 extern SDL_PixelFormat pixel_format_32;
1251
1252 template <>
randomize_pixel(uint16 pixel)1253 inline uint32 randomize_pixel(uint16 pixel)
1254 {
1255 return ((uint32)pixel^(((uint32)pixel)<<8)) | pixel_format_32.Amask;
1256 }
1257
1258 template <typename T>
randomize_line(T * start,uint32 count)1259 static inline void randomize_line(T* start, uint32 count)
1260 {
1261 static uint16 random_seed = 6906;
1262 for (int i = 0; i < count; ++i)
1263 {
1264 *start++ = randomize_pixel<T>(random_seed);
1265 if (random_seed&1) random_seed = (random_seed>>1)^0xb400;
1266 else random_seed = random_seed >> 1;
1267 }
1268 }
1269
fill_terminal_with_static(Rect * bounds)1270 static void fill_terminal_with_static(
1271 Rect *bounds)
1272 {
1273 for (int y = bounds->top; y < bounds->bottom; ++y)
1274 {
1275 int bpp = draw_surface->format->BytesPerPixel;
1276 int width = bounds->right - bounds->left;
1277
1278 uint8* p = (uint8*)draw_surface->pixels + y * draw_surface->pitch + bounds->left * bpp;
1279 if (bpp == 1)
1280 {
1281 randomize_line<uint8>(p, width);
1282 }
1283 else if (bpp == 2)
1284 {
1285 randomize_line<uint16>(reinterpret_cast<uint16*>(p), width);
1286 }
1287 else if (bpp == 4)
1288 {
1289 randomize_line<uint32>(reinterpret_cast<uint32*>(p), width);
1290 }
1291 }
1292 }
1293
1294 // LP addition: will return NULL if no terminal data was found for this terminal number
1295
1296 static boost::scoped_ptr<terminal_text_t> resource_terminal;
1297 static int resource_terminal_id = NONE;
1298
clear_compiled_terminal_cache()1299 void clear_compiled_terminal_cache()
1300 {
1301 resource_terminal.reset();
1302 resource_terminal_id = NONE;
1303 }
1304
1305 static terminal_text_t* compile_marathon_terminal(char*, short);
1306
get_indexed_terminal_data(short id)1307 static terminal_text_t *get_indexed_terminal_data(
1308 short id)
1309 {
1310 if (id < 0 || id >= int(map_terminal_text.size()))
1311 {
1312 id = 1000 + dynamic_world->current_level_number * 10 + id;
1313 if (id == resource_terminal_id)
1314 {
1315 return resource_terminal.get();
1316 }
1317 else if (ExternalResources.IsOpen())
1318 {
1319 LoadedResource rsrc;
1320 if (ExternalResources.Get('t', 'e', 'r', 'm', id, rsrc))
1321 {
1322 resource_terminal.reset(compile_marathon_terminal(reinterpret_cast<char*>(rsrc.GetPointer()), rsrc.GetLength()));
1323
1324 resource_terminal_id = id;
1325 return resource_terminal.get();
1326 }
1327 }
1328
1329 return NULL;
1330 }
1331
1332 terminal_text_t *t = &map_terminal_text[id];
1333
1334 // Note that this will only decode the text once
1335 decode_text(t);
1336 return t;
1337 }
1338
decode_text(terminal_text_t * terminal_text)1339 static void decode_text(
1340 terminal_text_t *terminal_text)
1341 {
1342 if(terminal_text->flags & _text_is_encoded_flag)
1343 {
1344 encode_text(terminal_text);
1345 terminal_text->flags &= ~_text_is_encoded_flag;
1346 }
1347 }
1348
encode_text(terminal_text_t * terminal_text)1349 static void encode_text(
1350 terminal_text_t *terminal_text)
1351 {
1352 int length = terminal_text->text.size();
1353 uint8 *p = terminal_text->text.data();
1354
1355 for (int i=0; i<length/4; i++) {
1356 p += 2;
1357 *p++ ^= 0xfe;
1358 *p++ ^= 0xed;
1359 }
1360 for (int i=0; i<length%4; i++)
1361 *p++ ^= 0xfe;
1362
1363 terminal_text->flags |= _text_is_encoded_flag;
1364 }
1365
1366 // LP change: added a flag to indicate whether stuff after the other
1367 // terminal stuff is to be drawn; if not, then draw the stuff before the
1368 // other terminal stuff.
draw_terminal_borders(struct player_terminal_data * terminal_data,bool after_other_terminal_stuff)1369 static void draw_terminal_borders(
1370 struct player_terminal_data *terminal_data,
1371 bool after_other_terminal_stuff)
1372 {
1373 Rect frame, border;
1374 short top_message, bottom_left_message, bottom_right_message;
1375 terminal_text_t *terminal_text= get_indexed_terminal_data(terminal_data->terminal_id);
1376
1377 // LP addition: quit if none
1378 if (terminal_text == NULL) return;
1379
1380 // LP change: just in case...
1381 struct terminal_groupings *current_group= get_indexed_grouping(terminal_text, terminal_data->current_group);
1382 if (current_group == NULL) return;
1383
1384 switch(current_group->type)
1385 {
1386 case _logon_group:
1387 top_message= _computer_starting_up;
1388 bottom_left_message= _computer_manufacturer;
1389 bottom_right_message= _computer_address;
1390 break;
1391
1392 case _logoff_group:
1393 top_message= _disconnecting_message;
1394 bottom_left_message= _computer_manufacturer;
1395 bottom_right_message= _computer_address;
1396 break;
1397
1398 default:
1399 top_message= _computer_terminal;
1400 bottom_left_message= _scrolling_message;
1401 bottom_right_message= _acknowledgement_message;
1402 break;
1403 }
1404
1405 /* First things first: draw the border.. */
1406 /* Get the destination frame.. */
1407 frame= get_term_rectangle(_terminal_screen_rect);
1408
1409 if (!after_other_terminal_stuff)
1410 {
1411 /* Erase the rectangle.. */
1412 _fill_screen_rectangle((screen_rectangle *) &frame, _black_color);
1413 } else {
1414
1415 /* Draw the top rectangle */
1416 border= get_term_rectangle(_terminal_header_rect);
1417 _fill_screen_rectangle((screen_rectangle *) &border, _computer_border_background_text_color);
1418
1419 /* Draw the top login header text... */
1420 border.left += LABEL_INSET; border.right -= LABEL_INSET;
1421 getcstr(temporary, strCOMPUTER_LABELS, top_message);
1422 _draw_screen_text(temporary, (screen_rectangle *) &border, _center_vertical, _computer_interface_font, _computer_border_text_color);
1423 get_date_string(temporary, current_group->flags);
1424 _draw_screen_text(temporary, (screen_rectangle *) &border, _right_justified | _center_vertical,
1425 _computer_interface_font, _computer_border_text_color);
1426
1427 /* Draw the the bottom rectangle & text */
1428 border= get_term_rectangle(_terminal_footer_rect);
1429 _fill_screen_rectangle((screen_rectangle *) &border, _computer_border_background_text_color);
1430 border.left += LABEL_INSET; border.right -= LABEL_INSET;
1431 getcstr(temporary, strCOMPUTER_LABELS, bottom_left_message);
1432 _draw_screen_text(temporary, (screen_rectangle *) &border, _center_vertical, _computer_interface_font,
1433 _computer_border_text_color);
1434 getcstr(temporary, strCOMPUTER_LABELS, bottom_right_message);
1435 _draw_screen_text(temporary, (screen_rectangle *) &border, _right_justified | _center_vertical,
1436 _computer_interface_font, _computer_border_text_color);
1437
1438 // LP change: done with stuff for after the other rendering
1439 }
1440 }
1441
next_terminal_state(short player_index)1442 static void next_terminal_state(
1443 short player_index)
1444 {
1445 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
1446
1447 switch(terminal->state)
1448 {
1449 case _reading_terminal:
1450 initialize_player_terminal_info(player_index);
1451 break;
1452
1453 case _no_terminal_state:
1454 default:
1455 break;
1456 }
1457 }
1458
previous_terminal_group(short player_index,terminal_text_t * terminal_text)1459 static bool previous_terminal_group(
1460 short player_index,
1461 terminal_text_t *terminal_text)
1462 {
1463 struct player_terminal_data *terminal_data= get_player_terminal_data(player_index);
1464 bool success= false;
1465
1466 if(terminal_data->state==_reading_terminal)
1467 {
1468 short new_group_index= terminal_data->current_group-1;
1469 bool use_new_group= true;
1470 bool done= true;
1471
1472 do
1473 {
1474 if(new_group_index>=0)
1475 {
1476 struct terminal_groupings *new_group= get_indexed_grouping(terminal_text, new_group_index);
1477 // LP change: just in case...
1478 if (!new_group) return success;
1479 switch(new_group->type)
1480 {
1481 case _logon_group:
1482 case _end_group:
1483 use_new_group= false;
1484 break;
1485
1486 case _interlevel_teleport_group:
1487 case _intralevel_teleport_group:
1488 // dprintf("This shouldn't happen!");
1489 break;
1490
1491 case _sound_group:
1492 case _tag_group:
1493 new_group_index--;
1494 done= false;
1495 break;
1496
1497 case _movie_group:
1498 case _track_group:
1499 case _checkpoint_group:
1500 case _pict_group:
1501 case _information_group:
1502 case _camera_group:
1503 done = true;
1504 break;
1505
1506 case _unfinished_group:
1507 case _success_group:
1508 case _failure_group:
1509 case _static_group:
1510 use_new_group= false;
1511 break;
1512
1513 default:
1514 break;
1515 }
1516 } else {
1517 done= true;
1518 use_new_group= false;
1519 }
1520 if (!use_new_group) done = true; // this fixes perma-loop freeze
1521 } while(!done);
1522
1523 if(use_new_group)
1524 {
1525 /* Go there.. */
1526 goto_terminal_group(player_index, terminal_text, new_group_index);
1527 success= true;
1528 }
1529 }
1530
1531 return success;
1532 }
1533
next_terminal_group(short player_index,terminal_text_t * terminal_text)1534 static void next_terminal_group(
1535 short player_index,
1536 terminal_text_t *terminal_text)
1537 {
1538 struct player_terminal_data *terminal_data= get_player_terminal_data(player_index);
1539 bool update_line_count= false;
1540
1541 if(terminal_data->current_group==NONE)
1542 {
1543 update_line_count= true;
1544
1545 switch(terminal_data->level_completion_state)
1546 {
1547 case _level_unfinished:
1548 terminal_data->current_group= find_group_type(terminal_text, _unfinished_group);
1549 break;
1550
1551 case _level_finished:
1552 terminal_data->current_group= find_group_type(terminal_text, _success_group);
1553 if(terminal_data->current_group==static_cast<int16>(terminal_text->groupings.size()))
1554 {
1555 /* Fallback. */
1556 terminal_data->current_group= find_group_type(terminal_text, _unfinished_group);
1557 // assert(terminal_data->current_group != terminal_text->groupings.size());
1558 }
1559 break;
1560
1561 case _level_failed:
1562 terminal_data->current_group= find_group_type(terminal_text, _failure_group);
1563 if(terminal_data->current_group==static_cast<int16>(terminal_text->groupings.size()))
1564 {
1565 /* Fallback. */
1566 terminal_data->current_group= find_group_type(terminal_text, _unfinished_group);
1567 // assert(terminal_data->current_group != terminal_text->groupings.size());
1568 }
1569 break;
1570
1571 default:
1572 break;
1573 }
1574
1575 if (terminal_data->current_group==static_cast<int16>(terminal_text->groupings.size()) && terminal_text->groupings.size() && (terminal_text->groupings[0].flags & _group_is_marathon_1))
1576 {
1577 // Marathon 1 fallback
1578 terminal_data->current_group = 0;
1579 } else {
1580
1581 /* Note that the information groups are now keywords, and can have no data associated with them */
1582 next_terminal_group(player_index, terminal_text);
1583 }
1584 } else {
1585 terminal_data->current_group++;
1586 assert(terminal_data->current_group >= 0);
1587 if((size_t)terminal_data->current_group>=terminal_text->groupings.size())
1588 {
1589 next_terminal_state(player_index);
1590 } else {
1591 update_line_count= true;
1592 }
1593 }
1594
1595 if(update_line_count)
1596 {
1597 goto_terminal_group(player_index, terminal_text, terminal_data->current_group);
1598 }
1599
1600 SET_TERMINAL_IS_DIRTY(terminal_data, true);
1601 }
1602
goto_terminal_group(short player_index,terminal_text_t * terminal_text,short new_group_index)1603 static void goto_terminal_group(
1604 short player_index,
1605 terminal_text_t *terminal_text,
1606 short new_group_index)
1607 {
1608 struct player_terminal_data *terminal_data= get_player_terminal_data(player_index);
1609 struct player_data *player= get_player_data(player_index);
1610 struct terminal_groupings *current_group;
1611
1612 terminal_data->current_group= new_group_index;
1613
1614 current_group= get_indexed_grouping(terminal_text, terminal_data->current_group);
1615 // LP change: just in case...
1616 if (!current_group)
1617 {
1618 // Copied from _end_group case
1619 next_terminal_state(player_index);
1620 terminal_data->maximum_line= 1; // any click or keypress will get us out...
1621 return;
1622 }
1623
1624 terminal_data->current_line= 0;
1625
1626 switch(current_group->type)
1627 {
1628 case _logon_group:
1629 play_object_sound(player->object_index, Sound_TerminalLogon());
1630 terminal_data->phase= LOG_DURATION_BEFORE_TIMEOUT;
1631 terminal_data->maximum_line= current_group->maximum_line_count;
1632 break;
1633
1634 case _logoff_group:
1635 play_object_sound(player->object_index, Sound_TerminalLogoff());
1636 terminal_data->phase= LOG_DURATION_BEFORE_TIMEOUT;
1637 terminal_data->maximum_line= current_group->maximum_line_count;
1638 break;
1639
1640 case _interlevel_teleport_group:
1641 case _intralevel_teleport_group:
1642 case _sound_group:
1643 case _tag_group:
1644 case _movie_group:
1645 case _track_group:
1646 case _camera_group:
1647 terminal_data->phase= NONE;
1648 terminal_data->maximum_line= current_group->maximum_line_count;
1649 break;
1650
1651 case _checkpoint_group:
1652 case _pict_group:
1653 terminal_data->phase= NONE;
1654 if(dynamic_world->player_count>1)
1655 {
1656 /* Use what the server told us */
1657 terminal_data->maximum_line= current_group->maximum_line_count;
1658 } else {
1659 /* Calculate this for ourselves. */
1660 Rect text_bounds;
1661
1662 /* The only thing we care about is the width. */
1663 calculate_bounds_for_text_box(current_group->flags, &text_bounds);
1664 terminal_data->maximum_line= count_total_lines(get_text_base(terminal_text),
1665 RECTANGLE_WIDTH(&text_bounds), current_group->start_index,
1666 current_group->start_index+current_group->length);
1667 }
1668 break;
1669
1670 case _information_group:
1671 terminal_data->phase= NONE;
1672 if(dynamic_world->player_count>1)
1673 {
1674 /* Use what the server told us */
1675 terminal_data->maximum_line= current_group->maximum_line_count;
1676 } else {
1677 /* Calculate this for ourselves. */
1678 Rect bounds= get_term_rectangle(_terminal_full_text_rect);
1679 terminal_data->maximum_line= count_total_lines(get_text_base(terminal_text),
1680 RECTANGLE_WIDTH(&bounds), current_group->start_index,
1681 current_group->start_index+current_group->length);
1682 }
1683 break;
1684
1685 case _static_group:
1686 terminal_data->phase= current_group->permutation;
1687 terminal_data->maximum_line= current_group->maximum_line_count;
1688 break;
1689
1690 case _end_group:
1691 /* Get Out! */
1692 next_terminal_state(player_index);
1693 terminal_data->maximum_line= 1; // any click or keypress will get us out...
1694 break;
1695
1696 case _unfinished_group:
1697 case _success_group:
1698 case _failure_group:
1699 vwarn(0, "You shouldn't be coming to this group");
1700 break;
1701
1702 default:
1703 break;
1704 }
1705 }
1706
1707 /* I'll use this function, almost untouched.. */
get_date_string(char * date_string,short flags)1708 static void get_date_string(
1709 char *date_string,
1710 short flags)
1711 {
1712 char temp_string[101];
1713 int32 game_time_passed;
1714 time_t seconds;
1715 struct tm game_time;
1716
1717 /* Treat the date as if it were recent. */
1718 game_time_passed= INT32_MAX - dynamic_world->game_information.game_time_remaining;
1719
1720 /* convert the game seconds to machine seconds */
1721 if (flags & _group_is_marathon_1)
1722 {
1723 seconds = 809304137;
1724 seconds += 7*60*(game_time_passed / TICKS_PER_SECOND);
1725 }
1726 else
1727 {
1728 seconds = 800070137;
1729 seconds += game_time_passed / TICKS_PER_SECOND;
1730 }
1731 game_time = *localtime(&seconds);
1732 game_time.tm_year= 437;
1733 game_time.tm_yday= 0;
1734 game_time.tm_isdst= 0;
1735
1736 getcstr(temp_string, strCOMPUTER_LABELS, _date_format);
1737 strftime(date_string, 100, temp_string, &game_time);
1738 }
1739
present_checkpoint_text(terminal_text_t * terminal_text,short current_group_index,short current_line)1740 static void present_checkpoint_text(
1741 terminal_text_t *terminal_text,
1742 short current_group_index,
1743 short current_line)
1744 {
1745 Rect bounds;
1746 struct overhead_map_data overhead_data;
1747 struct terminal_groupings *current_group;
1748
1749 current_group= get_indexed_grouping(terminal_text, current_group_index);
1750 // LP change: just in case...
1751 if (!current_group) return;
1752
1753 // draw the overhead map.
1754 calculate_bounds_for_object(current_group->flags, &bounds, NULL);
1755 overhead_data.scale = 1;
1756
1757 if(find_checkpoint_location(current_group->permutation, &overhead_data.origin,
1758 &overhead_data.origin_polygon_index))
1759 {
1760 overhead_data.top= bounds.top;
1761 overhead_data.left= bounds.left;
1762 overhead_data.half_width= RECTANGLE_WIDTH(&bounds)/2;
1763 overhead_data.half_height= RECTANGLE_HEIGHT(&bounds)/2;
1764 overhead_data.width= RECTANGLE_WIDTH(&bounds);
1765 overhead_data.height= RECTANGLE_HEIGHT(&bounds);
1766 overhead_data.mode= _rendering_checkpoint_map;
1767 set_drawing_clip_rectangle(bounds.top, bounds.left, bounds.bottom, bounds.right);
1768 _render_overhead_map(&overhead_data);
1769 set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
1770 } else {
1771 char format_string[128];
1772
1773 SDL_Rect r = {bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top};
1774 SDL_FillRect(/*world_pixels*/draw_surface, &r, SDL_MapRGB(/*world_pixels*/draw_surface->format, 0, 0, 0));
1775
1776 getcstr(format_string, strERRORS, checkpointNotFound);
1777 sprintf(temporary, format_string, current_group->permutation);
1778
1779 const font_info *font = GetInterfaceFont(_computer_interface_title_font);
1780 // const sdl_font_info *font = load_font(*_get_font_spec(_computer_interface_title_font));
1781 int width = text_width(temporary, font, styleNormal);
1782 draw_text(/*world_pixels*/draw_surface, temporary,
1783 bounds.left + (RECTANGLE_WIDTH(&bounds) - width) / 2,
1784 bounds.top + RECTANGLE_HEIGHT(&bounds) / 2,
1785 SDL_MapRGB(/*world_pixels*/draw_surface->format, 0xff, 0xff, 0xff),
1786 font, styleNormal);
1787 }
1788
1789 // draw the text
1790 calculate_bounds_for_text_box(current_group->flags, &bounds);
1791 draw_computer_text(&bounds, terminal_text, current_group_index, current_line);
1792 }
1793
find_checkpoint_location(short checkpoint_index,world_point2d * location,short * polygon_index)1794 static bool find_checkpoint_location(
1795 short checkpoint_index,
1796 world_point2d *location,
1797 short *polygon_index)
1798 {
1799 bool success= false;
1800 short ii;
1801 struct map_object *saved_object;
1802 short match_count;
1803
1804 location->x= location->y= match_count= 0;
1805 for (ii = 0, saved_object= saved_objects; ii < dynamic_world->initial_objects_count; ii++, saved_object++)
1806 {
1807 if (saved_object->type==_saved_goal && saved_object->index==checkpoint_index)
1808 {
1809 location->x+= saved_object->location.x;
1810 location->y+= saved_object->location.y;
1811 // *polygon_index= saved_object->polygon_index;
1812 match_count++;
1813 }
1814 }
1815
1816 if(match_count)
1817 {
1818 /* Now average.. */
1819 location->x /= match_count;
1820 location->y /= match_count;
1821 *polygon_index= world_point_to_polygon_index(location);
1822 success = (*polygon_index != NONE);
1823 }
1824
1825 return success;
1826 }
1827
handle_reading_terminal_keys(short player_index,int32 action_flags)1828 static void handle_reading_terminal_keys(
1829 short player_index,
1830 int32 action_flags)
1831 {
1832 struct player_terminal_data *terminal= get_player_terminal_data(player_index);
1833 terminal_text_t *terminal_text= get_indexed_terminal_data(terminal->terminal_id);
1834 // LP addition: quit if none
1835 if (!terminal_text) return;
1836 struct terminal_groupings *current_group;
1837 short initial_group= terminal->current_group;
1838 short initial_line= terminal->current_line;
1839 bool aborted= false;
1840 struct player_data *player= get_player_data(player_index);
1841 short line_delta= 0;
1842 bool change_state= false;
1843
1844 current_group= get_indexed_grouping(terminal_text, terminal->current_group);
1845 // LP change: just in case...
1846 if (!current_group)
1847 {
1848 // Copied from _end_group case
1849 next_terminal_state(player_index);
1850 aborted= true;
1851 }
1852
1853 switch(current_group->type)
1854 {
1855 case _logon_group:
1856 case _logoff_group:
1857 case _unfinished_group:
1858 case _success_group:
1859 case _failure_group:
1860 case _information_group:
1861 case _checkpoint_group:
1862 case _pict_group:
1863 case _camera_group:
1864 case _static_group:
1865 if(action_flags & _terminal_up_arrow)
1866 {
1867 line_delta= -1;
1868 }
1869
1870 if(action_flags & _terminal_down_arrow)
1871 {
1872 line_delta= 1;
1873 }
1874
1875 if(action_flags & _terminal_page_down)
1876 {
1877 play_object_sound(player->object_index, Sound_TerminalPage());
1878 line_delta= terminal_text->lines_per_page;
1879 }
1880
1881 if(action_flags & _terminal_page_up)
1882 {
1883 play_object_sound(player->object_index, Sound_TerminalPage());
1884 line_delta= -terminal_text->lines_per_page;
1885 }
1886
1887 /* this one should change state, if necessary */
1888 if(action_flags & _terminal_next_state)
1889 {
1890 play_object_sound(player->object_index, Sound_TerminalPage());
1891 /* Force a state change. */
1892 line_delta= terminal_text->lines_per_page;
1893 change_state= true;
1894 }
1895
1896 if(action_flags & _any_abort_key_mask)
1897 {
1898 /* Abort! */
1899 initialize_player_terminal_info(player_index);
1900 aborted= true;
1901 }
1902 break;
1903
1904 case _movie_group:
1905 case _track_group:
1906 break;
1907
1908 case _end_group:
1909 next_terminal_state(player_index);
1910 aborted= true;
1911 break;
1912
1913 case _interlevel_teleport_group: // permutation is level to go to
1914 teleport_to_level(current_group->permutation);
1915 initialize_player_terminal_info(player_index);
1916 aborted= true;
1917 break;
1918
1919 case _intralevel_teleport_group: // permutation is polygon to go to.
1920 teleport_to_polygon(player_index, current_group->permutation);
1921 initialize_player_terminal_info(player_index);
1922 aborted= true;
1923 break;
1924
1925 case _sound_group: // permutation is the sound id to play
1926 /* Play the sound immediately, and then go to the next level.. */
1927 {
1928 struct player_data *player= get_player_data(player_index);
1929 play_object_sound(player->object_index, current_group->permutation);
1930 next_terminal_group(player_index, terminal_text);
1931 aborted= true;
1932 }
1933 break;
1934
1935 case _tag_group:
1936 set_tagged_light_statuses(current_group->permutation, true);
1937 try_and_change_tagged_platform_states(current_group->permutation, true);
1938 next_terminal_group(player_index, terminal_text);
1939 aborted= true;
1940 break;
1941
1942 default:
1943 break;
1944 }
1945
1946 /* If we are dirty.. */
1947 terminal->current_line+= line_delta;
1948 if(!aborted && (initial_group != terminal->current_group ||
1949 initial_line != terminal->current_line))
1950 {
1951 if(terminal->current_line<0)
1952 {
1953 if(!previous_terminal_group(player_index, terminal_text))
1954 {
1955 terminal->current_line= 0;
1956 }
1957 }
1958
1959 if(terminal->current_line>=terminal->maximum_line)
1960 {
1961 assert(terminal->current_group >= 0);
1962 if(static_cast<size_t>(terminal->current_group)+1>=terminal_text->groupings.size())
1963 {
1964 if(change_state)
1965 {
1966 /* Go ahead an let the terminal group deal with it.. */
1967 next_terminal_group(player_index, terminal_text);
1968 }
1969 else
1970 {
1971 /* renumber the lines */
1972 terminal->current_line-= line_delta;
1973 }
1974 }
1975 else
1976 {
1977 next_terminal_group(player_index, terminal_text);
1978 }
1979 }
1980
1981 SET_TERMINAL_IS_DIRTY(terminal, true);
1982 }
1983 }
1984
calculate_bounds_for_object(short flags,Rect * bounds,Rect * source)1985 static void calculate_bounds_for_object(
1986 short flags,
1987 Rect *bounds,
1988 Rect *source)
1989 {
1990 if(source && flags & _center_object)
1991 {
1992 *bounds = get_term_rectangle(_terminal_logon_graphic_rect);
1993 if(RECTANGLE_WIDTH(source)>RECTANGLE_WIDTH(bounds)
1994 || RECTANGLE_HEIGHT(source)>RECTANGLE_HEIGHT(bounds))
1995 {
1996 /* Just return the normal frame. Aspect ratio will take care of us.. */
1997 } else {
1998 InsetRect(bounds, (RECTANGLE_WIDTH(bounds)-RECTANGLE_WIDTH(source))/2,
1999 (RECTANGLE_HEIGHT(bounds)-RECTANGLE_HEIGHT(source))/2);
2000 }
2001 }
2002 else if(flags & _draw_object_on_right)
2003 {
2004 *bounds = get_term_rectangle(_terminal_right_rect);
2005 }
2006 else
2007 {
2008 *bounds = get_term_rectangle(_terminal_left_rect);
2009 }
2010 }
2011
2012 #define MAXIMUM_GROUPS_PER_TERMINAL 15
2013
2014 static void calculate_maximum_lines_for_groups(struct terminal_groupings *groups,
2015 short group_count, char *text_base);
2016
2017 class MarathonTerminalCompiler
2018 {
2019 public:
MarathonTerminalCompiler(char * text,short length)2020 MarathonTerminalCompiler(char* text, short length) : terminal(new terminal_text_t), in_buffer(text, length), in(&in_buffer) {
2021 briefing_group.type = 0;
2022 success_group.type = 0;
2023 failure_group.type = 0;
2024 unfinished_group.type = 0;
2025 group.type = NONE;
2026 }
2027 terminal_text_t* Compile();
2028
2029 private:
2030 void FinishGroup();
2031 void CompileLine(const std::string& line);
2032
2033 void BuildUnfinishedGroup();
2034 void BuildSuccessGroup();
2035 void BuildFailureGroup();
2036
2037 std::unique_ptr<terminal_text_t> terminal;
2038
2039 io::stream_buffer<io::array_source> in_buffer;
2040 std::istream in;
2041
2042 std::vector<uint8_t> out;
2043
2044 int16 current_group_type;
2045 terminal_groupings group;
2046
2047 terminal_groupings logon_group;
2048 terminal_groupings briefing_group;
2049 terminal_groupings unfinished_group;
2050 terminal_groupings failure_group;
2051 terminal_groupings success_group;
2052 std::vector<terminal_groupings> other_groups;
2053 };
2054
Compile()2055 terminal_text_t* MarathonTerminalCompiler::Compile()
2056 {
2057 std::string line;
2058 while (std::getline(in, line, static_cast<char>(MAC_LINE_END)))
2059 {
2060 if (line[0] == '#')
2061 {
2062 FinishGroup();
2063
2064 group.flags = _group_is_marathon_1;
2065 group.permutation = 0;
2066
2067 if (algo::istarts_with(line, "#logon"))
2068 {
2069 group.type = _logon_group;
2070 }
2071 else if (algo::istarts_with(line, "#information"))
2072 {
2073 group.type = _information_group;
2074 }
2075 else if (algo::istarts_with(line, "#checkpoint"))
2076 {
2077 group.type = _checkpoint_group;
2078 std::istringstream permutation(line.substr(1 + strlen("#checkpoint")));
2079 permutation >> group.permutation;
2080 }
2081 else if (algo::istarts_with(line, "#briefing"))
2082 {
2083 group.type = _interlevel_teleport_group;
2084 std::istringstream permutation(line.substr(1 + strlen("#briefing")));
2085 permutation >> group.permutation;
2086 }
2087 else if (algo::istarts_with(line, "#unfinished"))
2088 {
2089 group.type = _unfinished_group;
2090 }
2091 else if (algo::istarts_with(line, "#success"))
2092 {
2093 group.type = _success_group;
2094 }
2095 else if (algo::istarts_with(line, "#failure"))
2096 {
2097 group.type = _failure_group;
2098 }
2099 else
2100 {
2101 logWarning("Unrecognized group");
2102 terminal.reset(0);
2103 return 0;
2104 }
2105
2106 group.start_index = out.size();
2107 }
2108 else if (line[0] != ';')
2109 {
2110 CompileLine(line);
2111 }
2112 }
2113
2114 FinishGroup();
2115
2116 BuildUnfinishedGroup();
2117 BuildSuccessGroup();
2118 BuildFailureGroup();
2119
2120 terminal->text = out;
2121
2122 terminal->lines_per_page = calculate_lines_per_page();
2123 calculate_maximum_lines_for_groups(&terminal->groupings[0], terminal->groupings.size(), reinterpret_cast<char*>(terminal->text.data()));
2124
2125 return terminal.release();
2126 }
2127
compile_marathon_terminal(char * text,short length)2128 static terminal_text_t* compile_marathon_terminal(char* text, short length)
2129 {
2130 MarathonTerminalCompiler compiler(text, length);
2131 return compiler.Compile();
2132 }
2133
FinishGroup()2134 void MarathonTerminalCompiler::FinishGroup()
2135 {
2136 group.length = out.size() - group.start_index;
2137
2138 out.push_back('\0');
2139
2140 switch (group.type)
2141 {
2142 case _logon_group:
2143 logon_group = group;
2144 break;
2145 case _information_group:
2146 case _checkpoint_group:
2147 other_groups.push_back(group);
2148 break;
2149 case _interlevel_teleport_group:
2150 briefing_group = group;
2151 break;
2152 case _success_group:
2153 success_group = group;
2154 break;
2155 case _failure_group:
2156 failure_group = group;
2157 break;
2158 case _unfinished_group:
2159 unfinished_group = group;
2160 break;
2161 }
2162 }
2163
CompileLine(const std::string & line)2164 void MarathonTerminalCompiler::CompileLine(const std::string& line)
2165 {
2166 // Marathon formatting resets at the beginning of the line
2167 text_face_data font_change;
2168 font_change.index = out.size();
2169 font_change.face = 0;
2170 font_change.color = 0;
2171
2172 terminal->font_changes.push_back(font_change);
2173
2174 for (std::string::const_iterator it = line.begin(); it != line.end(); ++it)
2175 {
2176 if (*it == '$' && (it + 1 != line.end()))
2177 {
2178 font_change.index = out.size();
2179 char c = *(it + 1);
2180 switch (c)
2181 {
2182 case 'B':
2183 font_change.face |= _bold_text;
2184 terminal->font_changes.push_back(font_change);
2185 ++it;
2186 break;
2187 case 'b':
2188 font_change.face &= ~_bold_text;
2189 terminal->font_changes.push_back(font_change);
2190 ++it;
2191 break;
2192 case 'I':
2193 font_change.face |= _italic_text;
2194 terminal->font_changes.push_back(font_change);
2195 ++it;
2196 break;
2197 case 'i':
2198 font_change.face &= ~_italic_text;
2199 terminal->font_changes.push_back(font_change);
2200 ++it;
2201 break;
2202 case 'U':
2203 font_change.face |= _underline_text;
2204 terminal->font_changes.push_back(font_change);
2205 ++it;
2206 break;
2207 case 'u':
2208 font_change.face &= ~_underline_text;
2209 terminal->font_changes.push_back(font_change);
2210 ++it;
2211 break;
2212 case 'C':
2213 if (it + 2 != line.end())
2214 {
2215 char cc = *(it + 2);
2216 if (cc >= '0' && cc <= '7')
2217 {
2218 font_change.color = cc - '0';
2219 terminal->font_changes.push_back(font_change);
2220 it += 2;
2221 }
2222 else
2223 {
2224 out.push_back(*it);
2225 }
2226 }
2227 else
2228 {
2229 out.push_back(*it);
2230 }
2231 break;
2232 default:
2233 ++it;
2234 }
2235 }
2236 else if (*it == '%' && (it + 1 != line.end()))
2237 {
2238 static char replacement[] = "The colony has been wiped out. Phhht! Just like that.";
2239
2240 char c = *(it + 1);
2241 switch (c)
2242 {
2243 case 'r':
2244 out.insert(out.end(), replacement, replacement + strlen(replacement));
2245 ++it;
2246 break;
2247 case '%':
2248 out.push_back(*it);
2249 ++it;
2250 break;
2251 default:
2252 out.push_back(*it);
2253 }
2254 }
2255 else
2256 {
2257 out.push_back(*it);
2258 }
2259 }
2260
2261 out.push_back(MAC_LINE_END);
2262 }
2263
BuildUnfinishedGroup()2264 void MarathonTerminalCompiler::BuildUnfinishedGroup()
2265 {
2266 terminal_groupings group;
2267 group.flags = 0;
2268 group.type = _unfinished_group;
2269 group.permutation = 0;
2270 group.start_index = 0;
2271 group.length = 0;
2272 terminal->groupings.push_back(group);
2273
2274 terminal->groupings.push_back(logon_group);
2275
2276 // if there's an unfinished group, push it as unfinished
2277 // otherwise, this must not be an end-of-level term, use all the other groups
2278 if (unfinished_group.type)
2279 {
2280 group = unfinished_group;
2281 group.type = _information_group;
2282 terminal->groupings.push_back(group);
2283
2284 group = logon_group;
2285 group.type = _logoff_group;
2286 terminal->groupings.push_back(group);
2287 }
2288 else
2289 {
2290 terminal->groupings.insert(terminal->groupings.end(), other_groups.begin(), other_groups.end());
2291
2292 group = logon_group;
2293 group.type = _logoff_group;
2294 terminal->groupings.push_back(group);
2295 }
2296
2297 group.type = _end_group;
2298 group.flags = 0;
2299 group.permutation = 0;
2300 group.start_index = 0;
2301 group.length = 0;
2302 terminal->groupings.push_back(group);
2303 }
2304
BuildSuccessGroup()2305 void MarathonTerminalCompiler::BuildSuccessGroup()
2306 {
2307 if (success_group.type || briefing_group.type)
2308 {
2309 terminal_groupings group;
2310 group.flags = 0;
2311 group.type = _success_group;
2312 group.permutation = 0;
2313 group.start_index = 0;
2314 group.length = 0;
2315 terminal->groupings.push_back(group);
2316
2317 terminal->groupings.push_back(logon_group);
2318
2319 if (success_group.type == _success_group)
2320 {
2321 group = success_group;
2322 group.type = _information_group;
2323 terminal->groupings.push_back(group);
2324 }
2325
2326 if (briefing_group.type)
2327 {
2328 group = briefing_group;
2329 group.type = _information_group;
2330 group.permutation = 0;
2331 terminal->groupings.push_back(group);
2332 }
2333
2334 group = logon_group;
2335 group.type = _logoff_group;
2336 terminal->groupings.push_back(group);
2337
2338 if (briefing_group.type)
2339 {
2340 group.type = _interlevel_teleport_group;
2341 group.permutation = briefing_group.permutation;
2342 group.start_index = 0;
2343 group.length = 0;
2344 terminal->groupings.push_back(group);
2345 }
2346
2347 group.type = _end_group;
2348 group.flags = 0;
2349 group.permutation = 0;
2350 group.start_index = 0;
2351 group.length = 0;
2352 terminal->groupings.push_back(group);
2353 }
2354 }
2355
BuildFailureGroup()2356 void MarathonTerminalCompiler::BuildFailureGroup()
2357 {
2358 if (failure_group.type)
2359 {
2360 terminal_groupings group;
2361 group.flags = 0;
2362 group.type = _failure_group;
2363 group.permutation = 0;
2364 group.start_index = 0;
2365 group.length = 0;
2366 terminal->groupings.push_back(group);
2367
2368 terminal->groupings.push_back(logon_group);
2369
2370 group = failure_group;
2371 group.type = _information_group;
2372 terminal->groupings.push_back(group);
2373
2374 if (briefing_group.type)
2375 {
2376 group = briefing_group;
2377 group.type = _information_group;
2378 group.permutation = 0;
2379 terminal->groupings.push_back(group);
2380 }
2381
2382 group = logon_group;
2383 group.type = _logoff_group;
2384 terminal->groupings.push_back(group);
2385
2386 if (briefing_group.type)
2387 {
2388 group.type = _interlevel_teleport_group;
2389 group.permutation = briefing_group.permutation;
2390 group.start_index = 0;
2391 group.length = 0;
2392 terminal->groupings.push_back(group);
2393 }
2394
2395 group.type = _end_group;
2396 group.flags = 0;
2397 group.permutation = 0;
2398 group.start_index = 0;
2399 group.length = 0;
2400 terminal->groupings.push_back(group);
2401 }
2402 }
2403
calculate_maximum_lines_for_groups(struct terminal_groupings * groups,short group_count,char * text_base)2404 static void calculate_maximum_lines_for_groups(
2405 struct terminal_groupings *groups,
2406 short group_count,
2407 char *text_base)
2408 {
2409 short index;
2410
2411 for(index= 0; index<group_count; ++index)
2412 {
2413 switch(groups[index].type)
2414 {
2415 case _logon_group:
2416 case _logoff_group:
2417 case _interlevel_teleport_group:
2418 case _intralevel_teleport_group:
2419 case _sound_group:
2420 case _tag_group:
2421 case _movie_group:
2422 case _track_group:
2423 case _camera_group:
2424 case _static_group:
2425 case _end_group:
2426 groups[index].maximum_line_count= 1; // any click or keypress gets us out.
2427 break;
2428
2429 case _unfinished_group:
2430 case _success_group:
2431 case _failure_group:
2432 groups[index].maximum_line_count= 0; // should never get to one of these groups.
2433 break;
2434
2435 case _checkpoint_group:
2436 case _pict_group:
2437 {
2438 Rect text_bounds;
2439
2440 /* The only thing we care about is the width. */
2441 calculate_bounds_for_text_box(groups[index].flags, &text_bounds);
2442 groups[index].maximum_line_count= count_total_lines(text_base,
2443 RECTANGLE_WIDTH(&text_bounds), groups[index].start_index,
2444 groups[index].start_index+groups[index].length);
2445 }
2446 break;
2447
2448 case _information_group:
2449 {
2450 Rect text_bounds= get_term_rectangle(_terminal_full_text_rect);
2451
2452 groups[index].maximum_line_count= count_total_lines(text_base,
2453 RECTANGLE_WIDTH(&text_bounds), groups[index].start_index,
2454 groups[index].start_index+groups[index].length);
2455 }
2456 break;
2457
2458 default:
2459 break;
2460 }
2461 }
2462 }
2463
get_indexed_grouping(terminal_text_t * data,short index)2464 static struct terminal_groupings *get_indexed_grouping(
2465 terminal_text_t *data,
2466 short index)
2467 {
2468 if (index < 0 || index >= int(data->groupings.size()))
2469 return NULL;
2470
2471 return &data->groupings[index];
2472 }
2473
get_indexed_font_changes(terminal_text_t * data,short index)2474 static struct text_face_data *get_indexed_font_changes(
2475 terminal_text_t *data,
2476 short index)
2477 {
2478 if (index < 0 || index >= int(data->font_changes.size()))
2479 return NULL;
2480
2481 return &data->font_changes[index];
2482 }
2483
get_text_base(terminal_text_t * data)2484 static char *get_text_base(
2485 terminal_text_t *data)
2486 {
2487 return (char *)data->text.data();
2488 }
2489
calculate_lines_per_page(void)2490 static short calculate_lines_per_page(
2491 void)
2492 {
2493 Rect bounds;
2494 short lines_per_page;
2495
2496 if (!film_profile.calculate_terminal_lines_correctly)
2497 {
2498 bounds= get_term_rectangle(_terminal_screen_rect);
2499 lines_per_page= (RECTANGLE_HEIGHT(&bounds)-2*BORDER_HEIGHT)/_get_font_line_height(_computer_interface_font);
2500 lines_per_page-= FUDGE_FACTOR;
2501 }
2502 else
2503 {
2504 bounds= get_term_rectangle(_terminal_full_text_rect);
2505 lines_per_page= RECTANGLE_HEIGHT(&bounds)/_get_font_line_height(_computer_interface_font);
2506 }
2507
2508 return lines_per_page;
2509 }
2510
get_term_rectangle(short index)2511 static Rect get_term_rectangle(short index)
2512 {
2513 Rect bounds;
2514 screen_rectangle *term_rect = get_interface_rectangle(_terminal_screen_rect);
2515 screen_rectangle *target_rect = get_interface_rectangle(index);
2516 bounds.left = target_rect->left - term_rect->left;
2517 bounds.top = target_rect->top - term_rect->top;
2518 bounds.right = target_rect->right - term_rect->left;
2519 bounds.bottom = target_rect->bottom - term_rect->top;
2520 return bounds;
2521 }
2522
2523
2524 /*
2525 * Calculate the length the loaded terminal data would take up on disk
2526 * (for saving)
2527 */
2528
packed_terminal_length(const terminal_text_t & t)2529 static size_t packed_terminal_length(const terminal_text_t &t)
2530 {
2531 return SIZEOF_static_preprocessed_terminal_data
2532 + t.groupings.size() * SIZEOF_terminal_groupings
2533 + t.font_changes.size() * SIZEOF_text_face_data
2534 + t.text.size();
2535 }
2536
calculate_packed_terminal_data_length(void)2537 size_t calculate_packed_terminal_data_length(void)
2538 {
2539 size_t total = 0;
2540
2541 // Loop for all terminals
2542 vector<terminal_text_t>::const_iterator t = map_terminal_text.begin(), tend = map_terminal_text.end();
2543 while (t != tend) {
2544 total += packed_terminal_length(*t);
2545 t++;
2546 }
2547
2548 return total;
2549 }
2550
2551
2552 /*
2553 * Unpack terminal data from stream
2554 */
2555
unpack_map_terminal_data(uint8 * p,size_t count)2556 void unpack_map_terminal_data(uint8 *p, size_t count)
2557 {
2558 // Clear existing terminals
2559 map_terminal_text.clear();
2560
2561 // Unpack all terminals
2562 while (count > 0) {
2563
2564 // Create new terminal_text_t
2565 terminal_text_t t;
2566 map_terminal_text.push_back(t);
2567 terminal_text_t &data = map_terminal_text.back();
2568
2569 // Read header
2570 uint8 *p_start = p, *p_header = p;
2571 uint16 total_length, grouping_count, font_changes_count;
2572 StreamToValue(p, total_length);
2573 StreamToValue(p, data.flags);
2574 StreamToValue(p, data.lines_per_page);
2575 StreamToValue(p, grouping_count);
2576 StreamToValue(p, font_changes_count);
2577 assert((p - p_start) == static_cast<ptrdiff_t>(SIZEOF_static_preprocessed_terminal_data));
2578
2579 // Reserve memory for groupings and font changes
2580 data.groupings.reserve(grouping_count);
2581 data.font_changes.reserve(font_changes_count);
2582
2583 // Read groupings
2584 p_start = p;
2585 for (int i=0; i<grouping_count; i++) {
2586 terminal_groupings g;
2587 StreamToValue(p, g.flags);
2588 StreamToValue(p, g.type);
2589 StreamToValue(p, g.permutation);
2590 StreamToValue(p, g.start_index);
2591 StreamToValue(p, g.length);
2592 StreamToValue(p, g.maximum_line_count);
2593 data.groupings.push_back(g);
2594 }
2595 assert((p - p_start) == static_cast<ptrdiff_t>(SIZEOF_terminal_groupings) * grouping_count);
2596
2597 // Read font changes
2598 p_start = p;
2599 for (int i=0; i<font_changes_count; i++) {
2600 text_face_data f;
2601 StreamToValue(p, f.index);
2602 StreamToValue(p, f.face);
2603 StreamToValue(p, f.color);
2604 data.font_changes.push_back(f);
2605 }
2606 assert((p - p_start) == static_cast<ptrdiff_t>(SIZEOF_text_face_data) * font_changes_count);
2607
2608 // Read text (no conversion)
2609 const int text_length = total_length - static_cast<int>(p - p_header);
2610 assert(text_length >= 0);
2611 data.text.resize(text_length);
2612 StreamToBytes(p, data.text.data(), data.text.size());
2613
2614 // Continue with next terminal
2615 count -= total_length;
2616 }
2617 }
2618
2619
2620 /*
2621 * Pack terminal data to stream
2622 */
2623
pack_map_terminal_data(uint8 * p,size_t count)2624 void pack_map_terminal_data(uint8 *p, size_t count)
2625 {
2626 // Pack all terminals
2627 vector<terminal_text_t>::const_iterator t = map_terminal_text.begin(), tend = map_terminal_text.end();
2628 while (t != tend) {
2629
2630 // Write header
2631 uint8 *p_start = p;
2632 uint16 total_length = static_cast<uint16>(packed_terminal_length(*t));
2633 uint16 grouping_count = static_cast<uint16>(t->groupings.size());
2634 uint16 font_changes_count = static_cast<uint16>(t->font_changes.size());
2635 ValueToStream(p, total_length);
2636 ValueToStream(p, t->flags);
2637 ValueToStream(p, t->lines_per_page);
2638 ValueToStream(p, grouping_count);
2639 ValueToStream(p, font_changes_count);
2640 assert((p - p_start) == static_cast<ptrdiff_t>(SIZEOF_static_preprocessed_terminal_data));
2641
2642 // Write groupings
2643 p_start = p;
2644 vector<terminal_groupings>::const_iterator g = t->groupings.begin(), gend = t->groupings.end();
2645 while (g != gend) {
2646 ValueToStream(p, g->flags);
2647 ValueToStream(p, g->type);
2648 ValueToStream(p, g->permutation);
2649 ValueToStream(p, g->start_index);
2650 ValueToStream(p, g->length);
2651 ValueToStream(p, g->maximum_line_count);
2652 g++;
2653 }
2654 assert((p - p_start) == static_cast<ptrdiff_t>(SIZEOF_terminal_groupings) * grouping_count);
2655
2656 // Write font changes
2657 p_start = p;
2658 vector<text_face_data>::const_iterator f = t->font_changes.begin(), fend = t->font_changes.end();
2659 while (f != fend) {
2660 ValueToStream(p, f->index);
2661 ValueToStream(p, f->face);
2662 ValueToStream(p, f->color);
2663 f++;
2664 }
2665 assert((p - p_start) == static_cast<ptrdiff_t>(SIZEOF_text_face_data) * font_changes_count);
2666
2667 // Write text (no conversion)
2668 BytesToStream(p, t->text.data(), t->text.size());
2669
2670 // Continue with next terminal
2671 t++;
2672 }
2673 }
2674
2675
unpack_player_terminal_data(uint8 * Stream,size_t Count)2676 uint8 *unpack_player_terminal_data(uint8 *Stream, size_t Count)
2677 {
2678 uint8* S = Stream;
2679 player_terminal_data* ObjPtr = player_terminals;
2680
2681 for (size_t k = 0; k < Count; k++, ObjPtr++)
2682 {
2683 StreamToValue(S,ObjPtr->flags);
2684 StreamToValue(S,ObjPtr->phase);
2685 StreamToValue(S,ObjPtr->state);
2686 StreamToValue(S,ObjPtr->current_group);
2687 StreamToValue(S,ObjPtr->level_completion_state);
2688 StreamToValue(S,ObjPtr->current_line);
2689 StreamToValue(S,ObjPtr->maximum_line);
2690 StreamToValue(S,ObjPtr->terminal_id);
2691 StreamToValue(S,ObjPtr->last_action_flag);
2692 }
2693
2694 assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_player_terminal_data));
2695 return S;
2696 }
2697
pack_player_terminal_data(uint8 * Stream,size_t Count)2698 uint8 *pack_player_terminal_data(uint8 *Stream, size_t Count)
2699 {
2700 uint8* S = Stream;
2701 player_terminal_data* ObjPtr = player_terminals;
2702
2703 for (size_t k = 0; k < Count; k++, ObjPtr++)
2704 {
2705 ValueToStream(S,ObjPtr->flags);
2706 ValueToStream(S,ObjPtr->phase);
2707 ValueToStream(S,ObjPtr->state);
2708 ValueToStream(S,ObjPtr->current_group);
2709 ValueToStream(S,ObjPtr->level_completion_state);
2710 ValueToStream(S,ObjPtr->current_line);
2711 ValueToStream(S,ObjPtr->maximum_line);
2712 ValueToStream(S,ObjPtr->terminal_id);
2713 ValueToStream(S,ObjPtr->last_action_flag);
2714 }
2715
2716 assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_player_terminal_data));
2717 return S;
2718 }
2719