/*=============================================================================
    Name    : ETG.C
    Purpose : Effects Tool.  OF THE GODS!
        Parsing and execution code.

    Created 11/13/1997 by lmoloney
    Copyright Relic Entertainment, Inc.  All rights reserved.
=============================================================================*/

#include <windows.h>
#include "glinc.h"
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include "types.h"
#include "file.h"
#include "memory.h"
#include "debug.h"
#include "color.h"
#include "particle.h"
#include "randy.h"
#include "eval.h"
#include "statscript.h"
#include "utility.h"
#include "texreg.h"
#include "mesh.h"
#include "select.h"
#include "task.h"
#include "fastmath.h"
#include "battle.h"
#include "mainrgn.h"
#include "render.h"
#include "btg.h"
#include "etg.h"

#include "soundevent.h"
#include "spaceobj.h"
#include "mex.h"
#include "universe.h"
#include "univupdate.h"
#include "damage.h"
#include "horserace.h"

/*=============================================================================
    Data:
=============================================================================*/
/*-----------------------------------------------------------------------------
    Effect-event registry table
-----------------------------------------------------------------------------*/
etgevent etgEventTable[ETG_EventListLength];
sdword etgEventLoadCount = 0;
//sdword etgEventIndex = 0;

//data for error recovery during ETG parsing
bool etgErrorRecoverable = FALSE;
bool etgErrorEncountered = FALSE;
#define PARSEERROR      (etgErrorRecoverable && etgErrorEncountered)

