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
[str
[0]];
76 l2
= letter_to_code
[str
[1]];
77 l3
= letter_to_code
[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
= fopen(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 while(buf
<= buf_end
)
213 { // start of a scriptzz
216 script
->Append8(OP_END
);
220 int scriptno
= ReadNumber(&buf
, buf_end
);
221 if (scriptno
>= 10000 || scriptno
< 0)
223 staterr("tsc_compile: invalid script number: %d", scriptno
);
227 // skip the CR after the script #
230 if (*buf
!= '\r' && *buf
!= '\n') break;
234 //stat("Parsing script #%04d", scriptno);
235 if (page
->scripts
.get(scriptno
))
237 staterr("tsc_compile WARNING: duplicate script #%04d; ignoring", scriptno
);
238 // because script is left null, we'll ignore everything until we see another #
242 script
= new DBuffer
;
243 page
->scripts
.put(scriptno
, script
);
246 else if (ch
== '<' && script
)
248 // read the command type
249 cmdbuf
[0] = nextchar(&buf
, buf_end
);
250 cmdbuf
[1] = nextchar(&buf
, buf_end
);
251 cmdbuf
[2] = nextchar(&buf
, buf_end
);
253 int cmd
= MnemonicToOpcode(cmdbuf
);
254 if (cmd
== -1) return 1;
256 //stat("Command '%s', parameters %d", cmdbuf, cmd_table[cmd].nparams);
257 script
->Append8(cmd
);
259 // read all parameters expected by that command
260 int nparams
= cmd_table
[cmd
].nparams
;
261 for(int i
=0;i
<nparams
;i
++)
263 int val
= ReadNumber(&buf
, buf_end
);
265 script
->Append8(val
>> 8);
266 script
->Append8(val
& 0xff);
268 // colon between params
269 if (i
< (nparams
- 1))
274 { // text for message boxes
277 script
->Append8(OP_TEXT
);
278 ReadText(script
, &buf
, buf_end
);
284 script
->Append8(OP_END
);
289 static char nextchar(const char **buf
, const char *buf_end
)
297 static int ReadNumber(const char **buf
, const char *buf_end
)
299 static char num
[5] = { 0 };
304 num
[i
] = nextchar(buf
, buf_end
);
305 if (!isdigit(num
[i
]))
317 static void ReadText(DBuffer
*script
, const char **buf
, const char *buf_end
)
319 while(*buf
<= buf_end
)
321 char ch
= nextchar(buf
, buf_end
);
322 if (ch
== '<' || ch
== '#')
332 script
->Append8('\0');
336 void c------------------------------() {}
339 void RunScripts(void)
341 if (curscript
.running
)
342 ExecScript(&curscript
);
345 void StopScripts(void)
347 if (curscript
.running
)
348 StopScript(&curscript
);
351 int GetCurrentScript(void)
353 if (curscript
.running
)
354 return curscript
.scriptno
;
359 ScriptInstance
*GetCurrentScriptInstance()
361 if (curscript
.running
)
368 void c------------------------------() {}
371 // returns a pointer to the executable data/bytecode of the given script.
372 // handles looking on head, etc.
373 const uint8_t *FindScriptData(int scriptno
, int pageno
, int *page_out
)
375 ScriptPage
*page
= &script_pages
[pageno
];
378 script
= page
->scripts
.get(scriptno
);
381 if (pageno
!= SP_HEAD
)
382 { // try to find the script in head.tsc
383 return FindScriptData(scriptno
, SP_HEAD
, page_out
);
391 if (page_out
) *page_out
= pageno
;
392 return script
->Data();
396 ScriptInstance
*StartScript(int scriptno
, int pageno
)
398 const uint8_t *program
;
401 program
= FindScriptData(scriptno
, pageno
, &found_pageno
);
404 staterr("StartScript: no script at position #%04d page %d!", scriptno
, pageno
);
408 // don't start regular map scripts (e.g. hvtrigger) if player is dead
409 if (player
->dead
&& found_pageno
!= SP_HEAD
)
411 stat("Not starting script %d; player is dead", scriptno
);
416 memset(&curscript
, 0, sizeof(ScriptInstance
));
418 curscript
.program
= program
;
419 curscript
.scriptno
= scriptno
;
420 curscript
.pageno
= found_pageno
;
422 curscript
.ynj_jump
= -1;
423 curscript
.running
= true;
425 textbox
.ResetState();
426 stat(" - Started script %04d", scriptno
);
432 void StopScript(ScriptInstance
*s
)
438 stat(" - Stopped script %04d", s
->scriptno
);
440 // TRA is really supposed to be a jump, not a script restart--
441 // in that in maintains KEY/PRI across the stage transition.
442 // Emulate this by leaving the script state alone until the
443 // on-entry script starts.
444 player
->inputs_locked
= false;
446 player
->lookaway
= false;
448 textbox
.ResetState();
452 void c------------------------------() {}
455 bool JumpScript(int newscriptno
, int pageno
)
457 ScriptInstance
*s
= &curscript
;
462 stat("JumpScript: moving to script #%04d page %d", newscriptno
, pageno
);
464 s
->program
= FindScriptData(newscriptno
, pageno
, &s
->pageno
);
465 s
->scriptno
= newscriptno
;
470 staterr("JumpScript: missing script #%04d! Script terminated.", newscriptno
);
476 s
->waitforkey
= false;
477 s
->wait_standing
= false;
479 // <EVE doesn't clear textbox mode or the face etc
480 if (textbox
.IsVisible())
484 // see entrance to Sacred Grounds when you have the Nikumaru Counter
485 // to witness that EVE clears TUR.
486 textbox
.SetFlags(TB_LINE_AT_ONCE
, false);
487 textbox
.SetFlags(TB_VARIABLE_WIDTH_CHARS
, false);
488 textbox
.SetFlags(TB_CURSOR_NEVER_SHOWN
, false);
494 void ExecScript(ScriptInstance
*s
)
503 char debugbuffer
[100];
505 #define JUMP_IF(cond) \
509 if (JumpScript(parm[1])) return; \
513 // pause script while FAI/FAO still working
514 if (fade
.getstate() == FS_FADING
) return;
515 if (game
.mode
== GM_ISLAND
) return;
517 // waiting for an answer from a Yes/No prompt?
518 if (s
->ynj_jump
!= -1)
520 if (textbox
.YesNoPrompt
.ResultReady())
522 if (textbox
.YesNoPrompt
.GetResult() == NO
)
523 JumpScript(s
->ynj_jump
);
525 textbox
.YesNoPrompt
.SetVisible(false);
529 { // pause script until answer is receieved
534 // pause script while text is still displaying
535 if (textbox
.IsBusy()) return;
537 // pause while NOD is in effect
540 if (s
->nod_delay
) // used to pause during <QUA without freezing textboxes in Hell
544 else if (buttondown())
546 if (!s
->keysdown
) // release nod
548 // hide the fact that the key was just pushed
549 // so player doesn't jump/fire stupidly when dismissing textboxes
550 lastinputs
[JUMPKEY
] = true;
551 lastinputs
[FIREKEY
] = true;
552 lastpinputs
[JUMPKEY
] = true;
553 lastpinputs
[FIREKEY
] = true;
555 s
->waitforkey
= false;
556 textbox
.ShowCursor(false);
564 if (s
->waitforkey
) return;
567 // pause scripts while WAI is in effect.
568 // <WAI9999, used in inventory/stage-select screen, means forever.
571 if (s
->delaytimer
== 9999)
573 UnlockInventoryInput();
583 // pause while WAS (wait until standing) is in effect.
584 if (s
->wait_standing
)
586 if (!player
->blockd
) return;
587 s
->wait_standing
= false;
590 // main execution loop
594 cmd
= s
->program
[cmdip
];
595 mnemonic
= (char *)cmd_table
[cmd
].mnemonic
;
599 sprintf(debugbuffer
, "%04x <%s ", cmd
, mnemonic
);
600 for(i
=0;i
<cmd_table
[cmd
].nparams
;i
++)
602 val
= ((int)s
->program
[s
->ip
++]) << 8;
603 val
|= s
->program
[s
->ip
++];
605 sprintf(debugbuffer
, "%s %04d", debugbuffer
, val
);
611 char debugbuffer2
[10000];
612 crtoslashn((char *)&s
->program
[s
->ip
], debugbuffer2
);
613 sprintf(debugbuffer
, "TEXT '%s'", debugbuffer2
);
616 if (cmd
== OP_TEXT
&& !textbox
.IsVisible() && !strcmp(debugbuffer
, "TEXT '\n'")) { }
619 stat("%04d:%d %s", s
->scriptno
, cmdip
, debugbuffer
);
625 case OP_END
: StopScript(s
); return;
627 case OP_FAI
: fade
.Start(FADE_IN
, parm
[0], SPR_FADE_DIAMOND
); return;
628 case OP_FAO
: fade
.Start(FADE_OUT
, parm
[0], SPR_FADE_DIAMOND
); return;
629 case OP_FLA
: flashscreen
.Start(); break;
631 case OP_SOU
: sound(parm
[0]); break;
632 case OP_CMU
: music(parm
[0]); break;
633 case OP_RMU
: music(music_lastsong()); break;
634 case OP_FMU
: org_fade(); break;
636 case OP_SSS
: StartStreamSound(parm
[0]); break;
637 case OP_SPS
: StartPropSound(); break;
639 case OP_CSS
: // these seem identical-- either one will
640 case OP_CPS
: // in fact stop the other.
646 // free menu selector in Inventory. It also undoes <PRI,
647 // as can be seen at the entrance to Sacred Ground.
651 player
->inputs_locked
= false;
652 UnlockInventoryInput();
656 case OP_PRI
: // freeze entire game (players + NPCs)
659 player
->inputs_locked
= false;
660 statusbar
.xpflashcount
= 0; // looks odd if this happens after a long <PRI, even though it's technically correct
664 case OP_KEY
: // lock players input but NPC/objects still run
667 player
->inputs_locked
= true;
672 player
->x
= (parm
[0] * TILE_W
) << CSF
;
673 player
->y
= (parm
[1] * TILE_H
) << CSF
;
674 player
->xinertia
= player
->yinertia
= 0;
675 player
->lookaway
= false;
679 player
->movementmode
= parm
[0];
680 map_scroll_lock(parm
[0]); // locks on anything other than 0
683 case OP_MNA
: // show map name (as used on entry)
687 case OP_MLP
: // bring up Map System
688 game
.setmode(GM_MAP_SYSTEM
, game
.mode
);
693 bool waslocked
= (player
->inputs_locked
|| game
.frozen
);
695 stat("******* Executing <TRA to stage %d", parm
[0]);
696 game
.switchstage
.mapno
= parm
[0];
697 game
.switchstage
.eventonentry
= parm
[1];
698 game
.switchstage
.playerx
= parm
[2];
699 game
.switchstage
.playery
= parm
[3];
702 if (game
.switchstage
.mapno
!= 0)
704 // KEY is maintained across TRA as if the TRA
705 // were a jump instead of a restart; but if the
706 // game is in PRI then it is downgraded to a KEY.
707 // See entrance to Yamashita Farm.
710 player
->inputs_locked
= true;
719 case OP_AMPLUS
: GetWeapon(parm
[0], parm
[1]); lastammoinc
= parm
[1]; break;
720 case OP_AMMINUS
: LoseWeapon(parm
[0]); break;
721 case OP_TAM
: TradeWeapon(parm
[0], parm
[1], parm
[2]); break;
722 case OP_AMJ
: JUMP_IF(player
->weapons
[parm
[0]].hasWeapon
); break;
724 case OP_ZAM
: // drop all weapons to level 1
726 for(int i
=0;i
<WPN_COUNT
;i
++)
728 player
->weapons
[i
].xp
= 0;
729 player
->weapons
[i
].level
= 0;
734 case OP_EVE
: JumpScript(parm
[0]); break; // unconditional jump to event
736 case OP_FLPLUS
: game
.flags
[parm
[0]] = 1; break;
737 case OP_FLMINUS
: game
.flags
[parm
[0]] = 0; break;
738 case OP_FLJ
: JUMP_IF(game
.flags
[parm
[0]]); break;
740 case OP_ITPLUS
: AddInventory(parm
[0]); break;
741 case OP_ITMINUS
: DelInventory(parm
[0]); break;
742 case OP_ITJ
: JUMP_IF((FindInventory(parm
[0]) != -1)); break;
744 // the PSelectSprite is a hack so when the Mimiga Mask is taken
745 // it disappears immediately even though the game is in <PRI.
746 case OP_EQPLUS
: player
->equipmask
|= parm
[0]; PSelectSprite(); break;
747 case OP_EQMINUS
: player
->equipmask
&= ~parm
[0]; PSelectSprite(); break;
749 case OP_SKPLUS
: game
.skipflags
[parm
[0]] = 1; break;
750 case OP_SKMINUS
: game
.skipflags
[parm
[0]] = 0; break;
751 case OP_SKJ
: JUMP_IF(game
.skipflags
[parm
[0]]); break;
753 case OP_PSPLUS
: textbox
.StageSelect
.SetSlot(parm
[0], parm
[1]); break;
756 JUMP_IF(CountObjectsOfType(parm
[0]) > 0);
759 case OP_ECJ
: // unused but valid
760 JUMP_IF(FindObjectByID2(parm
[0]));
763 // life capsule--add to max life
765 player
->maxHealth
+= parm
[0];
766 player
->hp
= player
->maxHealth
;
769 case OP_FON
: // focus on NPC
771 if ((o
= FindObjectByID2(parm
[0])))
773 map_focus(o
, parm
[1]);
777 case OP_FOB
: // focus on boss
779 if (game
.stageboss
.object
)
780 map_focus(game
.stageboss
.object
, parm
[1]);
782 staterr("tsc: <FOB without stage boss");
785 case OP_FOM
: // focus back to player (mychar)
787 map_focus(NULL
, parm
[0]);
791 case OP_DNA
: // delete all objects of type parm1
793 Object
*o
= firstobject
;
796 if (o
->type
== parm
[0]) o
->Delete();
802 case OP_ANP
: NPCDo(parm
[0], parm
[1], parm
[2], DoANP
); break;
803 case OP_CNP
: NPCDo(parm
[0], parm
[1], parm
[2], DoCNP
); break;
804 case OP_DNP
: NPCDo(parm
[0], parm
[1], parm
[2], DoDNP
); break;
806 case OP_MNP
: // move object X to (Y,Z) with direction W
807 if ((o
= FindObjectByID2(parm
[0])))
809 SetCSDir(o
, parm
[3]);
810 o
->x
= (parm
[1] * TILE_W
) << CSF
;
811 o
->y
= (parm
[2] * TILE_H
) << CSF
;
815 case OP_BOA
: // set boss state
817 game
.stageboss
.SetState(parm
[0]);
820 case OP_BSL
: // bring up boss bar
824 { // <BSL0000 means the stage boss
825 target
= game
.stageboss
.object
;
826 if (!game
.stageboss
.object
)
827 staterr("<BSL0000 but no stage boss present");
831 target
= FindObjectByID2(parm
[0]);
836 game
.bossbar
.object
= target
;
837 game
.bossbar
.defeated
= false;
838 game
.bossbar
.starting_hp
= target
->hp
;
839 game
.bossbar
.bar
.displayed_value
= target
->hp
;
843 staterr("Target of <BSL not found");
848 case OP_MM0
: player
->xinertia
= 0; break;
849 case OP_MYD
: SetPDir(parm
[0]); break;
852 player
->lookaway
= 0;
853 player
->yinertia
= -0x200;
856 if (dir
>= 10) // bump away from the object in parm
858 o
= FindObjectByID2(dir
);
861 if (player
->CenterX() > o
->CenterX())
871 player
->xinertia
= 0x200;
876 player
->xinertia
= -0x200;
881 case OP_WAI
: s
->delaytimer
= parm
[0]; return;
882 case OP_WAS
: s
->wait_standing
= true; return; // wait until player has blockd
884 case OP_SMP
: map
.tiles
[parm
[0]][parm
[1]]--; break;
886 case OP_CMP
: // change map tile at x:y to z and create smoke
890 map
.tiles
[x
][y
] = parm
[2];
893 x
= ((x
* TILE_W
) + (TILE_W
/ 2)) << CSF
;
894 y
= ((y
* TILE_H
) + (TILE_H
/ 2)) << CSF
;
895 // when tiles are CMP'd during a PRI the smoke is not visible
896 // until the game is released, so I came up with this scheme
897 // to make that happen. See the "you see a button" destroyable
898 // box on the 2nd level of Maze M.
901 o
= CreateObject(x
, y
, OBJ_SMOKE_DROPPER
);
902 o
->timer2
= 4; // amount of smoke
906 SmokeXY(x
, y
, 4, TILE_W
/2, TILE_H
/2);
911 case OP_QUA
: s
->nod_delay
= game
.quaketime
= parm
[0]; break;
913 case OP_LIPLUS
: AddHealth(parm
[0]); break;
914 case OP_AEPLUS
: RefillAllAmmo(); break; // refills missiles
916 case OP_INI
: game
.switchstage
.mapno
= NEW_GAME
; break; // restart game from beginning
917 case OP_STC
: niku_save(game
.counter
); break;
921 if (!settings
->multisave
)
923 if (!Replay::IsPlaying())
924 game_save(settings
->last_save_slot
);
928 textbox
.SaveSelect
.SetVisible(true, SS_SAVING
);
929 s
->delaytimer
= 9999;
935 game
.switchstage
.mapno
= LOAD_GAME
;
938 case OP_HMC
: player
->hide
= true; break;
939 case OP_SMC
: player
->hide
= false; break;
941 // ---------------------------------------
943 case OP_MSG
: // bring up text box
945 // required for post-Ballos cutscene
946 textbox
.SetFlags(TUR_PARAMS
, false);
947 textbox
.SetVisible(true, TB_DEFAULTS
);
951 case OP_MS2
: // bring up text box, at top, with no border
953 textbox
.SetFace(0); // required for Undead Core intro
954 textbox
.SetFlags(TUR_PARAMS
, false);
955 textbox
.SetVisible(true, TB_DRAW_AT_TOP
| TB_NO_BORDER
);
959 case OP_MS3
: // bring up text box, at top
961 textbox
.SetFlags(TUR_PARAMS
, false);
962 textbox
.SetVisible(true, TB_DRAW_AT_TOP
);
966 case OP_CLO
: // dismiss text box.
967 textbox
.SetVisible(false);
969 // ...don't ResetState(), or it'll clear <FAC during Momorin dialog (Hideout)
972 case OP_TEXT
: // text to be displayed
974 str
= (char *)&s
->program
[s
->ip
];
975 s
->ip
+= (strlen(str
) + 1);
977 textbox
.AddText(str
);
979 // must yield execution, because the message is busy now.
980 // however, if the message contains only CR's, then we don't yield,
981 // because CR's take no time to display.
982 if (contains_non_cr(str
))
987 case OP_CLR
: // erase all text in box
991 case OP_FAC
: // set and slide in given character face
992 textbox
.SetFace(parm
[0]);
995 case OP_NOD
: // pause till user presses key
997 if (textbox
.IsVisible())
999 s
->waitforkey
= true; // pause exec till key pressed
1000 s
->keysdown
= true; // don't release immediately if key already down
1002 textbox
.ShowCursor(true);
1007 case OP_YNJ
: // prompt Yes or No and jump to given script if No
1009 textbox
.YesNoPrompt
.SetVisible(true);
1010 s
->ynj_jump
= parm
[0];
1016 case OP_SAT
: // disables typing animation
1017 case OP_CAT
: // unused synonym
1019 textbox
.SetFlags(TB_LINE_AT_ONCE
| TB_CURSOR_NEVER_SHOWN
, true);
1023 case OP_TUR
: // set text mode to that used for signs
1025 textbox
.SetFlags(TUR_PARAMS
, true);
1026 textbox
.SetCanSpeedUp(false);
1030 case OP_GIT
: // show item graphic
1036 if (parm
[0] >= 1000)
1038 sprite
= SPR_ITEMIMAGE
;
1039 frame
= (parm
[0] - 1000);
1043 sprite
= SPR_ARMSICONS
;
1047 textbox
.ItemImage
.SetSprite(sprite
, frame
);
1048 textbox
.ItemImage
.SetVisible(true);
1052 textbox
.ItemImage
.SetVisible(false);
1058 { // seems to show the last value that was used with "AM+"
1060 sprintf(buf
, "%d", lastammoinc
);
1062 textbox
.AddText(buf
);
1066 case OP_SLP
: // bring up teleporter menu
1068 textbox
.StageSelect
.SetVisible(true);
1080 // ---------------------------------------
1082 // trigger island-falling cinematic
1083 // if the parameter is 0, the island crashes (good ending);
1084 // if the parameter is 1, the island survives (best ending)
1087 game
.setmode(GM_ISLAND
, parm
[0]);
1094 game
.setmode(GM_CREDITS
);
1100 credit_set_image(parm
[0]);
1104 credit_clear_image();
1108 console
.Print("- unimplemented opcode %s; script %04d halted.", cmd_table
[cmd
].mnemonic
, s
->scriptno
);
1116 void c------------------------------() {}
1119 int CVTDir(int csdir
)
1121 const int cdir_to_nxdir
[4] = { LEFT
, UP
, RIGHT
, DOWN
};
1123 if (csdir
>= 0 && csdir
< 4)
1124 return cdir_to_nxdir
[csdir
];
1126 staterr("CVTDir: invalid direction %d, returning LEFT", csdir
);
1130 const char *DescribeCSDir(int csdir
)
1134 case 0: return "LEFT";
1135 case 1: return "UP";
1136 case 2: return "RIGHT";
1137 case 3: return "DOWN";
1138 case 4: return "FACE_PLAYER";
1139 case 5: return "NO_CHANGE";
1140 default: return stprintf("Invalid CS Dir %d", csdir
);
1144 // converts from a CS direction (0123 = left,up,right,down)
1145 // into a NXEngine direction (0123 = right,left,up,down),
1146 // and applies the converted direction to the object.
1147 void SetCSDir(Object
*o
, int csdir
)
1151 o
->dir
= CVTDir(csdir
);
1153 else if (csdir
== 4)
1154 { // face towards player
1155 o
->dir
= (o
->x
>= player
->x
) ? LEFT
: RIGHT
;
1157 else if (csdir
== 5)
1158 { // no-change, used with e.g. ANP
1162 staterr("SetCSDir: warning: invalid direction %04d passed as dirparam only", csdir
);
1165 // a few late-game objects, such as statues in the statue room,
1166 // use ANP/CNP's direction parameter as an extra generic parameter
1167 // to the object. I didn't feel it was safe to set a dir of say 200
1168 // in our engine as it may cause crashes somewhere if the sprite was
1169 // ever tried to be drawn using that dir. There's also the complication
1170 // that we're about to munge the requested values since our direction
1171 // constants don't have the same numerical values as CS's engine.
1172 // So is dirparam holds the raw value of the last dir that a script
1174 o
->dirparam
= csdir
;
1181 player
->lookaway
= 1;
1185 player
->lookaway
= 0;
1188 { // set direction - left/right/up/down
1189 SetCSDir(player
, d
);
1192 { // face the object in parm
1195 if ((o
= FindObjectByID2(d
)))
1197 player
->dir
= (player
->x
> o
->x
) ? LEFT
:RIGHT
;
1202 player
->xinertia
= 0;
1207 void c------------------------------() {}
1210 // call action_function on all NPCs with id2 matching "id2".
1211 void NPCDo(int id2
, int p1
, int p2
, void (*action_function
)(Object
*o
, int p1
, int p2
))
1213 // make a list first, as during <CNP, changing the
1214 // object type may call BringToFront and break stuff
1215 // if there are multiple hits.
1216 Object
*hits
[MAX_OBJECTS
], *o
;
1221 if (o
->id2
== id2
&& o
!= player
)
1223 if (numhits
< MAX_OBJECTS
)
1224 hits
[numhits
++] = o
;
1228 for(int i
=0;i
<numhits
;i
++)
1229 (*action_function
)(hits
[i
], p1
, p2
);
1233 void DoANP(Object
*o
, int p1
, int p2
) // ANIMATE (set) object's state to p1 and set dir to p2
1236 stat("ANP: Obj %08x (%s): setting state: %d and dir: %s", \
1237 o
, DescribeObjectType(o
->type
), p1
, DescribeCSDir(p2
));
1244 void DoCNP(Object
*o
, int p1
, int p2
) // CHANGE object to p1 and set dir to p2
1247 stat("CNP: Obj %08x changing from %s to %s, new dir = %s",
1248 o
, DescribeObjectType(o
->type
), DescribeObjectType(p1
), DescribeCSDir(p2
));
1251 // Must set direction BEFORE changing type, so that the Carried Puppy object
1252 // gets priority over the direction to use while the game is <PRI'd.
1257 void DoDNP(Object
*o
, int p1
, int p2
) // DELETE object
1260 stat("DNP: %08x (%s) deleted", o
, DescribeObjectType(o
->type
));
1267 void c------------------------------() {}
1270 // delimit real newlines in 'in' to "\n"'s.
1271 void crtoslashn(const char *in
, char *out
)
1275 for(i
=j
=0;in
[i
];i
++)
1291 bool contains_non_cr(const char *str
)
1293 for(int i
=0;str
[i
];i
++)
1295 if (str
[i
] != '\r' && str
[i
] != '\n')