/*
 *      PEscript compiler Virtual Machine
 *
 *      A lot of this code will need checking/fixing on 64-bit architectures
 *      to prevent 64-bit pointers being truncated by 32-bit pointer math
 */

// Controls
#define PTR unsigned long // Must match pointer size but support binary ops
#define EMERGENCY_QUIT // Allow SHIFT-BREAK to quit the VM for postmortems
//#define SAFER        // More vigorous pointer checks (but slower?)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <allegro.h>

#include "../ithelib.h"
#include "../console.h"
#include "../mouse.h"
#include "../oscli.h"
#include "../object.hpp"
#include "../gamedata.h"
#include "opcodes.h"
#include "pe_api.hpp"

#ifdef __BEOS__
#define _WIN32
#endif


// Game manipulation functions:

#include "../core.hpp"
#include "../library.hpp"
#include "../gamedata.h"
#include "../init.h"
#include "../media.h"
#include "../object.hpp"
#include "../linklist.hpp"
#include "../sound.h"
#include "../nuspeech.hpp"
#include "../project.hpp"

extern void SetDarkness(int d);
extern char *BestName(OBJECT *o);
extern OBJECT *GameGetObject(int x,int y);
extern TILE *GetTile(int x,int y);
extern void MoveToTop(OBJECT *o);
extern void MoveToFloor(OBJECT *o);
extern void spillcontents(OBJECT *bag,int x,int y);
extern void gen_largemap(void);
extern void RedrawMap(void);
extern int isSolid(int x,int y);
extern void CheckHurt(OBJECT *list);
extern int GetBulk(OBJECT *obj);
extern int getYN(char *q);
extern int get_num(int no);
extern void ResyncEverything(void);
extern int Restart(void);
extern int get_key_debounced();
extern int get_key_debounced_quiet();
extern int AddToParty(OBJECT *new_member);
extern void object_sound(char *a, OBJECT *b);
extern int FindPath(OBJECT *start, OBJECT *end, int diagonal);
extern int SumObjects(OBJECT *c,char *o,int q);
extern void AddQuantity(OBJECT *c,char *o,int q);
extern int TakeQuantity(OBJECT *c,char *o,int q);
extern int MoveQuantity(OBJECT *s,OBJECT *d,char *o,int q);
extern void ResumeSchedule(OBJECT *o);
extern void CheckTime();
extern OBJECT *find_nearest(OBJECT *o, char *type);
extern void find_nearby(OBJECT *o, char *type, OBJECT **list, int listsize);
extern void set_light(int x, int y, int x2, int y2, int light);
extern int isBlanked(int x, int y);
extern void ForceUpdateTag(int tag);
extern OBJECT *GetFirstObject(OBJECT *cont, char *name);
extern OBJECT *GetFirstObjectFuzzy(OBJECT *cont, char *name);
extern void CentreMap(OBJECT *centre);
extern void get_mouse_release();
extern int LoadGame();
extern void SaveGame();
extern void SoundSettings();
extern void ZoomMap(int zoom, int roof);

extern char *pe_get_function(unsigned int opcode);

extern char user_input[128];
extern "C" char DebugVM;
extern int do_lightning;
extern int do_earthquake;
extern int irekey;
extern int pevm_err;
static void CheckRange(PEVM *vm, void *ptr, int ident);


// Defines

#define VMOP(x) vmop[PEVM_##x] = PV_##x;
#define ADD_IP(ptr,add) (VMTYPE *)((char *)ptr + add)
#define OPMASK 31 // Max 32 operators (at present)
#define VM_STACKLIMIT 1024
#define OPCODEMASK 255
#define VMEM 128

// Variables

static int cur_x=0;		// Text cursor X,Y
static int cur_y=0,cur_margin=0;
static int printlogging=0;
static int abortkernel=0;
static char vm_up=0,SI_Search=0;
static VMTYPE *vmem;   // This is the address space where the VM runs
static unsigned int vmem_ptr; // This is the allocation point
static unsigned int vmem_limit; // Size of the VM space in words

char debcurfunc[1024];
char debmsg[1024];
char *pevm_context="Undefined";

// Functions

static void PV_Invalid();
static void PV_Return();
static void PV_Printstr();
static void PV_Printint();
static void PV_PrintXint();
static void PV_PrintCR();
static void PV_ClearLine();
static void PV_Callfunc();
static void PV_CallfuncS();
static void PV_Let_iei();
static void PV_Let_ieipi();
static void PV_Let_pes();
static void PV_Add();
static void PV_Goto();
static void PV_If_iei();
static void PV_If_ses();
static void PV_If_oics();
static void PV_If_oeo();
static void PV_If_oEo();
static void PV_If_o();
static void PV_If_no();
static void PV_For();
static void PV_Searchin();
static void PV_Listafter();
static void PV_SearchWorld();
static void PV_StopSearch();
static void PV_PlayMusic();
static void PV_StopMusic();
static void PV_PlaySound();
static void PV_IsPlaying();
static void PV_ObjSound();
static void PV_Create();
static void PV_CreateS();
static void PV_Destroy();
static void PV_SetDarkness();
static void PV_SetFlag();
static void PV_GetFlag();
static void PV_ResetFlag();
static void PV_If_on();
static void PV_If_non();
static void PV_If_tn();
static void PV_If_ntn();
static void PV_MoveObject();
static void PV_PushObject();
static void PV_XferObject();
static void PV_ShowObject();
static void PV_PrintXYi();
static void PV_PrintXYx();
static void PV_PrintXYs();
static void PV_PrintXYcr();
static void PV_GotoXY();
static void PV_TextColour();
static void PV_TextFont();
static void PV_SetSequenceN();
static void PV_SetSequenceS();
static void PV_Lightning();
static void PV_Earthquake();
static void PV_Bestname();
static void PV_GetObject();
static void PV_GetSolidObject();
static void PV_GetFirstObject();
static void PV_GetTile();
static void PV_GetObjectBelow();
static void PV_ChangeObject();
static void PV_ChangeObjectS();
static void PV_ReplaceObject();
static void PV_ReplaceObjectS();
static void PV_SetDir();
static void PV_RedrawText();
static void PV_RedrawMap();
static void PV_MoveToPocket();
static void PV_XferToPocket();
static void PV_MoveFromPocket();
static void PV_ForceFromPocket();
static void PV_Spill();
static void PV_SpillXY();
static void PV_FadeOut();
static void PV_FadeIn();
static void PV_MoveToTop();
static void PV_MoveToFloor();
static void PV_GetLOS();
static void PV_CheckHurt();
static void PV_IfSolid();
static void PV_IfVisible();
static void PV_SetUFlag();
static void PV_GetUFlag();
static void PV_SetLocal();
static void PV_GetLocal();
static void PV_WeighObject();
static void PV_GetBulk();
static void PV_InputInt();
static void PV_IfInPocket();
static void PV_IfNInPocket();
static void PV_ReSyncEverything();
static void PV_TalkTo1();
static void PV_TalkTo2();
static void PV_Random();
static void PV_GetYN();
static void PV_GetYNN();
static void PV_Restart();
static void PV_ScrTileN();
static void PV_ScrTileS();
static void PV_While1();
static void PV_While2();
static void PV_Break();
static void PV_GetKey();
static void PV_GetKey_quiet();
static void PV_DoAct();
static void PV_DoActS();
static void PV_DoActTo();
static void PV_DoActSTo();
static void PV_NextAct();
static void PV_NextActS();
static void PV_NextActTo();
static void PV_NextActSTo();
static void PV_InsAct();
static void PV_InsActS();
static void PV_InsActTo();
static void PV_InsActSTo();
static void PV_StopAct();
static void PV_ResumeAct();
static void PV_GetFuncP();
static void PV_GetContainer();
static void PV_SetLeader();
static void PV_SetMember();
static void PV_AddMember();
static void PV_DelMember();
static void PV_MoveTowards8();
static void PV_MoveTowards4();
static void PV_WaitFor();
static void PV_WaitForAnimation();
static void PV_If_oonscreen();
static void PV_If_not_oonscreen();
static void PV_TakeQty();
static void PV_GiveQty();
static void PV_MoveQty();
static void PV_CountQty();
static void PV_CopySchedule();
static void PV_AllOnscreen();
static void PV_AllAround();
static void PV_CheckTime();
static void PV_FindNear();
static void PV_FindNearby();
static void PV_FindTag();
static void PV_FastTag();
static void PV_MakeTagList();
static void PV_SetLight();
static void PV_SetLight_single();
static void PV_LastOp();
static void PV_MovePartyToObj();
static void PV_MovePartyFromObj();
static void PV_MoveTag();
static void PV_UpdateTag();
static void PV_GetDataSSS();
static void PV_GetDataSSI();
static void PV_GetDataSIS();
static void PV_GetDataSII();
static void PV_GetDataISS();
static void PV_GetDataISI();
static void PV_GetDataIIS();
static void PV_GetDataIII();
static void PV_GetDecor();
static void PV_DelDecor();
static void PV_SearchContainer();
static void PV_ChangeMap1();
static void PV_ChangeMap2();
static void PV_PicScroll();
static void PV_SetPanel();
static void PV_FindPathMarker();
static void PV_ReSolid();
static void PV_SetPName();
static void PV_DelProp();
static void PV_QUpdate();

static void PV_AddRange();
static void PV_GridRange();
static void PV_AddButton();
static void PV_PushButton();
static void PV_RightClick();
static void PV_FlushMouse();
static void PV_RangePointer();
static void PV_SaveScreen();
static void PV_RestoreScreen();
static void PV_fxGetXY();
static void PV_fxColour();
static void PV_fxAlpha();
static void PV_fxAlphaMode();
static void PV_fxRandom();
static void PV_fxLine();
static void PV_fxPoint();
static void PV_fxRect();
static void PV_fxPoly();
static void PV_fxOrbit();
static void PV_fxSprite();
static void PV_fxCorona();
static void PV_fxAnimSprite();
static void PV_dofx();
static void PV_dofxS();
static void PV_dofxOver();
static void PV_dofxOverS();
static void PV_stopfx();

static void PV_Printaddr();
static void PV_PrintLog();
static void PV_Dump();
static void PV_NOP();
static void PV_Dofus();

static void VMstacktrace();

static int OP_Invalid(int a, int b);
static int OP_Add(int a, int b);
static int OP_Sub(int a, int b);
static int OP_Mul(int a, int b);
static int OP_Div(int a, int b);
static int OP_Equal(int a, int b);
static int OP_NotEqual(int a, int b);
static int OP_Less(int a, int b);
static int OP_LessEq(int a, int b);
static int OP_Great(int a, int b);
static int OP_GreatEq(int a, int b);
static int OP_Modulus(int a, int b);
static int OP_And(int a, int b);
static int OP_Or(int a, int b);
static int OP_Xor(int a, int b);
static int OP_Nand(int a, int b);
static int OP_Shl(int a, int b);
static int OP_Shr(int a, int b);

static int *GET_INT();
static int *SAFE_INT();
static void GET_ARRAY_INFO(void **array, int *size, int *idx);


static void SI_RecursiveLoop(OBJECT *obj, int funccall);

void InitVM();
void CallVM(char *function);
void CallVMnum(int pos);
void DumpVM(int force);
static void VMbug();
static VMTYPE *VMget(int bytes);
static void VMfree(VMTYPE *ptr);


static PEVM *AddVM(PEM *p);
static void DelVM();
static void microkernel();
static void Bang(char *errmsg);
static void VMstop(char *msg, char *deref);
static PEVM vmstack[VM_STACKLIMIT];
static int vmstack_ptr=0;

static PEVM *curvm;
static VMTYPE *lastip=NULL;

static void (*vmop[256])(void);
static int (*Operator[32])(int x,int y);
static char *oplist[32];

// Code

#ifdef SAFER
	#define CHECK_VM(); if(!curvm) return NULL;
#else
	#define CHECK_VM(); ;
#endif

inline unsigned char GET_BYTE()
{
CHECK_VM();
return (*curvm->ip++).u8;
}

inline unsigned char UNGET_BYTE()
{
CHECK_VM();
return (*curvm->ip--).u8;
}

#define CHECK_POINTER_CORE(x,f,l) \
if(!x)\
	{\
	Bug("ERROR:  Null pointer in %s at %d\n",f,l);\
	VMstop("Null pointer!\n",NULL);\
	if(curvm) Bug("(ip = 0x%p)\n",(curvm->ip-curvm->code)*sizeof(VMTYPE));\
	return;\
	}

#define CHECK_POINTER(x); CHECK_POINTER_CORE(x,__FILE__,__LINE__);


inline int GET_DWORD()
{
CHECK_VM();
curvm->ip++; // skip addressing word
return (*curvm->ip++).i32;
}

inline int GET_RAW_DWORD()
{
CHECK_VM();
return (*curvm->ip++).i32;
}

inline int *GET_DWORD_PTR()
{
CHECK_VM();
return (int *)curvm->ip++; // Just get the address here, don't dereference it
}


inline void *GET_PTR()
{
CHECK_VM();
return (void *)(*curvm->ip++).ptr;
}

inline char *GET_STR(int len)
{
VMTYPE *x;
CHECK_VM();
x = curvm->ip;
curvm->ip = (VMTYPE *)((char *)curvm->ip + len);
return (char *)x;
}

inline int *GET_MEMBER_PTR()
{
volatile char **obj;
volatile int *ptr,**pointer=NULL;
volatile void *var2;
unsigned char refs,ctr;
char debugchain[2048];
char msg[128];

obj = (volatile char **)GET_PTR();
ptr = (int *)*obj;

if(!ptr)
	{
	VMstop("DANGER!  Encountered NULL data structure in GetMemberPtr!\n",NULL);
	return NULL;
	}

if(DebugVM)
	sprintf(debugchain,"%p->",obj);

refs = GET_BYTE();

for(ctr=0;ctr<refs;ctr++)
	{
	var2 = (void *)GET_PTR();
	ptr = (int *)((char *)ptr + (PTR)var2);
	pointer = (volatile int **)ptr;
	ptr = (int *)*ptr;
	if(DebugVM)
		{
		sprintf(msg,"%p->",pointer);
		strcat(debugchain,msg);
		}

	if(!ptr)
		{
		VMstop("DANGER!  NULL data lookup in GetMemberPtr!\n",debugchain);
		return NULL;
		}
	}
return (int *)pointer;
}

inline int *GET_ARRAY()
{
int **array;
int *idx;
int size;

array = (int **)GET_PTR();
size = GET_DWORD();
idx = (int *)GET_INT();

if(!idx)
	{
	Bug("ERROR:  Array pointer corrupt\n");
	Bug("        Dumping VM to bootlog.txt...\n");
	DumpVM(1);
	Bug("        Current VM cannot continue.\n");
	VMbug();
	Bug("Terminating crashed VM...\n");
	DelVM();
	return NULL;
	}

// Make sure it doesn't exceed array bounds

if(*idx<1 || *idx > size)
	{
	Bug("ERROR:  Array access out of bounds\n");
	Bug("        Array stretches 1-%d, tried to access %d\n",size,*idx);
	Bug("        Dumping VM to bootlog.txt...\n");
	DumpVM(1);
	Bug("        Current VM cannot continue.\n");
	VMbug();
	Bug("Terminating crashed VM...\n");
	DelVM();
	return NULL;
	}

array += (*idx)-1; // Now points to appropriate element

M_chk(array);

return (int *)array;
}


inline int *CHECK_ARRAY()
{
int **array;
int *idx;
int size;

array = (int **)GET_PTR();
size = GET_DWORD();
idx = (int *)GET_INT();

if(!idx)
	return NULL;

// Make sure it doesn't exceed array bounds

if(*idx<1 || *idx > size)
	return NULL; // Bad

if(!array)
	return NULL; // Even badder

array += (*idx)-1; // Point it where it ought to be

return (int *)array;
}


