1 //----------------------------------------------------------------------------
2 // EDGE Radius Trigger / Tip Code
3 //----------------------------------------------------------------------------
4 //
5 // Copyright (c) 1999-2010 The EDGE Team.
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (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 //----------------------------------------------------------------------------
18 //
19 // -KM- 1998/11/25 Fixed problems created by DDF.
20 // Radius Triggers can be added to wad files. RSCRIPT is the lump.
21 // Tip function can handle graphics.
22 // New functions: ondeath, #version
23 // Radius Triggers with radius < 0 affect entire map.
24 // Radius triggers used to save compatibility with hacks in Doom/Doom2
25 // (eg MAP07, E2M8, E3M8, MAP32 etc..)
26 //
27 // -AJA- 1999/10/23: Began work on a state model for RTS actions.
28 //
29 // -AJA- 1999/10/24: Split off actions into rad_act.c, and structures
30 // into the rad_main.h file.
31 //
32 // -AJA- 2000/01/04: Split off parsing code into rad_pars.c.
33 //
34
35 #include "i_defs.h"
36
37 #include "epi/file.h"
38 #include "epi/filesystem.h"
39
40 #include "dm_defs.h"
41 #include "dm_state.h"
42 #include "e_main.h"
43 #include "hu_draw.h"
44 #include "hu_style.h"
45 #include "hu_stuff.h"
46 #include "g_game.h"
47 #include "m_argv.h"
48 #include "m_misc.h"
49 #include "m_random.h"
50 #include "p_local.h"
51 #include "p_spec.h"
52 #include "rad_trig.h"
53 #include "rad_act.h"
54 #include "r_defs.h"
55 #include "am_map.h"
56 #include "r_colormap.h"
57 #include "r_draw.h"
58 #include "r_modes.h"
59 #include "s_sound.h"
60 #include "w_wad.h"
61 #include "z_zone.h"
62
63
64 #define MAXRTSLINE 2048
65
66
67 // Static Scripts. Never change once all scripts have been read in.
68 rad_script_t *r_scripts = NULL;
69
70 // Dynamic Triggers. These only exist for the current level.
71 rad_trigger_t *active_triggers = NULL;
72
73
74 class rts_menu_c
75 {
76 private:
77 static const int MAX_TITLE = 24;
78 static const int MAX_CHOICE = 9;
79
80 rad_trigger_t *trigger;
81
82 style_c *style;
83
84 std::string title;
85
86 std::vector< std::string > choices;
87
88 public:
rts_menu_c(s_show_menu_t * menu,rad_trigger_t * _trigger,style_c * _style)89 rts_menu_c(s_show_menu_t *menu, rad_trigger_t *_trigger, style_c *_style) :
90 trigger(_trigger), style(_style), title(), choices()
91 {
92 const char * text = menu->title;
93 if (menu->use_ldf)
94 text = language[text];
95
96 title = text;
97
98 bool no_choices = (! menu->options[0] || ! menu->options[1]);
99
100 for (int idx = 0; (idx < 9) && menu->options[idx]; idx++)
101 AddChoice(no_choices ? 0 : ('1' + idx), menu->options[idx], menu->use_ldf);
102 }
103
~rts_menu_c()104 ~rts_menu_c() { /* nothing to do */ }
105
106 private:
AddChoice(char key,const char * text,bool use_ldf)107 void AddChoice(char key, const char *text, bool use_ldf)
108 {
109 if (use_ldf)
110 text = language[text];
111
112 std::string choice_line = text;
113
114 if (key)
115 {
116 char buffer[8];
117 sprintf(buffer, "%c. ", key);
118
119 choice_line = std::string(buffer) + choice_line;
120 }
121
122 choices.push_back(choice_line);
123 }
124
125 public:
NumChoices() const126 int NumChoices() const
127 {
128 return (int)choices.size();
129 }
130
NotifyResult(int result)131 void NotifyResult(int result)
132 {
133 trigger->menu_result = result;
134 }
135
Drawer()136 void Drawer()
137 {
138 style->DrawBackground();
139
140 HUD_Reset();
141
142 HUD_SetAlignment(0, -1);
143 HUD_SetTextColor(T_WHITE); // TODO changeable
144
145 float total_h = HUD_StringHeight(title.c_str());
146 total_h += HUD_FontHeight() * (NumChoices() + 1);
147
148 float y = 100 - total_h / 2.0f;
149
150 HUD_DrawText(160, y, title.c_str());
151
152 y += HUD_StringHeight(title.c_str());
153 y += HUD_FontHeight();
154
155 HUD_SetTextColor(T_LTBLUE); // TODO changeable
156
157 for (int c = 0; c < NumChoices(); c++, y += HUD_FontHeight())
158 {
159 HUD_DrawText(160, y, choices[c].c_str());
160 }
161
162 HUD_SetAlignment();
163 HUD_SetTextColor();
164 }
165
CheckKey(int key)166 int CheckKey(int key)
167 {
168 if ('a' <= key && key <= 'z')
169 key = toupper(key);
170
171 if (key == 'Q' || key == 'X') // cancel
172 return 0;
173
174 if ('1' <= key && key <= ('0' + NumChoices()))
175 return key - '0';
176
177 if (NumChoices() < 2 &&
178 (key == KEYD_SPACE || key == KEYD_ENTER || key == 'Y'))
179 return 1;
180
181 return -1; /* invalid */
182 }
183 };
184
185 // RTS menu active ?
186 bool rts_menuactive = false;
187 static rts_menu_c *rts_curr_menu = NULL;
188
189 // Current RTS file or lump being parsed.
190 static byte *rad_memfile;
191 static byte *rad_memfile_end;
192 static byte *rad_memptr;
193 static int rad_memfile_size;
194
195
RAD_FindScriptByName(const char * map_name,const char * name)196 rad_script_t * RAD_FindScriptByName(const char *map_name, const char *name)
197 {
198 rad_script_t *scr;
199
200 for (scr=r_scripts; scr; scr=scr->next)
201 {
202 if (scr->script_name == NULL)
203 continue;
204
205 if (strcmp(scr->mapid, map_name) != 0)
206 continue;
207
208 if (DDF_CompareName(scr->script_name, name) == 0)
209 return scr;
210 }
211
212 I_Error("RTS: No such script `%s' on map %s.\n", name, map_name);
213 return NULL;
214 }
215
216
RAD_FindTriggerByName(const char * name)217 rad_trigger_t * RAD_FindTriggerByName(const char *name)
218 {
219 rad_trigger_t *trig;
220
221 for (trig=active_triggers; trig; trig=trig->next)
222 {
223 if (trig->info->script_name == NULL)
224 continue;
225
226 if (DDF_CompareName(trig->info->script_name, name) == 0)
227 return trig;
228 }
229
230 I_Warning("RTS: No such trigger `%s'.\n", name);
231 return NULL;
232 }
233
234
RAD_FindTriggerByScript(const rad_script_t * scr)235 rad_trigger_t * RAD_FindTriggerByScript(const rad_script_t *scr)
236 {
237 rad_trigger_t *trig;
238
239 for (trig=active_triggers; trig; trig=trig->next)
240 {
241 if (trig->info == scr)
242 return trig;
243 }
244
245 return NULL; // no worries if none.
246 }
247
248
RAD_FindStateByLabel(rad_script_t * scr,char * label)249 rts_state_t * RAD_FindStateByLabel(rad_script_t *scr, char *label)
250 {
251 rts_state_t *st;
252
253 for (st=scr->first_state; st; st=st->next)
254 {
255 if (st->label == NULL)
256 continue;
257
258 if (DDF_CompareName(st->label, label) == 0)
259 return st;
260 }
261
262 // NOTE: no error message, unlike the other Find funcs
263 return NULL;
264 }
265
266 //
267 // Looks for all current triggers with the given tag number, and
268 // either enables them or disables them (based on `disable').
269 // Actor can be NULL.
270 //
RAD_EnableByTag(mobj_t * actor,int tag,bool disable)271 void RAD_EnableByTag(mobj_t *actor, int tag, bool disable)
272 {
273 rad_trigger_t *trig;
274
275 if (tag <= 0)
276 I_Error("INTERNAL ERROR: RAD_EnableByTag: bad tag %d\n", tag);
277
278 for (trig=active_triggers; trig; trig=trig->next)
279 {
280 if (trig->info->tag == tag)
281 break;
282 }
283
284 // were there any ?
285 if (! trig)
286 return;
287
288 for (; trig; trig=trig->tag_next)
289 {
290 if (disable)
291 trig->disabled = true;
292 else
293 trig->disabled = false;
294 }
295 }
296
RAD_WithinRadius(mobj_t * mo,rad_script_t * r)297 bool RAD_WithinRadius(mobj_t * mo, rad_script_t * r)
298 {
299 if (r->rad_x >= 0 && fabs(r->x - mo->x) > r->rad_x + mo->radius)
300 return false;
301
302 if (r->rad_y >= 0 && fabs(r->y - mo->y) > r->rad_y + mo->radius)
303 return false;
304
305 if (r->rad_z >= 0 && fabs(r->z - MO_MIDZ(mo)) > r->rad_z + mo->height/2)
306 {
307 return false;
308 }
309
310 return true;
311 }
312
313
RAD_AlivePlayers(void)314 static int RAD_AlivePlayers(void)
315 {
316 int result = 0;
317
318 for (int pnum = 0; pnum < MAXPLAYERS; pnum++)
319 {
320 player_t *p = players[pnum];
321
322 if (p && p->playerstate != PST_DEAD)
323 result |= (1 << pnum);
324 }
325
326 return result;
327 }
328
RAD_AllPlayersInRadius(rad_script_t * r,int mask)329 static int RAD_AllPlayersInRadius(rad_script_t * r, int mask)
330 {
331 int result = 0;
332
333 for (int pnum = 0; pnum < MAXPLAYERS; pnum++)
334 {
335 player_t *p = players[pnum];
336
337 if (p && (mask & (1 << pnum)) && RAD_WithinRadius(p->mo, r))
338 result |= (1 << pnum);
339 }
340
341 return result;
342 }
343
RAD_AllPlayersUsing(int mask)344 static int RAD_AllPlayersUsing(int mask)
345 {
346 int result = 0;
347
348 for (int pnum = 0; pnum < MAXPLAYERS; pnum++)
349 {
350 player_t *p = players[pnum];
351
352 if (p && p->usedown)
353 result |= (1 << pnum);
354 }
355
356 return result & mask;
357 }
358
RAD_AllPlayersCheckCond(rad_script_t * r,int mask)359 static int RAD_AllPlayersCheckCond(rad_script_t * r, int mask)
360 {
361 int result = 0;
362
363 for (int pnum = 0; pnum < MAXPLAYERS; pnum++)
364 {
365 player_t *p = players[pnum];
366
367 if (p && (mask & (1 << pnum)) && G_CheckConditions(p->mo, r->cond_trig))
368 result |= (1 << pnum);
369 }
370
371 return result;
372 }
373
374
RAD_CheckBossTrig(rad_trigger_t * trig,s_ondeath_t * cond)375 static bool RAD_CheckBossTrig(rad_trigger_t *trig, s_ondeath_t *cond)
376 {
377 mobj_t *mo;
378
379 int count = 0;
380
381 // lookup thing type if we haven't already done so
382 if (! cond->cached_info)
383 {
384 if (cond->thing_name)
385 cond->cached_info = mobjtypes.Lookup(cond->thing_name);
386 else
387 {
388 cond->cached_info = mobjtypes.Lookup(cond->thing_type);
389
390 if (cond->cached_info == NULL)
391 I_Error("RTS ONDEATH: Unknown thing type %d.\n",
392 cond->thing_type);
393 }
394 }
395
396 // scan the remaining mobjs to see if all bosses are dead
397 for (mo=mobjlisthead; mo != NULL; mo=mo->next)
398 {
399 if (mo->info == cond->cached_info && mo->health > 0)
400 {
401 count++;
402
403 if (count > cond->threshhold)
404 return false;
405 }
406 }
407
408 return true;
409 }
410
411
RAD_CheckHeightTrig(rad_trigger_t * trig,s_onheight_t * cond)412 static bool RAD_CheckHeightTrig(rad_trigger_t *trig,
413 s_onheight_t *cond)
414 {
415 float h;
416
417 // lookup sector if we haven't already done so
418 if (! cond->cached_sector)
419 {
420 if (cond->sec_num >= 0)
421 {
422 if (cond->sec_num >= numsectors)
423 I_Error("RTS ONHEIGHT: no such sector %d.\n", cond->sec_num);
424
425 cond->cached_sector = & sectors[cond->sec_num];
426 }
427 else
428 {
429 cond->cached_sector = R_PointInSubsector(trig->info->x,
430 trig->info->y)->sector;
431 }
432 }
433
434 if (cond->is_ceil)
435 h = cond->cached_sector->c_h;
436 else
437 h = cond->cached_sector->f_h;
438
439 return (cond->z1 <= h && h <= cond->z2);
440 }
441
RAD_CheckReachedTrigger(mobj_t * thing)442 bool RAD_CheckReachedTrigger(mobj_t * thing)
443 {
444 rad_script_t * scr = (rad_script_t *) thing->path_trigger;
445 rad_trigger_t * trig;
446
447 rts_path_t *path;
448 int choice;
449
450 if (! RAD_WithinRadius(thing, scr))
451 return false;
452
453 // Thing has reached this path node. Update so it starts following
454 // the next node. Handle any PATH_EVENT too. Enable the associated
455 // trigger (could be none if there were no states).
456
457 trig = RAD_FindTriggerByScript(scr);
458
459 if (trig)
460 trig->disabled = false;
461
462 if (scr->path_event_label)
463 {
464 statenum_t state = P_MobjFindLabel(thing, scr->path_event_label);
465
466 if (state)
467 P_SetMobjStateDeferred(thing, state + scr->path_event_offset, 0);
468 }
469
470 if (scr->next_path_total == 0)
471 {
472 thing->path_trigger = NULL;
473 return true;
474 }
475 else if (scr->next_path_total == 1)
476 choice = 0;
477 else
478 choice = P_Random() % scr->next_path_total;
479
480 path = scr->next_in_path;
481 SYS_ASSERT(path);
482
483 for (; choice > 0; choice--)
484 {
485 path = path->next;
486 SYS_ASSERT(path);
487 }
488
489 if (! path->cached_scr)
490 path->cached_scr = RAD_FindScriptByName(scr->mapid, path->name);
491
492 SYS_ASSERT(path->cached_scr);
493
494 thing->path_trigger = path->cached_scr;
495 return true;
496 }
497
DoRemoveTrigger(rad_trigger_t * trig)498 static void DoRemoveTrigger(rad_trigger_t *trig)
499 {
500 // handle tag linkage
501 if (trig->tag_next)
502 trig->tag_next->tag_prev = trig->tag_prev;
503
504 if (trig->tag_prev)
505 trig->tag_prev->tag_next = trig->tag_next;
506
507 // unlink and free it
508 if (trig->next)
509 trig->next->prev = trig->prev;
510
511 if (trig->prev)
512 trig->prev->next = trig->next;
513 else
514 active_triggers = trig->next;
515
516 S_StopFX(&trig->sfx_origin);
517
518 // FIXME: delete !
519 Z_Free(trig);
520 }
521
522 //
523 // Radius Trigger Event handler.
524 //
RAD_RunTriggers(void)525 void RAD_RunTriggers(void)
526 {
527 rad_trigger_t *trig, *next;
528
529 // Start looking through the trigger list.
530 for (trig=active_triggers; trig; trig=next)
531 {
532 next = trig->next;
533
534 // stop running all triggers when an RTS menu becomes active
535 if (rts_menuactive)
536 break;
537
538 // Don't process, if disabled
539 if (trig->disabled)
540 continue;
541
542 // Handle repeat delay (from TAGGED_REPEATABLE). This must be
543 // done *before* all the condition checks, and that's what makes
544 // it different from `wait_tics'.
545 //
546 if (trig->repeat_delay > 0)
547 {
548 trig->repeat_delay--;
549 continue;
550 }
551
552 // Independent, means you don't have to stay within the trigger
553 // radius for it to operate, It will operate on it's own.
554
555 if (! (trig->info->tagged_independent && trig->activated))
556 {
557 int mask = RAD_AlivePlayers();
558
559 // Immediate triggers are just that. Immediate.
560 // Not within range so skip it.
561 //
562 if (!trig->info->tagged_immediate)
563 {
564 mask = RAD_AllPlayersInRadius(trig->info, mask);
565 if (mask == 0)
566 continue;
567 }
568
569 // Check for use key trigger.
570 if (trig->info->tagged_use)
571 {
572 mask = RAD_AllPlayersUsing(mask);
573 if (mask == 0)
574 continue;
575 }
576
577 // height check...
578 if (trig->info->height_trig)
579 {
580 s_onheight_t *cur;
581
582 for (cur=trig->info->height_trig; cur; cur=cur->next)
583 if (! RAD_CheckHeightTrig(trig, cur))
584 break;
585
586 // if they all succeeded, then cur will be NULL...
587 if (cur)
588 continue;
589 }
590
591 // ondeath check...
592 if (trig->info->boss_trig)
593 {
594 s_ondeath_t *cur;
595
596 for (cur=trig->info->boss_trig; cur; cur=cur->next)
597 if (! RAD_CheckBossTrig(trig, cur))
598 break;
599
600 // if they all succeeded, then cur will be NULL...
601 if (cur)
602 continue;
603 }
604
605 // condition check...
606 if (trig->info->cond_trig)
607 {
608 mask = RAD_AllPlayersCheckCond(trig->info, mask);
609 if (mask == 0)
610 continue;
611 }
612
613 trig->activated = true;
614 trig->acti_players = mask;
615 }
616
617 // If we are waiting, decrement count and skip it.
618 // Note that we must do this *after* all the condition checks.
619 //
620 if (trig->wait_tics > 0)
621 {
622 trig->wait_tics--;
623 continue;
624 }
625
626 // Waiting until monsters are dead?
627 while (trig->wait_tics == 0 && trig->wud_count <= 0)
628 {
629 // Execute current command
630 rts_state_t *state = trig->state;
631 SYS_ASSERT(state);
632
633 // move to next state. We do this NOW since the action itself
634 // may want to change the trigger's state (to support GOTO type
635 // actions and other possibilities).
636 //
637 trig->state = trig->state->next;
638
639 (*state->action)(trig, state->param);
640
641 if (! trig->state)
642 break;
643
644 trig->wait_tics += trig->state->tics;
645
646 if (trig->disabled || rts_menuactive)
647 break;
648 }
649
650 if (trig->state)
651 continue;
652
653 // we've reached the end of the states. Delete the trigger unless
654 // it is Tagged_Repeatable and has some more repeats left.
655 //
656 if (trig->info->repeat_count != REPEAT_FOREVER)
657 trig->repeats_left--;
658
659 if (trig->repeats_left > 0)
660 {
661 trig->state = trig->info->first_state;
662 trig->wait_tics = trig->state->tics;
663 trig->repeat_delay = trig->info->repeat_delay;
664 continue;
665 }
666
667 DoRemoveTrigger(trig);
668 }
669 }
670
RAD_MonsterIsDead(mobj_t * mo)671 void RAD_MonsterIsDead(mobj_t *mo)
672 {
673 if (mo->hyperflags & HF_WAIT_UNTIL_DEAD)
674 {
675 mo->hyperflags &= ~HF_WAIT_UNTIL_DEAD;
676
677 rad_trigger_t *trig;
678
679 for (trig = active_triggers ; trig ; trig = trig->next)
680 {
681 if (trig->wud_tag == mo->tag)
682 {
683 trig->wud_count--;
684 }
685 }
686 }
687 }
688
689
690 //
691 // Called from RAD_SpawnTriggers to set the tag_next & tag_prev fields
692 // of each rad_trigger_t, keeping all triggers with the same tag in a
693 // linked list for faster handling.
694 //
RAD_GroupTriggerTags(rad_trigger_t * trig)695 void RAD_GroupTriggerTags(rad_trigger_t *trig)
696 {
697 rad_trigger_t *cur;
698
699 trig->tag_next = trig->tag_prev = NULL;
700
701 // find first trigger with the same tag #
702 for (cur=active_triggers; cur; cur=cur->next)
703 {
704 if (cur == trig)
705 continue;
706
707 if (cur->info->tag == trig->info->tag)
708 break;
709 }
710
711 if (! cur)
712 return;
713
714 // link it in
715
716 trig->tag_next = cur;
717 trig->tag_prev = cur->tag_prev;
718
719 if (cur->tag_prev)
720 cur->tag_prev->tag_next = trig;
721
722 cur->tag_prev = trig;
723 }
724
725
RAD_SpawnTriggers(const char * map_name)726 void RAD_SpawnTriggers(const char *map_name)
727 {
728 rad_script_t *scr;
729 rad_trigger_t *trig;
730
731 #ifdef DEVELOPERS
732 if (active_triggers)
733 I_Error("RAD_SpawnTriggers without RAD_ClearTriggers\n");
734 #endif
735
736 for (scr=r_scripts; scr; scr=scr->next)
737 {
738 // This is from a different map!
739 if (strcmp(map_name, scr->mapid) != 0 && strcmp(scr->mapid, "ALL") != 0)
740 continue;
741
742 // -AJA- 1999/09/25: Added skill checks.
743 if (! G_CheckWhenAppear(scr->appear))
744 continue;
745
746 // -AJA- 2000/02/03: Added player num checks.
747 if (numplayers < scr->min_players || numplayers > scr->max_players)
748 continue;
749
750 // ignore empty scripts (e.g. path nodes)
751 if (! scr->first_state)
752 continue;
753
754 // OK, spawn new dynamic trigger
755 trig = Z_New(rad_trigger_t, 1);
756
757 Z_Clear(trig, rad_trigger_t, 1);
758
759 trig->info = scr;
760 trig->disabled = scr->tagged_disabled;
761 trig->repeats_left = (scr->repeat_count < 0 ||
762 scr->repeat_count == REPEAT_FOREVER) ? 1 : scr->repeat_count;
763 trig->repeat_delay = 0;
764 trig->tip_slot = 0;
765 trig->wud_tag = trig->wud_count = 0;
766
767 RAD_GroupTriggerTags(trig);
768
769 // initialise state machine
770 trig->state = scr->first_state;
771 trig->wait_tics = scr->first_state->tics;
772
773 // link it in
774 trig->next = active_triggers;
775 trig->prev = NULL;
776
777 if (active_triggers)
778 active_triggers->prev = trig;
779
780 active_triggers = trig;
781 }
782 }
783
784
RAD_ClearCachedInfo(void)785 static void RAD_ClearCachedInfo(void)
786 {
787 rad_script_t *scr;
788 s_ondeath_t *d_cur;
789 s_onheight_t *h_cur;
790
791 for (scr=r_scripts; scr; scr=scr->next)
792 {
793 // clear ONDEATH cached info
794 for (d_cur=scr->boss_trig; d_cur; d_cur=d_cur->next)
795 {
796 d_cur->cached_info = NULL;
797 }
798
799 // clear ONHEIGHT cached info
800 for (h_cur=scr->height_trig; h_cur; h_cur=h_cur->next)
801 {
802 h_cur->cached_sector = NULL;
803 }
804 }
805 }
806
807
RAD_ClearTriggers(void)808 void RAD_ClearTriggers(void)
809 {
810 // remove all dynamic triggers
811 while (active_triggers)
812 {
813 rad_trigger_t *trig = active_triggers;
814 active_triggers = trig->next;
815
816 Z_Free(trig);
817 }
818
819 RAD_ClearCachedInfo();
820 RAD_ResetTips();
821 }
822
823 //
824 // Loads the script file into memory for parsing.
825 //
826 // -AJA- 2000/01/04: written, based on DDF_MainCacheFile
827 // -AJA- FIXME: merge them both into a single utility routine.
828 // (BETTER: a single utility parsing module).
829 //
RAD_MainCacheFile(const char * filename)830 static void RAD_MainCacheFile(const char *filename)
831 {
832 FILE *file;
833
834 // open the file
835 file = fopen(filename, "rb");
836
837 if (file == NULL)
838 I_Error("\nRAD_MainReadFile: Unable to open: '%s'", filename);
839
840 // get to the end of the file
841 fseek(file, 0, SEEK_END);
842
843 // get the size
844 rad_memfile_size = ftell(file);
845
846 // reset to beginning
847 fseek(file, 0, SEEK_SET);
848
849 // malloc the size
850 rad_memfile = Z_New(byte, rad_memfile_size + 1);
851 rad_memfile_end = &rad_memfile[rad_memfile_size];
852
853 // read the goodies
854 fread(rad_memfile, 1, rad_memfile_size, file);
855
856 // null Terminated string.
857 rad_memfile[rad_memfile_size] = 0;
858
859 // close the file
860 fclose(file);
861 }
862
ReadScriptLine(char * buf,int max)863 static int ReadScriptLine(char *buf, int max)
864 {
865 int real_num = 1;
866
867 while (rad_memptr < rad_memfile_end)
868 {
869 if (rad_memptr[0] == '\n')
870 {
871 // skip trailing EOLN
872 rad_memptr++;
873 break;
874 }
875
876 // line concatenation
877 if (rad_memptr+2 < rad_memfile_end && rad_memptr[0] == '\\')
878 {
879 if (rad_memptr[1] == '\n' ||
880 (rad_memptr[1] == '\r' && rad_memptr[2] == '\n'))
881 {
882 real_num++;
883 rad_memptr += (rad_memptr[1] == '\n') ? 2 : 3;
884 continue;
885 }
886 }
887
888 // ignore carriage returns
889 if (rad_memptr[0] == '\r')
890 {
891 rad_memptr++;
892 continue;
893 }
894
895 if (max <= 2)
896 I_Error("RTS script: line %d too long !!\n", rad_cur_linenum);
897
898 *buf++ = *rad_memptr++; max--;
899 }
900
901 *buf = 0;
902
903 return real_num;
904 }
905
906
907 //
908 // -ACB- 1998/07/10 Renamed function and used I_Print for functions,
909 // Version displayed at all times.
910 //
RAD_ParseScript(void)911 static void RAD_ParseScript(void)
912 {
913 RAD_ParserBegin();
914
915 rad_cur_linenum = 1;
916 rad_memptr = rad_memfile;
917
918 char linebuf[MAXRTSLINE];
919
920 while (rad_memptr < rad_memfile_end)
921 {
922 int real_num = ReadScriptLine(linebuf, MAXRTSLINE);
923
924 #if (DEBUG_RTS)
925 L_WriteDebug("RTS LINE: '%s'\n", linebuf);
926 #endif
927
928 RAD_ParseLine(linebuf);
929
930 rad_cur_linenum += real_num;
931 }
932
933 RAD_ParserDone();
934 }
935
936
RAD_LoadFile(const char * name)937 void RAD_LoadFile(const char *name)
938 {
939 SYS_ASSERT(name);
940
941 L_WriteDebug("RTS: Loading File %s\n", name);
942
943 rad_cur_filename = name;
944
945 RAD_MainCacheFile(name);
946
947 // OK we have the file in memory. Parse it to death :-)
948 RAD_ParseScript();
949
950 Z_Free(rad_memfile);
951 }
952
953
RAD_ReadScript(void * data,int size)954 bool RAD_ReadScript(void *data, int size)
955 {
956 if (data == NULL)
957 {
958 std::string fn = M_ComposeFileName(ddf_dir.c_str(), "rscript.rts");
959
960 if (! epi::FS_Access(fn.c_str(), epi::file_c::ACCESS_READ))
961 return false;
962
963 RAD_LoadFile(fn.c_str());
964 return true;
965 }
966
967 L_WriteDebug("RTS: Loading LUMP (size=%d)\n", size);
968
969 rad_cur_filename = "RSCRIPT LUMP";
970
971 rad_memfile_size = size;
972 rad_memfile = Z_New(byte, size + 1);
973 rad_memfile_end = &rad_memfile[size];
974
975 Z_MoveData(rad_memfile, (byte *)data, byte, size);
976
977 // Null Terminated string.
978 rad_memfile[size] = 0;
979
980 // OK we have the file in memory. Parse it to death :-)
981 RAD_ParseScript();
982
983 Z_Free(rad_memfile);
984
985 return true;
986 }
987
988
RAD_Init(void)989 void RAD_Init(void)
990 {
991 RAD_InitTips();
992 }
993
RAD_StartMenu(rad_trigger_t * R,s_show_menu_t * menu)994 void RAD_StartMenu(rad_trigger_t *R, s_show_menu_t *menu)
995 {
996 SYS_ASSERT(! rts_menuactive);
997
998 // find the right style
999 styledef_c *def = NULL;
1000
1001 if (R->menu_style_name)
1002 def = styledefs.Lookup(R->menu_style_name);
1003
1004 if (! def) def = styledefs.Lookup("RTS MENU");
1005 if (! def) def = styledefs.Lookup("MENU");
1006 if (! def) def = default_style;
1007
1008 rts_curr_menu = new rts_menu_c(menu, R, hu_styles.Lookup(def));
1009 rts_menuactive = true;
1010 }
1011
RAD_FinishMenu(int result)1012 void RAD_FinishMenu(int result)
1013 {
1014 if (! rts_menuactive)
1015 return;
1016
1017 SYS_ASSERT(rts_curr_menu);
1018
1019 // zero is cancelled, otherwise result is 1..N
1020 if (result < 0 || result > MAX(1, rts_curr_menu->NumChoices()))
1021 return;
1022
1023 rts_curr_menu->NotifyResult(result);
1024
1025 delete rts_curr_menu;
1026
1027 rts_curr_menu = NULL;
1028 rts_menuactive = false;
1029 }
1030
RAD_MenuDrawer(void)1031 static void RAD_MenuDrawer(void)
1032 {
1033 SYS_ASSERT(rts_curr_menu);
1034
1035 rts_curr_menu->Drawer();
1036 }
1037
1038
RAD_Drawer(void)1039 void RAD_Drawer(void)
1040 {
1041 if (! automapactive)
1042 RAD_DisplayTips();
1043
1044 if (rts_menuactive)
1045 RAD_MenuDrawer();
1046 }
1047
1048
RAD_Responder(event_t * ev)1049 bool RAD_Responder(event_t * ev)
1050 {
1051 if (ev->type != ev_keydown)
1052 return false;
1053
1054 if (! rts_menuactive)
1055 return false;
1056
1057 SYS_ASSERT(rts_curr_menu);
1058
1059 int check = rts_curr_menu->CheckKey(ev->value.key.sym);
1060
1061 if (check >= 0)
1062 {
1063 RAD_FinishMenu(check);
1064 return true;
1065 }
1066
1067 return false;
1068 }
1069
1070 //--- editor settings ---
1071 // vi:ts=4:sw=4:noexpandtab
1072