NXEngine v1.0.0.6
[NXEngine.git] / tsc.cpp
blob5f80d2e5045644f7c68f3002f33e0de46855cd34
2 // TSC script parser & executor
4 #include "nx.h"
5 #include "common/DBuffer.h"
6 #include "vararray.h"
7 #include "tsc.h"
8 #include "tsc.fdh"
10 #define TRACE_SCRIPT
12 // which textbox options are enabled by the "<TUR" script command.
13 #define TUR_PARAMS (TB_LINE_AT_ONCE | TB_VARIABLE_WIDTH_CHARS | TB_CURSOR_NEVER_SHOWN)
15 static ScriptInstance curscript;
16 static int lastammoinc = 0;
18 struct ScriptPage
20 // a variable-length array of pointers to compiled script code
21 // for each script in the page; their indexes in this array
22 // correspond to their script numbers.
23 VarArray<DBuffer *> scripts;
25 void Clear()
27 for(int i=0;i<scripts.nitems;i++)
28 delete scripts.get(i); // it's safe to delete NULL, so no check here
30 scripts.MakeEmpty();
34 ScriptPage script_pages[NUM_SCRIPT_PAGES];
37 void c------------------------------() {}
40 struct TSCCommandTable
42 const char *mnemonic;
43 int nparams;
45 #include "tsc_cmdtbl.cpp"
47 unsigned char codealphabet[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123+-" };
48 unsigned char letter_to_code[256];
49 unsigned char mnemonic_lookup[32*32*32];
52 static void GenLTC(void)
54 int i;
55 uchar ch;
57 memset(letter_to_code, 0xff, sizeof(letter_to_code));
58 for(i=0;;i++)
60 if (!(ch = codealphabet[i])) break;
61 letter_to_code[ch] = i;
64 memset(mnemonic_lookup, 0xff, sizeof(mnemonic_lookup));
65 for(i=0;i<OP_COUNT;i++)
67 mnemonic_lookup[MnemonicToIndex(cmd_table[i].mnemonic)] = i;
71 static int MnemonicToIndex(const char *str)
73 int l1, l2, l3;
75 l1 = letter_to_code[(uint8_t)str[0]];
76 l2 = letter_to_code[(uint8_t)str[1]];
77 l3 = letter_to_code[(uint8_t)str[2]];
78 if (l1==0xff || l2==0xff || l3==0xff) return -1;
80 return (l1 << 10) | (l2 << 5) | l3;
83 static int MnemonicToOpcode(char *str)
85 int index = MnemonicToIndex(str);
86 if (index != -1)
88 index = mnemonic_lookup[index];
89 if (index != 0xff) return index;
92 staterr("MnemonicToOpcode: No such command '%s'", str);
93 return -1;
97 void c------------------------------() {}
100 bool tsc_init(void)
102 char fname[MAXPATHLEN];
104 GenLTC();
105 curscript.running = false;
106 for(int i=0;i<NUM_SCRIPT_PAGES;i++)
108 // load the "common" TSC scripts available to all maps
109 sprintf(fname, "%s/Head.tsc", data_dir);
110 if (tsc_load(fname, SP_HEAD)) return 1;
112 // load the inventory screen scripts
113 sprintf(fname, "%s/ArmsItem.tsc", data_dir);
114 if (tsc_load(fname, SP_ARMSITEM)) return 1;
116 // load stage select/teleporter scripts
117 sprintf(fname, "%s/StageSelect.tsc", data_dir);
118 if (tsc_load(fname, SP_STAGESELECT)) return 1;
120 return 0;
123 void tsc_close(void)
125 // free all loaded scripts
126 for(int i=0;i<NUM_SCRIPT_PAGES;i++)
127 script_pages[i].Clear();
130 // load a tsc file and return the highest script # in the file
131 bool tsc_load(const char *fname, int pageno)
133 ScriptPage *page = &script_pages[pageno];
134 int fsize;
135 char *buf;
136 bool result;
138 stat("tsc_load: loading '%s' to page %d", fname, pageno);
139 if (curscript.running && curscript.pageno == pageno)
140 StopScript(&curscript);
142 page->Clear();
144 // load the raw script text
145 buf = tsc_decrypt(fname, &fsize);
146 if (!buf)
148 staterr("tsc_load: failed to load file: '%s'", fname);
149 return 1;
152 // now "compile" all the scripts in the TSC
153 //int top_script = CompileScripts(buf, fsize, base);
154 result = tsc_compile(buf, fsize, pageno);
155 free(buf);
157 return result;
161 char *tsc_decrypt(const char *fname, int *fsize_out)
163 FILE *fp;
164 int fsize, i;
166 fp = fileopen(fname, "rb");
167 if (!fp)
169 staterr("tsc_decrypt: no such file: '%s'!", fname);
170 return NULL;
173 fseek(fp, 0, SEEK_END);
174 fsize = ftell(fp);
175 fseek(fp, 0, SEEK_SET);
177 // load file
178 char *buf = (char *)malloc(fsize+1);
179 fread(buf, fsize, 1, fp);
180 buf[fsize] = 0;
181 fclose(fp);
183 // get decryption key, which is actually part of the text
184 int keypos = (fsize / 2);
185 int key = buf[keypos];
187 // everything EXCEPT the key is encrypted
188 for(i=0;i<keypos;i++) { buf[i] = (buf[i] - key); }
189 for(i++;i<fsize;i++) { buf[i] = (buf[i] - key); }
191 if (fsize_out) *fsize_out = fsize;
192 return buf;
196 void c------------------------------() {}
199 // compile a tsc file--a set of scripts in raw text format--into 'bytecode',
200 // and place the finished scripts into the given page.
201 bool tsc_compile(const char *buf, int bufsize, int pageno)
203 ScriptPage *page = &script_pages[pageno];
204 const char *buf_end = (buf + (bufsize - 1));
205 DBuffer *script = NULL;
206 char cmdbuf[4] = { 0 };
208 //stat("<> tsc_compile bufsize = %d pageno = %d", bufsize, pageno);
210 while(buf <= buf_end)
212 char ch = *(buf++);
214 if (ch == '#')
215 { // start of a scriptzz
216 if (script)
218 script->Append8(OP_END);
219 script = NULL;
222 int scriptno = ReadNumber(&buf, buf_end);
223 if (scriptno >= 10000 || scriptno < 0)
225 staterr("tsc_compile: invalid script number: %d", scriptno);
226 return 1;
229 // skip the CR after the script #
230 while(buf < buf_end)
232 if (*buf != '\r' && *buf != '\n') break;
233 buf++;
236 //stat("Parsing script #%04d", scriptno);
237 if (page->scripts.get(scriptno))
239 staterr("tsc_compile WARNING: duplicate script #%04d; ignoring", scriptno);
240 // because script is left null, we'll ignore everything until we see another #
242 else
244 script = new DBuffer;
245 page->scripts.put(scriptno, script);
248 else if (ch == '<' && script)
250 // read the command type
251 cmdbuf[0] = nextchar(&buf, buf_end);
252 cmdbuf[1] = nextchar(&buf, buf_end);
253 cmdbuf[2] = nextchar(&buf, buf_end);
255 int cmd = MnemonicToOpcode(cmdbuf);
256 if (cmd == -1) return 1;
258 //stat("Command '%s', parameters %d", cmdbuf, cmd_table[cmd].nparams);
259 script->Append8(cmd);
261 // read all parameters expected by that command
262 int nparams = cmd_table[cmd].nparams;
263 for(int i=0;i<nparams;i++)
265 int val = ReadNumber(&buf, buf_end);
267 script->Append8(val >> 8);
268 script->Append8(val & 0xff);
270 // colon between params
271 if (i < (nparams - 1))
272 buf++;
275 else if (script)
276 { // text for message boxes
277 buf--;
278 script->Append8(OP_TEXT);
279 ReadText(script, &buf, buf_end);
284 if (script)
285 script->Append8(OP_END);
287 return 0;
290 static char nextchar(const char **buf, const char *buf_end)
292 if (*buf <= buf_end)
293 return *(*buf)++;
295 return 0;
298 static int ReadNumber(const char **buf, const char *buf_end)
300 static char num[5] = { 0 };
301 int i = 0;
303 while(i < 4)
305 num[i] = nextchar(buf, buf_end);
306 if (!isdigit(num[i]))
308 (*buf)--;
309 break;
312 i++;
315 return atoi(num);
318 static void ReadText(DBuffer *script, const char **buf, const char *buf_end)
320 while(*buf <= buf_end)
322 char ch = nextchar(buf, buf_end);
323 if (ch == '<' || ch == '#')
325 (*buf)--;
326 break;
329 if (ch != 10)
330 script->Append8(ch);
333 script->Append8('\0');
337 void c------------------------------() {}
340 void RunScripts(void)
342 if (curscript.running)
343 ExecScript(&curscript);
346 void StopScripts(void)
348 if (curscript.running)
349 StopScript(&curscript);
352 int GetCurrentScript(void)
354 if (curscript.running)
355 return curscript.scriptno;
357 return -1;
360 ScriptInstance *GetCurrentScriptInstance()
362 if (curscript.running)
363 return &curscript;
365 return NULL;
369 void c------------------------------() {}
372 // returns a pointer to the executable data/bytecode of the given script.
373 // handles looking on head, etc.
374 const uint8_t *FindScriptData(int scriptno, int pageno, int *page_out)
376 ScriptPage *page = &script_pages[pageno];
377 DBuffer *script;
379 script = page->scripts.get(scriptno);
380 if (!script)
382 if (pageno != SP_HEAD)
383 { // try to find the script in head.tsc
384 return FindScriptData(scriptno, SP_HEAD, page_out);
386 else
388 return NULL;
392 if (page_out) *page_out = pageno;
393 return script->Data();
397 ScriptInstance *StartScript(int scriptno, int pageno)
399 const uint8_t *program;
400 int found_pageno;
402 program = FindScriptData(scriptno, pageno, &found_pageno);
403 if (!program)
405 staterr("StartScript: no script at position #%04d page %d!", scriptno, pageno);
406 return NULL;
409 // don't start regular map scripts (e.g. hvtrigger) if player is dead
410 if (player->dead && found_pageno != SP_HEAD)
412 stat("Not starting script %d; player is dead", scriptno);
413 return NULL;
416 // set the script
417 memset(&curscript, 0, sizeof(ScriptInstance));
419 curscript.program = program;
420 curscript.scriptno = scriptno;
421 curscript.pageno = found_pageno;
423 curscript.ynj_jump = -1;
424 curscript.running = true;
426 textbox.ResetState();
427 stat(" - Started script %04d", scriptno);
429 RunScripts();
430 return &curscript;
433 void StopScript(ScriptInstance *s)
435 if (!s->running)
436 return;
438 s->running = false;
439 stat(" - Stopped script %04d", s->scriptno);
441 // TRA is really supposed to be a jump, not a script restart--
442 // in that in maintains KEY/PRI across the stage transition.
443 // Emulate this by leaving the script state alone until the
444 // on-entry script starts.
445 player->inputs_locked = false;
446 game.frozen = false;
447 player->lookaway = false;
449 textbox.ResetState();
453 void c------------------------------() {}
456 bool JumpScript(int newscriptno, int pageno)
458 ScriptInstance *s = &curscript;
460 if (pageno == -1)
461 pageno = s->pageno;
463 stat("JumpScript: moving to script #%04d page %d", newscriptno, pageno);
465 s->program = FindScriptData(newscriptno, pageno, &s->pageno);
466 s->scriptno = newscriptno;
467 s->ip = 0;
469 if (!s->program)
471 staterr("JumpScript: missing script #%04d! Script terminated.", newscriptno);
472 StopScript(s);
473 return 1;
476 s->delaytimer = 0;
477 s->waitforkey = false;
478 s->wait_standing = false;
480 // <EVE doesn't clear textbox mode or the face etc
481 if (textbox.IsVisible())
483 textbox.ClearText();
485 // see entrance to Sacred Grounds when you have the Nikumaru Counter
486 // to witness that EVE clears TUR.
487 textbox.SetFlags(TB_LINE_AT_ONCE, false);
488 textbox.SetFlags(TB_VARIABLE_WIDTH_CHARS, false);
489 textbox.SetFlags(TB_CURSOR_NEVER_SHOWN, false);
492 return 0;
495 void ExecScript(ScriptInstance *s)
497 char debugbuffer[256];
498 int cmd;
499 int val, parm[6];
500 int i;
501 Object *o;
502 char *mnemonic;
503 char *str;
504 int cmdip;
506 #define JUMP_IF(cond) \
508 if (cond) \
510 if (JumpScript(parm[1])) return; \
514 // pause script while FAI/FAO still working
515 if (fade.getstate() == FS_FADING) return;
516 if (game.mode == GM_ISLAND) return;
518 // waiting for an answer from a Yes/No prompt?
519 if (s->ynj_jump != -1)
521 if (textbox.YesNoPrompt.ResultReady())
523 if (textbox.YesNoPrompt.GetResult() == NO)
524 JumpScript(s->ynj_jump);
526 textbox.YesNoPrompt.SetVisible(false);
527 s->ynj_jump = -1;
529 else
530 { // pause script until answer is receieved
531 return;
535 // pause script while text is still displaying
536 if (textbox.IsBusy()) return;
538 // pause while NOD is in effect
539 if (s->waitforkey)
541 if (s->nod_delay) // used to pause during <QUA without freezing textboxes in Hell
543 s->nod_delay--;
545 else
547 // if key was just pressed release nod.
548 // check them separately to allow holding X while
549 // tapping Z to keep text scrolling fast.
550 if ((inputs[JUMPKEY] && !s->lastjump) || \
551 (inputs[FIREKEY] && !s->lastfire))
553 // hide the fact that the key was just pushed
554 // so player doesn't jump/fire stupidly when dismissing textboxes
555 lastinputs[JUMPKEY] |= inputs[JUMPKEY];
556 lastinputs[FIREKEY] |= inputs[FIREKEY];
557 lastpinputs[JUMPKEY] |= inputs[JUMPKEY];
558 lastpinputs[FIREKEY] |= inputs[FIREKEY];
560 s->waitforkey = false;
561 textbox.ShowCursor(false);
564 s->lastjump = inputs[JUMPKEY];
565 s->lastfire = inputs[FIREKEY];
568 // if still on return
569 if (s->waitforkey) return;
572 // pause scripts while WAI is in effect.
573 // <WAI9999, used in inventory/stage-select screen, means forever.
574 if (s->delaytimer)
576 if (s->delaytimer == 9999)
578 UnlockInventoryInput();
580 else
582 s->delaytimer--;
585 return;
588 // pause while WAS (wait until standing) is in effect.
589 if (s->wait_standing)
591 if (!player->blockd) return;
592 s->wait_standing = false;
595 //stat("<> Entering script execution loop at ip = %d", s->ip);
597 // main execution loop
598 for(;;)
600 cmdip = s->ip++;
601 cmd = s->program[cmdip];
602 mnemonic = (char *)cmd_table[cmd].mnemonic;
604 if (cmd != OP_TEXT)
606 snprintf(debugbuffer, sizeof(debugbuffer), "%04x <%s ", cmd, mnemonic);
607 for(i=0;i<cmd_table[cmd].nparams;i++)
609 val = ((int)s->program[s->ip++]) << 8;
610 val |= s->program[s->ip++];
611 parm[i] = val;
612 snprintf(debugbuffer, sizeof(debugbuffer), "%s %04d", debugbuffer, val);
615 #ifdef TRACE_SCRIPT
616 else
618 char debugbuffer2[10000];
619 crtoslashn((char *)&s->program[s->ip], debugbuffer2);
620 snprintf(debugbuffer, sizeof(debugbuffer), "TEXT '%s'", debugbuffer2);
623 if (cmd == OP_TEXT && !textbox.IsVisible() && !strcmp(debugbuffer, "TEXT '\n'")) { }
624 else
626 stat("%04d:%d %s", s->scriptno, cmdip, debugbuffer);
628 #endif
630 switch(cmd)
632 case OP_END: StopScript(s); return;
634 case OP_FAI: fade.Start(FADE_IN, parm[0], SPR_FADE_DIAMOND); return;
635 case OP_FAO: fade.Start(FADE_OUT, parm[0], SPR_FADE_DIAMOND); return;
636 case OP_FLA: flashscreen.Start(); break;
638 case OP_SOU: sound(parm[0]); break;
639 case OP_CMU: music(parm[0]); break;
640 case OP_RMU: music(music_lastsong()); break;
641 case OP_FMU: org_fade(); break;
643 case OP_SSS: StartStreamSound(parm[0]); break;
644 case OP_SPS: StartPropSound(); break;
646 case OP_CSS: // these seem identical-- either one will
647 case OP_CPS: // in fact stop the other.
649 StopLoopSounds();
651 break;
653 // free menu selector in Inventory. It also undoes <PRI,
654 // as can be seen at the entrance to Sacred Ground.
655 case OP_FRE:
657 game.frozen = false;
658 player->inputs_locked = false;
659 UnlockInventoryInput();
661 break;
663 case OP_PRI: // freeze entire game (players + NPCs)
665 game.frozen = true;
666 player->inputs_locked = false;
667 statusbar.xpflashcount = 0; // looks odd if this happens after a long <PRI, even though it's technically correct
669 break;
671 case OP_KEY: // lock players input but NPC/objects still run
673 game.frozen = false;
674 player->inputs_locked = true;
676 break;
678 case OP_MOV:
679 player->x = (parm[0] * TILE_W) << CSF;
680 player->y = (parm[1] * TILE_H) << CSF;
681 player->xinertia = player->yinertia = 0;
682 player->lookaway = false;
683 break;
685 case OP_UNI:
686 player->movementmode = parm[0];
687 map_scroll_lock(parm[0]); // locks on anything other than 0
688 break;
690 case OP_MNA: // show map name (as used on entry)
691 map_show_map_name();
692 break;
694 case OP_MLP: // bring up Map System
695 game.setmode(GM_MAP_SYSTEM, game.mode);
696 break;
698 case OP_TRA:
700 bool waslocked = (player->inputs_locked || game.frozen);
702 stat("******* Executing <TRA to stage %d", parm[0]);
703 game.switchstage.mapno = parm[0];
704 game.switchstage.eventonentry = parm[1];
705 game.switchstage.playerx = parm[2];
706 game.switchstage.playery = parm[3];
707 StopScript(s);
709 if (game.switchstage.mapno != 0)
711 // KEY is maintained across TRA as if the TRA
712 // were a jump instead of a restart; but if the
713 // game is in PRI then it is downgraded to a KEY.
714 // See entrance to Yamashita Farm.
715 if (waslocked)
717 player->inputs_locked = true;
718 game.frozen = false;
722 return;
724 break;
726 case OP_AMPLUS: GetWeapon(parm[0], parm[1]); lastammoinc = parm[1]; break;
727 case OP_AMMINUS: LoseWeapon(parm[0]); break;
728 case OP_TAM: TradeWeapon(parm[0], parm[1], parm[2]); break;
729 case OP_AMJ: JUMP_IF(player->weapons[parm[0]].hasWeapon); break;
731 case OP_ZAM: // drop all weapons to level 1
733 for(int i=0;i<WPN_COUNT;i++)
735 player->weapons[i].xp = 0;
736 player->weapons[i].level = 0;
739 break;
741 case OP_EVE: JumpScript(parm[0]); break; // unconditional jump to event
743 case OP_FLPLUS: game.flags[parm[0]] = 1; break;
744 case OP_FLMINUS: game.flags[parm[0]] = 0; break;
745 case OP_FLJ: JUMP_IF(game.flags[parm[0]]); break;
747 case OP_ITPLUS: AddInventory(parm[0]); break;
748 case OP_ITMINUS: DelInventory(parm[0]); break;
749 case OP_ITJ: JUMP_IF((FindInventory(parm[0]) != -1)); break;
751 // the PSelectSprite is a hack so when the Mimiga Mask is taken
752 // it disappears immediately even though the game is in <PRI.
753 case OP_EQPLUS: player->equipmask |= parm[0]; PSelectSprite(); break;
754 case OP_EQMINUS: player->equipmask &= ~parm[0]; PSelectSprite(); break;
756 case OP_SKPLUS: game.skipflags[parm[0]] = 1; break;
757 case OP_SKMINUS: game.skipflags[parm[0]] = 0; break;
758 case OP_SKJ: JUMP_IF(game.skipflags[parm[0]]); break;
760 case OP_PSPLUS: textbox.StageSelect.SetSlot(parm[0], parm[1]); break;
762 case OP_NCJ:
763 JUMP_IF(CountObjectsOfType(parm[0]) > 0);
764 break;
766 case OP_ECJ: // unused but valid
767 JUMP_IF(FindObjectByID2(parm[0]));
768 break;
770 // life capsule--add to max life
771 case OP_MLPLUS:
772 player->maxHealth += parm[0];
773 player->hp = player->maxHealth;
774 break;
776 case OP_FON: // focus on NPC
778 if ((o = FindObjectByID2(parm[0])))
780 map_focus(o, parm[1]);
783 break;
784 case OP_FOB: // focus on boss
786 if (game.stageboss.object)
787 map_focus(game.stageboss.object, parm[1]);
788 else
789 staterr("tsc: <FOB without stage boss");
791 break;
792 case OP_FOM: // focus back to player (mychar)
794 map_focus(NULL, parm[0]);
796 break;
798 case OP_DNA: // delete all objects of type parm1
800 Object *o = firstobject;
801 while(o)
803 if (o->type == parm[0]) o->Delete();
804 o = o->next;
807 break;
809 case OP_ANP: NPCDo(parm[0], parm[1], parm[2], DoANP); break;
810 case OP_CNP: NPCDo(parm[0], parm[1], parm[2], DoCNP); break;
811 case OP_DNP: NPCDo(parm[0], parm[1], parm[2], DoDNP); break;
813 case OP_MNP: // move object X to (Y,Z) with direction W
814 if ((o = FindObjectByID2(parm[0])))
816 SetCSDir(o, parm[3]);
817 o->x = (parm[1] * TILE_W) << CSF;
818 o->y = (parm[2] * TILE_H) << CSF;
820 break;
822 case OP_BOA: // set boss state
824 game.stageboss.SetState(parm[0]);
826 break;
827 case OP_BSL: // bring up boss bar
829 Object *target;
830 if (parm[0] == 0)
831 { // <BSL0000 means the stage boss
832 target = game.stageboss.object;
833 if (!game.stageboss.object)
834 staterr("<BSL0000 but no stage boss present");
836 else
838 target = FindObjectByID2(parm[0]);
841 if (target)
843 game.bossbar.object = target;
844 game.bossbar.defeated = false;
845 game.bossbar.starting_hp = target->hp;
846 game.bossbar.bar.displayed_value = target->hp;
848 else
850 staterr("Target of <BSL not found");
853 break;
855 case OP_MM0: player->xinertia = 0; break;
856 case OP_MYD: SetPDir(parm[0]); break;
857 case OP_MYB:
859 player->lookaway = 0;
860 player->yinertia = -0x200;
861 int dir = parm[0];
863 if (dir >= 10) // bump away from the object in parm
865 o = FindObjectByID2(dir);
866 if (o)
868 if (player->CenterX() > o->CenterX())
869 dir = 0;
870 else
871 dir = 2;
875 if (dir == 0)
877 player->dir = LEFT;
878 player->xinertia = 0x200;
880 else if (dir == 2)
882 player->dir = RIGHT;
883 player->xinertia = -0x200;
886 break;
888 case OP_WAI: s->delaytimer = parm[0]; return;
889 case OP_WAS: s->wait_standing = true; return; // wait until player has blockd
891 case OP_SMP: map.tiles[parm[0]][parm[1]]--; break;
893 case OP_CMP: // change map tile at x:y to z and create smoke
895 int x = parm[0];
896 int y = parm[1];
897 map.tiles[x][y] = parm[2];
899 // get smoke coords
900 x = ((x * TILE_W) + (TILE_W / 2)) << CSF;
901 y = ((y * TILE_H) + (TILE_H / 2)) << CSF;
902 // when tiles are CMP'd during a PRI the smoke is not visible
903 // until the game is released, so I came up with this scheme
904 // to make that happen. See the "you see a button" destroyable
905 // box on the 2nd level of Maze M.
906 if (game.frozen)
908 o = CreateObject(x, y, OBJ_SMOKE_DROPPER);
909 o->timer2 = 4; // amount of smoke
911 else
913 SmokeXY(x, y, 4, TILE_W/2, TILE_H/2);
916 break;
918 case OP_QUA: s->nod_delay = game.quaketime = parm[0]; break;
920 case OP_LIPLUS: AddHealth(parm[0]); break;
921 case OP_AEPLUS: RefillAllAmmo(); break; // refills missiles
923 case OP_INI: game.switchstage.mapno = NEW_GAME; break; // restart game from beginning
924 case OP_STC: niku_save(game.counter); break;
926 case OP_SVP:
928 if (!settings->multisave)
930 if (!Replay::IsPlaying())
931 game_save(settings->last_save_slot);
933 else
935 textbox.SaveSelect.SetVisible(true, SS_SAVING);
936 s->delaytimer = 9999;
937 return;
940 break;
941 case OP_LDP:
942 game.switchstage.mapno = LOAD_GAME;
943 break;
945 case OP_HMC: player->hide = true; break;
946 case OP_SMC: player->hide = false; break;
948 // ---------------------------------------
950 case OP_MSG: // bring up text box
952 // required for post-Ballos cutscene
953 textbox.SetFlags(TUR_PARAMS, false);
954 textbox.SetVisible(true, TB_DEFAULTS);
956 break;
958 case OP_MS2: // bring up text box, at top, with no border
960 textbox.SetFace(0); // required for Undead Core intro
961 textbox.SetFlags(TUR_PARAMS, false);
962 textbox.SetVisible(true, TB_DRAW_AT_TOP | TB_NO_BORDER);
964 break;
966 case OP_MS3: // bring up text box, at top
968 textbox.SetFlags(TUR_PARAMS, false);
969 textbox.SetVisible(true, TB_DRAW_AT_TOP);
971 break;
973 case OP_CLO: // dismiss text box.
974 textbox.SetVisible(false);
975 textbox.ClearText();
976 // ...don't ResetState(), or it'll clear <FAC during Momorin dialog (Hideout)
977 break;
979 case OP_TEXT: // text to be displayed
981 str = (char *)&s->program[s->ip];
982 s->ip += (strlen(str) + 1);
984 textbox.AddText(str);
986 // must yield execution, because the message is busy now.
987 // however, if the message contains only CR's, then we don't yield,
988 // because CR's take no time to display.
989 if (contains_non_cr(str))
991 //stat("<> Pausing script execution to display message.");
992 return;
994 /*else
996 stat("<> Message is only CR's, continuing script...");
999 break;
1001 case OP_CLR: // erase all text in box
1002 textbox.ClearText();
1003 break;
1005 case OP_FAC: // set and slide in given character face
1006 textbox.SetFace(parm[0]);
1007 break;
1009 case OP_NOD: // pause till user presses key
1011 if (textbox.IsVisible())
1013 s->waitforkey = true; // pause exec till key pressed
1014 // don't release immediately if keys already down
1015 s->lastjump = true;
1016 s->lastfire = true;
1018 textbox.ShowCursor(true);
1021 return;
1023 case OP_YNJ: // prompt Yes or No and jump to given script if No
1025 textbox.YesNoPrompt.SetVisible(true);
1026 s->ynj_jump = parm[0];
1028 return;
1030 break;
1032 case OP_SAT: // disables typing animation
1033 case OP_CAT: // unused synonym
1035 textbox.SetFlags(TB_LINE_AT_ONCE | TB_CURSOR_NEVER_SHOWN, true);
1037 break;
1039 case OP_TUR: // set text mode to that used for signs
1041 textbox.SetFlags(TUR_PARAMS, true);
1042 textbox.SetCanSpeedUp(false);
1044 break;
1046 case OP_GIT: // show item graphic
1048 if (parm[0] != 0)
1050 int sprite, frame;
1052 if (parm[0] >= 1000)
1053 { // an item
1054 sprite = SPR_ITEMIMAGE;
1055 frame = (parm[0] - 1000);
1057 else
1058 { // a weapon
1059 sprite = SPR_ARMSICONS;
1060 frame = parm[0];
1063 textbox.ItemImage.SetSprite(sprite, frame);
1064 textbox.ItemImage.SetVisible(true);
1066 else
1068 textbox.ItemImage.SetVisible(false);
1071 break;
1073 case OP_NUM:
1074 { // seems to show the last value that was used with "AM+"
1075 char buf[16];
1076 sprintf(buf, "%d", lastammoinc);
1078 textbox.AddText(buf);
1080 break;
1082 case OP_SLP: // bring up teleporter menu
1084 textbox.StageSelect.SetVisible(true);
1085 return;
1087 break;
1089 case OP_ESC:
1091 StopScript(s);
1092 game.reset();
1094 break;
1096 // ---------------------------------------
1098 // trigger island-falling cinematic
1099 // if the parameter is 0, the island crashes (good ending);
1100 // if the parameter is 1, the island survives (best ending)
1101 case OP_XX1:
1103 game.setmode(GM_ISLAND, parm[0]);
1104 return;
1106 break;
1108 case OP_CRE:
1110 game.setmode(GM_CREDITS);
1111 return;
1113 break;
1115 case OP_SIL:
1116 credit_set_image(parm[0]);
1117 break;
1119 case OP_CIL:
1120 credit_clear_image();
1121 break;
1123 default:
1125 if (cmd < OP_COUNT)
1126 console.Print("- unimplemented opcode %s; script %04d halted.", cmd_table[cmd].mnemonic, s->scriptno);
1127 else
1128 console.Print("- unimplemented opcode %02x; script %04d halted.", cmd, s->scriptno);
1130 StopScript(s);
1131 return;
1138 void c------------------------------() {}
1141 int CVTDir(int csdir)
1143 const int cdir_to_nxdir[4] = { LEFT, UP, RIGHT, DOWN };
1145 if (csdir >= 0 && csdir < 4)
1146 return cdir_to_nxdir[csdir];
1148 staterr("CVTDir: invalid direction %d, returning LEFT", csdir);
1149 return LEFT;
1152 const char *DescribeCSDir(int csdir)
1154 switch(csdir)
1156 case 0: return "LEFT";
1157 case 1: return "UP";
1158 case 2: return "RIGHT";
1159 case 3: return "DOWN";
1160 case 4: return "FACE_PLAYER";
1161 case 5: return "NO_CHANGE";
1162 default: return stprintf("Invalid CS Dir %d", csdir);
1166 // converts from a CS direction (0123 = left,up,right,down)
1167 // into a NXEngine direction (0123 = right,left,up,down),
1168 // and applies the converted direction to the object.
1169 void SetCSDir(Object *o, int csdir)
1171 if (csdir < 4)
1173 o->dir = CVTDir(csdir);
1175 else if (csdir == 4)
1176 { // face towards player
1177 o->dir = (o->x >= player->x) ? LEFT : RIGHT;
1179 else if (csdir == 5)
1180 { // no-change, used with e.g. ANP
1182 else
1184 staterr("SetCSDir: warning: invalid direction %04d passed as dirparam only", csdir);
1187 // a few late-game objects, such as statues in the statue room,
1188 // use ANP/CNP's direction parameter as an extra generic parameter
1189 // to the object. I didn't feel it was safe to set a dir of say 200
1190 // in our engine as it may cause crashes somewhere if the sprite was
1191 // ever tried to be drawn using that dir. There's also the complication
1192 // that we're about to munge the requested values since our direction
1193 // constants don't have the same numerical values as CS's engine.
1194 // So is dirparam holds the raw value of the last dir that a script
1195 // tried to set.
1196 o->dirparam = csdir;
1199 void SetPDir(int d)
1201 if (d == 3)
1202 { // look away
1203 player->lookaway = 1;
1205 else
1207 player->lookaway = 0;
1209 if (d < 10)
1210 { // set direction - left/right/up/down
1211 SetCSDir(player, d);
1213 else
1214 { // face the object in parm
1215 Object *o;
1217 if ((o = FindObjectByID2(d)))
1219 player->dir = (player->x > o->x) ? LEFT:RIGHT;
1224 player->xinertia = 0;
1225 PSelectFrame();
1229 void c------------------------------() {}
1232 // call action_function on all NPCs with id2 matching "id2".
1233 void NPCDo(int id2, int p1, int p2, void (*action_function)(Object *o, int p1, int p2))
1235 // make a list first, as during <CNP, changing the
1236 // object type may call BringToFront and break stuff
1237 // if there are multiple hits.
1238 Object *hits[MAX_OBJECTS], *o;
1239 int numhits = 0;
1241 FOREACH_OBJECT(o)
1243 if (o->id2 == id2 && o != player)
1245 if (numhits < MAX_OBJECTS)
1246 hits[numhits++] = o;
1250 for(int i=0;i<numhits;i++)
1251 (*action_function)(hits[i], p1, p2);
1255 void DoANP(Object *o, int p1, int p2) // ANIMATE (set) object's state to p1 and set dir to p2
1257 #ifdef TRACE_SCRIPT
1258 stat("ANP: Obj %08x (%s): setting state: %d and dir: %s", \
1259 o, DescribeObjectType(o->type), p1, DescribeCSDir(p2));
1260 #endif
1262 o->state = p1;
1263 SetCSDir(o, p2);
1266 void DoCNP(Object *o, int p1, int p2) // CHANGE object to p1 and set dir to p2
1268 #ifdef TRACE_SCRIPT
1269 stat("CNP: Obj %08x changing from %s to %s, new dir = %s",
1270 o, DescribeObjectType(o->type), DescribeObjectType(p1), DescribeCSDir(p2));
1271 #endif
1273 // Must set direction BEFORE changing type, so that the Carried Puppy object
1274 // gets priority over the direction to use while the game is <PRI'd.
1275 SetCSDir(o, p2);
1276 o->ChangeType(p1);
1279 void DoDNP(Object *o, int p1, int p2) // DELETE object
1281 #ifdef TRACE_SCRIPT
1282 stat("DNP: %08x (%s) deleted", o, DescribeObjectType(o->type));
1283 #endif
1285 o->Delete();
1289 void c------------------------------() {}
1292 // delimit real newlines in 'in' to "\n"'s.
1293 void crtoslashn(const char *in, char *out)
1295 int i, j;
1297 for(i=j=0;in[i];i++)
1299 if (in[i] == 13)
1301 out[j++] = '\\';
1302 out[j++] = 'n';
1304 else
1306 out[j++] = in[i];
1310 out[j] = 0;
1313 bool contains_non_cr(const char *str)
1315 for(int i=0;str[i];i++)
1317 if (str[i] != '\r' && str[i] != '\n')
1318 return true;
1321 return false;