static void GET_ARRAY_INFO(void **array, int *length, int *index)
{
int **arr;
int *idx;
int size;
VMTYPE *swap;
unsigned char flags;

swap = curvm->ip; // Store IP before processing

flags = GET_BYTE();
if(!(flags&ACC_ARRAY))
	{
	Bug("Array_info: Array is not an array!  Flag '%x' isn't %x\n",flags,ACC_ARRAY);
	Bug("         Dumping VM to bootlog.txt...\n");
	DumpVM(1);
	Bug("         Current VM cannot continue.\n");
	VMbug();
		Bug("Terminating crashed VM...\n");
	DelVM();
	return;
	}

arr = (int **)GET_PTR();
size = GET_DWORD();
idx = (int *)GET_INT();

// Store info
*array=(void *)arr;
*length=size;
*index=*idx;

curvm->ip = swap; // Restore pointer
}


inline int *GET_MEMBER()
{
OBJECT **obj;
int *ptr,**pointer=NULL;
void *var2;
unsigned char refs,ctr;
char debugchain[2048];
char msg[128];

obj = (OBJECT **)GET_PTR();
ptr = (int *)*obj;
if(!ptr)
	{
	VMstop("DANGER!  NULL structure in GetMember\n",NULL);
	return NULL;
	}

if(DebugVM)
	sprintf(debugchain,"%p->",obj);

refs = GET_BYTE();
for(ctr=0;ctr<refs;ctr++)
	{
	var2 = (void *)GET_PTR();
	ptr = (int *)((char *)ptr + (PTR)var2);
	pointer = (int **)ptr;
	if(ptr)
		ptr = (int *)*ptr;
	if(DebugVM)
		{
		sprintf(msg,"%p->",pointer);
		strcat(debugchain,msg);
		}

	if(!ptr) // Ignore the last one
	if(ctr<refs-1)
		{
		VMstop("DANGER!  NULL data lookup in GetMember!\n",debugchain);
		return NULL;
		}
	}
return (int *)pointer;
}

// Traverse the chain and make sure it's accessible.
// Although we want to abort on an error, we MUST traverse the whole thing
// otherwise the VM's EIP will be pointing at at the wrong address and the
// VM will crash.

inline int *CHECK_MEMBER()
{
OBJECT **obj;

// Mark all the pointers volatile to avoid gcc optimiser bug(?)
volatile int *ptr=NULL;
volatile int **pointer=NULL;
volatile void *var2;
volatile unsigned char refs,ctr;
int good;

good=1;
obj = (OBJECT **)GET_PTR();
if(!obj)
	good=0;
if(good)
	{
	ptr = (int *)*obj;
	if(!ptr)
		good=0;
	}

refs = GET_BYTE();
for(ctr=0;ctr<refs;ctr++)
	{
	var2 = (void *)GET_PTR();
	if(good && ptr)
		{
		ptr = (int *)((char *)ptr + (PTR)var2);
		pointer = (volatile int **)ptr;
		ptr = (int *)*ptr;
		}
	if(!ptr)
		good=0;
	}
if(good)
	return (int *)pointer;
return NULL;
}

inline int *SKIP_MEMBER()
{
OBJECT **obj;
void *var2;
unsigned char refs,ctr;
obj = (OBJECT **)GET_PTR();
refs = GET_BYTE();
for(ctr=0;ctr<refs;ctr++)
	var2 = (void *)GET_PTR();
return (int *)obj;
}

inline int *GET_ARRAY_MEMBER_PTR()
{
volatile void *var2;
unsigned char refs,ctr;
volatile int *ptr,**pointer=NULL;
volatile int **array;
volatile int *idx;
int size;
char msg[128];
char debugchain[2048];

array = (volatile int **)GET_PTR();
//ilog_quiet("Marray = %p\n",array);
size = GET_DWORD();
//ilog_quiet("Msize = %d\n",size);
idx = (int *)GET_INT();
//ilog_quiet("Midx = %p\n",idx);

refs = GET_BYTE();
//ilog_quiet("Refs = %d\n",refs);

if(!idx || !array)
	{
	VMstop("ERROR:  Array pointer corrupt\n",NULL);
	return NULL;
	}

// Make sure it doesn't exceed array bounds

if(*idx<1 || *idx > size)
	{
	VMstop("ERROR:  Array access out of bounds\n",NULL);
	Bug("Details: Array is 1-%d, tried to access %d\n",size,*idx);
	return NULL;
	}

array += (*idx)-1; // Now points to appropriate element (it had better, anyway)

// Okay, now do the member lookup
ptr = *array;

//refs = GET_BYTE();
for(ctr=0;ctr<refs;ctr++)
	{
	var2 = (void *)GET_PTR();
	ptr = (int *)((char *)ptr + (PTR)var2);
	pointer = (volatile int **)ptr;
	ptr = (int *)*ptr;
	if(DebugVM)
		{
		sprintf(msg,"%p->",pointer);
		strcat(debugchain,msg);
		}

	if(!ptr) // Ignore the last one
		{
		VMstop("DANGER!  NULL data lookup in GetArrayMemberPtr\n",debugchain);
		return NULL;
		}
	}

return (int *)pointer;
}

inline int *GET_ARRAY_MEMBER()
{

// Mark all the pointers volatile to avoid gcc optimiser bug(?)
volatile void *var2;
unsigned char refs,ctr;
volatile int *ptr,**pointer=NULL;
volatile int **array;
volatile int *idx;
int size;
char msg[128];
char debugchain[2048];

array = (volatile int **)GET_PTR();
//ilog_quiet("array = %p\n",array);
size = GET_DWORD();
//ilog_quiet("size = %d\n",size);
idx = (int *)GET_INT();
//ilog_quiet("idx = %p\n",idx);

refs = GET_BYTE();
//ilog_quiet("Refs = %d\n",refs);

if(!idx || !array)
	{
	VMstop("ERROR:  Array pointer corrupt\n",NULL);
	return NULL;
	}

// Make sure it doesn't exceed array bounds

if(*idx<1 || *idx > size)
	{
	VMstop("ERROR:  Array access out of bounds\n",NULL);
	Bug("Details: Array is 1-%d elements long, tried to access %d\n",size,*idx);
	return NULL;
	}

array += (*idx)-1; // Now points to appropriate element (it had better, anyway)

// Okay, now do the member lookup
ptr = *array;

//refs = GET_BYTE();
for(ctr=0;ctr<refs;ctr++)
	{
	var2 = (void *)GET_PTR();
/*
	if(!var2)
		{
		VMstop("DANGER!  NULL data lookup in GetArrayMember\n",debugchain);
		return NULL;
		}
*/

	ptr = (int *)((char *)ptr + (PTR)var2);
	if(!ptr)
		{
		VMstop("DANGER!  NULL data lookup in GetArrayMember\n",debugchain);
		return NULL;
		}

	pointer = (volatile int **)ptr;
	ptr = (int *)*ptr;
	if(DebugVM)
		{
		sprintf(msg,"%p->",pointer);
		strcat(debugchain,msg);
		}

	if(!ptr) // Ignore the last one
		if(ctr<refs-1)
			{
			VMstop("DANGER!  NULL data lookup(2) in GetArrayMember\n",debugchain);
			return NULL;
			}
	}

return (int *)pointer;
}

inline int *SKIP_ARRAY_MEMBER()
{
void *var2;
unsigned char refs,ctr;
int **array;
int *idx;
int size;

array = (int **)GET_PTR();
size = GET_DWORD();
idx = (int *)GET_INT();

if(!idx)
	return NULL;

//ilog_quiet("(%d/%d) ",*idx,size);

refs = GET_BYTE();
//ilog_quiet("[Refs = %d]\n",refs);
for(ctr=0;ctr<refs;ctr++)
	{
	var2 = (void *)GET_PTR();
//	ilog_quiet("<%p> ",var2);
	}

// Make sure it doesn't exceed array bounds

if(*idx<1 || *idx > size)
	return NULL; // Bad

if(!array)
	return NULL;

//(*array) += *idx; // Now points to appropriate element (it had better, anyway)
//return (int *)array;
return NULL;
}



OBJECT **GET_OBJECT()
{
unsigned char flags;
flags = GET_BYTE();
if(flags&ACC_INDIRECT)
	return (OBJECT **)GET_PTR();
if(flags&ACC_MEMBER)
	return (OBJECT **)GET_MEMBER_PTR();
if(flags&ACC_ARRAY)
	return (OBJECT **)GET_ARRAY();
Bug("Fatal: Unknown data method '%x' in GET_OBJECT()\n",flags);
return NULL;
}

OBJECT **CHECK_OBJ()
{
unsigned char flags;

flags = GET_BYTE();
if(flags&ACC_INDIRECT)
	return (OBJECT **)GET_PTR();
if(flags&ACC_MEMBER)
	return (OBJECT **)CHECK_MEMBER();
if(flags&ACC_ARRAY)
	return (OBJECT **)CHECK_ARRAY();  //was GET_ARRAY();
Bug("Fatal: Unknown data method '%x' in CHECK_OBJ()\n",flags);
return NULL;
}

int *GET_INT()
{
unsigned char flags;
flags = GET_BYTE();
if(!flags)
	return GET_DWORD_PTR();
if(flags == ACC_INDIRECT)
	return (int *)GET_PTR();
if(flags == ACC_MEMBER)
	return GET_MEMBER();
if(flags == ACC_ARRAY)
	return (int *)GET_ARRAY();
if(flags == ACC_ARRAY| ACC_MEMBER)
	return (int *)GET_ARRAY_MEMBER();
Bug("Fatal: Unknown data method '%x' in GET_INT()\n",flags);
return NULL;
}

int *SAFE_INT()
{
unsigned char flags;
flags = GET_BYTE();
if(!flags)
	return GET_DWORD_PTR();
if(flags == ACC_INDIRECT)
	return (int *)GET_PTR();
if(flags == ACC_MEMBER)
	return SKIP_MEMBER();
if(flags == ACC_ARRAY)
	return (int *)CHECK_ARRAY();
if(flags == ACC_ARRAY | ACC_MEMBER)
	return (int *)SKIP_ARRAY_MEMBER();
Bug("Fatal: Unknown data method '%x' in GET_INT()\n",flags);
return NULL;
}

char *GET_STRING()
{
unsigned char len;
unsigned char flags;
char **str;

flags = GET_BYTE();
if(!flags || flags & ACC_IMMSTR)
	{
	len = GET_DWORD();
	return GET_STR(len);
	}
if(flags == ACC_INDIRECT)
	{
	str = (char **)GET_PTR();
	if(!str) return NULL;
	return *str;
	}
if(flags == ACC_ARRAY)
	return (char *)GET_ARRAY();
if(flags == ACC_MEMBER)
	{
	str = (char **)GET_MEMBER_PTR();
	if(!str) return NULL;
	return *str;
	}
if(flags == ACC_ARRAY|ACC_MEMBER)
	{
	str = (char **)GET_ARRAY_MEMBER_PTR();
	if(!str) return NULL;
	return *str;
	}
Bug("Fatal: Unknown data method '%x' in GET_STRING()\n",flags);
return NULL;
}


/*
 *      VM functions follow
 */


void InitVM()
{
int ctr,bytes;

// Create the VM address space

// If the user overrode the default of 128k..
if(vcpu_memory < 1)
	vcpu_memory = VMEM;

// Allocate the address space
bytes = vcpu_memory*1024;
vmem = (VMTYPE *)M_get(1,bytes);
vmem_ptr = 0;  // High-water mark to zero
vmem_limit = bytes/sizeof(VMTYPE); // get limit in words

// Set up opcode jump table

for(ctr=0;ctr<256;ctr++)
	vmop[ctr]=PV_Invalid;

// VMOP macro splices strings in preprocessor to make VM maintenance easier
// For example VMOP(Return);
// compiles as: vmop[PEVM_Return] = PV_Return;

VMOP(Return);
VMOP(Printint);
VMOP(Printstr);
VMOP(PrintXint);
VMOP(PrintCR);
VMOP(ClearLine);
VMOP(Callfunc);
VMOP(CallfuncS);
VMOP(Let_iei);
VMOP(Let_ieipi);
VMOP(Let_pes);
VMOP(Add);
VMOP(Goto);
VMOP(If_iei);
VMOP(If_ses);
VMOP(If_oics);
VMOP(If_oeo);
VMOP(If_oEo);
VMOP(If_o);
VMOP(If_no);
VMOP(For);
VMOP(Searchin);
VMOP(Listafter);
VMOP(SearchWorld);
VMOP(StopSearch);
VMOP(PlayMusic);
VMOP(StopMusic);
VMOP(IsPlaying);
VMOP(PlaySound);
VMOP(ObjSound);
VMOP(Create);
VMOP(CreateS);
VMOP(Destroy);
VMOP(SetFlag);
VMOP(GetFlag);
VMOP(ResetFlag);
VMOP(If_on);
VMOP(If_non);
VMOP(If_tn);
VMOP(If_ntn);
VMOP(MoveObject);
VMOP(PushObject);
VMOP(XferObject);
VMOP(ShowObject);
VMOP(SetDarkness);
VMOP(GotoXY);
VMOP(PrintXYs);
VMOP(PrintXYi);
VMOP(PrintXYx);
VMOP(PrintXYcr);
VMOP(TextColour);
VMOP(TextFont);
VMOP(SetSequenceS);
VMOP(SetSequenceN);
VMOP(Lightning);
VMOP(Earthquake);
VMOP(Bestname);
VMOP(GetObject);
VMOP(GetSolidObject);
VMOP(GetFirstObject);
VMOP(GetTile);
VMOP(GetObjectBelow);
VMOP(ChangeObject);
VMOP(ChangeObjectS);
VMOP(ReplaceObject);
VMOP(ReplaceObjectS);
VMOP(SetDir);
VMOP(RedrawMap);
VMOP(RedrawText);
VMOP(MoveToPocket);
VMOP(XferToPocket);
VMOP(MoveFromPocket);
VMOP(ForceFromPocket);
VMOP(Spill);
VMOP(SpillXY);
VMOP(FadeOut);
VMOP(FadeIn);
VMOP(MoveToTop);
VMOP(MoveToFloor);
VMOP(GetLOS);
VMOP(CheckHurt);
VMOP(IfSolid);
VMOP(IfVisible);
VMOP(SetUFlag);
VMOP(GetUFlag);
VMOP(SetLocal);
VMOP(GetLocal);
VMOP(WeighObject);
VMOP(GetBulk);
VMOP(InputInt);
VMOP(IfInPocket);
VMOP(IfNInPocket);
VMOP(ReSyncEverything);
VMOP(TalkTo1);
VMOP(TalkTo2);
VMOP(Random);
VMOP(GetYN);
VMOP(GetYNN);
VMOP(Restart);
VMOP(ScrTileN);
VMOP(ScrTileS);
VMOP(While1);
VMOP(While2);
VMOP(Break);
VMOP(GetKey);
VMOP(GetKey_quiet);
VMOP(DoAct);
VMOP(DoActS);
VMOP(DoActTo);
VMOP(DoActSTo);
VMOP(NextAct);
VMOP(NextActS);
VMOP(NextActTo);
VMOP(NextActSTo);
VMOP(InsAct);
VMOP(InsActS);
VMOP(InsActTo);
VMOP(InsActSTo);
VMOP(StopAct);
VMOP(ResumeAct);
VMOP(GetFuncP);
VMOP(GetContainer);
VMOP(SetLeader);
VMOP(SetMember);
VMOP(AddMember);
VMOP(DelMember);
VMOP(MoveTowards8);
VMOP(MoveTowards4);
VMOP(WaitFor);
VMOP(WaitForAnimation);
VMOP(If_oonscreen);
VMOP(If_not_oonscreen);
VMOP(TakeQty);
VMOP(GiveQty);
VMOP(MoveQty);
VMOP(CountQty);
VMOP(CopySchedule);
VMOP(AllOnscreen);
VMOP(AllAround);
VMOP(CheckTime);
VMOP(FindNear);
VMOP(FindNearby);
VMOP(FindTag);
VMOP(FastTag);
VMOP(MakeTagList);
VMOP(SetLight);
VMOP(SetLight_single);
VMOP(Printaddr);
VMOP(PrintLog);
VMOP(MovePartyToObj);
VMOP(MovePartyFromObj);
VMOP(MoveTag);
VMOP(UpdateTag);
VMOP(GetDataSSS);
VMOP(GetDataSSI);
VMOP(GetDataSIS);
VMOP(GetDataSII);
VMOP(GetDataISS);
VMOP(GetDataISI);
VMOP(GetDataIIS);
VMOP(GetDataIII);
VMOP(GetDecor);
VMOP(DelDecor);
VMOP(SearchContainer);
VMOP(ChangeMap1);
VMOP(ChangeMap2);
VMOP(PicScroll);
VMOP(SetPanel);
VMOP(FindPathMarker);
VMOP(ReSolid);
VMOP(SetPName);
VMOP(DelProp);
VMOP(QUpdate);

VMOP(AddRange);
VMOP(GridRange);
VMOP(AddButton);
VMOP(PushButton);
VMOP(RightClick);
VMOP(FlushMouse);
VMOP(RangePointer);
VMOP(SaveScreen);
VMOP(RestoreScreen);
VMOP(fxGetXY);
VMOP(fxColour);
VMOP(fxAlpha);
VMOP(fxAlphaMode);
VMOP(fxRandom);
VMOP(fxLine);
VMOP(fxPoint);
VMOP(fxRect);
VMOP(fxPoly);
VMOP(fxOrbit);
VMOP(fxSprite);
VMOP(fxCorona);
VMOP(fxAnimSprite);
VMOP(dofx);
VMOP(dofxS);
VMOP(dofxOver);
VMOP(dofxOverS);
VMOP(stopfx);

// These are used internally by the compiler

VMOP(LastOp);
VMOP(Dump);
VMOP(NOP);
VMOP(Dofus);

// Now set up Operator jumptable

for(ctr=0;ctr<32;ctr++)
	Operator[ctr]=OP_Invalid;

Operator[1]=OP_Add;
Operator[2]=OP_Sub;
Operator[3]=OP_Div;
Operator[4]=OP_Mul;
Operator[5]=OP_Equal;
Operator[6]=OP_NotEqual;
Operator[7]=OP_Less;
Operator[8]=OP_LessEq;
Operator[9]=OP_Great;
Operator[10]=OP_GreatEq;
Operator[11]=OP_Modulus;
Operator[12]=OP_And;
Operator[13]=OP_Or;
Operator[14]=OP_Xor;
Operator[15]=OP_Nand;
Operator[16]=OP_Shl;
Operator[17]=OP_Shr;

oplist[0]="<!>";
oplist[1]="+";
oplist[2]="-";
oplist[3]="/";
oplist[4]="*";
oplist[5]="=";
oplist[6]="!=";
oplist[7]="<";
oplist[8]="<=";
oplist[9]=">";
oplist[10]=">=";
oplist[11]="MOD";
oplist[12]="AND";
oplist[13]="OR";
oplist[14]="XOR";
oplist[15]="NAND";
oplist[16]="SHL";
oplist[17]="SHR";

vm_up=1;
ilog_quiet("Script VM initialised\n");
}

