/* pr_parse.c map and savegame parsing Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include #include #include "qfalloca.h" #if defined(_WIN32) && defined(HAVE_MALLOC_H) #include #endif #include "QF/dstring.h" #include "QF/mathlib.h" #include "QF/plist.h" #include "QF/progs.h" #include "QF/script.h" #include "QF/sys.h" #include "compat.h" /* PR_UglyValueString Returns a string describing *data in a type specific manner Easier to parse than PR_ValueString */ static const char * PR_UglyValueString (progs_t *pr, etype_t type, pr_type_t *val, dstring_t *line) { pr_def_t *def; dfunction_t *f; type &= ~DEF_SAVEGLOBAL; switch (type) { case ev_string: dsprintf (line, "%s", PR_GetString (pr, PR_PTR (string, val))); break; case ev_entity: dsprintf (line, "%d", NUM_FOR_BAD_EDICT (pr, PROG_TO_EDICT (pr, PR_PTR (entity, val)))); break; case ev_func: f = pr->pr_functions + PR_PTR (func, val); dsprintf (line, "%s", PR_GetString (pr, f->name)); break; case ev_field: def = PR_FieldAtOfs (pr, PR_PTR (int, val)); dsprintf (line, "%s", PR_GetString (pr, def->name)); break; case ev_void: dstring_copystr (line, "void"); break; case ev_float: dsprintf (line, "%.9g", PR_PTR (float, val)); break; case ev_int: dsprintf (line, "%d", PR_PTR (int, val)); break; case ev_vector: dsprintf (line, "%.9g %.9g %.9g", VectorExpand (&PR_PTR (float, val))); break; case ev_quaternion: dsprintf (line, "%.9g %.9g %.9g %.9g", QuatExpand (&PR_PTR (float, val))); break; default: dsprintf (line, "bad type %i", type); break; } return line->str; } VISIBLE plitem_t * ED_EntityDict (progs_t *pr, edict_t *ed) { dstring_t *dstr = dstring_newstr (); plitem_t *entity = PL_NewDictionary (pr->hashctx); pr_uint_t i; int j; int type; const char *name; const char *value; pr_type_t *v; if (!ed->free) { for (i = 0; i < pr->progs->fielddefs.count; i++) { pr_def_t *d = &pr->pr_fielddefs[i]; name = PR_GetString (pr, d->name); if (!name[0]) continue; // skip unnamed fields if (name[strlen (name) - 2] == '_') continue; // skip _x, _y, _z vars v = &E_fld (ed, d->ofs); // if the value is still all 0, skip the field type = d->type & ~DEF_SAVEGLOBAL; for (j = 0; j < pr_type_size[type]; j++) if (v[j].value) break; if (j == pr_type_size[type]) continue; value = PR_UglyValueString (pr, type, v, dstr); PL_D_AddObject (entity, name, PL_NewString (value)); } } dstring_delete (dstr); return entity; } /* ARCHIVING GLOBALS FIXME: need to tag constants, doesn't really work */ VISIBLE plitem_t * ED_GlobalsDict (progs_t *pr) { dstring_t *dstr = dstring_newstr (); plitem_t *globals = PL_NewDictionary (pr->hashctx); pr_uint_t i; const char *name; const char *value; pr_def_t *def; int type; for (i = 0; i < pr->progs->globaldefs.count; i++) { def = &pr->pr_globaldefs[i]; type = def->type; if (!(def->type & DEF_SAVEGLOBAL)) continue; type &= ~DEF_SAVEGLOBAL; if (type != ev_string && type != ev_float && type != ev_entity) continue; name = PR_GetString (pr, def->name); value = PR_UglyValueString (pr, type, &pr->pr_globals[def->ofs], dstr); PL_D_AddObject (globals, name, PL_NewString (value)); } dstring_delete (dstr); return globals; } static int ED_NewString (progs_t *pr, const char *string) { char *new, *new_p; int i, l; l = strlen (string) + 1; new = alloca (l); new_p = new; 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 PR_SetString (pr, new); } /* ED_ParseEval Can parse either fields or globals returns false if error */ VISIBLE bool ED_ParseEpair (progs_t *pr, pr_type_t *base, pr_def_t *key, const char *s) { pr_def_t *def; pr_type_t *d; dfunction_t *func; d = &base[key->ofs]; switch (key->type & ~DEF_SAVEGLOBAL) { case ev_string: PR_PTR (string, d) = ED_NewString (pr, s); break; case ev_float: PR_PTR (float, d) = atof (s); break; case ev_vector: vec3_t vec = {}; char *str = alloca (strlen (s) + 1); strcpy (str, s); for (char *v = str; *v; v++) { if (*v == ',') { *v = ' '; } } if (sscanf (str, "%f %f %f", VectorExpandAddr (vec)) != 3) { Sys_Printf ("Malformed vector %s\n", s); } VectorCopy (vec, PR_PTR (vector, d)); break; case ev_entity: PR_PTR (entity, d) = EDICT_TO_PROG (pr, EDICT_NUM (pr, atoi (s))); break; case ev_field: def = PR_FindField (pr, s); if (!def) { Sys_Printf ("Can't find field %s\n", s); return false; } PR_PTR (int, d) = G_INT (pr, def->ofs); break; case ev_func: func = PR_FindFunction (pr, s); if (!func) { Sys_Printf ("Can't find function %s\n", s); return false; } PR_PTR (func, d) = func - pr->pr_functions; break; default: break; } return true; } /* ED_ParseOld The entities are directly placed in the array, rather than allocated with ED_Alloc, because otherwise an error loading the map would have entity number references out of order. Creates a server's entity / program execution context by parsing textual entity definitions out of an ent file. Used for both fresh maps and savegame loads. A fresh map would also need to call ED_CallSpawnFunctions () to let the objects initialize themselves. */ VISIBLE plitem_t * ED_ConvertToPlist (script_t *script, int nohack, struct hashctx_s **hashctx) { dstring_t *dstr = dstring_newstr (); plitem_t *plist = PL_NewArray (); plitem_t *ent; plitem_t *key; plitem_t *value; char *token; int anglehack; const char *msg = ""; while (Script_GetToken (script, 1)) { token = script->token->str; if (!strequal (token, "{")) { msg = "EOF without closing brace"; goto parse_error; } ent = PL_NewDictionary (hashctx); while (1) { int n; if (!Script_GetToken (script, 1)) { msg = "EOF without closing brace"; goto parse_error; } token = script->token->str; if (strequal (token, "}")) break; // hack to take care of trailing spaces in field names // (looking at you, Rogue) for (n = strlen (token); n && token[n - 1] == ' '; n--) { token[n - 1] = 0; } anglehack = 0; if (!nohack && strequal (token, "angle")) { key = PL_NewString ("angles"); anglehack = 1; } else if (!nohack && strequal (token, "light")) { key = PL_NewString ("light_lev"); } else { key = PL_NewString (token); } if (!Script_TokenAvailable (script, 0)) { msg = "EOL without value"; goto parse_error; } Script_GetToken (script, 0); token = script->token->str; if (strequal (token, "}")) { msg = "closing brace without data"; goto parse_error; } if (anglehack) { dsprintf (dstr, "0 %s 0", token); value = PL_NewString (dstr->str); } else { value = PL_NewString (token); } PL_D_AddObject (ent, PL_String (key), value); PL_Release (key); } PL_A_AddObject (plist, ent); } dstring_delete (dstr); return plist; parse_error: Sys_Printf ("%s:%d: %s", script->file, script->line, msg); dstring_delete (dstr); PL_Release (plist); return 0; } VISIBLE void ED_InitGlobals (progs_t *pr, plitem_t *globals) { pr_def_t vector_def; pr_def_t *global; plitem_t *keys; int count; const char *global_name; const char *value; keys = PL_D_AllKeys (globals); count = PL_A_NumObjects (keys); while (count--) { global_name = PL_String (PL_ObjectAtIndex (keys, count)); value = PL_String (PL_ObjectForKey (globals, global_name)); global = PR_FindGlobal (pr, global_name); //FIXME should this be here? //This is a hardcoded fix for a design mistake in the original qcc //(saving global vector components rather than the whole vector). if (!global) { int len = strlen (global_name); const char *tag = global_name + len - 2; if (len > 2 && tag[0] == '_' && strchr ("xyz", tag[1])) { char *vector_name = strdup (global_name); vector_name[len - 2] = 0; global = PR_FindGlobal (pr, vector_name); if (global) { if ((global->type & ~DEF_SAVEGLOBAL) == ev_vector) { vector_def = *global; vector_def.ofs += tag[1] - 'x'; vector_def.type = ev_float; global = &vector_def; } else { global = 0; } } } } if (!global) { Sys_Printf ("'%s' is not a global\n", global_name); continue; } if (!ED_ParseEpair (pr, pr->pr_globals, global, value)) PR_Error (pr, "ED_InitGlobals: parse error"); } PL_Release (keys); } VISIBLE void ED_InitEntity (progs_t *pr, plitem_t *entity, edict_t *ent) { pr_def_t *field; plitem_t *keys; const char *field_name; const char *value; int count; int init = 0; keys = PL_D_AllKeys (entity); count = PL_A_NumObjects (keys); while (count--) { field_name = PL_String (PL_ObjectAtIndex (keys, count)); value = PL_String (PL_ObjectForKey (entity, field_name)); field = PR_FindField (pr, field_name); if (!field) { if (!pr->parse_field || !pr->parse_field (pr, field_name, value)) { Sys_Printf ("'%s' is not a field\n", field_name); continue; } } else { if (!ED_ParseEpair (pr, &E_fld (ent, 0), field, value)) PR_Error (pr, "ED_InitEntity: parse error"); } init = 1; } PL_Release (keys); if (!init) ent->free = 1; } static void ED_SpawnEntities (progs_t *pr, plitem_t *entity_list) { edict_t *ent; int inhibit = 0; plitem_t *entity; plitem_t *item; int i; int count; const char *classname; dfunction_t *func; pr_int_t max_edicts = pr->pr_edict_area_size / pr->pr_edict_size; max_edicts -= *pr->num_edicts; count = PL_A_NumObjects (entity_list); for (i = 0; i < count; i++) { entity = PL_ObjectAtIndex (entity_list, i); item = PL_ObjectForKey (entity, "classname"); if (!item) PR_Error (pr, "no classname for entity %d", i); classname = PL_String (item); if (strequal (classname, "worldspawn")) ent = EDICT_NUM (pr, 0); else ent = ED_Alloc (pr); // don't allow the last edict to be used, as otherwise we can't detect // too many edicts if (NUM_FOR_EDICT (pr, ent) >= pr->max_edicts - 1) PR_Error (pr, "too many entities: %d > %d", count, max_edicts); ED_InitEntity (pr, entity, ent); // remove things from different skill levels or deathmatch if (pr->prune_edict && pr->prune_edict (pr, ent)) { ED_Free (pr, ent); inhibit++; continue; } //XXX should the field be checked instead of going direct? func = PR_FindFunction (pr, classname); if (!func) { Sys_Printf ("No spawn function for :\n"); ED_Print (pr, ent, 0); ED_Free (pr, ent); continue; } *pr->globals.self = EDICT_TO_PROG (pr, ent); PR_ExecuteProgram (pr, func - pr->pr_functions); if (pr->flush) pr->flush (); } } VISIBLE plitem_t * ED_Parse (progs_t *pr, const char *data) { script_t *script; plitem_t *entity_list = 0; script = Script_New (); Script_Start (script, "ent data", data); if (Script_GetToken (script, 1)) { if (strequal (script->token->str, "(")) { // new style (plist) entity data entity_list = PL_GetPropertyList (data, pr->hashctx); } else { // old style entity data Script_UngetToken (script); entity_list = ED_ConvertToPlist (script, 0, pr->hashctx); } } Script_Delete (script); return entity_list; } VISIBLE void ED_LoadFromFile (progs_t *pr, const char *data) { plitem_t *entity_list; if (pr->edict_parse) { PR_PushFrame (pr); PR_RESET_PARAMS (pr); P_INT (pr, 0) = PR_SetTempString (pr, data); pr->pr_argc = 1; PR_ExecuteProgram (pr, pr->edict_parse); PR_PopFrame (pr); return; } entity_list = ED_Parse (pr, data); if (entity_list) { ED_SpawnEntities (pr, entity_list); PL_Release (entity_list); } } VISIBLE void ED_EntityParseFunction (progs_t *pr, void *data) { pr->edict_parse = P_FUNCTION (pr, 0); }