/*
===========================================================================
Copyright (C) 2008 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// g_spawn.cpp : spawner for scripts.

#include "g_local.h"
#include "g_spawn.h"
#include "../script/scriptvariable.h"
#include "object.h"
#include "../qcommon/tiki.h"

#include "../fgame/entity.h"
#include "../fgame/actor.h"

extern Event EV_Entity_Start;
CLASS_DECLARATION(Class, SpawnArgs, NULL) {
    {NULL, NULL}
};

Container<SafePtr<Listener>> g_spawnlist;

SpawnArgs::SpawnArgs() {}

SpawnArgs::SpawnArgs(SpawnArgs& otherlist)
{
    int num;
    int i;

    num = otherlist.NumArgs();
    keyList.Resize(num);
    valueList.Resize(num);
    for (i = 1; i <= num; i++) {
        keyList.AddObject(otherlist.keyList.ObjectAt(i));
        valueList.AddObject(otherlist.valueList.ObjectAt(i));
    }
}

void SpawnArgs::Clear(void)
{
    keyList.FreeObjectList();
    valueList.FreeObjectList();
}

/*
====================
Parse

Parses spawnflags out of the given string, returning the new position.
Clears out any previous args.
====================
*/
char *SpawnArgs::Parse(char *data, bool bAllowUtils)
{
    str         keyname;
    const char *com_token;

    Clear();

    // parse the opening brace
    com_token = COM_Parse(&data);
    if (!data) {
        return NULL;
    }

    if (com_token[0] != '{') {
        gi.Error(ERR_DROP, "SpawnArgs::Parse : found %s when expecting {", com_token);
    }

    // go through all the dictionary pairs
    while (1) {
        // parse key
        com_token = COM_Parse(&data);
        if (com_token[0] == '}') {
            break;
        }

        if (!data) {
            gi.Error(ERR_DROP, "SpawnArgs::Parse : EOF without closing brace");
        }

        keyname = com_token;

        // parse value
        com_token = COM_Parse(&data);
        if (!data) {
            gi.Error(ERR_DROP, "SpawnArgs::Parse : EOF without closing brace");
        }

        if (com_token[0] == '}') {
            gi.Error(ERR_DROP, "SpawnArgs::Parse : closing brace without data");
        }

        // keynames with a leading underscore are used for utility comments,
        // and are immediately discarded by the game
        if (!bAllowUtils && keyname[0] == '_') {
            continue;
        }

        setArg(keyname.c_str(), com_token);
    }

    return data;
}

const char *SpawnArgs::getArg(const char *key, const char *defaultValue)
{
    int i;
    int num;

    num = keyList.NumObjects();
    for (i = 1; i <= num; i++) {
        if (keyList.ObjectAt(i) == key) {
            return valueList.ObjectAt(i);
        }
    }

    return defaultValue;
}

void SpawnArgs::setArg(const char *key, const char *value)
{
    int i;
    int num;

    num = keyList.NumObjects();
    for (i = 1; i <= num; i++) {
        if (keyList.ObjectAt(i) == key) {
            valueList.ObjectAt(i) = value;
            return;
        }
    }

    keyList.AddObject(str(key));
    valueList.AddObject(str(value));
}

void SpawnArgs::operator=(SpawnArgs& otherlist)
{
    int num;
    int i;

    Clear();

    num = otherlist.NumArgs();
    keyList.Resize(num);
    valueList.Resize(num);

    for (i = 1; i <= num; i++) {
        keyList.AddObject(otherlist.keyList.ObjectAt(i));
        valueList.AddObject(otherlist.valueList.ObjectAt(i));
    }
}

int SpawnArgs::NumArgs(void)
{
    return keyList.NumObjects();
}

const char *SpawnArgs::getKey(int index)
{
    return keyList.ObjectAt(index + 1);
}

const char *SpawnArgs::getValue(int index)
{
    return valueList.ObjectAt(index + 1);
}

void SpawnArgs::Archive(Archiver& arc)
{
    Class::Archive(arc);

    //keyList.Archive( arc );
    //valueList.Archive( arc );
}

/*
===============
getClass

Finds the spawn function for the entity and returns ClassDef *
===============
*/