/*
 *  A simple (but fast) stack-based memory allocator
 */

VMTYPE *VMget(int bytes)
{
unsigned int words,vp;
VMTYPE *pvm,*start;

// Now find out if it is divisible by a word and pad it if necessary
words = bytes/sizeof(VMTYPE);
if(bytes & (sizeof(VMTYPE)-1))
	words++;

// Add room for a control word
words++;

// Get start of block
start = &vmem[vmem_ptr];

// Make sure we're not out of memory
vp = vmem_ptr+words;
if(vp>vmem_limit)
	return NULL;
vmem_ptr=vp; // Start of next block

// Get the word at this point and go back to store block size
pvm = &vmem[vp];
pvm--;
(*pvm).u32 = words;

return start;
}

// Deallocator

void VMfree(VMTYPE *p)
{
VMTYPE *pvm;
unsigned int pos;

pvm=&vmem[vmem_ptr];
pvm--; // Should be control word

pos = (*pvm).u32;
pvm++; // Back across control word
pvm -= pos; // back this number of words

if(pvm != p)
	ithe_panic("VM address space corrupt deleting function",curvm->name);

vmem_ptr -= pos; // Just wind back the allocation pointer
}



char *CurrentVM()
{
return debcurfunc;
}

void CallVM(char *function)
{
PEVM *p;
int pos;

if(!vm_up)
	return;
//	ithe_panic("Tried to use PEVM before InitVM()","Aiee!");

if(!function)
	{
	Bug("Tried to call NULL function\n");
	VMbug();
	return;
	}

strcpy(debcurfunc,function);

pos = getnum4PE(function);
if(pos == -1)
	{
	Bug("CallVM: PE script '%s' not found\n",function);
	return;
	}

if(show_vrm_calls)
	ilog_quiet("Call '%s'  (context %s)\n",debcurfunc,pevm_context);

p=AddVM(&PElist[pos]);
if(!p)
	return; // Stack error?
curvm = p;

DumpVM(0);

microkernel();
}

void VMbug()
{
if(!curvm)
	{
	Bug("Serious..  not in a function.  Error in game loop?\n");
	return;
	}
    
Bug("Error in function %s, in file %s (context %s)\n",curvm->name,curvm->file,pevm_context);

ilog_quiet("\n");
VMstacktrace();
}


void VMstop(char *msg, char *debugchain)
{
Bug(msg);

if(DebugVM)
	{
	Bug("         Dumping VM to bootlog.txt...\n");
	if(debugchain)
		{
		Bug("         Dereference chain:\n");
		Bug("         %sNULL\n",debugchain);
		}
	}
else
	Bug("         If repeatable, run IRE with -DebugVM for more info.\n");

DumpVM(1);

Bug("         Current VM cannot continue.\n");
VMbug();
Bug("Terminating crashed VM...\n");
DelVM();
}

void VMstacktrace()
{
int ctr;

if(!curvm)
	return;

ilog_quiet("VM Stack trace:\n");
for(ctr=vmstack_ptr;ctr>0;ctr--)
	ilog_quiet("  %d: %s (%s)\n",ctr,vmstack[ctr].name,vmstack[ctr].file);
}

void CallVMnum(int pos)
{
PEVM *p;

if(!vm_up)
	ithe_panic("Tried to use PEVM before InitVM()","Aiee!");

if(pos < 0 || pos > PEtot)
	{
	Bug("CALL: Invalid function %d (0x%x)\n",pos,pos);
	VMbug();
	return;
	}

strcpy(debcurfunc,PElist[pos].name);
if(DebugVM||show_vrm_calls)
	ilog_quiet("Call '%s' (context %s)\n",debcurfunc,pevm_context);

p=AddVM(&PElist[pos]);
if(!p)
	return; // Stack error?
curvm = p;

DumpVM(0);

microkernel();
}


void microkernel()
{
unsigned int opcode;

if(!curvm)
	ithe_panic("VM started on an empty stomach",NULL);

do  {
	lastip = curvm->ip;    // Preserve IP (for error messages)
	opcode = (*curvm->ip++).u32; // Fetch
#ifdef EMERGENCY_QUIT
	poll_keyboard();
	if(key[KEY_PAUSE])
		if(key_shifts & KB_SHIFT_FLAG)
			Bang("User Break");
#endif
	if(DebugVM)
		ilog_quiet("MK: %04x [%02x]\n",(lastip - curvm->code)*sizeof(VMTYPE),opcode);

	// Execute the instruction
	vmop[opcode&OPCODEMASK]();

	if(abortkernel)
		{
		abortkernel=0;
		return;
		}
	} while(curvm && curvm->ip);
}


void Bang(char *err)
{
char msg[128];

// We're going to die anyway, so disable break
ilog_break=0;

if(!curvm)
	{
	Bug("All virtual machines shut down.\n");
	return;
	}

VMbug();

ilog_quiet(">>> VIRTUAL MACHINE CRASHED...\n");
ilog_quiet(">>> DISASSEMBLY FOLLOWS...\n");
ilog_quiet(">>> IP = 0x%p\n",(lastip-curvm->code)*sizeof(VMTYPE));
ilog_quiet("==========================\n");
//DebugVM=1;
DumpVM(1);
ilog_quiet("==========================\n");

sprintf(msg,"In function %s in file %s, check bootlog.txt",curvm->name,curvm->file);
ithe_panic(err,msg);
}

// AddVM - create a new VM at the top of the stack

PEVM *AddVM(PEM *f)
{
PEVM *p;
// Absorb spurious abort codes
abortkernel=0;

if(vmstack_ptr<(VM_STACKLIMIT-1))
	{
	p=&vmstack[++vmstack_ptr];
	p->size = f->size;
	p->codelen = f->codelen;
//	p->code = (VMTYPE *)M_get(1,p->size);
	p->code = (VMTYPE *)VMget(p->size);
	if(!p->code)
		{
		Bang("Out of VCPU memory"); // time to die
		return NULL;
		}
	memcpy(p->code,f->code,p->size);
	p->name = f->name;
	p->file = f->file;
	p->ip = p->code;
	return p;
	}

// Oh shit.
Bang("Stack overflow.. too much recursion"); // time to die
return NULL;
}

// DelVM - pop the top Virtual Machine off the stack

void DelVM()
{
// Kill the running VM
abortkernel=1;

if(vmstack_ptr>0)
	{
	// Free existing VM
//	M_free(curvm->code);
	VMfree(curvm->code); // Free the VM
	// Get new current VM context and go away
	curvm=&vmstack[--vmstack_ptr];
	return;
	}

Bug("VM: Stack underflow.. no VM to delete");
}

void DumpVM(int force)
{
int ctr,len;
unsigned int proglen;
int data;
char *func;
unsigned int opcode,op;
VMTYPE *oldip;

if(!DebugVM && !force)
	return;

if(!curvm)
	{
	ilog_quiet("DumpVM: Not running.\n");
	return;
	}

if(!curvm->code)
	return;

ilog_quiet("(%s)\n",curvm->name);
ilog_quiet("(%x-%x)\n",(unsigned int)curvm->code,(unsigned int)curvm->code+(unsigned int)curvm->size);

oldip = curvm->ip;
curvm->ip = curvm->code;
proglen = (unsigned int)curvm->codelen + (unsigned int)curvm->ip;
//proglen = curvm->codelen + curvm->ip;

for(;(unsigned int)curvm->ip<proglen;)
	{
	if(curvm->ip == lastip)
		ilog_quiet("-> ");
	else
		ilog_quiet("   ");
	// Calculate address
	ilog_quiet("%04x ",(curvm->ip-curvm->code)*sizeof(VMTYPE));
	opcode = (*curvm->ip++).u32; // Fetch
	func=pe_get_function(opcode);
	ilog_quiet(" [%02x] %s ",opcode,&func[1]); // Skip first byte (no of operands)
	len = func[0];
	for(ctr=0;ctr<len;ctr++)
		{
		M_chk(curvm->ip);
//		op = (*curvm->ip).u32;
		op=GET_BYTE();
		UNGET_BYTE();
		switch(op)
			{
			case ACC_IMMEDIATE:
			data=GET_DWORD();
			// Hack to get a function name if applicable
			if(opcode== PEVM_Callfunc && data>0 && data<PEtot)
				ilog_quiet("'%s' ",PElist[data].name);
			else
				ilog_quiet("%d ",data);
			break;

			case ACC_INDIRECT:
			ilog_quiet("[%x] ",GET_DWORD());
			break;

			case ACC_MEMBER:
			ilog_quiet("[%x] ",SAFE_INT());
			break;

			case ACC_ARRAY:
			ilog_quiet("[%x] ",SAFE_INT());
			break;

			case ACC_ARRAY | ACC_MEMBER:
			ilog_quiet("[%x] ",SAFE_INT());
			break;

			case ACC_IMMSTR:
			ilog_quiet("\"%s\" ",GET_STRING());
			break;

			case ACC_JUMP:
			ilog_quiet(" jmp->%x ",GET_DWORD());
			break;

			case ACC_OPERATOR|1:
			GET_BYTE();
			break;

			default:
			if(op & ACC_OPERATOR)
				{
				ilog_quiet("%s ",oplist[op&OPMASK]);
				GET_BYTE();
				}
			else
				ilog_quiet("??%x?? ",op);
			};
		}
	if(len == -1)
		{
		ilog_quiet("%d ",GET_PTR());
		ilog_quiet("[%p] ",GET_PTR());
		}
	ilog_quiet("\n");
	}

curvm->ip = oldip;

}

// Helper functions for VM opcodes

void SI_RecursiveLoop(OBJECT *obj, int funccall)
{
OBJECT *temp;

for(temp = obj;temp;temp=temp->next)
	if(temp->flags.on)
		{
		// Search cancelled?
		if(!SI_Search)
			return;
		current_object = temp;
		CallVMnum(funccall);
		if(temp->pocket)
			SI_RecursiveLoop(temp->pocket,funccall);
		}
}

/*
 *	Now the opcodes:
 */

void PV_Invalid()
{
char msg[128];
sprintf(msg,"Invalid VM opcode 0x%02x",((*lastip).u32)&0xff);
Bang(msg);
}

void PV_Return()
{

DelVM();
if(DebugVM)
	DumpVM(0);
/*
if(curvm)
	ilog_quiet("VM now : %s\n",curvm->name);
else
	ilog_quiet("VM now empty\n");
*/
}

void PV_Printstr()
{
char *str;
str = GET_STRING();
//CHECK_POINTER(str);
if(!str)
	str="<null>";
irecon_printf(str); // Print it
if(DebugVM || printlogging)
	ilog_quiet("%s",str);
}

void PV_Printint()
{
int *num;

num = GET_INT();
CHECK_POINTER(num);

irecon_printf("%d",*num);
if(DebugVM || printlogging)
	ilog_quiet("%d",*num);
}


void PV_PrintXint()
{
int *num;
num = GET_INT();
CHECK_POINTER(num);

irecon_printf("%08x",*num);       // Print it
if(DebugVM || printlogging)
	ilog_quiet("%08x",*num);
}

void PV_PrintCR()
{
irecon_newline();
if(DebugVM || printlogging)
	ilog_quiet("\n");
}

void PV_ClearLine()
{
irecon_cls();
}

void PV_Callfunc()
{
int *func;

func = GET_INT();
CHECK_POINTER(func);
//if(DebugVM)
//	ilog_quiet("calling %d/%d\n",*func,PEtot);
CallVMnum(*func);
}

void PV_CallfuncS()
{
char *func;

func = GET_STRING();
CHECK_POINTER(func);
//if(DebugVM)
//	ilog_quiet("calling %s\n",func);
CallVM(func);
}

// LET <int> equal <number>

void PV_Let_iei()
{
int *a,*b;

a = GET_INT();
CHECK_POINTER(a);
b = GET_INT();
CHECK_POINTER(b);

*a = *b;
}

// LET <int> equal <int> [operator] <int>

void PV_Let_ieipi()
{
int *a,*b,*c;
unsigned char op;

a = GET_INT();
CHECK_POINTER(a);
b = GET_INT();
CHECK_POINTER(b);
op = GET_BYTE();
c = GET_INT();
CHECK_POINTER(c);

*a = Operator[op&OPMASK](*b,*c);
}

// LET <stringPtr> equal "string literal"

void PV_Let_pes()
{
int **a,function;
char *b;
VMTYPE *p1,*p2;
unsigned int offset;

a = (int **)GET_INT();
CHECK_POINTER(a);

b = GET_STRING();
CHECK_POINTER(b);

// Now we have the address of the string in the VCPU copy of the function
// Find the address in the original binary code

// First get the ID number of the currently running function
function = getnum4PE(curvm->name);
// I feel my eyes go really wide.
if(function == -1)
	ithe_panic("current function does not exist in let_pes",curvm->name);

// Now, work out the offset of the string into the bytecode
p1=(VMTYPE *)b;
p2=(VMTYPE *)curvm->code;
offset = (unsigned int)(p1 - p2); // The offset is in words not bytes

// Add this offset to the master copy and we should have the string
p1 = ADD_IP(PElist[function].code,offset*sizeof(VMTYPE*));

// Shove the address of the string into the variable
*a = (int *)p1;
}


// ADD <int> [operator] <int>

void PV_Add()
{
int *a,*b;
unsigned char op;

a = GET_INT();
CHECK_POINTER(a);
op = GET_BYTE();
b = GET_INT();
CHECK_POINTER(b);

*a = Operator[op&OPMASK](*a,*b);
}


// Goto

