mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-10 21:03:31 +00:00
1064 lines
28 KiB
C++
1064 lines
28 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/agt/agility.h"
|
|
#include "glk/agt/interp.h"
|
|
#include "glk/agt/exec.h"
|
|
|
|
namespace Glk {
|
|
namespace AGT {
|
|
|
|
/* */
|
|
/* This module contains most of the routines that implement the */
|
|
/* "physics" of the AGT world, including the scope routines. */
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/* Functions for manipulating parse_recs */
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/* Make artificial parse record for an object */
|
|
parse_rec *make_parserec(int obj, parse_rec *rec) {
|
|
if (rec == nullptr) rec = (parse_rec *)rmalloc(sizeof(parse_rec));
|
|
rec->obj = obj;
|
|
rec->info = D_NOUN;
|
|
rec->noun = it_name(obj);
|
|
rec->adj = it_adj(obj);
|
|
rec->num = 0;
|
|
return rec;
|
|
}
|
|
|
|
/* This is used by the parser to initialize a blank parse_rec */
|
|
void tmpobj(parse_rec *objrec) {
|
|
objrec->info = D_NOUN;
|
|
objrec->num = 0;
|
|
objrec->noun = objrec->adj = 0;
|
|
objrec->obj = 0;
|
|
}
|
|
|
|
parse_rec *copy_parserec(parse_rec *rec) {
|
|
parse_rec *newrec;
|
|
if (rec == nullptr) return nullptr;
|
|
newrec = (parse_rec *)rmalloc(sizeof(parse_rec));
|
|
memcpy(newrec, rec, sizeof(parse_rec));
|
|
return newrec;
|
|
}
|
|
|
|
void free_all_parserec(void) {
|
|
rfree(actor_rec);
|
|
rfree(dobj_rec);
|
|
rfree(iobj_rec);
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/* Functions for doing basic manipulation of items and extracting */
|
|
/* nformation about these items */
|
|
/* (often used to blackbox the difference between nouns and creatures) */
|
|
|
|
|
|
|
|
rbool matchclass(int obj, int oclass) {
|
|
int i;
|
|
if (oclass == 0) return 0;
|
|
for (i = obj; i != oclass && i != 0; i = it_class(i));
|
|
return i == oclass;
|
|
}
|
|
|
|
|
|
/* Functions for getting bascic information about items */
|
|
|
|
static const char *it_sdesc(int item) {
|
|
if (tnoun(item)) return noun[item - first_noun].shortdesc;
|
|
if (tcreat(item)) return creature[item - first_creat].shortdesc;
|
|
if (item < 0) return dict[-item];
|
|
return nullptr;
|
|
}
|
|
|
|
rbool it_possess(int item) {
|
|
int l;
|
|
|
|
l = it_loc(item);
|
|
return (l == 1 || l == 1000);
|
|
}
|
|
|
|
rbool it_proper(int item) {
|
|
if (tcreat(item))
|
|
return (!PURE_PROPER) || creature[item - first_creat].proper;
|
|
if (tnoun(item))
|
|
return noun[item - first_noun].proper;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static rbool invischeck(const char *s) {
|
|
while (rspace(*s)) s++;
|
|
return strncasecmp(s, "INVISIBLE", 9) == 0;
|
|
}
|
|
|
|
|
|
static rbool it_invisible(int item, rbool sdesc_flag) {
|
|
if (sdesc_flag)
|
|
return invischeck(it_sdesc(item));
|
|
else {
|
|
char *s;
|
|
rbool tmp;
|
|
|
|
if (it_name(item) == 0 && it_adj(item) == 0) return 1;
|
|
s = objname(item); /* Must remember to rfree s before exiting */
|
|
tmp = invischeck(s);
|
|
rfree(s);
|
|
return tmp;
|
|
}
|
|
}
|
|
|
|
|
|
static rbool it_appears_empty(int item) {
|
|
int i;
|
|
int sdesc_flag;
|
|
|
|
if (item < 0) return 1;
|
|
sdesc_flag = !player_has(item);
|
|
|
|
contloop(i, item)
|
|
if (!it_invisible(i, sdesc_flag)) return 0;
|
|
return 1;
|
|
}
|
|
|
|
/* We classify something as a weapon if it kills something. */
|
|
rbool it_isweapon(int objnum) {
|
|
int i;
|
|
|
|
creatloop(i)
|
|
if (matchclass(objnum, creature[i].weapon)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* This used to be a macro like troom, tnoun, and tcreat, but it
|
|
got too complicated and isn't used in time-critical areas,
|
|
anyhow */
|
|
|
|
rbool it_door(int obj, word nword) {
|
|
if (aver >= AGX00) return 0; /* No doors under Magx */
|
|
if (tdoor(obj)) return 1; /* The basic door */
|
|
if (it_loc(obj) == loc + first_room) return 0;
|
|
return (nword == ext_code[wdoor]);
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/* Routines that manipulate the linked lists representing containment */
|
|
/* information */
|
|
|
|
static void set_contents(int p, int newval) {
|
|
if (troom(p)) room[p - first_room].contents = newval;
|
|
else if (p == 1) player_contents = newval;
|
|
else if (p == 1000) player_worn = newval;
|
|
else if (tnoun(p)) noun[p - first_noun].contents = newval;
|
|
else if (tcreat(p)) creature[p - first_creat].contents = newval;
|
|
else {
|
|
writeln("INT ERR: Invalid object heading chain.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void set_next(int p, int newval) {
|
|
if (tnoun(p)) noun[p - first_noun].next = newval;
|
|
else if (tcreat(p)) creature[p - first_creat].next = newval;
|
|
else {
|
|
writeln("INT ERR: Invalid object in chain.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void add_object(int loc_, int item) {
|
|
int p, q;
|
|
|
|
set_next(item, 0);
|
|
|
|
if (loc_ == 0) return;
|
|
p = it_contents(loc_);
|
|
|
|
if (p == 0 || p > item) {
|
|
set_contents(loc_, item);
|
|
set_next(item, p);
|
|
} else { /* Figure out where to put the item */
|
|
do {
|
|
q = p;
|
|
p = it_next(p);
|
|
} while (p != 0 && p < item);
|
|
|
|
set_next(q, item);
|
|
set_next(item, p);
|
|
}
|
|
}
|
|
|
|
|
|
static void set_location(int item, int newloc)
|
|
/* This routine assumes item is either a noun or a creature */
|
|
{
|
|
int p, q;
|
|
|
|
p = it_loc(item);
|
|
if (p != 0) { /* Fix .next values */
|
|
q = it_contents(p);
|
|
if (q == item) set_contents(p, it_next(item));
|
|
else {
|
|
while (q != item && q != 0) {
|
|
p = q;
|
|
q = it_next(p);
|
|
}
|
|
assert(q != 0); /* This would mean the list structure was corrupted */
|
|
set_next(p, it_next(item));
|
|
}
|
|
}
|
|
/* We've unlinked it from the list at this point. */
|
|
|
|
if (tnoun(item))
|
|
noun[item - first_noun].location = newloc;
|
|
else if (tcreat(item))
|
|
creature[item - first_creat].location = newloc;
|
|
|
|
add_object(newloc, item);
|
|
}
|
|
|
|
|
|
void it_reposition(int item, int newloc, rbool save_pos) {
|
|
integer i;
|
|
|
|
if (tnoun(item)) {
|
|
if (player_has(item)) totwt -= noun[item - first_noun].weight;
|
|
if (it_loc(item) == 1) totsize -= noun[item - first_noun].size;
|
|
|
|
/* Set position to NULL */
|
|
if (!save_pos) {
|
|
noun[item - first_noun].pos_prep = 0;
|
|
noun[item - first_noun].pos_name = 0;
|
|
noun[item - first_noun].nearby_noun = 0;
|
|
noun[item - first_noun].position = nullptr;
|
|
#if 0 /* I think this was wrong, so I'm commenting it out. */
|
|
noun[item - first_noun].initdesc = 0;
|
|
#endif
|
|
}
|
|
|
|
set_location(item, newloc);
|
|
|
|
if (player_has(item)) {
|
|
totwt += noun[item - first_noun].weight;
|
|
if (noun[item - first_noun].win)
|
|
winflag = 1;
|
|
}
|
|
if (it_loc(item) == 1) /* only things you are carrying directly count vs.
|
|
size limit. */
|
|
totsize += noun[item - first_noun].size;
|
|
} else if (tcreat(item)) {
|
|
if (newloc == 0) {
|
|
creature[item - first_creat].timecounter = 0; /* Reset attack counter */
|
|
creature[item - first_creat].counter = 0;
|
|
}
|
|
set_location(item, newloc);
|
|
}
|
|
|
|
nounloop(i)
|
|
if (noun[i].nearby_noun == item) {
|
|
noun[i].nearby_noun = 0;
|
|
noun[i].pos_prep = 0;
|
|
noun[i].pos_name = 0;
|
|
noun[i].position = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/* Routines to deal with size and weight */
|
|
|
|
static long contsize(integer obj) {
|
|
int i;
|
|
long net;
|
|
|
|
net = 0;
|
|
contloop(i, obj) {
|
|
if (tnoun(i))
|
|
net += noun[i - first_noun].size;
|
|
if (aver < AGX00) /* Under Magx, size isn't recursive */
|
|
net += contsize(i);
|
|
}
|
|
return net;
|
|
}
|
|
|
|
static long contweight(integer obj) {
|
|
int i;
|
|
long net;
|
|
|
|
net = 0;
|
|
contloop(i, obj) {
|
|
if (tnoun(i))
|
|
net += noun[i - first_noun].weight;
|
|
net += contweight(i);
|
|
}
|
|
return net;
|
|
}
|
|
|
|
rbool is_within(integer obj1, integer obj2, rbool stop_if_closed)
|
|
/* True if obj1 is contained in obj2 */
|
|
{
|
|
int i;
|
|
long cnt;
|
|
|
|
for (i = obj1, cnt = 0;
|
|
i != obj2 && i >= maxroom && i != 1000 && cnt < 40000L;
|
|
cnt++) {
|
|
i = it_loc(i);
|
|
if (stop_if_closed && !it_open(i)) break;
|
|
}
|
|
if (cnt >= 40000L) {
|
|
/* writeln("GAME ERROR: Loop in object tree.");*/
|
|
return 0;
|
|
}
|
|
if (i == obj2) return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int check_fit(int obj1, int obj2)
|
|
/* Does obj1 fit inside obj2? Return one of the FIT_... values
|
|
defined in exec.h */
|
|
{
|
|
int size, weight;
|
|
long net;
|
|
|
|
assert(tnoun(obj1)); /* This should have been checked earlier */
|
|
if (obj2 == 1000) obj2 = 1;
|
|
|
|
if (obj2 == 1) size = weight = 100;
|
|
else {
|
|
assert(tnoun(obj2)); /* check_fit shouldn't be called otherwise */
|
|
size = noun[obj2 - first_noun].size;
|
|
weight = noun[obj2 - first_noun].weight;
|
|
}
|
|
|
|
/* Weight */
|
|
if (obj2 == 1 || (aver > AGTME15 && aver < AGX00)) {
|
|
/* Pre-1.56 interpreters forgot to check this;
|
|
Magx deliberatly *doesn't* check this */
|
|
|
|
net = noun[obj1 - first_noun].weight;
|
|
if (aver >= AGX00) net += contweight(obj1);
|
|
if (net > weight) return FIT_WEIGHT;
|
|
|
|
if (obj2 == 1) {
|
|
if (is_within(obj1, 1, 0) || is_within(obj1, 1000, 0)) net = 0;
|
|
net += contweight(1);
|
|
if (aver >= AGX00)
|
|
net += contweight(1000);
|
|
if (!PURE_SIZE) net = 0;
|
|
} else {
|
|
if (is_within(obj1, obj2, 0)) net = 0; /* Avoid double-counting */
|
|
net += contweight(obj2); /* Net size of contents of obj2 */
|
|
}
|
|
if (net > weight) return FIT_NETWEIGHT;
|
|
}
|
|
|
|
net = noun[obj1 - first_noun].size;
|
|
if (net > size) return FIT_SIZE;
|
|
|
|
if (obj2 == 1 && !PURE_SIZE) return FIT_OK;
|
|
|
|
if (obj2 == 1 || aver > AGTME15) {
|
|
/* Pre-ME/1.56 interpreters didn't check this except for the player's
|
|
inventory */
|
|
if (it_loc(obj1) == obj2
|
|
|| (aver < AGX00 && is_within(obj1, obj2, 0)))
|
|
net = 0; /* Avoid double-counting */
|
|
net += contsize(obj2); /* Net size of contents of obj2 */
|
|
if (net > size) return FIT_NETSIZE;
|
|
}
|
|
|
|
return FIT_OK;
|
|
}
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/* Scope and visibility routines */
|
|
|
|
integer it_room(int item) {
|
|
int tmploc;
|
|
long cnt;
|
|
|
|
cnt = 0;
|
|
while (!troom(item)) {
|
|
tmploc = item;
|
|
if (item == 0) return 0;
|
|
if (item == 1 || item == 1000) item = loc;
|
|
else item = it_loc(item);
|
|
if (item == tmploc || ++cnt >= 40000L) {
|
|
/* writeln("GAME ERROR: Loop in object tree."); */
|
|
return 0;
|
|
}
|
|
}
|
|
return item;
|
|
}
|
|
|
|
rbool player_has(int item) {
|
|
return is_within(item, 1, 0) || is_within(item, 1000, 0);
|
|
}
|
|
|
|
|
|
rbool in_scope(int item)
|
|
/* strictly speaking, visible actually checks scope; this routine
|
|
determines if an object would be in scope if there were no light
|
|
problems. */
|
|
{
|
|
int curloc;
|
|
int tmp;
|
|
long cnt;
|
|
|
|
if (it_isglobal(item)) return 1; /* Global objects always in scope. */
|
|
|
|
/* Flag objects in scope if their associated flag is set. */
|
|
tmp = it_flagnum(item);
|
|
if (tmp &&
|
|
(room[loc].flag_noun_bits & (1L << (tmp - 1)))) return 1;
|
|
|
|
curloc = it_loc(item); /* Should work for nouns or creatures */
|
|
cnt = 0;
|
|
while (curloc > maxroom && curloc != 1000 && it_open(curloc)) {
|
|
int tmploc;
|
|
tmploc = it_loc(curloc);
|
|
if (tmploc == curloc || ++cnt >= 40000L) {
|
|
/* writeln("GAME ERROR: Loop in the object tree."); */
|
|
return 0;
|
|
} else curloc = tmploc;
|
|
}
|
|
if (curloc == 1 || curloc == 1000 || curloc == loc + first_room) return 1;
|
|
else return 0;
|
|
}
|
|
|
|
|
|
static int good_light(int obj, int roomlight, rbool active)
|
|
/* obj is a noun number */
|
|
/* If active is false, we don't care if the light is actually turned
|
|
on is the valid light */
|
|
{
|
|
if (roomlight == 1 && !noun[obj].light)
|
|
return 0; /* obj is not a light source */
|
|
if (roomlight > 1) {
|
|
if (!matchclass(first_noun + obj, roomlight))
|
|
return 0; /* Not the correct light */
|
|
else return 1; /* The correct light _always_ illuminates the room */
|
|
}
|
|
if (!active) return 1;
|
|
/* Now need to determine if obj is actually providing light */
|
|
if (!noun[obj].on)
|
|
return 0; /* Light source is off or extinguished */
|
|
return 1;
|
|
}
|
|
|
|
int lightcheck(int parent, int roomlight, rbool active)
|
|
/* This checks to see if anything contained in parent is a valid
|
|
room light */
|
|
/* active=1 means that we only want active lights;
|
|
active=0 indicates that extinguished light sources are okay. */
|
|
{
|
|
int i;
|
|
|
|
contloop(i, parent) {
|
|
if (tnoun(i) && good_light(i - first_noun, roomlight, active)) return 1;
|
|
if (it_open(i) && lightcheck(i, roomlight, active))
|
|
return 1; /* Check children */
|
|
}
|
|
return 0;
|
|
/*
|
|
nounloop(i)
|
|
if (good_light(i,room[loc].light) && in_scope(i+first_noun))
|
|
return 1;
|
|
return 0;*/
|
|
}
|
|
|
|
|
|
rbool islit(void) {
|
|
if (room[loc].light == 0) return 1;
|
|
if (lightcheck(loc + first_room, room[loc].light, 1)) return 1;
|
|
if (lightcheck(1, room[loc].light, 1)) return 1; /* Player carried light */
|
|
if (lightcheck(1000, room[loc].light, 1)) return 1; /* Worn light */
|
|
return 0;
|
|
}
|
|
|
|
/* Is item visible to player? */
|
|
/* visible only works for "normal" items; if the object could
|
|
be a virtual object (i.e. with negative item number), then use
|
|
gen_visible() below */
|
|
rbool visible(int item) {
|
|
assert(item >= 0);
|
|
if (islit())
|
|
return in_scope(item);
|
|
else
|
|
return player_has(item);
|
|
}
|
|
|
|
rbool genvisible(parse_rec *dobj_) {
|
|
int i;
|
|
|
|
if (dobj_->obj > 0) return visible(dobj_->obj);
|
|
|
|
if (dobj_->info == D_INTERN) {
|
|
if (dobj_->obj != -ext_code[wdoor]) return 1;
|
|
return islit(); /* If item is a is a door */
|
|
}
|
|
if (dobj_->info == D_GLOBAL || dobj_->info == D_NUM) return 1;
|
|
if (dobj_->info == D_FLAG) {
|
|
for (i = 0; i < MAX_FLAG_NOUN; i++) /* Flag nouns */
|
|
if (flag_noun[i] != 0 && dobj_->obj == -flag_noun[i]
|
|
&& (room[loc].flag_noun_bits & (1L << i)) != 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
if (dobj_->info == D_PIX) {
|
|
for (i = 0; i < MAX_PIX; i++) /* PIX names */
|
|
if (pix_name[i] != 0 && dobj_->obj == -pix_name[i] &&
|
|
(room[loc].PIX_bits & (1L << i)) != 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
fatal("INTERNAL ERROR: Invalid gen_visible type.");
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Need to find a noun related to w */
|
|
/* If there is an object with name w in scope, it returns 0
|
|
(since the object will have already been added to the menu).
|
|
if there are none, it returns the first object with name w. */
|
|
static integer find_related(word w) {
|
|
int i;
|
|
int item;
|
|
|
|
if (w == 0) return 0;
|
|
item = 0;
|
|
nounloop(i)
|
|
if (noun[i].name == w) {
|
|
if (visible(i + first_noun)) return i + first_noun;
|
|
else if (item == 0) item = i + first_noun;
|
|
}
|
|
creatloop(i)
|
|
if (creature[i].name == w) {
|
|
if (visible(i + first_creat)) return i + first_creat;
|
|
else if (item == 0) item = i + first_creat;
|
|
}
|
|
return item;
|
|
}
|
|
|
|
|
|
static void add_to_scope(integer item) {
|
|
integer i;
|
|
|
|
if (tnoun(item)) {
|
|
noun[item - first_noun].scope = 1;
|
|
i = find_related(noun[item - first_noun].related_name);
|
|
if (i != 0) {
|
|
if (tnoun(i)) noun[i - first_noun].scope = 1;
|
|
else if (tcreat(i)) creature[i - first_creat].scope = 1;
|
|
}
|
|
} else if (tcreat(item)) creature[item - first_creat].scope = 1;
|
|
if (item == 1 || item == 1000 || troom(item) || it_open(item))
|
|
contloop(i, item)
|
|
add_to_scope(i);
|
|
}
|
|
|
|
|
|
void compute_scope(void) {
|
|
int i;
|
|
uint32 rflag;
|
|
|
|
nounloop(i) noun[i].scope = 0;
|
|
creatloop(i) creature[i].scope = 0;
|
|
add_to_scope(1);
|
|
add_to_scope(1000);
|
|
add_to_scope(loc + first_room);
|
|
rflag = room[loc].flag_noun_bits;
|
|
nounloop(i)
|
|
if (noun[i].isglobal ||
|
|
(noun[i].flagnum && (rflag & (1L << (noun[i].flagnum - 1)))))
|
|
add_to_scope(i + first_noun);
|
|
creatloop(i)
|
|
if (creature[i].isglobal ||
|
|
(creature[i].flagnum && (rflag & (1L << (creature[i].flagnum - 1)))))
|
|
add_to_scope(i + first_creat);
|
|
}
|
|
|
|
void compute_seen(void) {
|
|
int i;
|
|
|
|
compute_scope();
|
|
nounloop(i)
|
|
noun[i].seen = noun[i].seen || noun[i].scope;
|
|
creatloop(i)
|
|
creature[i].seen = creature[i].seen || creature[i].scope;
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Routines to compute the score */
|
|
|
|
void recompute_score(void) {
|
|
int obj;
|
|
|
|
tscore -= objscore;
|
|
objscore = 0;
|
|
nounloop(obj)
|
|
if (noun[obj].points && !noun[obj].unused &&
|
|
(visible(obj + first_noun)
|
|
|| is_within(obj + first_noun, treas_room, 0)))
|
|
objscore += noun[obj].points;
|
|
creatloop(obj)
|
|
if (!creature[obj].unused && creature[obj].points
|
|
&& visible(obj + first_creat))
|
|
objscore += creature[obj].points;
|
|
tscore += objscore;
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* Menu Noun section: routines to get a list of 'relevant' nouns for */
|
|
/* the menuing system. They're here because they belong next to the */
|
|
/* scope routines, above */
|
|
|
|
static int *nlist, nleng; /* These are really local variables */
|
|
|
|
static void add_mnoun(int n) {
|
|
nlist = (int *)rrealloc(nlist, (nleng + 2) * sizeof(int));
|
|
nlist[nleng] = n;
|
|
nlist[++nleng] = 0;
|
|
}
|
|
|
|
|
|
/* This adds mitem and everything it contains */
|
|
static void add_mitem(int item) {
|
|
integer i;
|
|
|
|
if (tnoun(item) || tcreat(item)) add_mnoun(item);
|
|
if (item == 1 || item == 1000 || troom(item) || it_open(item))
|
|
contloop(i, item)
|
|
add_mitem(i);
|
|
/* Need to check related nouns */
|
|
if (tnoun(item)) {
|
|
i = find_related(noun[item - first_noun].related_name);
|
|
if (i != 0) add_mnoun(i);
|
|
}
|
|
}
|
|
|
|
|
|
static word getword(int item, int n)
|
|
/* Gets nth word associated with item */
|
|
{
|
|
if (n == 1) {
|
|
if (item < 0) return -item;
|
|
else if (tnoun(item)) return noun[item - first_noun].adj;
|
|
else if (tcreat(item)) return creature[item - first_creat].adj;
|
|
}
|
|
if (n == 2)
|
|
if (tnoun(item) || tcreat(item)) return it_name(item);
|
|
return 0;
|
|
}
|
|
|
|
static int cmp_nouns(const void *a, const void *b)
|
|
/* *a, *b are object numbers; need alphabetic sort.*/
|
|
{
|
|
word wa, wb;
|
|
int cmp;
|
|
|
|
wa = getword(*((const int *)a), 1);
|
|
wb = getword(*((const int *)b), 1);
|
|
cmp = strcmp(dict[wa], dict[wb]);
|
|
if (cmp != 0) return cmp;
|
|
wa = getword(*(const int *)a, 2);
|
|
wb = getword(*(const int *)b, 2);
|
|
return strcmp(dict[wa], dict[wb]);
|
|
}
|
|
|
|
int *get_nouns(void)
|
|
/* This returns the list of all objects that should show up on the menu */
|
|
/* The list should be 0 terminated and needs to be sorted */
|
|
{
|
|
int i;
|
|
uint32 rflag;
|
|
|
|
nlist = (int *)rmalloc(sizeof(int));
|
|
nlist[0] = 0;
|
|
nleng = 0;
|
|
|
|
for (i = 0; i < numglobal; i++)
|
|
add_mnoun(-globalnoun[i]);
|
|
for (i = 0; i < MAX_FLAG_NOUN; i++)
|
|
if (room[loc].flag_noun_bits & (1L << i))
|
|
add_mnoun(-flag_noun[i]);
|
|
add_mitem(1);
|
|
add_mitem(1000);
|
|
add_mitem(loc + first_room);
|
|
rflag = room[loc].flag_noun_bits;
|
|
nounloop(i)
|
|
if (noun[i].isglobal ||
|
|
(noun[i].flagnum && (rflag & (1L << (noun[i].flagnum - 1)))))
|
|
add_mitem(i + first_noun);
|
|
creatloop(i)
|
|
if (creature[i].isglobal ||
|
|
(creature[i].flagnum && (rflag & (1L << (creature[i].flagnum - 1)))))
|
|
add_mitem(i + first_creat);
|
|
qsort(nlist, nleng, sizeof(int), cmp_nouns);
|
|
return nlist;
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
/* goto_room, the basic primitive used to move the player around */
|
|
|
|
void goto_room(int newroom) {
|
|
int i, j;
|
|
|
|
/* Move group members in old room to new room */
|
|
safecontloop(i, j, loc + first_room)
|
|
if (it_group(i))
|
|
it_move(i, newroom + first_room);
|
|
|
|
#if 0 /* -- this has been moved to v_go*/
|
|
if (loc != newroom)
|
|
oldloc = loc; /* Save old location for NO_BLOCK_HOSTILE purposes */
|
|
#endif
|
|
loc = newroom;
|
|
if (loc != newroom) oldloc = loc; /* No backtracking unless v_go allows it */
|
|
if (!room[loc].seen) {
|
|
room[loc].seen = 1;
|
|
tscore += room[loc].points;
|
|
first_visit_flag = 1;
|
|
room_firstdesc = 1;
|
|
v_look();
|
|
} else {
|
|
first_visit_flag = 0;
|
|
if (verboseflag)
|
|
v_look(); /* But see v_go() for a special case involving SPECIAL */
|
|
room_firstdesc = 0;
|
|
}
|
|
if (room[loc].end) endflag = 1;
|
|
if (room[loc].win) winflag = 1;
|
|
if (room[loc].killplayer) deadflag = 1;
|
|
do_autoverb = 1;
|
|
set_statline();
|
|
}
|
|
|
|
|
|
static void rundesc(int i, descr_ptr dp_[], const char *shortdesc, int msgid) {
|
|
if (dp_[i].size > 0)
|
|
print_descr(dp_[i], 1);
|
|
else if (!invischeck(shortdesc))
|
|
raw_lineout(shortdesc, 1, MSG_DESC, nullptr);
|
|
else sysmsg(msgid, "$You$ see nothing unexpected.");
|
|
}
|
|
|
|
void it_describe(int dobj_) {
|
|
if (troom(dobj_))
|
|
print_descr(room_ptr[dobj_ - first_room], 1);
|
|
else if (tnoun(dobj_))
|
|
rundesc(dobj_ - first_noun, noun_ptr, noun[dobj_ - first_noun].shortdesc, 194);
|
|
else if (tcreat(dobj_))
|
|
rundesc(dobj_ - first_creat, creat_ptr,
|
|
creature[dobj_ - first_creat].shortdesc, 195);
|
|
else if (dobj_ == -ext_code[wdoor]) { /* i.e. DOOR */
|
|
if (room[loc].locked_door)
|
|
sysmsg(21, "$You$ see a locked door.");
|
|
else sysmsg(22, "$You$ see a perfectly normal doorway.");
|
|
} else sysmsg(194, "$You$ see nothing unexpected.");
|
|
if (tnoun(dobj_) &&
|
|
(noun[dobj_ - first_noun].open || !noun[dobj_ - first_noun].closable) &&
|
|
!it_appears_empty(dobj_)) {
|
|
sysmsg(228, "Which contains:");
|
|
print_contents(dobj_, 1);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static char *build_position(word prep_, word name)
|
|
/* Return the malloc'd string '$prep_$ the $name$' */
|
|
{
|
|
int leng;
|
|
char *s;
|
|
|
|
leng = strlen(dict[prep_]) + strlen(dict[name]) + 6; /* includes final '\0' */
|
|
s = (char *)rmalloc(leng * sizeof(char));
|
|
|
|
Common::strcpy_s(s, leng, dict[prep_]);
|
|
Common::strcat_s(s, leng, " the ");
|
|
Common::strcat_s(s, leng, dict[name]);
|
|
assert((int)strlen(s) + 1 == leng);
|
|
return s;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int print_obj(int obj, int ind_lev)
|
|
/* Prints out s on a line of its own if obj isn't INVISIBLE */
|
|
/* parent_descr is true if the parent has been described, false
|
|
otherwise (say if the parent is invisible). */
|
|
/* ind_lev=indentation level */
|
|
/* Return 1 if we actually printed something, 0 if obj is invisible */
|
|
{
|
|
int sdesc_flag; /* True if should print out as sdesc rather than
|
|
as adjective-noun */
|
|
int i, retval, parent;
|
|
const char *s;
|
|
char *t, *s0, *posstr;
|
|
|
|
if (tcreat(obj) && creature[obj - first_creat].initdesc != 0)
|
|
return 0; /* Don't print normal description if printing initdesc */
|
|
|
|
s0 = nullptr;
|
|
sdesc_flag = !player_has(obj); /* This should be tested. */
|
|
sdesc_flag = sdesc_flag || (ind_lev > 1); /* It seems that AGT uses the
|
|
sdesc for describing items
|
|
contained in other items */
|
|
/* Some code below relies on this, as well */
|
|
|
|
if (sdesc_flag)
|
|
s = it_sdesc(obj);
|
|
else if (it_name(obj) == 0 && it_adj(obj) == 0) /* Invisible */
|
|
return 0;
|
|
else {
|
|
s0 = objname(obj); /* Must remember to rfree s before exiting */
|
|
if (aver >= AGTME10) {
|
|
for (t = s0; isspace(*t); t++); /* Skip over initial whitespace... */
|
|
*t = toupper(*t); /* ...and upcase the first non-space character */
|
|
}
|
|
s = s0;
|
|
}
|
|
|
|
retval = 0;
|
|
if (sdesc_flag && tnoun(obj) && noun[obj - first_noun].initdesc != 0) {
|
|
retval = 1;
|
|
msgout(noun[obj - first_noun].initdesc, 1);
|
|
noun[obj - first_noun].initdesc = 0; /* Only show it once */
|
|
} else if (!invischeck(s)) {
|
|
retval = 1; /* We're actually going to print something */
|
|
for (i = 0; i < ind_lev; i++) writestr(" ");
|
|
raw_lineout(s, sdesc_flag, MSG_DESC, nullptr);
|
|
/* Do $word$ formatting if sdesc */
|
|
/* Need to output container */
|
|
parent = it_loc(obj);
|
|
if (tnoun(obj) && noun[obj - first_noun].pos_prep != 0) {
|
|
writestr(" (");
|
|
if (noun[obj - first_noun].pos_prep == -1)
|
|
writestr(noun[obj - first_noun].position);
|
|
else {
|
|
posstr = build_position(noun[obj - first_noun].pos_prep,
|
|
noun[obj - first_noun].pos_name);
|
|
writestr(posstr);
|
|
rfree(posstr);
|
|
}
|
|
writestr(")");
|
|
} else if (parent >= first_noun && it_invisible(parent, sdesc_flag)
|
|
&& (it_name(parent) != 0 || it_adj(parent) != 0)) {
|
|
/* If the parent object *isn't* invisible, we will already have
|
|
printed it out */
|
|
/* This also relies on sdesc_flag being the same for parent
|
|
and child objects */
|
|
|
|
if (parent >= first_creat && parent <= maxcreat)
|
|
sysmsg(221, "(Carried by");
|
|
else
|
|
sysmsg(222, " (Inside");
|
|
t = objname(parent);
|
|
writestr(t);
|
|
rfree(t);
|
|
sysmsg(223, ")");
|
|
}
|
|
if (tnoun(obj) && noun[obj - first_noun].light && noun[obj - first_noun].on
|
|
&& PURE_OBJ_DESC)
|
|
sysmsg(220, " (Providing light)");
|
|
writeln("");
|
|
}
|
|
if (!sdesc_flag)
|
|
rfree(s0);
|
|
return retval;
|
|
}
|
|
|
|
|
|
int print_contents(int obj, int ind_lev)
|
|
/* obj=object to list contents of; ind_lev=indentation level */
|
|
/* Returns number of objects contained in obj that were listed */
|
|
{
|
|
int i, cnt;
|
|
|
|
cnt = 0;
|
|
|
|
contloop(i, obj) {
|
|
if (print_obj(i, ind_lev)) cnt++;
|
|
if (it_open(i)) print_contents(i, ind_lev + 1);
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/* Routines for directly getting and setting object properties and */
|
|
/* attributes. */
|
|
|
|
|
|
#define NUM_WPROP 6
|
|
#define NUM_WATTR 6
|
|
|
|
|
|
static void *compute_addr(int obj, int prop, const prop_struct *ptable) {
|
|
int ofs;
|
|
void *base;
|
|
|
|
if (DIAG)
|
|
rprintf("(Accessing %s->%s)\n", dict[it_name(obj)], ptable[prop].name);
|
|
if (troom(obj)) {
|
|
base = (void *)(&room[obj - first_room]);
|
|
ofs = ptable[prop].room;
|
|
} else if (tnoun(obj)) {
|
|
base = (void *)(&noun[obj - first_noun]);
|
|
ofs = ptable[prop].noun;
|
|
} else if (tcreat(obj)) {
|
|
base = (void *)(&creature[obj - first_creat]);
|
|
ofs = ptable[prop].creature;
|
|
} else return nullptr;
|
|
|
|
if (ofs == -1) /* Field doesn't exist in this type of object */
|
|
return nullptr;
|
|
|
|
return (void *)(((char *)base) + ofs);
|
|
}
|
|
|
|
|
|
long getprop(int obj, int prop) {
|
|
integer *paddr;
|
|
|
|
if (prop >= NUM_PROP) return 0;
|
|
paddr = (integer *)compute_addr(obj, prop, proplist);
|
|
if (paddr == nullptr) return 0;
|
|
return *paddr;
|
|
}
|
|
|
|
void setprop(int obj, int prop, long val) {
|
|
integer *paddr;
|
|
|
|
if (prop >= NUM_WPROP) {
|
|
writeln("GAME ERROR: Read-only or non-existent property.");
|
|
return;
|
|
}
|
|
|
|
paddr = (integer *)compute_addr(obj, prop, proplist);
|
|
if (paddr == nullptr) {
|
|
writeln("GAME ERROR: Property-object mismatch.");
|
|
return;
|
|
}
|
|
*paddr = val;
|
|
}
|
|
|
|
rbool getattr(int obj, int prop) {
|
|
rbool *paddr;
|
|
|
|
if (prop >= NUM_ATTR) return 0;
|
|
paddr = (rbool *)compute_addr(obj, prop, attrlist);
|
|
if (paddr == nullptr) return 0;
|
|
return *paddr;
|
|
}
|
|
|
|
void setattr(int obj, int prop, rbool val) {
|
|
rbool *paddr;
|
|
|
|
if (prop >= NUM_WATTR && prop != 24) {
|
|
writeln("GAME ERROR: Read-only or non-existent attribute.");
|
|
return;
|
|
}
|
|
|
|
paddr = (rbool *)compute_addr(obj, prop, attrlist);
|
|
if (paddr == nullptr) {
|
|
writeln("GAME ERROR: Property-object mismatch.");
|
|
return;
|
|
}
|
|
*paddr = val;
|
|
}
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/* This sets up the creat_fix[] array, which is used to determine the */
|
|
/* scan ranges for addressed creatures in cases where there is more */
|
|
/* than one creature of the same name */
|
|
|
|
void init_creat_fix(void) {
|
|
int i, j;
|
|
|
|
creat_fix = (integer *)rmalloc(rangefix(maxcreat - first_creat + 1) * sizeof(integer));
|
|
for (i = 0; i < maxcreat - first_creat + 1; i++)
|
|
creat_fix[i] = i + first_creat;
|
|
for (i = 0; i < maxcreat - first_creat + 1; i++)
|
|
if (creat_fix[i] == i + first_creat) /* That is, it hasn't changed. */
|
|
for (j = i + 1; j < maxcreat - first_creat + 1; j++)
|
|
if (creature[i].name == creature[j].name &&
|
|
creature[i].adj == creature[j].adj)
|
|
creat_fix[j] = i + first_creat; /* That is, j --> i */
|
|
}
|
|
|
|
void free_creat_fix(void) {
|
|
rfree(creat_fix);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
#ifndef IT_MACRO
|
|
int it_contents(integer obj) {
|
|
if (tnoun(obj)) return noun[obj - first_noun].contents;
|
|
else if (troom(obj)) return room[obj - first_room].contents;
|
|
else if (tcreat(obj)) return creature[obj - first_creat].contents;
|
|
else if (obj == 1) return player_contents;
|
|
else if (obj == 1000) return player_worn;
|
|
else return 0;
|
|
}
|
|
|
|
rbool it_lockable(integer obj, word nword) {
|
|
if (tnoun(obj)) return noun[obj - first_noun].lockable;
|
|
else if (it_door(obj, nword)) return 1;
|
|
else return 0;
|
|
}
|
|
|
|
rbool it_locked(integer obj, word nword) {
|
|
if (tnoun(obj)) return noun[obj - first_noun].locked;
|
|
else if (it_door(obj, nword) && room[loc].locked_door) return 1;
|
|
else return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
} // End of namespace AGT
|
|
} // End of namespace Glk
|