ClassDef *SpawnArgs::getClassDef(qboolean *tikiWasStatic)
{
    const char  *classname;
    ClassDef    *cls  = NULL;
    dtiki_t     *tiki = NULL;
    dtikianim_t *a    = NULL;

    classname = getArg("classname");

    if (tikiWasStatic) {
        *tikiWasStatic = false;
    }

    //
    // check normal spawn functions
    // see if the class name is stored within the model
    //
    if (classname) {
        //
        // explicitly inhibit lights
        //
        if (!Q_stricmp(classname, "light")) {
            //
            // HACK HACK HACK
            // hack to suppress a warning message
            //
            if (tikiWasStatic) {
                *tikiWasStatic = true;
            }
            return NULL;
        }

        cls = getClassForID(classname);
        if (!cls) {
            cls = getClass(classname);
        }
    }

    if (!cls) {
        const char *model;

        //
        // get Object in case we cannot find an alternative
        //
        cls   = &Object::ClassInfo;
        model = getArg("model");
        if (model) {
            int i;

            model = CanonicalTikiName(model);
            tiki  = gi.modeltiki(model);

            //
            // get handle to def file
            //
            if (tiki) {
                const char *s;

                s = getArg("make_static");

                if (s && atoi(s)) {
                    //
                    // if make_static then we don't want to spawn
                    //
                    if (tikiWasStatic) {
                        *tikiWasStatic = true;
                    }

                    return NULL;
                }

                a = tiki->a;

                if (a->num_server_initcmds) {
                    for (i = 0; i < a->num_server_initcmds; i++) {
                        if (!Q_stricmp(a->server_initcmds[i].args[0], "classname")) {
                            cls = getClass(a->server_initcmds[i].args[1]);
                            break;
                        }
                    }

                    if (i == a->num_server_initcmds) {
                        gi.DPrintf(
                            "Classname %s used, but 'classname' was not found in Initialization commands, using "
                            "Object.\n",
                            classname
                        );
                    }
                } else {
                    gi.DPrintf(
                        "Classname %s used, but TIKI had no Initialization commands, using Object.\n", classname
                    );
                }
            } else {
                gi.DPrintf("Classname %s used, but model was not a TIKI, using Object.\n", classname);
            }
        } else {
            gi.DPrintf("Classname %s' used, but no model was set, using Object.\n", classname);
        }
    }

    return cls;
}

/*
===============
Spawn

Finds the spawn function for the entity and calls it.
Returns pointer to Entity
===============
*/

Listener *SpawnArgs::Spawn(void)
{
    SafePtr<Listener> ent = (Listener *)SpawnInternal();
	if (!ent) {
		return NULL;
	}

	ent->ProcessPendingEvents();
	if (!ent) {
		// Fixed in OPM
		//  Make sure to check the entity after processing events,
		//  the ent could be deleted due to a posted remove event
		return NULL;
	}

	ent->ProcessEvent(EV_Entity_Start);

    return ent;
}

