mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
670 lines
14 KiB
C++
670 lines
14 KiB
C++
/* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "glk/hugo/hugo.h"
|
|
|
|
namespace Glk {
|
|
namespace Hugo {
|
|
|
|
#if defined (DEBUGGER)
|
|
|
|
int Hugo::CheckObjectRange(int obj) {
|
|
if (runtime_warnings) {
|
|
return CheckinRange((unsigned)obj, (unsigned)objects, "object");
|
|
} else
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
int Hugo::Child(int obj) {
|
|
int c;
|
|
|
|
#if defined (DEBUGGER)
|
|
if (!CheckObjectRange(obj)) return 0;
|
|
#endif
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
defseg = objtable;
|
|
|
|
c = PeekWord(2 + obj*object_size + object_size - 4);
|
|
|
|
defseg = gameseg;
|
|
|
|
return c;
|
|
}
|
|
|
|
int Hugo::Children(int obj) {
|
|
int count = 0;
|
|
int nextobj;
|
|
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
nextobj = Child(obj);
|
|
while (nextobj)
|
|
{count++;
|
|
nextobj = Sibling(nextobj);}
|
|
return count;
|
|
}
|
|
|
|
int Hugo::Elder(int obj) {
|
|
int lastobj;
|
|
int p, cp;
|
|
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
p = Parent(obj);
|
|
cp = Child(p);
|
|
|
|
if (p==0 || cp==obj)
|
|
return 0;
|
|
|
|
lastobj = cp;
|
|
while (Sibling(lastobj) != obj)
|
|
lastobj = Sibling(lastobj);
|
|
|
|
return lastobj;
|
|
}
|
|
|
|
unsigned long Hugo::GetAttributes(int obj, int attribute_set) {
|
|
unsigned long a;
|
|
|
|
defseg = objtable;
|
|
|
|
#if defined (DEBUGGER)
|
|
if (!CheckObjectRange(obj)) return 0;
|
|
#endif
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
a = (unsigned long)PeekWord(2 + obj*object_size + attribute_set*4)
|
|
+ (unsigned long)PeekWord(2 + obj*object_size + attribute_set*4 + 2)*65536L;
|
|
|
|
defseg = gameseg;
|
|
|
|
return a;
|
|
}
|
|
|
|
int Hugo::GetProp(int obj, int p, int n, char s) {
|
|
char objonly, /* no verbroutine given in before, etc. */
|
|
isadditive = 0, /* before, after, etc. */
|
|
gotone = 0, /* when a match has been made */
|
|
getpropaddress = 0; /* when getting &object.property */
|
|
int i;
|
|
int tempself,
|
|
objtype, /* i.e., what we're matching to */
|
|
flag = 0;
|
|
int g = 0;
|
|
int templocals[MAXLOCALS];
|
|
int temp_stack_depth;
|
|
char tempinexpr = inexpr;
|
|
unsigned int pa, /* property address */
|
|
offset = 0;
|
|
long inprop, /* code position in complex property */
|
|
returnaddr;
|
|
#if defined (DEBUGGER)
|
|
long orig_inprop;
|
|
int tempdbnest;
|
|
|
|
/* Don't check a possible non-existent display object (-1) */
|
|
if (obj!=-1 || display_object!=-1) CheckObjectRange(obj);
|
|
#endif
|
|
/* This way either -1 (the non-existent display object) or a too-high
|
|
object will fail
|
|
*/
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
/* The display object, which is automatically created by the compiler,
|
|
did not exist pre-v2.4
|
|
*/
|
|
if ((obj==display_object) && game_version>=24)
|
|
{
|
|
/* There are no actual default "properties" per se on the
|
|
display object--but reading them returns certain data about
|
|
the current state of display affairs
|
|
*/
|
|
|
|
/* no display.<prop> #2, etc. */
|
|
if (n==1 && p<=pointer_y)
|
|
{
|
|
if (p==screenwidth)
|
|
#if defined (GLK) && defined (ACTUAL_LINELENGTH)
|
|
g = ACTUAL_LINELENGTH();
|
|
#else
|
|
g = SCREENWIDTH/FIXEDCHARWIDTH;
|
|
#endif
|
|
else if (p==screenheight)
|
|
/* ACTUAL_SCREENHEIGHT can be set to a non-portable function if
|
|
SCREENHEIGHT and SCREENHEIGHT have been set to large values in
|
|
order to force the non-portable layer to handle wrapping and
|
|
scrolling (as in the Glk port).
|
|
*/
|
|
#if defined (ACTUAL_SCREENHEIGHT)
|
|
g = ACTUAL_SCREENHEIGHT();
|
|
#else
|
|
g = SCREENHEIGHT/FIXEDLINEHEIGHT;
|
|
#endif
|
|
else if (p==linelength)
|
|
/* ACTUAL_LINELENGTH functions similarly to ACTUAL_SCREENWIDTH,
|
|
above.
|
|
*/
|
|
#if defined (ACTUAL_LINELENGTH)
|
|
g = ACTUAL_LINELENGTH();
|
|
#else
|
|
g = physical_windowwidth/FIXEDCHARWIDTH;
|
|
#endif
|
|
else if (p==windowlines)
|
|
g = physical_windowheight/FIXEDLINEHEIGHT;
|
|
else if (p==cursor_column)
|
|
g = (currentpos+1+hugo_textwidth(pbuffer))/FIXEDCHARWIDTH;
|
|
else if (p==cursor_row)
|
|
g = currentline;
|
|
else if (p==hasgraphics)
|
|
g = hugo_hasgraphics();
|
|
else if (p==title_caption)
|
|
g = FindWord(game_title);
|
|
else if (p==hasvideo)
|
|
#if !defined (COMPILE_V25)
|
|
g = hugo_hasvideo();
|
|
#else
|
|
g = 0;
|
|
#endif
|
|
else if (p==needs_repaint)
|
|
g = display_needs_repaint;
|
|
else if (p==pointer_x)
|
|
g = display_pointer_x;
|
|
else if (p==pointer_y)
|
|
g = display_pointer_y;
|
|
else
|
|
g = 0;
|
|
|
|
return g;
|
|
}
|
|
}
|
|
|
|
/* To avoid prematurely getting an address in &obj.prop.prop */
|
|
if (getaddress && MEM(codeptr)!=DECIMAL_T)
|
|
getpropaddress = true;
|
|
|
|
tempself = var[self];
|
|
if (!s) var[self] = obj;
|
|
|
|
temp_stack_depth = stack_depth;
|
|
|
|
|
|
GetNextProp:
|
|
|
|
pa = PropAddr(obj, p, offset);
|
|
|
|
defseg = proptable;
|
|
|
|
/* If the object doesn't have property p, see if there's a
|
|
default value.
|
|
*/
|
|
if (!pa)
|
|
{
|
|
if (offset) goto NoMorePropMatches;
|
|
|
|
if (getpropaddress) /* if an &value */
|
|
g = 0;
|
|
else
|
|
g = PeekWord(p * 2 + 2);
|
|
}
|
|
|
|
else
|
|
{
|
|
/* Property is a value... */
|
|
if (Peek(pa+1) < PROP_ROUTINE)
|
|
{
|
|
if (getaddress || (int)Peek(pa+1) < n || n<=0)
|
|
{
|
|
#if defined (DEBUGGER)
|
|
if (n!=1)
|
|
CheckinRange(n, (int)Peek(pa+1), "property element");
|
|
#endif
|
|
g = 0;
|
|
}
|
|
else
|
|
g = PeekWord(pa + n * 2);
|
|
}
|
|
|
|
/* ...or a property routine */
|
|
else
|
|
{
|
|
/* Check if this is an additive property */
|
|
defseg = proptable;
|
|
if (Peek(2 + Peek(0)*2 + p)&ADDITIVE_FLAG)
|
|
isadditive = true;
|
|
|
|
/* If an &value, return the address of the
|
|
property routine.
|
|
*/
|
|
if (getpropaddress)
|
|
{
|
|
g = PeekWord(pa+2);
|
|
goto NoMorePropMatches;
|
|
}
|
|
else
|
|
{
|
|
#if defined (DEBUGGER)
|
|
if (debug_eval)
|
|
{
|
|
debug_eval_error = true;
|
|
DebugMessageBox("Expression Error",
|
|
"Property routine illegal in watch/assignment");
|
|
defseg = gameseg;
|
|
return 0;
|
|
}
|
|
#endif
|
|
/* If not a complex property such as
|
|
before or after:
|
|
*/
|
|
if ((game_version>=22 && (Peek(2 + Peek(0)*2 + p)&COMPLEX_FLAG)==0) || (game_version<22 && p!=before && p!=after))
|
|
{
|
|
ret = 1;
|
|
returnaddr = codeptr;
|
|
|
|
/* Check to see if this is a valid tail-recursive return... */
|
|
if (tail_recursion==TAIL_RECURSION_PROPERTY && MEM(codeptr)==EOL_T)
|
|
{
|
|
PassLocals(0);
|
|
tail_recursion_addr = (long)PeekWord(pa+2)*address_scale;
|
|
return 0;
|
|
}
|
|
/* ...but if we're not immediately followed by and end-of-line marker,
|
|
or another property value, cancel the pending tail-recursion
|
|
*/
|
|
else if (MEM(codeptr)!=DECIMAL_T)
|
|
{
|
|
tail_recursion = 0;
|
|
}
|
|
|
|
for (i=0; i<MAXLOCALS; i++)
|
|
templocals[i] = var[MAXGLOBALS+i];
|
|
|
|
PassLocals(0);
|
|
|
|
SetStackFrame(stack_depth+1, RUNROUTINE_BLOCK, 0, 0);
|
|
#if defined (DEBUGGER)
|
|
tempdbnest = dbnest;
|
|
DebugRunRoutine((long)PeekWord(pa+2)*address_scale);
|
|
dbnest = tempdbnest;
|
|
#else
|
|
RunRoutine((long)PeekWord(pa+2)*address_scale);
|
|
#endif
|
|
|
|
retflag = 0;
|
|
codeptr = returnaddr;
|
|
g = ret;
|
|
ret = 0;
|
|
}
|
|
|
|
/* Complex property: */
|
|
else
|
|
{
|
|
for (i=0; i<MAXLOCALS; i++)
|
|
templocals[i] = var[MAXGLOBALS+i];
|
|
|
|
inprop = (long)PeekWord(pa + 2)*address_scale;
|
|
#ifdef DEBUGGER
|
|
orig_inprop = inprop;
|
|
#endif
|
|
defseg = gameseg;
|
|
while (Peek(inprop)!=CLOSE_BRACE_T)
|
|
{
|
|
returnaddr = codeptr;
|
|
|
|
codeptr = inprop;
|
|
objonly = false;
|
|
objtype = GetValue();
|
|
inprop = codeptr;
|
|
codeptr = returnaddr;
|
|
|
|
flag = 0;
|
|
|
|
/* If only an object (or other variable) is
|
|
given, and no verbroutine
|
|
*/
|
|
if (Peek(inprop)==JUMP_T)
|
|
{
|
|
objonly = true;
|
|
if (!gotone && obj==objtype) flag = 1;
|
|
}
|
|
|
|
/* Otherwise, one or more verbroutines are
|
|
specified
|
|
*/
|
|
else
|
|
{
|
|
while (Peek(inprop)!=JUMP_T)
|
|
{
|
|
if (PeekWord(inprop+1)==(unsigned int)var[verbroutine] ||
|
|
|
|
/* This is necessary because of the awkward way the pre-v2.2
|
|
differentiated non-verbroutine blocks, i.e., with Parse
|
|
*/
|
|
((game_version<22) && PeekWord(inprop+1)==(unsigned int)parseaddr && !gotone))
|
|
{
|
|
if (obj==objtype) flag = 1;
|
|
}
|
|
inprop += 3;
|
|
}
|
|
}
|
|
|
|
if (flag==1)
|
|
{
|
|
gotone = true;
|
|
ret = 1;
|
|
returnaddr = codeptr;
|
|
|
|
SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0);
|
|
|
|
PassLocals(0);
|
|
#if defined (DEBUGGER)
|
|
/* Prevent premature stopping */
|
|
if (debugger_step_over && !debugger_finish)
|
|
debugger_run = true;
|
|
|
|
if (IsBreakpoint(orig_inprop))
|
|
complex_prop_breakpoint = true;
|
|
|
|
Common::sprintf_s(debug_line, "Calling: %s.%s", objectname[obj], propertyname[p]);
|
|
trace_complex_prop_routine = true;
|
|
|
|
tempdbnest = dbnest;
|
|
DebugRunRoutine(inprop+3);
|
|
dbnest = tempdbnest;
|
|
#else
|
|
RunRoutine(inprop+3);
|
|
#endif
|
|
retflag = 0;
|
|
codeptr = returnaddr;
|
|
g = ret;
|
|
ret = 0;
|
|
}
|
|
|
|
/* The following used to read "if (!flag || objonly..."
|
|
meaning that any non-verbroutine related routines
|
|
would fall through regardless of whether they returned
|
|
true or false. I don't recall the rationale for this,
|
|
and have therefore removed it.
|
|
*/
|
|
if (!flag || (objonly && !g) || ((game_version<22) && PeekWord(inprop-2)==(unsigned int)parseaddr))
|
|
inprop = (long)PeekWord(inprop+1)*address_scale;
|
|
else break;
|
|
}
|
|
}
|
|
|
|
for (i=0; i<MAXLOCALS; i++)
|
|
var[MAXGLOBALS+i] = templocals[i];
|
|
|
|
if (isadditive && !g)
|
|
{
|
|
offset = pa + 4;
|
|
gotone = false;
|
|
goto GetNextProp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NoMorePropMatches:
|
|
|
|
defseg = gameseg;
|
|
|
|
var[self] = tempself;
|
|
inexpr = tempinexpr;
|
|
stack_depth = temp_stack_depth;
|
|
|
|
return g;
|
|
}
|
|
|
|
int Hugo::GrandParent(int obj) {
|
|
int nextobj;
|
|
|
|
#if defined (DEBUGGER)
|
|
if (!CheckObjectRange(obj)) return 0;
|
|
#endif
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
defseg = objtable;
|
|
while ((nextobj = PeekWord(2 + obj*object_size + object_size-8)) != 0)
|
|
obj = nextobj;
|
|
defseg = gameseg;
|
|
|
|
return obj;
|
|
}
|
|
|
|
void Hugo::MoveObj(int obj, int p) {
|
|
int oldparent, prevobj, s;
|
|
unsigned int objaddr, parentaddr, lastobjaddr;
|
|
|
|
if (obj==p) return;
|
|
if (obj<0 || obj>=objects) return;
|
|
|
|
oldparent = Parent(obj);
|
|
/* if (oldparent==p) return; */
|
|
|
|
objaddr = 2 + obj*object_size;
|
|
|
|
/* First, detach the object from its old parent and siblings... */
|
|
|
|
prevobj = Elder(obj);
|
|
s = Sibling(obj);
|
|
defseg = objtable;
|
|
if (prevobj) /* sibling */
|
|
PokeWord(2 + prevobj*object_size + object_size-6, s);
|
|
else /* child */
|
|
PokeWord(2 + oldparent*object_size + object_size-4, s);
|
|
|
|
|
|
/* Then move it to the new parent... */
|
|
|
|
defseg = objtable;
|
|
PokeWord(objaddr + object_size-8, p); /* new parent */
|
|
PokeWord(objaddr + object_size-6, 0); /* erase old sibling */
|
|
|
|
/* Only operate on the new parent if it isn't object 0 */
|
|
if (p!=0)
|
|
{
|
|
|
|
/* Object is sole child, or... */
|
|
if (Child(p)==0)
|
|
{
|
|
parentaddr = 2 + p*object_size;
|
|
defseg = objtable;
|
|
PokeWord(parentaddr + object_size-4, obj);
|
|
}
|
|
|
|
/* ...object is next sibling. */
|
|
else
|
|
{
|
|
lastobjaddr = 2 + Youngest(p)*object_size;
|
|
defseg = objtable;
|
|
PokeWord(lastobjaddr + object_size-6, obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *Hugo::Name(int obj) {
|
|
int p;
|
|
|
|
p = GetProp(obj, 0, 1, 0);
|
|
|
|
if (p)
|
|
return GetWord((unsigned int)p);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
int Hugo::Parent(int obj) {
|
|
int p;
|
|
|
|
#if defined (DEBUGGER)
|
|
if (!CheckObjectRange(obj)) return 0;
|
|
#endif
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
defseg = objtable;
|
|
|
|
p = PeekWord(2 + obj*object_size + object_size-8);
|
|
|
|
defseg = gameseg;
|
|
|
|
return p;
|
|
}
|
|
|
|
unsigned int Hugo::PropAddr(int obj, int p, unsigned int offset) {
|
|
unsigned char c;
|
|
int proplen;
|
|
unsigned int ptr;
|
|
|
|
#if defined (DEBUGGER)
|
|
/* Don't check any non-existent display object (-1) */
|
|
if (p!=-1) CheckinRange(p, properties, "property");
|
|
CheckObjectRange(obj);
|
|
#endif
|
|
/* This way either -1 (the non-existent display object) or a too-high
|
|
object will fail
|
|
*/
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
defseg = objtable;
|
|
|
|
/* Position in the property table...
|
|
|
|
i.e., ptr = PeekWord(2 + obj*object_size + (object_size-2));
|
|
*/
|
|
ptr = PeekWord(object_size*(obj+1));
|
|
|
|
/* ...unless a position has already been given */
|
|
if (offset) ptr = offset;
|
|
|
|
defseg = proptable;
|
|
|
|
c = Peek(ptr);
|
|
while (c != PROP_END && c != (unsigned char)p)
|
|
{
|
|
proplen = Peek(ptr + 1);
|
|
|
|
/* Property routine address is 1 word */
|
|
if (proplen==PROP_ROUTINE) proplen = 1;
|
|
|
|
ptr += proplen * 2 + 2;
|
|
c = Peek(ptr);
|
|
}
|
|
|
|
defseg = gameseg;
|
|
|
|
if (c==PROP_END)
|
|
return 0;
|
|
else
|
|
return ptr;
|
|
}
|
|
|
|
void Hugo::PutAttributes(int obj, unsigned long a, int attribute_set) {
|
|
unsigned int lword, hword;
|
|
|
|
hword = (unsigned int)(a/65536L);
|
|
lword = (unsigned int)(a%65536L);
|
|
|
|
defseg = objtable;
|
|
|
|
PokeWord(2 + obj*object_size + attribute_set*4, lword);
|
|
PokeWord(2 + obj*object_size + attribute_set*4 + 2, hword);
|
|
|
|
defseg = gameseg;
|
|
}
|
|
|
|
void Hugo::SetAttribute(int obj, int attr, int c) {
|
|
unsigned long a, mask;
|
|
|
|
#if defined (DEBUGGER)
|
|
if (!CheckObjectRange(obj)) return;
|
|
#endif
|
|
if (obj<0 || obj>=objects) return;
|
|
|
|
a = GetAttributes(obj, attr/32);
|
|
|
|
mask = 1L<<(long)(attr%32);
|
|
|
|
if (c==1)
|
|
a = a | mask;
|
|
else
|
|
{
|
|
if (a & mask)
|
|
a = a ^ mask;
|
|
}
|
|
|
|
PutAttributes(obj, a, attr/32);
|
|
}
|
|
|
|
int Hugo::Sibling(int obj) {
|
|
int s;
|
|
|
|
#if defined (DEBUGGER)
|
|
if (!CheckObjectRange(obj)) return 0;
|
|
#endif
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
defseg = objtable;
|
|
|
|
s = PeekWord(2+ obj*object_size + object_size-6);
|
|
|
|
defseg = gameseg;
|
|
|
|
return s;
|
|
}
|
|
|
|
int Hugo::TestAttribute(int obj, int attr, int nattr) {
|
|
unsigned long a, mask, ta;
|
|
|
|
#if defined (DEBUGGER)
|
|
if (!CheckObjectRange(obj)) return 0;
|
|
#endif
|
|
if (obj<0 || obj>=objects) return 0;
|
|
|
|
a = GetAttributes(obj, attr/32);
|
|
|
|
mask = 1L<<(attr%32);
|
|
|
|
ta = a & mask;
|
|
if (ta) ta = 1;
|
|
|
|
if (nattr) ta = ta ^ 1;
|
|
|
|
return (int)ta;
|
|
}
|
|
|
|
int Hugo::Youngest(int obj) {
|
|
int nextobj;
|
|
|
|
if (Child(obj)==0) return 0;
|
|
|
|
nextobj = Child(obj);
|
|
|
|
while (Sibling(nextobj))
|
|
nextobj = Sibling(nextobj);
|
|
|
|
return nextobj;
|
|
}
|
|
|
|
} // End of namespace Hugo
|
|
} // End of namespace Glk
|