/*-----------------------------------------------------------------------------
    Master dispatch table for processing effects:
-----------------------------------------------------------------------------*/
sdword etgNOP(Effect *effect, etgeffectstatic *stat, ubyte *opcode);
sdword etgVarCopy(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgVarAssign(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgFunctionCall(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEnd(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgDelete(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgCompareVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgNotEqualVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgGreaterVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgGreaterEqualVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgLessVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgLessEqualVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgCompareVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgNotEqualVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgGreaterVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgGreaterEqualVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgLessVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgLessEqualVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEqualVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgNotEqualVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgGreaterVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgGreaterEqualVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgLessVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgLessEqualVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEqualVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgNotEqualVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgGreaterVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgGreaterEqualVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgLessVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgLessEqualVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgBranchTo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgCallTo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgReturnTo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgAlternate(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgBetween(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEffectorCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEffectorCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEffectorCCo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEffectorCCa(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEffectorVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEffectorVCo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEffectorVCa(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgAt(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgAtVar(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEvery(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgEveryVar(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgBefore(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgBeforeVar(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgAfter(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
sdword etgAfterVar(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode);
ophandleentry etgHandleTable[] =
{
/* EOP_Nop            */ {sizeof(etgnop), etgNOP},
/*//time-index code bl*/
/* EOP_Before         */ {sizeof(etgbeforeafterat), etgBefore},
/* EOP_BeforeVar      */ {sizeof(etgbeforeafterat), etgBeforeVar},
/* EOP_After          */ {sizeof(etgbeforeafterat), etgAfter},
/* EOP_AfterVar       */ {sizeof(etgbeforeafterat), etgAfterVar},
/* EOP_At             */ {sizeof(etgbeforeafterat), etgAt},
/* EOP_AtVar          */ {sizeof(etgbeforeafterat), etgAtVar},
/* EOP_Between        */ {sizeof(etgbetween), etgBetween},
/* EOP_Every          */ {sizeof(etgbeforeafterat), etgEvery},
/* EOP_EveryVar       */ {sizeof(etgbeforeafterat), etgEveryVar},
/* EOP_EffectorCCI    */ {sizeof(etgeffector), NULL},
/* EOP_EffectorCCF    */ {sizeof(etgeffector), NULL},
/* EOP_EffectorCCCo   */ {sizeof(etgeffector), NULL},
/* EOP_EffectorCCCa   */ {sizeof(etgeffector), NULL},
/* EOP_EffectorCVI    */ {sizeof(etgeffector), NULL},
/* EOP_EffectorCVF    */ {sizeof(etgeffector), NULL},
/* EOP_EffectorCVCo   */ {sizeof(etgeffector), NULL},
/* EOP_EffectorCVCa   */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVCI    */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVCF    */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVCCo   */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVCCa   */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVVI    */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVVF    */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVVCo   */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVVCa   */ {sizeof(etgeffector), NULL},
/* EOP_EffectorVI,    */ {sizeof(etgseffector), NULL},
/* EOP_EffectorVF,    */ {sizeof(etgseffector), etgEffectorVF},
/* EOP_EffectorVCo    */ {sizeof(etgseffector), etgEffectorVCo},
/* EOP_EffectorVCa    */ {sizeof(etgseffector), etgEffectorVCa},
/* EOP_EffectorCI,    */ {sizeof(etgseffector), etgEffectorCI},
/* EOP_EffectorCF,    */ {sizeof(etgseffector), etgEffectorCF},
/* EOP_EffectorCCo    */ {sizeof(etgseffector), etgEffectorCCo},
/* EOP_EffectorCCa    */ {sizeof(etgseffector), etgEffectorCCa},
/*//process-control (a*/
/* EOP_Delete         */ {sizeof(etgprocesscontrol), etgDelete},
/* EOP_End            */ {sizeof(etgprocesscontrol), etgEnd},
/* EOP_Yeild          */ {sizeof(etgprocesscontrol), NULL},
/*//branching         */
/* EOP_Goto           */ {sizeof(etgbranch), etgBranchTo},
/* EOP_Call           */ {sizeof(etgbranch), etgCallTo},
/* EOP_Return         */ {sizeof(etgreturn), etgReturnTo},
/* EOP_Spawn          */ {sizeof(etgnop), NULL},       //!!! needed?
/* EOP_Start          */ {sizeof(etgnop), NULL},       //!!! needed?
/*//conditionals      */
/* EOP_EqualVCI       */ {sizeof(etgconditional), etgCompareVCI     },
/* EOP_NotEqualVCI    */ {sizeof(etgconditional), etgNotEqualVCI    },
/* EOP_GreaterVCI     */ {sizeof(etgconditional), etgGreaterVCI     },
/* EOP_GreatorEqualVCI*/ {sizeof(etgconditional), etgGreaterEqualVCI},
/* EOP_LessVCI        */ {sizeof(etgconditional), etgLessVCI        },
/* EOP_LessEqualVCI   */ {sizeof(etgconditional), etgLessEqualVCI   },
/* EOP_EqualVVI       */ {sizeof(etgconditional), etgEqualVVI       },
/* EOP_NotEqualVVI    */ {sizeof(etgconditional), etgNotEqualVVI    },
/* EOP_GreaterVVI     */ {sizeof(etgconditional), etgGreaterVVI     },
/* EOP_GreatorEqualVVI*/ {sizeof(etgconditional), etgGreaterEqualVVI},
/* EOP_LessVVI        */ {sizeof(etgconditional), etgLessVVI        },
/* EOP_LessEqualVVI   */ {sizeof(etgconditional), etgLessEqualVVI   },
/* EOP_EqualVCF       */ {sizeof(etgconditional), etgCompareVCF     },
/* EOP_NotEqualVCF    */ {sizeof(etgconditional), etgNotEqualVCF    },
/* EOP_GreaterVCF     */ {sizeof(etgconditional), etgGreaterVCF     },
/* EOP_GreatorEqualVCF*/ {sizeof(etgconditional), etgGreaterEqualVCF},
/* EOP_LessVCF        */ {sizeof(etgconditional), etgLessVCF        },
/* EOP_LessEqualVCF   */ {sizeof(etgconditional), etgLessEqualVCF   },
/* EOP_EqualVVF       */ {sizeof(etgconditional), etgEqualVVF       },
/* EOP_NotEqualVVF    */ {sizeof(etgconditional), etgNotEqualVVF    },
/* EOP_GreaterVVF     */ {sizeof(etgconditional), etgGreaterVVF     },
/* EOP_GreatorEqualVVF*/ {sizeof(etgconditional), etgGreaterEqualVVF},
/* EOP_LessVVF        */ {sizeof(etgconditional), etgLessVVF        },
/* EOP_LessEqualVVF   */ {sizeof(etgconditional), etgLessEqualVVF   },
/* EOP_ZeroVI         */ {sizeof(etgconditional), NULL},
/* EOP_NotZeroVI      */ {sizeof(etgconditional), NULL},
/* EOP_ZeroVF         */ {sizeof(etgconditional), NULL},
/* EOP_NotZeroVF      */ {sizeof(etgconditional), NULL},
/*//general           */
/* EOP_VariableCopy   */ {sizeof(etgvariablecopy), etgVarCopy},
/* EOP_VariableAssign */ {sizeof(etgvariablecopy), etgVarAssign},
/* EOP_Function       */ {sizeof(etgfunctioncall), etgFunctionCall},
/* EOP_Alternate      */ {sizeof(etgdecision),     etgAlternate},
/* EOP_Case           */ {sizeof(etgdecision),     NULL},
/* EOP_LastOp         */ {0, NULL},
};
//#if sizeof(opHandleTable) / sizeof(ophandleentry) != EOP_LastOp + 1
//#error opHandleTable wrong size
//#endif

/*-----------------------------------------------------------------------------
    Master table for parsing opcodes:
-----------------------------------------------------------------------------*/
sdword etgLineNumberUpdate(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgNOPCreate(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgVariableBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgStartupBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgEachFrameBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgTimeIndexBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgNewInteger(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgNewFloat(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgBlockClose(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgConditional(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgElse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgNewLocalLabel(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgLabelCall(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgLabelGoto(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgReturn(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgNewEventParse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgEndOpcode(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgYeildOpcode(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgDeleteOpcode(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgAternateStart(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgAternateSet(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgTimeBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgTimeIndexDefine(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgNParticleBlocksSet(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgNHistorySet(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgNewRGB(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgNewRGBA(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgSubTextureDWORD(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgMorphAnimDWORD(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgSetTextureParse(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgSpawnParse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgAtDefine(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgBetweenDefine(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgCreateParticlesParse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgCreateEffectsParse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret);
sdword etgSetMeshParse(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
sdword etgWorldRenderSet(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
//sdword etgForceVisibleSet(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret);
#define parseEntry(name, function, flags) {name, flags, function}
opparseentry etgParseTable[] =
{
    parseEntry("nop",           etgNOPCreate, 0),
//conditionals
    parseEntry("if",            etgConditional, PTF_Parameters),
    parseEntry("else",          etgElse, 0),
//labels
    parseEntry("label",         etgNewLocalLabel, PTF_Parameters),
/*
    parseEntry("exlabel",       etgNewExportLabel, PTF_Parameters),
*/
    parseEntry("eventStart",    etgNewEventParse, PTF_Parameters),
    parseEntry("variable",      etgVariableBlockOpen, 0),
//overall structure/syntax
    parseEntry("{",             etgBlockOpen, 0),
    parseEntry("}",             etgBlockClose, 0),
//modifications to the effect
    parseEntry("startup",       etgStartupBlockOpen, 0),
    parseEntry("eachFrame",     etgEachFrameBlockOpen, 0),
    parseEntry("timeIndex",     etgTimeIndexBlockOpen, 0),
    parseEntry("particleBlocks",etgNParticleBlocksSet, PTF_Parameters),
    parseEntry("maxFrequency",  etgNHistorySet, PTF_Parameters),
//    parseEntry("setForceVisible", etgForceVisibleSet, 0),
    parseEntry("worldRender",   etgWorldRenderSet, 0),
//variable declaration
    parseEntry("int",           etgNewInteger, PTF_RestOfLine),
    parseEntry("float",         etgNewFloat, PTF_RestOfLine),
    parseEntry("RGB",           etgNewRGB, PTF_RestOfLine),
    parseEntry("RGBA",          etgNewRGBA, PTF_RestOfLine),
    parseEntry("subTextureDWORD",etgSubTextureDWORD, PTF_Parameters),
    parseEntry("morphAnimDWORD",etgMorphAnimDWORD, PTF_Parameters),
    parseEntry("setTexture",    etgSetTextureParse, PTF_Parameters),
    parseEntry("modifyTexture", etgSetTextureParse, PTF_Parameters),
    parseEntry("setMesh",       etgSetMeshParse, PTF_Parameters),
/*
    parseEntry("vector",        etgNewVector, PTF_RestOfLine),
//time-index block comparisons
*/
    parseEntry("at",            etgAtDefine, PTF_Parameters),
    parseEntry("every",         etgAtDefine, PTF_Parameters),
    parseEntry("before",        etgAtDefine, PTF_Parameters),
    parseEntry("after",         etgAtDefine, PTF_Parameters),
    parseEntry("between",       etgBetweenDefine, PTF_Parameters),
/*
//effector definition
    parseEntry("effect"",        etgEffectorDefine, PTF_Parameters),
*/
//time-index blocks
    parseEntry("timeBlock",     etgTimeBlockOpen, PTF_Parameters),
    parseEntry("time",          etgTimeIndexDefine, PTF_Parameters),
//branching
    parseEntry("goto",          etgLabelGoto, PTF_Parameters),
    parseEntry("call",          etgLabelCall, PTF_Parameters),
    parseEntry("return",        etgReturn, 0),
//process control
    parseEntry("end",           etgEndOpcode, PTF_Parameters),
    parseEntry("delete",        etgDeleteOpcode, PTF_Parameters),
    parseEntry("yield",         etgYeildOpcode, PTF_Parameters),
//alternates
    parseEntry("alternate",     etgAternateStart, 0),
    parseEntry("alt",           etgAternateSet, PTF_Parameters),
//misc
    parseEntry("nop",           etgNOPCreate, 0),
    parseEntry("#line",         etgLineNumberUpdate, PTF_RestOfLine),
    parseEntry("spawn",         etgSpawnParse, PTF_Parameters),
/*
    parseEntry("createSprites", etgCreateParticlesParse, PTF_Parameters),
    parseEntry("createCircles", etgCreateParticlesParse, PTF_Parameters),
    parseEntry("createPoints",  etgCreateParticlesParse, PTF_Parameters),
    parseEntry("createLines",   etgCreateParticlesParse, PTF_Parameters),
    parseEntry("createMeshes",  etgCreateParticlesParse, PTF_Parameters),
*/
    parseEntry("createEffects", etgCreateEffectsParse, PTF_Parameters),
//********** functions/var copys are searched in a different table
    parseEntry(NULL, NULL, 0)
};

//master function parsing table
void etgSetDefaults(void);
udword etgInc(udword value);
udword etgDec(udword value);
udword etgFdiv(real32 numer, real32 denom);
udword etgFmult(real32 numer, real32 denom);
udword etgFadd(real32 numer, real32 denom);
udword etgFsub(real32 numer, real32 denom);
sdword etgAdd(sdword numer, sdword denom);
sdword etgSub(sdword numer, sdword denom);
sdword etgMult(sdword numer, sdword denom);
sdword etgDiv(sdword numer, sdword denom);
udword etgSin(real32 ang);
udword etgCos(real32 ang);
ubyte *etgCreateCircles(Effect *effect, sdword number, sdword dist);
ubyte *etgCreateSprites(Effect *effect, sdword number, sdword dist);
ubyte *etgCreatePoints(Effect *effect, sdword number, sdword dist);
ubyte *etgCreateMeshes(Effect *effect, sdword number, sdword dist);
ubyte *etgCreateLines(Effect *effect, sdword number, sdword dist);
void etgCreateEffects(Effect *effect, etgeffectstatic *stat, sdword number, sdword dist, sdword nParams, ...);
void etgParentShipDelete(Effect *effect);
void etgParentShipHide(Effect *effect);
void etgChatterEventPlay(Effect *effect, battlechatterevent event);
void etgDeathCriesPlay(Effect *effect);
void etgDamageDone(Effect *effect);
udword etgSpawnNewEffect(Effect *effect, etgeffectstatic *stat, sdword nParams, ...);
void etgEffOffsetLOF(real32 newVal);
void etgEffChangeLOF(real32 newTheta, real32 newMu);
void etgEffOffsetR(real32 newR, real32 newTheta);
void etgEffOffsetXYZ(real32 x, real32 y, real32 z);
void etgEffOffsetVelLOF(real32 newVal);
void etgEffOffsetSpin(real32 newVal);
void etgEffOffsetTime(real32 newVal);
void etgEffOffsetDrag(Effect *effect, real32 newVal);
void etgEffDrag(real32 newDrag);
void etgEffAttachParent(bool attach);
void etgEffSpin(real32 spin);
void etgEffDeltaSpin(real32 deltaSpin);
void etgEffDefaults(void);
/*
void etgThisChangeLOF(Effect *effect, real32 theta, real32 mu);
void etgThisOffsetLOF(Effect *effect, real32 offset);
void etgThisOffsetR(Effect *effect, real32 R, real32 theta);
*/
void etgThisOffsetXYZ(Effect *effect, real32 x, real32 y, real32 z);
//void etgThisOffsetVelLOF(Effect *effect, real32 vel);
//void etgThisOffsetVelR(Effect *effect, real32 R, real32 theta);
void etgThisSetDrag(Effect *effect, real32 drag);
void etgThisScaleVelocity(Effect *effect, real32 factor);
void etgThisOffsetSpin(Effect *effect, real32 spin);
void etgThisOffsetDeltaSpin(Effect *effect, real32 spin);
void etgThisOffsetTime(Effect *effect, real32 time);
void etgSetDrag(real32 n);
void etgModifyDrag(ubyte *sys, real32 drag);
void etgSetDepthWrite(udword write);
void etgModifyDepthWrite(ubyte *sys, udword write);
void etgSetColorA(color c);
void etgSetColor(color c);
void etgSetAnimation(void *animation, real32 frameRate, sdword loopCount);
void etgSetMorphAnimation(void *animation, real32 frameRate, sdword loopCount);
udword etgIRandom(udword low, udword high);
udword etgFRandom(real32 low, real32 high);
udword etgCRandom(udword loR, udword hiR, udword loG, udword hiG, udword loB, udword hiB);
udword etgCARandom(udword loR, udword hiR, udword loG, udword hiG, udword loB, udword hiB, udword loA, udword hiA);
udword etgFloat2Int(real32 f);
udword etgInt2Float(sdword f);
udword etgInts2Color(sdword red, sdword green, sdword blue);
udword etgInts2ColorA(sdword red, sdword green, sdword blue, sdword alpha);
udword etgFloats2Color(real32 red, real32 green, real32 blue);
udword etgFloats2ColorA(real32 red, real32 green, real32 blue, real32 alpha);
void etgSetSpecular(real32 exponent);
udword etgOwnerColorSchemeGet(Effect *effect);
udword etgEffectVelocityGet(Effect *effect);
//resolve functions:
void etgCreationResolve(etgeffectstatic *stat, etgfunctioncall *call);
void etgDepthWriteResolve(struct etgeffectstatic *stat, etgfunctioncall *call);
#define funcEntryn(name, ret, function)  {name, function, ret, (ubyte)FALSE, ETG_VariableParams}
#define funcEntry0(name, ret, function)  {name, function, ret, (ubyte)FALSE, NULL, 0}
#define funcEntry1(name, ret, function, p0) {name, function, ret, (ubyte)FALSE, NULL, 1, p0}
#define funcEntry2(name, ret, function, p0, p1) {name, function, ret, (ubyte)FALSE, NULL, 2, p0, p1}
#define funcEntry3(name, ret, function, p0, p1, p2) {name, function, ret, (ubyte)FALSE, NULL, 3, p0, p1, p2}
#define funcEntry4(name, ret, function, p0, p1, p2, p3) {name, function, ret, (ubyte)FALSE, NULL, 4, p0, p1, p2, p3}
#define funcEntry5(name, ret, function, p0, p1, p2, p3, p4) {name, function, ret, (ubyte)FALSE, NULL, 5, p0, p1, p2, p3, p4}
#define funcEntry6(name, ret, function, p0, p1, p2, p3, p4, p5) {name, function, ret, (ubyte)FALSE, NULL, 6, p0, p1, p2, p3, p4, p5}
#define funcEntry7(name, ret, function, p0, p1, p2, p3, p4, p5, p6) {name, function, ret, (ubyte)FALSE, NULL, 7, p0, p1, p2, p3, p4, p5, p6}
#define funcEntry8(name, ret, function, p0, p1, p2, p3, p4, p5, p6, p7) {name, function, ret, (ubyte)FALSE, NULL, 7, p0, p1, p2, p3, p4, p5, p6, p7}
#define funcEntryThisn(name, ret, function)  {name, function, ret, (ubyte)TRUE, ETG_VariableParams}
#define funcEntryThis0(name, ret, function)  {name, function, ret, (ubyte)TRUE, NULL, 0}
#define funcEntryThis1(name, ret, function, p0) {name, function, ret, (ubyte)TRUE, NULL, 1, p0}
#define funcEntryThis2(name, ret, function, p0, p1) {name, function, ret, (ubyte)TRUE, NULL, 2, p0, p1}
#define funcEntryThis3(name, ret, function, p0, p1, p2) {name, function, ret, (ubyte)TRUE, NULL, 3, p0, p1, p2}
#define funcEntryThis4(name, ret, function, p0, p1, p2, p3) {name, function, ret, (ubyte)TRUE, NULL, 4, p0, p1, p2, p3}
#define funcEntryThis5(name, ret, function, p0, p1, p2, p3, p4) {name, function, ret, (ubyte)TRUE, NULL, 5, p0, p1, p2, p3, p4}
#define funcEntryThis6(name, ret, function, p0, p1, p2, p3, p4, p5) {name, function, ret, (ubyte)TRUE, NULL, 6, p0, p1, p2, p3, p4, p5}
#define funcEntryThis7(name, ret, function, p0, p1, p2, p3, p4, p5, p6) {name, function, ret, (ubyte)TRUE, NULL, 7, p0, p1, p2, p3, p4, p5, p6}
#define funcEntryThis8(name, ret, function, p0, p1, p2, p3, p4, p5, p6, p7) {name, function, ret, (ubyte)TRUE, NULL, 7, p0, p1, p2, p3, p4, p5, p6, p7}
#define funcEntryR1(name, ret, function, p0, resolve) {name, function, ret, (ubyte)FALSE, resolve, 1, p0}
#define funcEntryR2(name, ret, function, p0, p1, resolve) {name, function, ret, (ubyte)FALSE, resolve, 2, p0, p1}
#define funcEntryThisR1(name, ret, function, p0, resolve) {name, function, ret, (ubyte)TRUE, resolve, 1, p0}
#define funcEntryThisR2(name, ret, function, p0, p1, resolve) {name, function, ret, (ubyte)TRUE, resolve, 2, p0, p1}
opfunctionentry etgFunctionTable[] =
{
    //set properties of particle systems to be created
    funcEntry0("setDefaults", EVT_Void, etgSetDefaults),
    funcEntry1("setDrag", EVT_Void, etgSetDrag, EVT_Float),
    funcEntry1("setScale", EVT_Void, partSetScale, EVT_Float),
    funcEntry3("setXYZScale", EVT_Void, partSetXYZScale, EVT_Float, EVT_Float, EVT_Float),
    funcEntry1("setScaleDist", EVT_Void, partSetScaleDist, EVT_Float),
    funcEntry1("setDeltaScale", EVT_Void, partSetDeltaScale, EVT_Float),
    funcEntry1("setDeltaScaleDist", EVT_Void, partSetDeltaScaleDist, EVT_Float),
    funcEntry1("setOffsetLOF", EVT_Void, partSetOffsetLOF, EVT_Float),
    funcEntry1("setOffsetR", EVT_Void, partSetOffsetR, EVT_Float),
    funcEntry1("setOffsetTheta", EVT_Void, partSetOffsetTheta, EVT_Float),
    funcEntry1("setOffsetArray", EVT_Void, partSetOffsetArray, EVT_ConstLabel),
    funcEntry1("setDeltaLOF", EVT_Void, partSetDeltaLOF, EVT_Float),
    funcEntry1("setDeltaLOFDist", EVT_Void, partSetDeltaLOFDist, EVT_Float),
    funcEntry2("setDeltaR", EVT_Void, partSetDeltaR, EVT_Float, EVT_Float),
    funcEntry2("setDeltaRDist", EVT_Void, partSetDeltaRDist, EVT_Float, EVT_Float),
    funcEntry1("setVelLOF", EVT_Void, partSetVelLOF, EVT_Float),
    funcEntry1("setVelLOFDist", EVT_Void, partSetVelLOFDist, EVT_Float),
    funcEntry1("setVelR", EVT_Void, partSetVelR, EVT_Float),
    funcEntry1("setVelRDist", EVT_Void, partSetVelRDist, EVT_Float),
    funcEntry1("setSpriteRot", EVT_Void, partSetAng, EVT_Float),
    funcEntry1("setSpriteRotAngle", EVT_Void, partSetAng, EVT_Float),
    funcEntry1("setSpriteDeltaRot", EVT_Void, partSetAngDelta, EVT_Float),
    funcEntry1("setDeltaVelLOF", EVT_Void, partSetDeltaVelLOF, EVT_Float),
    funcEntry1("setDeltaVelLOFDist", EVT_Void, partSetDeltaVelLOFDist, EVT_Float),
    funcEntry1("setDeltaVelR", EVT_Void, partSetDeltaVelR, EVT_Float),
    funcEntry1("setDeltaVelRDist", EVT_Void, partSetDeltaVelRDist, EVT_Float),
    funcEntry1("setSpriteRotDist", EVT_Void, partSetAngDist, EVT_Float),
    funcEntry1("setSpriteDeltaRot", EVT_Void, partSetAngDelta, EVT_Float),
    funcEntry1("setSpriteDeltaRotDist", EVT_Void, partSetAngDeltaDist, EVT_Float),
    funcEntry1("setColor", EVT_Void, etgSetColor, EVT_RGB),
    funcEntry3("setColorDist", EVT_Void, partSetColorDist, EVT_Float, EVT_Float, EVT_Float),
    funcEntry3("setDeltaColor", EVT_Void, partSetDeltaColor, EVT_Float, EVT_Float, EVT_Float),
    funcEntry3("setDeltaColorDist", EVT_Void, partSetDeltaColorDist, EVT_Float, EVT_Float, EVT_Float),
    funcEntry1("setColorA", EVT_Void, etgSetColorA, EVT_RGBA),
    funcEntry4("setColorADist", EVT_Void, partSetColorADist, EVT_Float, EVT_Float, EVT_Float, EVT_Float),
    funcEntry4("setDeltaColorA", EVT_Void, partSetDeltaColorA, EVT_Float, EVT_Float, EVT_Float, EVT_Float),
    funcEntry4("setDeltaColorADist", EVT_Void, partSetDeltaColorADist, EVT_Float, EVT_Float, EVT_Float, EVT_Float),
    funcEntry3("setAddColor", EVT_Void, partSetColorBias, EVT_Float, EVT_Float, EVT_Float),
    funcEntry3("setAddColorDist", EVT_Void, partSetColorBiasDist, EVT_Float, EVT_Float, EVT_Float),
    funcEntry1("setLighting", EVT_Void, partSetLighting, EVT_Int),
    funcEntry1("setIsWorldSpace", EVT_Void, partSetIsWorldspace, EVT_Int),
    funcEntry1("setVelocityInWorldSpace", EVT_Void, partSetVelocityInWorldSpace, EVT_Int),
    funcEntry1("setIllum", EVT_Void, partSetIllum, EVT_Float),
    funcEntry1("setIllumDist", EVT_Void, partSetIllumDist, EVT_Float),
    funcEntry1("setDeltaIllum", EVT_Void, partSetDeltaIllum, EVT_Float),
    funcEntry1("setDeltaIllumDist", EVT_Void, partSetDeltaIllumDist, EVT_Float),
    funcEntry1("setLifespan", EVT_Void, partSetLifespan, EVT_Float),
    funcEntry1("setLifespanDist", EVT_Void, partSetLifespanDist, EVT_Float),
    funcEntry1("setWaitSpan", EVT_Void, partSetWaitspan, EVT_Float),
    funcEntry1("setWaitSpanDist", EVT_Void, partSetWaitspanDist, EVT_Float),
    funcEntry1("setLength", EVT_Void, partSetLength, EVT_Float),
    funcEntry1("setLengthDist", EVT_Void, partSetLengthDist, EVT_Float),
    funcEntry1("setDeltaLength", EVT_Void, partSetDeltaLength, EVT_Float),
    funcEntry1("setDeltaLengthDist", EVT_Void, partSetDeltaLengthDist, EVT_Float),
    funcEntry1("setCircleSlices", EVT_Void, partSetSlices, EVT_Int),
    funcEntry3("setTumble", EVT_Void, partSetTumble, EVT_Float, EVT_Float, EVT_Float),
    funcEntry3("setDeltaTumble", EVT_Void, partSetDeltaTumble, EVT_Float, EVT_Float, EVT_Float),
    funcEntry3("setAnimation", EVT_Void, etgSetAnimation, EVT_ConstLabel, EVT_Float, EVT_Int),
    funcEntry3("setMeshAnimation", EVT_Void, etgSetMorphAnimation, EVT_ConstLabel, EVT_Float, EVT_Int),
    funcEntry1("setMeshStartFrame", EVT_Void, partSetMeshStartFrame, EVT_Float),
    funcEntry1("setFramerate", EVT_Void, partSetFramerate, EVT_Float),
    funcEntry1("setLoopFlag", EVT_Void, partSetLoopFlag, EVT_Int),
    funcEntry1("setStartFrame", EVT_Void, partSetStartFrame, EVT_Int),
    funcEntry1("setAlphaMode", EVT_Void, partSetAlphaMode, EVT_Int),
    funcEntry1("setSpecular", EVT_Void, etgSetSpecular, EVT_Float),
    funcEntry1("setColorScheme", EVT_Void, partSetColorScheme, EVT_Int),
    funcEntry1("setTrueBillboard", EVT_Void, partSetTrueBillboard, EVT_Int),
    funcEntry1("setPseudoBillboard", EVT_Void, partSetPseudoBillboard, EVT_Int),
    funcEntryR1("setDepthWrite", EVT_Void, etgSetDepthWrite, EVT_Int, etgDepthWriteResolve),
//    funcEntryR1("setForceVisible", EVT_Void, etgSetDepthWrite, EVT_Int, etgDepthWriteResolve),
    //funcEntry1("", EVT_Void, , EVT_Float),

    //modify a current particle system
    funcEntry2("modifyDrag", EVT_Void, etgModifyDrag, EVT_Int, EVT_Float),
    funcEntry2("modifyScale", EVT_Void, partModifyScale, EVT_Int, EVT_Float),
    funcEntry2("modifyDeltaScale", EVT_Void, partModifyDeltaScale, EVT_Int, EVT_Float),
    funcEntry2("modifyDeltaLength", EVT_Void, partModifyDeltaLength, EVT_Int, EVT_Float),
    funcEntry2("modifyColorA", EVT_Void, partModifyColorC, EVT_Int, EVT_RGBA),
    funcEntry2("modifyColor", EVT_Void, partModifyColorC, EVT_Int, EVT_RGB),
    funcEntry4("modifyColorBias", EVT_Void, partModifyColorBias, EVT_Int, EVT_Float, EVT_Float, EVT_Float),
    funcEntry2("modifyAddColor", EVT_Void, partModifyAddColor, EVT_Int, EVT_RGB),
    funcEntry5("modifyDeltaColor", EVT_Void, partModifyDeltaColor, EVT_Int, EVT_Float, EVT_Float, EVT_Float, EVT_Float),
    funcEntry2("modifyLighting", EVT_Void, partModifyLighting, EVT_Int, EVT_Float),
    funcEntry2("modifyIllum", EVT_Void, partModifyIllum, EVT_Int, EVT_Float),
    funcEntry2("modifyDeltaIllum", EVT_Void, partModifyDeltaIllum, EVT_Int, EVT_Float),
    funcEntry2("modifyLifespan", EVT_Void,  partModifyLifespan, EVT_Int, EVT_Float),
    funcEntry2("modifyAnimation", EVT_Void, partModifyAnimation, EVT_Int, EVT_ConstLabel),
    funcEntry2("modifyFramerate", EVT_Void, partModifyFramerate, EVT_Int, EVT_Float),
    funcEntry2("modifyVelLOF", EVT_Void, partModifyVelLOF, EVT_Int, EVT_Float),
    funcEntry2("modifyDeltaVelLOF", EVT_Void, partModifyDeltaVelLOF, EVT_Int, EVT_Float),
    funcEntry2("modifyVelR", EVT_Void, partModifyVelR, EVT_Int, EVT_Float),
    funcEntry2("modifyDeltaVelR", EVT_Void, partModifyDeltaVelR, EVT_Int, EVT_Float),
    funcEntry2("modifyMesh", EVT_Void, partModifyMesh, EVT_Int, EVT_Float),
    funcEntry2("modifyMeshAnimation", EVT_Void, partModifyMorph, EVT_Int, EVT_ConstLabel),
    funcEntry2("modifyMeshFramerate", EVT_Void, partModifyMorphFramerate, EVT_Int, EVT_Float),
    funcEntry2("modifyLength", EVT_Void, partModifyLength, EVT_Int, EVT_Float),
    funcEntry2("modifyLength", EVT_Void, partModifyLength, EVT_Int, EVT_Float),
    funcEntry2("modifyAlphaMode", EVT_Void, partModifyAlphaMode, EVT_Int, EVT_Int),
    funcEntry2("modifySpecular", EVT_Void, partModifyExponent, EVT_Int, EVT_Float),
    funcEntry2("modifyColorScheme", EVT_Void, partModifyColorScheme, EVT_Int, EVT_Int),
    funcEntry2("modifyAlphaMode", EVT_Void, partModifyAlphaMode, EVT_Int, EVT_Int),
    funcEntry2("modifyTrueBillboard", EVT_Void, partModifyPseudoBillboard, EVT_Int, EVT_Int),
    funcEntry2("modifyPseudoBillboard", EVT_Void, partModifyPseudoBillboard, EVT_Int, EVT_Int),
//    funcEntry2("modifyDepthWrite", EVT_Void, etgModifyDepthWrite, EVT_Int, EVT_Int),
/*
    funcEntry2("modifyMesh", EVT_Void, partModifyMesh, EVT_Int, EVT_Float),
//    funcEntry2("modifyMorphTarget", EVT_Void, , EVT_Int, EVT_Float),
//    funcEntry2("modifyMorphTime", EVT_Void, , EVT_Int, EVT_Float),
//    funcEntry2("modifyTumbleSpeed", EVT_Void, , EVT_Int, EVT_Float),
//    funcEntry2("modifyAnimation", EVT_Void, , EVT_Int, EVT_Float),
//    funcEntry2("modifyAnimFrame", EVT_Void, , EVT_Int, EVT_Float),
*/
/*
//    funcEntry2("modifyVelocity", EVT_Void, , EVT_Int, EVT_Float),
//    funcEntry2("modifyDeltaVelocity", EVT_Void, , EVT_Int, EVT_Float),
//    funcEntry2("modifyRotAngle", EVT_Void, , EVT_Int, EVT_Float),
//    funcEntry2("modifyRotAngleDelta", EVT_Void, , EVT_Int, EVT_Float),
*/
    //create particles
    funcEntryThisR2("createSprites", EVT_Int, etgCreateSprites, EVT_Int, EVT_Int, etgCreationResolve),
    funcEntryThisR2("createCircles", EVT_Int, etgCreateCircles, EVT_Int, EVT_Int, etgCreationResolve),
    funcEntryThisR2("createPoints", EVT_Int, etgCreatePoints, EVT_Int, EVT_Int, etgCreationResolve),
    funcEntryThisR2("createLines", EVT_Int, etgCreateLines, EVT_Int, EVT_Int, etgCreationResolve),
    funcEntryThisR2("createMeshes", EVT_Int, etgCreateMeshes, EVT_Int, EVT_Int, etgCreationResolve),
    //funcEntryThis2("createEffects", EVT_Int, etgCreateEffects, EVT_Int, EVT_Int),

    //Basic mathematics.  Need more?
    funcEntry1("inc", EVT_Int, etgInc, EVT_Int),
    funcEntry1("dec", EVT_Int, etgDec, EVT_Int),
    funcEntry2("fdiv", EVT_Float, etgFdiv, EVT_Float, EVT_Float),
    funcEntry2("fmult", EVT_Float, etgFmult, EVT_Float, EVT_Float),
    funcEntry2("fadd", EVT_Float, etgFadd, EVT_Float, EVT_Float),
    funcEntry2("fsub", EVT_Float, etgFsub, EVT_Float, EVT_Float),
    funcEntry2("div", EVT_Int, etgDiv, EVT_Int, EVT_Int),
    funcEntry2("mult", EVT_Int, etgMult, EVT_Int, EVT_Int),
    funcEntry2("add", EVT_Int, etgAdd, EVT_Int, EVT_Int),
    funcEntry2("sub", EVT_Int, etgSub, EVT_Int, EVT_Int),
    funcEntry1("sin", EVT_Float, etgSin, EVT_Float),
    funcEntry1("cos", EVT_Float, etgCos, EVT_Float),
    funcEntry2("frandom", EVT_Float, etgFRandom, EVT_Float, EVT_Float),
    funcEntry2("irandom", EVT_Int, etgIRandom, EVT_Int, EVT_Int),
    funcEntry6("crandom", EVT_RGB, etgCRandom, EVT_Int, EVT_Int, EVT_Int, EVT_Int, EVT_Int, EVT_Int),
    funcEntry8("carandom", EVT_RGBA, etgCARandom, EVT_Int, EVT_Int, EVT_Int, EVT_Int, EVT_Int, EVT_Int, EVT_Int, EVT_Int),

    //special-case functions
    funcEntryThis0("deleteParentShip", EVT_Int, etgParentShipDelete),
    funcEntryThis0("hideParentShip", EVT_Int, etgParentShipHide),
    funcEntryThis1("playSound", EVT_Int, soundEffect, EVT_Int),
    funcEntryThis1("playChatter", EVT_Int, etgChatterEventPlay, EVT_Int),
    funcEntryThis0("deathCries", EVT_Int, etgDeathCriesPlay),
    funcEntryThis0("getColorScheme", EVT_Int, etgOwnerColorSchemeGet),
    funcEntryThis0("getEffectVelocity",   EVT_Int, etgEffectVelocityGet),
    funcEntryThis0("damageDone", EVT_Int, etgDamageDone),

    //adjust parameters of effects to be spawned
    funcEntry1("setEffOffsetLOF",   EVT_Void, etgEffOffsetLOF,  EVT_Float),
    funcEntry2("setEffChangeLOF",   EVT_Void, etgEffChangeLOF,  EVT_Float, EVT_Float),
    funcEntry2("setEffOffsetR",     EVT_Void, etgEffOffsetR,    EVT_Float, EVT_Float),
    funcEntry3("setEffOffsetXYZ",   EVT_Void, etgEffOffsetXYZ , EVT_Float, EVT_Float, EVT_Float),
    funcEntry1("setEffOffsetVelLOF",EVT_Void, etgEffOffsetVelLOF,EVT_Float),
    funcEntry1("setEffOffsetSpin",  EVT_Void, etgEffOffsetSpin, EVT_Float),
    funcEntry1("setEffOffsetTime",  EVT_Void, etgEffOffsetTime, EVT_Float),
    funcEntryThis1("setEffOffsetDrag",  EVT_Void, etgEffOffsetDrag, EVT_Float),
    funcEntry1("setEffDrag",        EVT_Void, etgEffDrag      , EVT_Float),
    funcEntry0("setEffDefaults",    EVT_Void, etgEffDefaults),
    funcEntry1("setEffAttachParent",EVT_Void, etgEffAttachParent, EVT_Int),
    funcEntry1("setEffSpin",EVT_Void, etgEffSpin, EVT_Int),
    funcEntry1("setEffDeltaSpin",EVT_Void, etgEffDeltaSpin, EVT_Int),

    //adjust parameters of current effect
//    funcEntryThis2("effChangeLOF",  EVT_Void,    etgThisChangeLOF, EVT_Float, EVT_Float),
//    funcEntryThis1("effOffsetLOF",  EVT_Void,    etgThisOffsetLOF, EVT_Float),
//    funcEntryThis2("effOffsetR",  EVT_Void,      etgThisOffsetR, EVT_Float, EVT_Float),
    funcEntryThis3("effModifyXYZ",  EVT_Void,    etgThisOffsetXYZ, EVT_Float, EVT_Float, EVT_Float),
//    funcEntryThis1("effModifyVelLOF",  EVT_Void, etgThisOffsetVelLOF, EVT_Float),
//    funcEntryThis2("effModifyVelR",  EVT_Void,   etgThisOffsetVelR, EVT_Float, EVT_Float),
    funcEntryThis1("effScaleVelocity",  EVT_Void,etgThisScaleVelocity, EVT_Float),
    funcEntryThis1("effModifySpin",  EVT_Void,   etgThisOffsetSpin, EVT_Float),
    funcEntryThis1("effModifyDeltaSpin",  EVT_Void,   etgThisOffsetDeltaSpin, EVT_Float),
    funcEntryThis1("effModifyOffsetTime",  EVT_Void,   etgThisOffsetTime, EVT_Float),
    funcEntryThis1("effModifyDrag",  EVT_Void,      etgThisSetDrag, EVT_Float),

    //convert types
    funcEntry1("float2Int",  EVT_Int,    etgFloat2Int, EVT_Float),
    funcEntry1("int2Float",  EVT_Float,    etgInt2Float, EVT_Int),
    funcEntry3("int2Color",  EVT_RGB,    etgInts2Color, EVT_Int, EVT_Int, EVT_Int),
    funcEntry4("int2ColorA",  EVT_RGB,    etgInts2ColorA, EVT_Int, EVT_Int, EVT_Int, EVT_Int),
    funcEntry3("floats2Color",  EVT_RGB,    etgFloats2Color, EVT_Float, EVT_Float, EVT_Float),
    funcEntry4("floats2ColorA",  EVT_RGB,    etgFloats2ColorA, EVT_Float, EVT_Float, EVT_Float, EVT_Float),


    {NULL}
};

//this table handles differing formats of the standard 'if' statement
etgcondentry etgConditionalTable[] =
{
    {"=", "=",  {EOP_EqualVCI,       EOP_EqualVVI,       EOP_EqualVCF,       EOP_EqualVVF}},
    {"==", "=", {EOP_EqualVCI,       EOP_EqualVVI,       EOP_EqualVCF,       EOP_EqualVVF}},
    {"!=", "!=",{EOP_NotEqualVCI,    EOP_NotEqualVVI,    EOP_NotEqualVCF,    EOP_NotEqualVVF}},
    {"<>", "<>",{EOP_NotEqualVCI,    EOP_NotEqualVVI,    EOP_NotEqualVCF,    EOP_NotEqualVVF}},
    {">", "<=", {EOP_GreaterVCI,     EOP_GreaterVVI,     EOP_GreaterVCF,     EOP_GreaterVVF}},
    {">=", "<", {EOP_GreaterEqualVCI,EOP_GreaterEqualVVI,EOP_GreaterEqualVCF,EOP_GreaterEqualVVF}},
    {"<", ">=", {EOP_LessVCI,        EOP_LessVVI,        EOP_LessVCF,        EOP_LessVVF}},
    {"<=", ">", {EOP_LessEqualVCI,   EOP_LessEqualVVI,   EOP_LessEqualVCF,   EOP_LessEqualVVF}},
    {"end"}
};

//table for parsing the etg script
etgeffectstatic *etgEffectLoad(char *directory,char *field,void *dataToFillIn);
void etgEffectTestKey(char *directory,char *field,void *dataToFillIn);
void etgEffectProfileKey(char *directory,char *field,void *dataToFillIn);
void etgDeathEventSet(char *directory,char *field,void *dataToFillIn);
void etgFireEventSet(char *directory,char *field,void *dataToFillIn);
void etgHitEventSet(char *directory,char *field,void *dataToFillIn);
void etgBulletEffectSet(char *directory,char *field,void *dataToFillIn);
//void etgBulletDeflectEffectSet(char *directory,char *field,void *dataToFillIn);
void etgBulletDestroyEffectSet(char *directory,char *field,void *dataToFillIn);
void etgResourceEffectSet(char *directory,char *field,void *dataToFillIn);
void etgTractorBeamEffectSet(char *directory,char *field,void *dataToFillIn);
void etgSpecialPurposeEffectSet(char *directory,char *field, void *dataToFillIn);
void etgBulletColorSet(char *directory,char *field,void *dataToFillIn);
void etgSetBigDeathFactor(char *directory,char *field,void *dataToFillIn);
void etgDamageEventSet(char *directory,char *field,void *dataToFillIn);
void etgHyperspaceEventSet(char *directory,char *field,void *dataToFillIn);
void etgNumberEffectsParse(char *directory,char *field,void *dataToFillIn);
void etgSoftwareEffect(char *directory,char *field,void *dataToFillIn);
scriptEntry etgScriptParseTable[] =
{
    {"LoadEffect",                  (setVarCback)etgEffectLoad, NULL},
    {"SoftwareVersion",             etgSoftwareEffect, NULL},
    {"setDeathEvent",               etgDeathEventSet, NULL},
    {"setFireEvent",                etgFireEventSet, NULL},
    {"setHitEvent",                 etgHitEventSet, NULL},
    {"setBulletDestroyEvent",       etgBulletDestroyEffectSet, NULL},
//    {"setBulletDeflectEvent",       etgBulletDeflectEffectSet, NULL},
    {"bigBoomFactor",               etgSetBigDeathFactor, NULL},
    {"setBulletEffect",             etgBulletEffectSet, NULL},
    {"setBulletColor",              etgBulletColorSet, NULL},
    {"setAsteroidEffect",           etgResourceEffectSet, NULL},
    {"setGaseousEffect",            etgResourceEffectSet, (void *)1},
    {"setTractorBeamEffect",        etgTractorBeamEffectSet, NULL},
    {"setSpecialPurposeEffect",     etgSpecialPurposeEffectSet, NULL},
    {"setDamageEvent",              etgDamageEventSet, NULL},
    {"setHyperspaceEvent",          etgHyperspaceEventSet, NULL},
    {"NumberEffects",               etgNumberEffectsParse, NULL},
    {"etgHistoryScalarMin",         scriptSetSdwordCB, &etgHistoryScalarMin},
    {"etgSoftwareScalarDamage",     scriptSetReal32CB, &etgSoftwareScalarDamage},
    {"etgSoftwareScalarHit",        scriptSetReal32CB, &etgSoftwareScalarHit},
    {"etgSoftwareScalarFire",       scriptSetReal32CB, &etgSoftwareScalarFire},
#if ETG_TESTING
    {"TestEffect",                  etgEffectTestKey, NULL},
    {"ProfileEffect",               etgEffectProfileKey, NULL},
#endif
    {NULL, NULL, NULL}
};
sdword etgNumberEffectsExpected = -1;
sdword etgNumberEffectsLoaded = 0;
//list of script files to load
char *etgScriptFilename[] =
{
    "general.script",
    "R1.script",
    "R2.script",
    "P1.script",
    "P2.script",
    "P3.script",
    "Traders.script",
    NULL
};
#if ETG_TESTING
//stuff for testing effects
struct
{
    char *type;                                 //type of light
    char *lightName;                            //name of light
    char *effectName;                           //name of effect
    regionhandle region;                        //hot key
    sdword nTimes;                              //number of times to execute
    sdword iTime;                               //updated as the effect is being profiled
    real32 duration;                            //duration over which to execute these effects
}
etgTestKey[ETG_NumberTestKeys];
sdword etgTestKeyIndex;
#endif //ETG_TESTING

//dispatch tables for standard events
//etgeffectstatic *etgDefaultBoom;
//etgeffectstatic *etgDefaultBlast;
etglod *etgDeathEventTable[NUM_RACES][NUM_CLASSES][EDT_NumberExplosionTypes];
etglod *etgDeathEventTableDerelict[NUM_DERELICTTYPES][EDT_NumberExplosionTypes];
etglod *etgGunEventTable[NUM_RACES][NUM_GUN_SOUND_TYPES][EGT_NumberGunTypes];
etglod *etgResourceEffectTable[NUM_RACES][ETG_NumberResourceTypes];
etglod *etgTractorBeamEffectTable[NUM_RACES];
etglod *etgSpecialPurposeEffectTable[EGT_NumberOfSpecialEffects];
etglod *etgDamageEffectTable[NUM_CLASSES][DMG_NumberDamageTypes];
etglod *etgHyperspaceEffect = NULL;

sdword etgBigDeathFactor[NUM_RACES][NUM_CLASSES];
sdword etgBigDeathFactorDerelict[NUM_DERELICTTYPES];
color etgBulletColor[NUM_RACES][NUM_GUN_SOUND_TYPES];
ubyte etgDeathModeByGunType[] =
{
    /*GS_LargeEnergyCannon,    */EDT_ProjectileHit,
    /*GS_LargeIonCannon,       */EDT_BeamHit,
    /*GS_LargePlasmaBomb,      */EDT_ProjectileHit,
    /*GS_LargeProjectile,      */EDT_ProjectileHit,
    /*GS_MediumEnergyCannon,   */EDT_ProjectileHit,
    /*GS_MediumIonCannon,      */EDT_BeamHit,
    /*GS_MediumPlasmaBomb,     */EDT_ProjectileHit,
    /*GS_MediumProjectile,     */EDT_ProjectileHit,
    /*GS_MineLauncher,         */EDT_ProjectileHit,
    /*GS_MissleLauncher,       */EDT_ProjectileHit,
    /*GS_SmallEnergyCannon,    */EDT_ProjectileHit,
    /*GS_SmallIonCannon,       */EDT_ProjectileHit,
    /*GS_SmallPlasmaBomb,      */EDT_ProjectileHit,
    /*GS_SmallProjectile,      */EDT_ProjectileHit,
    /*GS_VeryLargeEnergyCannon,*/EDT_ProjectileHit,
    /*GS_VeryLargeIonCannon,   */EDT_ProjectileHit,
    /*GS_VeryLargePlasmaBomb,  */EDT_ProjectileHit,
    /*GS_Laser,                */EDT_BeamHit,
};

//spacial offset information for effects to be created in subsequent 'spawn' calls
struct
{
    real32 offsetLOF;
    real32 offsetR, offsetTheta;
    real32 changeLOFTheta, changeLOFMu;
    vector offsetXYZ;
    real32 offsetVelLOF;
    real32 offsetSpin;
    real32 offsetTime;
    real32 drag;
    bool   attachParent;
    real32 spin;
    real32 deltaSpin;
}
etgEffOffset =
{
    0.0f,
    0.0f, 0.0f,
    0.0f, 0.0f,
    {0.0f, 0.0f, 0.0f},
    0.0f,
    0.0f,
    0.0f,
    0.0f,
    FALSE,
    0.0f,
    0.0f
};

//ETG mesh registry
//meshdata *etgMeshRegistry[ETG_NumberMeshes];
//sdword etgMeshRegistryIndex = 0;

typedef struct
{
    meshdata *mesh;                             //NULL = registry slot free
    char *filename;
}
etgmeshreg;
etgmeshreg etgMeshRegistry[ETG_NumberMeshes];

#if ETG_DISABLEABLE
sdword etgEffectsEnabled = TRUE;
#endif

//variables for scanning time index blocks
etgvarentry *etgTimeIndexVar;
real32 etgTimeIndexTime;

//variables for ETG user tweaks:
real32 etgSoftwareScalarDamage = ETG_SoftwareScalarDamage;//scale damage effects down when in software mode.
real32 etgSoftwareScalarHit = ETG_SoftwareScalarHit;//scale down hits, deflections, bullet deaths
real32 etgSoftwareScalarFire = ETG_SoftwareScalarFire;//scale down muzzle flash effects
sdword etgHistoryScalar = 256;                  //scale down the number of effects at the user's command
sdword etgHistoryScalarMin = ETG_HistoryScalarMin;//minimum frequency scale-down
bool   etgDamageEffectsEnabled = TRUE;          //damage effects on
bool   etgHitEffectsEnabled = TRUE;             //hit effects on
bool   etgFireEffectsEnabled = TRUE;            //muzzle flash effects on
bool   etgBulletEffectsEnabled = TRUE;          //bullet effects on

/*=============================================================================
    Functions:
=============================================================================*/

/*-----------------------------------------------------------------------------
    Name        : etgLODLoad
    Description : Loads in an effect into a LOD structure, growing or creating
                    the structure as needed.
    Inputs      : directory,name - full filename of effect to load (when combined).
                  oldLOD - LOD structure to grow or NULL if not already loaded.
    Outputs     : allocates and returns a LOD structure of oldLOD is NULL, or
                    re-allocates and grows the structure for returning.
    Return      : newly (re)allocated structure.
----------------------------------------------------------------------------*/
etglod *etgLODLoad(char *directory, char *name, etglod *oldLOD)
{
    char path[PATH_Max];
    etglod *newLOD;

    //create path to effect
    strcpy(path, directory);
    strcat(path, name);

    //allocate a LOD structure as needed
    if (oldLOD == NULL)
    {
        newLOD = memAlloc(etgLODSize(1), "NewEtgLod", NonVolatile);
        newLOD->nLevels = 0;
    }
    else
    {
        newLOD = memAlloc(etgLODSize(oldLOD->nLevels + 1), "Realloc'dEtgLod", NonVolatile)
        memcpy(newLOD, oldLOD, etgLODSize(oldLOD->nLevels));
        memFree(oldLOD);
    }
    //load in the effect
    etgErrorRecoverable = FALSE;
    newLOD->level[newLOD->nLevels] = etgEffectCodeLoad(path);
    newLOD->nLevels++;                                      //count the new level loaded
    return(newLOD);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectLoad
    Description : Load in an effect after manipulating the filename a bit.
    Inputs      : Standard scripting parameters.
    Outputs     : Newly loaded effect.
    Return      :
    Note        : Can be called from script loading stuff or directly from
                    special loading functions.
----------------------------------------------------------------------------*/
etgeffectstatic *etgEffectLoad(char *directory,char *field,void *dataToFillIn)
{
    char path[PATH_Max];

    strcpy(path, directory);
    strcat(path, field);
    etgErrorRecoverable = FALSE;
    return(etgEffectCodeLoad(path));
}

/*-----------------------------------------------------------------------------
    Name        : etgSoftwareEffect
    Description : Script-parsing callback to define what effect to use for a
                    specified effect if playing in software
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgSoftwareEffect(char *directory,char *field,void *dataToFillIn)
{
    etgeffectstatic *hardwareEffect;
    char *hardwareName, *softwareName;

    hardwareName = strtok(field, ETG_TokenDelimiters);
    softwareName = strtok(NULL, ETG_TokenDelimiters);
    hardwareEffect = etgEffectLoad(directory, hardwareName, NULL);
    hardwareEffect->softwareVersion = etgEffectLoad(directory, softwareName, NULL);
}

/*-----------------------------------------------------------------------------
    Name        : etgSetBigDeathFactor
    Description : Set the factor at which a ship dies cataclysmically
    Inputs      : field in the format of <shipClass>, <number>
                    number is a negative, absolute number
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgSetBigDeathFactor(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipClass classy;
    ShipRace racerX;
    DerelictType typist;
    sdword *whereToScan;
    sdword nScanned;

    param = strtok(field, ETG_TokenDelimiters);
    dbgAssert(param);
    racerX = StrToShipRace(field);

    if (racerX == -1)
    {                                                       //if we could not parse a race, it may be a derelict type
        typist = StrToDerelictType(param);
        dbgAssert(typist >= 0 && typist < NUM_DERELICTTYPES);
        whereToScan = &etgBigDeathFactorDerelict[typist];
        goto scanTheFactor;
    }

    dbgAssert(racerX >= 0 && racerX < NUM_RACES);
    param = strtok(NULL, ETG_TokenDelimiters);                           //get the second parameter (class)
    dbgAssert(param);
    classy = StrToShipClass(param);
    dbgAssert(classy >= CLASS_Mothership && classy < NUM_CLASSES);
    whereToScan = &etgBigDeathFactor[racerX][classy];

scanTheFactor:
    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    nScanned = sscanf(param, "%d", whereToScan);
    dbgAssert(nScanned == 1);
}

/*-----------------------------------------------------------------------------
    Name        : etgNumberEffectsParse
    Description : Read in the number of effects to expect, for the horse race.
    Inputs      : standard script-parsing fare
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgNumberEffectsParse(char *directory,char *field,void *dataToFillIn)
{
    scriptSetSdwordCB(directory, field, &etgNumberEffectsExpected);
    dbgAssert(etgNumberEffectsExpected > 0 && etgNumberEffectsExpected < 2000);
    HorseRaceBeginBar(ETG_BAR);
}

/*-----------------------------------------------------------------------------
    Name        : etgTokTheType
    Description : Called by the following function, calls strtok to get a
                    explosion type.
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
sdword etgTokTheType(void)
{
    char *param;
    sdword exType;

    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    if (strcmp(param, "damage") == 0)
    {
        exType = EDT_AccumDamage;
    }
    else if (strcmp(param, "projectile") == 0)
    {
        exType = EDT_ProjectileHit;
    }
    else if (strcmp(param, "beam") == 0)
    {
        exType = EDT_BeamHit;
    }
    else if (strcmp(param, "collision") == 0)
    {
        exType = EDT_Collision;
    }
    else if (strcmp(param, "special") == 0)
    {
        exType = EDT_Special;
    }
    else
    {
        dbgFatalf(DBG_Loc, "Unrecognised explosion type '%s'", param);
    }
    return(exType);
}

/*-----------------------------------------------------------------------------
    Name        : etgDeathEventSet
    Description : Sets a death event for a ship.  It is assumed that all death
                    events are stubbed out already.
    Inputs      : scripting callback
                  field should have the format: <className>, <deathType>, <eventFile>
    Outputs     : loads in the named effect for the named death type of the named ship class
    Return      :
----------------------------------------------------------------------------*/
void etgDeathEventSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipClass classy;
    ShipRace racerX;
    DerelictType typist;
    sdword exType;
    etglod **whereToLoad;

    param = strtok(field, ETG_TokenDelimiters);
    dbgAssert(param);
    racerX = StrToShipRace(field);

    if (racerX == -1)
    {                                                       //if we could not parse a race, it may be a derelict type
        typist = StrToDerelictType(param);
        dbgAssert(typist >= 0 && typist < NUM_DERELICTTYPES);
        exType = etgTokTheType();
        whereToLoad = &etgDeathEventTableDerelict[typist][exType];
        goto loadTheEffect;
    }

    dbgAssert(racerX >= 0 && racerX < NUM_RACES);
    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    classy = StrToShipClass(param);
    dbgAssert(classy >= CLASS_Mothership && classy < NUM_CLASSES);

    exType = etgTokTheType();
    whereToLoad = &etgDeathEventTable[racerX][classy][exType];

loadTheEffect:
    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    if (strcmp(param, "NULL") == 0)
    {
        //!!! notice the potential memory leak here?  There could be valid effects assigned to this pointer and then this pointer is set to NULL.
        *whereToLoad = NULL;
    }
    else
    {
        *whereToLoad = etgLODLoad(directory, param, *whereToLoad);
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgParseRaceSoundParam
    Description : Parse a race, gun sound type and parameter from a string
    Inputs      : params - string to parse
    Outputs     : race - race parsed from string
                  gunSound - sound parsed from string
                  param - remainder of string
    Return      : void
----------------------------------------------------------------------------*/
void etgParseRaceSoundParam(char *params, ShipRace *race, GunSoundType *gunSound, char **param)
{
    *param = strtok(params, ETG_TokenDelimiters);
    dbgAssert(*param);
    *race = StrToShipRace(params);
    dbgAssert(*race >= 0 && *race < NUM_RACES);

    *param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(*param);
    *gunSound = StrToGunSoundType(*param);
    dbgAssert(*gunSound >= GS_LargeEnergyCannon && *gunSound < NUM_GUN_SOUND_TYPES);

    *param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(*param);
}

/*-----------------------------------------------------------------------------
    Name        : etgFireEventSet
    Description : Same as before, just for guns.  This one defines the shot event.
    Inputs      :
    Outputs     : Sets the fire, hit or bullet effects.
    Return      :
----------------------------------------------------------------------------*/
void etgFireEventSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipRace racerX;
    GunSoundType gunSound;

    etgParseRaceSoundParam(field, &racerX, &gunSound, &param);

    if (strcmp(param, "NULL") == 0)
    {
        etgGunEventTable[racerX][gunSound][EGT_GunFire] = NULL;
    }
    else
    {
        etgGunEventTable[racerX][gunSound][EGT_GunFire] = etgLODLoad(directory, param, etgGunEventTable[racerX][gunSound][EGT_GunFire]);
    }
}

void etgHitEventSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipRace racerX;
    GunSoundType gunSound;

    etgParseRaceSoundParam(field, &racerX, &gunSound, &param);

    if (strcmp(param, "NULL") == 0)
    {
        etgGunEventTable[racerX][gunSound][EGT_GunFire] = NULL;
    }
    else
    {
        etgGunEventTable[racerX][gunSound][EGT_GunHit] = etgLODLoad(directory, param, etgGunEventTable[racerX][gunSound][EGT_GunHit]);
    }
}

void etgBulletEffectSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipRace racerX;
    GunSoundType gunSound;

    etgParseRaceSoundParam(field, &racerX, &gunSound, &param);

    if (strcmp(param, "NULL") == 0)
    {
        etgGunEventTable[racerX][gunSound][EGT_GunBullet] = NULL;
    }
    else
    {
        etgGunEventTable[racerX][gunSound][EGT_GunBullet] = etgLODLoad(directory, param, etgGunEventTable[racerX][gunSound][EGT_GunBullet]);
    }
}

void etgBulletDestroyEffectSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipRace racerX;
    GunSoundType gunSound;

    etgParseRaceSoundParam(field, &racerX, &gunSound, &param);

    if (strcmp(param, "NULL") == 0)
    {
        etgGunEventTable[racerX][gunSound][EGT_BulletDestroyed] = NULL;
    }
    else
    {
        etgGunEventTable[racerX][gunSound][EGT_BulletDestroyed] = etgLODLoad(directory, param, etgGunEventTable[racerX][gunSound][EGT_BulletDestroyed]);
    }
}
/*
void etgBulletDeflectEffectSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipRace racerX;
    GunSoundType gunSound;

    etgParseRaceSoundParam(field, &racerX, &gunSound, &param);

    if (strcmp(param, "NULL") == 0)
    {
        etgGunEventTable[racerX][gunSound][EGT_BulletDeflected] = NULL;
    }
    else
    {
        etgGunEventTable[racerX][gunSound][EGT_BulletDeflected] = etgLODLoad(directory, param, etgGunEventTable[racerX][gunSound][EGT_BulletDeflected]);
    }
}
*/
void etgResourceEffectSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipRace racerX;

    param = strtok(field, ETG_TokenDelimiters);
    dbgAssert(param);
    racerX = StrToShipRace(field);
    dbgAssert(racerX >= 0 && racerX < NUM_RACES);

    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    if (dataToFillIn == NULL)
    {                                                       //NULL = asteroid, !NULL = gaseous
        etgResourceEffectTable[racerX][ETG_AsteroidEffect] =
            etgLODLoad(directory, param, etgResourceEffectTable[racerX][ETG_AsteroidEffect]);
    }
    else
    {
        etgResourceEffectTable[racerX][ETG_GaseousEffect] =
            etgLODLoad(directory, param, etgResourceEffectTable[racerX][ETG_GaseousEffect]);
    }
}

void etgTractorBeamEffectSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipRace racerX;

    param = strtok(field, ETG_TokenDelimiters);
    dbgAssert(param);
    racerX = StrToShipRace(field);
    dbgAssert(racerX >= 0 && racerX < NUM_RACES);

    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    etgTractorBeamEffectTable[racerX] = etgLODLoad(directory, param, etgTractorBeamEffectTable[racerX]);
}

void etgDamageEventSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipClass classy;
    sdword exType;

    param = strtok(field, ETG_TokenDelimiters);
    dbgAssert(param);
    classy = StrToShipClass(field);
    dbgAssert(classy >= CLASS_Mothership && classy < NUM_CLASSES);

    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    if (strcmp(param, "light") == 0)
    {
        exType = DMG_Light;
    }
    else if (strcmp(param, "heavy") == 0)
    {
        exType = DMG_Heavy;
    }
    else if (strcmp(param, "dying") == 0)
    {
        exType = DMG_Dying;
    }
    else
    {
        dbgFatalf(DBG_Loc, "Unrecognized damage type '%s'", param);
    }

    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    if (strcmp(param, "NULL") == 0)
    {
        etgDamageEffectTable[classy][exType] = NULL;
    }
    else
    {
        etgDamageEffectTable[classy][exType] = etgLODLoad(directory, param, etgDamageEffectTable[classy][exType]);
    }
}

void etgHyperspaceEventSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;

    param = strtok(field, ETG_TokenDelimiters);
    dbgAssert(param);
    if (strcmp(param, "NULL") == 0)
    {
        etgHyperspaceEffect = NULL;
    }
    else
    {
        etgHyperspaceEffect = etgLODLoad(directory, param, etgHyperspaceEffect);
    }
}

void etgSpecialPurposeEffectSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    SpecialEffectType effectnum;

    param = strtok(field, ETG_TokenDelimiters);
    dbgAssert(param);
    effectnum = StrToEffectNum(field);
    dbgAssert(effectnum >= 0 && effectnum < EGT_NumberOfSpecialEffects);

    param = strtok(NULL, ETG_TokenDelimiters);
    dbgAssert(param);
    etgSpecialPurposeEffectTable[effectnum] = etgLODLoad(directory, param, etgSpecialPurposeEffectTable[effectnum]);
}


void etgBulletColorSet(char *directory,char *field,void *dataToFillIn)
{
    char *param;
    ShipRace racerX;
    GunSoundType gunSound;
    udword red, green, blue, nScanned;

    etgParseRaceSoundParam(field, &racerX, &gunSound, &param);

    nScanned = sscanf(param, "%d", &red);
    dbgAssert(nScanned == 1);
    dbgAssert(red >= 0 && red < 256);
    param = strtok(NULL, ETG_TokenDelimiters);
    nScanned = sscanf(param, "%d", &green);
    dbgAssert(nScanned == 1);
    dbgAssert(green >= 0 && green < 256);
    param = strtok(NULL, ETG_TokenDelimiters);
    nScanned = sscanf(param, "%d", &blue);
    dbgAssert(nScanned == 1);
    dbgAssert(blue >= 0 && blue < 256);
    etgBulletColor[racerX][gunSound] = colRGB(red, green, blue);
}

#if ETG_TESTING
/*-----------------------------------------------------------------------------
    Name        : etgEffectTest
    Description : key-region callback handler for testing effects
    Inputs      : ID - index into etgTestKey of effect to test
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
udword etgEffectTest(regionhandle reg, sdword ID, udword event, udword data)
{
    etgeffectstatic *stat;
    Effect *newEffect;
    MEXChunk *mexChunk;
    vector *posChunk, *LOFChunk, LOFTemp, LOF;//, position;
    Ship *ship;
    ShipStaticInfo *shipStatic;
    matrix coordSystem;
    sdword size;

    if (multiPlayerGame)
    {
#if ETG_VERBOSE_LEVEL >= 1
        dbgMessagef("\nEffect testing disabled in multiplayer game");
#endif
        return(0);
    }

    //get ship and ship static pointers
    if (selSelected.numShips == 0)
    {
#if ETG_VERBOSE_LEVEL >= 1
        dbgMessagef("\nSelect a ship before trying to test an effect");
#endif
        return(0);
    }
    ship = selSelected.ShipPtr[0];
    shipStatic = (ShipStaticInfo *)ship->staticinfo;

    //find the light
    mexChunk = mexGetChunk(shipStatic->staticheader.pMexData, etgTestKey[ID].type, etgTestKey[ID].lightName);
    if (mexChunk == NULL)
    {
#if ETG_VERBOSE_LEVEL >= 1
        dbgMessagef("\nLight '%s' type '%s' not found in ship 0x%x", etgTestKey[ID].lightName, etgTestKey[ID].type, ship);
#endif
        return(0);
    }
    if ((strcmp(mexChunk->type, "Eng") == 0) || (strcmp(mexChunk->type, "Gun") == 0))
    {
        LOFChunk = &((MEXEngineChunk *)mexChunk)->normal;
        posChunk = &((MEXEngineChunk *)mexChunk)->position;
    }
    else
    {
#if ETG_VERBOSE_LEVEL >= 1
        dbgMessagef("\nLight '%s' type '%s' not a gun or engine chunk.", etgTestKey[ID].lightName, etgTestKey[ID].type);
#endif
        return(0);
    }

    //find the effect
    stat = etgEffectStaticFind(etgTestKey[ID].effectName, FALSE);
    if (stat == NULL)
    {
#if ETG_VERBOSE_LEVEL >= 1
        dbgMessagef("\nEffect '%s' not found.", etgTestKey[ID].effectName);
#endif
        return(0);
    }
    if (RGLtype == SWtype)
    {
        if (stat->softwareVersion != NULL)
        {
            stat = stat->softwareVersion;
        }
    }
#if ETG_VERBOSE_LEVEL >= 1
    dbgMessagef("\nTesting effect '%s' with light '%s'", etgTestKey[ID].effectName, etgTestKey[ID].lightName);
#endif

    //now we've found everything we need, let's make a matrix
    mexVecToVec(&LOFTemp, LOFChunk);
    matMultiplyMatByVec(&LOF, &ship->rotinfo.coordsys, &LOFTemp);//LOF in world coords
    matCreateCoordSysFromHeading(&coordSystem, &LOF);
    //now we have a coordinate system, let's create an effect
//    size = etgEffectSize(stat->nParticleBlocks);            //compute size of effect
    size = stat->effectSize;
    newEffect = memAlloc(size, "TestingEffect", 0);         //allocate the new effect
    newEffect->objtype = OBJ_EffectType;
    newEffect->flags = SOF_Rotatable | SOF_AttachPosition | SOF_AttachVelocity | SOF_AttachCoordsys;
    newEffect->staticinfo = (StaticInfo *)stat;
    ClearNode(newEffect->renderlink);
    newEffect->currentLOD = ship->currentLOD;
    newEffect->cameraDistanceSquared = ship->cameraDistanceSquared;
    newEffect->timeElapsed = 0.0f;                          //brand new !!! how about time-offsets?

//    vecAdd(newEffect->posinfo.position, position, ship->posinfo.position);
    newEffect->posinfo.position = ship->posinfo.position;
    vecAddToScalarMultiply(newEffect->posinfo.position, ship->posinfo.velocity, universe.phystimeelapsed);
    etgEffectCodeStart(stat, newEffect, 0);                 //get the code a-runnin'
    if (bitTest(stat->specialOps, ESO_SortForward))
    {                                                       //make it sort forward, if applicable
        bitSet(newEffect->flags, SOF_AlwaysSortFront);
    }
    newEffect->posinfo.isMoving = FALSE;
    newEffect->posinfo.haventCalculatedDist = TRUE;
    newEffect->rotinfo.coordsys = coordSystem;
    univUpdateObjRotInfo((SpaceObjRot *)newEffect);

    newEffect->posinfo.velocity = ship->posinfo.velocity;
    newEffect->drag = 1.0f;                                 //default is no drag at all
//    newEffect->owner = ship;                                // ship that hosts this effect
    newEffect->owner = NULL;
    newEffect->nSpawnedAttachedToOwner = 0;
    newEffect->magnitudeSquared = 1.0f;
    listAddNode(&universe.SpaceObjList,&(newEffect->objlink),newEffect);
    listAddNode(&universe.effectList,&(newEffect->effectLink),newEffect);
    if (bitTest(stat->specialOps, ESO_WorldRender))
    {
        bitSet(newEffect->flags, SOF_Hide);
        univAddToWorldList((Derelict *)newEffect);
    }
    else
    {
        univAddObjToRenderListIf((SpaceObj *)newEffect,(SpaceObj *)ship);     // add to render list if parent ship is in render list
    }

    return(0);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectProfileBaby
    Description : Baby callback for profiling an effect
    Inputs      : ID - index of test key structure
                  data -  NULL
    Outputs     :
    Return      : TRUE if we've profiled enough already
----------------------------------------------------------------------------*/
bool etgEffectProfileBaby(udword ID, void *data, struct BabyCallBack *baby)
{
    if (etgTestKey[ID].iTime <= 0)
    {
        return(TRUE);
    }

    etgEffectTest(NULL, ID, 0xffffffff, 0);                 //test an effect
    etgTestKey[ID].iTime--;                                 //decrement the counter
    if (etgTestKey[ID].iTime == 0)                          //see if it's timed out
    {
        return(TRUE);
    }
    return(FALSE);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectProfile
    Description : Profile and effect (region callback)
    Inputs      : standard region crap
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
udword etgEffectProfile(regionhandle reg, sdword ID, udword event, udword data)
{
    if (etgTestKey[ID].iTime != 0)
    {                                                       //if already being profiled
        etgTestKey[ID].iTime = 0;                           //stop the profile
        return(0);
    }
    //start the baby calling
    taskCallBackRegister(etgEffectProfileBaby, ID, NULL, min(etgTestKey[ID].duration / (real32)etgTestKey[ID].nTimes, UNIVERSE_UPDATE_PERIOD));

    etgTestKey[ID].iTime = etgTestKey[ID].nTimes - 1;
    return(0);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectTestKey
    Description : Script callback for testing an effect
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgEffectTestKey(char *directory,char *field,void *dataToFillIn)
{
    keyindex key;
    char *lightName, *effectName, *type;

    //find key number
    lightName = strtok(field, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (lightName == NULL)
    {
        dbgFatalf(DBG_Loc, "Error in %s\\%s: TestEffect format is '<keyChar>, <LightName>, <effectName>."ETG_Directory, ETG_Script);
    }
#endif
    key = *lightName;
    //find mex type
    type = strtok(NULL, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (type == NULL)
    {
        dbgFatalf(DBG_Loc, "Error in %s\\%s: TestEffect format is '<keyChar>, <LightName>, <effectName>."ETG_Directory, ETG_Script);
    }
#endif
    //find gunName
    lightName = strtok(NULL, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (lightName == NULL)
    {
        dbgFatalf(DBG_Loc, "Error in %s\\%s: TestEffect format is '<keyChar>, <LightName>, <effectName>."ETG_Directory, ETG_Script);
    }
#endif
    //find effect name
    effectName = strtok(NULL, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (effectName == NULL)
    {
        dbgFatalf(DBG_Loc, "Error in %s\\%s: TestEffect format is '<keyChar>, <LightName>, <effectName>."ETG_Directory, ETG_Script);
    }
#endif

    //now create a key region to execute this fandangled effect
#if ETG_ERROR_CHECKING
    if (etgTestKeyIndex >= ETG_NumberTestKeys)
    {
        dbgFatalf(DBG_Loc, "Exceeded %d test keys.", ETG_NumberTestKeys);
    }
#endif
#if ETG_VERBOSE_LEVEL >= 1
    dbgMessagef("\nAdding effect test key '%c' to test effect '%s' with vector from '%s'", key, effectName, lightName);
#endif
    etgTestKey[etgTestKeyIndex].type = memStringDupeNV(type);
    etgTestKey[etgTestKeyIndex].lightName = memStringDupeNV(lightName);
    etgTestKey[etgTestKeyIndex].effectName = memStringDupeNV(effectName);
    etgTestKey[etgTestKeyIndex].region = regKeyChildAlloc(ghMainRegion, etgTestKeyIndex, RPE_KeyDown, etgEffectTest, 1, key);
    etgTestKey[etgTestKeyIndex].nTimes = 1;
    etgTestKey[etgTestKeyIndex].iTime = 0;
    etgTestKey[etgTestKeyIndex].duration = 1.0f;
    etgTestKeyIndex++;
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectProfileKey
    Description : Script parsing callback for profiling an effect
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgEffectProfileKey(char *directory,char *field,void *dataToFillIn)
{
    sdword nScanned;
    char *string;

    etgEffectTestKey(directory, field, dataToFillIn);

    string = strtok(NULL, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (string == NULL)
    {
        dbgFatalf(DBG_Loc, "Error in %s\\%s: ProfileEffect format is '<keyChar>, <LightName>, <effectName>, <nTimes>, <duration>."ETG_Directory, ETG_Script);
    }
#endif
    nScanned = sscanf(string, "%d", &etgTestKey[etgTestKeyIndex - 1].nTimes);
#if ETG_ERROR_CHECKING
    if (nScanned != 1)
    {
        dbgFatalf(DBG_Loc, "Error in %s\\%s: ProfileEffect format is '<keyChar>, <LightName>, <effectName>, <nTimes>, <duration>."ETG_Directory, ETG_Script);
    }
#endif
    string = strtok(NULL, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (string == NULL)
    {
        dbgFatalf(DBG_Loc, "Error in %s\\%s: ProfileEffect format is '<keyChar>, <LightName>, <effectName>, <nTimes>, <duration>."ETG_Directory, ETG_Script);
    }
#endif
    nScanned = sscanf(string, "%f", &etgTestKey[etgTestKeyIndex - 1].duration);
#if ETG_ERROR_CHECKING
    if (nScanned != 1)
    {
        dbgFatalf(DBG_Loc, "Error in %s\\%s: ProfileEffect format is '<keyChar>, <LightName>, <effectName>, <nTimes>, <duration>."ETG_Directory, ETG_Script);
    }
#endif
#if ETG_VERBOSE_LEVEL >= 1
    dbgMessagef(" %d times in %.2f seconds.", etgTestKey[etgTestKeyIndex - 1].nTimes, etgTestKey[etgTestKeyIndex - 1].duration);
#endif
    regFunctionSet(etgTestKey[etgTestKeyIndex - 1].region, etgEffectProfile);
}
#endif //ETG_TESTING

/*-----------------------------------------------------------------------------
    Name        : etgStartup
    Description : Starts the ETG module and loads in any files which need loading.
    Inputs      : void
    Outputs     : Load in some ETG files.
    Return      :
----------------------------------------------------------------------------*/
void etgStartup(void)
{
    sdword index;
#if ETG_VERBOSE_LEVEL >= 1
    dbgMessagef("\nStarting effects");
#endif
    //load in and set the default explosion and hit/fire
    memClearDword(etgDeathEventTable, 0, NUM_RACES * NUM_CLASSES * EDT_NumberExplosionTypes);
    memClearDword(etgDeathEventTableDerelict, 0, NUM_DERELICTTYPES * EDT_NumberExplosionTypes);
    memClearDword(etgGunEventTable, 0, NUM_RACES * NUM_GUN_SOUND_TYPES * EGT_NumberGunTypes);
    memClearDword(etgResourceEffectTable, 0, NUM_RACES * ETG_NumberResourceTypes);
    memClearDword(etgTractorBeamEffectTable, 0, NUM_RACES);
    memClearDword(etgSpecialPurposeEffectTable,0,EGT_NumberOfSpecialEffects);

    //default the big death amount to something
    memClearDword(etgBigDeathFactor, (udword)-10, NUM_RACES * NUM_CLASSES);
    memClearDword(etgBigDeathFactorDerelict, (udword)-10, NUM_DERELICTTYPES);
    //set the default bullet colors for R1 and everything else
    memClearDword(etgBulletColor, R2BulletColor, NUM_RACES * NUM_GUN_SOUND_TYPES);
    memClearDword(etgBulletColor, R1BulletColor, NUM_GUN_SOUND_TYPES);
    //load in the rest of the effects
#if ETG_TESTING
    etgTestKeyIndex = 0;
#endif
    memset(etgMeshRegistry, 0, sizeof(etgMeshRegistry));    //initial clear out of the mesh registry
    memset(etgEventTable, 0, sizeof(etgEventTable));        //initial clear out of the event registry
    for (index = 0; etgScriptFilename[index] != NULL; index++)
    {
        scriptSet(ETG_Directory, etgScriptFilename[index], etgScriptParseTable);
    }
    etgNumberEffectsExpected = -1;
//    scriptSet(ETG_Directory, ETG_Script, etgScriptParseTable);
}

/*-----------------------------------------------------------------------------
    Name        : etgShutdown
    Description : Shuts down the effect module, freeing up all the old effects.
    Inputs      : void
    Outputs     : Frees all the effects loaded in etgStartup
    Return      : void
----------------------------------------------------------------------------*/
void etgShutdown(void)
{
    sdword index, j, k;

    //free all effect statics
#if ETG_VERBOSE_LEVEL >= 1
    dbgMessagef("\nDeleting up to %d effects", ETG_EventListLength);
#endif
    for (index = 0; index < ETG_EventListLength; index++)
    {
        if (etgEventTable[index].effectStatic != NULL)
        {
            etgEffectCodeDelete(etgEventTable[index].effectStatic, TRUE);
            etgEventTable[index].effectStatic = NULL;
        }
    }
    //free all meshes allocated for effects
    /*
    for (index = 0; index < etgMeshRegistryIndex; index++)
    {
        meshFree(etgMeshRegistry[index]);
    }
    */
//    etgMeshRegistryIndex = 0;
    etgMeshRegistryReset();
    //free all the effect LOD's
    for (index = 0; index < NUM_RACES; index++)
    {
        for (j = 0; j < NUM_CLASSES; j++)
        {
            for (k = 0; k < EDT_NumberExplosionTypes; k++)
            {
                if (etgDeathEventTable[index][j][k] != NULL)
                {
                    memFree(etgDeathEventTable[index][j][k]);
                }
            }
        }
    }
    for (index = 0; index < NUM_DERELICTTYPES; index++)
    {
        for (k = 0; k < EDT_NumberExplosionTypes; k++)
        {
            if (etgDeathEventTableDerelict[index][k] != NULL)
            {
                memFree(etgDeathEventTableDerelict[index][k]);
            }
        }
    }
    for (index = 0; index < NUM_RACES; index++)
    {
        for (j = 0; j < NUM_GUN_SOUND_TYPES; j++)
        {
            for (k = 0; k < EGT_NumberGunTypes; k++)
            {
                if (etgGunEventTable[index][j][k] != NULL)
                {
                    memFree(etgGunEventTable[index][j][k]);
                }
            }
        }
    }
    for (index = 0; index < NUM_RACES; index++)
    {
        for (j = 0; j < ETG_NumberResourceTypes; j++)
        {
            if (etgResourceEffectTable[index][j] != NULL)
            {
                memFree(etgResourceEffectTable[index][j]);
            }
        }
    }
    for (index = 0; index < NUM_RACES; index++)
    {
        if (etgTractorBeamEffectTable[index] != NULL)
        {
            memFree(etgTractorBeamEffectTable[index]);
        }
    }
    for (index = 0; index < EGT_NumberOfSpecialEffects; index++)
    {
        if (etgSpecialPurposeEffectTable[index] != NULL)
        {
            memFree(etgSpecialPurposeEffectTable[index]);
        }
    }
    for (index = 0; index < NUM_CLASSES; index++)
    {
        for (j = 0; j < DMG_NumberDamageTypes; j++)
        {
            if (etgDamageEffectTable[index][j] != NULL)
            {
                memFree(etgDamageEffectTable[index][j]);
            }
        }
    }
    if (etgHyperspaceEffect != NULL)
    {
        memFree(etgHyperspaceEffect);
    }
#if ETG_TESTING
    //free all the test keys
    for (index = 0; index < etgTestKeyIndex; index++)
    {
        memFree(etgTestKey[index].type);
        memFree(etgTestKey[index].effectName);
        memFree(etgTestKey[index].lightName);
        regRegionDelete(etgTestKey[index].region);
    }
    etgTestKeyIndex = 0;
#endif
}

/*-----------------------------------------------------------------------------
    Name        : etgReloadReset
    Description : Reset the already loaded ETG effects after a reload.
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgReloadReset(void)
{
    sdword index, j;
    etgeffectstatic *newStatic;

    //recolorize all the meshes
    for (index = 0; index < ETG_NumberMeshes; index++)
    {                                                       //scan through the registry
        if (etgMeshRegistry[index].filename != NULL)
        {                                                   //if this guy has somethig allocated
            meshRecolorize(etgMeshRegistry[index].mesh);
        }
    }
    for (index = 0; index < ETG_EventListLength; index++)
    {
        if (etgEventTable[index].effectStatic != NULL)
        {                                                   //find a free event as we search
            newStatic = etgEventTable[index].effectStatic;
            for (j = 0; j < newStatic->nHistoryList; j++)
            {                                               //zero out the history
                newStatic->historyList[j] = REALlyNegative;
            }
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgFixupUV
    Description : Adjust UV coordinates on ETG meshes to eliminate filtered oversampling
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgFixupUV(void)
{
    sdword index;

    for (index = 0; index < ETG_NumberMeshes; index++)
    {
        if (etgMeshRegistry[index].filename != NULL)
        {
            meshFixupUV(etgMeshRegistry[index].mesh);
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgReset
    Description : Resets the etg module by freeing all the current effects and
                    loading them into the same address.
    Inputs      : void
    Outputs     : ..
    Return      : void
----------------------------------------------------------------------------*/
void etgReset(void)
{
    sdword index, j;
    char path[PATH_Max];
    etgeffectstatic *newStat;
    char *nameSave;

#if ETG_VERBOSE_LEVEL >= 1
    dbgMessagef("\nResetting up to %d effects", ETG_EventListLength);
#endif
    //free all meshes allocated for effects, they will be re-loaded later
    /*
    for (index = 0; index < etgMeshRegistryIndex; index++)
    {
        meshFree(etgMeshRegistry[index]);
    }
    etgMeshRegistryIndex = 0;
    */
    etgMeshRegistryReset();

    etgEventLoadCount++;                                    //update load count variable

    etgErrorRecoverable = TRUE;

    //reload all event statics
    for (index = 0; index < ETG_EventListLength; index++)
    {
        if (etgEventTable[index].effectStatic == NULL)
        {                                                   //if entry unused
            continue;
        }
        if (etgEventTable[index].loadCount < etgEventLoadCount)
        {                                                   //if this effect was loaded previously
            strcpy(path, ETG_Directory);                    //build a full path name
            strcat(path, etgEventTable[index].effectStatic->name);
            /*
            etgEffectCodeDelete(etgEventTable[index].effectStatic, FALSE);
            newStat = etgEffectCodeLoad(path);
            dbgAssert(newStat == etgEventTable[index].effectStatic);
            */
            nameSave = etgEventTable[index].effectStatic->name;//make sure the effect will be re-loaded
            etgEventTable[index].effectStatic->name = "DON'T LOAD ME";
            newStat = etgEffectCodeLoad(path);              //load in the effect
            etgEventTable[index].effectStatic->name = nameSave;//restore it's proper name
            if (newStat != NULL)                            //if the effect was loaded OK
            {
                etgEffectCodeDelete(etgEventTable[index].effectStatic, FALSE);
                memFree(etgEventTable[index].effectStatic->name);//free the name not freed by previous line
                *etgEventTable[index].effectStatic = *newStat;//delete the registry entry
                for (j = 0; j < ETG_EventListLength; j++)
                {                                           //find effect just loaded
                    if (etgEventTable[j].effectStatic == newStat)
                    {                                       //make sure the effect static pointer is in the same loacation as originally loaded
                        etgEventTable[j].effectStatic = NULL;
                        break;
                    }
                }
            }                                               //else retain the previously loaded effect
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectCodeExecute
    Description : Execute a block of code.
    Inputs      : stat - static info for the effect
                  effect - local effect who has all the goods
                  codeBlock - which code block to execute
    Outputs     : Executes the specified p-code
    Return      : void
----------------------------------------------------------------------------*/
//#define ETG_ExecStackDepth   4
//global variables for execution of a chunk of code
typedef struct
{
    etgcodeblock etgCodeBlock[ETG_NumberCodeBlocks];
    //ubyte *etgCurrentCodeBlock;
    udword etgCodeBlockIndex;
    //udword etgCodeOffset;
    //udword etgCodeLength;
    ubyte *etgVariables;
    bool etgDeleteFlag;
}
etgeffectstack;
etgeffectstack etgExecStack;//[ETG_ExecStackDepth];
//sdword etgExecStackIndex = -1;
void etgEffectCodeExecute(etgeffectstatic *stat, Effect *effect, udword codeBlock)
{
    udword opcode;
    sdword size;
    ubyte *pOpcode;
/*
#if ETG_ERROR_CHECKING
    if (etgExecStackIndex >= ETG_ExecStackDepth - 1)
    {
        dbgFatalf(ETG, "Overflowed exec stack of depth %d", ETG_ExecStackDepth);
    }
#endif
    etgExecStackIndex++;
*/
	//this function does not interface well with optimized code which assumes 
	//certain variables will not get stomped, hence the pushes
	_asm
	{
		push eax
		push ebx
		push ecx
		push edx
		push esi
		push edi
	}
    etgExecStack.etgCodeBlockIndex = codeBlock;
    //set the proper code block and code length
    //start at beginning of code.  !!!We may want to implement the yield() function.
//    etgCodeOffset = 0;
    //!!! this does not support the yield function
    etgExecStack.etgCodeBlock[EPM_Startup].offset = etgExecStack.etgCodeBlock[EPM_EachFrame].offset = etgExecStack.etgCodeBlock[EPM_TimeIndex].offset = 0;
    etgExecStack.etgVariables = effect->variable;

    while (etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset <
           etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].length)
    {
        pOpcode = etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].code + etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset;
        opcode = *((udword *)pOpcode);                      //get an opcode
#if ETG_ERROR_CHECKING
        if (opcode < 0 || opcode >= EOP_LastOp)
        {
            dbgFatalf(DBG_Loc, "Effect '%s' has a bad opcode in code segment %d offset %d", stat->name, etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].code, etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset);
        }
        if (etgHandleTable[opcode].function == NULL)
        {
            dbgFatalf(DBG_Loc, "etgEffectCodeExecute: NULL opcode %d", opcode);
        }
#endif
        //execute the opcode
        size = etgHandleTable[opcode].function(effect, stat, pOpcode);
        etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset += size;
    }
//    etgExecStackIndex--;
	//this function does not interface well with optimized code which assumes 
	//certain variables will not get stomped, hence the pushes
	_asm
	{
		pop edi
		pop esi
		pop edx
		pop ecx
		pop ebx
		pop eax
	}
}

/*-----------------------------------------------------------------------------
    Name        : etgCodeMakeCurrent
    Description : Make a particular efect static's code blocks current
    Inputs      : stat - code static to make current
    Outputs     : Copies the etgcodeblock structures from the effect static to
                    etgCodeBlocks.
    Return      : void
----------------------------------------------------------------------------*/
void etgCodeMakeCurrent(etgeffectstatic *stat)
{
    memcpy(etgExecStack.etgCodeBlock, stat->codeBlock, sizeof(etgcodeblock) * ETG_NumberCodeBlocks);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectCodeStart
    Description : Starts up the code, variables etc. of an effect in a useable form.
    Inputs      : effect - effect we're starting up
                  nParams - number of user parameters
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgEffectCodeStart(etgeffectstatic *stat, Effect *effect, sdword nParams, ...)
{
    sdword index;
    va_list marker;

#if ETG_ERROR_CHECKING
    if (stat->nParticleBlocks == 0xffffffff)
    {
        dbgFatalf(DBG_Loc, "Effect '%s' not loaded properly", stat->name);
    }
#endif
    //set the variable and variable rate blocks
    if (stat->variableSize)
    {
//        effect->variable = memAlloc(stat->variableSize + stat->rateSize +
//                                    stat->variableSize / 4, "EffectVariables", 0);
        effect->variable = ((ubyte *)effect) + etgEffectSize(stat->nParticleBlocks);
        effect->variableRate = effect->variable + stat->variableSize;
        effect->effectID = effect->variableRate + stat->rateSize;
        memset(effect->effectID, 0xff, stat->variableSize / 4); //clear out the effect ID's
        memset(effect->variableRate, 0, stat->rateSize);
    }
    else
    {
        effect->variable = effect->variableRate = effect->effectID = NULL;
    }
    //set the particle block info
    effect->nParticleBlocks = stat->nParticleBlocks;
    effect->iParticleBlock = 0;
    memset(effect->particleBlock, 0, effect->nParticleBlocks * sizeof(ubyte *));
    //init the variables to initial values
    for (index = 0; index < stat->initLength; index++)
    {
        ((udword *)effect->variable)[stat->variableOffsets[index]] =
            stat->variableInitData[index];
    }
    //init the user parameters, if any
    va_start(marker, nParams);
    for (index = 0; index < nParams; index++)
    {
        ((udword *)effect->variable)[index] = va_arg(marker, int);
    }
    va_end(marker);
    //clear out the flags list
    effect->effectFlags = 0;
    //no spin yet
    effect->spin = effect->deltaSpin = 0.0f;
    //update the effect's history, if applicable
    etgHistoryRegister(stat);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectDelete
    Description : Deletes an effect just before it is removed from mission spheres etc.
    Inputs      :
    Outputs     : Does not memFree the effect
    Return      :
----------------------------------------------------------------------------*/
void etgEffectDelete(Effect *effect)
{
    sdword index;
    for (index = 0; index < effect->iParticleBlock; index++)
    {
        if (effect->particleBlock[index] != NULL)
        {
            memFree(effect->particleBlock[index]);
        }
    }
    if (bitTest(((etgeffectstatic *)effect->staticinfo)->specialOps, ESO_WorldRender))
    {
        univRemoveFromWorldList((Derelict *)effect);
    }
    listRemoveNode(&effect->effectLink);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectCodeDelete
    Description : Deletes an effect static
    Inputs      : stat - effect static to delete
                  bFullDelete - if TRUE will memFree the actual static structure itself.
    Outputs     : Frees all memory allocated to the effect static, including
                    the effect static structure itself.
    Return      : void
----------------------------------------------------------------------------*/
void etgEffectCodeDelete(etgeffectstatic *stat, bool bFullDelete)
{
    if (bFullDelete)
    {
        memFree(stat->name);
    }
#if ETG_ERROR_CHECKING
    if (stat->nParticleBlocks == 0xffffffff)
    {
        dbgWarningf(DBG_Loc, "etgEffectCodeDelete: Effect '%s' not loaded properly", stat->name);
        if (bFullDelete)
        {
            memFree(stat);
        }
        return;
    }
#endif
//    memFree(stat->variableOffsets);
    if (stat->variableInitData)
    {
        memFree(stat->variableInitData);
        stat->variableInitData = NULL;
    }
    if (stat->constData)
    {
        memFree(stat->constData);
        stat->constData = NULL;
    }
    if (stat->decisions)
    {
        memFree(stat->decisions);
        stat->decisions = NULL;
    }
    if (stat->alternateOffsets)
    {
        memFree(stat->alternateOffsets);
        stat->alternateOffsets = NULL;
    }
    if (stat->codeBlock[EPM_Startup].code != NULL)
    {
        memFree(stat->codeBlock[EPM_Startup].code);
        stat->codeBlock[EPM_Startup].code = 0;
    }
    if (stat->codeBlock[EPM_EachFrame].code != NULL)
    {
        memFree(stat->codeBlock[EPM_EachFrame].code);
        stat->codeBlock[EPM_EachFrame].code = 0;
    }
    if (stat->codeBlock[EPM_TimeIndex].code != NULL)
    {
        memFree(stat->codeBlock[EPM_TimeIndex].code);
        stat->codeBlock[EPM_TimeIndex].code = 0;
    }
    if (stat->nHistoryList > 0)
    {
        memFree(stat->historyList);
    }
    stat->nParticleBlocks = 0xffffffff;
    if (bFullDelete)
    {
        memFree(stat);
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectUpdate
    Description : Update a specified effect
    Inputs      : effect - effect we are to update
                  timeElapsed - time elapsed since effect last updated
    Outputs     : executes code segments which will do whatever they will.
    Return      : TRUE if we are to delete the effect, false otherwise
----------------------------------------------------------------------------*/
real32 etgTimeElapsed;
real32 etgTotalTimeElapsed;
bool etgEffectUpdate(Effect *effect, real32 timeElapsed)
{
    etgeffectstatic *stat = (etgeffectstatic *)effect->staticinfo;
    matrix tempMatrix, rotMatrix;
    real32 spin;

    etgExecStack.etgDeleteFlag = FALSE;                     //set some global parameters
    etgTimeElapsed = timeElapsed;
    etgTotalTimeElapsed = effect->timeElapsed + timeElapsed;
    effect->timeElapsed += timeElapsed;                     //record additional time elapsed
    if (etgTotalTimeElapsed < 0.0f)
    {                                                       //if effect still waiting to start
        return(FALSE);
    }
    etgCodeMakeCurrent(stat);
    if (etgTotalTimeElapsed <= timeElapsed + 0.005)
    {                                                       //if effect just started
        etgEffectCodeExecute(stat, effect, EPM_Startup);
    }

    //execute the time index block
    etgEffectCodeExecute(stat, effect, EPM_TimeIndex);
    if (etgExecStack.etgDeleteFlag != TRUE)
    {   //execute the eachFrame
        etgEffectCodeExecute(stat, effect, EPM_EachFrame);
    }
    //update the physics of the effect
    if (effect->owner)
    {
        if (bitTest(effect->flags, SOF_AttachCoordsys))
        {
            effect->rotinfo.coordsys = effect->owner->rotinfo.coordsys;
        }
        if (bitTest(effect->flags, SOF_AttachPosition))
        {
            effect->posinfo.position = effect->owner->posinfo.position;
        }
        if (bitTest(effect->flags, SOF_AttachVelocity))
        {
            effect->posinfo.velocity = effect->owner->posinfo.velocity;
        }
        else
        {
            vecMultiplyByScalar(effect->posinfo.velocity, effect->drag);
        }
        if (bitTest(effect->flags, SOF_AttachNLips))
        {
            effect->magnitudeSquared = effect->owner->magnitudeSquared;
        }
    }
    else
    {
        vecMultiplyByScalar(effect->posinfo.velocity, effect->drag);
    }
    vecAddToScalarMultiply(effect->posinfo.position, effect->posinfo.velocity, timeElapsed);
    effect->spin += effect->deltaSpin;
    if (effect->spin != 0.0f)
    {
        //build a rotation matrix
        spin = effect->spin * etgTimeElapsed;
        matMakeRotAboutZ(&rotMatrix, (real32)sin((double)spin), (real32)cos((double)spin));
        tempMatrix = effect->rotinfo.coordsys;
        //rotate the effect
        matMultiplyMatByMat(&effect->rotinfo.coordsys, &rotMatrix, &tempMatrix);
        //rotate all particle systems
    }
    //return the proper flag to ensure the effect was rendered at least once
    if (bitTest(effect->effectFlags, EF_Rendered))
    {
        return(etgExecStack.etgDeleteFlag | (effect->effectFlags & EM_ToBeDeleted));
    }
    else if (etgExecStack.etgDeleteFlag | (effect->effectFlags & EM_ToBeDeleted))
    {
        effect->effectFlags += EF_ToBeDeleted;
        if ((effect->effectFlags & EM_ToBeDeleted) > EM_ForceDelete)
        {
            return(TRUE);
        }
    }
    return(FALSE);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectDraw
    Description : Draw an effect
    Inputs      : effect - effect to draw
    Outputs     :
    Return      :
    Note        : it is assumed that the effect's matrix has already been applied
----------------------------------------------------------------------------*/
void etgEffectDraw(Effect *effect)
{
    sdword index;
    hmatrix coordMatrixX;
    matrix scaledEffectSys;
    bool bBillboard = FALSE;
    pointSystem *part;
    real32 timeElapsed;
    vector velInverse;

    //!!!ASSERT: glMatrixMode == GL_MODELVIEW

    if (effect->timeElapsed < 0.0f)
    {                                                       //if effect hasn't started yet
        return;
    }
    //make sure the effect's N-Lips is up-to-date
    if (effect->owner)
    {
        if (bitTest(effect->flags, SOF_AttachNLips))
        {
            effect->magnitudeSquared = effect->owner->magnitudeSquared;
        }
    }
    //compute a N-Lipped effect coord sys
    partNLips = effect->magnitudeSquared;
    matCopyAndScale(&scaledEffectSys, &effect->rotinfo.coordsys, partNLips);
    //compute inverse of that coord sys
    //matMultiplyVecByMat(&velInverse, &effect->posinfo.velocity, &effect->rotinfo.coordsys);
    matMultiplyVecByMat(&velInverse, &effect->posinfo.velocity, &scaledEffectSys);
    hmatMakeHMatFromMat(&coordMatrixX,&scaledEffectSys);
    hmatPutVectIntoHMatrixCol4(effect->posinfo.position,coordMatrixX);
    for (index = 0; index < effect->iParticleBlock; index++)
    {
        if (effect->particleBlock[index] != NULL)
        {
            part = (pointSystem *)effect->particleBlock[index];
            timeElapsed = universe.totaltimeelapsed - part->lastUpdated;
            if (timeElapsed >= 0)
            {
                partUpdateSystem(effect->particleBlock[index], timeElapsed, &velInverse);
                part->lastUpdated = universe.totaltimeelapsed;
            }

            if (bitTest(part->flags, PART_WORLDSPACE))
            {
                if ((effect->owner != NULL) &&
                    (effect->owner->objtype == OBJ_ShipType))
                {
                    //this is for the benefit of the hyperspace effect
                    partEffectOwnerSystem   = scaledEffectSys;
                    partEffectOwnerPosition = effect->posinfo.position;
                    partEffectColor         = effect->owner->staticinfo->hyperspaceColor;
                }
                partRenderSystem(effect->particleBlock[index]);
            }
            else if (bitTest(effect->flags, EAF_OwnerCoordSys))
            {
                hmatrix ownerCoordMatrix;
                matrix tempMatrix;

                dbgAssert(effect->owner != NULL);

                matCopyAndScale(&tempMatrix, &effect->owner->rotinfo.coordsys, effect->owner->magnitudeSquared);
                //hmatMakeHMatFromMat(&ownerCoordMatrix, &effect->owner->rotinfo.coordsys);
                hmatMakeHMatFromMat(&ownerCoordMatrix, &tempMatrix);
                hmatPutVectIntoHMatrixCol4(effect->owner->posinfo.position, ownerCoordMatrix);

                glPushMatrix();
                glMultMatrixf((GLfloat*)&ownerCoordMatrix);
                glMultMatrixf((GLfloat*)&coordMatrixX);

                partRenderSystem(effect->particleBlock[index]);

                glPopMatrix();
            }
            else
            {
                if (partTypeof(effect->particleBlock[index]) == PART_BILLBOARD)
                {
                    partModifyBillPosition(effect->particleBlock[index],
                                       &effect->posinfo.position);
                }
                glPushMatrix();
                glMultMatrixf((float *)&coordMatrixX);      //effect's rotation matrix
                partRenderSystem(effect->particleBlock[index]);
                glPopMatrix();
            }
        }
    }
    bitSet(effect->effectFlags, EF_Rendered);
/*
    if (worldRendered)
    {
        glEnable(GL_DEPTH_TEST);
        rndNormalizeEnable(FALSE);
//        glPopMatrix();
        glMatrixMode(GL_PROJECTION);
        glLoadMatrixf((GLfloat*)&rndProjectionMatrix);
        glMatrixMode(GL_MODELVIEW);
    }
*/
}

/*-----------------------------------------------------------------------------
    Effect-loading functions:
-----------------------------------------------------------------------------*/
filehandle etgFileHandle = 0;                //handle of file we're reading
char *etgFileName = NULL;
sdword etgFileLine;                             //current line in that file
sdword etgParseMode;                            //current parsing mode
#define STRING_LENGTH           256
#define OPCODE_MAX              524
#define ETG                     etgFileName, etgFileLine - 1
#define ETG_U                   etgFileName, -1
//code blocks where the code is composed to
/*
ubyte *etgCodeStartup;
ubyte *etgCodeEachFrame;
ubyte *etgCodeTimeIndex;
sdword etgOffsetStartup;
sdword etgOffsetEachFrame;
sdword etgOffsetTimeIndex;
*/
//variable block used during parsing
etgvarentry *etgVariableTable;
sdword etgVariableIndex;
//brace-nesting stack
etgnestentry etgNestStack[ETG_NestStackDepth];
sdword etgNestLevel;
//alternate/case-making stuff
sdword etgTotalOffsets;
etgdecision *etgDecisionOpcode = NULL;
//udword etgAlternateIndex;
etgextalt *etgAlternateTable;
sdword etgAlternateStructIndex;
//stuff for time index creation
sdword etgTimeIndexIndex = 0;
//constant data pool
ubyte *etgConstData;

/*-----------------------------------------------------------------------------
    Name        : etgLoadError
    Description : Handle an ETG parse error with error recovery if applicable.
    Inputs      : same as dbgFatal
    Outputs     :
    Return      : 0
----------------------------------------------------------------------------*/
sdword etgLoadError(char *file, sdword line, char *error)
{
    if (etgErrorRecoverable)
    {
        dbgNonFatal(file, line, error);
    }
    else
    {
        dbgFatal(file, line, error);
    }
    return(0);
}

/*-----------------------------------------------------------------------------
    Name        : etgLoadErrorf
    Description : Handle an ETG parse error with error recovery and variable parameters.
    Inputs      : same as dbgFatalf
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
sdword etgLoadErrorf(char *file, sdword line, char *format, ...)
{
    va_list argList;
    char error[DBG_BufferLength];

    va_start(argList, format);                              //get first arg
    vsprintf(error, format, argList);                       //prepare output string
    va_end(argList);

    etgLoadError(file, line, error);                        //print the message
    return(0);
}

/*-----------------------------------------------------------------------------
    Name        : etgLineRead
    Description : Read a single line from the etg file and put it into the correct format
    Inputs      : dest - where to store the destination string
                  length - length of string
    Outputs     : reads and beautifies a string
    Return      : NULL if end of file, dest otherwise
----------------------------------------------------------------------------*/
char *etgLineRead(char *dest, sdword length)
{
    char string[STRING_LENGTH];
    char *pString, *pStart, *pEnd, *pDest;
    ERR_TYPE exprError;
    double exprValue;

    //read in lines until we get a non-blank one
    do
    {
        //read in a line
        if (fileLineRead(etgFileHandle, string, length) == FR_EndOfFile)
        {
            return(NULL);
        }
        etgFileLine++;
        //strip line comments, if any.  There shouldn't be any
        if ((pString = strstr(string, "//")) != NULL)
        {
            *pString = 0;
        }
        //strip leading spaces
//        pStart = pEnd = NULL;
        for (pStart = pString = string; ; pString++)
        {                                                   //find first non-whitespace char
            if (strchr(" \t", *pString) == NULL)
            {
                pStart = pString;
                break;
            }
            if (*pString == 0)
            {
                pStart = pString;
                break;
            }
        }
        //strip trailing spaces
        for (pEnd = pString = string + strlen(string); pString > string; pString--)
        {                                                   //find last non-whitespace character
            if (strchr(" \t\n", *pString) == NULL)
            {                                               //if non-whitespace
                pEnd = pString + 1;                         //end of string
                break;
            }
        }
        *pEnd = 0;                                          //remove the trailing spaces
    }
    while (strlen(pStart) < 1);
    //search for, and expand all expressions
    pDest = dest;
    dest[0] = 0;
    while ((pString = strchr(pStart, '[')) != NULL)
    {                                                       //while there is an expression visible
        //exploit the strncopy NULL-terminator strangeness
        strncpy(pDest, pStart, pString - pStart);           //copy up to the bracket
        pDest += pString - pStart;
        *pDest = 0;
#if ETG_ERROR_CHECKING
        if (strchr(pString + 1, ']') == NULL)
        {                                                   //if no closing bracket
            etgLoadErrorf(ETG, "'[' without ']'");
            return(NULL);
        }
#endif
        pEnd = strchr(pString, ']');                        //get closing bracket
        *pEnd = 0;                                          //null-terminate this expression
#if ETG_ERROR_CHECKING
        exprError = evalSyntaxOK(pString + 1);              //check expression syntax
        if (exprError != ALL_OK)
        {
            etgLoadErrorf(ETG, "%s in expression '%s'", evalErrorString(exprError), pString + 1);
            return(NULL);
        }
#endif
        exprValue = evalEvaluate(pString + 1, &exprError);  //evaluate expression
#if ETG_ERROR_CHECKING
        if (exprError != ALL_OK)
        {
            etgLoadErrorf(ETG, "%s in expression '%s'", evalErrorString(exprError), pString + 1);
            return(NULL);
        }
#endif
        evalNum2Str(exprValue, pDest);                      //print the string out
        pDest += strlen(pDest);                             //update the dest pointer
        pStart = pEnd + 1;                                  //update the source pointer
    }
    strcpy(pDest, pStart);                                  //copy the whole expression
    return(dest);
}

/*-----------------------------------------------------------------------------
    Name        : etgParseNamesMatch
    Description : A specialized sort of string compare, this function compares
                    two strings.  The first n characters of string must be equal
                    to nameToMatch.
    Inputs      : string - string to seach in
                  nameToMatch - name to, well, match
    Outputs     : ..
    Return      : length of nameToMatch of they match, or 0 if they don't
----------------------------------------------------------------------------*/
char etgLabelString[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz12345667890-_{}#";
sdword etgParseNamesMatch(char *string, char *nameToMatch)
{
    sdword countS, countN;

    for (countS = 0; string[countS] && strchr(etgLabelString, string[countS]); countS++);
    for (countN = 0; nameToMatch[countN] && strchr(etgLabelString, nameToMatch[countN]); countN++);
    if (countN != countS || strncmp(string, nameToMatch, countN) != 0)
    {
        return(0);
    }
    return(countN);
}

/*-----------------------------------------------------------------------------
    Name        : etgParametersIsolate
    Description : Isolate the parameter block (within parenteses) from a string.
    Inputs      : string - string to look in
                  offset starting offset in that string
    Outputs     : kills the parentheses (converts them to zero)
    Return      : pointer to start of parameter block
----------------------------------------------------------------------------*/
char *etgParametersIsolate(char *string, sdword offset)
{
#if ETG_ERROR_CHECKING
    char *temp;
    if ((temp = strchr(string, '(')) == NULL)
    {
        etgLoadErrorf(ETG, "Expecting parameters in '%s'", string);
        return(NULL);
    }
    if (!strchr(temp, ')'))
    {
        etgLoadErrorf(ETG, "No closing parenthesis in '%s'", string);
        return(NULL);
    }
#endif
    string = strchr(string, '(');
    *string = 0;                                            //kill opening perenthesis
    string++;
    *strchr(string, ')') = 0;                               //kill closing parenthesis
    return(string);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectStaticFind
    Description : Find a named effect
    Inputs      : name - name of effect to find
                  bRegister - if TRUE, the effect will be registered for later
                    loading.  Usually set to FALSE
    Outputs     : If effect not already registered, a new effect will be created
                    with a given name to be loaded in later.
    Return      : pointer to effect, or NULL if not already registered
----------------------------------------------------------------------------*/
etgeffectstatic *etgEffectStaticFind(char *name, bool bRegister)
{
    sdword index;
    etgeffectstatic *newStatic;
    char *slash;
    etgevent *free = NULL;

    //strip all the path crap off, so we have just a name
    for (slash = strchr(name, '\\'); slash != NULL; slash = strchr(slash + 1, '\\'))
    {
        name = slash + 1;
    }
    //see if it'a already registered
    for (index = 0; index < ETG_EventListLength; index++)
    {
        if (etgEventTable[index].effectStatic == NULL)
        {                                                   //find a free event as we search
            if (free == NULL)
            {
                free = &etgEventTable[index];
            }
        }
        else
        {
            if (strcmp(etgEventTable[index].effectStatic->name, name) == 0)
            {                                                   //if names match
                return(etgEventTable[index].effectStatic);
            }
        }
    }

    if (bRegister == FALSE)
    {
        return(NULL);
    }
    //if it gets here, event doesn't exist.  If this is the case, let's allocate
    //a new blank effect static to be filled in later.
#if ETG_ERROR_CHECKING
    if (free == NULL)
    {
        dbgFatalf(DBG_Loc, "No room in event list to load '%s'", name);
    }
    /*
    if (etgEventIndex >= ETG_EventListLength)
    {
        etgLoadErrorf(ETG, "Exceeded %d effects.", ETG_EventListLength);
    }
    */
#endif
    newStatic = memAlloc(sizeof(etgeffectstatic), "EffectStatic", NonVolatile);
    newStatic->name = memStringDupeNV(name);
    newStatic->nParticleBlocks = 0xffffffff;                //mark as not yet allocated
    free->effectStatic = newStatic;                         //store the effect reference
    free->loadCount = etgEventLoadCount;                    //store the effect load counter
//    etgEventIndex++;
    return(newStatic);
}

/*-----------------------------------------------------------------------------
    Name        : etgOpcodeScan
    Description : Scan to see if we can create an opcode from the specified string.
    Inputs      : stat - effect static data we're working on
                  string - sring representing the code for the line
                  dest - where to store the opcode generated, if any
                  destLength - where to store the length of the opcode
    Outputs     : May generate and opcode and store it in dest, the length of
                    it in length.
    Return      : TRUE if a processor function found
----------------------------------------------------------------------------*/
bool etgOpcodeScan(struct etgeffectstatic *stat, char *string, ubyte *dest, sdword *destLength)
{
    sdword index;
    sdword strLength;
    char *start, *params;
    char returnValue[STRING_LENGTH];
    bool bReturnValue;

    //find return value name, if any
    if ((start = strstr(string, "<-")) != NULL)             //if found an assignment sign
    {
        memcpy(returnValue, string, start - string - 1);    //duplicate the return value bit
        returnValue[start - string - 1] = 0;                //NULL-terminate it
        strtok(returnValue, " \t");                         //remove trailing spaces
        start += strlen("<-");                              //skip assignment symbol
        while (strchr(" \t", *start) != NULL)
        {                                                   //find next non-whitespace
            start++;
        }
        bReturnValue = TRUE;
    }
    else
    {
        start = string;
        bReturnValue = FALSE;
    }
    //see if there is an opcode match anywhere
    for (index = 0; etgParseTable[index].name != NULL; index++)
    {                                                       //for all parse table entries
        strLength = etgParseNamesMatch(start, etgParseTable[index].name);
        if (strLength > 0)                                  //if found a match
        {
#if ETG_ERROR_CHECKING
            if (!bReturnValue)
            {                                               //if no return value
                if (bitTest(etgParseTable[index].flags, PTF_Return))
                {                                           //if we need a return value
                    etgLoadErrorf(ETG, "Expression '%s' requires a return parameter.", etgParseTable[index].name);
                    return(FALSE);
                }
            }
            else
            {
                if (!bitTest(etgParseTable[index].flags, PTF_ReturnOptional))
                {
                    etgLoadErrorf(ETG, "Expression '%s' has no return parameter.", etgParseTable[index].name);
                    return(FALSE);
                }
            }
#endif
            if (bitTest(etgParseTable[index].flags, PTF_Parameters))
            {                                               //isolate parameters appropriately
                params = etgParametersIsolate(start, strLength);
            }
            else if (bitTest(etgParseTable[index].flags, PTF_RestOfLine))
            {
                params = start + strLength;
                while (strchr(" \t", *params))
                {
                    params++;                               //find next non-whitespace
                }
            }
            else
            {
                params = NULL;
            }
            //call the parsing function
            *destLength = etgParseTable[index].function(stat, dest, start, params, returnValue);
            dbgAssert(*destLength < OPCODE_MAX && *destLength >= 0);
            return(TRUE);
        }
    }
    return(FALSE);
}

/*-----------------------------------------------------------------------------
    Name        : etgIsNumber
    Description : Determine if a string is a number
    Inputs      : number - string to check numerality of
    Outputs     :
    Return      : TRUE if a number, false otherwise
----------------------------------------------------------------------------*/
bool etgIsNumber(char *number)
{
    while (*number)
    {
        if (strchr("0123456789.-", *number) == NULL)
        {
            return(FALSE);
        }
        number++;
    }
    return(TRUE);
}

/*-----------------------------------------------------------------------------
    Name        : etgVariableFind
    Description : Find a variable, it it exists
    Inputs      : name - name of variable to find
    Outputs     :
    Return      : variable info structure if found or NULL if not found
----------------------------------------------------------------------------*/
etgvarentry *etgVariableFind(char *name)
{
    sdword index;

    for (index = 0; index < etgVariableIndex; index++)
    {
        if (strcmp(etgVariableTable[index].name, name) == 0)//if found a matching variable
        {
            return(&etgVariableTable[index]);               //return the variable found
        }
    }
    return(NULL);
}

/*-----------------------------------------------------------------------------
    Name        : etgColorScan
    Description : Attemp to scan a color from string
    Inputs      : string - string to scan.  There must be 3 or 4 comma-separated
                    values representing red, green, blue and alpha.  Alpha is
                    optional.  These numbers are treated as floating values in
                    the range of 0..1
    Outputs     :
    Return      : scanned color
    Note        : will generate an error if invalid string
----------------------------------------------------------------------------*/
color etgColorScan(char *string)
{
    sdword nScanned;
    real32 red, green, blue, alpha;

    nScanned = sscanf(string, "%f,%f,%f,%f", &red, &green, &blue, &alpha);
    if (nScanned == 3)
    {
        alpha = 1.0f;
    }
#if ETG_ERROR_CHECKING
    else if (nScanned != 4)
    {
        etgLoadErrorf(ETG, "Error scanning '%s' for a color.", string);
        return(0);
    }
#endif
    return(colRGBA(colRealToUbyte(red), colRealToUbyte(green),
                   colRealToUbyte(blue), colRealToUbyte(alpha)));
}

/*-----------------------------------------------------------------------------
    Name        : etgVarCopyScan
    Description : Scan for a variable assignment to a constant or other variable
    Inputs      : same as for etgOpcodeScan
    Outputs     : Creates an opcode if one found and stores the length in *destLength
    Return      : TRUE if a scanned properly, FALSE if found
----------------------------------------------------------------------------*/
bool etgVarCopyScan(struct etgeffectstatic *stat, char *string, ubyte *dest, sdword *destLength)
{
    char *rValue, *lValue, *parser;
    etgvariablecopy *opcode = (etgvariablecopy *)dest;
    etgvarentry *rVar, *lVar;
    udword constant, offset;
    sdword nScanned;

    //find assignment statement, if one exists
    if ((rValue = strstr(string, "<-")) != NULL)            //if found an assignment sign
    {
        lValue = strtok(string, " \t<-");                   //get left and right values
        rValue = strtok(NULL, " \t<-");

        //figure out the l-value (variable only)
        lVar = etgVariableFind(lValue);                     //find the variable
#if ETG_ERROR_CHECKING
        if (lVar == NULL)
        {
            etgLoadErrorf(ETG, "Variable '%s' not found.", lValue);
            return(FALSE);
        }
        if (lVar->type == EVT_Label || lVar->type == EVT_ConstLabel || lVar->type == EVT_VarLabel)
        {
            etgLoadErrorf(ETG, "Cannot set label '%s'.", lValue);
            return(FALSE);
        }
        if (lVar->type == EVT_Pointer)
        {
            etgLoadErrorf(ETG, "!!!Cannot do pointer types yet.");
            return(FALSE);
        }
#endif
        opcode->dest = lVar->offset;

        //figure out the r-value (constant or variable)
        if (etgIsNumber(rValue))
        {                                                   //assigning a constant
            if (lVar->type == EVT_Float)
            {                                               //determine how to scan
                parser = "%f";
            }
            else
            {
                parser = "%d";
            }
            nScanned = sscanf(rValue, parser, &constant);
#if ETG_ERROR_CHECKING
            if (nScanned != 1)
            {
                etgLoadErrorf(ETG, "Bad format for constant '%s'.", rValue);
                return(FALSE);
            }
#endif
            opcode->opcode = EOP_VariableAssign;
            opcode->source = constant;
            *destLength = sizeof(etgvariablecopy);
        }
        else
        {                                                   //else r-value is a variable
            //get variable
            rVar = etgVariableFind(rValue);
#if ETG_ERROR_CHECKING
            if (rVar == NULL)
            {
                etgLoadErrorf(ETG, "Variable '%s' not found.", rValue);
                return(FALSE);
            }
//          if (rVar->type == EVT_Label)
//          {
//              etgLoadErrorf(ETG, "Cannot pass label '%s' to function.", rValue);
//          }
            if (rVar->type == EVT_Pointer)
            {
                etgLoadErrorf(ETG, "!!!Cannot do pointer types yet.");
                return(FALSE);
            }
#endif
            switch (rVar->type)
            {                                               //adjust opcode based on var type
#if ETG_ERROR_CHECKING
                case EVT_Label:
                    etgLoadErrorf(ETG, "Cannot use label '%s' as an r-value", rValue);
                    return(FALSE);
                    break;
#endif
                case EVT_ConstLabel:
                case EVT_VarLabel:
                    opcode->opcode = EOP_VariableAssign;    //setting to label, same as
                    offset = rVar->initial;
                    break;                                  //assigning a constant
                default:
                    opcode->opcode = EOP_VariableCopy;
                    offset = rVar->offset;
                    break;
            }
            opcode->source = offset;
            *destLength = sizeof(etgvariablecopy);
        }
        return(TRUE);
    }
    return(FALSE);
}

/*-----------------------------------------------------------------------------
    Name        : etgFunctionCallScan
    Description : Scan for a matching function call name
    Inputs      : same as for etgOpcodeScan
    Outputs     : Creates an opcode if one found and stores the length in *destLength
    Return      : TRUE if a function found, FALSE if not found
----------------------------------------------------------------------------*/
bool etgFunctionCallScan(struct etgeffectstatic *stat, char *string, ubyte *dest, sdword *destLength)
{
    sdword index, varIndex;
    sdword strLength, nScanned;
    udword constant, offset;
    char *start, *params, *param, *parser;
    char returnValue[STRING_LENGTH];
    bool bReturnValue;
    etgfunctioncall *opcode = (etgfunctioncall *)dest;
    etgvarentry *variable;

    //find return value name, if any
    if ((start = strstr(string, "<-")) != NULL)             //if found an assignment sign
    {
        memcpy(returnValue, string, start - string - 1);    //duplicate the return value bit
        returnValue[start - string - 1] = 0;                //NULL-terminate it
        strtok(returnValue, " \t");                         //remove trailing spaces
        start += strlen("<-");                              //skip assignment symbol
        while (strchr(" \t", *start) != NULL)
        {                                                   //find next non-whitespace
            start++;
        }
        bReturnValue = TRUE;
    }
    else
    {
        start = string;
        bReturnValue = FALSE;
    }
    //search the function dispatch table
    for (index = 0; etgFunctionTable[index].name != NULL; index++)
    {
        strLength = etgParseNamesMatch(start, etgFunctionTable[index].name);
        if (strLength > 0)                                  //if found a match
        {
            opcode->opcode = EOP_Function;
            opcode->function = etgFunctionTable[index].function;
            opcode->passThis = (bool)etgFunctionTable[index].passThis;
            opcode->returnValue = 0xffffffff;
            opcode->nParameters = 0;
            //isolate and set the parameter template
            params = etgParametersIsolate(start, strLength);
            param = strtok(params, ETG_TokenDelimiters);
            varIndex = 0;
            while (param != NULL)
            {
#if ETG_ERROR_CHECKING
                if (varIndex >= etgFunctionTable[index].nParams && varIndex > 0)
                {
                    etgLoadErrorf(ETG, "Function call exceeds %d parameters.", etgFunctionTable[index].nParams);
                    return(FALSE);
                }
#endif
                if (etgIsNumber(param))
                {                                           //passing a constant
                    if (etgFunctionTable[index].type[varIndex] == EVT_Float)
                    {                                       //determine how to scan
                        parser = "%f";
                    }
                    else
                    {
                        parser = "%d";
                    }
                    nScanned = sscanf(param, parser, &constant);
#if ETG_ERROR_CHECKING
                    if (nScanned != 1)
                    {
                        etgLoadErrorf(ETG, "Bad format for constant '%s'.", param);
                        return(FALSE);
                    }
#endif
                    opcode->parameter[varIndex].type = EVT_Constant;
                    opcode->parameter[varIndex].param = constant;
                }
                else
                {
                    //get variable
                    variable = etgVariableFind(param);
#if ETG_ERROR_CHECKING
                    if (variable == NULL)
                    {
                        etgLoadErrorf(ETG, "Variable or label '%s' not found.", param);
                        return(FALSE);
                    }
//                  if (variable->type == EVT_Label)
//                  {
//                      etgLoadErrorf(ETG, "Cannot pass label '%s' to function.", param);
//                  }
                    if (variable->type == EVT_Pointer)
                    {
                        etgLoadErrorf(ETG, "!!!Cannot do pointer types yet.");
                        return(FALSE);
                    }
                    if (variable->type != etgFunctionTable[index].type[varIndex])
                    {
                        etgLoadErrorf(ETG, "Incompatable type for parameter %d. (%d != %d)", varIndex, variable->type, etgFunctionTable[index].type[varIndex]);
                        return(FALSE);
                    }
#endif
                    opcode->parameter[varIndex].type = etgFunctionTable[index].type[varIndex];
                    switch (variable->type)
                    {                                               //adjust opcode based on var type
#if ETG_ERROR_CHECKING
                        case EVT_Label:
                            etgLoadErrorf(ETG, "Cannot use label '%s' as a function parameter.", param);
                            return(FALSE);
                            break;
#endif
                        case EVT_ConstLabel:
                        case EVT_VarLabel:
                            offset = variable->initial;
                            break;                                  //assigning a constant
                        default:
                            offset = variable->offset;
                            break;
                    }
                    opcode->parameter[varIndex].param = offset;
                }
                varIndex++;
                param = strtok(NULL, ETG_TokenDelimiters);
            }
            opcode->nParameters = varIndex;
#if ETG_ERROR_CHECKING
            if (varIndex != etgFunctionTable[index].nParams)
            {
                if ((etgFunctionTable[index].nParams & ETG_VariableParams) &&
                    varIndex >= etgFunctionTable[index].nParams)
                {
                    ;
                }
                else
                {
                    etgLoadErrorf(ETG, "Function call to '%s' has wrong number of parameters.  Expected %d found %d.",
                              etgFunctionTable[index].name, etgFunctionTable[index].nParams, varIndex);
                    return(FALSE);
                }
            }
#endif
            //figure out and set the return value
            if (bReturnValue)
            {                                               //if there is a return expected
#if ETG_ERROR_CHECKING
                if (etgFunctionTable[index].returnType == EVT_Void)
                {                                           //if there is no return value to be had
                    etgLoadErrorf(ETG, "No return value avaiable for '%s'", start);
                    return(FALSE);
                }
#endif
                variable = etgVariableFind(returnValue);
#if ETG_ERROR_CHECKING
                if (variable == NULL)
                {
                    etgLoadErrorf(ETG, "Variable '%s' not found.", returnValue);
                    return(FALSE);
                }
                if (variable->type == EVT_Label)
                {
                    etgLoadErrorf(ETG, "Cannot use label '%s' as return variable.", returnValue);
                    return(FALSE);
                }
                if (variable->type == EVT_Pointer)
                {
                    etgLoadErrorf(ETG, "!!!Cannot do pointer types yet.");
                    return(FALSE);
                }
#endif
                switch (variable->type)
                {                                               //adjust opcode based on var type
#if ETG_ERROR_CHECKING
                    case EVT_Label:
                        etgLoadErrorf(ETG, "Cannot use label '%s' as a function parameter.", returnValue);
                        return(FALSE);
                        break;
#endif
                    case EVT_ConstLabel:
                    case EVT_VarLabel:
                        offset = variable->initial;
                        break;                                  //assigning a constant
                    default:
                        offset = variable->offset;
                        break;
                }
                opcode->returnValue = offset;
            }
            //compute length of opcode
            *destLength = sizeof(etgfunctioncall) + sizeof(udword) * 2 * (varIndex - 1);
            if (etgFunctionTable[index].resolve != NULL)    //if there is a resolve-function
            {
                etgFunctionTable[index].resolve(stat, opcode);    //call the resolve function
            }
            return(TRUE);                                   //finished
        }
    }
    return(FALSE);                                          //no matching function found
}

/*-----------------------------------------------------------------------------
    Name        : etgNestFunctionSet
    Description : Sets a function to be called at the closing of a brace nesting
    Inputs      : openFunction - function to call when a '{' is encountered, (optional)
                  function - function to call when closing brace encountered
                  code block - current code block (EPM_Startup etc.)
                  offset - current offset in that code block
                  userData - pointer parameter to be passed to callback
    Outputs     : stores these parameters in the current code nesting info
                    structure but does not increment etgNestLevel.
    Return      :
    Note        : usually in response to a conditional statement
----------------------------------------------------------------------------*/
void etgNestFunctionSet(nestopenfunction openFunction, nestfunction function, sdword codeBlock, sdword offset, ubyte *userData)
{
    //dbgAssert(etgNestStack[etgNestLevel].function == NULL);
#if ETG_ERROR_CHECKING
    if (etgNestLevel >= ETG_NestStackDepth)
    {
        etgLoadErrorf(ETG, "Nested %d levels of braces - too many.", ETG_NestStackDepth);
        return;
    }
#endif
    etgNestStack[etgNestLevel].openFunction = openFunction;
    etgNestStack[etgNestLevel].function = function;
    etgNestStack[etgNestLevel].codeBlock = codeBlock;
    etgNestStack[etgNestLevel].offset = offset;
    etgNestStack[etgNestLevel].userData = userData;
}

/*-----------------------------------------------------------------------------
    Name        : etgNestPush
    Description : Increments etgNestLevel in response to an open brace
    Inputs      : void
    Outputs     : ..
    Return      : void
----------------------------------------------------------------------------*/
void etgNestPush(void)
{
#if ETG_ERROR_CHECKING
    if (etgNestLevel >= ETG_NestStackDepth)
    {
        etgLoadErrorf(ETG, "Nested %d levels of braces - too many.", ETG_NestStackDepth);
        return;
    }
#endif
    etgNestLevel++;
    if (etgNestStack[etgNestLevel - 1].openFunction != NULL)
    {
        etgNestStack[etgNestLevel - 1].openFunction(etgNestStack[etgNestLevel - 1].codeBlock,
            etgNestStack[etgNestLevel - 1].offset, etgNestStack[etgNestLevel - 1].userData);
        etgNestStack[etgNestLevel - 1].openFunction = NULL;
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgNestPop
    Description : Decrements etgNestLevel and calls the previously-set nesing function.
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgNestPop(sdword newOffset)
{
#if ETG_ERROR_CHECKING
    if (etgNestLevel < 1)
    {
        etgLoadError(ETG, "More '}' than '{'.");
        return;
    }
#endif
    etgNestLevel--;                                         //decrement the nesting level
    if (etgNestStack[etgNestLevel].function != NULL)
    {                                                       //call the function
        etgNestStack[etgNestLevel].function(etgNestStack[etgNestLevel].codeBlock,
            etgNestStack[etgNestLevel].offset, newOffset, etgNestStack[etgNestLevel].userData);
        etgNestStack[etgNestLevel].function = NULL;
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgNewVariableCreate
    Description : Create a new variable in the effect structure.
    Inputs      : name - name of variable
                  stat - effect we're working on
                  type - type of varible
                  setInitial - flag indicating initial value needs setting
                  bInitial - initial value
                  size - length of pointer value to set
    Outputs     : fills in the next entry in the variable name table
    Return      : length of name table
----------------------------------------------------------------------------*/
sdword etgNewVariableCreate(char *name, etgeffectstatic *stat, sdword type, bool bInitial, udword initial, sdword size)
{
#if ETG_ERROR_CHECKING
    sdword index;

    for (index = 0; index < etgVariableIndex; index++)
    {
        if (strcmp(etgVariableTable[index].name, name) == 0)
        {
            dbgMessagef(ETG, "Redefinition of symbol '%s'.", name);
        }
    }
    if (etgVariableIndex >= ETG_VarTableParseLength)
    {
        etgLoadErrorf(ETG, "Out of space in variable name table: %d entries", ETG_VarTableParseLength);
        return(0);
    }
#endif
    etgVariableTable[etgVariableIndex].name = memStringDupe(name);
    etgVariableTable[etgVariableIndex].offset = stat->variableSize;
    etgVariableTable[etgVariableIndex].rateOffset = 0xffffffff;
    etgVariableTable[etgVariableIndex].initial = initial;
    etgVariableTable[etgVariableIndex].bInitial = (ubyte)bInitial;
    etgVariableTable[etgVariableIndex].type = (ubyte)type;
    etgVariableTable[etgVariableIndex].size = (ubyte)size;
    etgVariableTable[etgVariableIndex].codeBlock = (ubyte)etgParseMode;
    etgVariableIndex++;
    if (type != EVT_Label && type != EVT_ConstLabel && type != EVT_VarLabel)
    {
        stat->variableSize += sizeof(udword);
    }
    return(etgVariableIndex);
}

/*-----------------------------------------------------------------------------
    Name        : etgVariableTableProcess
    Description : Process the variable table after the effect has been loaded
                    properly.
    Inputs      : void
    Outputs     : Frees all the strings in the table but not before creating a
                    variable initialization table with the following format:
                  destIndices[n] is index of variable to initialize with *destDataInit[n]
    Return      : number of initialization variables
----------------------------------------------------------------------------*/
sdword etgVariableTableProcess(ubyte **destOffsets, udword **destInitData)
{
    udword *tempBlock, *finalBlock;
    ubyte *tempOffsets, *finalOffsets;
    sdword tableIndex, index, varIndex;

    //create a temp block in the biggest size it could be
    tempBlock = memAlloc((sizeof(ubyte) + sizeof(udword)) * etgVariableIndex, "TempVarInit", 0);
    tempOffsets = (ubyte *)(&tempBlock[etgVariableIndex]);
    for (tableIndex = index = varIndex = 0; index < etgVariableIndex; index++)
    {
        if (etgVariableTable[index].bInitial)
        {                                                   //if initialize this variable
            dbgAssert(tableIndex < etgVariableIndex);
            dbgAssert(index <= UDWORD_Max);
            tempOffsets[tableIndex] = (ubyte)varIndex;         //store variable index
            tempBlock[tableIndex] = etgVariableTable[index].initial;//store actual init data
            tableIndex++;
        }
        if (etgVariableTable[index].type != EVT_Label &&
            etgVariableTable[index].type != EVT_ConstLabel &&
            etgVariableTable[index].type != EVT_VarLabel)
        {
            varIndex++;
        }
        memFree(etgVariableTable[index].name);              //no name is needed anymore
    }
    if (tableIndex == 0)
    {                                                       //if no variables initialized
        memFree(tempBlock);                                 //free temporary memory
        *destInitData = NULL;
        *destOffsets = NULL;
        return(0);                                          //don't do nuttin'
    }
    finalBlock = memAlloc((sizeof(ubyte) + sizeof(udword)) * tableIndex, "VarInitData", NonVolatile);
    finalOffsets = (ubyte *)(&finalBlock[tableIndex]);      //allocate the final blocks
    memcpy(finalBlock, tempBlock, sizeof(udword) * tableIndex);//copy them over
    memcpy(finalOffsets, tempOffsets, sizeof(ubyte) * tableIndex);
    memFree(tempBlock);                                     //free temporary memory
    *destInitData = finalBlock;                             //return pointers
    *destOffsets = finalOffsets;
    return(tableIndex);
}

/*-----------------------------------------------------------------------------
    Name        : etgCodeBlockDistill
    Description : Resizes a code block to the minimum size required, then frees it.
    Inputs      : codeBlock - code block to resize
                  codeLength - current length of code block
                  newCode - where to store the new code block pointer
    Outputs     : newCode
    Return      : length of new code block code.
----------------------------------------------------------------------------*/
sdword etgCodeBlockDistill(ubyte *codeBlock, sdword codeLength, ubyte **newCode)
{
    if (codeLength == 0)
    {
        *newCode = NULL;
    }
    else
    {
        *newCode = memAlloc(codeLength, "EffectCodeBlock", NonVolatile);//alloc new block
        memcpy(*newCode, codeBlock, codeLength);            //copy old block over
    }
    memFree(codeBlock);                                     //free old block
    return(codeLength);                                     //length of new block
}

/*-----------------------------------------------------------------------------
    Name        : etgForwardReferenceFix
    Description : Fix all the forward references in a code block.
    Inputs      : codeSegment - segment to search through and fix
                  segmentLength - length of segment
    Outputs     : Any opcodes in the code segment with the EOP_Resolve in their
                    opcode will have their forward references fixed.
    Return      :
----------------------------------------------------------------------------*/
void etgForwardReferenceFix(ubyte *codeSegment, sdword segmentLength)
{
    sdword offset;
    etgopcode opcode;
    etgfunctioncall *call;
    etgbranch *branch;
    char *name;
    etgvarentry *label;

    for (offset = 0; offset < segmentLength;)
    {
        opcode = *((udword *)(codeSegment + offset));
        if (opcode & EOP_Resolve)
        {                                                   //if we have to update this opcode's branch member
            branch = (etgbranch *)(codeSegment + offset);
            name = (char *)branch->branchTo;                //get name
            label = etgVariableFind(name);                  //find the label
#if ETG_ERROR_CHECKING
            if (label == NULL)
            {
                etgLoadErrorf(ETG_U, "Unresolved branch destination '%s'", name);
                return;
            }
            if (label->type != EVT_Label)
            {
                etgLoadErrorf(ETG_U, "Symbol '%s' not a proper label name.", name);
                return;
            }
#endif
            branch->branchTo = label->initial;              //fix up the new offset
            branch->codeBlock = label->codeBlock;           //set code block
            memFree(name);                                  //free this no-longer needed name
            bitClear(branch->opcode, EOP_Resolve);          //clear the resolve bit
            bitClear(opcode, EOP_Resolve);
        }
        if (opcode == EOP_Function)
        {
            call = (etgfunctioncall *)(codeSegment + offset);
            offset += etgFunctionSize(call->nParameters);   //special offset update for function calls
        }
        else
        {
            offset += etgHandleTable[opcode].length;        //update the opcode pointer
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectCodeLoad
    Description : Load in an effect from a .etg file.
    Inputs      : fileName - name of file to load
    Outputs     : allocates an effect static block and loads it in.
    Return      : newly allocated and loaded effect.
----------------------------------------------------------------------------*/
etgeffectstatic *etgEffectCodeLoad(char *fileName)
{
    char string[STRING_LENGTH];
    char *pString;
    etgeffectstatic *newStatic;
    ubyte tempOpcode[OPCODE_MAX];
    sdword opcodeLength, index, offset;

    //allocate and initialize the new effect static structure
    //newStatic = memAlloc(sizeof(etgeffectstatic), "EffectStatic", 0);
    //newStatic->name = NULL;                                 //no name yet
    newStatic = etgEffectStaticFind(fileName, TRUE);        //see if this effect already loaded
    if (newStatic->nParticleBlocks != 0xffffffff)
    {                                                       //if effect has already been loaded properly
        return(newStatic);                                  //return because we're done
    }
    if (etgNumberEffectsExpected > 0)
    {
        HorseRaceNext((real32)etgNumberEffectsLoaded / (real32)etgNumberEffectsExpected);
        etgNumberEffectsLoaded++;
    }
    newStatic->nParticleBlocks = ETG_NewParticleLength;     //default particle block size
    newStatic->nHistoryList = EH_DefaultSize;
    newStatic->variableSize = 0;                            //no variables yet
    newStatic->rateSize = 0;                                //no rate variables yet
    newStatic->softwareVersion = NULL;                      //by default, there is no software version

    //open the file
    dbgAssert(!etgFileHandle);
    etgFileHandle = fileOpen(fileName, FF_TextMode);        //open file
    etgFileLine = 0;                                        //start on first line
    etgTimeIndexVar = NULL;                                 //handle nesting errors OK
//    etgFileName = memStringDupe(etgLineRead(string, STRING_LENGTH));//first line of file is always name of source file
    etgFileName = memStringDupe(fileName);                  //save copy of file name
    etgParseMode = EPM_Nothing;                             //start parsing with nothing
    etgNestLevel = 0;                                       //start at base nesting level
    memset(etgNestStack, 0, sizeof(etgnestentry) * ETG_NestStackDepth);

#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\netgEffectCodeLoad: '%s'", fileName);
#elif ETG_VERBOSE_LEVEL == 1
    dbgMessagef(".");
#endif
    //set up some variables, allocate memory
    etgExecStack.etgCodeBlock[EPM_Startup].offset = etgExecStack.etgCodeBlock[EPM_EachFrame].offset = etgExecStack.etgCodeBlock[EPM_TimeIndex].offset = 0;
    etgExecStack.etgCodeBlock[EPM_Startup].code = memAlloc(ETG_StartupParseSize, "StartupCodePool", 0);
    etgExecStack.etgCodeBlock[EPM_Startup].length = ETG_StartupParseSize;
    etgExecStack.etgCodeBlock[EPM_EachFrame].code = memAlloc(ETG_EachFrameParseSize, "EachFrameCodePool", 0);
    etgExecStack.etgCodeBlock[EPM_EachFrame].length = ETG_EachFrameParseSize;
    etgExecStack.etgCodeBlock[EPM_TimeIndex].code = memAlloc(ETG_TimeIndexParseSize, "TimeIndexCodePool", 0);
    etgExecStack.etgCodeBlock[EPM_TimeIndex].length = ETG_TimeIndexParseSize;
    etgVariableTable = memAlloc(sizeof(etgvarentry) * ETG_VarTableParseLength, "VariableNameBlock", 0);
    etgVariableIndex = 0;
    etgConstData = memAlloc(ETG_ConstDataPool, "ConstDataTemp", 0);
    newStatic->constLength = 0;
    newStatic->constData = NULL;
//    newStatic->bSelfDeleting = FALSE;
    newStatic->specialOps = 0;
    //set up for alternates
    etgAlternateTable = memAlloc(sizeof(etgextalt) * ETG_MaxAlternates, "LoadAltTable", 0);
    etgAlternateStructIndex = 0;
    etgTotalOffsets = 0;                                    //currently no decision offset tables

    while ((pString = etgLineRead(string, STRING_LENGTH)) != NULL)
    {
#if ETG_VERBOSE_LEVEL >= 3
        dbgMessagef("\n%s", pString);
#endif
        opcodeLength = 0;                                   //default to no opcode
        if (!etgOpcodeScan(newStatic, string, tempOpcode, &opcodeLength))//scan to see if there was an opcode
        {                                                   //if not a built-in opcode
            if (!etgFunctionCallScan(newStatic, string, tempOpcode, &opcodeLength))
            {                                               //or a defined function
                if (!etgVarCopyScan(newStatic, string, tempOpcode, &opcodeLength))
                {                                           //or even a variable assignment
#if ETG_ERROR_CHECKING
                    etgLoadErrorf(ETG, "Don't know how to handle '%s'", pString);
#endif
                }
            }
        }
        //set up an opcode structure in the appropriate code block
        if (opcodeLength > 0)
        {
#if ETG_ERROR_CHECKING
            if (opcodeLength > OPCODE_MAX)
            {
                etgLoadErrorf(ETG, "Opcode too long (%d > %d).", opcodeLength, OPCODE_MAX);
            }
            if (etgExecStack.etgCodeBlock[etgParseMode].offset + opcodeLength >= etgExecStack.etgCodeBlock[etgParseMode].length)
            {
                dbgFatalf(ETG, "Exceeded %d bytes of code in code block %d.",  etgExecStack.etgCodeBlock[etgParseMode].length, etgParseMode);
            }
#endif
            memcpy(etgExecStack.etgCodeBlock[etgParseMode].code + etgExecStack.etgCodeBlock[etgParseMode].offset,
                   tempOpcode, opcodeLength);
            etgExecStack.etgCodeBlock[etgParseMode].offset += opcodeLength;
        }
        if (PARSEERROR)
        {                                                   //if an error was encountered
            sdword j;
            //should free a bunch of temporary memory here
            for (j = 0; j < ETG_EventListLength; j++)
            {
                if (etgEventTable[j].effectStatic == newStatic)
                {
                    //since there are some things in this effect which may
                    //or may not have been allocated, let's just not free them.
                    //This is a debugging feature anyhow.
                    memFree(etgEventTable[j].effectStatic->name);
                    memFree(etgEventTable[j].effectStatic);
                    etgEventTable[j].effectStatic = NULL;
                    goto foundThisNewEffect;
                }
            }
            dbgFatalf(DBG_Loc, "Newly loaded effect '%s' not found in registry", newStatic->name);
foundThisNewEffect:;
            memFree(etgConstData);
            memFree(etgVariableTable);
            memFree(etgFileName);
            fileClose(etgFileHandle);                       //close the handle
            etgFileHandle = 0;
            etgErrorEncountered = FALSE;
            return(NULL);
        }
    }
    //fix all forward references in the code segments
    etgForwardReferenceFix(etgExecStack.etgCodeBlock[EPM_Startup].code, etgExecStack.etgCodeBlock[EPM_Startup].offset);
    etgForwardReferenceFix(etgExecStack.etgCodeBlock[EPM_EachFrame].code, etgExecStack.etgCodeBlock[EPM_EachFrame].offset);
    etgForwardReferenceFix(etgExecStack.etgCodeBlock[EPM_TimeIndex].code, etgExecStack.etgCodeBlock[EPM_TimeIndex].offset);

    //combine all the wastefully allocated alternate blocks
    if (etgAlternateStructIndex > 0)
    {
        newStatic->decisions = memAlloc(sizeof(etgalternate) * etgAlternateStructIndex, "DecideTable", NonVolatile);
        if (etgTotalOffsets > 0)                                //allocate memory
        {
            newStatic->alternateOffsets = memAlloc(sizeof(udword) * etgTotalOffsets, "AltOffsetTable", NonVolatile);
        }
        else
        {
            newStatic->alternateOffsets = NULL;
        }
        for (index = offset = 0; index < etgAlternateStructIndex; index++)
        {
            pString = (char *)etgAlternateTable[index].decision->offset;
            memcpy(newStatic->alternateOffsets + offset, pString,//copy local offset table for this one
                   sizeof(udword) * etgAlternateTable[index].decision->tableLength);
            etgAlternateTable[index].decision->offset = newStatic->alternateOffsets + offset;
            offset += etgAlternateTable[index].decision->tableLength;
            newStatic->decisions[index] = etgAlternateTable[index].alt;//copy just the needed part of the alternate offset table
            memFree(pString);                                   //free the old list
        }
        newStatic->nDecisions = etgAlternateStructIndex;  //remember how many alternates there are
    }
    else
    {
        newStatic->nDecisions = 0;
        newStatic->decisions = NULL;
        newStatic->alternateOffsets = NULL;
    }
    memFree(etgAlternateTable);                             //free the alternates table
    //free up the variable names and set variable init data members
    newStatic->initLength = etgVariableTableProcess(&newStatic->variableOffsets, &newStatic->variableInitData);
    dbgAssert(newStatic->variableSize + newStatic->rateSize >= 0);
    newStatic->effectSize = etgEffectSize(newStatic->nParticleBlocks) +
                newStatic->variableSize + newStatic->rateSize + newStatic->variableSize / 4;

    //create code blocks in proper size
    newStatic->codeBlock[EPM_Startup].length = etgCodeBlockDistill(etgExecStack.etgCodeBlock[EPM_Startup].code, etgExecStack.etgCodeBlock[EPM_Startup].offset, &newStatic->codeBlock[EPM_Startup].code);
    newStatic->codeBlock[EPM_EachFrame].length = etgCodeBlockDistill(etgExecStack.etgCodeBlock[EPM_EachFrame].code, etgExecStack.etgCodeBlock[EPM_EachFrame].offset, &newStatic->codeBlock[EPM_EachFrame].code);
    newStatic->codeBlock[EPM_TimeIndex].length = etgCodeBlockDistill(etgExecStack.etgCodeBlock[EPM_TimeIndex].code, etgExecStack.etgCodeBlock[EPM_TimeIndex].offset, &newStatic->codeBlock[EPM_TimeIndex].code);

    //distill the constant data
    if (newStatic->constLength > 0)
    {                                                       //if anything was allocated
        newStatic->constData = memAlloc(newStatic->constLength, "ConstData", NonVolatile);
        memcpy(newStatic->constData, etgConstData, newStatic->constLength);
    }
    //create a history list, if applicable
    if (newStatic->nHistoryList > 0)
    {
        newStatic->iHistoryList = 0;
        newStatic->historyList = memAlloc(sizeof(real32) * newStatic->nHistoryList, "ETGHistoryList", NonVolatile);
        for (index = 0; index < newStatic->nHistoryList; index++)
        {
            newStatic->historyList[index] = REALlyNegative;
        }
    }
    else
    {
        newStatic->historyList = NULL;
    }
    //free some memory
    memFree(etgConstData);
    memFree(etgVariableTable);
    memFree(etgFileName);
    fileClose(etgFileHandle);                               //close the handle
    etgFileHandle = 0;
    return(newStatic);                                      //all done
}

/*-----------------------------------------------------------------------------
    Effect parsing functions:
-----------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------
    Name        :
    Description : Opcode parsing functions have the following format
    Inputs      : dest - where to store the new opcode structure, if any
                  opcode - string representing the opcode-string
                  params - string representing the parameters (or rest of the line)
                  ret - string representing the parameter to assign return value to
    Outputs     : Creates a new opcode of some format and stores it at dest.
    Return      : Amount to update the code handle by, if any.
----------------------------------------------------------------------------*/
sdword etgLineNumberUpdate(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    char string[STRING_LENGTH];
    sdword nScanned, lineNumber;

    nScanned = sscanf(params, "%d \"%s\"", &lineNumber, string);
#if ETG_ERROR_CHECKING
    if (nScanned != 2 || lineNumber < 1 || strlen(string) < 1)
    {
        etgLoadErrorf(ETG, "Invalid '#line' directive '%s'.", params);
    }
#endif
    etgFileLine = lineNumber;                               //set current line number
    memFree(etgFileName);                                   //free old name
    etgFileName = memStringDupe(string);                    //all done
    return(0);
}

sdword etgNOPCreate(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    etgnop *nop = (etgnop *)dest;
    nop->opcode = EOP_Nop;
    return(sizeof(etgnop));
}
//handle the '{'
sdword etgBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    etgNestPush();
    return(0);
}
//handle the '}'
sdword etgBlockClose(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    sdword offset = -1;
    if (etgParseMode < ETG_NumberCodeBlocks)
    {
        offset = etgExecStack.etgCodeBlock[etgParseMode].offset;
    }
    etgNestPop(offset);
    return(0);
}

//callback for handing the '}' of the master block levels
void etgParseModeRestore(sdword codeBlock, sdword offset, sdword newOffset, ubyte *userData)
{
    etgParseMode = codeBlock;
}

//callback for handing '}' to insert an 'end' opcode in the code block
void etgCodeBlockEnd(sdword codeBlock, sdword off, sdword newOffset, ubyte *userData)
{
    sdword *offset;
    etgprocesscontrol *opcode;

    dbgAssert(etgParseMode >= 0 && etgParseMode < ETG_NumberCodeBlocks);
    offset = &etgExecStack.etgCodeBlock[etgParseMode].offset;
    opcode = (etgprocesscontrol *)(etgExecStack.etgCodeBlock[etgParseMode].code + *offset);
    opcode->opcode = EOP_End;                               //create the opcode
    *offset += sizeof(etgprocesscontrol);                   //and update pointer
    etgParseMode = codeBlock;                               //and finally restore the parsing mode of before
    etgTimeIndexIndex = 0;                                  //in case we're ending a time index block, reset this counter
}

//handle opening the variable block
sdword etgVariableBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Nothing || etgNestLevel != 0)
    {
        dbgMessagef(ETG, "Starting variable block in nesting level %d", etgNestLevel);
    }
#endif
    etgNestFunctionSet(NULL, etgParseModeRestore, etgParseMode, 0, NULL);//set a callback
    etgParseMode = EPM_Variables;                           //we're now in variable land
    return(0);
}

//handle opening code blocks
sdword etgStartupBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    etgprocesscontrol *endcode;
#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Nothing || etgNestLevel != 0)
    {
        dbgMessagef(ETG, "Starting code block in nesting level %d", etgNestLevel);
    }
#endif
    if (etgExecStack.etgCodeBlock[EPM_Startup].offset != 0)
    {                                                       //if not the first such code block
        endcode = (etgprocesscontrol *)(etgExecStack.etgCodeBlock[EPM_Startup].code + etgExecStack.etgCodeBlock[EPM_Startup].offset - sizeof(etgprocesscontrol));
        dbgAssert((ubyte *)endcode >= etgExecStack.etgCodeBlock[EPM_Startup].code);
        dbgAssert(endcode->opcode == EOP_End);
        etgExecStack.etgCodeBlock[EPM_Startup].offset -= sizeof(etgprocesscontrol);//remove the previous end opcode
    }
    etgNestFunctionSet(NULL, etgCodeBlockEnd, etgParseMode, 0, NULL);//set a callback
    etgParseMode = EPM_Startup;                             //we're now in some code block
    return(0);
}
sdword etgEachFrameBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    etgprocesscontrol *endcode;
#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Nothing || etgNestLevel != 0)
    {
        dbgWarningf(ETG, "Starting code block in nesting level %d", etgNestLevel);
    }
#endif
    if (etgExecStack.etgCodeBlock[EPM_EachFrame].offset != 0)
    {                                                       //if not the first such code block
        endcode = (etgprocesscontrol *)(etgExecStack.etgCodeBlock[EPM_EachFrame].code + etgExecStack.etgCodeBlock[EPM_EachFrame].offset - sizeof(etgprocesscontrol));
        dbgAssert((ubyte *)endcode >= etgExecStack.etgCodeBlock[EPM_EachFrame].code);
        dbgAssert(endcode->opcode == EOP_End);
        etgExecStack.etgCodeBlock[EPM_EachFrame].offset -= sizeof(etgprocesscontrol);//remove the previous end opcode
    }
    etgNestFunctionSet(NULL, etgCodeBlockEnd, etgParseMode, 0, NULL);//set a callback
    etgParseMode = EPM_EachFrame;                             //we're now in some code block
    return(0);
}
sdword etgTimeIndexBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    etgprocesscontrol *endcode;
#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Nothing || etgNestLevel != 0)
    {
        dbgMessagef(ETG, "Starting code block in nesting level %d", etgNestLevel);
    }
#endif
    if (etgExecStack.etgCodeBlock[EPM_TimeIndex].offset != 0)
    {                                                       //if not the first such code block
        endcode = (etgprocesscontrol *)(etgExecStack.etgCodeBlock[EPM_TimeIndex].code + etgExecStack.etgCodeBlock[EPM_TimeIndex].offset - sizeof(etgprocesscontrol));
        dbgAssert((ubyte *)endcode >= etgExecStack.etgCodeBlock[EPM_TimeIndex].code);
        dbgAssert(endcode->opcode == EOP_End);
        etgExecStack.etgCodeBlock[EPM_TimeIndex].offset -= sizeof(etgprocesscontrol);//remove the previous end opcode
    }
    etgNestFunctionSet(NULL, etgCodeBlockEnd, etgParseMode, 0, NULL);//set a callback
    etgParseMode = EPM_TimeIndex;                             //we're now in some code block
    return(0);
}

sdword etgWorldRenderSet(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    bitSet(stat->specialOps, ESO_WorldRender);
    return(0);
}
/*
sdword etgForceVisibleSet(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    bitSet(stat->specialOps, ESO_ForceVisible);
    return(0);
}
*/
/*-----------------------------------------------------------------------------
    Name        : etgParseVariable
    Description : service function for parsing help in variable definitions
    Inputs      : params - pointer to something like "velocity = 35"
    Outputs     : splits the assignment and the variable name apart by null-
                    terminating the variable name.  params would become
                    just "velocity".
    Return      : pointer to assignment value (35 in above example), or NULL
                    if no initial value.
----------------------------------------------------------------------------*/
char *etgParseVariable(char *params, bool *bSetInitial)
{
    char *start, *end;
    if ((start = end = strchr(params, '=')) == NULL)
    {                                                       //if no initial value
        *bSetInitial = FALSE;
        return(NULL);
    }
    else
    {
        *bSetInitial = TRUE;
        start++;                                            //skip the '='
        while (strchr(" \t", *start))
        {                                                   //skip leading whitespace
            start++;
        }
    }
    end--;
    while (strchr(" \t", *end))                             //find end of variable name
    {
        end--;
    }
    *(end + 1) = 0;                                         //NULL-terminate end of name
#if ETG_ERROR_CHECKING
    if (strlen(params) <= 1)
    {
        etgLoadErrorf(ETG, "syntax error defining variable");
    }
#endif
    return(start);
}

//create a new integer
sdword etgNewInteger(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    udword initial;
    sdword nScanned;
    bool bSetInitial;
    char *start;

    if (etgParseMode == EPM_Constant)
    {                                                       //if we're creating a constant
        start = strtok(params, " =\t");
        nScanned = sscanf(start, "%d", &initial);
#if ETG_ERROR_CHECKING
        if (stat->constLength + 4 > ETG_ConstDataPool)
        {
            etgLoadErrorf(ETG, "Exceeded %d bytes of constant data.", ETG_ConstDataPool);
            return(0);
        }
        if (nScanned != 1)
        {
            etgLoadErrorf(ETG, "Error scanning '%s' for integer constant", params);
            return(0);
        }
#endif
        *((udword *)(etgConstData + stat->constLength)) = initial;
        stat->constLength += sizeof(udword);
        return(0);
    }
#if ETG_ERROR_CHECKING                                      //else it'a an actual variable
    if (etgParseMode != EPM_Variables)
    {
        etgLoadErrorf(ETG, "Variable definition outside of variable block.");
        return(0);
    }
#endif
    start = etgParseVariable(params, &bSetInitial);         //prepare string
    if (bSetInitial)
    {
        nScanned = sscanf(start, "%d", &initial);           //scan the initial value
#if ETG_ERROR_CHECKING
        if (nScanned != 1)
        {
            etgLoadErrorf(ETG, "syntax error in variable initialization: '%s'", start);
            return(0);
        }
#endif
    }                                                       //create new variable
    etgNewVariableCreate(params, stat, EVT_Int, bSetInitial, initial, 0);
    return(0);
}

//create a new RGB variable
sdword etgNewRGB(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    udword initial;
    bool bSetInitial;
    char *start;

#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Variables)
    {
        etgLoadErrorf(ETG, "Variable definition outside of variable block.");
        return(0);
    }
#endif
    start = etgParseVariable(params, &bSetInitial);         //prepare string
    if (bSetInitial)
    {
        initial = etgColorScan(start);
    }                                                       //create new variable
    etgNewVariableCreate(params, stat, EVT_RGB, bSetInitial, initial, 0);
    return(0);
}

//create a new RGBA variable
sdword etgNewRGBA(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    udword initial;
    bool bSetInitial;
    char *start;

#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Variables)
    {
        etgLoadErrorf(ETG, "Variable definition outside of variable block.");
        return(0);
    }
#endif
    start = etgParseVariable(params, &bSetInitial);         //prepare string
    if (bSetInitial)
    {
        initial = etgColorScan(start);
    }                                                       //create new variable
    etgNewVariableCreate(params, stat, EVT_RGBA, bSetInitial, initial, 0);
    return(0);
}

//create a new texture anim frame
sdword etgSubTextureDWORD(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    char *texName, *string;
    udword nScanned, xyInfo, flags;

    texName = strtok(params, " \t,");
#if ETG_ERROR_CHECKING
    if (texName == NULL)
    {
        etgLoadErrorf(ETG, "Error reading animation frame info.");
        return(0);
    }
#endif
    string = strtok(NULL, " \t,");
#if ETG_ERROR_CHECKING
    if (string == NULL)
    {
        etgLoadErrorf(ETG, "Error reading animation frame info.");
        return(0);
    }
#endif
    nScanned = sscanf(string, "%d", &xyInfo);
#if ETG_ERROR_CHECKING
    if (nScanned != 1)
    {
        etgLoadErrorf(ETG, "Error reading animation frame info.");
        return(0);
    }
#endif
    string = strtok(NULL, " \t,");
#if ETG_ERROR_CHECKING
    if (string == NULL)
    {
        etgLoadErrorf(ETG, "Error reading animation frame info.");
        return(0);
    }
#endif
    nScanned = sscanf(string, "%d", &flags);
#if ETG_ERROR_CHECKING
    if (nScanned != 1)
    {
        etgLoadErrorf(ETG, "Error reading animation frame info.");
        return(0);
    }
    if (stat->constLength + 12 > ETG_ConstDataPool)
    {
        etgLoadErrorf(ETG, "Exceeded %d bytes of constant data.", ETG_ConstDataPool);
    }
#endif
    *((trhandle *)(etgConstData + stat->constLength)) = trTextureRegister(texName, NULL, stat);
    stat->constLength += sizeof(trhandle);
    *((udword *)(etgConstData + stat->constLength)) = xyInfo;
    stat->constLength += sizeof(udword);
    *((udword *)(etgConstData + stat->constLength)) = flags;
    stat->constLength += sizeof(udword);
    return(0);
}

//create a new mesh-morph anim frame
sdword etgMorphAnimDWORD(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    char *meshName, *string;
    udword nScanned, flags;

    meshName = strtok(params, " \t,");
#if ETG_ERROR_CHECKING
    if (meshName == NULL)
    {
        etgLoadErrorf(ETG, "Error reading morph frame info.");
        return(0);
    }
#endif
    string = strtok(NULL, " \t,");
#if ETG_ERROR_CHECKING
    if (string == NULL)
    {
        etgLoadErrorf(ETG, "Error reading morph frame info.");
        return(0);
    }
#endif
    nScanned = sscanf(string, "%d", &flags);
#if ETG_ERROR_CHECKING
    if (nScanned != 1)
    {
        etgLoadErrorf(ETG, "Error reading morph frame info.");
        return(0);
    }
    if (stat->constLength + 8 > ETG_ConstDataPool)
    {
        etgLoadErrorf(ETG, "Exceeded %d bytes of constant data.", ETG_ConstDataPool);
    }
#endif
    *((meshdata **)(etgConstData + stat->constLength)) = etgMeshRegister(meshName);
    stat->constLength += sizeof(meshdata *);
//    *((udword *)(etgConstData + stat->constLength)) = xyInfo;
//    stat->constLength += sizeof(udword);
    *((udword *)(etgConstData + stat->constLength)) = flags;
    stat->constLength += sizeof(udword);
    return(0);
}

//handle setting the texture handle for a sprite.  Creates a special function call
sdword etgSetTextureParse(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    etgfunctioncall *function = (etgfunctioncall *)dest;
    char path[PATH_Max];
    char *param;
    etgvarentry *lVar;
    sdword index, nScanned;

    param = strtok(params, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (param == NULL)
    {
        etgLoadErrorf(ETG, "'%s' syntax: setTexture(<texName>, x, y, x', y')", opcode);
        return(0);
    }
#endif
    if (strcmp(opcode, "setTexture") == 0)
    {
        function->function = (udword (*)(void))partSetTrHandle;
    }
    else
    {
        lVar = etgVariableFind(param);                     //find the variable
#if ETG_ERROR_CHECKING
        if (lVar == NULL)
        {
            etgLoadErrorf(ETG, "Variable '%s' not found.", param);
            return(0);
        }
        if (lVar->type != EVT_Int)
        {
            etgLoadErrorf(ETG, "'%s' needs integer handle.", opcode);
            return(0);
        }
        if (lVar->type == EVT_Pointer)
        {
            etgLoadErrorf(ETG, "!!!Cannot do pointer types yet.");
            return(0);
        }
#endif
        function->parameter[function->nParameters].type = EVT_Int;//first param is the texture handle
        function->parameter[function->nParameters].param = lVar->offset;
        function->function = (udword (*)(void))partModifyTexture;
        function->nParameters++;
        param = strtok(NULL, ETG_TokenDelimiters);
    }
#if ETG_ERROR_CHECKING
    if (param == NULL)
    {
        etgLoadErrorf(ETG, "'%s' syntax: setTexture(<texName>, x, y, x', y')", opcode);
        return(0);
    }
#endif
    strcpy(path, ETG_Directory);
    strcat(path, param);
    function->opcode = EOP_Function;
    function->nParameters = 0;
    //function->nParameters = 1;  //!!!
    //function->nParameters = 5
    function->returnValue = 0xffffffff;
    function->passThis = FALSE;
    function->parameter[function->nParameters].type = EVT_Constant;                 //first param is the texture handle
    function->parameter[function->nParameters].param = trTextureRegister(path, NULL, dest);//a dummy dest should prevent it from sorting with other ships
    function->nParameters++;


    for (index = 1; index <= 4; index++)
    {
        param = strtok(NULL, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
        if (param == NULL)
        {
            etgLoadErrorf(ETG, "'%s' syntax: setTexture(<texName>, x, y, x', y').  Missing parameter %d", opcode, index + 1);
            return(0);
        }
#endif
        function->parameter[function->nParameters].type = EVT_Constant;
        nScanned = sscanf(param, "%d", &function->parameter[function->nParameters].param);
#if ETG_ERROR_CHECKING
        if (nScanned != 1)
        {
            etgLoadErrorf(ETG, "'%s' bad format for parameter %d", opcode, index + 1);
            return(0);
        }
#endif
        function->nParameters++;
    }

    return(etgFunctionSize(function->nParameters));
}

/*-----------------------------------------------------------------------------
    Name        : etgMeshRegister
    Description : Load in a mesh if it's not already loaded
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
meshdata *etgMeshRegister(char *filename)
{
    sdword index;
    etgmeshreg *free = NULL;

    for (index = 0; index < ETG_NumberMeshes; index++)
    {                                                       //scan through the registry
        if (etgMeshRegistry[index].filename == NULL)
        {                                                   //if this guy free
            if (free == NULL)
            {                                               //record pointer to a free one
                free = &etgMeshRegistry[index];
            }
        }
        else
        {                                                   //else something loaded here
            if (!_stricmp(filename, etgMeshRegistry[index].filename))
            {                                               //if this is the mesh we want
                return(etgMeshRegistry[index].mesh);
            }
        }
    }
    //if it gets here, no mesh was found, load it in.
#if ETG_ERROR_CHECKING
    if (free == NULL)
    {                                                       //if no free spots left
        dbgFatalf(DBG_Loc, "All %d slots in mesh registry used when loading '%s'", ETG_NumberMeshes, filename);
    }
#endif
    free->filename = memStringDupeNV(filename);
    free->mesh = meshLoad(filename);
    return(free->mesh);
}

/*-----------------------------------------------------------------------------
    Name        : etgMeshRegistryReset
    Description : Clear out any meshes loaded into the mesh registry.
    Inputs      : void
    Outputs     : Frees all texture loaded into to mesh registry and set the
                    registry pointers to NULL.
    Return      : void
    Note        : No effects should be loaded when this is called as they
                    reference the data to be freed by this function.  Also,
                    this should not be called at startup of the game.
----------------------------------------------------------------------------*/
void etgMeshRegistryReset(void)
{
    sdword index;

    for (index = 0; index < ETG_NumberMeshes; index++)
    {                                                       //scan through the registry
        if (etgMeshRegistry[index].filename != NULL)
        {                                                   //if this guy has something allocated
#if ETG_VERBOSE_LEVEL >= 1
            dbgMessagef("\netgMeshRegistryClear: freeing '%s' at 0x%x", etgMeshRegistry[index].filename, etgMeshRegistry[index].mesh);
#endif
            memFree(etgMeshRegistry[index].filename);
            etgMeshRegistry[index].filename = NULL;
            meshFree(etgMeshRegistry[index].mesh);
            etgMeshRegistry[index].mesh = NULL;
        }
    }
}

//handle 'setMesh'
sdword etgSetMeshParse(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    etgfunctioncall *function = (etgfunctioncall *)dest;
    char *param;

    param = strtok(params, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (param == NULL)
    {
        etgLoadErrorf(ETG, "Syntax: %s(<meshName>)", opcode);
        return(0);
    }
/*
    if (etgMeshRegistryIndex >= ETG_NumberMeshes)
    {
        etgLoadErrorf(ETG, "Exceeded %d ETG meshes.", ETG_NumberMeshes);
    }
*/
#endif
    function->opcode = EOP_Function;
    function->function = (udword (*)(void))partSetMeshdata;
    function->nParameters = 1;
    function->returnValue = 0xffffffff;
    function->passThis = FALSE;

    //!!! perhaps we can register these instead of just loading them?
//    etgMeshRegistry[etgMeshRegistryIndex] = meshLoad(param);//load mesh into mesh registry
    function->parameter[0].type = EVT_Constant;             //and store pointer
//    function->parameter[0].param = (udword)etgMeshRegistry[etgMeshRegistryIndex];
    function->parameter[0].param = (udword)etgMeshRegister(param);
//    etgMeshRegistryIndex++;                                 //update mesh registry index
    return(etgFunctionSize(1));
}

//create a new float
sdword etgNewFloat(struct etgeffectstatic *stat, ubyte *dest, char *opcode, char *params, char *ret)
{
    udword initial;
    sdword nScanned;
    bool bSetInitial;
    char *start;

    if (etgParseMode == EPM_Constant)
    {                                                       //if we're creating a constant
        start = strtok(params, " =\t");
        nScanned = sscanf(start, "%f", &initial);
#if ETG_ERROR_CHECKING
        if (stat->constLength + 4 > ETG_ConstDataPool)
        {
            etgLoadErrorf(ETG, "Exceeded %d bytes of constant data.", ETG_ConstDataPool);
            return(0);
        }
        if (nScanned != 1)
        {
            etgLoadErrorf(ETG, "Error scanning '%s' for float constant", params);
            return(0);
        }
#endif
        *((udword *)(etgConstData + stat->constLength)) = initial;
        stat->constLength += sizeof(udword);
        return(0);
    }
#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Variables)
    {
        etgLoadErrorf(ETG, "Variable definition outside of variable block.");
        return(0);
    }
#endif
    start = etgParseVariable(params, &bSetInitial);         //prepare string
    if (bSetInitial)
    {
        nScanned = sscanf(start, "%f", &initial);           //scan the initial value
#if ETG_ERROR_CHECKING
        if (nScanned != 1)
        {
            etgLoadErrorf(ETG, "syntax error in variable initialization: '%s'", start);
            return(0);
        }
#endif
    }                                                       //create new variable
    etgNewVariableCreate(params, stat, EVT_Float, bSetInitial, initial, 0);
    return(0);
}

/*-----------------------------------------------------------------------------
    Name        : etgConditionalComplete
    Description : Complete a conditional opcode in response to a '}'
    Inputs      : codeBlock: either startp, eachFrame or TimeIndex
                  offset: offset of the opcode to look for
                  newOffset: new code offset in code segment
    Outputs     : sets the codeBytes of the conditional opcode
    Return      :
----------------------------------------------------------------------------*/
etgconditional *etgIfOpcode;
void etgConditionalComplete(sdword codeBlock, sdword offset, sdword newOffset, ubyte *userData)
{
    etgconditional *opcode;

#if ETG_ERROR_CHECKING
    if (etgParseMode != codeBlock)
    {
        etgLoadErrorf(ETG, "closing '}' in different code block from the corresponding 'if'");
        return;
    }
    if (newOffset < offset)
    {
        etgLoadErrorf(ETG, "negative code bytes in 'if' code block");
        return;
    }
#endif
    //get opcode pointer
    opcode = (etgconditional *)(etgExecStack.etgCodeBlock[codeBlock].code + offset);
    //make sure we've found a good opcode
#if ETG_ERROR_CHECKING
    if (opcode->opcode < EOP_LowestConditional || opcode->opcode > EOP_HighestConsitional)
    {
        etgLoadErrorf(ETG, "Cannot find conditional opcode.  Found opcode %d instead", opcode->opcode);
        return;
    }
    dbgAssert(opcode->codeBytes == -1);
#endif
    //set code length in opcode
    opcode->codeBytes = newOffset - offset;
    etgIfOpcode = opcode;                               //save opcode reference
}

//handle 'if' statements
sdword etgConditional(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    char *param0, *oper, *param1;
    etgvarentry *var0, *var1;
    sdword isVar0, isVar1, bTranspose;
    char *parser;
    udword constant;
    etgconditional *opcode = (etgconditional *)dest;
    sdword index, subIndex;

#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Startup && etgParseMode != EPM_EachFrame && etgParseMode != EPM_TimeIndex)
    {
        etgLoadError(ETG, "'if' conditional statement outside of code blocks.");
        return(0);
    }
#endif
    param0 = strtok(params, " \t");
#if ETG_ERROR_CHECKING
    if (param0 == NULL)
    {
        etgLoadError(ETG, "'if' conditional statement missing left parameter.");
        return(0);
    }
#endif
    oper = strtok(NULL, " \t");
#if ETG_ERROR_CHECKING
    if (oper == NULL)
    {
        etgLoadError(ETG, "'if' conditional statement missing operator.");
        return(0);
    }
#endif
    param1 = strtok(NULL, " \t");
#if ETG_ERROR_CHECKING
    if (param1 == NULL)
    {
        etgLoadError(ETG, "'if' conditional statement missing right parameter.");
        return(0);
    }
#endif
    //now that we've isolated the parameters, let's figure out the types and stuff
    if (!etgIsNumber(param0))
    {                                                       //if first parameter is a variable
        isVar0 = 1;
        var0 = etgVariableFind(param0);
    }
    else
    {
        isVar0 = 0;
    }
    if (!etgIsNumber(param1))
    {                                                       //if other parameter is a variable
        isVar1 = 1;
        var1 = etgVariableFind(param1);
    }
    else
    {
        isVar1 = 0;
    }
    //ensure they're in the right order
    if ((!isVar0) && isVar1)                                //if constant/variable
    {
        bTranspose = TRUE;                                  //need to use the transpose operator
        swapInt(isVar0, isVar1);                            //swap the operators
        swapInt((udword)param0, (udword)param1);
        swapInt((udword)var0, (udword)var1);
    }
    else
    {
        bTranspose = FALSE;
    }
    //ensure the types are the same if they are both variables
    if (isVar0)
    {                                                       //if first one a variable
#if ETG_ERROR_CHECKING
        if (isVar1)
        {
            if (var0->type != EVT_Float && var1->type != EVT_Int)
            {                                               //if not a float or an int
                etgLoadErrorf(ETG, "Variable '%s' not a comparable type.");
                return(0);
            }
            if (var0->type != var1->type)
            {                                               //if differing types
                etgLoadErrorf(ETG, "Variables '%s' and '%s' are of different type", param0, param1);
                return(0);
            }
        }
#endif
    }
    else
    {
#if ETG_ERROR_CHECKING
        if (!isVar1)
        {                                                   //if both constants
            etgLoadErrorf(ETG, "'%s' and '%s' are both constants - use #if", param0, param1);
            return(0);
        }
#endif
    }

    if (var0->type == EVT_Float)
    {                                                       //choose appropriate sscanf format string
        parser = "%f";
    }
    else
    {
        parser = "%d";
    }
    if (!isVar1)
    {                                                       //if var1 constant
        sscanf(param1, parser, &constant);                  //scan in the constant
    }
    //now set the parameters in the compare opcode
    opcode->param0 = var0->offset;                          //set offset of this variable
    if (isVar1)
    {
        opcode->param1 = var1->offset;
    }
    else
    {
        opcode->param1 = constant;
    }
    //now let's find the appropriate opcode
    if (isVar1)
    {                                                       //if var-var
        if (var0->type == EVT_Float)
        {                                                   //var-var-float
            subIndex = 3;
        }
        else
        {                                                   //var-var-int
            subIndex = 1;
        }
    }
    else
    {
        if (var0->type == EVT_Float)
        {                                                   //var-const-float
            subIndex = 2;
        }
        else
        {                                                   //var-const-int
            subIndex = 0;
        }
    }
    //now we have everything but an operator string match.  Let's be doing that presently
    for (index = 0; strcmp(etgConditionalTable[index].symbol, "end"); index++)
    {
        if (strcmp(etgConditionalTable[index].symbol, oper) == 0)
        {                                                   //if names match
            if (bTranspose)
            {                                               //if we're supposed to use the transpose operator
                bTranspose = FALSE;                         //let's use it
                oper = etgConditionalTable[index].transpose;
                index = -1;
                continue;
            }
            opcode->opcode = etgConditionalTable[index].opcode[subIndex];
            break;                                          //set the opcode and stop looking
        }
    }
    //the only thing left is the codebytes member which must be set at the
    //  matching '}' line.  Let's prepare for this eventuality.
    dbgAssert(etgParseMode >= 0 && etgParseMode < ETG_NumberCodeBlocks);
    index = etgExecStack.etgCodeBlock[etgParseMode].offset;
    opcode->codeBytes = -1;                                 //flag code bytes not set
    etgNestFunctionSet(NULL, etgConditionalComplete, etgParseMode, index, (ubyte *)opcode);//set function to be called later
    return(sizeof(etgconditional));
}

//handle closing an 'else' block
void etgElseComplete(sdword codeBlock, sdword offset, sdword newOffset, ubyte *userData)
{
    etgbranch *opcode;
    etgconditional *ifOpcode;

#if ETG_ERROR_CHECKING
    if (etgParseMode != codeBlock)
    {
        etgLoadErrorf(ETG, "Closing '}' in different code block from the corresponding 'else'");
        return;
    }
    if (newOffset < offset)
    {
        etgLoadErrorf(ETG, "Negative code bytes in 'else' code block.");
        return;
    }
    if (newOffset == offset)
    {
        dbgWarningf(ETG, "Zero code bytes in 'else' code block.");
        return;
    }
#endif
    //get opcode pointers
    if (codeBlock == EPM_Startup)
    {
        opcode = (etgbranch *)(etgExecStack.etgCodeBlock[EPM_Startup].code + offset);
    }
    else if (codeBlock == EPM_EachFrame)
    {
        opcode = (etgbranch *)(etgExecStack.etgCodeBlock[EPM_EachFrame].code + offset);
    }
    else if (codeBlock == EPM_TimeIndex)
    {
        opcode = (etgbranch *)(etgExecStack.etgCodeBlock[EPM_TimeIndex].code + offset);
    }
    ifOpcode = (etgconditional *)userData;
    //make sure we've found a good opcode
    if (ifOpcode->opcode < EOP_EqualVCI || ifOpcode->opcode > EOP_NotZeroVF)
    {
        etgLoadErrorf(ETG, "'else' statement matches with an invalid 'if' (opcode %d)", ifOpcode->opcode);
        return;
    }
    ifOpcode->codeBytes += sizeof(etgbranch);
#if ETG_ERROR_CHECKING
    if (opcode->opcode != EOP_Goto)
    {
        etgLoadErrorf(ETG, "Cannot find else 'goto' opcode.  Found opcode %d instead.", opcode->opcode);
        return;
    }
    dbgAssert(opcode->branchTo == 0xffffffff);
#endif
    //set code length in opcode
    opcode->branchTo = newOffset;
}

//handle 'else'
sdword etgElse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgbranch *opcode = (etgbranch *)dest;
    sdword index;

    opcode->opcode = EOP_Goto;
    opcode->codeBlock = etgParseMode;
    opcode->branchTo = 0xffffffff;
    dbgAssert(etgParseMode >= 0 && etgParseMode < ETG_NumberCodeBlocks);
    index = etgExecStack.etgCodeBlock[etgParseMode].offset;
    etgNestFunctionSet(NULL, etgElseComplete, etgParseMode, index, (ubyte *)etgIfOpcode);//set function to be called later
    return(sizeof(etgbranch));
}

//handle the 'goto'
sdword etgLabelGoto(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgvarentry *label;
    etgbranch *opcode = (etgbranch *)dest;

    label = etgVariableFind(params);
    opcode->opcode = EOP_Goto;
    if (label == NULL)
    {                                                       //if label not found
        opcode->opcode |= EOP_Resolve;                      //resolve
        opcode->branchTo = (udword)memStringDupe(params);   //save name for later
    }
    else
    {
        opcode->branchTo = label->initial;
        opcode->codeBlock = label->codeBlock;
    }
#if ETG_ERROR_CHECKING
    if (label != NULL && label->type != EVT_Label)
    {
        etgLoadErrorf(ETG, "Symbol '%s' not a proper label name.", params);
        return(0);
    }
#endif
    return(sizeof(etgbranch));
}

//handle 'call'
sdword etgLabelCall(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgvarentry *label;
    etgbranch *opcode = (etgbranch *)dest;

    label = etgVariableFind(params);
    opcode->opcode = EOP_Call;
    if (label == NULL)
    {                                                       //if label not found
        opcode->opcode |= EOP_Resolve;                      //resolve
        opcode->branchTo = (udword)memStringDupe(params);   //save name for later
    }
    else
    {
        opcode->branchTo = label->offset;
        opcode->codeBlock = label->size;
    }
#if ETG_ERROR_CHECKING
    if (label != NULL && label->type != EVT_Label)
    {
        etgLoadErrorf(ETG, "Symbol '%s' not a proper label name.", params);
        return(0);
    }
#endif
    return(sizeof(etgbranch));
}

//handle return
sdword etgReturn(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgbranch *opcode = (etgbranch *)dest;
    opcode->opcode = EOP_Return;
    opcode->codeBlock = 0xffffffff;
    opcode->branchTo = 0xffffffff;
    return(sizeof(etgbranch));
}

//handle the 'label' operator
sdword etgNewLocalLabel(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    char *string;
    sdword offset = -1;
    sdword type;

    string = strtok(params, " \t.,][()");
#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\nCreating label '%s'", string);
#endif
    if (etgParseMode == EPM_Constant)
    {
        offset = stat->constLength;
        type = EVT_ConstLabel;
    }
    else if (etgParseMode == EPM_Variables)
    {
        offset = stat->variableSize;
        type = EVT_VarLabel;
    }
    else
    {
        offset = etgExecStack.etgCodeBlock[etgParseMode].offset;
        type = EVT_Label;
    }
    etgNewVariableCreate(string, stat, type, FALSE, offset, 0);
    return(0);
}

//handle the 'eventStart' label-function with name and optional parameters
sdword etgNewEventParse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    char *token;
    sdword opcodeLength;
    ubyte opcode[OPCODE_MAX];

//    token = strtok(params, ",");
#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_Nothing)
    {
        etgLoadErrorf(ETG, "'eventStart' encountered in a context block.");
        return(0);
    }
    if (etgVariableIndex != 0)
    {
        etgLoadErrorf(ETG, "'eventStart': %d variables already defined for this effect", etgVariableIndex);
        return(0);
    }
#endif

    //set variable mode
    etgParseMode = EPM_Variables;
    token = strtok(params, ",");
    while (token != NULL)
    {                                                       //remaining tokens will be parameters
        while (strchr(" \t", *token) != NULL)
        {                                                   //strip leading whitespace
            token++;
        }
        while (strlen(token) > 0 && token[strlen(token) - 1] == ' ')
        {
            token[strlen(token) - 1] = 0;                   //strip trailing whitespace
        }
        opcodeLength = 0;
        if (!etgOpcodeScan(stat, token, opcode, &opcodeLength))
        {                                                   //call scan function to create the variable
#if ETG_ERROR_CHECKING
            etgLoadErrorf(ETG, "Don't know how to handle '%s'", token);
            return(0);
#endif
        }
#if ETG_ERROR_CHECKING
        if (opcodeLength != 0)
        {                                                   //if an opcode was created
            etgLoadErrorf(ETG, "'%s' wasn't a variable definition", token);
            return(0);
        }
#endif
        token = strtok(NULL, ",");
    }
    //restore to nothing mode
    etgParseMode = EPM_Nothing;
    return(0);
}

//handle 'end', 'yield' and 'delete'
sdword etgEndOpcode(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgprocesscontrol *opcode = (etgprocesscontrol *)dest;
    opcode->opcode = EOP_End;
    return(sizeof(etgprocesscontrol));
}
sdword etgYeildOpcode(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgprocesscontrol *opcode = (etgprocesscontrol *)dest;
    opcode->opcode = EOP_Yeild;
    return(sizeof(etgprocesscontrol));
}
sdword etgDeleteOpcode(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgprocesscontrol *opcode = (etgprocesscontrol *)dest;
    opcode->opcode = EOP_Delete;
//    stat->bSelfDeleting = TRUE;
    bitSet(stat->specialOps, ESO_SelfDeleting);
    return(sizeof(etgprocesscontrol));
}

/*-----------------------------------------------------------------------------
    Name        : etgAlternateAdd
    Description : Allocate a new alternate structure and return it's index
    Inputs      : void
    Outputs     : Actual structure filled in later
    Return      : index of new alternate structre
----------------------------------------------------------------------------*/
udword etgAlternateAdd(void)
{
#if ETG_ERROR_CHECKING
    if (etgAlternateStructIndex > ETG_MaxAlternates)
    {
        etgLoadErrorf(ETG, "Exceeded %d alternates.", ETG_MaxAlternates);
        return(0);
    }
#endif
    etgAlternateStructIndex++;
    return(etgAlternateStructIndex - 1);
}

//complete a previously-started alternate block
void etgAlternateComplete(sdword codeBlock, sdword offset, sdword newOffset, ubyte *userData)
{
#if ETG_ERROR_CHECKING
    udword index;

    if (etgDecisionOpcode == NULL)
    {                                                       //if already creating a decision opcode
        etgLoadErrorf(ETG, "Unexpected '}' associated with 'alternate'.");
        return;
    }
    dbgAssert(etgDecisionOpcode->opcode == EOP_Alternate);
    dbgAssert(etgDecisionOpcode->codeBytes == 0xffffffff);
    for (index = 0; index < etgDecisionOpcode->tableLength; index++)
    {
        if (etgDecisionOpcode->offset[index] == 0xffffffff)
        {
            dbgWarningf(ETG, "Unallocated entry(s) in alternate table.");
            break;
        }
    }
#endif                                                      //set length of full alternate statement
    etgDecisionOpcode->codeBytes = newOffset - offset + sizeof(etgreturn);
    //the offset table will later be re-allocated into one big table
    etgTotalOffsets += etgDecisionOpcode->tableLength;
    etgAlternateTable[etgDecisionOpcode->criteria].alt.type = EAT_Int;
    etgAlternateTable[etgDecisionOpcode->criteria].alt.low = 0;
    etgAlternateTable[etgDecisionOpcode->criteria].alt.high = etgDecisionOpcode->tableLength;
    //now create a dummy return opcode to prevent stack overflow (remember how we pushed earlier?)
    ((etgreturn *)(etgExecStack.etgCodeBlock[etgParseMode].code + etgExecStack.etgCodeBlock[etgParseMode].offset))->opcode = EOP_Return;
    etgExecStack.etgCodeBlock[etgParseMode].offset += sizeof(EOP_Return);

    //now restore previous level of alternate's information
    etgDecisionOpcode = (etgdecision *)userData;
}

//create an alternate opcode
sdword etgAternateStart(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgdecision *opcode = (etgdecision *)dest, *oldOpcode;

#if ETG_ERROR_CHECKING
    if (etgDecisionOpcode != NULL)
    {                                                       //if already creating a decision opcode
//        etgLoadErrorf(ETG, "Cannot nest 'alternate' or 'case' instructions.");
        dbgMessagef("\nNesting alternate.");
    }
    if (etgParseMode < EPM_Startup || etgParseMode >= ETG_NumberCodeBlocks)
    {
        etgLoadErrorf(ETG, "'alternate' outside of valid code block: %d", etgParseMode);
        return(0);
    }
#endif
    oldOpcode = etgDecisionOpcode;
    opcode->opcode = EOP_Alternate;
    opcode->tableLength = 0;                                //no entries created yet
    opcode->criteria = etgAlternateAdd();                   //add a new alternate criteria
    opcode->codeBytes = 0xffffffff;                         //no code length yet
    opcode->offset = memAlloc(sizeof(decisionoffset) * ETG_DecisionMax, "Decision offset table", 0);

    etgDecisionOpcode = (etgdecision *)(etgExecStack.etgCodeBlock[etgParseMode].code +
                         etgExecStack.etgCodeBlock[etgParseMode].offset);//this is where this opcode will be stored
    etgNestFunctionSet(NULL, etgAlternateComplete, etgParseMode,  //set function to be called later
        etgExecStack.etgCodeBlock[etgParseMode].offset, (ubyte *)oldOpcode);
    etgAlternateTable[opcode->criteria].decision = etgDecisionOpcode;//save opcode reference for later

    return(sizeof(etgdecision));
}

//add a new alternate into the current alternate list
sdword etgAternateSet(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    sdword length, nEntries, index, nScanned;
    udword offset;
    etgreturn *branchOpcode = (etgreturn *)dest;

    nScanned = sscanf(params, "%d", &nEntries);             //scan size of this chunk of alternate table
#if ETG_ERROR_CHECKING
    if (etgDecisionOpcode == NULL)
    {
        etgLoadErrorf(ETG, "'alt' encountered outside an alternate block.");
        return(0);
    }
    if (nScanned != 1)
    {
        etgLoadErrorf(ETG, "Cannot scan '%s' for alternate entry length.", params);
        return(0);
    }
    if (nEntries < 1 || nEntries >= ETG_DecisionMax)
    {
        etgLoadErrorf(ETG, "Invalid alternate length: %d", nEntries);
        return(0);
    }
    dbgAssert(etgDecisionOpcode->opcode == EOP_Alternate);
    if (etgDecisionOpcode->tableLength + nEntries >= ETG_DecisionMax)
    {
        etgLoadErrorf(ETG, "%d alternates exceeds max of %d",
                  etgDecisionOpcode->tableLength + nEntries + 1, ETG_DecisionMax);
        return(0);
    }
#endif
    if (etgDecisionOpcode->tableLength != 0)
    {                                                       //if not first entry in the list
        branchOpcode->opcode = EOP_Return;                  //generate a return opcode
        length = sizeof(etgreturn);
    }
    else
    {
        length = 0;
    }
    offset = (ubyte *)etgDecisionOpcode - etgExecStack.etgCodeBlock[etgParseMode].code;
    for (index = 0; index < nEntries; index++)
    {                                                       //for each alternate entry
        etgDecisionOpcode->offset[etgDecisionOpcode->tableLength] =
            etgExecStack.etgCodeBlock[etgParseMode].offset - offset + length;//store the offset to current instruction
        etgDecisionOpcode->tableLength++;                   //add another entry to the list
    }
    return(length);
}

//handle the closing brace on a 'timeblock'
void etgTimeBlockComplete(sdword codeBlock, sdword offset, sdword newOffset, ubyte *userData)
{
#if ETG_ERROR_CHECKING
    dbgAssert(etgTimeIndexVar != NULL);
#endif
    etgTimeIndexVar = NULL;
}

//handle opening time index block ('timeblock')
sdword etgTimeBlockOpen(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
#if ETG_ERROR_CHECKING
    if (etgTimeIndexVar != NULL)
    {
        etgLoadErrorf(ETG, "Cannot nest 'timeblock's.");
        return(0);
    }
#endif
    etgTimeIndexVar = etgVariableFind(params);
#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_TimeIndex)
    {
        etgLoadErrorf(ETG, "'timeblock' can only appear in the 'timeindex' block.");
        return(0);
    }
    if (etgTimeIndexVar == NULL)
    {
        etgLoadErrorf(ETG, "'%s' is not a valid variable name.", params);
        return(0);
    }
    if (etgTimeIndexVar->type != EVT_Float && etgTimeIndexVar->type != EVT_Int &&
        etgTimeIndexVar->type != EVT_RGB && etgTimeIndexVar->type != EVT_RGBA)
    {
        etgLoadErrorf(ETG, "'timeblock' variable '%s' is of type %d.",
                  etgTimeIndexVar->name, etgTimeIndexVar->type);
        return(0);
    }
#endif
    if (etgTimeIndexVar->rateOffset == 0xffffffff)
    {                                                       //if this variable has no rate offset associated with it
        etgTimeIndexVar->rateOffset = stat->rateSize;
        switch (etgTimeIndexVar->type)
        {
            case EVT_Int:
            case EVT_Float:
                stat->rateSize += sizeof(udword);
                break;
            case EVT_RGB:
                stat->rateSize += sizeof(etgratergb);
                break;
            case EVT_RGBA:
                stat->rateSize += sizeof(etgratergba);
                break;
            default:
                dbgAssert(FALSE);
        }
    }
    etgNestFunctionSet(NULL, etgTimeBlockComplete, etgParseMode,  //set function to be called later
        etgExecStack.etgCodeBlock[etgParseMode].offset, NULL);
    etgTimeIndexTime = 0.0f;
    return(0);
}

//handle time index entries ('time')
//this is the only way to define one-point effectors
//generates two opcodes: a between and a time-index effector
sdword etgTimeIndexDefine(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    real32 time, valueReal;
    udword valueInt, nScanned;
    color valueRGBA;
    etgopcode opcode;
    etgbetween *betweenOpcode;
    etgseffector *effector;
    etgvarentry *endVar;
    char *param;

#if ETG_ERROR_CHECKING
    if (etgTimeIndexVar == NULL)
    {
        etgLoadErrorf(ETG, "'time' can only appear in a 'timeblock'.");
        return(0);
    }
    if (strchr(params, ',') == NULL)
    {
        etgLoadErrorf(ETG, "'time' operator requires a time and a value parameter. '%s' just won't do.", params);
        return(0);
    }
#endif
    //check the time
    nScanned = sscanf(params, "%f", &time);
#if ETG_ERROR_CHECKING
    if (nScanned != 1)
    {
        etgLoadErrorf(ETG, "Error scanning time from '%s''", params);
        return(0);
    }
    if (time < 0.0f || time > 200.0)
    {
        etgLoadErrorf(ETG, "Invalid time-index in '%s'", params);
        return(0);
    }
    if (time <= etgTimeIndexTime)
    {
        etgLoadErrorf(ETG, "Non-positive time delta from last time-index (%f <= %f).", time, etgTimeIndexTime);
        return(0);
    }
    if (etgTimeIndexIndex >= UDWORD_Max)
    {
        etgLoadErrorf(ETG, "Cannot have more than 255 time index effectors in a single 'timeindex' block.");
        return(0);
    }
#endif
    param = strchr(params, ',');
    while (strchr("\t ,", *param) != NULL)
    {
        param++;
    }
    //read the end-point value, whatever it's type may be
    if ((endVar = etgVariableFind(param)) != NULL)
    {                                                       //if var-current
#if ETG_ERROR_CHECKING
        if (etgTimeIndexVar->type != endVar->type)
        {
            etgLoadErrorf(ETG, "Variables '%s' and '%s' incompatable types for effectors.", etgTimeIndexVar->name, endVar->name);
            return(0);
        }
#endif
        valueInt = endVar->offset;                          //effector end parameter is now offset for variable
        switch (etgTimeIndexVar->type)
        {
            case EVT_Int:
                opcode = EOP_EffectorVI;
                break;
            case EVT_Float:
                opcode = EOP_EffectorVF;
                break;
            case EVT_RGB:
                opcode = EOP_EffectorVCo;
                break;
            case EVT_RGBA:
                opcode = EOP_EffectorVCa;
                break;
            default:
                dbgAssert(FALSE);
        }
    }
    else
    {                                                       //else const->current
        switch (etgTimeIndexVar->type)
        {
            case EVT_Int:
                nScanned = sscanf(param, "%d", &valueInt);
                opcode = EOP_EffectorCI;
                break;
            case EVT_Float:
                nScanned = sscanf(param, "%f", &valueReal);
                valueInt = TreatAsUdword(valueReal);
                opcode = EOP_EffectorCF;
                break;
            case EVT_RGB:
                valueRGBA = etgColorScan(param);
                valueInt = (udword)valueRGBA;
                opcode = EOP_EffectorCCo;
                break;
            case EVT_RGBA:
                valueRGBA = etgColorScan(param);
                valueInt = (udword)valueRGBA;
                opcode = EOP_EffectorCCa;
                break;
            default:
                dbgAssert(FALSE);
        }
#if ETG_ERROR_CHECKING
        if (nScanned != 1)
        {
            etgLoadErrorf(ETG, "Error value from '%s''", param);
            return(0);
        }
#endif
    }
    //create the between opcode
    betweenOpcode = (etgbetween *)dest;
    betweenOpcode->opcode = EOP_Between;
    betweenOpcode->start = etgTimeIndexTime;
    betweenOpcode->end = time;
    betweenOpcode->startVar = betweenOpcode->endVar = 0xffffffff;
    betweenOpcode->codeBytes = sizeof(etgbetween) + sizeof(etgseffector);
    //create the effector opcode
    effector = (etgseffector *)(dest + sizeof(etgbetween));
    effector->opcode = opcode;
    effector->end = valueInt;
    effector->effector = etgTimeIndexVar->offset;
    effector->rate = etgTimeIndexVar->rateOffset;
    effector->effectorID = (ubyte)etgTimeIndexIndex;
    etgTimeIndexIndex++;

    etgTimeIndexTime = time;                                //update the current timeblock time
    return(sizeof(etgbetween) + sizeof(etgseffector));      //size of 2 opcodes
}


//create an 'at' block
void etgAtComplete(sdword codeBlock, sdword offset, sdword newOffset, ubyte *userData)
{
    etgbeforeafterat *opcode;
    //get opcode pointer
    opcode = (etgbeforeafterat *)(etgExecStack.etgCodeBlock[codeBlock].code + offset);
    dbgAssert(opcode->codeBytes == 0xffffffff);
    dbgAssert(codeBlock == etgParseMode);
    opcode->codeBytes = newOffset - offset;
}

//complete the 'between' block
void etgBetweenComplete(sdword codeBlock, sdword offset, sdword newOffset, ubyte *userData)
{
    etgbetween *opcode;
    //get opcode pointer
    opcode = (etgbetween *)(etgExecStack.etgCodeBlock[codeBlock].code + offset);
    dbgAssert(opcode->codeBytes == 0xffffffff);
    dbgAssert(codeBlock == etgParseMode);
    opcode->codeBytes = newOffset - offset;
}

sdword etgBetweenDefine(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgbetween *opcode = (etgbetween *)dest;
    sdword nScanned, index;
    etgvarentry *var;
    real32 time;
    udword varOffset;
    char *param;

    opcode->opcode = EOP_Between;

    param = strtok(params, ETG_TokenDelimiters);

    for (index = 0; index < 2; index++)
    {                                                       //start/end parameters
        time = -1.0f;
        varOffset = 0xffffffff;
        if (etgIsNumber(param))
        {
            nScanned = sscanf(param, "%f", &time);
#if ETG_ERROR_CHECKING
            if (nScanned != 1)
            {
                etgLoadErrorf(ETG, "Error scanning '%s' for time.", param);
                return(0);
            }
            if (time < 0 || time > 1000)
            {
                etgLoadErrorf(ETG, "Invalid time '%f' for '%s'.", time, opcodeString);
                return(0);
            }
#endif
        }
        else
        {                                                   //else it's a parameter
            var = etgVariableFind(param);
#if ETG_ERROR_CHECKING
            if (var == NULL)
            {
                etgLoadErrorf(ETG, "Variable '%s' not found.", params);
                return(0);
            }
            if (var->type != EVT_Float)
            {
                etgLoadErrorf(ETG, "Variable '%s' not of type float.  Type %d instead", params, var->type);
                return(0);
            }
#endif
            varOffset = var->offset;
        }
        if (index == 0)
        {                                                   //if defining the start time
            opcode->start = time;
            opcode->startVar = varOffset;
        }
        else
        {                                                   //else defining the end time
            opcode->end = time;
            opcode->endVar = varOffset;
        }
        param = strtok(NULL, ETG_TokenDelimiters);
    }
#if ETG_ERROR_CHECKING
    if (etgParseMode != EPM_EachFrame && etgParseMode != EPM_TimeIndex)
    {
        etgLoadErrorf(ETG, "'%s' must appear in timeIndex or eachFrame blocks", opcodeString);
        return(0);
    }
    if (opcode->start >= opcode->end)
    {
        etgLoadErrorf(ETG, "'%s' - end time must be greater than start time (%.2f >= %.2f", opcode->start, opcode->end);
        return(0);
    }
#endif
    opcode->codeBytes = 0xffffffff;

    etgNestFunctionSet(NULL, etgBetweenComplete, etgParseMode,  //set function to be called later
        etgExecStack.etgCodeBlock[etgParseMode].offset, NULL);
    return(sizeof(etgbetween));
}
//handle 'before', 'every', 'after' and 'at'
sdword etgAtDefine(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgbeforeafterat *opcode = (etgbeforeafterat *)dest;
    sdword nScanned;
    etgvarentry *var;
    real32 timeDiff;

    if (etgParseNamesMatch(opcodeString, "at"))
    {
        opcode->opcode = EOP_At;
    }
    else if (etgParseNamesMatch(opcodeString, "before"))
    {
        opcode->opcode = EOP_Before;
    }
    else if (etgParseNamesMatch(opcodeString, "after"))
    {
        opcode->opcode = EOP_After;
    }
    else
    {
        opcode->opcode = EOP_Every;
    }
    if (etgIsNumber(params))
    {
        nScanned = sscanf(params, "%f", &opcode->time);
#if ETG_ERROR_CHECKING
        if (etgParseMode != EPM_EachFrame && etgParseMode != EPM_TimeIndex)
        {
            etgLoadErrorf(ETG, "'%s' must appear in timeIndex or eachFrame blocks", opcodeString);
            return(0);
        }
        if (nScanned != 1)
        {
            etgLoadErrorf(ETG, "Error scanning '%s' for time.", params);
            return(0);
        }
        if (opcode->time < 0 || opcode->time > 1000)
        {
            etgLoadErrorf(ETG, "Invalid time '%f' for '%s'.", opcode->time, opcodeString);
            return(0);
        }
#endif
        if (opcode->opcode == EOP_At)
        {
            timeDiff = (real32)fmod((double)opcode->time, (double)ETG_UpdateRoundOff);
            if (timeDiff > ETG_UpdateRoundOff / 2.0f)
            {
                opcode->time -= timeDiff - ETG_UpdateRoundOff / 2.0f;
            }
            else
            {
                opcode->time += ETG_UpdateRoundOff / 2.0f - timeDiff;
            }
        }
    }
    else
    {
        var = etgVariableFind(params);
#if ETG_ERROR_CHECKING
        if (etgParseMode != EPM_EachFrame && etgParseMode != EPM_TimeIndex)
        {
            etgLoadErrorf(ETG, "'%s' must appear in timeIndex or eachFrame blocks", opcodeString);
            return(0);
        }
        if (var == NULL)
        {
            etgLoadErrorf(ETG, "Variable '%s' not found.", params);
            return(0);
        }
        if (var->type != EVT_Float)
        {
            etgLoadErrorf(ETG, "Variable '%s' not of type float.  Type %d instead", params, var->type);
            return(0);
        }
#endif
        opcode->opcode++;                                   //the corresponding variable version should be 1 greater than the constant version
        ((etgbeforeafteratvar *)opcode)->variable = var->offset;
    }
    opcode->codeBytes = 0xffffffff;
    etgNestFunctionSet(NULL, etgAtComplete, etgParseMode,  //set function to be called later
        etgExecStack.etgCodeBlock[etgParseMode].offset, NULL);
    return(sizeof(etgbeforeafterat));
}
//handle the 'particleBlocks' operator
sdword etgNParticleBlocksSet(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    sdword nScanned;

    nScanned = sscanf(params, "%d", &stat->nParticleBlocks);
#if ETG_ERROR_CHECKING
    if (nScanned != 1)
    {
        etgLoadErrorf(ETG, "Error scanning number of particle blocks from '%s'", params);
        return(0);
    }
#endif
    return(0);
}

//handle the 'maxFrequency' operator
sdword etgNHistorySet(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    sdword nScanned;

    nScanned = sscanf(params, "%d", &stat->nHistoryList);
#if ETG_ERROR_CHECKING
    if (nScanned != 1)
    {
        etgLoadErrorf(ETG, "Error scanning number of particle blocks from '%s'", params);
        return(0);
    }
#endif
    return(0);
}

//handle the 'spawn' operator.  Creates a special function call opcode
sdword etgSpawnParse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgfunctioncall *call = (etgfunctioncall *)dest;
    char *param;
    udword nParams = 2, nScanned;
    etgeffectstatic *spawnEffect;
    etgvarentry *var;

    param = strtok(params, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (param == NULL || strlen(param) < 4)
    {
        etgLoadErrorf(ETG, "Syntax error.  Supposed to be: spawn(<name>, <parameter> [, parameters...]");
        return(0);
    }
#endif
    spawnEffect = etgEffectStaticFind(param, TRUE);
    call->parameter[0].type = EVT_Constant;
    call->parameter[0].param = (udword)spawnEffect;

    call->opcode = EOP_Function;
    call->function = (udword (*)(void))etgSpawnNewEffect;
    call->returnValue = 0xffffffff;
    call->passThis = TRUE;
    while((param = strtok(NULL, ETG_TokenDelimiters)) != NULL)
    {
        if (etgIsNumber(param))
        {                                                   //if it's a number parameter
            call->parameter[nParams].type = EVT_Constant;
            if (strchr(param, '.'))
            {                                               //if it's a floating-point
                nScanned = sscanf(param, "%f", &call->parameter[nParams].param);
            }
            else
            {                                               //else it's an integer
                nScanned = sscanf(param, "%d", &call->parameter[nParams].param);
            }
#if ETG_ERROR_CHECKING
            if (nScanned != 1)
            {
                etgLoadErrorf(ETG, "Error parsing spawn parameter '%s'", param);
                return(0);
            }
#endif
        }
        else
        {                                                   //else it's a varaible parameter
            var = etgVariableFind(param);
#if ETG_ERROR_CHECKING
            if (var == NULL)
            {
                etgLoadErrorf(ETG, "Variable '%s' not found - check your head.", param);
                return(0);
            }
#endif
            switch (var->type)
            {
#if ETG_ERROR_CHECKING
                case EVT_Label:
                    etgLoadErrorf(ETG, "Cannot use label '%s' as a parameter.", param);
                    return(0);
                    break;
#endif
                case EVT_ConstLabel:
                case EVT_VarLabel:
                    call->parameter[nParams].param = var->initial;
                    break;                                  //assigning a constant
                default:
                    call->parameter[nParams].type = (udword)var->type;
                    call->parameter[nParams].param = var->offset;
            }
            call->parameter[nParams].type = (udword)var->type;
        }
        nParams++;
    }
    call->parameter[1].type = EVT_Constant;                 //second parameter is number of user parameters to pass
    call->parameter[1].param = nParams - 2;
    call->nParameters = nParams;
    return(etgFunctionSize(nParams));
}

/*-----------------------------------------------------------------------------
    Name        : etgCreationCallback
    Description : particle-creation callback to execute a piece of p-code
    Inputs      : userValue - offset to start execution at.
                  userData - effect we're working on
    Outputs     : executes some p-code, whatever that will do
    Return      :
----------------------------------------------------------------------------*/
void etgCreationCallback(sdword userValue, ubyte *userData)
{
    sdword codeBlock, offset;
    udword opcode;
    sdword size;
    ubyte *pOpcode;
#define effect          ((Effect *)userData)
    etgeffectstatic *stat = (etgeffectstatic *)effect->staticinfo;

    //save the code block info for later restoration
    codeBlock = etgExecStack.etgCodeBlockIndex;
    offset = etgExecStack.etgCodeBlock[codeBlock].offset;
    //set the current code offset to the start of the callback code block
    etgExecStack.etgCodeBlock[codeBlock].offset = (udword)userValue;
    //execute this little chunk of code until we find an end code
    while (etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset <
           etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].length)
    {
        pOpcode = etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].code + etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset;
        opcode = *((udword *)pOpcode);                      //get an opcode
#if ETG_ERROR_CHECKING
        if (opcode < 0 || opcode >= EOP_LastOp)
        {
            dbgFatalf(DBG_Loc, "Effect '%s' has a bad opcode in code segment %d offset %d", stat->name, etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].code, etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset);
        }
        if (etgHandleTable[opcode].function == NULL)
        {
            dbgFatalf(DBG_Loc, "etgEffectCodeExecute: NULL opcode %d", opcode);
        }
#endif
        //execute the opcode
        size = etgHandleTable[opcode].function(effect, stat, pOpcode);
        etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset += size;
    }
    //restore the code block info
    etgExecStack.etgCodeBlockIndex = codeBlock;
    etgExecStack.etgCodeBlock[codeBlock].offset = offset;
    //p-code back to normal, keep creating particles
#undef effect
}

/*-----------------------------------------------------------------------------
    Name        : etgCreateCallbackSetup
    Description : Called just before a particle creation, sets the particle-
                    creation callback.
    Inputs      : codeOffset - offset to p-code to execute
    Outputs     : just sets callback to the correct p-code
    Return      : void
----------------------------------------------------------------------------*/
void etgCreateCallbackSetup(Effect *effect, sdword codeOffset)
{
    partCreateCallbackSet(etgCreationCallback, codeOffset, (ubyte *)effect  );
}

/*-----------------------------------------------------------------------------
    Name        : etgCallbackOpen
    Description : Open the callback code block for particle creation.
    Inputs      : About the same as for block close functions.
    Outputs     : Creates some new opcodes: a call to partCreateCallbackSet and
                    a branch.  Here is an example of a particle creation callback
                    code and corresponding p-code representation:
    createCircles(1, 0)         EOP_Function(partCreateCallbackSet), EOP_Function(createCircles)
    {                       +---branch
        alternate {...}     |       alternate
        ...other code       |       ...other code
    }                       |   end
    ..continue with code    +-->..continue with p-code
    Return      : void
----------------------------------------------------------------------------*/
void etgCallbackOpen(sdword codeBlock, sdword offset, ubyte *userData)
{
    etgfunctioncall *call;                       //we can create a local call because it only has 1 parameter
    etgbranch *branch;                           //branch to skip over the alternate code
#if ETG_ERROR_CHECKING
    etgfunctioncall *createCall;
    //first, let's take move the existing function opcode for the create()
    //function forward enough to fit the call to partCreateCallbackSet
    createCall = (etgfunctioncall *)userData;
    dbgAssert(createCall->opcode == EOP_Function && createCall->nParameters == 2);
#endif
    memcpy(userData + etgFunctionSize(1), userData, etgFunctionSize(2));//move the function forward
    //set up the call to partCreateCallbackSet()
    call = (etgfunctioncall *)userData;
    call->opcode = EOP_Function;
    call->function = (udword (*)(void))etgCreateCallbackSetup;
    call->passThis = TRUE;
    call->returnValue = 0xffffffff;
    call->nParameters = 1;
    call->parameter[0].param = (udword)etgExecStack.etgCodeBlock[etgParseMode].offset +
            etgFunctionSize(1) + sizeof(etgbranch);         //callback should skip these functions and the branch
    call->parameter[0].type = EVT_Constant;
    etgExecStack.etgCodeBlock[etgParseMode].offset += etgFunctionSize(1);//update for size of function call
    //now create the branch opcode
    branch = (etgbranch *)(userData + etgFunctionSize(2) + etgFunctionSize(1));
    dbgAssert(etgNestLevel >= 1);                           //ensure sanity
    etgNestStack[etgNestLevel - 1].userData = (ubyte *)branch;//set new callback user data for when we're closing the code block
    branch->opcode = EOP_Goto;
    branch->codeBlock = codeBlock;
    branch->branchTo = 0xffffffff;
    etgExecStack.etgCodeBlock[etgParseMode].offset += sizeof(etgbranch);//update for size of branch
}

/*-----------------------------------------------------------------------------
    Name        : etgCallbackClose
    Description : Close a particle creation call-back code block
    Inputs      : standard except for -
                  userData - pointer to the branch opcode at the start of the block
    Outputs     : updates the branch opcode
    Return      :
----------------------------------------------------------------------------*/
void etgCallbackClose(sdword codeBlock, sdword offset, sdword newOffset, ubyte *userData)
{
    etgbranch *branch = (etgbranch *)userData;
    sdword *offsetVar;
    etgprocesscontrol *opcode;

    //create an end opcode at end of block
    dbgAssert(etgParseMode >= 0 && etgParseMode < ETG_NumberCodeBlocks);
    offsetVar = &etgExecStack.etgCodeBlock[etgParseMode].offset;
    opcode = (etgprocesscontrol *)(etgExecStack.etgCodeBlock[etgParseMode].code + *offsetVar);
    opcode->opcode = EOP_End;                               //create the opcode
    *offsetVar += sizeof(etgprocesscontrol);                //and update pointer

    //update the branch opcode as start of block
    dbgAssert(branch->opcode == EOP_Goto || branch->branchTo == 0xffffffff);
    branch->branchTo = newOffset + sizeof(etgprocesscontrol); //fill in the branch opcode
}

//handle a variety of 'createXXX' functions; all but 'createEffects'.  This has
//a special-case parsing function to handle particle-creation call-back code.
void etgCreationResolve(struct etgeffectstatic *stat, etgfunctioncall *call)
{
    etgNestFunctionSet(etgCallbackOpen, etgCallbackClose, etgParseMode,//set callbacks to structure the callback code
        etgExecStack.etgCodeBlock[etgParseMode].offset,
        (ubyte *)(etgExecStack.etgCodeBlock[etgParseMode].code +
                  etgExecStack.etgCodeBlock[etgParseMode].offset));
}

//handle 'etgDepthWrite'
void etgDepthWriteResolve(struct etgeffectstatic *stat, etgfunctioncall *call)
{
    if (call->parameter[0].type == EVT_Constant && call->parameter[0].param == 0)
    {                                                       //if setting no depth write
        bitSet(stat->specialOps, ESO_SortForward);          //this effect has to sort forward
    }
}

//handle the 'createEffects' operator.  Creates a special function call opcode
//parameters are: this, static, number, variance, nParams, ...
sdword etgCreateEffectsParse(struct etgeffectstatic *stat, ubyte *dest, char *opcodeString, char *params, char *ret)
{
    etgfunctioncall *call = (etgfunctioncall *)dest;
    char *param;
    udword nParams = 4, nScanned, index, value;
    etgeffectstatic *spawnEffect;
    etgvarentry *var;

    param = strtok(params, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
    if (param == NULL || strlen(param) < 4)
    {
        etgLoadErrorf(ETG, "Syntax error.  Supposed to be: spawn(<name>, <parameter> [, parameters...]");
        return(0);
    }
#endif
    spawnEffect = etgEffectStaticFind(param, TRUE);
    call->parameter[0].type = EVT_Constant;
    call->parameter[0].param = (udword)spawnEffect;

    //scan in number/distribution of effect-particles
    for (index = 1; index <= 2; index++)
    {
        param = strtok(NULL, ETG_TokenDelimiters);
#if ETG_ERROR_CHECKING
        if (param == NULL)
        {
            etgLoadErrorf(ETG, "Syntax error.  Supposed to be: %s(<name>, <parameter> [, parameters...]", opcodeString);
            return(0);
        }
#endif
        if (etgIsNumber(param))
        {
            nScanned = sscanf(param, "%d", &value);
#if ETG_ERROR_CHECKING
            if (nScanned != 1 || value < 0 || value > 1000)
            {
                etgLoadErrorf(ETG, "Error scanning '%s' for number of effects/distribution.", param);
                return(0);
            }
#endif
            call->parameter[index].type = EVT_Constant;
            call->parameter[index].param = value;
        }
        else
        {
            var = etgVariableFind(param);
#if ETG_ERROR_CHECKING
            if (var == NULL)
            {
                etgLoadErrorf(ETG, "Cannot find variable '%s'", param);
                return(0);
            }
            if (var->type != EVT_Int)
            {
                etgLoadErrorf(ETG, "Variable '%s' not of type integer.", param);
                return(0);
            }
#endif
            call->parameter[index].type = EVT_Int;
            call->parameter[index].param = var->offset;
        }
    }
    call->opcode = EOP_Function;
    call->function = (udword (*)(void))etgCreateEffects;
    call->returnValue = 0xffffffff;
    call->passThis = TRUE;
    while((param = strtok(NULL, ETG_TokenDelimiters)) != NULL)
    {
        if (etgIsNumber(param))
        {                                                   //if it's a number parameter
            call->parameter[nParams].type = EVT_Constant;
            if (strchr(param, '.'))
            {                                               //if it's a floating-point
                nScanned = sscanf(param, "%f", &call->parameter[nParams].param);
            }
            else
            {                                               //else it's an integer
                nScanned = sscanf(param, "%d", &call->parameter[nParams].param);
            }
#if ETG_ERROR_CHECKING
            if (nScanned != 1)
            {
                etgLoadErrorf(ETG, "Error parsing spawn parameter '%s'", param);
                return(0);
            }
#endif
        }
        else
        {                                                   //else it's a varaible parameter
            var = etgVariableFind(param);
#if ETG_ERROR_CHECKING
            if (var == NULL)
            {
                etgLoadErrorf(ETG, "Variable '%s' not found - check your head.", param);
                return(0);
            }
#endif
            switch (var->type)
            {
#if ETG_ERROR_CHECKING
                case EVT_Label:
                    etgLoadErrorf(ETG, "Cannot use label '%s' as a parameter.", param);
                return(0);
                    break;
#endif
                case EVT_ConstLabel:
                case EVT_VarLabel:
                    call->parameter[nParams].param = var->initial;
                    break;                                  //assigning a constant
                default:
                    call->parameter[nParams].type = (udword)var->type;
                    call->parameter[nParams].param = var->offset;
            }
            call->parameter[nParams].type = (udword)var->type;
        }
        nParams++;
    }
    call->parameter[3].type = EVT_Constant;                 //4th parameter is number of user parameters to pass
    call->parameter[3].param = nParams - 4;
    call->nParameters = nParams;
    return(etgFunctionSize(nParams));
}

/*-----------------------------------------------------------------------------
    Effect opcode processing:
-----------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------
    Name        :
    Description : Effect-opcode processing functions have this general format:
    Inputs      : effect - pointer to the effect we're processing
                  effectStatic - pointer to effect static info (needed?)
                  opcode - pointer to opcode structure
    Outputs     : Processes opcode in an opcode-specific way
    Return      : Size of opcode structure (amount to update code pointer)
----------------------------------------------------------------------------*/
sdword etgNOP(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    //nop's don't do much
    return(sizeof(etgnop));
}

//handle variable copy
sdword etgVarCopy(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    *((udword *)(effect->variable + ((etgvariablecopy *)opcode)->dest)) =
        *((udword *)(effect->variable + ((etgvariablecopy *)opcode)->source));
    return(sizeof(etgvariablecopy));
}

//handle variable assign
sdword etgVarAssign(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    *((udword *)(effect->variable + ((etgvariablecopy *)opcode)->dest)) = ((etgvariablecopy *)opcode)->source;
    return(sizeof(etgvariablecopy));
}

//handle function calls
sdword etgFunctionCall(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword param, nParams, returnType;
    sdword index;

    nParams = ((etgfunctioncall *)opcode)->nParameters;
    returnType = ((etgfunctioncall *)opcode)->returnValue;
    for (index = (sdword)nParams - 1; index >= 0; index--)
    {                                                       //for each parameter
        param = ((etgfunctioncall *)opcode)->parameter[index].param;
        switch (((etgfunctioncall *)opcode)->parameter[index].type)
        {
            case EVT_Constant:
                break;
            case EVT_Label:
                dbgAssert(FALSE);
                break;
            case EVT_ConstLabel:
                param = (udword)stat->constData + param;
                break;
            case EVT_VarLabel:
                param = (udword)effect->variable + param;
                break;
            default:
                param = *((udword *)(effect->variable + param));
                break;
        }
//      if (((etgfunctioncall *)opcode)->parameter[index].type != EVT_Constant)
//      {
//          param = *((udword *)(effect->variable + param));
//      }
        _asm                                                //push it onto the stack
        {
            mov eax, param
            push eax
        }
    }
    if (((etgfunctioncall *)opcode)->passThis)
    {                                                       //pass a 'this' pointer
        _asm
        {
            mov eax, effect
            push eax
        }
    }
    param = ((etgfunctioncall *)opcode)->function();        //call the function
    if (returnType != 0xffffffff)                           //if a return value is desired
    {
        *((udword *)(effect->variable + returnType)) = param;//set the return parameter
    }
    return(etgFunctionSize(nParams));
}

//handle 'end'
sdword etgEnd(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    return(0x100000);                                       //!!!this should put a stop to the execution
}

//handle 'delete'
sdword etgDelete(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    etgExecStack.etgDeleteFlag = TRUE;                      //make it delete itself
    return(0x100000);                                       //!!!this should put a stop to the execution
}

//handle 'if'
sdword etgCompareVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var;

    var = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var == ((etgconditional *)opcode)->param1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgNotEqualVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var;

    var = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var != ((etgconditional *)opcode)->param1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgGreaterVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var;

    var = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var > ((etgconditional *)opcode)->param1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgGreaterEqualVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var;

    var = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var >= ((etgconditional *)opcode)->param1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgLessVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var;

    var = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var < ((etgconditional *)opcode)->param1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgLessEqualVCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var;

    var = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var <= ((etgconditional *)opcode)->param1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

//handle 'if'
sdword etgCompareVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    float var;

    var = *((float *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var == TreatAsReal32(((etgconditional *)opcode)->param1))
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgNotEqualVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    float var;

    var = *((float *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var != TreatAsReal32(((etgconditional *)opcode)->param1))
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgGreaterVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    float var;

    var = *((float *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var > TreatAsReal32(((etgconditional *)opcode)->param1))
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgGreaterEqualVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    float var;

    var = *((float *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var >= TreatAsReal32(((etgconditional *)opcode)->param1))
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgLessVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    float var;

    var = *((float *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var < TreatAsReal32(((etgconditional *)opcode)->param1))
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgLessEqualVCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    float var;

    var = *((float *)(effect->variable + ((etgconditional *)opcode)->param0));
    if (var <= TreatAsReal32(((etgconditional *)opcode)->param1))
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgEqualVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var0, var1;

    var0 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 == var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgNotEqualVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var0, var1;

    var0 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 != var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgGreaterVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var0, var1;

    var0 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 > var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgGreaterEqualVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var0, var1;

    var0 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 >= var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgLessVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var0, var1;

    var0 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 < var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgLessEqualVVI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword var0, var1;

    var0 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((udword *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 <= var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgEqualVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 var0, var1;

    var0 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 == var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgNotEqualVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 var0, var1;

    var0 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 != var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgGreaterVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 var0, var1;

    var0 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 > var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgGreaterEqualVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 var0, var1;

    var0 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 >= var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgLessVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 var0, var1;

    var0 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 < var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

sdword etgLessEqualVVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 var0, var1;

    var0 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param0));
    var1 = *((real32 *)(effect->variable + ((etgconditional *)opcode)->param1));
    if (var0 <= var1)
    {                                                       //if compare succeeds
        return(sizeof(etgconditional));                     //just size of this opcode
    }                                                       //else condition failed
    return(((etgconditional *)opcode)->codeBytes);
}

//handle 'goto' or automatically-generated branches
sdword etgBranchTo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    etgbranch *branchTo = (etgbranch *)opcode;
    etgExecStack.etgCodeBlockIndex = branchTo->codeBlock;
    etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset = branchTo->branchTo;
    return(0);
}

//handle 'call' or automatically-generated branches
#define ETG_CallStackSize       20
sdword etgCallStackIndex = 0;
etgcallstackentry etgCallStack[ETG_CallStackSize];
sdword etgCallTo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    etgbranch *branchTo = (etgbranch *)opcode;
#if ETG_ERROR_CHECKING
    if (etgCallStackIndex >= ETG_CallStackSize)
    {
        dbgFatalf(DBG_Loc, "Overflowed ETG call stack of depth %d.", ETG_CallStackSize);
    }
#endif
    etgCallStack[etgCallStackIndex].codeBlock = etgExecStack.etgCodeBlockIndex;
    etgCallStack[etgCallStackIndex].codeOffset = etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset + sizeof(etgbranch);
    etgCallStackIndex++;
    etgExecStack.etgCodeBlockIndex = branchTo->codeBlock;
    etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset = branchTo->branchTo;
    return(0);
}

//handle 'return' by popping a member off the call stack
sdword etgReturnTo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
#if ETG_ERROR_CHECKING
    if (etgCallStackIndex < 1)
    {
        dbgFatal(DBG_Loc, "Underflowed ETG call stack.");
    }
#endif
    etgCallStackIndex--;
    etgExecStack.etgCodeBlockIndex = etgCallStack[etgCallStackIndex].codeBlock;
    etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset = etgCallStack[etgCallStackIndex].codeOffset;
    return(0);
}

//handle 'alternate' by pushing the address of the end of the alternate block onto
//the call stack and then selecting the proper alternate from the offset table.
//When the alternate code is finished, the return will return it to the end of
//the alternate block, rather than the start
sdword etgAlternate(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword alt;
    etgalternate *decision;

#if ETG_ERROR_CHECKING
    if (etgCallStackIndex >= ETG_CallStackSize)
    {
        dbgFatalf(DBG_Loc, "Overflowed ETG call stack of depth %d.", ETG_CallStackSize);
    }
#endif
    etgCallStack[etgCallStackIndex].codeBlock = etgExecStack.etgCodeBlockIndex;
    etgCallStack[etgCallStackIndex].codeOffset =            //prepare the reture address
        etgExecStack.etgCodeBlock[etgExecStack.etgCodeBlockIndex].offset + ((etgdecision *)opcode)->codeBytes;
    etgCallStackIndex++;                                    //push it onto stack
//    alt = effect->alternate[((etgdecision *)opcode)->criteria];//get alternate number
    decision = &stat->decisions[((etgdecision*)opcode)->criteria];
    alt = ranRandom(RAN_ETG) % (decision->high - decision->low) + decision->low;
    dbgAssert(alt < ((etgdecision *)opcode)->tableLength);
    return(((etgdecision *)opcode)->offset[alt]);
}

//handle 'between' operator
real32 etgEffectorDuration;
sdword etgBetween(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 start, end;

    if (((etgbetween *)opcode)->startVar != 0xffffffff)
    {
        start = *((real32 *)(effect->variable + ((etgbetween *)opcode)->startVar));
    }
    else
    {
        start = ((etgbetween *)opcode)->start;
    }
    if (((etgbetween *)opcode)->endVar != 0xffffffff)
    {
        end = *((real32 *)(effect->variable + ((etgbetween *)opcode)->endVar));
    }
    else
    {
        end = ((etgbetween *)opcode)->end;
    }
    dbgAssert(start < end);
    if (etgTotalTimeElapsed >= start && etgTotalTimeElapsed < end)
    {                                                       //if within the time constraints
        etgEffectorDuration = end - start;
        return(sizeof(etgbetween));                         //return size of this structure
    }
    return(((etgbetween *)opcode)->codeBytes);              //else return the amount to skip
}

//handle 'at' operator
sdword etgAt(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 timeDiff;

    timeDiff = etgTotalTimeElapsed - ((etgbeforeafterat *)opcode)->time;

    if (timeDiff >= 0.0000005f && timeDiff < etgTimeElapsed + 0.0000005f)
    {                                                       //if this is the frame
        return(sizeof(etgbeforeafterat));                   //just skip over this opcode
    }
    return(((etgbeforeafterat *)opcode)->codeBytes);        //skip over entire block
}

//handle 'at' operator with a variable for a parameter
sdword etgAtVar(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 timeDiff;
    real32 variable;

    variable = *((real32 *)(effect->variable + ((etgbeforeafteratvar *)opcode)->variable));
#if ETG_ERROR_CHECKING
    if (variable <= 0.0f || variable > 10000.0f)
    {
        dbgFatalf(DBG_Loc, "Invalid 'at' variable set to %.4f", variable);
    }
#endif
    timeDiff = etgTotalTimeElapsed - variable;

    if (timeDiff >= 0.0000005f && timeDiff < etgTimeElapsed + 0.0000005f)
    {                                                       //if this is the frame
        return(sizeof(etgbeforeafterat));                   //just skip over this opcode
    }
    return(((etgbeforeafterat *)opcode)->codeBytes);        //skip over entire block
}

//handle the 'before' operator
sdword etgBefore(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    if (((etgbeforeafterat *)opcode)->time > etgTotalTimeElapsed)
    {                                                       //if it is before
        return(sizeof(etgbeforeafterat));                   //just skip over this opcode
    }
    return(((etgbeforeafterat *)opcode)->codeBytes);        //skip over entire block
}
sdword etgBeforeVar(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 variable;

    variable = *((real32 *)(effect->variable + ((etgbeforeafteratvar *)opcode)->variable));
#if ETG_ERROR_CHECKING
    if (variable <= 0.0f || variable > 10000.0f)
    {
        dbgFatalf(DBG_Loc, "Invalid 'at' variable set to %.4f", variable);
    }
#endif
    if (variable > etgTotalTimeElapsed)
    {                                                       //if it is before
        return(sizeof(etgbeforeafterat));                   //just skip over this opcode
    }
    return(((etgbeforeafterat *)opcode)->codeBytes);        //skip over entire block
}

//handle the 'after' operator
sdword etgAfter(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    if (((etgbeforeafterat *)opcode)->time <= etgTotalTimeElapsed)
    {                                                       //if it is before
        return(sizeof(etgbeforeafterat));                   //just skip over this opcode
    }
    return(((etgbeforeafterat *)opcode)->codeBytes);        //skip over entire block
}
sdword etgAfterVar(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    real32 variable;

    variable = *((real32 *)(effect->variable + ((etgbeforeafteratvar *)opcode)->variable));
#if ETG_ERROR_CHECKING
    if (variable <= 0.0f || variable > 10000.0f)
    {
        dbgFatalf(DBG_Loc, "Invalid 'at' variable set to %.4f", variable);
    }
#endif
    if (variable <= etgTotalTimeElapsed)
    {                                                       //if it is before
        return(sizeof(etgbeforeafterat));                   //just skip over this opcode
    }
    return(((etgbeforeafterat *)opcode)->codeBytes);        //skip over entire block
}


//handle 'every' operator for constants and variables
#define ETG_FrameScale              65536
sdword etgEvery(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword time, timeElapsed, totalTime;

    time = (udword)(((etgbeforeafterat *)opcode)->time * ETG_FrameScale);
    timeElapsed = (udword)(etgTimeElapsed * ETG_FrameScale);
    totalTime = (udword)(etgTotalTimeElapsed * ETG_FrameScale);

    if (totalTime % time < timeElapsed)
    {                                                       //if this is the frame
        return(sizeof(etgbeforeafterat));                   //just skip over this opcode
    }
    return(((etgbeforeafterat *)opcode)->codeBytes);        //skip over entire block
}
sdword etgEveryVar(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword time, timeElapsed, totalTime;
    real32 variable;

    variable = *((real32 *)(effect->variable + ((etgbeforeafteratvar *)opcode)->variable));
#if ETG_ERROR_CHECKING
    if (variable <= 0 || variable > 10)
    {
        dbgFatalf(DBG_Loc, "Invalid 'at' variable set to %.4f", variable);
    }
#endif
    time = (udword)(variable * ETG_FrameScale);
    timeElapsed = (udword)(etgTimeElapsed * ETG_FrameScale);
    totalTime = (udword)(etgTotalTimeElapsed * ETG_FrameScale);

    if (totalTime % time <= timeElapsed + 1)
    {                                                       //if this is the frame
        return(sizeof(etgbeforeafterat));                   //just skip over this opcode
    }
    return(((etgbeforeafterat *)opcode)->codeBytes);        //skip over entire block
}

//handle EOP_EffectorCI
sdword etgEffectorCI(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword offset = ((etgseffector *)opcode)->effector;
    sdword *rate, *var, end;

    rate = (sdword *)(effect->variableRate + ((etgseffector *)opcode)->rate);
    var = (sdword *)(effect->variable + ((etgseffector *)opcode)->effector);
    if (effect->effectID[offset / 4] != ((etgseffector *)opcode)->effectorID)
    {                                                       //if effector just now starting
        *rate = *var;                                       //in the case of integers, the rate is really the start setting
//        *rate = (end - *var) * (sdword)(1.0f / etgEffectorDuration);//rate to be scaled by time delta
//        *rate = (end - var) * (sdword)
        effect->effectID[offset / 4] = ((etgseffector *)opcode)->effectorID;
    }
//!!!    *var += *rate * etgTimeElapsed;                         //as default, just update the effector variable by a rate
    end = (sdword)(((etgseffector *)opcode)->end);      //get integer endpoint
    *var = (end - *rate) * UNIVERSE_UPDATE_RATE /
        ((sdword)(etgEffectorDuration * (real32)UNIVERSE_UPDATE_RATE)) + *rate;
    return(sizeof(etgseffector));
}

//handle EOP_EffectorCF
sdword etgEffectorCF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword offset = ((etgseffector *)opcode)->effector;
    real32 *rate, *var, end;

    rate = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate);
    var = (real32 *)(effect->variable + ((etgseffector *)opcode)->effector);
    if (effect->effectID[offset / 4] != ((etgseffector *)opcode)->effectorID)
    {                                                       //if effector just now starting
        end = TreatAsReal32(((etgseffector *)opcode)->end);  //get floating-point endpoint
        *rate = (end - *var) / etgEffectorDuration;         //rate to be scaled by time delta
        effect->effectID[offset / 4] = ((etgseffector *)opcode)->effectorID;
    }
    *var += *rate * etgTimeElapsed;                         //as default, just update the effector variable by a rate
    return(sizeof(etgseffector));
}

//handle EOP_EffectorVF
sdword etgEffectorVF(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword offset = ((etgseffector *)opcode)->effector;
    real32 *rate, *var, *end;

    rate = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate);
    var = (real32 *)(effect->variable + ((etgseffector *)opcode)->effector);
    if (effect->effectID[offset / 4] != ((etgseffector *)opcode)->effectorID)
    {                                                       //if effector just now starting
        //end = TreatAsReal32(((etgseffector *)opcode)->end);  //get floating-point endpoint
        end = (real32 *)(effect->variable + ((etgseffector *)opcode)->end);
        *rate = (*end - *var) / etgEffectorDuration;         //rate to be scaled by time delta
        effect->effectID[offset / 4] = ((etgseffector *)opcode)->effectorID;
    }
    *var += *rate * etgTimeElapsed;                         //as default, just update the effector variable by a rate
    return(sizeof(etgseffector));
}

//handle EOP_EffectorCCo
sdword etgEffectorCCo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword offset = ((etgseffector *)opcode)->effector;
    real32 *rateRed, *rateGreen, *rateBlue, red, green, blue;
    color end, *var;

    rateRed = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate);
    rateGreen = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32));
    rateBlue = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32) * 2);
    var = (color *)(effect->variable + ((etgseffector *)opcode)->effector);
    red =  colUbyteToReal(colRed(*var));
    green =colUbyteToReal(colGreen(*var));
    blue = colUbyteToReal(colBlue(*var));
    if (effect->effectID[offset / 4] != ((etgseffector *)opcode)->effectorID)
    {                                                       //if effector just now starting
        end = (color)(((etgseffector *)opcode)->end);       //get color endpoint
        *rateRed = (colUbyteToReal(colRed(end)) -     red) / etgEffectorDuration;//rate to be scaled by time delta
        *rateGreen = (colUbyteToReal(colGreen(end)) - green) / etgEffectorDuration;//rate to be scaled by time delta
        *rateBlue = (colUbyteToReal(colBlue(end)) -   blue) / etgEffectorDuration;//rate to be scaled by time delta
        effect->effectID[offset / 4] = ((etgseffector *)opcode)->effectorID;
    }
//    *var += *rate * etgTimeElapsed;                         //as default, just update the effector variable by a rate
    red =   red + *rateRed * etgTimeElapsed;                 //update the values per-channel
    green = red + *rateGreen * etgTimeElapsed;
    blue =  red + *rateBlue * etgTimeElapsed;
    if (red < 0.0f)                                         //clamp the values
    {
        red = 0.0f;
    }
    if (red > 1.0f)
    {
        red = 1.0f;
    }
    if (green < 0.0f)
    {
        green = 0.0f;
    }
    if (green > 1.0f)
    {
        green = 1.0f;
    }
    if (blue < 0.0f)
    {
        blue = 0.0f;
    }
    if (blue > 1.0f)
    {
        blue = 1.0f;
    }                                                       //convert back to the color value
    *var = colRGB(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue));
    return(sizeof(etgseffector));
}

//handle EOP_EffectorVCo
sdword etgEffectorVCo(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword offset = ((etgseffector *)opcode)->effector;
    real32 *rateRed, *rateGreen, *rateBlue, red, green, blue;
    color *end, *var;

    rateRed = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate);
    rateGreen = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32));
    rateBlue = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32) * 2);
    var = (color *)(effect->variable + ((etgseffector *)opcode)->effector);
    red =  colUbyteToReal(colRed(*var));
    green =colUbyteToReal(colGreen(*var));
    blue = colUbyteToReal(colBlue(*var));
    if (effect->effectID[offset / 4] != ((etgseffector *)opcode)->effectorID)
    {                                                       //if effector just now starting
//        end = (color)(((etgseffector *)opcode)->end);       //get color endpoint
        end = (color *)(effect->variable + ((etgseffector *)opcode)->end);
        *rateRed = (colUbyteToReal(colRed(*end)) -     red) / etgEffectorDuration;//rate to be scaled by time delta
        *rateGreen = (colUbyteToReal(colGreen(*end)) - green) / etgEffectorDuration;//rate to be scaled by time delta
        *rateBlue = (colUbyteToReal(colBlue(*end)) -   blue) / etgEffectorDuration;//rate to be scaled by time delta
        effect->effectID[offset / 4] = ((etgseffector *)opcode)->effectorID;
    }
//    *var += *rate * etgTimeElapsed;                         //as default, just update the effector variable by a rate
    red =   red + *rateRed * etgTimeElapsed;                 //update the values per-channel
    green = green + *rateGreen * etgTimeElapsed;
    blue =  blue + *rateBlue * etgTimeElapsed;
    if (red < 0.0f)                                         //clamp the values
    {
        red = 0.0f;
    }
    if (red > 1.0f)
    {
        red = 1.0f;
    }
    if (green < 0.0f)
    {
        green = 0.0f;
    }
    if (green > 1.0f)
    {
        green = 1.0f;
    }
    if (blue < 0.0f)
    {
        blue = 0.0f;
    }
    if (blue > 1.0f)
    {
        blue = 1.0f;
    }                                                       //convert back to the color value
    *var = colRGB(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue));
    return(sizeof(etgseffector));
}

//handle EOP_EffectorCCa
sdword etgEffectorCCa(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword offset = ((etgseffector *)opcode)->effector;
    real32 *rateRed, *rateGreen, *rateBlue, *rateAlpha, red, green, blue, alpha;
    color end, *var;

    rateRed = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate);
    rateGreen = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32));
    rateBlue = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32) * 2);
    rateAlpha = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32) * 3);
    var = (color *)(effect->variable + ((etgseffector *)opcode)->effector);
    red =  colUbyteToReal(colRed(*var));
    green =colUbyteToReal(colGreen(*var));
    blue = colUbyteToReal(colBlue(*var));
    alpha = colUbyteToReal(colAlpha(*var));
    if (effect->effectID[offset / 4] != ((etgseffector *)opcode)->effectorID)
    {                                                       //if effector just now starting
        end = (color)(((etgseffector *)opcode)->end);       //get color endpoint
        *rateRed = (colUbyteToReal(colRed(end)) -     red) / etgEffectorDuration;//rate to be scaled by time delta
        *rateGreen = (colUbyteToReal(colGreen(end)) - green) / etgEffectorDuration;//rate to be scaled by time delta
        *rateBlue = (colUbyteToReal(colBlue(end)) -   blue) / etgEffectorDuration;//rate to be scaled by time delta
        *rateAlpha = (colUbyteToReal(colAlpha(end)) - alpha) / etgEffectorDuration;//rate to be scaled by time delta
        effect->effectID[offset / 4] = ((etgseffector *)opcode)->effectorID;
    }
//    *var += *rate * etgTimeElapsed;                         //as default, just update the effector variable by a rate
    red =   red + *rateRed * etgTimeElapsed;                 //update the values per-channel
    green = green + *rateGreen * etgTimeElapsed;
    blue =  blue + *rateBlue * etgTimeElapsed;
    alpha =  alpha + *rateAlpha * etgTimeElapsed;
    if (red < 0.0f)                                         //clamp the values
    {
        red = 0.0f;
    }
    if (red > 1.0f)
    {
        red = 1.0f;
    }
    if (green < 0.0f)
    {
        green = 0.0f;
    }
    if (green > 1.0f)
    {
        green = 1.0f;
    }
    if (blue < 0.0f)
    {
        blue = 0.0f;
    }
    if (blue > 1.0f)
    {
        blue = 1.0f;
    }                                                       //convert back to the color value
    if (alpha < 0.0f)
    {
        alpha = 0.0f;
    }
    if (alpha > 1.0f)
    {
        alpha = 1.0f;
    }                                                       //convert back to the color value
    *var = colRGBA(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue), colRealToUbyte(alpha));
    return(sizeof(etgseffector));
}

//handle EOP_EffectorVCa
sdword etgEffectorVCa(Effect *effect, struct etgeffectstatic *stat, ubyte *opcode)
{
    udword offset = ((etgseffector *)opcode)->effector;
    real32 *rateRed, *rateGreen, *rateBlue, *rateAlpha, red, green, blue, alpha;
    color *end, *var;

    rateRed = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate);
    rateGreen = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32));
    rateBlue = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32) * 2);
    rateAlpha = (real32 *)(effect->variableRate + ((etgseffector *)opcode)->rate + sizeof(real32) * 3);
    var = (color *)(effect->variable + ((etgseffector *)opcode)->effector);
    red =  colUbyteToReal(colRed(*var));
    green =colUbyteToReal(colGreen(*var));
    blue = colUbyteToReal(colBlue(*var));
    alpha = colUbyteToReal(colAlpha(*var));
    if (effect->effectID[offset / 4] != ((etgseffector *)opcode)->effectorID)
    {                                                       //if effector just now starting
//        end = (color)(((etgseffector *)opcode)->end);       //get color endpoint
        end = (color *)(effect->variable + ((etgseffector *)opcode)->end);
        *rateRed = (colUbyteToReal(colRed(*end)) -     red) / etgEffectorDuration;//rate to be scaled by time delta
        *rateGreen = (colUbyteToReal(colGreen(*end)) - green) / etgEffectorDuration;//rate to be scaled by time delta
        *rateBlue = (colUbyteToReal(colBlue(*end)) -   blue) / etgEffectorDuration;//rate to be scaled by time delta
        *rateAlpha = (colUbyteToReal(colAlpha(*end)) - alpha) / etgEffectorDuration;//rate to be scaled by time delta
        effect->effectID[offset / 4] = ((etgseffector *)opcode)->effectorID;
    }
//    *var += *rate * etgTimeElapsed;                         //as default, just update the effector variable by a rate
    red =   red + *rateRed * etgTimeElapsed;                 //update the values per-channel
    green = green + *rateGreen * etgTimeElapsed;
    blue =  blue + *rateBlue * etgTimeElapsed;
    alpha =  alpha + *rateAlpha * etgTimeElapsed;
    if (red < 0.0f)                                         //clamp the values
    {
        red = 0.0f;
    }
    if (red > 1.0f)
    {
        red = 1.0f;
    }
    if (green < 0.0f)
    {
        green = 0.0f;
    }
    if (green > 1.0f)
    {
        green = 1.0f;
    }
    if (blue < 0.0f)
    {
        blue = 0.0f;
    }
    if (blue > 1.0f)
    {
        blue = 1.0f;
    }                                                       //convert back to the color value
    if (alpha < 0.0f)
    {
        alpha = 0.0f;
    }
    if (alpha > 1.0f)
    {
        alpha = 1.0f;
    }                                                       //convert back to the color value
    *var = colRGBA(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue), colRealToUbyte(alpha));
    return(sizeof(etgseffector));
}

/*-----------------------------------------------------------------------------
    Actual p-code function functions, as opposed to p-code functions.
-----------------------------------------------------------------------------*/

//handle 'inc'
udword etgInc(udword value)
{
    return(value + 1);
}

//handle 'dec'
udword etgDec(udword value)
{
    return(value - 1);
}

//handle 'fmult'
udword etgFmult(real32 numer, real32 denom)
{
    real32 value;
    value = numer * denom;
    return(TreatAsUdword(value));
}

//handle 'fdiv'
udword etgFdiv(real32 numer, real32 denom)
{
    real32 value;
    value = numer / denom;
    return(TreatAsUdword(value));
}

//handle 'fadd'
udword etgFadd(real32 numer, real32 denom)
{
    real32 value;
    value = numer + denom;
    return(TreatAsUdword(value));
}

//handle 'fsub'
udword etgFsub(real32 numer, real32 denom)
{
    real32 value;
    value = numer - denom;
    return(TreatAsUdword(value));
}

//handle 'add'
sdword etgAdd(sdword numer, sdword denom)
{
    return (numer + denom);
}

//handle 'sub'
sdword etgSub(sdword numer, sdword denom)
{
    return (numer - denom);
}

//handle 'mult'
sdword etgMult(sdword numer, sdword denom)
{
    return (numer * denom);
}

//handle 'div'
sdword etgDiv(sdword numer, sdword denom)
{
    return (numer / denom);
}

//handle 'sin'
udword etgSin(real32 ang)
{
    real32 value;
    value = sin(ang);
    return(TreatAsUdword(value));
}

//handle 'cos'
udword etgCos(real32 ang)
{
    real32 value;
    value = cos(ang);
    return(TreatAsUdword(value));
}

/*-----------------------------------------------------------------------------
    Name        : etgDetatchThisOwner
    Description : Detaches other effects from the said owner.
    Inputs      : effect - effect the search starts on, this effect will not
                    be checked.
                  owner - ship that is dying and is to be removed from references.
                  nToFind - maximum number of effects we can expect to find
                  directions - what direction to search in spaceobj list, forward and/or back
    Outputs     :
    Return      : number of effects detached
----------------------------------------------------------------------------*/
sdword etgDetatchThisOwner(Effect *effect, Ship *owner, sdword nToFind)
{
    Node *fNode, *bNode;
    Effect *thisEffect;
    sdword nDetached;
#if ETG_DETATCH_STATS
    static nDetachRequests = 0;
    static nTotalDetached = 0;
    static nWalks = 0;

    nDetachRequests += nToFind;
#endif

    dbgAssert(nToFind > 0);
    bNode = effect->effectLink.prev;
    fNode = effect->effectLink.next;

    while (bNode || fNode)
    {
        if (fNode != NULL)
        {
#if ETG_DETATCH_STATS
            nWalks++;
#endif
            thisEffect = listGetStructOfNode(fNode);
            if (thisEffect->objtype == OBJ_EffectType && thisEffect->owner == owner)
            {
#if ETG_DETATCH_STATS
                nTotalDetached++;
#endif
                thisEffect->owner = NULL;
                nDetached++;
                if (nDetached >= nToFind)
                {
                    break;
                }
            }
            fNode = fNode->prev;
        }
        if (bNode != NULL)
        {
#if ETG_DETATCH_STATS
            nWalks++;
#endif
            thisEffect = listGetStructOfNode(bNode);
            if (thisEffect->objtype == OBJ_EffectType && thisEffect->owner == owner)
            {
#if ETG_DETATCH_STATS
                nTotalDetached++;
#endif
                thisEffect->owner = NULL;
                nDetached++;
                if (nDetached == nToFind)
                {
                    break;
                }
            }
            bNode = bNode->prev;
        }
    }
#if ETG_DETATCH_STATS
    if (nTotalDetached >= 10)
    {
        dbgMessagef("\netgDetatchThisOwner: detached %d effects for %d requests (%.2f%%) in %d walks (%.2f/per).",
                    nTotalDetached, nDetachRequests, (real32)nTotalDetached / (real32)nDetachRequests * 100.0f, nWalks, (real32)nWalks / (real32)nTotalDetached);
        nDetachRequests = nTotalDetached = nWalks = 0;
    }
#endif
    return(nDetached);
}

//handle the 'deleteParentShip' command
//extern void univWipeShipOutOfExistence(void *ship);                  // totally deletes ship, frees memory
void etgParentShipDelete(Effect *effect)
{
    if (effect->owner != NULL)
    {                                                       //if properly attached
        if (effect->owner->objtype == OBJ_ShipType)
        {                                                   //to a ship (could be attached to another effect)
            if (bitTest(effect->effectFlags, EAF_PlayingSpeech))
            {                                                   //if the ship is screaming out in pain
                soundEventShipDied(effect->owner);
                bitClear(effect->effectFlags, EAF_PlayingSpeech);
            }
            univWipeShipOutOfExistence(effect->owner);
        }
        else if (effect->owner->objtype == OBJ_DerelictType)
        {
            univWipeDerelictOutOfExistance((Derelict *)effect->owner);
        }
        if (effect->nSpawnedAttachedToOwner > 0)
        {
            etgDetatchThisOwner(effect, effect->owner, effect->nSpawnedAttachedToOwner);
        }
        effect->owner = NULL;
    }
    else
    {
#if ETG_VERBOSE_LEVEL >= 1
        dbgMessagef("\nEffect '%s' tried to delete ship more than once.", ((etgeffectstatic *)effect->staticinfo)->name);
#endif
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgShipDied
    Description : Remove references to a ship from any effects that may refer
                    to it.
    Inputs      : deadDuck - the ship that is dying
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgShipDied(Ship *deadDuck)
{
    Node *node;
    Effect *effect;

    for (node = universe.effectList.head; node != NULL; node = node->next)
    {
        effect = listGetStructOfNode(node);
        if (effect->owner == deadDuck)
        {
            effect->owner = NULL;
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgDeleteEffectsOwnedBy
    Description : Delete all effects that have the specified ship as their owner.
    Inputs      : owner - ship that may be owner of one or more effects
    Outputs     :
    Return      : Number of effects deleted
----------------------------------------------------------------------------*/
sdword etgDeleteEffectsOwnedBy(Ship *owner)
{
    Node *node, *nextNode;
    Effect *effect;
    sdword nFreed = 0;

    for (node = universe.effectList.head; node != NULL; node = nextNode)
    {
        nextNode = node->next;
        effect = listGetStructOfNode(node);
        if (effect->owner == owner)
        {
            etgEffectDelete(effect);                        //delete the effect
            univRemoveObjFromRenderList((SpaceObj *)effect);//remove from render list
            listDeleteNode(&effect->objlink);               //remove from object list
            nFreed++;
        }
    }
    return(nFreed);
}

//handle the 'damageDone' command (callback)
//extern void dmgStopSingleEffect(Effect* effect)
void etgDamageDone(Effect *effect)
{
    if (effect->owner != NULL)
    {
        dmgStopSingleEffect(effect);
    }
    else
    {
#if ETG_VERBOSE_LEVEL >= 1
        dbgMessagef("\nEffect '%s' tried to call a damage callback with no owner.", ((etgeffectstatic*)effect->staticinfo)->name);
#endif
    }
    etgExecStack.etgDeleteFlag = TRUE;
}

//handle 'getColorScheme' function
udword etgOwnerColorSchemeGet(Effect *effect)
{
    if (effect->owner != NULL)
    {
        return(effect->owner->colorScheme);
    }
    return(0);
}

//handle the 'getEffectVelocity' function
udword etgEffectVelocityGet(Effect *effect)
{
    vector *velVector;
    real32 velocity;
    udword intVelocity;

    if (effect->owner != NULL)
    {
        velVector = &effect->owner->posinfo.velocity;
    }
    else
    {
        velVector = &effect->posinfo.velocity;
    }
    velocity = vecMagnitudeSquared(*velVector);
    velocity = fsqrt(velocity);
    intVelocity = TreatAsUdword(velocity);
    return(intVelocity);
}

//handle the 'hideParentShip' function
void etgParentShipHide(Effect *effect)
{
    if (effect->owner != NULL)
    {
        univHideShipFromSpheres(effect->owner);
    }
    else
    {
#if ETG_VERBOSE_LEVEL >= 1
        dbgMessagef("\nEffect '%s' at 0x%x tried to hide ship which was deleted.", ((etgeffectstatic *)effect->staticinfo)->name, effect);
#endif
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgChatterEventPlay
    Description : Play a speech event on the effect's owner object
    Inputs      : effect - what effect to play the event on
                  event - what battle chatter event to play
    Outputs     : set's the ship's EAF_PlayingSpeech bit which can later be
                    used to turn the speech event off when the ship is killed.
    Return      :
----------------------------------------------------------------------------*/
void etgChatterEventPlay(Effect *effect, battlechatterevent event)
{
    if (effect->owner != NULL)
    {                                                       //if this effect has an owner
        dbgAssert(event >= 0 && event < BCE_LastBCE);
        if (battleChatterAttempt(SOUND_EVENT_DEFAULT, event, effect->owner, SOUND_EVENT_DEFAULT))
        {
            bitSet(effect->effectFlags, EAF_PlayingSpeech);     //note that we are playing some speech
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : etgDeathCriesPlay
    Description : Play death cry battle chatter for a dying ship
    Inputs      : effect - effect who's ship is blowing up
    Outputs     : will play a viriety of different battle chatter events based
                    on ship class and whatnot.
    Return      :
----------------------------------------------------------------------------*/
void etgDeathCriesPlay(Effect *effect)
{
    Ship *owner = effect->owner;

    if (owner != NULL)
    {
        battleShipDyingWithTimeToScream(owner);
    }
}

//handle creation of sprite-particles
ubyte *etgCreateSprites(Effect *effect, sdword number, sdword dist)
{
#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\nCreating %d +- %d sprite sprite-particles for 0x%x", number, dist, effect);
#endif
#if ETG_ERROR_CHECKING
    if (effect->iParticleBlock >= effect->nParticleBlocks)
    {
        dbgFatalf(DBG_Loc, "etgCreateCircles: exceeded %d particle blocks", effect->iParticleBlock);
    }
#endif
    partSetPosition(&effect->posinfo.position);
    partSetCoordSys(&effect->rotinfo.coordsys);
    partSetWorldVel(&effect->posinfo.velocity);
    effect->particleBlock[effect->iParticleBlock] =
        partCreateSystemWithDelta(PART_BILLBOARD, number, dist);
    effect->iParticleBlock++;
    return(effect->particleBlock[effect->iParticleBlock - 1]);
}

//handle creation of circle-particles
ubyte *etgCreateCircles(Effect *effect, sdword number, sdword dist)
{
#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\nCreating %d +- %d circle-particles for 0x%x", number, dist, effect);
#endif
#if ETG_ERROR_CHECKING
    if (effect->iParticleBlock >= effect->nParticleBlocks)
    {
        dbgFatalf(DBG_Loc, "etgCreateCircles: exceeded %d particle blocks", effect->iParticleBlock);
    }
#endif
    partSetTrHandle(TR_Invalid, 0, 0, 0, 0);
    partSetPosition(&effect->posinfo.position);
    partSetCoordSys(&effect->rotinfo.coordsys);
    partSetWorldVel(&effect->posinfo.velocity);
    effect->particleBlock[effect->iParticleBlock] =
        partCreateSystemWithDelta(PART_BILLBOARD, number, dist);
    effect->iParticleBlock++;
    return(effect->particleBlock[effect->iParticleBlock - 1]);
}

//handle creation of point-particles
ubyte *etgCreatePoints(Effect *effect, sdword number, sdword dist)
{
#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\nCreating %d +- %d point-particles for 0x%x", number, dist, effect);
#endif
#if ETG_ERROR_CHECKING
    if (effect->iParticleBlock >= effect->nParticleBlocks)
    {
        dbgFatalf(DBG_Loc, "etgCreateCircles: exceeded %d particle blocks", effect->iParticleBlock);
    }
#endif
    partSetPosition(&effect->posinfo.position);
    partSetCoordSys(&effect->rotinfo.coordsys);
    partSetWorldVel(&effect->posinfo.velocity);
    effect->particleBlock[effect->iParticleBlock] =
        partCreateSystemWithDelta(PART_POINTS, number, dist);
    effect->iParticleBlock++;
    return(effect->particleBlock[effect->iParticleBlock - 1]);
}

//handle creation of mesh-particles
ubyte *etgCreateMeshes(Effect *effect, sdword number, sdword dist)
{
#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\nCreating %d +- %d mesh-particles for 0x%x", number, dist, effect);
#endif
#if ETG_ERROR_CHECKING
    if (effect->iParticleBlock >= effect->nParticleBlocks)
    {
        dbgFatalf(DBG_Loc, "etgCreateCircles: exceeded %d particle blocks", effect->iParticleBlock);
    }
#endif
    partSetPosition(&effect->posinfo.position);
    partSetCoordSys(&effect->rotinfo.coordsys);
    partSetWorldVel(&effect->posinfo.velocity);
    effect->particleBlock[effect->iParticleBlock] =
        partCreateSystemWithDelta(PART_MESH, number, dist);
    effect->iParticleBlock++;
    return(effect->particleBlock[effect->iParticleBlock - 1]);
}

//handle creation of line-particles
ubyte *etgCreateLines(Effect *effect, sdword number, sdword dist)
{
#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\nCreating %d +- %d line-particles for 0x%x", number, dist, effect);
#endif
#if ETG_ERROR_CHECKING
    if (effect->iParticleBlock >= effect->nParticleBlocks)
    {
        dbgFatalf(DBG_Loc, "etgCreateCircles: exceeded %d particle blocks", effect->iParticleBlock);
    }
#endif
    partSetPosition(&effect->posinfo.position);
    partSetCoordSys(&effect->rotinfo.coordsys);
    partSetWorldVel(&effect->posinfo.velocity);
    effect->particleBlock[effect->iParticleBlock] =
        partCreateSystemWithDelta(PART_LINES, number, dist);
    effect->iParticleBlock++;
    return(effect->particleBlock[effect->iParticleBlock - 1]);
}

//handle the creation of effect-particles
void etgCreateEffects(Effect *effect, etgeffectstatic *stat, sdword number, sdword dist, sdword nParams, ...)
{
    psysPtr system;
    particle *part;
    sdword index, size, j;
    vector up, right, LOF, tempVector;
    Effect *newEffect;
    sdword arg[ETG_NumberParameters];
    va_list argList;

    if (RGLtype == SWtype)
    {
        if (stat->softwareVersion != NULL)
        {
            stat = stat->softwareVersion;
        }
    }
    matGetVectFromMatrixCol1(up, effect->rotinfo.coordsys);

#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\nCreating %d +- %d effect-particles for 0x%x", number, dist, effect);
#endif
    if (dist != 0)
    {
        number = ranRandom(RAN_ETG) % (dist * 2) + number - dist;
    }
    partSetPosition(&effect->posinfo.position);
    partSetCoordSys(&effect->rotinfo.coordsys);
    partSetWorldVel(&effect->posinfo.velocity);
    system = partCreateSystem(PART_POINTS, number);
    for (index = 0, part = (particle*)(system + partHeaderSize(system));
         index < partNumberof(system); index++, part++)
    {
        if (etgFrequencyExceeded(stat))
        {
            continue;
        }
        size = stat->effectSize;
        newEffect = memAlloc(size, "Ef(Effect)", Pyrophoric);   //allocate the new effect
        newEffect->objtype = OBJ_EffectType;
        newEffect->flags = SOF_Rotatable;
        newEffect->staticinfo = (StaticInfo *)stat;
        ClearNode(newEffect->renderlink);
        newEffect->currentLOD = effect->currentLOD;
        newEffect->cameraDistanceSquared = effect->cameraDistanceSquared;
        newEffect->timeElapsed = -part->waitspan;           //use delay from the particle

        newEffect->posinfo.isMoving = FALSE;
        newEffect->posinfo.haventCalculatedDist = TRUE;
        newEffect->drag = ((pointSystem*)system)->drag;
        //compute new velocity vector
        if (part->velR != 0.0f)
        {
            vecMultiplyByScalar(part->rvec, part->velR);    //get velocity in effect space
        }
        part->rvec.z = part->velLOF;
        if (bitTest(((pointSystem *)system)->flags, PART_WORLDSPACE))
        {
            newEffect->posinfo.velocity = part->wVel;
            newEffect->posinfo.position = part->position;
        }
        else
        {
            //!!! this is inconsistent with the velocity vector stuff in particle.c !!!
            matMultiplyMatByVec(&newEffect->posinfo.velocity,   //convert to world space
                                &effect->rotinfo.coordsys, &part->rvec);
            matMultiplyMatByVec(&newEffect->posinfo.position,   //position in world coords
                                &effect->rotinfo.coordsys, &part->position);
            vecAdd(newEffect->posinfo.position,                 //position not relative to parent effect
                   newEffect->posinfo.position, effect->posinfo.position);
        }
        //velocity vector is new LOF, create a coordinate system for it
        matMultiplyMatByVec(&LOF, &effect->rotinfo.coordsys, &part->rvec);
        vecCrossProduct(right, LOF, up);
        vecCrossProduct(tempVector, LOF, right);
        vecNormalize(&LOF);
        vecNormalize(&right);
        vecNormalize(&tempVector);

        matPutVectIntoMatrixCol1(tempVector, newEffect->rotinfo.coordsys);
        matPutVectIntoMatrixCol2(right, newEffect->rotinfo.coordsys);
        matPutVectIntoMatrixCol3(LOF, newEffect->rotinfo.coordsys);

        if (!bitTest(((pointSystem *)system)->flags, PART_WORLDSPACE))
        {
            univUpdateObjRotInfo((SpaceObjRot *)newEffect);
        }

        dbgAssert(nParams >= 0 && nParams <= ETG_NumberParameters);
        va_start(argList, nParams);
        for (j = 0; j < nParams; j++)
        {
            arg[j] = va_arg(argList, udword);
        }
        va_end(argList);                                    //pass all possible parameters

        etgEffectCodeStart(stat, newEffect, nParams, arg[0], arg[1], arg[2],
            arg[3], arg[4], arg[5], arg[6], arg[7]);        //get the code a-runnin'

        if (bitTest(stat->specialOps, ESO_SortForward))
        {                                                   //make it sort forward, if applicable
            bitSet(newEffect->flags, SOF_AlwaysSortFront);
        }
        newEffect->owner = NULL;                            // no host for this effect
        newEffect->nSpawnedAttachedToOwner = 0;
        newEffect->magnitudeSquared = effect->magnitudeSquared;
        newEffect->spin = etgEffOffset.spin;
        newEffect->deltaSpin = etgEffOffset.deltaSpin;
        listAddNode(&universe.SpaceObjList,&(newEffect->objlink),newEffect);
        listAddNode(&universe.effectList,&(newEffect->effectLink),newEffect);
        if (bitTest(stat->specialOps, ESO_WorldRender))
        {
            bitSet(newEffect->flags, SOF_Hide);
            univAddToWorldList((Derelict *)newEffect);
        }
        else
        {
            univAddObjToRenderListIf((SpaceObj *)newEffect,(SpaceObj *)effect);     // add to render list if parent ship is in render list
        }
    }
    memFree(system);
}

/*-----------------------------------------------------------------------------
    Name        : etgEffectCreate
    Description : Add an effect to the universe.
    Inputs      : stat - what effect to use
                  owner - parent object of this effect, NULL if none
                  pos - position of the effect, NULL to get from parent
                  vel - velocity of the effect, NULL to get from parent
                  coordsys - coordinate system for effect, NULL to get from parent
                  nLips - initial nLips value
                  flags - attachment flags for the effect
                  nParams - number of parameters for the effect code
                  ... - parameters for the effect code
    Outputs     :
    Return      : Newly allocated and started effect.
----------------------------------------------------------------------------*/
void *etgEffectCreate(etgeffectstatic *stat, void *owner, vector *pos, vector *vel, matrix *coordsys, real32 nLips, udword flags, sdword nParams, ...)
{
    sdword index;
    va_list argList;
    sdword arg[ETG_NumberParameters];
    Effect *newEffect;

    if (RGLtype == SWtype)
    {
        if (stat->softwareVersion != NULL)
        {
            stat = stat->softwareVersion;
        }
    }
    newEffect = memAlloc(stat->effectSize, "GE(GenEffect)", Pyrophoric);//allocate the new effect

    newEffect->objtype = OBJ_EffectType;                    //type of spaceobj
    newEffect->flags = SOF_Rotatable | flags;               //basic flags
    newEffect->staticinfo = (StaticInfo *)stat;             //save static info
    ClearNode(newEffect->renderlink);                       //no in render list
    newEffect->currentLOD = 0;//owner ? ((Ship *)owner)->currentLOD : 0;
    newEffect->cameraDistanceSquared = owner ? ((Ship *)owner)->cameraDistanceSquared : REALlyBig;
    newEffect->timeElapsed = 0.0f;                          //effect just now starting
    newEffect->drag = 1.0f;                                 //default is no drag at all

    //position
    if (pos == NULL)
    {
        dbgAssert(((Ship *)owner) != NULL);
//        if (!bitTest(flags, SOF_AttachPosition))
        {
            newEffect->posinfo.position = ((Ship *)owner)->posinfo.position;
        }
    }
    else
    {
        newEffect->posinfo.position = *pos;
    }

    //velocity
    if (vel == NULL)
    {
        dbgAssert(((Ship *)owner) != NULL);
//        if (!bitTest(flags, SOF_AttachVelocity))
        {
            newEffect->posinfo.velocity = ((Ship *)owner)->posinfo.velocity;
        }
    }
    else
    {
        newEffect->posinfo.velocity = *vel;
    }

    //coordinate system
    if (coordsys == NULL)
    {
        dbgAssert(((Ship *)owner) != NULL);
//        if (!bitTest(flags, SOF_AttachCoordsys))
        {
            newEffect->rotinfo.coordsys = ((Ship *)owner)->rotinfo.coordsys;
        }
    }
    else
    {
        newEffect->rotinfo.coordsys = *coordsys;
    }
    newEffect->owner = (Ship *)owner;                       // ((Ship *)owner) that effect is attached to
    newEffect->nSpawnedAttachedToOwner = 0;
    if (nLips == 0.0f)
    {
        nLips = 1.0f;
    }
    newEffect->magnitudeSquared = nLips;                    //set initial nLips value

    //prepare to pass the parameters through
    dbgAssert(nParams >= 0 && nParams <= ETG_NumberParameters);
    va_start(argList, nParams);
    for (index = 0; index < nParams; index++)
    {
        arg[index] = va_arg(argList, udword);
    }
    va_end(argList);                                        //pass all possible parameters

    //!!! note: if this is general enough, may be able to unroll etgEffectCodeStart
    //right in this function.  Would save us some parameter voodoo.
    etgEffectCodeStart(stat, newEffect, nParams,            //get the code a-runnin'
            arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7]);
    if (bitTest(stat->specialOps, ESO_SortForward))
    {                                                       //make it sort forward, if applicable
        bitSet(newEffect->flags, SOF_AlwaysSortFront);
    }
    listAddNode(&universe.SpaceObjList,&(newEffect->objlink),newEffect);
    listAddNode(&universe.effectList,&(newEffect->effectLink),newEffect);
    if (((Ship *)owner) != NULL)
    {
        if (bitTest(stat->specialOps, ESO_WorldRender))
        {
            bitSet(newEffect->flags, SOF_Hide);
            univAddToWorldList((Derelict *)newEffect);
        }
        else
        {
            univAddObjToRenderListIf((SpaceObj *)newEffect,(SpaceObj *)owner);      // add to render list if parent ((Ship *)owner) is in render list
        }
    }
    else
    {
        if (bitTest(stat->specialOps, ESO_WorldRender))
        {
            bitSet(newEffect->flags, SOF_Hide);
            univAddToWorldList((Derelict *)newEffect);
        }
        else
        {
            univAddObjToRenderList((SpaceObj *)newEffect);      // add to render list if parent ((Ship *)owner) is in render list
        }
    }
    if (bitTest(stat->specialOps, ESO_SortForward))
    {                                                       //make it sort forward, if applicable
        bitSet(newEffect->flags, SOF_AlwaysSortFront);
    }
    return(newEffect);
}

/*-----------------------------------------------------------------------------
    Name        : etgHistoryRegisterFunction
    Description : Register an effect creation in this effect's history list
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void etgHistoryRegisterFunction(etgeffectstatic *stat)
{
/*
    if (stat->historyList == NULL)
    {                                                       //if effect has no history
        return;
    }
*/
    dbgAssert(stat->historyList != NULL);                   //make sure effect has a history list
    if (stat->iHistoryList == stat->nHistoryList)
    {                                                       //if at end of history list
        stat->iHistoryList = 0;                             //wrap around to start
    }
    stat->historyList[stat->iHistoryList] = universe.totaltimeelapsed;
    stat->iHistoryList++;
}

/*-----------------------------------------------------------------------------
    Name        : etgFrequencyExceeded
    Description : See if an effect is being played too often
    Inputs      : stat - effect static to check
    Outputs     :
    Return      : TRUE if the effect should not be played because it's max frequency
                    is exceeded.
----------------------------------------------------------------------------*/
bool etgFrequencyExceeded(etgeffectstatic *stat)
{
    sdword index, max = stat->nHistoryList, count = 0, countThisSecond = 0;
    real32 *historyList = stat->historyList;

    if (historyList == NULL)
    {                                                       //if no history list
#if ETG_VERBOSE_LEVEL > 1
        dbgMessagef("\netgFrequencyExceeded: effect '%s' has no history.", stat->name);
#endif
        return(FALSE);                                      //go ahead and make an effect
    }

    for (index = stat->iHistoryList - 1; count < max; index--, count++, countThisSecond++)
    {
        if (index < 0)
        {                                                   //if start of history list
            index = max - 1;                                //wrap around to end
        }
        if (universe.totaltimeelapsed - historyList[index] > EH_TimeElapsed)
        {                                                   //if this one was way long ago
            break;                                          //found all the instances spawned this second
        }
    }

    max = max * etgHistoryScalar / 256;
    if (countThisSecond >= max)
    {
        return(TRUE);                                       //all used, don't play
    }
    if (randyrandombetween(RAN_ETG, countThisSecond, max) < countThisSecond)
    {                                                       //randomly see if we should play
        return(TRUE);                                       //don't play if the number comes up
    }
    return(FALSE);                                          //you are free to play this effect
}

/*-----------------------------------------------------------------------------
    Name        : etgSpawnNewEffect
    Description : handle spawning new effects
    Inputs      : effect - effect which is spawning new effect
                  stat - static info of new effect to create
                  ... user parameters
    Outputs     : Creates a new effect using the location, velocity, etc. of
                    the specified effect and using the new effect offsets.
    Return      : pointer to newly created effect, or NULL if not created
----------------------------------------------------------------------------*/
udword etgSpawnNewEffect(Effect *effect, etgeffectstatic *stat, sdword nParams, ...)
{
    Effect *newEffect;
    sdword size, index;
    vector position, newPos;
    matrix rotTheta, rotMu, tempMatrix;
    real32 sinTheta, cosTheta, sinMu, cosMu;
    va_list argList;
    udword arg[ETG_NumberParameters];

    if (RGLtype == SWtype)
    {
        if (stat->softwareVersion != NULL)
        {
            stat = stat->softwareVersion;
        }
    }
    if (etgFrequencyExceeded(stat))
    {
        return(0);
    }
//    size = etgEffectSize(stat->nParticleBlocks);            //compute size of effect
    size = stat->effectSize;
    newEffect = memAlloc(size, "Ef(Effect)", Pyrophoric);       //allocate the new effect
    newEffect->objtype = OBJ_EffectType;
    if (etgEffOffset.attachParent && effect->owner != NULL)
    {
        newEffect->flags = SOF_Rotatable | SOF_AttachPosition | SOF_AttachVelocity;
        newEffect->owner = effect->owner;                   // no host for this effect
        effect->nSpawnedAttachedToOwner++;
    }
    else
    {
        newEffect->flags = SOF_Rotatable;
        newEffect->owner = NULL;                            // no host for this effect
    }
    newEffect->staticinfo = (StaticInfo *)stat;
    ClearNode(newEffect->renderlink);
    newEffect->currentLOD = effect->currentLOD;
    newEffect->cameraDistanceSquared = effect->cameraDistanceSquared;
    newEffect->timeElapsed = -etgEffOffset.offsetTime;      //brand new !!! how about time-offsets?

    //determine new position in space of current effect
    position.x = etgEffOffset.offsetR; position.y = 0.0f; position.z = etgEffOffset.offsetLOF;
    if (etgEffOffset.offsetR != 0.0f)
    {
        sinTheta = (real32)sin((double)etgEffOffset.offsetTheta);
        cosTheta = (real32)cos((double)etgEffOffset.offsetTheta);
        matMakeRotAboutZ(&rotTheta, cosTheta, sinTheta);
//        matMultiplyMatByMat(&rotMu, &rotTheta, &effect->rotinfo.coordsys);
        matMultiplyVecByMat(&newPos, &position, &rotTheta);    //new radial offset
        position = newPos;
    }

    vecAdd(newPos, position, etgEffOffset.offsetXYZ);       //add in the XYZ offset
    matMultiplyMatByVec(&position, &effect->rotinfo.coordsys, &newPos);//get offset vector in world space
    vecAdd(newEffect->posinfo.position, position,             //and get position in world space
           effect->posinfo.position);

    //create new coordinate system using current effects matrix and the new LOF
    //offsets from current
    if (etgEffOffset.changeLOFTheta != 0.0f || etgEffOffset.changeLOFMu != 0.0f)
    {                                                       //if there is even any offsets
        position.x = 0.0f; position.y = 0.0f; position.z = 1.0f;

        sinTheta = (real32)sin((double)etgEffOffset.changeLOFTheta);
        cosTheta = (real32)cos((double)etgEffOffset.changeLOFTheta);
        sinMu = (real32)sin((double)etgEffOffset.changeLOFMu);
        cosMu = (real32)cos((double)etgEffOffset.changeLOFMu);
        matMakeRotAboutY(&rotMu, cosMu, sinMu);
        matMakeRotAboutZ(&rotTheta, cosTheta, sinTheta);
        matMultiplyMatByMat(&tempMatrix, &rotTheta, &rotMu);//concat the two rotations
        matMultiplyMatByMat(&newEffect->rotinfo.coordsys,   //matrix in world space
                            &effect->rotinfo.coordsys, &tempMatrix);
    }
    else
    {
        newEffect->rotinfo.coordsys = effect->rotinfo.coordsys;
    }
    newEffect->posinfo.isMoving = FALSE;
    newEffect->posinfo.haventCalculatedDist = TRUE;
    newEffect->drag = effect->drag;
//    newEffect->rotinfo.coordsys = coordSystem;

    //create new compound velocity vector
//    position.x = 0.0f; position.y = 0.0f; position.z = etgEffOffset.offsetVelLOF;
//    matMultiplyVecByMat(&newEffect->posinfo.velocity, &position, &newEffect->rotinfo.coordsys);
//    vecAdd(newEffect->posinfo.velocity,
//           newEffect->posinfo.velocity, effect->posinfo.velocity);
    newEffect->posinfo.velocity = effect->posinfo.velocity;

    univUpdateObjRotInfo((SpaceObjRot *)newEffect);

    //get the parameters
    dbgAssert(nParams >= 0 && nParams <= ETG_NumberParameters);
    va_start(argList, nParams);
    for (index = 0; index < nParams; index++)
    {
        arg[index] = va_arg(argList, udword);
    }
    va_end(argList);                                        //pass all possible parameters
    etgEffectCodeStart(stat, newEffect, nParams, arg[0], arg[1], arg[2],
        arg[3], arg[4], arg[5], arg[6], arg[7]);            //get the code a-runnin'

    if (bitTest(stat->specialOps, ESO_SortForward))
    {                                                       //make it sort forward, if applicable
        bitSet(newEffect->flags, SOF_AlwaysSortFront);
    }
    //newEffect->owner = effect->owner;                       // no host for this effect
    newEffect->nSpawnedAttachedToOwner = 0;
    newEffect->magnitudeSquared = effect->magnitudeSquared;
    newEffect->spin = etgEffOffset.spin;
    newEffect->deltaSpin = etgEffOffset.deltaSpin;
    listAddNode(&universe.SpaceObjList,&(newEffect->objlink),newEffect);
    listAddNode(&universe.effectList,&(newEffect->effectLink),newEffect);
    univAddObjToRenderListIf((SpaceObj *)newEffect,(SpaceObj *)effect);// add to render list if parent ship is in render list

    return((udword)newEffect);                              //return pointer to ETG code
}

//adjust parameters of effects to be spawned
void etgEffOffsetLOF(real32 newVal)
{
    etgEffOffset.offsetLOF = newVal;
}
void etgEffChangeLOF(real32 newTheta, real32 newMu)
{
    etgEffOffset.changeLOFTheta = newTheta;
    etgEffOffset.changeLOFMu = newMu;
}
void etgEffOffsetR(real32 newR, real32 newTheta)
{
    etgEffOffset.offsetR = newR;
    etgEffOffset.offsetTheta = newTheta;
//    etgEffOffset.offsetMu = newMu;
}
void etgEffOffsetXYZ(real32 x, real32 y, real32 z)
{
    etgEffOffset.offsetXYZ.x = x;
    etgEffOffset.offsetXYZ.y = y;
    etgEffOffset.offsetXYZ.z = z;
}
void etgEffOffsetVelLOF(real32 newVal)
{
    etgEffOffset.offsetVelLOF = newVal;
}
void etgEffOffsetSpin(real32 newVal)
{
    etgEffOffset.offsetSpin = newVal;
}
void etgEffOffsetTime(real32 newVal)
{
    etgEffOffset.offsetTime = newVal;
}
void etgEffOffsetDrag(Effect *effect, real32 newVal)
{
    etgEffOffset.drag = effect->drag + 1.0f - newVal;
}
void etgEffDrag(real32 newDrag)
{
    etgEffOffset.drag = 1.0f - newDrag;
}
void etgEffAttachParent(bool attach)
{
    etgEffOffset.attachParent = attach;
}
void etgEffSpin(real32 spin)
{
    etgEffOffset.spin = spin;
}
void etgEffDeltaSpin(real32 deltaSpin)
{
    etgEffOffset.deltaSpin = deltaSpin;
}
void etgEffDefaults(void)
{
    memset(&etgEffOffset, 0, sizeof(etgEffOffset));
}
//functions for modifying the current effect
/*
void etgThisChangeLOF(Effect *effect, real32 theta, real32 mu)
{
    dbgMessagef("\netgThisChangeLOF not implemented yet!");
}
void etgThisOffsetLOF(Effect *effect, real32 offset)
{
    dbgMessagef("\netgThisOffsetLOF not implemented yet!");
}
void etgThisOffsetR(Effect *effect, real32 R, real32 theta)
{
    dbgMessagef("\netgThisOffsetR not implemented yet!");
}
*/
void etgThisOffsetXYZ(Effect *effect, real32 x, real32 y, real32 z)
{
    dbgMessagef("\netgThisOffsetXYZ not implemented yet!");
}
/*
void etgThisOffsetVelLOF(Effect *effect, real32 vel)
{
    dbgMessagef("\netgThisOffsetVelLOF not implemented yet!");
}
void etgThisOffsetVelR(Effect *effect, real32 R, real32 theta)
{
    dbgMessagef("\netgThisOffsetVelR not implemented yet!");
}
*/
void etgThisScaleVelocity(Effect *effect, real32 factor)
{
    vecMultiplyByScalar(effect->posinfo.velocity, factor);
    dbgMessagef("\netgThisScaleVelocity not implemented yet!");
}
//spin an effect about it's axis
void etgThisOffsetSpin(Effect *effect, real32 spin)
{
    effect->spin = spin;
}
void etgThisOffsetDeltaSpin(Effect *effect, real32 spinDelta)
{
    effect->deltaSpin = spinDelta;
}
void etgThisOffsetTime(Effect *effect, real32 time)
{
    effect->timeElapsed -= time;
}
void etgThisSetDrag(Effect *effect, real32 drag)
{
    effect->drag = 1.0f - drag;
}
void etgSetDefaults(void)
{
    etgEffDefaults();
    partSetDefaults();
}
void etgSetDrag(real32 n)
{
    partSetDrag(1.0f - n);
}
void etgSetDepthWrite(udword write)
{
    partSetNoDepthWrite(!write);
}

void etgModifyDrag(ubyte *sys, real32 drag)
{
    partModifyDrag(sys, 1.0f - drag);
}
void etgModifyDepthWrite(ubyte *sys, udword write)
{
    partModifyNoDepthWrite(sys, !write);
}

void etgSetColorA(color c)
{
    partSetColorA(colUbyteToReal(colRed(c)), colUbyteToReal(colGreen(c)), colUbyteToReal(colBlue(c)), colUbyteToReal(colAlpha(c)));
}
void etgSetColor(color c)
{
    partSetColor(colUbyteToReal(colRed(c)), colUbyteToReal(colGreen(c)), colUbyteToReal(colBlue(c)));
}

void etgSetAnimation(void *animation, real32 frameRate, sdword loopCount)
{
    partSetAnimation(animation);
    partSetFramerate(frameRate);
    partSetLoopFlag((bool8)(loopCount != 0));
}

void etgSetMorphAnimation(void *animation, real32 frameRate, sdword loopCount)
{
    partSetMorph(animation);
    partSetMorphFramerate(frameRate);
    partSetMorphLoopCount(loopCount);
}

void etgSetSpecular(real32 exponent)
{
    if (exponent == 0.0f)
    {
        partSetSpecular(FALSE);
    }
    else
    {
        partSetSpecular(TRUE);
        partSetExponent(exponent);
    }
}

udword etgFRandom(real32 low, real32 high)
{
    udword valueInt;
    real32 value;

    if (high <= low)
    {
        return(TreatAsUdword(low));
    }
//    dbgAssert(high > low);
    valueInt = ranRandom(RAN_ETG);
    value = (real32)valueInt * (high - low) / (real32)UDWORD_Max + low;
#if ETG_VERBOSE_LEVEL >= 2
    dbgMessagef("\nfrandom(%f, %f) = %f", low, high, value);
#endif
    return(TreatAsUdword(value));
}

udword etgIRandom(udword low, udword high)
{
    if (high <= low)
    {
        return(low);
    }
//    dbgAssert(high > low);
    return(ranRandom(RAN_ETG) % (high - low) + low);
}

udword etgCRandom(udword loR, udword hiR, udword loG, udword hiG, udword loB, udword hiB)
{
    if (loB <= hiB)
    {
        hiB = loB + 1;
    }
    if (loG <= hiG)
    {
        hiG = loG + 1;
    }
    if (loR <= hiR)
    {
        hiR = loR + 1;
    }
    return(colRGB(randyrandombetween(RAN_ETG, loR, hiR), randyrandombetween(RAN_ETG, loG, hiB), randyrandombetween(RAN_ETG, loB, hiB)));
}

udword etgCARandom(udword loR, udword hiR, udword loG, udword hiG, udword loB, udword hiB, udword loA, udword hiA)
{
    if (loA <= hiA)
    {
        hiA = loA + 1;
    }
    if (loB <= hiB)
    {
        hiB = loB + 1;
    }
    if (loG <= hiG)
    {
        hiG = loG + 1;
    }
    if (loR <= hiR)
    {
        hiR = loR + 1;
    }
    return(colRGBA(randyrandombetween(RAN_ETG, loR, hiR), randyrandombetween(RAN_ETG, loG, hiB), randyrandombetween(RAN_ETG, loB, hiB), randyrandombetween(RAN_ETG, loA, hiA)));
}

//convert data types
udword etgFloat2Int(real32 f)
{
    return((udword)(sdword)f);
}

udword etgInt2Float(sdword f)
{
    real32 retVal = (real32)f;
    return(TreatAsUdword(retVal));
}

udword etgInts2Color(sdword red, sdword green, sdword blue)
{
    return(colRGB(red, green, blue));
}

udword etgInts2ColorA(sdword red, sdword green, sdword blue, sdword alpha)
{
    return(colRGBA(red, green, blue, alpha));
}

udword etgFloats2Color(real32 red, real32 green, real32 blue)
{
    return(colRGB(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue)));
}

udword etgFloats2ColorA(real32 red, real32 green, real32 blue, real32 alpha)
{
    return(colRGBA(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue), colRealToUbyte(alpha)));
}
/*
"ETG Fixes: Following modify functions would be very useful if added to the ETG:
X modifyVelLOF
X modifyDeltaVelLOF
X modifyVelR
X modifyDeltaVelR
? modifyVelTheta
? modifyDeltaVelTheta
X modifyMesh(ObHandle, Handle)
X modifyLength(ObHandle, Handle)"
*/

/*=============================================================================
    Save Game Stuff
=============================================================================*/

sdword saveEtglodGunEventToIndex(etglod *lod)
{
    sdword i,j,k;

    for (i=0;i<NUM_RACES;i++)
    {
        for (j=0;j<NUM_GUN_SOUND_TYPES;j++)
        {
            for (k=0;k<EGT_NumberGunTypes;k++)
            {
                if (lod == etgGunEventTable[i][j][k])
                {
                    return (i<<16) | (j<<8) | k;
                }
            }
        }
    }

    return -1;
}

etglod *saveIndexToEtglodGunEvent(sdword index)
{
    sdword i,j,k;

    if (index == -1)
    {
        return NULL;
    }

    i = (index>>16) & 255;
    j = (index>>8) & 255;
    k = index & 255;

    if (i < NUM_RACES && j < NUM_GUN_SOUND_TYPES && k < EGT_NumberGunTypes)
    {
        return etgGunEventTable[i][j][k];
    }

    return NULL;
}