Listener *SpawnArgs::SpawnInternal(void)
{
    str         classname;
    ClassDef   *cls;
    Listener   *obj;
    Event      *ev;
    int         i;
    qboolean    tikiWasStatic; // used to determine if entity was intentionally suppressed
    const char *key, *value;
    str         keyname;
    int         t1, t2;
#ifdef WITH_SCRIPT_ENGINE
    unsigned int        eventnum;
    EventDef           *def;
    ScriptVariable     *var;
    ScriptVariableList *varList;
#endif

    if (!g_spawnentities->integer) {
        value = getArg("classname", NULL);

        // only spawn a playerstart or the world
        if (value) {
            if ((Q_stricmp(value, "info_player_start") && Q_stricmp(value, "worldspawn"))) {
                Com_Printf("%s\n", value);
                return NULL;
            }
        }
    }

    if (!g_spawnai->integer) {
        value = getArg("classname", NULL);

        //dont spawn ai_* if g_spawnai is 0
        if (value) {
            if (!Q_stricmpn(value, "ai_", 3)) {
                Com_Printf("%s\n", value);
                return NULL;
            }
        }
    }

    t1 = gi.Milliseconds();

    classname = getArg("classname", "Unspecified");
    cls       = getClassDef(&tikiWasStatic);

    if (!cls) {
        if (!tikiWasStatic) {
            gi.DPrintf("%s doesn't have a spawn function\n", classname.c_str());

            if (g_scriptcheck->integer) {
                gi.Error(ERR_DROP, "Script check failed");
            }
        }

        return NULL;
    }

    if (checkInheritance(PathNode::classinfostatic(), cls)) {
        // use the existing node that was loaded
        obj = PathManager.GetSpawnNode(cls);
    } else {
        obj = (Listener *)cls->newInstance();
    }

    for (int i = g_spawnlist.NumObjects(); i > 0; i--) {
        if (g_spawnlist.ObjectAt(i) == NULL) {
            g_spawnlist.RemoveObjectAt(i);
        }
    }

    g_spawnlist.AddObject(obj);

    // post spawnarg events
    for (i = 0; i < NumArgs(); i++) {
        key   = getKey(i);
        value = getValue(i);

        if (*key == '#') {
            // don't count the prefix
            keyname = (key + 1);

#ifdef WITH_SCRIPT_ENGINE
            // initialize the object vars
            varList = obj->Vars();

            var = varList->GetVariable(keyname);
            if (var) {
                Com_Printf(
                    "^~^~^ variable '%s' already set with string value '%s' - failed to attempt to set with numeric "
                    "value '%s'\n",
                    keyname.c_str(),
                    var->stringValue().c_str(),
                    value
                );

                continue;
            }

            eventnum = Event::FindSetterEventNum(keyname);

            if (!eventnum || !(def = cls->GetDef(eventnum))) {
                if (strchr(keyname.c_str(), '.')) {
                    // it's a float
                    varList->SetVariable(keyname, (float)atof(value));
                } else {
                    varList->SetVariable(keyname, atoi(value));
                }
            } else if (def->type != EV_SETTER) {
                Com_Printf("^~^~^ Cannot set a read-only variable '%s'\n", keyname.c_str());
            } else {
                ev = new Event(keyname, EV_SETTER);

                if (strchr(keyname.c_str(), '.')) {
                    ev->AddFloat(atof(value));
                } else {
                    ev->AddInteger(atoi(value));
                }

                obj->PostEvent(ev, EV_SPAWNARG);
            }
#endif
        } else if (*key == '$') {
            keyname = (key + 1);

#ifdef WITH_SCRIPT_ENGINE
            varList = obj->Vars();

            var = varList->GetVariable(keyname);
            if (var) {
                Com_Printf(
                    "^~^~^ variable '%s' already set with string value '%s' - failed to attempt to set with string "
                    "value '%s'\n",
                    keyname.c_str(),
                    var->stringValue().c_str(),
                    value
                );

                continue;
            }

            eventnum = Event::FindSetterEventNum(keyname);

            if (!eventnum || !(def = cls->GetDef(eventnum))) {
                varList->SetVariable(keyname, value);
            } else if (def->type != EV_SETTER) {
                Com_Printf("^~^~^ Cannot set a read-only variable '%s'\n", keyname.c_str());
            } else {
                ev = new Event(eventnum);
                ev->AddString(value);

                obj->PostEvent(ev, EV_SPAWNARG);
            }
#endif
        }
        // if it is the "script" key, execute the script commands individually
        else if (!Q_stricmp(key, "script")) {
            char *ptr;
            char *token;

            ptr = const_cast<char *>(getValue(i));
            while (1) {
                token = COM_ParseExt(&ptr, true);
                if (!token[0]) {
                    break;
                }
                if (strchr(token, ':')) {
                    gi.DPrintf("Label %s imbedded inside editor script for %s.\n", token, classname.c_str());
                } else {
                    ev = new Event(token);
                    while (1) {
                        token = COM_ParseExt(&ptr, false);
                        if (!token[0]) {
                            break;
                        }
                        ev->AddToken(token);
                    }

                    obj->PostEvent(ev, EV_SPAWNARG);
                }
            }
        } else {
            ev = new Event(key);
            ev->AddToken(value);

            if (Q_stricmp(key, "model") == 0) {
                if (obj->isSubclassOf(SimpleActor)) {
                    if (Q_stricmpn(value, "human", 5) && Q_stricmpn(value, "models/human", 12)
                        && Q_stricmpn(value, "models//human", 13) && Q_stricmpn(value, "animal", 6)
                        && Q_stricmpn(value, "models/animal", 13)) {
                        Com_Printf("^~^~^ model '%s' cannot be an actor - please fix the .tik file\n", value);

                        delete obj;
                        return NULL;
                    }

                    obj->PostEvent(ev, EV_PRIORITY_SPAWNACTOR);
                } else {
                    obj->PostEvent(ev, EV_PRIORITY_SPAWNARG);
                }
            } else {
                if (obj->isSubclassOf(SimpleActor)) {
                    obj->PostEvent(ev, EV_SPAWNACTOR);
                } else {
                    obj->PostEvent(ev, EV_SPAWNARG);
                }
            }
        }
    }

    if (!obj) {
        gi.DPrintf("%s failed on newInstance\n", classname.c_str());
        return NULL;
    }

    t2 = gi.Milliseconds();
    if (t2 - t1 >= 100) {
        //
        // above 100 ms send a message to console
        //
        for (i = 0; i < NumArgs(); i++) {
            key   = getKey(i);
            value = getValue(i);

            if (!Q_stricmp(key, "classname")) {
                Com_Printf("spawn '%s': %5.2f seconds\n", value, (t2 - t1) / 1000.f);
                break;
            }
        }
    }

    return obj;
}

