2 // TSC script parser & executor
5 #include "common/DBuffer.h"
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;
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
;
27 for(int i
=0;i
<scripts
.nitems
;i
++)
28 delete scripts
.get(i
); // it's safe to delete NULL, so no check here
34 ScriptPage script_pages
[NUM_SCRIPT_PAGES
];
37 void c------------------------------() {}
40 struct TSCCommandTable
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)
57 memset(letter_to_code
, 0xff, sizeof(letter_to_code
));
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
)
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
);
88 index
= mnemonic_lookup
[index
];
89 if (index
!= 0xff) return index
;
92 staterr("MnemonicToOpcode: No such command '%s'", str
);
97 void c------------------------------() {}
102 char fname
[MAXPATHLEN
];
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;
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
];
138 stat("tsc_load: loading '%s' to page %d", fname
, pageno
);
139 if (curscript
.running
&& curscript
.pageno
== pageno
)
140 StopScript(&curscript
);
144 // load the raw script text
145 buf
= tsc_decrypt(fname
, &fsize
);
148 staterr("tsc_load: failed to load file: '%s'", fname
);
152 // now "compile" all the scripts in the TSC
153 //int top_script = CompileScripts(buf, fsize, base);
154 result
= tsc_compile(buf
, fsize
, pageno
);
161 char *tsc_decrypt(const char *fname
, int *fsize_out
)
166 fp
= fileopen(fname
, "rb");
169 staterr("tsc_decrypt: no such file: '%s'!", fname
);
173 fseek(fp
, 0, SEEK_END
);
175 fseek(fp
, 0, SEEK_SET
);
178 char *buf
= (char *)malloc(fsize
+1);
179 fread(buf
, fsize
, 1, 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
;
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
)
215 { // start of a scriptzz
218 script
->Append8(OP_END
);
222 int scriptno
= ReadNumber(&buf
, buf_end
);
223 if (scriptno
>= 10000 || scriptno
< 0)
225 staterr("tsc_compile: invalid script number: %d", scriptno
);
229 // skip the CR after the script #
232 if (*buf
!= '\r' && *buf
!= '\n') break;
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 #
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))
276 { // text for message boxes
278 script
->Append8(OP_TEXT
);
279 ReadText(script
, &buf
, buf_end
);
285 script
->Append8(OP_END
);
290 static char nextchar(const char **buf
, const char *buf_end
)
298 static int ReadNumber(const char **buf
, const char *buf_end
)
300 static char num
[5] = { 0 };
305 num
[i
] = nextchar(buf
, buf_end
);
306 if (!isdigit(num
[i
]))
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
== '#')
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
;
360 ScriptInstance
*GetCurrentScriptInstance()
362 if (curscript
.running
)
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
];
379 script
= page
->scripts
.get(scriptno
);
382 if (pageno
!= SP_HEAD
)
383 { // try to find the script in head.tsc
384 return FindScriptData(scriptno
, SP_HEAD
, page_out
);
392 if (page_out
) *page_out
= pageno
;
393 return script
->Data();
397 ScriptInstance
*StartScript(int scriptno
, int pageno
)
399 const uint8_t *program
;
402 program
= FindScriptData(scriptno
, pageno
, &found_pageno
);
405 staterr("StartScript: no script at position #%04d page %d!", scriptno
, pageno
);
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
);
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
);
433 void StopScript(ScriptInstance
*s
)
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;
447 player
->lookaway
= false;
449 textbox
.ResetState();
453 void c------------------------------() {}
456 bool JumpScript(int newscriptno
, int pageno
)
458 ScriptInstance
*s
= &curscript
;
463 stat("JumpScript: moving to script #%04d page %d", newscriptno
, pageno
);
465 s
->program
= FindScriptData(newscriptno
, pageno
, &s
->pageno
);
466 s
->scriptno
= newscriptno
;
471 staterr("JumpScript: missing script #%04d! Script terminated.", newscriptno
);
477 s
->waitforkey
= false;
478 s
->wait_standing
= false;
480 // <EVE doesn't clear textbox mode or the face etc
481 if (textbox
.IsVisible())
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);
495 void ExecScript(ScriptInstance
*s
)
497 char debugbuffer
[256];
506 #define JUMP_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);
530 { // pause script until answer is receieved
535 // pause script while text is still displaying
536 if (textbox
.IsBusy()) return;
538 // pause while NOD is in effect
541 if (s
->nod_delay
) // used to pause during <QUA without freezing textboxes in Hell
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.
576 if (s
->delaytimer
== 9999)
578 UnlockInventoryInput();
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
601 cmd
= s
->program
[cmdip
];
602 mnemonic
= (char *)cmd_table
[cmd
].mnemonic
;
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
++];
612 snprintf(debugbuffer
, sizeof(debugbuffer
), "%s %04d", debugbuffer
, val
);
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'")) { }
626 stat("%04d:%d %s", s
->scriptno
, cmdip
, debugbuffer
);
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.
653 // free menu selector in Inventory. It also undoes <PRI,
654 // as can be seen at the entrance to Sacred Ground.
658 player
->inputs_locked
= false;
659 UnlockInventoryInput();
663 case OP_PRI
: // freeze entire game (players + NPCs)
666 player
->inputs_locked
= false;
667 statusbar
.xpflashcount
= 0; // looks odd if this happens after a long <PRI, even though it's technically correct
671 case OP_KEY
: // lock players input but NPC/objects still run
674 player
->inputs_locked
= true;
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;
686 player
->movementmode
= parm
[0];
687 map_scroll_lock(parm
[0]); // locks on anything other than 0
690 case OP_MNA
: // show map name (as used on entry)
694 case OP_MLP
: // bring up Map System
695 game
.setmode(GM_MAP_SYSTEM
, game
.mode
);
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];
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.
717 player
->inputs_locked
= true;
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;
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;
763 JUMP_IF(CountObjectsOfType(parm
[0]) > 0);
766 case OP_ECJ
: // unused but valid
767 JUMP_IF(FindObjectByID2(parm
[0]));
770 // life capsule--add to max life
772 player
->maxHealth
+= parm
[0];
773 player
->hp
= player
->maxHealth
;
776 case OP_FON
: // focus on NPC
778 if ((o
= FindObjectByID2(parm
[0])))
780 map_focus(o
, parm
[1]);
784 case OP_FOB
: // focus on boss
786 if (game
.stageboss
.object
)
787 map_focus(game
.stageboss
.object
, parm
[1]);
789 staterr("tsc: <FOB without stage boss");
792 case OP_FOM
: // focus back to player (mychar)
794 map_focus(NULL
, parm
[0]);
798 case OP_DNA
: // delete all objects of type parm1
800 Object
*o
= firstobject
;
803 if (o
->type
== parm
[0]) o
->Delete();
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
;
822 case OP_BOA
: // set boss state
824 game
.stageboss
.SetState(parm
[0]);
827 case OP_BSL
: // bring up boss bar
831 { // <BSL0000 means the stage boss
832 target
= game
.stageboss
.object
;
833 if (!game
.stageboss
.object
)
834 staterr("<BSL0000 but no stage boss present");
838 target
= FindObjectByID2(parm
[0]);
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
;
850 staterr("Target of <BSL not found");
855 case OP_MM0
: player
->xinertia
= 0; break;
856 case OP_MYD
: SetPDir(parm
[0]); break;
859 player
->lookaway
= 0;
860 player
->yinertia
= -0x200;
863 if (dir
>= 10) // bump away from the object in parm
865 o
= FindObjectByID2(dir
);
868 if (player
->CenterX() > o
->CenterX())
878 player
->xinertia
= 0x200;
883 player
->xinertia
= -0x200;
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
897 map
.tiles
[x
][y
] = parm
[2];
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.
908 o
= CreateObject(x
, y
, OBJ_SMOKE_DROPPER
);
909 o
->timer2
= 4; // amount of smoke
913 SmokeXY(x
, y
, 4, TILE_W
/2, TILE_H
/2);
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;
928 if (!settings
->multisave
)
930 if (!Replay::IsPlaying())
931 game_save(settings
->last_save_slot
);
935 textbox
.SaveSelect
.SetVisible(true, SS_SAVING
);
936 s
->delaytimer
= 9999;
942 game
.switchstage
.mapno
= LOAD_GAME
;
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
);
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
);
966 case OP_MS3
: // bring up text box, at top
968 textbox
.SetFlags(TUR_PARAMS
, false);
969 textbox
.SetVisible(true, TB_DRAW_AT_TOP
);
973 case OP_CLO
: // dismiss text box.
974 textbox
.SetVisible(false);
976 // ...don't ResetState(), or it'll clear <FAC during Momorin dialog (Hideout)
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.");
996 stat("<> Message is only CR's, continuing script...");
1001 case OP_CLR
: // erase all text in box
1002 textbox
.ClearText();
1005 case OP_FAC
: // set and slide in given character face
1006 textbox
.SetFace(parm
[0]);
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
1018 textbox
.ShowCursor(true);
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];
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);
1039 case OP_TUR
: // set text mode to that used for signs
1041 textbox
.SetFlags(TUR_PARAMS
, true);
1042 textbox
.SetCanSpeedUp(false);
1046 case OP_GIT
: // show item graphic
1052 if (parm
[0] >= 1000)
1054 sprite
= SPR_ITEMIMAGE
;
1055 frame
= (parm
[0] - 1000);
1059 sprite
= SPR_ARMSICONS
;
1063 textbox
.ItemImage
.SetSprite(sprite
, frame
);
1064 textbox
.ItemImage
.SetVisible(true);
1068 textbox
.ItemImage
.SetVisible(false);
1074 { // seems to show the last value that was used with "AM+"
1076 sprintf(buf
, "%d", lastammoinc
);
1078 textbox
.AddText(buf
);
1082 case OP_SLP
: // bring up teleporter menu
1084 textbox
.StageSelect
.SetVisible(true);
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)
1103 game
.setmode(GM_ISLAND
, parm
[0]);
1110 game
.setmode(GM_CREDITS
);
1116 credit_set_image(parm
[0]);
1120 credit_clear_image();
1126 console
.Print("- unimplemented opcode %s; script %04d halted.", cmd_table
[cmd
].mnemonic
, s
->scriptno
);
1128 console
.Print("- unimplemented opcode %02x; script %04d halted.", cmd
, s
->scriptno
);
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
);
1152 const char *DescribeCSDir(int 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
)
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
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
1196 o
->dirparam
= csdir
;
1203 player
->lookaway
= 1;
1207 player
->lookaway
= 0;
1210 { // set direction - left/right/up/down
1211 SetCSDir(player
, d
);
1214 { // face the object in parm
1217 if ((o
= FindObjectByID2(d
)))
1219 player
->dir
= (player
->x
> o
->x
) ? LEFT
:RIGHT
;
1224 player
->xinertia
= 0;
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
;
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
1258 stat("ANP: Obj %08x (%s): setting state: %d and dir: %s", \
1259 o
, DescribeObjectType(o
->type
), p1
, DescribeCSDir(p2
));
1266 void DoCNP(Object
*o
, int p1
, int p2
) // CHANGE object to p1 and set dir to p2
1269 stat("CNP: Obj %08x changing from %s to %s, new dir = %s",
1270 o
, DescribeObjectType(o
->type
), DescribeObjectType(p1
), DescribeCSDir(p2
));
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.
1279 void DoDNP(Object
*o
, int p1
, int p2
) // DELETE object
1282 stat("DNP: %08x (%s) deleted", o
, DescribeObjectType(o
->type
));
1289 void c------------------------------() {}
1292 // delimit real newlines in 'in' to "\n"'s.
1293 void crtoslashn(const char *in
, char *out
)
1297 for(i
=j
=0;in
[i
];i
++)
1313 bool contains_non_cr(const char *str
)
1315 for(int i
=0;str
[i
];i
++)
1317 if (str
[i
] != '\r' && str
[i
] != '\n')