void PV_Goto()
{
int jumpoffset;

jumpoffset = GET_DWORD();
curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF <int> = <number> then continue, else jump to offset

void PV_If_iei()
{
int jumpoffset,*a,*b;
unsigned char op;

a = (int *)GET_INT();
CHECK_POINTER(a);
op=GET_BYTE();
//ilog_quiet("Operator %d\n",op&OPMASK);
b = (int *)GET_INT();
CHECK_POINTER(b);
jumpoffset = GET_DWORD();

if(!Operator[op&OPMASK](*a,*b))
	{
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
	}
}

// IF <string> = <string> then continue, else jump to offset

void PV_If_ses()
{
int jumpoffset;
char *a,*b;
unsigned char op;

a = GET_STRING();
CHECK_POINTER(a);
op=GET_BYTE();
b = GET_STRING();
CHECK_POINTER(b);
jumpoffset = GET_DWORD();

// Do the comparison, then compare to 0 via given op.  If zero, jump
if(!Operator[op&OPMASK](istricmp_fuzzy(a,b),0))
	{
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
	}
}

// IF <object> Is Called <string> then continue, else jump to offset
// The string is stored first, not second

void PV_If_oics()
{
int jumpoffset;
char *str;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
str = GET_STRING();
CHECK_POINTER(str);
jumpoffset = GET_DWORD();

// If there was an accident, assume it's not true and continue

if(!*obj)
	{
	Bug("If object is called '%s': Object has not been initialised!\n",str);
	DumpVM(1);
	VMbug();
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
	return; // Leave quickly
	}

// Ok, do the condition

if(istricmp_fuzzy((*obj)->name,str))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF <object> = <object> then continue, else jump to offset

void PV_If_oeo()
{
int jumpoffset;
OBJECT **a,**b,*c1,*c2;

a = CHECK_OBJ();
b = CHECK_OBJ();
jumpoffset = GET_DWORD();

// Just because it's NULL doesn't mean it can't be equal

if(a)
	c1=*a;
else
	c1=(OBJECT *)a;

if(b)
	c2=*b;
else
	c2=(OBJECT *)b;

// Ok, try the condition

if(!(c1 == c2))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF <object> != <object> then continue, else jump to offset

void PV_If_oEo()
{
int jumpoffset;
OBJECT **a,**b,*c1,*c2;

a = CHECK_OBJ();
b = CHECK_OBJ();
jumpoffset = GET_DWORD();

// Just because it's NULL doesn't mean it can't be equal

if(a)
	c1=*a;
else
	c1=(OBJECT *)a;

if(b)
	c2=*b;
else
	c2=(OBJECT *)b;

// Ok, try the condition

if(!(c1 != c2))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF <object> then continue, else jump to offset

void PV_If_o()
{
int jumpoffset;
OBJECT **obj;
int ok;

obj = CHECK_OBJ();
jumpoffset = GET_DWORD();

ok=0;
if(obj)
	if(*obj)
		ok=1;

if(!ok)
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

void PV_If_no()
{
int jumpoffset;
OBJECT **obj;
int ok;

obj = CHECK_OBJ();
jumpoffset = GET_DWORD();

ok=0;
if(obj)
	if(*obj)
		ok=1;

if(ok)
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}


// FOR <integer> = <number> TO <number> (continue, unless false, then jump exit)

void PV_For()
{
int jumpoffset,*control,*start,*end,delta,oob;
VMTYPE *firsttime;

control = GET_INT();
CHECK_POINTER(control);
start = GET_INT();
CHECK_POINTER(start);
end = GET_INT();
CHECK_POINTER(end);

firsttime = curvm->ip++;      // This is the 'first time' flag
jumpoffset = GET_DWORD();

if(*start<*end)
    delta=1;
else
    delta=-1;

// Kludge to make it go the whole range

if((*firsttime).u32 & 1)
    {
    *control-=delta;
    (*firsttime).u32 = ACC_OPERATOR;
    }

*control+=delta;

// Check for out of bounds

oob=0;
if(delta > 0)
   {
   if(*control > *end)
       oob=1;
   }
else
   {
   if(*control < *end)
       oob=1;
   }

// If we're out of bounds, stop

if(oob)
    {
    curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
    (*firsttime).u32=1|ACC_OPERATOR;           // Reset the first-time flag
    }
}

// Search inside an object's pockets calling a function as we go

void PV_Searchin()
{
OBJECT **obj;
OBJREGS objvars;
int func;

// First get the object we're going to search inside of

obj = GET_OBJECT();
CHECK_POINTER(obj);
func = GET_DWORD();

if(!*obj)
	{
	Bug("Search_inside Obj calling <func>: Obj not initialised!\n");
	DumpVM(1);
	VMbug();
	return;
	}

VM_SaveRegs(&objvars);
SI_Search=1;
SI_RecursiveLoop((*obj)->pocket,func);
SI_Search=0;
VM_RestoreRegs(&objvars);
}

// List all objects after this one calling a function as we go

void PV_Listafter()
{
OBJECT **obj;
OBJECT *ptr;
OBJREGS objvars;
int func;

// First get the object we're going to search

obj = GET_OBJECT();
CHECK_POINTER(obj);
func = GET_DWORD();

if(!*obj)
	{
	Bug("List_After Obj calling <func>: Obj not initialised!\n");
	DumpVM(1);
	VMbug();
	return;
	}

VM_SaveRegs(&objvars);
SI_Search=1;
for(ptr=*obj;ptr;ptr=ptr->next)
	{
	// Search cancelled?
	if(!SI_Search)
		break;
	current_object=ptr;
	CallVMnum(func);
	}
SI_Search=0;
VM_RestoreRegs(&objvars);
}

// Search all the world calling a function as we go

void PV_SearchWorld()
{
OBJLIST *t;
OBJREGS objvars;
int func;

func = GET_DWORD();

VM_SaveRegs(&objvars);
SI_Search=1;
for(t=MasterList;t;t=t->next)
	{
	// Search cancelled?
	if(!SI_Search)
		break;
	if(t->ptr)
		if(t->ptr->flags.on)
			{
			current_object = t->ptr;
			CallVMnum(func);
			}
	}
VM_RestoreRegs(&objvars);
}

// Stop a search if one is in progress

void PV_StopSearch()
{
SI_Search=0;
}


void PV_Create()
{
OBJECT **obj,*temp;
int *var;

obj = GET_OBJECT();
CHECK_POINTER(obj);
var = GET_INT();
CHECK_POINTER(var);

// Is it decorative?
temp = OB_Decor_num(*var);
if(!temp)
	{
	temp = OB_Alloc();
	temp->curdir = temp->dir[0];
	OB_Init(temp,CHlist[*var].name);
	OB_SetDir(temp,CHAR_D,1);
	TransferToPocket(temp,syspocket);
	CreateContents(temp);

	*obj = temp;
	}
else
	*obj = NULL;
}

void PV_CreateS()
{
OBJECT **obj,*temp;
char *str;
int type;

obj = GET_OBJECT();
CHECK_POINTER(obj);
str = GET_STRING();
CHECK_POINTER(str);

type = getnum4char(str);
if(type == -1)
	{
	Bug("Create obj = %s : Could not find character '%s'\n",str,str);
	DumpVM(1);
	VMbug();
	*obj = NULL;
	return;
	}

// Is it decorative?
temp = OB_Decor(str);
if(!temp)
	{
	temp = OB_Alloc();
	temp->curdir = temp->dir[0];
	OB_Init(temp,CHlist[type].name);
	OB_SetDir(temp,CHAR_D,1);
	TransferToPocket(temp,syspocket);
	CreateContents(temp);
	*obj = temp;
	}
else
	*obj = NULL;
}

void PV_Destroy()
{
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);

if(*obj == player)
	{
	Bug("Attempted to destroy the player!\n");
	VMbug();
	return;
	}

if(*obj)
	{
	if((*obj)->flags.decor)	// Mustn't remove a decor, that's bad
		return;

//	ilog_printf("pevm: destroy %s\n",(*obj)->name);
//	ilog_printf("pevm: destroy %p (%s)\n",(*obj),(*obj)->name);
	DeleteObject(*obj);
//	(*obj)->flags.on = 0;   // Mark Pending-delete
//	pending_delete=1;       // Tell the garbage collector it will be needed
	}

*obj = NULL;
}


void PV_PlayMusic()
{
S_PlayMusic(GET_STRING());
}

void PV_StopMusic()
{
S_StopMusic();
}

void PV_IsPlaying()
{
int *a;

a = GET_INT();
CHECK_POINTER(a);

// Get result of music player
*a=S_IsPlaying();
}

void PV_PlaySound()
{
S_PlaySample(GET_STRING(),sf_volume);
}

void PV_ObjSound()
{
OBJECT **obj;
char *str;

str = GET_STRING();
CHECK_POINTER(str);
obj = GET_OBJECT();
CHECK_POINTER(obj);

object_sound(str, *obj);
//S_PlaySample(GET_STRING(),sf_volume);
}

void PV_SetDarkness()
{
int *var,dl;
var = GET_INT();
CHECK_POINTER(var);
dl = *var;
if(dl<0)
	dl=0;
if(dl>255)
	dl=255;

SetDarkness(dl);
}

// set_flag <object> <flag> = <i>

void PV_SetFlag()
{
int *var,*flag;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
flag = GET_INT();
CHECK_POINTER(flag);
var = GET_INT();
CHECK_POINTER(var);

if((*obj)->flags.decor)	// Mustn't change a decor, that's bad
	return;
set_flag(*obj,*flag,*var);
}

// get_flag <i> = <object> <flag>

void PV_GetFlag()
{
int *var,*flag;
OBJECT **obj;

var = GET_INT();
CHECK_POINTER(var);
obj = GET_OBJECT();
CHECK_POINTER(obj);
flag = GET_INT();
CHECK_POINTER(flag);

*var = get_flag(*obj,*flag);
}

// reset_flag <object> <flag>

void PV_ResetFlag()
{
int *flag;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
flag = GET_INT();
CHECK_POINTER(flag);

if((*obj)->flags.decor)	// Mustn't change a decor, that's bad
	return;

default_flag(*obj,*flag);
}

// IF <object> <flag> then continue, else jump to offset

void PV_If_on()
{
int jumpoffset,*flag;
OBJECT **obj;

obj = CHECK_OBJ();	// Allow a NULL object

flag = GET_INT();
CHECK_POINTER(flag);
jumpoffset = GET_DWORD();

// If we don't have a valid object, return false
if(!obj)
	{
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
	return;
	}

if(!get_flag(*obj,*flag))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF NOT <object> <flag> then continue, else jump to offset

void PV_If_non()
{
int jumpoffset,*flag;
OBJECT **obj;

obj = CHECK_OBJ();	// Allow a NULL object

flag = GET_INT();
CHECK_POINTER(flag);
jumpoffset = GET_DWORD();

// If it's null, assume it's false (do nothing)
if(!obj)
	return;

if(get_flag(*obj,*flag))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF <tile> <flag> then continue, else jump to offset

void PV_If_tn()
{
int jumpoffset,*flag;
TILE **tile;

tile = (TILE **)GET_OBJECT();
CHECK_POINTER(tile);
flag = GET_INT();
CHECK_POINTER(flag);
jumpoffset = GET_DWORD();

if(!get_tileflag(*tile,*flag))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF NOT <tile> <flag> then continue, else jump to offset

void PV_If_ntn()
{
int jumpoffset,*flag;
TILE **tile;

tile = (TILE **)GET_OBJECT();
CHECK_POINTER(tile);
flag = GET_INT();
CHECK_POINTER(flag);
jumpoffset = GET_DWORD();

if(get_tileflag(*tile,*flag))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// Move <object> <x> <y> and set pe_result_ok accordingly

void PV_MoveObject()
{
int *x,*y;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

// Do it and record the result

if(MoveObject(*obj,*x,*y,0))
	pevm_err=0;
else
	pevm_err=-1;
}


// Move <object> <x> <y> (slightly more forcibly) and set pe_result_ok accordingly

void PV_PushObject()
{
int *x,*y;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

// Do it and record the result

if(MoveObject(*obj,*x,*y,1))
	pevm_err=0;
else
	pevm_err=-1;
}

// Forcibly move <object> <x> <y>

void PV_XferObject()
{
int *x,*y;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

transfer_object(*obj,*x,*y);
}

// Display <object> onscreen at <x> <y>

void PV_ShowObject()
{
int *x,*y;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

if(!*obj)
	return;
draw_rle_sprite(swapscreen,(*obj)->form->seq[0]->image,*x,*y);
}

void PV_PrintXYs()
{
char *str;
str = GET_STRING();
CHECK_POINTER(str);

irecon_printxy(cur_x,cur_y,str); // Print it
cur_x += text_length(curfont,str);
}

void PV_PrintXYi()
{
int *num;
char str[128];

num = GET_INT();
CHECK_POINTER(num);

sprintf(str,"%d",*num);
irecon_printxy(cur_x,cur_y,str); // Print it
cur_x += text_length(curfont,str);
}

void PV_PrintXYx()
{
int *num;
char str[128];

num = GET_INT();
CHECK_POINTER(num);

sprintf(str,"%08x",*num);
irecon_printxy(cur_x,cur_y,str); // Print it
cur_x += text_length(curfont,str);
}

void PV_PrintXYcr()
{
cur_x = cur_margin;
cur_y += text_height(curfont);
}

void PV_GotoXY()
{
int *x,*y;

x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

cur_x = *x;
cur_margin = cur_x;
cur_y = *y;
}

void PV_TextColour()
{
int *r,*g,*b;

r = GET_INT();
CHECK_POINTER(r);
g = GET_INT();
CHECK_POINTER(g);
b = GET_INT();
CHECK_POINTER(b);

irecon_colour(*r,*g,*b);
}

void PV_TextFont()
{
int *f;

f = GET_INT();
CHECK_POINTER(f);

irecon_font(*f);
}


void PV_SetSequenceS()
{
char *str;
OBJECT **obj;
obj = GET_OBJECT();
CHECK_POINTER(obj);
str = GET_STRING();
CHECK_POINTER(str);

OB_SetSeq(*obj,str);
}

void PV_SetSequenceN()
{
OBJECT **obj;
int *n;
obj = GET_OBJECT();
CHECK_POINTER(obj);
n = GET_INT();
CHECK_POINTER(n);

OB_SetSeq(*obj,SQlist[*n].name);
}

void PV_Lightning()
{
int *n;

n=GET_INT();
CHECK_POINTER(n);

do_lightning=*n;
}

void PV_Earthquake()
{
int *n;

n=GET_INT();
CHECK_POINTER(n);

do_earthquake=*n;
}

void PV_Bestname()
{
OBJECT **obj;
obj = GET_OBJECT();
CHECK_POINTER(obj);

irecon_printf(BestName(*obj)); // Print it
if(DebugVM || printlogging)
	ilog_quiet(BestName(*obj));
}

void PV_GetObject()
{
OBJECT **obj;
int *x,*y;
obj = (OBJECT **)GET_INT(); // Hack: get pointer without final NULL check
CHECK_POINTER(obj);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

*obj = GameGetObject(*x,*y);
}

void PV_GetSolidObject()
{
OBJECT **obj;
int *x,*y;
obj = (OBJECT **)GET_INT(); // Hack: get pointer without final NULL check
CHECK_POINTER(obj);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

*obj = GetSolidObject(*x,*y);
}

void PV_GetFirstObject()
{
OBJECT **obj;
int *x,*y;
obj = (OBJECT **)GET_INT(); // Hack: get pointer without final NULL check
CHECK_POINTER(obj);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

*obj = GetObjectBase(*x,*y);
}

void PV_GetTile()
{
TILE **tile;
int *x,*y;
tile = (TILE **)GET_INT(); // Hack: get pointer without final NULL check
CHECK_POINTER(tile);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

*tile = GetTile(*x,*y);
}

void PV_GetObjectBelow()
{
OBJECT **obj1,**obj2;
obj1 = (OBJECT **)GET_INT(); // Hack: get pointer without final NULL check
CHECK_POINTER(obj1);
obj2 = GET_OBJECT();
CHECK_POINTER(obj2);

*obj1 = get_object_below(*obj2);
}

void PV_ChangeObject()
{
OBJECT **obj;
int *var;

STATS ostats;
char resname[32];
char pname[32];
char speech[128];
char user1[128];
char user2[128];
int tcache;
int partie;     // Must preserve this for dead party members

obj = GET_OBJECT();
CHECK_POINTER(obj);
CHECK_POINTER(*obj);

var = GET_INT();
CHECK_POINTER(var);

if(!*obj)
	{
	if(*var)
		Bug("ChangeObject <null> %s\n",CHlist[*var].name);
	else
		Bug("ChangeObject <null> <null>\n");
	return;
	}

partie = (*obj)->flags.party;
memcpy(&ostats,(*obj)->stats,sizeof(STATS));
strcpy(resname,(*obj)->funcs->resurrect);
strcpy(speech,(*obj)->funcs->talk);
strcpy(user1,(*obj)->funcs->user1);
strcpy(user2,(*obj)->funcs->user2);
tcache = (*obj)->funcs->tcache;
strcpy(pname,(*obj)->personalname);
OB_Init(*obj,CHlist[*var].name);
memcpy((*obj)->stats,&ostats,sizeof(STATS));
strcpy((*obj)->funcs->resurrect,resname);
strcpy((*obj)->personalname,pname);
strcpy((*obj)->funcs->talk,speech);
strcpy((*obj)->funcs->user1,user1);
strcpy((*obj)->funcs->user2,user2);
(*obj)->flags.party = partie;
(*obj)->funcs->tcache = tcache;
AL_Add(&ActiveList,*obj); // Make active (if it can be)
}


void PV_ChangeObjectS()
{
OBJECT **obj;
char *str;
int type;

STATS ostats;
char resname[32];
char pname[32];
char speech[128];
int tcache;
int partie;     // Must preserve this for dead party members

obj = GET_OBJECT();
CHECK_POINTER(obj);
CHECK_POINTER(*obj);
str = GET_STRING();
CHECK_POINTER(str);

type = getnum4char(str);
if(type == -1)
	{
	Bug("Change obj %s : Could not find character '%s'\n",str,str);
	DumpVM(1);
	VMbug();
	return;
	}

partie = (*obj)->flags.party;
memcpy(&ostats,(*obj)->stats,sizeof(STATS));
strcpy(resname,(*obj)->funcs->resurrect);
strcpy(speech,(*obj)->funcs->talk);
tcache = (*obj)->funcs->tcache;
strcpy(pname,(*obj)->personalname);
OB_Init(*obj,CHlist[type].name);
memcpy((*obj)->stats,&ostats,sizeof(STATS));
strcpy((*obj)->funcs->resurrect,resname);
strcpy((*obj)->personalname,pname);
strcpy((*obj)->funcs->talk,speech);
(*obj)->flags.party = partie;
(*obj)->funcs->tcache = tcache;
AL_Add(&ActiveList,*obj); // Make active (if it can be)
}

void PV_ReplaceObject()
{
OBJECT **obj;
int *var,k;

obj = GET_OBJECT();
CHECK_POINTER(obj);
CHECK_POINTER(*obj);
var = GET_INT();
CHECK_POINTER(var);

k=fullrestore;
fullrestore=0;
OB_Init(*obj,CHlist[*var].name);
fullrestore=k;
gen_largemap();     // Recalc solid objects cache
AL_Add(&ActiveList,*obj); // Make active (if it can be)
}

void PV_ReplaceObjectS()
{
OBJECT **obj;
int type,k;
char *str;

obj = GET_OBJECT();
CHECK_POINTER(obj);
CHECK_POINTER(*obj);
str = GET_STRING();
CHECK_POINTER(str);

type = getnum4char(str);
if(type == -1)
	{
	Bug("Replace obj %s : Could not find character '%s'\n",str,str);
	DumpVM(1);
	VMbug();
	return;
	}

k=fullrestore;
fullrestore=0;
OB_Init(*obj,CHlist[type].name);
fullrestore=k;
gen_largemap();     // Recalc solid objects cache
AL_Add(&ActiveList,*obj); // Make active (if it can be)
}

void PV_SetDir()
{
OBJECT **obj;
int *n;
obj = GET_OBJECT();
CHECK_POINTER(obj);
n = GET_INT();
CHECK_POINTER(n);
pevm_err=OB_SetDir(*obj,*n,0);
}

void PV_RedrawText()
{
irecon_update();            // Redraw all text output
Show();                     // And blit the screen again
}

void PV_RedrawMap()
{
RedrawMap();
Show();                     // And blit the screen again
}

// Forcibly move <object> <to> <pocket>

void PV_MoveToPocket()
{
OBJECT **pocket,**obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
pocket = GET_OBJECT();
CHECK_POINTER(pocket);

move_to_pocket(*obj,*pocket);
}

// Forcibly move <object> <to> <pocket>

void PV_XferToPocket()
{
OBJECT **pocket,**obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
pocket = GET_OBJECT();
CHECK_POINTER(pocket);

transfer_to_pocket(*obj,*pocket);
}

// move <object> <from> <pocket> to <x> <y>

void PV_MoveFromPocket()
{
OBJECT **pocket,**obj;
int *x,*y;

obj = GET_OBJECT();
CHECK_POINTER(obj);
pocket = GET_OBJECT();
CHECK_POINTER(pocket);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

if(MoveFromPocket(*obj,*pocket,*x,*y))
	pevm_err=0;
else
	pevm_err=-1;
}

// Forcibly move <object> <from> <pocket> to <x> <y>

void PV_ForceFromPocket()
{
OBJECT **pocket,**obj;
int *x,*y;

obj = GET_OBJECT();
CHECK_POINTER(obj);
pocket = GET_OBJECT();
CHECK_POINTER(pocket);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

ForceFromPocket(*obj,*pocket,*x,*y);
}

// Spill contents of <object> at current location

void PV_Spill()
{
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);

spillcontents(*obj,(*obj)->x,(*obj)->y);
}

// Spill contents of <object> at <X> <Y>

void PV_SpillXY()
{
OBJECT **obj;
int *x,*y;

obj = GET_OBJECT();
CHECK_POINTER(obj);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

spillcontents(*obj,*x,*y);
}

// Fade Out

void PV_FadeOut()
{
vrmfade_out();
}

// Fade Out

void PV_FadeIn()
{
vrmfade_in();
}

// Move object to top of the heap

void PV_MoveToTop()
{
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);

MoveToTop(*obj);
}

// Move object to bottom of the heap

void PV_MoveToFloor()
{
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);

MoveToFloor(*obj);
}

// Get line-of-sight between two objects, return number of steps

void PV_GetLOS()
{
int *x;
OBJECT **obj1,**obj2;

x = GET_INT();
CHECK_POINTER(x);
obj1 = GET_OBJECT();
CHECK_POINTER(obj1);
obj2 = GET_OBJECT();
CHECK_POINTER(obj2);

if(!*obj1)
	{
	Bug("If_line_of_sight: Object1 has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}
if(!*obj2)
	{
	Bug("If_line_of_sight: Object2 has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

// Ok, do it

*x=line_of_sight((*obj1)->x,(*obj1)->y,(*obj2)->x,(*obj2)->y);
}

// Check if an object has been hurt

void PV_CheckHurt()
{
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);

CheckHurt(*obj);
}

// IF a specified square is solid then continue, else jump to offset

void PV_IfSolid()
{
int jumpoffset,*a,*b;

a = (int *)GET_INT();
CHECK_POINTER(a);
b = (int *)GET_INT();
CHECK_POINTER(b);
jumpoffset = GET_DWORD();

if(!isSolid(*a,*b))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF a specified square can be seen then continue, else jump to offset

void PV_IfVisible()
{
int jumpoffset,*a,*b;

a = (int *)GET_INT();
CHECK_POINTER(a);
b = (int *)GET_INT();
CHECK_POINTER(b);
jumpoffset = GET_DWORD();

if(isBlanked(*a,*b))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump

}

// set_user_flag <string> = <i>

void PV_SetUFlag()
{
int *var;
char *str;

str = GET_STRING();
CHECK_POINTER(str);
var = GET_INT();
CHECK_POINTER(var);

Set_tFlag(str,*var);
}

// get_user_flag <i> = <string>

void PV_GetUFlag()
{
int *var;
char *str;

var = GET_INT();
CHECK_POINTER(var);
str = GET_STRING();
CHECK_POINTER(str);

*var = Get_tFlag(str);
}

// set_local <person> <flag> = <state>

void PV_SetLocal()
{
int *var;
char *str;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
str = GET_STRING();
CHECK_POINTER(str);
var = GET_INT();
CHECK_POINTER(var);

NPC_set_lFlag(*obj,str,player,*var);
}

// get_local <var> = <person> <flag>

void PV_GetLocal()
{
int *var;
char *str;
OBJECT **obj;

var = GET_INT();
CHECK_POINTER(var);
obj = GET_OBJECT();
CHECK_POINTER(obj);
str = GET_STRING();
CHECK_POINTER(str);

*var = NPC_get_lFlag(*obj,str,player);
}

// weigh_object <var> = <object>

void PV_WeighObject()
{
int *var;
OBJECT **obj;

var = GET_INT();
CHECK_POINTER(var);
obj = GET_OBJECT();
CHECK_POINTER(obj);

*var = WeighObject(*obj);
}

// get_bulk <var> = <object>

void PV_GetBulk()
{
int *var;
OBJECT **obj;

var = GET_INT();
CHECK_POINTER(var);
obj = GET_OBJECT();
CHECK_POINTER(obj);

*var = GetBulk(*obj);
}

// input <intvar>

void PV_InputInt()
{
int *var;

var = GET_INT();
CHECK_POINTER(var);

if(!get_num(*var))
    *var = 0;
else
	*var = atoi(user_input);
}

// IF object is in a pocket then continue, else jump to offset

void PV_IfInPocket()
{
int jumpoffset;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);

jumpoffset = GET_DWORD();

// If there was an accident, assume it's not true and continue

if(!*obj)
	{
	Bug("If_In_Pocket: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
	return; // Leave quickly
	}

// Ok, do the condition

if(!(*obj)->parent)
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// IF object is NOT in a pocket then continue, else jump to offset

void PV_IfNInPocket()
{
int jumpoffset;
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
jumpoffset = GET_DWORD();

// If there was an accident, assume it's not true and continue

if(!*obj)
	{
	Bug("If_In_Pocket: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
	return; // Leave quickly
	}

// Ok, do the condition

if((*obj)->parent)
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

void PV_ReSyncEverything()
{
ResyncEverything();
}

// Talk to (file, startpage)

void PV_TalkTo2()
{
char *str1,*str2;

str1 = GET_STRING();
CHECK_POINTER(str1);
str2 = GET_STRING();
CHECK_POINTER(str2);

talk_to(str1,str2); // Str1 is null-protected
}

// Talk to (file)

void PV_TalkTo1()
{
char *str1;

str1 = GET_STRING();
CHECK_POINTER(str1);
talk_to(str1,NULL);
}

// random <var> max <maxnum>

void PV_Random()
{
int *var;
int *min;
int *max,tot;

var = GET_INT();
CHECK_POINTER(var);
min = GET_INT();
CHECK_POINTER(min);
max = GET_INT();
CHECK_POINTER(max);

if(*max < *min)
	{
	*var = 1; // Prevent division by zero
	Bug("Random: maximum (%d) is less than minimum (%d)!\n",*max,*min);
	DumpVM(1);
	VMbug();
	return;
	}

tot = (*max)-(*min);

if(tot<1)
	{
	*var = 1; // Prevent division by zero
	Bug("Random: maximum and minimum are less than 1\n");
	DumpVM(1);
	VMbug();
	}

if(*max > 0)
	*var = (rand()%tot)+(*min);
else
	{
	*var = 1; // Prevent division by zero
	Bug("Random: maximum is less than 1!\n");
	DumpVM(1);
	VMbug();
	}
}

void PV_GetYN()
{
int jumpoffset;
char *str;

str = GET_STRING();
CHECK_POINTER(str);
jumpoffset = GET_DWORD();

// Ok, do the condition

if(!getYN(str))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

void PV_GetYNN()
{
int jumpoffset;
char *str;

str = GET_STRING();
CHECK_POINTER(str);
jumpoffset = GET_DWORD();

// Ok, do the condition

if(getYN(str))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

void PV_Restart()
{
Restart();
}

// scroll_tile <num> <x> <y>

void PV_ScrTileN()
{
int *num,*x,*y;

num = GET_INT();
CHECK_POINTER(num);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

if(*num<0 || *num>=TItot)
	{
	Bug("No such tile number %d in %s\n",*num,debcurfunc);
	return;
	}

if(*x<0)
	*x=32+(*x);
if(*y<0)
	*y=32+(*y);

TIlist[*num].sdx=*x;
TIlist[*num].sdy=*y;

// If zero, reset the scroll offsets, since the user probably wants it to
// go back to normal

if(*x == 0 && *y == 0)
	{
	TIlist[*num].sx=0;
	TIlist[*num].sy=0;
	}

}

// scroll_tile "name" <x> <y>

void PV_ScrTileS()
{
int ctr,*x,*y;
char *name;

name = GET_STRING();
CHECK_POINTER(name);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

if(*x<0)
	*x=32+(*x);
if(*y<0)
	*y=32+(*y);

for(ctr=0;ctr<TItot;ctr++)
    if(!istricmp_fuzzy(TIlist[ctr].name,name))
		{
		TIlist[ctr].sdx=*x;
		TIlist[ctr].sdy=*y;

		// If zero, reset the scroll offsets, since the user probably wants it to
		// go back to normal

		if(*x == 0 && *y == 0)
			{
			TIlist[ctr].sx=0;
			TIlist[ctr].sy=0;
			}
		return;
		}

Bug("No such tile '%s' in %s\n",name,debcurfunc);
}

// While <exp> is nonzero go around the loop else fall through

void PV_While1()
{
int jumpoffset,*a;

a = (int *)GET_INT();
CHECK_POINTER(a);

jumpoffset = GET_DWORD();

if(!*a)
	; // Do Nothing
else
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// While <x> = <y> go around the loop else fall through

void PV_While2()
{
int jumpoffset,*a,*b;
unsigned char op;

a = (int *)GET_INT();
CHECK_POINTER(a);
op=GET_BYTE();
b = (int *)GET_INT();
CHECK_POINTER(b);

jumpoffset = GET_DWORD();

if(Operator[op&OPMASK](*a,*b))
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// Break from a While loop or For Loop.
// This is functionally equivalent to Goto, but has a separate opcode
// to make the disassembly clearer.

void PV_Break()
{
int jumpoffset;

jumpoffset = GET_DWORD();
curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump

}

// Wait for a key to be pressed

void PV_GetKey()
{
irekey=get_key_debounced();
}

// Wait for a key to be pressed

void PV_GetKey_quiet()
{
irekey=get_key_debounced_quiet();
}


// Set Object's current activity

void PV_DoAct()
{
OBJECT **obj;
int *act;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_INT();
CHECK_POINTER(act);

if(!*obj)
	{
	Bug("Do_Activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

//ilog_quiet("actnum %p %d NULL\n",*obj,*act);
//ilog_quiet("%s %s\n",(*obj)->name,PElist[*act].name);

if((*obj)->flags.decor)
	return;

SubAction_Wipe(*obj); // Delete any sub-actions
ActivityNum(*obj,*act,NULL);
}

// Set Object's current activity by string

void PV_DoActS()
{
OBJECT **obj;
char *act;
int pe;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_STRING();
CHECK_POINTER(act);

if(!*obj)
	{
	Bug("Do_Activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

//ilog_quiet("actstr %p %d NULL\n",*obj,getnum4PE(act));

pe = getnum4PE(act);
if(pe == -1)
	{
	Bug("Do_Activity: PE script '%s' not found in function %s\n",act,debcurfunc);
	return;
	}
SubAction_Wipe(*obj); // Delete any sub-actions
ActivityNum(*obj,pe,NULL);
}

// Set Object's current activity (with a target)

void PV_DoActTo()
{
OBJECT **obj,**target;
int *act;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_INT();
CHECK_POINTER(act);
target = GET_OBJECT();
CHECK_POINTER(target);

if(!*obj)
	{
	Bug("Do_Activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if(!*target)
	{
	Bug("Do_Activity: Target has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

//ilog_quiet("actnum %p %d %s\n",*obj,*act,(*target)->name);

SubAction_Wipe(*obj); // Delete any sub-actions
ActivityNum(*obj,*act,*target);
}

// Set Object's current activity by string (with a target)

void PV_DoActSTo()
{
OBJECT **obj,**target;
char *act;
int pe;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_STRING();
CHECK_POINTER(act);
target = GET_OBJECT();
CHECK_POINTER(target);

if(!*obj)
	{
	Bug("Do_Activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if(!*target)
	{
	Bug("Do_Activity: Target has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

//ilog_quiet("actstr %p %d %s\n",*obj,getnum4PE(act),(*target)->name);

pe = getnum4PE(act);
if(pe == -1)
	{
	Bug("Do_Activity: PE script '%s' not found in function %s\n",act,debcurfunc);
	return;
	}
SubAction_Wipe(*obj); // Delete any sub-actions
ActivityNum(*obj,pe,*target);
}


// InsertAction - Set Object's current activity WITHOUT erasing sub-tasks

// by number

void PV_InsAct()
{
OBJECT **obj;
int *act;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_INT();
CHECK_POINTER(act);

if(!*obj)
	{
	Bug("Ins_Activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

ActivityNum(*obj,*act,NULL);
}

// by string

void PV_InsActS()
{
OBJECT **obj;
char *act;
int pe;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_STRING();
CHECK_POINTER(act);

if(!*obj)
	{
	Bug("Do_Activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

pe = getnum4PE(act);
if(pe == -1)
	{
	Bug("Do_Activity: PE script '%s' not found in function %s\n",act,debcurfunc);
	return;
	}

ActivityNum(*obj,pe,NULL);
}

// by number with target

void PV_InsActTo()
{
OBJECT **obj,**target;
int *act;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_INT();
CHECK_POINTER(act);
target = GET_OBJECT();
CHECK_POINTER(target);

if(!*obj)
	{
	Bug("Do_Activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if(!*target)
	{
	Bug("Do_Activity: Target has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

ActivityNum(*obj,*act,*target);
}

// by string with target

void PV_InsActSTo()
{
OBJECT **obj,**target;
char *act;
int pe;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_STRING();
CHECK_POINTER(act);
target = GET_OBJECT();
CHECK_POINTER(target);

if(!*obj)
	{
	Bug("Do_Activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if(!*target)
	{
	Bug("Do_Activity: Target has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

pe = getnum4PE(act);
if(pe == -1)
	{
	Bug("Do_Activity: PE script '%s' not found in function %s\n",act,debcurfunc);
	return;
	}

ActivityNum(*obj,pe,*target);
}



// Halt scheduled activity (and stand there stupidly)

void PV_StopAct()
{
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);

if(!*obj)
	{
	Bug("stop_activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

//ilog_quiet("stop action for %s\n",(*obj)->name);

Inactive(*obj);
}


// Resume scheduled activity

void PV_ResumeAct()
{
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);

if(!*obj)
	{
	Bug("resume_activity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

//ilog_quiet("resume action for %s\n",(*obj)->name);

ResumeSchedule(*obj); // Anything within last hour
}

// Set Object's next sub-activity

void PV_NextAct()
{
OBJECT **obj;
int *act;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_INT();
CHECK_POINTER(act);

if(!*obj)
	{
	Bug("NextActivity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

SubAction_Push(*obj,*act,NULL);
}

// Set next subactivity by string

void PV_NextActS()
{
OBJECT **obj;
char *act;
int pe;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_STRING();
CHECK_POINTER(act);

if(!*obj)
	{
	Bug("NextActivity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

//ilog_quiet("actstr %p %d NULL\n",*obj,getnum4PE(act));

pe = getnum4PE(act);
if(pe == -1)
	{
	Bug("NextActivity: PE script '%s' not found in function %s\n",act,debcurfunc);
	return;
	}
SubAction_Push(*obj,pe,NULL);
}

// Set Object's current activity (with a target)

void PV_NextActTo()
{
OBJECT **obj,**target;
int *act;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_INT();
CHECK_POINTER(act);
target = GET_OBJECT();
CHECK_POINTER(target);

if(!*obj)
	{
	Bug("NextActivity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if(!*target)
	{
	Bug("NextActivity: Target has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

//ilog_quiet("actnum %p %d %s\n",*obj,*act,(*target)->name);

SubAction_Push(*obj,*act,*target);
}

// Set Object's current activity by string (with a target)

void PV_NextActSTo()
{
OBJECT **obj,**target;
char *act;
int pe;

obj = GET_OBJECT();
CHECK_POINTER(obj);
act = GET_STRING();
CHECK_POINTER(act);
target = GET_OBJECT();
CHECK_POINTER(target);

if(!*obj)
	{
	Bug("NextActivity: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if(!*target)
	{
	Bug("NextActivity: Target has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if((*obj)->flags.decor)
	return;

//ilog_quiet("actstr %p %d %s\n",*obj,getnum4PE(act),(*target)->name);

pe = getnum4PE(act);
if(pe == -1)
	{
	Bug("NextActivity: PE script '%s' not found in function %s\n",act,debcurfunc);
	return;
	}
SubAction_Push(*obj,pe,*target);
}


// Debugging function: Get PEscript name from a number

void PV_GetFuncP()
{
char **str;
int *a;

str = (char **)GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(str);
a = GET_INT();
CHECK_POINTER(a);

if(*a < 1 || *a > PEtot)
	*str=NULL;
else
	*str=PElist[*a].name;
}

// Debugging function: Get container for object

void PV_GetContainer()
{
OBJECT **obj1;
OBJECT **obj2;

obj1 = GET_OBJECT();
CHECK_POINTER(obj1);
obj2 = GET_OBJECT();
CHECK_POINTER(obj2);

*obj1=NULL; // Set to Nothing

if(!*obj2)
	return; // Nothing to look for

Bug("GetContainer is depreciated.. use 'object.parent' instead\n");
Bug("GetContainer used in function %s\n",debcurfunc);

*obj1 = (*obj2)->parent;
}

void PV_SetLeader()
{
OBJECT **o;
int *p,*f;

o = GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(o);
p = GET_INT();
CHECK_POINTER(p);
f = GET_INT();
CHECK_POINTER(f);

choose_leader(*o, PElist[*p].name, PElist[*f].name);
//int choose_leader(OBJECT *x, char *player_call, char *follower_call)
}

void PV_SetMember()
{
OBJECT **o;
int *p;

o = GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(o);
p = GET_INT();
CHECK_POINTER(p);

//int choose_member(OBJECT *x, char *player_call)
choose_member(*o, PElist[*p].name);
}

void PV_AddMember()
{
OBJECT **o;

o = GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(o);

AddToParty(*o);
}

void PV_DelMember()
{
OBJECT **o;

o = GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(o);

SubFromParty(*o);
ResumeSchedule(*o);

}

void PV_MoveTowards8()
{
OBJECT **o1,**o2;
char *s1,*s2;

pevm_err=-1; // Assume failure

o1 = GET_OBJECT();
CHECK_POINTER(o1);
o2 = GET_OBJECT();
CHECK_POINTER(o2);

if(!*o1 || !*o2)
	{
	s1="Null";
	s2="Null";

	if(*o1)
		s1=BestName(*o1);
	if(*o2)
		s2=BestName(*o2);
	Bug("FindPath (%s,%s)\n",s1,s2);
	return;
	}

pevm_err=FindPath(*o1,*o2,1);
}

void PV_MoveTowards4()
{
OBJECT **o1,**o2;
char *s1,*s2;

pevm_err=-1; // Assume failure

o1 = GET_OBJECT();
CHECK_POINTER(o1);
o2 = GET_OBJECT();
CHECK_POINTER(o2);

if(!*o1 || !*o2)
	{
	s1="Null";
	s2="Null";

	if(*o1)
		s1=BestName(*o1);
	if(*o2)
		s2=BestName(*o2);
	Bug("FindPath (%s,%s)\n",s1,s2);
	return;
	}

pevm_err=FindPath(*o1,*o2,0);
}

void PV_WaitForAnimation()
{
OBJECT **o;

o = GET_OBJECT();
CHECK_POINTER(o);

if((*o)->form->flags&6)	// Ignore if looped or stepped
	return;				// (Otherwise it will loop forever)

if((*o)->parent)		// Objects in pockets do not animate
	return;

for(;(*o)->sdir;RedrawMap());
}

void PV_WaitFor()
{
int *ms,*flag;
ms = GET_INT();
CHECK_POINTER(ms);
flag = GET_INT();
CHECK_POINTER(flag);
if(*flag)
	waitredraw(*ms);
else
	waitfor(*ms);
}

// IF <object> is onscreen then continue, else jump to offset

void PV_If_oonscreen()
{
int jumpoffset;
OBJECT **obj,*a;
int ok;

obj = CHECK_OBJ();
jumpoffset = GET_DWORD();

ok=0;
if(obj)
	if(*obj)
		{
		a=*obj;
		if(a->x+(a->mw-1) >=mapx)
			if(a->x <= mapx2)
				if(a->y+(a->mh-1) >=mapy)
					if(a->y <= mapy2)
						ok=1;    // Yes
		}
    
if(!ok)
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

void PV_If_not_oonscreen()
{
int jumpoffset;
OBJECT **obj,*a;
int ok;

obj = CHECK_OBJ();
jumpoffset = GET_DWORD();

ok=0;
if(obj)
	if(*obj)
		{
		a=*obj;
		if(a->x+(a->mw-1) >=mapx)
			if(a->x <= mapx2)
				if(a->y+(a->mh-1) >=mapy)
					if(a->y <= mapy2)
						ok=1;    // Yes
		}
    
if(ok)
	curvm->ip = ADD_IP(curvm->code,jumpoffset);  // Mess with IP for the jump
}

// Take quantity of objects from a container

void PV_TakeQty()
{
OBJECT **obj;
int *amount;
char *str;

amount = GET_INT();
CHECK_POINTER(amount);
str = GET_STRING();
CHECK_POINTER(str);
obj = GET_OBJECT();
CHECK_POINTER(obj);

if(!*obj)
	{
	Bug("Take: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

pevm_err=!TakeQuantity(*obj,str,*amount);
}

// Move quantity of objects from one container to another

void PV_MoveQty()
{
OBJECT **src,**dest;
int *amount;
char *str;

amount = GET_INT();
CHECK_POINTER(amount);
str = GET_STRING();
CHECK_POINTER(str);
src = GET_OBJECT();
CHECK_POINTER(src);
dest = GET_OBJECT();
CHECK_POINTER(dest);

if(!*src)
	{
	Bug("Move: source object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

if(!*dest)
	{
	Bug("Move: destination object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

pevm_err = !MoveQuantity(*src,*dest,str,*amount);
}

// Give quantity of objects to a container

void PV_GiveQty()
{
OBJECT **obj;
int *amount;
char *str;

amount = GET_INT();
CHECK_POINTER(amount);
str = GET_STRING();
CHECK_POINTER(str);
obj = GET_OBJECT();
CHECK_POINTER(obj);

if(!*obj)
	{
	Bug("Give: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

//pevm_err=!AddQuantity(*obj,str,*amount);
AddQuantity(*obj,str,*amount);
}


// Count quantity of objects in a container

void PV_CountQty()
{
OBJECT **obj;
int *amount;
char *str;

amount = GET_INT();
CHECK_POINTER(amount);
str = GET_STRING();
CHECK_POINTER(str);
obj = GET_OBJECT();
CHECK_POINTER(obj);

if(!*obj)
	{
	Bug("Count: Object has not been initialised!\n");
	DumpVM(1);
	VMbug();
	return; // Leave quickly
	}

*amount=SumObjects((*obj)->pocket,str,0);
}

void PV_CopySchedule()
{
OBJECT **o1,**o2;
char *s1,*s2;

o1 = GET_OBJECT();
CHECK_POINTER(o1);
o2 = GET_OBJECT();
CHECK_POINTER(o2);

if(!*o1 || !*o2)
	{
	s1="Null";
	s2="Null";

	if(*o1)
		s1=BestName(*o1);
	if(*o2)
		s2=BestName(*o2);
	Bug("CopySchedule(%s -> %s)\n",s1,s2);
	return;
	}

// Make sure the source has a schedule

if(!(*o1)->schedule)
	return;

// Make sure

if(!(*o2)->schedule)
	(*o2)->schedule = (SCHEDULE *)M_get(24,sizeof(SCHEDULE));

memcpy((*o2)->schedule,(*o1)->schedule,sizeof(SCHEDULE)*24);

// Copy labels too (they're just pointers to static text)
(*o2)->labels->race = (*o1)->labels->race;
(*o2)->labels->rank = (*o1)->labels->rank;
(*o2)->labels->party = (*o1)->labels->party;
}

// Call a function for all objects onscreen

void PV_AllOnscreen()
{
OBJREGS objvars;
int func,x,y;

// First get the function to call

func = GET_DWORD();

VM_SaveRegs(&objvars);

SI_Search=1;
for(y=mapy;y<mapy2;y++)
	{
	// Search cancelled?
	if(!SI_Search)
		break;
		for(x=mapx;x<mapx2;x++)
			{
			// Search cancelled?
			if(!SI_Search)
				break;
			current_object = GameGetObject(x,y);
			if(current_object)
				if(current_object->stats->hp > 0)
					if(current_object->stats->intel > 0)
						CallVMnum(func);
			}
	}
SI_Search=0;

VM_RestoreRegs(&objvars);
}

// Call a function for all objects in a certain radius
// ALL_AROUND <obj> radius <int> does <func>

void PV_AllAround()
{
OBJECT **focus;
OBJREGS objvars;
int func,sx,sy,ex,ey,x,y,*r;

// First the focus object,
focus = GET_OBJECT();
CHECK_POINTER(focus);

// Then the 'radius',
r = GET_INT();
CHECK_POINTER(r);

// Now get the function to call.
func = GET_DWORD();

// Calculate the start coordinates
sx = (*focus)->x - *r;
if(sx<0)
	sx=0;

sy = (*focus)->y - *r;
if(sy<0)
	sy=0;

// And the end coordinates
ex = (*focus)->x + *r;
if(ex>curmap->w)
	ex=curmap->w;

ey = (*focus)->y + *r;
if(ey>curmap->h)
	ey=curmap->h;

VM_SaveRegs(&objvars);

SI_Search=1;
for(y=sy;y<ey;y++)
	{
	// Search cancelled?
	if(!SI_Search)
		break;
		for(x=sx;x<ex;x++)
			{
			// Search cancelled?
			if(!SI_Search)
				break;
			current_object = GameGetObject(x,y);
			if(current_object)
				{
				if(current_object->stats->hp > 0)
					if(current_object->stats->intel > 0)
						CallVMnum(func);
				}
			}
	}
SI_Search=0;

VM_RestoreRegs(&objvars);
}


void PV_CheckTime()
{
CheckTime();
}

void PV_FindNear()
{
OBJECT **o1;
OBJECT **o2;
char *str;

o1 = GET_OBJECT();
CHECK_POINTER(o1);
str = GET_STRING();
CHECK_POINTER(str);
o2 = GET_OBJECT();
CHECK_POINTER(o2);

//ilog_quiet("find_nearest %s,%s\n",(*o2)->name,str);
*o1 = find_nearest(*o2,str);
}

// Find up to 16 nearby objects of given type (fuzzy search supported)

void PV_FindNearby()
{
OBJECT **o1;
OBJECT **o2;
char *str;
OBJECT *array;
int size,idx;

GET_ARRAY_INFO((void **)&array,&size,&idx); // Get size of array at IP

o1 = GET_OBJECT();
CHECK_POINTER(o1);
str = GET_STRING();
CHECK_POINTER(str);
o2 = GET_OBJECT();
CHECK_POINTER(o2);

// Give it the start of the array, not o1, in case it has been poisoned
find_nearby(*o2,str,(OBJECT **)array,size);
}

// Find a tag anywhere in the world

void PV_FindTag()
{
OBJLIST *t;
OBJECT **obj;
char *str;
int *tag;

obj = GET_OBJECT();
CHECK_POINTER(obj);
str = GET_STRING();
CHECK_POINTER(str);
tag = GET_INT();
CHECK_POINTER(tag);

// If it has empty string, find any matching tag

//if(strlen(str) == 0)
if(!str[0])
	str=NULL;


// Look near focussed object to start with (for speed)
if(person)
	{
	*obj=find_neartag(person,str,*tag);
	if(*obj)
		return;
	// Didn't find anything, carry on and search entire list
	}

for(t=MasterList;t;t=t->next)
	if(t->ptr->tag == *tag)
		if(str)
			{
			if(!istricmp_fuzzy(str,t->ptr->name))
				{
				*obj = t->ptr;
				return;
				}
			}
		else
			{
			*obj = t->ptr;
			return;
			}

*obj = NULL;
}

// Find a tag nearby someone

void PV_FastTag()
{
OBJECT **obj,**nearby;
int *tag;

obj = GET_OBJECT();
CHECK_POINTER(obj);
tag = GET_INT();
CHECK_POINTER(tag);
nearby = GET_OBJECT();
CHECK_POINTER(nearby);

*obj=find_neartag(*nearby,NULL,*tag);
}


// Build a list of matching tags

void PV_MakeTagList()
{
OBJLIST *t;
OBJECT **obj;
char *str;
int *tag;

OBJECT *array;
int size,idx,actr;

GET_ARRAY_INFO((void **)&array,&size,&idx); // Get size of array at IP
obj = GET_OBJECT();
CHECK_POINTER(obj);
str = GET_STRING();
CHECK_POINTER(str);
tag = GET_INT();
CHECK_POINTER(tag);


// If it has empty string, find any matching tag
if(strlen(str) == 0)
	str=NULL;

obj=(OBJECT **)array;
memset(obj,0,size*sizeof(OBJECT *)); // Blank the array
actr=0;

for(t=MasterList;t;t=t->next)
	if(t->ptr->tag == *tag)
		if(str)
			{
			if(!istricmp_fuzzy(str,t->ptr->name))
				{
				if(actr<size)
					obj[actr++] = t->ptr;
				}
			}
		else
			{
			if(actr<size)
				obj[actr++] = t->ptr;
			}

//*obj = NULL;
}

// Set light state for a region between two control objects.

#define MAX_LIGHTS 128

void PV_SetLight()
{
int *tag,*state;
OBJLIST *t;
OBJECT *start[MAX_LIGHTS],*end[MAX_LIGHTS];
int sctr,ectr;

tag = GET_INT();
CHECK_POINTER(tag);
state = GET_INT();
CHECK_POINTER(state);

sctr=0;
ectr=0;

// Build a list of all light starts and ends for this tag

for(t=MasterList;t;t=t->next)
	if(t->ptr->tag == *tag)
		{
		if(sctr<MAX_LIGHTS)
			if(!istricmp("light_start",t->ptr->name))
				start[sctr++]=t->ptr;
		if(ectr<MAX_LIGHTS)
			if(!istricmp("light_end",t->ptr->name))
				end[ectr++]=t->ptr;
		}

// Make sctr the lower of the two
if(ectr<sctr)
	sctr=ectr;

for(ectr=0;ectr<sctr;ectr++)
	set_light(start[ectr]->x,start[ectr]->y,end[ectr]->x,end[ectr]->y,*state);
}


void PV_SetLight_single()
{
int *state;
OBJECT **start,**end;

start = GET_OBJECT();
CHECK_POINTER(start);
end = GET_OBJECT();
CHECK_POINTER(end);
state = GET_INT();
CHECK_POINTER(state);

set_light((*start)->x,(*start)->y,(*end)->x,(*end)->y,*state);
}


void PV_Printaddr()
{
int *num;
num = GET_INT();
irecon_printf("%-08x",num);
if(DebugVM || printlogging)
	ilog_quiet("%-08x",num);
}

// Log console output?

void PV_PrintLog()
{
int *p;

p = GET_INT();
CHECK_POINTER(p);

printlogging = *p;

// Special effects for debugging purposes:

switch(printlogging)
	{
	case 1:
	irecon_savecol();
	irecon_colour(200,0,0);
	Bug("Problems in %s:\n",debcurfunc);
	break;

	case 0:
	irecon_loadcol();
	irecon_printf("\n");
	break;
	}
}


void PV_MovePartyToObj()
{
int ctr;
OBJECT **o1;

o1 = GET_OBJECT();
CHECK_POINTER(o1);

for(ctr=0;ctr<MAX_MEMBERS;ctr++)
	if(party[ctr])
		{
		TransferToPocket(party[ctr],*o1);
		// Update X/Y to match object
		party[ctr]->x = (*o1)->x;
		party[ctr]->y = (*o1)->y;
		}

}

void PV_MovePartyFromObj()
{
int ctr,*x,*y;
OBJECT **o1;

o1 = GET_OBJECT();
CHECK_POINTER(o1);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

for(ctr=0;ctr<MAX_MEMBERS;ctr++)
	if(party[ctr])
		ForceFromPocket(party[ctr],*o1,*x,*y);

}


void PV_MoveTag()
{
OBJLIST *t;
int *x,*y,*tag1,tag;

tag1 = GET_INT();
CHECK_POINTER(tag1);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

tag = -(*tag1); // Get negated tag number (e.g. 50 -> -50)

// Move them all

for(t=MasterList;t;t=t->next)
	if(t->ptr->tag == tag)
		{
		transfer_object(t->ptr,t->ptr->x+*x,t->ptr->y+*y);
		MoveToFloor(t->ptr); // Prevent candles etc on table disappearing
		}

}


void PV_UpdateTag()
{
int *tag;

tag = GET_INT();
CHECK_POINTER(tag);
ForceUpdateTag(*tag);
}

// GetDataSSS - GetData into S from table S with key S

void PV_GetDataSSS()
{
char **var,*table,*key;
DT_ITEM *p;

var = (char **)GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(var);
table = GET_STRING();
CHECK_POINTER(table);
key = GET_STRING();
CHECK_POINTER(key);

pevm_err=0;
p = GetTableName_s(table,key);
if(!p)
	{
	pevm_err=-1;
	return;
	}

*var = p->is;
}

// GetDataSSI - GetData into S from table S with key I

void PV_GetDataSSI()
{
char **var,*table;
int *key;
DT_ITEM *p;

var = (char **)GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(var);
table = GET_STRING();
CHECK_POINTER(table);
key = GET_INT();
CHECK_POINTER(key);

pevm_err=0;
p = GetTableName_i(table,*key);
if(!p)
	{
	pevm_err=-1;
	return;
	}

*var = p->is;
}

// GetDataSIS - GetData into S from table I with key S

void PV_GetDataSIS()
{
char **var,*key;
int *table;
DT_ITEM *p;

var = (char **)GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(var);
table = GET_INT();
CHECK_POINTER(table);
key = GET_STRING();
CHECK_POINTER(key);

pevm_err=0;
p = GetTableNum_s(*table,key);
if(!p)
	{
	pevm_err=-1;
	return;
	}

*var = p->is;
}

// GetDataSII - GetData into S from table I with key I

void PV_GetDataSII()
{
char **var;
int *key,*table;
DT_ITEM *p;

var = (char **)GET_OBJECT(); // Hack, we need the pointer not just the string
CHECK_POINTER(var);
table = GET_INT();
CHECK_POINTER(table);
key = GET_INT();
CHECK_POINTER(key);

pevm_err=0;
p = GetTableNum_i(*table,*key);
if(!p)
	{
	pevm_err=-1;
	return;
	}

*var = p->is;
}

// GetDataISS - GetData into I from table S with key S

void PV_GetDataISS()
{
int *var;
char *table,*key;
DT_ITEM *p;

var = GET_INT();
CHECK_POINTER(var);
table = GET_STRING();
CHECK_POINTER(table);
key = GET_STRING();
CHECK_POINTER(key);

pevm_err=0;
p = GetTableName_s(table,key);
if(!p)
	{
	pevm_err=-1;
	return;
	}

*var = p->ii;
}

// GetDataISI - GetData into I from table S with key I

void PV_GetDataISI()
{
char *table;
int *var,*key;
DT_ITEM *p;

var = GET_INT();
CHECK_POINTER(var);
table = GET_STRING();
CHECK_POINTER(table);
key = GET_INT();
CHECK_POINTER(key);

pevm_err=0;
p = GetTableName_i(table,*key);
if(!p)
	{
	pevm_err=-1;
	return;
	}

*var = p->ii;
}

// GetDataIIS - GetData into S from table I with key S

void PV_GetDataIIS()
{
char *key;
int *var,*table;
DT_ITEM *p;

var = GET_INT();
CHECK_POINTER(var);
table = GET_INT();
CHECK_POINTER(table);
key = GET_STRING();
CHECK_POINTER(key);

pevm_err=0;
p = GetTableNum_s(*table,key);
if(!p)
	{
	pevm_err=-1;
	return;
	}

*var = p->ii;
}

// GetDataIII - GetData into I from table I with key I

void PV_GetDataIII()
{
int *var,*key,*table;
DT_ITEM *p;

var = GET_INT();
CHECK_POINTER(var);
table = GET_INT();
CHECK_POINTER(table);
key = GET_INT();
CHECK_POINTER(key);

pevm_err=0;
p = GetTableNum_i(*table,*key);
if(!p)
	{
	pevm_err=-1;
	return;
	}

*var = p->ii;
}


// Delete a Decorative object

void PV_GetDecor()
{
int *x,*y;
OBJECT **obj;

x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);
obj = GET_OBJECT();
CHECK_POINTER(obj);

if(GetDecor(x,y,*obj))
	pevm_err=0;
else
	pevm_err=-1;
}


// Delete a Decorative object

void PV_DelDecor()
{
int *x,*y;
OBJECT **obj;

x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);
obj = GET_OBJECT();
CHECK_POINTER(obj);

DelDecor(*x,*y,*obj);
}

void PV_SearchContainer()
{
OBJECT **cont,**obj;
char *name;

obj = GET_OBJECT();
CHECK_POINTER(obj);
name = GET_STRING();
CHECK_POINTER(name);
cont = GET_OBJECT();
CHECK_POINTER(cont);

if(!*cont)
	{
	*obj=NULL;
	return;
	}

*obj = GetFirstObjectFuzzy((*cont)->pocket, name);
}

// Change map to fixed coordinates

void PV_ChangeMap1()
{
int *x,*y,*z;

z = GET_INT();
CHECK_POINTER(z);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);

change_map = *z;
change_map_x = *x;
change_map_y = *y;
}

// Change map using a tag

void PV_ChangeMap2()
{
int *z,*t;

z = GET_INT();
CHECK_POINTER(z);
t = GET_INT();
CHECK_POINTER(t);

change_map = *z;
change_map_tag = *t;
}

// Scroll a still picture

void PV_PicScroll()
{
char *name;
int *dx,*dy;

name = GET_STRING();
CHECK_POINTER(name);

dx = GET_INT();
CHECK_POINTER(dx);

dy = GET_INT();
CHECK_POINTER(dy);

NPC_ScrollPic(name,*dx,*dy,tfx_picdelay1,tfx_picdelay2,tfx_picdelay3);
}

// Set the panel image

void PV_SetPanel()
{
char *name;

name = GET_STRING();
CHECK_POINTER(name);

LoadBacking(swapscreen,name);

// Set up console
irecon_term();
irecon_init(conx,cony,conwid,conlen);
Show();
}

//

void PV_FindPathMarker()
{
OBJECT **o1;
OBJECT **o2;
char *str;

o1 = GET_OBJECT();
CHECK_POINTER(o1);
str = GET_STRING();
CHECK_POINTER(str);
o2 = GET_OBJECT();
CHECK_POINTER(o2);

*o1 = find_pathmarker(*o2,str);
}

// Centre the solid-map around an object

void PV_ReSolid()
{
OBJECT **obj;

obj = GET_OBJECT();
CHECK_POINTER(obj);
CentreMap(*obj);
gen_largemap();
}


// Set personal name

void PV_SetPName()
{
OBJECT **obj,*o;
char *name;

obj = GET_OBJECT();
CHECK_POINTER(obj);

name = GET_STRING();
CHECK_POINTER(name);

o=*obj;
if(o)
	{
	if(o->personalname)
		{
		// Don't want the nabes to do a buffer overflow
		strncpy(o->personalname,name,32);
		o->personalname[31]=0; // Trim any excess
		}
	}
}

// Delete all object's property

void PV_DelProp()
{
OBJECT **obj;
OBJLIST *t;
OBJREGS objvars;
int pe;

pe=getnum4PE("delprop_callback");  // If this exists, call it for each one

obj = GET_OBJECT();
CHECK_POINTER(obj);

VM_SaveRegs(&objvars);

for(t=MasterList;t;t=t->next)
	if(t->ptr)
		if(t->ptr->stats)
			if(t->ptr->stats->owner == *obj)
				{
				// Destroy it and everything within
				EraseContents(t->ptr->pocket);

				// If we have a special function, use that
				// Otherwise, nuke it
				if(pe>0)
					{
					person=t->ptr;
					current_object=person;
					CallVMnum(pe);
					}
				else
					DeleteObject(t->ptr);
				}

VM_RestoreRegs(&objvars);
}

void PV_QUpdate()
{
OBJECT **obj,*oldcur;

obj = GET_OBJECT();
CHECK_POINTER(obj);

if((*obj)->funcs->qcache >= 0)
	{
	oldcur = current_object;
	current_object = (*obj);
	CallVMnum((*obj)->funcs->qcache);
	current_object = oldcur;
	}
}

//
//  Graphics, Effects, Interface etc
//


// Create a mouse region

void PV_AddRange()
{
int *id,*x,*y,*w,*h;

id = GET_INT();
CHECK_POINTER(id);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);
w = GET_INT();
CHECK_POINTER(w);
h = GET_INT();
CHECK_POINTER(h);

AddMouseRange(*id,*x,*y,*w,*h);
}


void PV_GridRange()
{
int *id,*w,*h;

id = GET_INT();
CHECK_POINTER(id);
w = GET_INT();
CHECK_POINTER(w);
h = GET_INT();
CHECK_POINTER(h);

SetRangeGrid(*id,*w,*h);
}

// Create a mouse region

void PV_AddButton()
{
int *id,*x,*y;
char *normal, *pressed;

id = GET_INT();
CHECK_POINTER(id);
x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);
normal = GET_STRING();
CHECK_POINTER(normal);
pressed = GET_STRING();
CHECK_POINTER(pressed);

AddMouseRange(*id,*x,*y,1,1);
SetRangeButton(*id,normal,pressed);
DrawButtons();
}

// Create a mouse region

void PV_PushButton()
{
int *id,*x;

id = GET_INT();
CHECK_POINTER(id);
x = GET_INT();
CHECK_POINTER(x);

PushButton(*id,*x);
DrawButtons();
}

// Make the button right-clickable

void PV_RightClick()
{
int *id;

id = GET_INT();
CHECK_POINTER(id);

RightClick(*id);
}

// Wait for mouse button to be released

void PV_FlushMouse()
{
int *flag;
flag = GET_INT();
CHECK_POINTER(flag);
if(*flag)
	get_mouse_release();  // animating
else
	WaitForMouseRelease(); // blocking
}

void PV_RangePointer()
{
int *id,*p;

id = GET_INT();
CHECK_POINTER(id);
p = GET_INT();
CHECK_POINTER(p);

SetRangePointer(*id,*p);
}

void PV_SaveScreen()
{
SaveScreen();
SaveRanges();
ClearMouseRanges();
}

void PV_RestoreScreen()
{
RestoreScreen();
RestoreRanges();
}


void PV_dofx()
{
int *z;
OBJECT **o;

o = GET_OBJECT();
CHECK_POINTER(o);
z = GET_INT();
CHECK_POINTER(z);

if(*o)
	if((*o)->user)
		(*o)->user->fx_func=*z;
}

void PV_dofxS()
{
char *z;
OBJECT **o;
int zfunc;

o = GET_OBJECT();
CHECK_POINTER(o);
z = GET_STRING();
CHECK_POINTER(z);

zfunc = getnum4PE(z);
if(zfunc == -1)
	{
	Bug("startfx: Cannot find effect function '%s' in %s\n",z,debcurfunc);
	return;
	}

if(*o)
	if((*o)->user)
		(*o)->user->fx_func=zfunc;
}

void PV_dofxOver()
{
int *z;
OBJECT **o;

o = GET_OBJECT();
CHECK_POINTER(o);
z = GET_INT();
CHECK_POINTER(z);

if(*o)
	if((*o)->user)
		(*o)->user->fx_func=-*z;
}

void PV_dofxOverS()
{
int zfunc;
char *z;
OBJECT **o;

o = GET_OBJECT();
CHECK_POINTER(o);
z = GET_STRING();
CHECK_POINTER(z);

zfunc = getnum4PE(z);
if(zfunc == -1)
	{
	Bug("overfx: Cannot find effect function '%s' in %s\n",z,debcurfunc);
	return;
	}

if(*o)
	if((*o)->user)
		(*o)->user->fx_func=-zfunc;
}



void PV_stopfx()
{
OBJECT **o;

o = GET_OBJECT();
CHECK_POINTER(o);

if(*o)
	if((*o)->user)
		(*o)->user->fx_func=0;
}

// Get X,Y pixel coords for an object

void PV_fxGetXY()
{
int *x,*y;
OBJECT **o;

x = GET_INT();
CHECK_POINTER(x);
y = GET_INT();
CHECK_POINTER(y);
o = GET_OBJECT();
CHECK_POINTER(o);

if(!*o)
	return; // Safety check

*x=(((*o)->x-mapx)<<5)+(*o)->hotx; // Get grid position of viewscreen, multiply by tile size
*y=(((*o)->y-mapy)<<5)+(*o)->hoty; // Get grid position of viewscreen, multiply by tile size
}


// Set effects colour

void PV_fxColour()
{
int *a;
int r,g,b;

a = GET_INT();
CHECK_POINTER(a);

#if BYTE_ORDER == LITTLE_ENDIAN
	r=((*a)>>16)&0xff;   // get Red
	g=((*a)>>8)&0xff;    // get Green
	b=(*a)&0xff;         // get Blue
#else
	r=(*a)&0xff;         // get Red
	g=((*a)>>8)&0xff;    // get Green
	b=((*a)>>16)&0xff;   // get Blue
#endif

tfx_Colour=makecol(r,g,b);
}


// Set effects alpha level

void PV_fxAlpha()
{
int *a;

a = GET_INT();
CHECK_POINTER(a);
tfx_Alpha=*a;
}

#define ALPHA tfx_Alpha

// Set effects alpha mode

void PV_fxAlphaMode()
{
int *a;

a = GET_INT();
CHECK_POINTER(a);

switch(*a)
	{
	case ALPHA_TRANS:
	set_trans_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_ADD:
	set_add_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_BURN:
	set_burn_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_COLOR:
	set_color_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_DIFFERENCE:
	set_difference_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_DISSOLVE:
	set_dissolve_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_DODGE:
	set_burn_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_HUE:
	set_hue_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_INVERT:
	set_invert_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_LUMINANCE:
	set_luminance_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_MULTIPLY:
	set_multiply_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_SATURATION:
	set_saturation_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	case ALPHA_SCREEN:
	set_screen_blender(ALPHA,ALPHA,ALPHA,tfx_Alpha);
	drawing_mode(DRAW_MODE_TRANS,NULL,0,0);
	break;

	default:
	tfx_Alpha=255;
	set_trans_blender(255,255,255,255);
	drawing_mode(DRAW_MODE_SOLID,NULL,0,0);
	break;
	};

}

// Optimised random number generator

void PV_fxRandom()
{
int *var,*r;

var = GET_INT();
CHECK_POINTER(var);
r = GET_INT();
CHECK_POINTER(r);

if(*r > 0)
	*var = qrand()%*r;
}

// Line and Point primitives

void PV_fxLine()
{
if(tfx_Brushsize>1)
	do_line(gamewin,tfx_sx,tfx_sy,tfx_dx,tfx_dy,tfx_Brushsize,draw_fatline);
else
	line(gamewin,tfx_sx,tfx_sy,tfx_dx,tfx_dy,tfx_Colour);
}

void PV_fxPoint()
{
if(tfx_Brushsize>1)
	circlefill(gamewin,tfx_sx,tfx_sy,tfx_Brushsize,tfx_Colour);
else
	putpixel(gamewin,tfx_sx,tfx_sy,tfx_Colour);
}

void PV_fxRect()
{
rectfill(gamewin,tfx_sx,tfx_sy,tfx_dx,tfx_dy,tfx_Colour);
}

void PV_fxPoly()
{
int *pointdata,idx,vertices,t;
static int newpoints[64];

GET_ARRAY_INFO((void **)&pointdata,&vertices,&idx); // Get size of array at IP
GET_INT();	// skip the array

vertices>>=1; // This many vertices (half number of points)

// Clip vertices to 32
if(vertices>32)
	vertices=32;

// Copy vertices and offset them
for(idx=0;idx<vertices;idx++)
	{
	t=idx<<1; // two ints per vertex
	newpoints[t]=pointdata[t]+tfx_dx;
	t++;
	newpoints[t]=pointdata[t]+tfx_dy;
	}

// 'Do it' (Gollum gets straight to the point)
polygon(gamewin,vertices,newpoints,tfx_Colour);
}

void PV_fxOrbit()
{
int *angle;

angle = GET_INT();
CHECK_POINTER(angle);

CalcOrbit(angle, tfx_radius, tfx_drift, tfx_speed, &tfx_sx, &tfx_sy, tfx_dx, tfx_dy);
}

// Draw the current object's sprite in the current pen at sx,sy

void PV_fxSprite()
{
if(current_object)
	draw_trans_rle_sprite(gamewin,current_object->form->seq[current_object->sptr]->image,tfx_sx,tfx_sy);
}

// Draw a corona at sx,sy of size radius

void PV_fxCorona()
{
int *tint;
tint = GET_INT();
CHECK_POINTER(tint);

drawing_mode(DRAW_MODE_SOLID,NULL,0,0);
ProjectCorona(tfx_sx,tfx_sy,tfx_radius,tfx_intensity,tfx_falloff,*tint);
}


// Display <object> onscreen at <x> <y>

void PV_fxAnimSprite()
{
if(current_object)
	{
	current_object->stats->tick++;
	if(current_object->stats->tick>=current_object->form->speed)
		{
		if(current_object->form->flags&SEQFLAG_RANDOM)
			anim_random(current_object);
		else
			animate[current_object->form->flags&SEQFLAG_ANIMCODE](current_object);  // animate once
		current_object->stats->tick=0;
		}
//	draw_rle_sprite(gamewin,current_object->form->seq[current_object->sptr]->image,tfx_sx,tfx_sy);
	draw_trans_rle_sprite(gamewin,current_object->form->seq[current_object->sptr]->image,tfx_sx,tfx_sy);
	}
}


//
// LASTOP: new opcodes go above this line
//


// Print various status items (for debugging)

void PV_LastOp()
{
OBJLIST *t;
int ctr;
int *num;
num = GET_INT();

switch(*num)
	{
	case 0:
	ctr=0;
	for(t=ActiveList;t;t=t->next) ctr++;
	irecon_printf("%d Active objects\n",ctr);
	break;

	case 1:
	ilog_printf("current = %p\n",current_object);
	if(current_object)
		ilog_printf("current = '%s'\n",current_object->name);
	break;

	case 2:
	ctr=0;
	for(t=MasterList;t;t=t->next) ctr++;
	irecon_printf("%d objects in masterlist\n",ctr);
	break;

	case 3:
	irecon_printf("Stack trace written to logfile\n");
	VMstacktrace();
	break;

	case 4:
//	irecon_printf("Causing Panic at file e\n");
	DebugVM=1;
	VMstacktrace();
	Bang("Script code requested panic (status 4)");
	break;

	case 5:
	if(current_object)
		{
		if(ML_InList(&ActiveList,current_object))
			irecon_printf("%s is active\n",current_object->name);
		else
			irecon_printf("%s is not active\n",current_object->name);
		}
	break;

	case 6:
	if(current_object)
		{
		for(ctr=0;ctr<ACT_STACK;ctr++)
		if(current_object->user->actlist[ctr]>0)
			{
			ilog_printf("%d: do %s",ctr,PElist[current_object->user->actlist[ctr]].name);
			if(current_object->user->acttarget[ctr])
				ilog_printf(" to %s",current_object->user->acttarget[ctr]->name);
			ilog_printf("\n");
			}
		}
	break;

	case 7:
		// Quit game
		ire_running=0;
	break;

	case 8:
		pevm_err = LoadGame();
		if(pevm_err)
			ire_running=-1; // Success
		else
			ire_running=1;
	break;

	case 9:
		SaveGame();
	break;

	case 10:
		SoundSettings();
	break;

	case 11:
		// Project shrunk map (wizard eye etc)
		ZoomMap(pe_usernum1,pe_usernum2);
	break;

	default:
	Bug("Unknown status request %d\n",*num);
	}

}


// DumpVM

void PV_Dump()
{
int dvm;
dvm = DebugVM;
DebugVM=1;
DumpVM(1);
DebugVM = dvm;
}

// No Operation

void PV_NOP()
{
}

/*
 *  For VM debugging of pointers
 *  To make sure what we're changing is fully inside the address-space
 *  This is probably not portable and certainly not for general use.
 *  If it prevents the engine compiling on some future arch or compiler
 *  just comment the thing out.
 */

void CheckRange(PEVM *vm, void *ptr, int ident)
{
unsigned int a,b,c,d;

a=(unsigned int)vm->code;
b=(unsigned int)vm->size;
c=a+b;
d=(unsigned int)ptr;

if(d<a || d>c)
	{
	printf("%s: POINTER 0x%p OUT OF RANGE (0x%x-0x%x), ident %d\n",vm->name,ptr,a,c,ident);
//	CRASH();
	}
}

/*
 * DO Fix-Up System, internal system for local variable management
 * Pronounced 'doofus'
 */

/*
 This is an opcode that is needed in all functions that have local
 variables.  It is added automatically by the compiler, which will
 also have built a table of offsets and allocated space for the
 local variables to use.
 DOFUS goes through the table, and transforms each reference into a
 pointer that the VM will use.  The pointer must point to a slot
 allocated for the variable by the compiler.  Since local variables
 can and will have multiple instances, the fixup must be done at
 runtime whenever the function is called.  That's what DOFUS does.

 The memory map for functions with local variables will look like
 this:

   CODE
    :
   DOFUS PATCH TABLE
    :
   LOCAL VARIABLE DATASTORE

 ..it would be tempting to reuse the patch table for the datastore
 since it won't be needed after DOFUS has run, but alas the variables
 are not patched in physical storage order so this doesn't work.
 It might be possible to force the compiler to do this, but that is
 left as an exercise for the reader.

 The patch table consists of two words per entry; the offset into
 the code, and the variable slot number to use.

 The final point of note is that locals can be initialised to default
 values.  These default values are actually in the code!  Each reference
 to a variable in the code will have the default value stuffed in it
 so must be picked up and put into the variable store, before we can
 put the pointer in it's place.

*/

void PV_Dofus()
{
unsigned int num,ctr,val,*value_ptr;
unsigned int varoff,slotno,dataoff;
VMTYPE *varptr,*slotaddr,*data;


//#define DOFUSDUMP // For debugging the local variable fixup system
//#define DOFUS_DEBUG

#ifdef DOFUS_DEBUG
ilog_quiet("in %s\n",debcurfunc);
#define DOFUS_LOG(x,y); ilog_quiet(x,y);
#define DOFUS_LOG2(x,y); ilog_quiet(x,y,(y)-(int)(curvm->code));
#else
#define DOFUS_LOG(x,y); ;
#define DOFUS_LOG2(x,y); ;
#endif

#ifdef DOFUSDUMP
//ilog_quiet("%s\n",debcurfunc);
ilog_quiet("Doofus: dump before: ");
unsigned char *cc=(unsigned char *)curvm->code;
for(ctr=0;ctr<curvm->size;ctr++)
	{
	if(!(ctr % 16))
		ilog_quiet("\n%-3x  ",ctr/16);
	ilog_quiet("%02x ",cc[ctr]&255);
	}
ilog_quiet("\n");
#endif

num = GET_RAW_DWORD(); // Get number of patches
DOFUS_LOG("%x patches\n",num);

// Get the offset of the patch table into the VM's memory space
dataoff = GET_RAW_DWORD();
DOFUS_LOG("ptoffset = %x\n",dataoff);

// Turn the patch table offset into an actual pointer
// by adding the address of the start of the function
data = ADD_IP(curvm->code,dataoff);
DOFUS_LOG2("patchptr = %x [%x]\n",data);

// Variable slots are after the patch table, i.e. the patch table's address
// plus the no. of patches times the word size x2 (each patch has two entries)
slotaddr = data + (num*2); // Each slot takes two words
#ifdef DOFUS_DEBUG
ilog_quiet("(address space = 0x%x -> 0x%x)\n",curvm->code,(unsigned int)curvm->code + (unsigned int)curvm->size);
#endif
DOFUS_LOG2("slotaddress = %x [%x]\n---\n",slotaddr);

// Patch each of the entries

for(ctr=0;ctr<num;ctr++)
	{
	// Get the first word (variable offset)
	varoff = (*data++).u32;
	DOFUS_LOG2("var offset = %x [%x]\n",varoff);

	// Get the second word (variable slot number) and increment again
	slotno = (*data++).u32;
	DOFUS_LOG("slot number = %x\n",slotno);

	// Make the code offset into a pointer
	varptr=ADD_IP(curvm->ip,varoff);
	DOFUS_LOG2("patch address = %x [%x]\n",varptr);

	// Get the value that was there, this is the variable's default value
	// We put that into the var.slot later.  This could be done at build time
	// which may speed things up slightly.
	val = (*varptr).u32;
	DOFUS_LOG("def value = %x\n",val);

	// Replace the integer value that was there with the pointerized version
	// That the VM will use.
	(*varptr).ptr = slotaddr + slotno;
	DOFUS_LOG2("varslot ptr = %x [%x]\n",varptr);

	// Fill the variable slot with the default value obtained above
	value_ptr = (unsigned int *)(*varptr).ptr; // Get address
	*value_ptr = val; // Write default value into the variable

#ifdef DOFUS_DEBUG
ilog_quiet("round again.. [%d/%d]\n\n",ctr+1,num);
#endif
	}
#ifdef DOFUSDUMP
ilog_quiet("Doofus: dump after\n");
DumpVM(0);
ilog_quiet("Doofus: signing off\n");
#endif
}


//
//	Math Operators
//

int OP_Invalid(int a, int b)
{
Bug("Unknown operator! Either 'if %d ? %d' or let x = %d ? %d\n",a,b,a,b);
VMbug();
return 0;
}

int OP_Add(int a, int b)
{
return a+b;
}

int OP_Sub(int a, int b)
{
return a-b;
}

int OP_Div(int a, int b)
{
if(b==0)
	return 0; // No, this is not mathematically correct
return a/b;
}

int OP_Mul(int a, int b)
{
return a*b;
}

int OP_Equal(int a, int b)
{
return (a==b);
}

int OP_NotEqual(int a, int b)
{
//ilog_quiet("cmp %x != %x : result %d\n",a,b,(a!=b));
return (a!=b);
}

int OP_Less(int a, int b)
{
return (a<b);
}

int OP_LessEq(int a, int b)
{
return (a<=b);
}

int OP_Great(int a, int b)
{
return (a>b);
}

int OP_GreatEq(int a, int b)
{
return (a>=b);
}

int OP_Modulus(int a, int b)
{
if(b==0)
	return 0; // No, this is not mathematically correct
return a%b;
}

int OP_And(int a, int b)
{
return a&b;
}

int OP_Or(int a, int b)
{
return a|b;
}

int OP_Xor(int a, int b)
{
return a^b;
}

int OP_Nand(int a, int b)
{
return a&((unsigned int)b^0xffffffff);
}

int OP_Shl(int a, int b)
{
return (int)((unsigned int)a<<(unsigned int)b);
}

int OP_Shr(int a, int b)
{
return (int)((unsigned int)a>>(unsigned int)b);
}

/*
// Write to a string character

void PV_PokeStr()
{
char *name;
VMTYPE *strptr;
int *p;

// Get the address of the start of the string
name = GET_STRING();
CHECK_POINTER(name);

// Now go backwards from the start of the string to get the max. length
strptr = (VMTYPE *)name;
strptr--; // Back one word, this will be the string length
len=(*strptr).i32;

// Get the string offset to wurdle
p=GET_INT();
CHECK_POINTER(p);

b=GET_INT();

}
*/