ClassDef *FindClass(const char *name, qboolean *isModel)
{
    ClassDef *cls;

    *isModel = false;

    // first lets see if it is a registered class name
    cls = getClass(name);
    if (!cls) {
        SpawnArgs args;

        // if that didn't work lets try to resolve it as a model
        args.setArg("model", name);

        cls = args.getClassDef();
        if (cls) {
            *isModel = true;
        }
    }
    return cls;
}

/*
==============
G_InitClientPersistant

This is only called when the game first initializes in single player,
but is called after each death and level change in deathmatch
==============
*/
void G_InitClientPersistant(gclient_t *client)
{
    memset(&client->pers, 0, sizeof(client->pers));
}

#if 0

#    include "../qcommon/tiki_local.h" // fixme!

qboolean	G_SpawnString( const char *key, const char *defaultString, char **out ) {
	int		i;

	if ( !level.spawning ) {
		*out = (char *)defaultString;
//		G_Error( "G_SpawnString() called while not spawning" );
	}

	for ( i = 0 ; i < level.numSpawnVars ; i++ ) {
		if ( !Q_stricmp( key, level.spawnVars[i][0] ) ) {
			*out = level.spawnVars[i][1];
			return qtrue;
		}
	}

	*out = (char *)defaultString;
	return qfalse;
}

qboolean	G_SpawnFloat( const char *key, const char *defaultString, float *out ) {
	char		*s;
	qboolean	present;

	present = G_SpawnString( key, defaultString, &s );
	*out = atof( s );
	return present;
}

qboolean	G_SpawnInt( const char *key, const char *defaultString, int *out ) {
	char		*s;
	qboolean	present;

	present = G_SpawnString( key, defaultString, &s );
	*out = atoi( s );
	return present;
}

qboolean	G_SpawnVector( const char *key, const char *defaultString, float *out ) {
	char		*s;
	qboolean	present;

	present = G_SpawnString( key, defaultString, &s );
	sscanf( s, "%f %f %f", &out[0], &out[1], &out[2] );
	return present;
}

//
// fields are needed for spawning from the entity string
//
typedef enum {
	F_INT,
	F_FLOAT,
	F_LSTRING,			// string on disk, pointer in memory, TAG_LEVEL
	F_GSTRING,			// string on disk, pointer in memory, TAG_GAME
	F_VECTOR,
	F_ANGLEHACK,
	F_ENTITY,			// index on disk, pointer in memory
	F_ITEM,				// index on disk, pointer in memory
	F_CLIENT,			// index on disk, pointer in memory
	F_IGNORE
} fieldtype_t;

typedef struct
{
	char	*name;
	int		ofs;
	fieldtype_t	type;
	int		flags;
} gfield_t;

