scummvm/engines/glk/hugo/heobject.cpp
2022-10-23 22:46:19 +02:00

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