/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "glk/hugo/hugo.h"
namespace Glk {
namespace Hugo {
void Hugo::RunDo() {
long skip, enterptr;
enterptr = ++codeptr;
skip = PeekWord(codeptr); /* remember the skip distance */
codeptr+=2;
SetStackFrame(stack_depth, DOWHILE_BLOCK, skip+enterptr, codeptr);
#if defined (DEBUGGER)
dbnest++;
#endif
}
void Hugo::RunEvents() {
int i, tempundo, flag, temp_ret;
int eventin, tempself;
int templocals[MAXLOCALS];
int temp_stack_depth;
int temp_parse_location;
long tempptr, eventaddr;
#if defined (DEBUGGER)
int tempdbnest;
#endif
tempundo = undorecord;
undorecord = true;
tempptr = codeptr;
tempself = var[self];
temp_ret = ret;
temp_parse_location = parse_location;
parse_location = var[location]; /* for Available() */
temp_stack_depth = stack_depth;
for (i=0; iputBuffer("\n", 1);
#if defined (SCROLLBACK_DEFINED)
hugo_sendtoscrollback("\n");
#endif
AP(line);
}
}
#if defined (DEBUGGER)
if (debugger_collapsing)
goto NormalTermination;
runaway_counter = 0;
#endif
if (shouldQuit())
return;
SeparateWords();
if (record)
{
for (i=1; i<=words; i++)
{
if (!strcmp(word[i], "."))
{
/* fprintf() this way for Glk */
if (hugo_fprintf(record, "%s", "\n")<0)
FatalError(WRITE_E);
if (i==words) goto RecordedNewline;
}
else if (hugo_fputs(word[i], record)<0
|| hugo_fprintf(record, "%s", " ")<0)
{
FatalError(WRITE_E);
}
}
if (hugo_fprintf(record, "%s", "\n")<0) FatalError(WRITE_E);
RecordedNewline:;
}
}
else full_buffer = 0;
if (!strcmp(buffer, "") || buffer[0]=='.')
{
parseerr[0] = '\0';
/* "What?" */
ParseError(0, 0);
goto FreshInput;
}
}
/* Loop until valid input */
while (Parse()==false && strcmp(buffer, ""));
}
/* Else if there's something left in the input buffer */
else
{
newinput = false;
/* Erase the just-parsed command, and check to
to see if what's left is just blanks
*/
while (words > remaining)
KillWord(1);
flag = false;
for (i=1; i<=words; i++)
if (wd[i]!=0) flag = true;
if (!flag)
goto FreshInput;
if (words) AP("");
if (Parse()==false)
{
mc = false;
goto Skipmc;
}
}
/* Run the user Parse routine if one exists */
CallLibraryParse();
reparse_everything = false;
do
{
mc = MatchCommand();
if (mc==false)
{
remaining = 0;
}
} while (reparse_everything && !mc);
Skipmc:;
}
while (!mc);
if (!xverb) undorecord = true;
wasxverb = xverb;
/* If there's an unknown string to be put in parse$ */
if (parsestr[0]!='\0')
{
if (parsestr[0]=='\"')
{
Common::strcpy_s(parseerr, Right(parsestr, strlen(parsestr)-1));
if (parseerr[strlen(parseerr)-1]=='\"')
parseerr[strlen(parseerr)-1] = '\0';
}
}
else
parseerr[0] = '\0';
/* default actor */
var[actor] = var[player];
if (!newinput && lastspeaking) speaking = lastspeaking;
if (var[verbroutine]!=0 || (speaking))
{
/* If command addresses an object/char. */
if (speaking)
{
lastspeaking = speaking;
var[actor] = speaking;
/* If user Speakto routine exists */
if (speaktoaddr)
{
if (objcount) var[object] = objlist[0];
ret = 0;
SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0);
passlocal[0] = speaking;
PassLocals(1);
#if defined (DEBUGGER)
DebugRunRoutine((long)speaktoaddr*address_scale);
#else
RunRoutine((long)speaktoaddr*address_scale);
#endif
if (ret==0)
{
remaining = 0;
xverb = true;
}
retflag = 0;
}
else
{
/* "...start with a verb..." */
ParseError(2, 0);
xverb = true;
}
/* reset actor */
var[actor] = var[player];
}
/* Regular old vanilla command: */
else
{
speaking = 0;
lastspeaking = 0;
/* As of v2.5, the Perform junction routine takes care of calling the
before routines, verbroutine, etc.
*/
if (game_version>=25 && performaddr!=0)
{
i = 0;
NextPerform:
if (objcount) var[object] = objlist[i];
/* Have to do this before passing locals, in case
Name() ends up calling a routine (which would
trash passlocal[])
*/
if (parseerr[0]=='\0' && parsestr[0]=='\0')
Common::strcpy_s(parseerr, Name(objlist[i]));
/* Set up arguments for Perform */
passlocal[0] = var[verbroutine];
passlocal[1] = var[object];
passlocal[2] = var[xobject];
/* 'queue' argument, >1 if objcount > 1, or if
"all" has been used to refer to object(s) */
passlocal[3] = (objcount>1)?(i+1):(parse_allflag?1:0);
/* -1 if object is a digit */
if (object_is_number) passlocal[3] = (short)-1;
/* 'isxverb' argument */
if (game_version>=31 && xverb)
passlocal[4] = 1;
obj_match_state = -1;
startlocation = var[location];
ret = 0;
SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0);
PassLocals(4);
#if defined (DEBUGGER)
DebugRunRoutine((long)performaddr*address_scale);
#else
RunRoutine((long)performaddr*address_scale);
#endif
if (ret==0)
{
remaining = 0;
xverb = true;
}
retflag = 0;
/* Break if endflag is set or if the location has
changed from the first call to Perform
*/
if (var[endflag] || startlocation!=var[location])
goto EndofCommand;
if (objcount>1 && ++i 0) /* "if (objcount > 0" for pre-v2.5 */
{
obj_match_state = 1;
startlocation = var[location];
for (i=0; i1 && objlist[i]!=var[xobject]) || objcount==1))
{
var[object] = objlist[i];
if (GetProp(var[player], before, 1, 0)==0)
if (GetProp(var[location], before, 1, 0)==0)
if (GetProp(var[xobject], before, 1, 0)==0)
{
/* If multiple objects are specified, print
"name: " for each:
*/
if (objcount > 1)
{
Common::sprintf_s(line, "%s: \\;", Name(var[object]));
AP(line);
}
obj_match_state = 0;
if ((object_is_number) || GetProp(var[object], before, 1, 0)==0)
{
obj_match_state = -1;
ret = 0;
SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0);
PassLocals(0);
#if defined (DEBUGGER)
DebugRunRoutine((long)var[verbroutine]*address_scale);
#else
RunRoutine((long)var[verbroutine]*address_scale);
#endif
if (ret==0)
{
remaining = 0;
xverb = true;
}
retflag = 0;
GetProp(var[player], after, 1, 0);
GetProp(var[location], after, 1, 0);
}
}
}
if (var[endflag] || var[location]!=startlocation)
break;
}
}
/* No object(s) specified */
else
{
if (GetProp(var[player], before, 1, 0)==0)
{
if (GetProp(var[location], before, 1, 0)==0)
{
ret = 0;
SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0);
PassLocals(0);
#if defined (DEBUGGER)
DebugRunRoutine((long)var[verbroutine]*address_scale);
#else
RunRoutine((long)var[verbroutine]*address_scale);
#endif
if (ret==0)
{
remaining = 0;
xverb = true;
}
retflag = 0;
GetProp(var[player], after, 1, 0);
GetProp(var[location], after, 1, 0);
}
}
}
/* (end of pre-v2.5 verbroutine-calling) */
}
}
EndofCommand:
if (var[endflag])
break;
undorecord = false;
}
while (true); /* endless loop back to start */
undorecord = false;
if (var[endflag]==-1)
#if defined (DEBUGGER)
goto NormalTermination;
#else
return;
#endif
if (!jw)
{
undorecord = true;
SaveUndo(0, undoturn, 0, 0, 0);
undorecord = false;
undoturn = 0;
}
if (playback)
{
if (hugo_fclose(playback)) FatalError(READ_E);
playback = nullptr;
}
Flushpbuffer();
/* Run the user Endgame routine if one exists */
#if defined (DEBUGGER)
if (endgameaddr && !debugger_collapsing)
#else
if (endgameaddr)
#endif
{
passlocal[0] = var[endflag];
SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0);
ret = 0;
var[endflag] = 0;
PassLocals(0);
#if defined (DEBUGGER)
DebugRunRoutine((long)endgameaddr*address_scale);
#else
RunRoutine((long)endgameaddr*address_scale);
#endif
retflag = false;
}
else
ret = 0;
xverb = true;
wasxverb = true;
if (ret) goto Start;
/* Stop all audio after running EndGame */
hugo_stopmusic();
hugo_stopsample();
/* The debugger will reset endflag anyway, but we need it to signal ports
(like Palm) that the game loop has exited.
*/
var[endflag] = -1;
#if defined (DEBUGGER)
NormalTermination:
/* Normal program termination doesn't exit the debugger. */
debugger_interrupt = true;
debugger_run = false;
var[endflag] = false;
xverb = false;
SwitchtoDebugger();
UpdateDebugScreen();
if (debugger_collapsing!=2)
{
DebugMessageBox("Program Exiting", "Normal program termination");
}
debugger_collapsing = false;
if ((game!=nullptr) && !RunRestart())
DebugMessageBox("Restart Error", "Unable to restart");
SwitchtoGame();
history_count = 0;
window[VIEW_CALLS].count = 0;
for (i=0; i<(int)window[CODE_WINDOW].count; i++)
free(codeline[i]);
window[CODE_WINDOW].count = 0;
/* Force Code window redraw */
buffered_code_lines = FORCE_REDRAW;
goto RestartDebugger;
#endif
}
void Hugo::RunIf(char ovrride) {
char t, tempinexpr;
long enterptr, skip;
switch (t = MEM(codeptr))
{
case CASE_T:
case IF_T:
case ELSEIF_T:
case WHILE_T:
case FOR_T:
{
codeptr++;
enterptr = codeptr;
/* Remember the skip distance */
skip = PeekWord(codeptr);
codeptr += 2;
/* Check if we've already done an elseif */
if (ovrride && (t == ELSEIF_T)) {
codeptr = skip+enterptr;
return;
}
/* Read the expression */
tempinexpr = inexpr;
inexpr = 1;
SetupExpr();
inexpr = tempinexpr;
/* If the expression is false, skip the
conditional block
*/
if (EvalExpr(0)==0)
{
codeptr = skip+enterptr;
return;
}
/* Protect the stack if jumping backward */
if (MEM(codeptr)==JUMP_T)
{
if ((long)(PeekWord(codeptr+1)*address_scale) < codeptr)
if (--stack_depth < 0) stack_depth = 0;
}
/* Continue on into the conditional block if
the expression evaluated to non-zero
*/
PasstoBlock:
if (t==WHILE_T || t==FOR_T)
SetStackFrame(stack_depth, CONDITIONAL_BLOCK, skip+enterptr, 0);
else /* no 'break' parameter */
SetStackFrame(stack_depth, CONDITIONAL_BLOCK, 0, 0);
#if defined (DEBUGGER)
dbnest++;
#endif
return;
}
case ELSE_T:
{
skip = PeekWord(++codeptr);
enterptr = codeptr;
codeptr += 2;
if (ovrride)
{
codeptr = skip+enterptr;
return;
}
if (MEM(codeptr)==JUMP_T)
{
if ((long)(PeekWord(codeptr+1)*address_scale) < codeptr)
if (--stack_depth < 0) stack_depth = 0;
}
goto PasstoBlock;
}
}
}
void Hugo::RunInput() {
int i;
parseerr[0] = '\0';
Flushpbuffer();
if (icolor==-1) icolor = fcolor; /* check unset input color */
hugo_getline("");
#if defined (DEBUGGER)
if (debugger_collapsing) return;
#endif
Common::strcpy_s(buffer, Rtrim(hugo_strlwr(buffer)));
SeparateWords();
for (i=1; i<=words; i++)
{
wd[i] = FindWord(word[i]);
/* If a word isn't in the dictionary */
if (wd[i]==UNKNOWN_WORD)
{
wd[i] = 0;
Common::strcpy_s(parseerr, word[i]);
if (parseerr[0]=='\"')
{
Common::strcpy_s(parseerr, Right(parseerr, strlen(parseerr)-1));
if (parseerr[strlen(parseerr)-1]=='\"')
parseerr[strlen(parseerr)-1] = '\0';
}
}
}
currentpos = 0; /* left margin */
remaining = 0;
}
void Hugo::RunMove() {
int obj, p;
#if defined (DEBUGGER)
char out_of_range = 0;
#endif
switch (MEM(codeptr))
{
case MOVE_T:
{
codeptr++;
obj = GetValue();
#if defined (DEBUGGER)
if (!CheckinRange(obj, objects, "object"))
out_of_range = true;
else
#endif
SaveUndo(MOVE_T, obj, Parent(obj), 0, 0);
codeptr++; /* skip "to" */
p = GetValue();
#if defined (DEBUGGER)
if (!CheckinRange(p, objects, "object"))
out_of_range = true;
if (!out_of_range)
#endif
MoveObj(obj, p);
break;
}
case REMOVE_T:
{
codeptr++;
obj = GetValue();
#if defined (DEBUGGER)
if (!CheckinRange(obj, objects, "object"))
out_of_range = true;
else
#endif
SaveUndo(MOVE_T, obj, Parent(obj), 0, 0);
#if defined (DEBUGGER)
if (!out_of_range)
#endif
MoveObj(obj, 0); /* move to parent 0 */
break;
}
}
if (game_version>=23) codeptr++; /* eol */
}
void Hugo::RunPrint() {
char number = 0, hexnumber = 0;
int a;
int i, l;
codeptr++;
while (MEM(codeptr) != EOL_T)
{
line[0] = '\0';
switch (MEM(codeptr))
{
case NEWLINE_T:
{
codeptr++;
if (currentpos+hugo_textwidth(pbuffer)!=0)
AP("");
if (MEM(codeptr)==SEMICOLON_T) codeptr++;
continue;
}
case TO_T:
{
codeptr++;
#ifdef GLK
// WORKAROUND: Glk uses a non-fixed width font for displaying
// text, so get the length, but don't allow long runs of spaces
if ((a = GetValue()) > 20)
a = 0;
#elif !defined (ACTUAL_LINELENGTH)
if ((a = GetValue()) > physical_windowwidth/FIXEDCHARWIDTH)
a = physical_windowwidth/FIXEDCHARWIDTH;
#else
if ((a = GetValue()) > ACTUAL_LINELENGTH())
{
double ratio;
ratio = (physical_windowwidth/FIXEDCHARWIDTH) / a;
a = (int)(ACTUAL_LINELENGTH() / ratio);
}
#endif
line[0] = '\0';
l = 0;
if (a*FIXEDCHARWIDTH >
hugo_textwidth(pbuffer)+currentpos-hugo_charwidth(' '))
{
for (i=hugo_textwidth(pbuffer)+currentpos;
#ifdef NO_TERMINAL_LINEFEED
i= 24)
l = PeekWord(codeptr++);
else
l = Peek(codeptr);
for (i=0; i127) lowercase characters
*/
char diff;
diff = 'a'-'A';
if ((unsigned)line[0]+diff<=255 && (unsigned)line[0]-diff>127)
line[0] -= diff;
}
}
AP(line);
}
codeptr++;
}
int Hugo::RunRestart() {
unsigned int a;
long i = 0;
Common::SeekableReadStream *file;
#ifndef LOADGAMEDATA_REPLACED
remaining = 0;
#if !defined (GLK) /* with Glk, game is never closed */
/* Use file instead of game, just in case the call fails */
if (!(file = HUGO_FOPEN(gamefile, "rb"))) goto RestartError;
#else
file = game;
#endif
if (hugo_fseek(file, (objtable-gameseg)*16, SEEK_SET)) goto RestartError;
i = (objtable-gameseg)*16L;
do
{
int val;
val = hugo_fgetc(file);
SETMEM(i++, (unsigned char)val);
if (val==EOF || hugo_ferror(file)) goto RestartError;
}
while (i < codeend);
#if !defined (GLK)
if (fclose(file)) FatalError(READ_E);
#endif
#else
if (!(file = HUGO_FOPEN(gamefile, "rb"))) goto RestartError;
LoadGameData(true);
fclose(file);
#endif /* LOADGAMEDATA_REPLACED */
defseg = arraytable;
for (a=0; a and