gfield_t fields[] = {
	{"classname", FOFS(classname), F_LSTRING},
	{"origin", FOFS(s.origin), F_VECTOR},
	{"model", FOFS(model), F_LSTRING},
	{"model2", FOFS(model2), F_LSTRING},
	{"spawnflags", FOFS(spawnflags), F_INT},
	{"speed", FOFS(speed), F_FLOAT},
	{"target", FOFS(target), F_LSTRING},
	{"targetname", FOFS(targetname), F_LSTRING},
	{"message", FOFS(message), F_LSTRING},
	{"team", FOFS(team), F_LSTRING},
	{"wait", FOFS(wait), F_FLOAT},
	{"random", FOFS(random), F_FLOAT},
	{"count", FOFS(count), F_INT},
	{"health", FOFS(health), F_INT},
	{"light", 0, F_IGNORE},
	{"dmg", FOFS(damage), F_INT},
	{"angles", FOFS(s.angles), F_VECTOR},
	{"angle", FOFS(s.angles), F_ANGLEHACK},
	{"targetShaderName", FOFS(targetShaderName), F_LSTRING},
	{"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING},

	{NULL}
};


typedef struct {
	char	*name;
	void	(*spawn)(gentity_t *ent);
} spawn_t;

void SP_info_player_start (gentity_t *ent);
void SP_info_player_deathmatch (gentity_t *ent);
void SP_info_player_intermission (gentity_t *ent);
void SP_info_firstplace(gentity_t *ent);
void SP_info_secondplace(gentity_t *ent);
void SP_info_thirdplace(gentity_t *ent);
void SP_info_podium(gentity_t *ent);

void SP_func_plat (gentity_t *ent);
void SP_func_static (gentity_t *ent);
void SP_func_rotating (gentity_t *ent);
void SP_func_bobbing (gentity_t *ent);
void SP_func_pendulum( gentity_t *ent );
void SP_func_button (gentity_t *ent);
void SP_func_door (gentity_t *ent);
void SP_func_rotatingdoor (gentity_t *ent);	// IneQuation
void SP_func_train (gentity_t *ent);
void SP_func_timer (gentity_t *self);

void SP_trigger_always (gentity_t *ent);
void SP_trigger_multiple (gentity_t *ent);
void SP_trigger_push (gentity_t *ent);
void SP_trigger_teleport (gentity_t *ent);
void SP_trigger_hurt (gentity_t *ent);

void SP_target_remove_powerups( gentity_t *ent );
void SP_target_give (gentity_t *ent);
void SP_target_delay (gentity_t *ent);
void SP_target_speaker (gentity_t *ent);
void SP_target_print (gentity_t *ent);
//void SP_target_laser (gentity_t *self);
void SP_target_character (gentity_t *ent);
void SP_target_score( gentity_t *ent );
void SP_target_teleporter( gentity_t *ent );
void SP_target_relay (gentity_t *ent);
void SP_target_kill (gentity_t *ent);
void SP_target_position (gentity_t *ent);
void SP_target_location (gentity_t *ent);
void SP_target_push (gentity_t *ent);

void SP_light (gentity_t *self);
void SP_info_null (gentity_t *self);
void SP_info_notnull (gentity_t *self);
void SP_info_camp (gentity_t *self);
void SP_path_corner (gentity_t *self);

void SP_misc_teleporter_dest (gentity_t *self);
void SP_misc_model(gentity_t *ent);
void SP_misc_portal_camera(gentity_t *ent);
void SP_misc_portal_surface(gentity_t *ent);

void SP_shooter_rocket( gentity_t *ent );
void SP_shooter_plasma( gentity_t *ent );
void SP_shooter_grenade( gentity_t *ent );

void SP_team_CTF_redplayer( gentity_t *ent );
void SP_team_CTF_blueplayer( gentity_t *ent );

void SP_team_CTF_redspawn( gentity_t *ent );
void SP_team_CTF_bluespawn( gentity_t *ent );

#    ifdef MISSIONPACK
void SP_team_blueobelisk( gentity_t *ent );
void SP_team_redobelisk( gentity_t *ent );
void SP_team_neutralobelisk( gentity_t *ent );
#    endif
void SP_item_botroam( gentity_t *ent ) { }

spawn_t	spawns[] = {
	// info entities don't do anything at all, but provide positional
	// information for things controlled by other processes
	{"info_player_start", SP_info_player_start},
	{"info_player_deathmatch", SP_info_player_deathmatch},
	{"info_player_intermission", SP_info_player_intermission},
	{"info_null", SP_info_null},
	{"info_notnull", SP_info_notnull},		// use target_position instead
	{"info_camp", SP_info_camp},

	{"func_plat", SP_func_plat},
	{"func_button", SP_func_button},
	//{"func_door", SP_func_door},
	{"func_rotatingdoor", SP_func_rotatingdoor},	// IneQuation
	{"func_static", SP_func_static},
	{"func_rotating", SP_func_rotating},
	{"func_bobbing", SP_func_bobbing},
	{"func_pendulum", SP_func_pendulum},
	{"func_train", SP_func_train},
	{"func_group", SP_info_null},
	{"func_timer", SP_func_timer},			// rename trigger_timer?

	// Triggers are brush objects that cause an effect when contacted
	// by a living player, usually involving firing targets.
	// While almost everything could be done with
	// a single trigger class and different targets, triggered effects
	// could not be client side predicted (push and teleport).
	{"trigger_always", SP_trigger_always},
	{"trigger_multiple", SP_trigger_multiple},
	{"trigger_push", SP_trigger_push},
	{"trigger_teleport", SP_trigger_teleport},
	{"trigger_hurt", SP_trigger_hurt},

	// targets perform no action by themselves, but must be triggered
	// by another entity
	{"target_give", SP_target_give},
	{"target_remove_powerups", SP_target_remove_powerups},
	{"target_delay", SP_target_delay},
	{"target_speaker", SP_target_speaker},
	{"target_print", SP_target_print},
	//{"target_laser", SP_target_laser},
	{"target_score", SP_target_score},
	{"target_teleporter", SP_target_teleporter},
	{"target_relay", SP_target_relay},
	{"target_kill", SP_target_kill},
	{"target_position", SP_target_position},
	{"target_location", SP_target_location},
	{"target_push", SP_target_push},

	{"light", SP_light},
	{"path_corner", SP_path_corner},

	{"misc_teleporter_dest", SP_misc_teleporter_dest},
	{"misc_model", SP_misc_model},
	{"misc_portal_surface", SP_misc_portal_surface},
	{"misc_portal_camera", SP_misc_portal_camera},

	{"shooter_rocket", SP_shooter_rocket},
	{"shooter_grenade", SP_shooter_grenade},
	{"shooter_plasma", SP_shooter_plasma},

	// IneQuation
	{"info_player_axis", SP_team_CTF_redspawn},
	{"info_player_allied", SP_team_CTF_bluespawn},

#    ifdef MISSIONPACK
	{"team_redobelisk", SP_team_redobelisk},
	{"team_blueobelisk", SP_team_blueobelisk},
	{"team_neutralobelisk", SP_team_neutralobelisk},
#    endif
	{"item_botroam", SP_item_botroam},

	{NULL, 0}
};

const char *G_FixTIKIPath(const char *in) {
	static char path[256];
	void *buffer;

	if(in[0] == '*')
		return in; // that's a bsp inline model
	if(gi.FS_ReadFile( in, &buffer ) > 0) {
		return in;
	}
	Q_strncpyz(path,"models/", sizeof(path));
	Q_strcat(path,sizeof(path),in);
	if( gi.FS_ReadFile( in, &buffer ) > 0 ) {
		return path;
	}
	return in;
}

/*
===============
G_CallSpawn

Finds the spawn function for the entity and calls it,
returning qfalse if not found
===============
*/
qboolean G_CallSpawn( gentity_t *ent ) {
	spawn_t	*s;
	tiki_t *tiki;

	if ( !ent->classname ) {
		G_Printf ("G_CallSpawn: NULL classname\n");
		return qfalse;
	}

	// check normal spawn functions
	for ( s=spawns ; s->name ; s++ ) {
		if ( !strcmp(s->name, ent->classname) ) {
			// found it
			s->spawn(ent);
			return qtrue;
		}
	}

	// try to spawn a tiki model
	if(ent->model && ent->model[0] /*&& ent->model[0] != '*'*/) {
		if(ent->model[0] != '*') {
			char *ptr1, *ptr2;
			ptr1 = ent->model;
			ptr2 = ent->model;
			// su44: fix double '/' in TIKI paths from entities lump.
			// The same issue is also in R_LoadStaticModels.
			while(*ptr1) {
				if(ptr1[0] == '/' && ptr1[1] == '/') {
					ptr1++;
				}
				*ptr2 = *ptr1;
				ptr1++;
				ptr2++;
			}
			*ptr2 = 0;
		}
#    if 0
		tiki = gi.TIKI_RegisterModel(ent->model);
		if(!tiki) {
			tiki = gi.TIKI_RegisterModel(va("models/%s",ent->model));
		}
		if(tiki) {
			float dummy;
			ent->s.modelindex = G_ModelIndex(tiki->name);
			ent->s.eType = ET_MODELANIM;
			gi.TIKI_AppendFrameBoundsAndRadius(tiki,0,0,&dummy,ent->r.mins);
			ent->s.frameInfo[0].weight = 1.f;
			G_SetOrigin( ent, ent->s.origin );
			VectorCopy( ent->s.angles, ent->s.angles );
			gi.LinkEntity (ent);
			return qtrue;
		}
#    else
		ent->s.modelindex = G_ModelIndex( ( char * )G_FixTIKIPath( ent->model ) );
		ent->s.eType = ET_MODELANIM;
		//if(ent->model[0]=='*') {
		//	gi.SetBrushModel(ent, ent->model);
		//} else
		{
			VectorSet (ent->r.mins, -128, -128, -128);
			VectorSet (ent->r.maxs, 128, 128, 128);			
		}
		ent->s.frameInfo[0].weight = 1.f;
		G_SetOrigin( ent, ent->s.origin );
		gi.linkentity (ent);
		return qtrue;
#    endif
	}

	G_Printf ("%s doesn't have a spawn function\n", ent->classname);
	return qfalse;
}

/*
=============
G_NewString

Builds a copy of the string, translating \n to real linefeeds
so message texts can be multi-line
=============
*/
char *G_NewString( const char *string ) {
	char	*newb, *new_p;
	int		i,l;

	l = strlen(string) + 1;

	newb = ( char * )G_Alloc( l );

	new_p = newb;

	// turn \n into a real linefeed
	for ( i=0 ; i< l ; i++ ) {
		if (string[i] == '\\' && i < l-1) {
			i++;
			if (string[i] == 'n') {
				*new_p++ = '\n';
			} else {
				*new_p++ = '\\';
			}
		} else {
			*new_p++ = string[i];
		}
	}

	return newb;
}




/*
===============
G_ParseField

Takes a key/value pair and sets the binary values
in a gentity
===============
*/
void G_ParseField( const char *key, const char *value, gentity_t *ent ) {
	gfield_t	*f;
	byte	*b;
	float	v;
	vec3_t	vec;

	for ( f=fields ; f->name ; f++ ) {
		if ( !Q_stricmp(f->name, key) ) {
			// found it
			b = (byte *)ent;

			switch( f->type ) {
			case F_LSTRING:
				*(char **)(b+f->ofs) = G_NewString (value);
				break;
			case F_VECTOR:
				sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
				((float *)(b+f->ofs))[0] = vec[0];
				((float *)(b+f->ofs))[1] = vec[1];
				((float *)(b+f->ofs))[2] = vec[2];
				break;
			case F_INT:
				*(int *)(b+f->ofs) = atoi(value);
				break;
			case F_FLOAT:
				*(float *)(b+f->ofs) = atof(value);
				break;
			case F_ANGLEHACK:
				v = atof(value);
				((float *)(b+f->ofs))[0] = 0;
				((float *)(b+f->ofs))[1] = v;
				((float *)(b+f->ofs))[2] = 0;
				break;
			default:
			case F_IGNORE:
				break;
			}
			return;
		}
	}
}




/*
===================
G_SpawnGEntityFromSpawnVars

Spawn an entity and fill in all of the level fields from
level.spawnVars[], then call the class specfic spawn function
===================
*/
void G_SpawnGEntityFromSpawnVars( void ) {
	int			i;
	gentity_t	*ent;
	char		*s, *value, *gametypeName;
	static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester", "teamtournament"};

	// get the next free entity
	ent = G_Spawn();

	for ( i = 0 ; i < level.numSpawnVars ; i++ ) {
		G_ParseField( level.spawnVars[i][0], level.spawnVars[i][1], ent );
	}

	// check for "notsingle" flag
	if ( g_gametype->integer == GT_SINGLE_PLAYER ) {
		G_SpawnInt( "notsingle", "0", &i );
		if ( i ) {
			G_FreeEntity( ent );
			return;
		}
	}
	// check for "notteam" flag (GT_FFA, GT_TOURNAMENT, GT_SINGLE_PLAYER)
	if ( g_gametype->integer >= GT_TEAM ) {
		G_SpawnInt( "notteam", "0", &i );
		if ( i ) {
			G_FreeEntity( ent );
			return;
		}
	} else {
		G_SpawnInt( "notfree", "0", &i );
		if ( i ) {
			G_FreeEntity( ent );
			return;
		}
	}

#    ifdef MISSIONPACK
	G_SpawnInt( "notta", "0", &i );
	if ( i ) {
		G_FreeEntity( ent );
		return;
	}
#    else
	G_SpawnInt( "notq3a", "0", &i );
	if ( i ) {
		G_FreeEntity( ent );
		return;
	}
#    endif

	if( G_SpawnString( "gametype", NULL, &value ) ) {
		if( g_gametype->integer >= GT_FFA && g_gametype->integer < GT_MAX_GAME_TYPE ) {
			gametypeName = gametypeNames[g_gametype->integer];

			s = strstr( value, gametypeName );
			if( !s ) {
				G_FreeEntity( ent );
				return;
			}
		}
	}

	// move editor origin to pos
	VectorCopy( ent->s.origin, ent->r.currentOrigin );

	// if we didn't get a classname, don't bother spawning anything
	if ( !G_CallSpawn( ent ) ) {
		G_FreeEntity( ent );
	}
}



/*
====================
G_AddSpawnVarToken
====================
*/
char *G_AddSpawnVarToken( const char *string ) {
	int		l;
	char	*dest;

	l = strlen( string );
	if ( level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) {
		G_Error( "G_AddSpawnVarToken: MAX_SPAWN_CHARS" );
	}

	dest = level.spawnVarChars + level.numSpawnVarChars;
	memcpy( dest, string, l+1 );

	level.numSpawnVarChars += l + 1;

	return dest;
}

/*
====================
G_ParseSpawnVars

Parses a brace bounded set of key / value pairs out of the
level's entity strings into level.spawnVars[]

This does not actually spawn an entity.
====================
*/
qboolean G_ParseSpawnVars( void ) {
	char		keyname[MAX_TOKEN_CHARS];
	char		com_token[MAX_TOKEN_CHARS];

	level.numSpawnVars = 0;
	level.numSpawnVarChars = 0;

#    if 0
	// parse the opening brace
	if ( !gi.GetEntityToken( com_token, sizeof( com_token ) ) ) {
		// end of spawn string
		return qfalse;
	}
	if ( com_token[0] != '{' ) {
		G_Error( "G_ParseSpawnVars: found %s when expecting {",com_token );
	}

	// go through all the key / value pairs
	while ( 1 ) {
		// parse key
		if ( !gi.GetEntityToken( keyname, sizeof( keyname ) ) ) {
			G_Error( "G_ParseSpawnVars: EOF without closing brace" );
		}

		if ( keyname[0] == '}' ) {
			break;
		}

		// parse value
		if ( !gi.GetEntityToken( com_token, sizeof( com_token ) ) ) {
			G_Error( "G_ParseSpawnVars: EOF without closing brace" );
		}

		if ( com_token[0] == '}' ) {
			G_Error( "G_ParseSpawnVars: closing brace without data" );
		}
		if ( level.numSpawnVars == MAX_SPAWN_VARS ) {
			G_Error( "G_ParseSpawnVars: MAX_SPAWN_VARS" );
		}
		level.spawnVars[ level.numSpawnVars ][0] = G_AddSpawnVarToken( keyname );
		level.spawnVars[ level.numSpawnVars ][1] = G_AddSpawnVarToken( com_token );
		level.numSpawnVars++;
	}
#    endif

	return qtrue;
}



/*QUAKED worldspawn (0 0 0) ?

Every map should have exactly one worldspawn.
"music"		music wav file
"gravity"	800 is default gravity
"message"	Text to print during connection process
*/
void SP_worldspawn( void ) {
	char	*s;
	int i;

	G_SpawnString( "classname", "", &s );
	if ( Q_stricmp( s, "worldspawn" ) ) {
		G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" );
	}

	// make some data visible to connecting client
	gi.SetConfigstring( CS_GAME_VERSION, GAME_VERSION );

	gi.SetConfigstring( CS_LEVEL_START_TIME, va("%i", level.startTime ) );

	G_SpawnString( "music", "", &s );
	gi.SetConfigstring( CS_MUSIC, s );

	G_SpawnString( "message", "", &s );
	gi.SetConfigstring( CS_MESSAGE, s );				// map specific message

	gi.SetConfigstring( CS_MOTD, g_motd.string );		// message of the day

	G_SpawnString( "gravity", "800", &s );
	gi.cvar_set( "g_gravity", s );

	G_SpawnString( "enableDust", "0", &s );
	gi.cvar_set( "g_enableDust", s );

	G_SpawnString( "enableBreath", "0", &s );
	gi.cvar_set( "g_enableBreath", s );

	for( i = 0; i < level.numSpawnVars; i++ )
	{
		Event *ev = new Event( level.spawnVars[ i ][ 0 ] );
		ev->AddString( level.spawnVars[ i ][ 1 ] );

		world->PostEvent( ev, EV_SPAWNARG );
	}

	g_entities[ENTITYNUM_WORLD].s.number = ENTITYNUM_WORLD;
	g_entities[ENTITYNUM_WORLD].classname = "worldspawn";

	// see if we want a warmup time
	gi.SetConfigstring( CS_WARMUP, "" );
	if ( g_restarted.integer ) {
		gi.cvar_set( "g_restarted", "0" );
		level.warmupTime = 0;
	} else if ( g_doWarmup.integer ) { // Turn it on
		level.warmupTime = -1;
		gi.SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
		G_LogPrintf( "Warmup:\n" );
	}

}


/*
==============
G_SpawnEntitiesFromString

Parses textual entity definitions out of an entstring and spawns gentities.
==============
*/
void G_SpawnEntitiesFromString( void ) {
	// allow calls to G_Spawn*()
	level.spawning = qtrue;
	level.numSpawnVars = 0;

	// the worldspawn is not an actual entity, but it still
	// has a "spawn" function to perform any global setup
	// needed by a level (setting configstrings or cvars, etc)
	if ( !G_ParseSpawnVars() ) {
		G_Error( "SpawnEntities: no entities" );
	}
	SP_worldspawn();

	// parse ents
	while( G_ParseSpawnVars() ) {
		G_SpawnGEntityFromSpawnVars();
	}

	level.spawning = qfalse;			// any future calls to G_Spawn*() will be errors
}

void G_GetNullEntityState(entityState_t *nullState) {

	Com_Memset( nullState, 0, sizeof(entityState_t) );
	nullState->alpha = 1.0f;
	nullState->scale = 1.0f;
	nullState->parent = ENTITYNUM_NONE;
	nullState->tag_num = -1;
	nullState->renderfx = 16;
	nullState->bone_tag[4] = -1;
	nullState->bone_tag[3] = -1;
	nullState->bone_tag[2] = -1;
	nullState->bone_tag[1] = -1;
	nullState->bone_tag[0] = -1;
}
#endif
