/* * Copyright (C) 1997-2001 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 the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "g_local.h" #define Function(f) {#f, f} mmove_t mmove_reloc; field_t fields [] = { {"classname", FOFS(classname), F_LSTRING}, {"model", FOFS(model), F_LSTRING}, {"spawnflags", FOFS(spawnflags), F_INT}, {"speed", FOFS(speed), F_FLOAT}, {"accel", FOFS(accel), F_FLOAT}, {"decel", FOFS(decel), F_FLOAT}, {"target", FOFS(target), F_LSTRING}, {"targetname", FOFS(targetname), F_LSTRING}, {"pathtarget", FOFS(pathtarget), F_LSTRING}, {"deathtarget", FOFS(deathtarget), F_LSTRING}, {"killtarget", FOFS(killtarget), F_LSTRING}, {"combattarget", FOFS(combattarget), F_LSTRING}, {"message", FOFS(message), F_LSTRING}, {"team", FOFS(team), F_LSTRING}, {"wait", FOFS(wait), F_FLOAT}, {"delay", FOFS(delay), F_FLOAT}, {"random", FOFS(random), F_FLOAT}, {"move_origin", FOFS(move_origin), F_VECTOR}, {"move_angles", FOFS(move_angles), F_VECTOR}, {"style", FOFS(style), F_INT}, {"count", FOFS(count), F_INT}, {"health", FOFS(health), F_INT}, {"sounds", FOFS(sounds), F_INT}, {"light", 0, F_IGNORE}, {"dmg", FOFS(dmg), F_INT}, {"mass", FOFS(mass), F_INT}, {"volume", FOFS(volume), F_FLOAT}, {"attenuation", FOFS(attenuation), F_FLOAT}, {"map", FOFS(map), F_LSTRING}, {"origin", FOFS(s.origin), F_VECTOR}, {"angles", FOFS(s.angles), F_VECTOR}, {"angle", FOFS(s.angles), F_ANGLEHACK}, {"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN}, {"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN}, {"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN}, {"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN}, {"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN}, {"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN}, {"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN}, {"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN}, {"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN}, {"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN}, {"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN}, {"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN}, {"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN}, {"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN}, {"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN}, {"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN}, {"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN}, {"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN}, {"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN}, {"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN}, {"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN}, {"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN}, {"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN}, {"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN}, {"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN}, {"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN}, {"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN}, {"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN}, {"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN}, {"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN}, {"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN}, {"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN}, /* temp spawn vars -- only valid when the spawn function is called */ {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, /* * need for item field in edict struct, FFL_SPAWNTEMP item will be * skipped on saves */ {"item", FOFS(item), F_ITEM}, {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP}, {0, 0, 0, 0} }; field_t levelfields[] = { {"changemap", LLOFS(changemap), F_LSTRING}, {"sight_client", LLOFS(sight_client), F_EDICT}, {"sight_entity", LLOFS(sight_entity), F_EDICT}, {"sound_entity", LLOFS(sound_entity), F_EDICT}, {"sound2_entity", LLOFS(sound2_entity), F_EDICT}, {NULL, 0, F_INT} }; field_t clientfields[] = { {"pers.weapon", CLOFS(pers.weapon), F_ITEM}, {"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM}, {"newweapon", CLOFS(newweapon), F_ITEM}, {NULL, 0, F_INT} }; /* * ============ InitGame * * This will be called when the dll is first loaded, which only happens when a * new game is started or a save game is loaded. ============ */ void InitGame(void) { gi.dprintf("==== InitGame ====\n"); gun_x = gi.cvar("gun_x", "0", 0); gun_y = gi.cvar("gun_y", "0", 0); gun_z = gi.cvar("gun_z", "0", 0); /* FIXME: sv_ prefix is wrong for these */ sv_rollspeed = gi.cvar("sv_rollspeed", "200", 0); sv_rollangle = gi.cvar("sv_rollangle", "2", 0); sv_maxvelocity = gi.cvar("sv_maxvelocity", "2000", 0); sv_gravity = gi.cvar("sv_gravity", "800", 0); /* noset vars */ dedicated = gi.cvar("dedicated", "0", CVAR_NOSET); oldsave = gi.cvar("oldsave", "0", CVAR_ARCHIVE); /* latched vars */ sv_cheats = gi.cvar("cheats", "0", CVAR_SERVERINFO | CVAR_LATCH); gi.cvar("gamename", GAMEVERSION, CVAR_SERVERINFO | CVAR_LATCH); gi.cvar("gamedate", __DATE__, CVAR_SERVERINFO | CVAR_LATCH); maxclients = gi.cvar("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); maxspectators = gi.cvar("maxspectators", "4", CVAR_SERVERINFO); deathmatch = gi.cvar("deathmatch", "0", CVAR_LATCH); coop = gi.cvar("coop", "0", CVAR_LATCH); skill = gi.cvar("skill", "1", CVAR_LATCH); maxentities = gi.cvar("maxentities", "1024", CVAR_LATCH); /* change anytime vars */ dmflags = gi.cvar("dmflags", "0", CVAR_SERVERINFO); fraglimit = gi.cvar("fraglimit", "0", CVAR_SERVERINFO); timelimit = gi.cvar("timelimit", "0", CVAR_SERVERINFO); password = gi.cvar("password", "", CVAR_USERINFO); spectator_password = gi.cvar("spectator_password", "", CVAR_USERINFO); needpass = gi.cvar("needpass", "0", CVAR_SERVERINFO); filterban = gi.cvar("filterban", "1", 0); g_select_empty = gi.cvar("g_select_empty", "0", CVAR_ARCHIVE); run_pitch = gi.cvar("run_pitch", "0.002", 0); run_roll = gi.cvar("run_roll", "0.005", 0); bob_up = gi.cvar("bob_up", "0.005", 0); bob_pitch = gi.cvar("bob_pitch", "0.002", 0); bob_roll = gi.cvar("bob_roll", "0.002", 0); /* flood control */ flood_msgs = gi.cvar("flood_msgs", "4", 0); flood_persecond = gi.cvar("flood_persecond", "4", 0); flood_waitdelay = gi.cvar("flood_waitdelay", "10", 0); /* dm map list */ sv_maplist = gi.cvar("sv_maplist", "", 0); #ifdef WITH_ACEBOT /* bots */ botchat = gi.cvar("botchat", "1", CVAR_ARCHIVE); /* <-------botchat */ botauto_respawn = gi.cvar("botauto_respawn", "0", CVAR_ARCHIVE); #endif #ifdef GAME_MOD weap_shell = gi.cvar("weap_shell", "1", CVAR_ARCHIVE); alt_fire_blaster = gi.cvar("alt_fire_blaster", "0", CVAR_ARCHIVE); eject_bullets = gi.cvar("eject_bullets", "1", CVAR_ARCHIVE); eject_bullets_dm_coop = gi.cvar("eject_bullets_dm_coop", "0", 0); opt_dm_scoreboard = gi.cvar("opt_dm_scoreboard", "1", CVAR_ARCHIVE); help_min = gi.cvar("help_min", "0", CVAR_ARCHIVE); camoffset = gi.cvar("camoffset", "10", CVAR_SERVERINFO); #endif /* items */ InitItems(); Com_sprintf(game.helpmessage1, sizeof(game.helpmessage1), ""); Com_sprintf(game.helpmessage2, sizeof(game.helpmessage2), ""); /* initialize all entities for this game */ game.maxentities = maxentities->value; g_edicts = gi.TagMalloc(game.maxentities * sizeof(g_edicts[0]), TAG_GAME); globals.edicts = g_edicts; globals.max_edicts = game.maxentities; /* initialize all clients for this game */ game.maxclients = maxclients->value; game.clients = gi.TagMalloc(game.maxclients * sizeof(game.clients[0]), TAG_GAME); globals.num_edicts = game.maxclients + 1; } /* ========================================================= */ void WriteField1(FILE * f, field_t * field, byte * base) { void *p; int len; int index; if (field->flags & FFL_SPAWNTEMP) return; p = (void *)(base + field->ofs); switch (field->type) { case F_INT: case F_FLOAT: case F_ANGLEHACK: case F_VECTOR: case F_IGNORE: break; case F_LSTRING: case F_GSTRING: if (*(char **)p) len = strlen(*(char **)p) + 1; else len = 0; *(int *)p = len; break; case F_EDICT: if (*(edict_t **) p == NULL) index = -1; else index = *(edict_t **) p - g_edicts; *(int *)p = index; break; case F_CLIENT: if (*(gclient_t **) p == NULL) index = -1; else index = *(gclient_t **) p - game.clients; *(int *)p = index; break; case F_ITEM: if (*(edict_t **) p == NULL) index = -1; else index = *(gitem_t **) p - itemlist; *(int *)p = index; break; /* relative to code segment */ case F_FUNCTION: if (*(byte **) p == NULL) index = 0; else index = *(byte **) p - ((byte *) InitGame); *(int *)p = index; break; /* relative to data segment */ case F_MMOVE: if (*(byte **) p == NULL) index = 0; else index = *(byte **) p - (byte *) & mmove_reloc; *(int *)p = index; break; default: gi.error("WriteEdict: unknown field type"); } } void WriteField2(FILE * f, field_t * field, byte * base) { int len; void *p; if (field->flags & FFL_SPAWNTEMP) return; p = (void *)(base + field->ofs); switch (field->type) { case F_LSTRING: if (*(char **)p) { len = strlen(*(char **)p) + 1; fwrite(*(char **)p, len, 1, f); } break; default: break; } } void ReadField(FILE * f, field_t * field, byte * base) { void *p; int len; int index; if (field->flags & FFL_SPAWNTEMP) return; p = (void *)(base + field->ofs); switch (field->type) { case F_INT: case F_FLOAT: case F_ANGLEHACK: case F_VECTOR: case F_IGNORE: break; case F_LSTRING: len = *(int *)p; if (!len) *(char **)p = NULL; else { /* * SBF: FIXME - 32 extra bytes alloc'd since the * saved string might not be long enough */ *(char **)p = gi.TagMalloc(32 + len, TAG_LEVEL); fread(*(char **)p, len, 1, f); } break; case F_EDICT: index = *(int *)p; if (index == -1) *(edict_t **) p = NULL; else *(edict_t **) p = &g_edicts[index]; break; case F_CLIENT: index = *(int *)p; if (index == -1) *(gclient_t **) p = NULL; else *(gclient_t **) p = &game.clients[index]; break; case F_ITEM: index = *(int *)p; if (index == -1) *(gitem_t **) p = NULL; else *(gitem_t **) p = &itemlist[index]; break; /* relative to code segment */ case F_FUNCTION: index = *(int *)p; if (index == 0) *(byte **) p = NULL; else *(byte **) p = ((byte *) InitGame) + index; break; /* relative to data segment */ case F_MMOVE: index = *(int *)p; if (index == 0) *(byte **) p = NULL; else *(byte **) p = (byte *) & mmove_reloc + index; break; default: gi.error("ReadEdict: unknown field type"); } } /* ========================================================= */ /* * ============== WriteClient * * All pointer variables (except function pointers) must be handled specially. * ============== */ void WriteClient(FILE * f, gclient_t * client) { field_t *field; gclient_t temp; /* all of the ints, floats, and vectors stay as they are */ temp = *client; /* change the pointers to lengths or indexes */ for (field = clientfields; field->name; field++) { WriteField1(f, field, (byte *) & temp); } /* write the block */ fwrite(&temp, sizeof(temp), 1, f); /* now write any allocated data following the edict */ for (field = clientfields; field->name; field++) { WriteField2(f, field, (byte *) client); } } /* * ============== ReadClient * * All pointer variables (except function pointers) must be handled specially. * ============== */ void ReadClient(FILE * f, gclient_t * client) { field_t *field; fread(client, sizeof(*client), 1, f); for (field = clientfields; field->name; field++) { ReadField(f, field, (byte *) client); } } /* * ============ WriteGame * * This will be called whenever the game goes to a new level, and when the user * explicitly saves the game. * * Game information include cross level data, like multi level triggers, help * computer info, and all client states. * * A single player death will automatically restore from the last save position. * ============ */ void WriteGame(char *filename, qboolean autosave) { FILE *f; int i; char str [16]; if (!autosave) SaveClientData(); f = fopen(filename, "wb"); if (!f) gi.error("Couldn't open %s", filename); memset(str, 0, sizeof(str)); /* strcpy (str, __DATE__); */ strcpy(str, "Oct 16 1998"); /* HACK */ fwrite(str, sizeof(str), 1, f); game.autosaved = autosave; fwrite(&game, sizeof(game), 1, f); game.autosaved = false; for (i = 0; i < game.maxclients; i++) WriteClient(f, &game.clients[i]); fclose(f); } void ReadGame(char *filename) { FILE *f; int i; char str [16]; gi.FreeTags(TAG_GAME); f = fopen(filename, "rb"); if (!f) gi.error("Couldn't open %s", filename); fread(str, sizeof(str), 1, f); /* if (strcmp (str, __DATE__)) */ /* if (strcmp(str, "Oct 16 1998")) // HACK */ if (!oldsave->value && strcmp(str, "Oct 16 1998")) { fclose(f); gi.error("Savegame from an older version :P\n"); } g_edicts = gi.TagMalloc(game.maxentities * sizeof(g_edicts[0]), TAG_GAME); globals.edicts = g_edicts; fread(&game, sizeof(game), 1, f); game.clients = gi.TagMalloc(game.maxclients * sizeof(game.clients[0]), TAG_GAME); for (i = 0; i < game.maxclients; i++) ReadClient(f, &game.clients[i]); fclose(f); } /* ========================================================== */ /* * ============== WriteEdict * * All pointer variables (except function pointers) must be handled specially. * ============== */ void WriteEdict(FILE * f, edict_t * ent) { field_t *field; edict_t temp; /* all of the ints, floats, and vectors stay as they are */ temp = *ent; /* change the pointers to lengths or indexes */ for (field = fields; field->name; field++) { WriteField1(f, field, (byte *) & temp); } /* write the block */ fwrite(&temp, sizeof(temp), 1, f); /* now write any allocated data following the edict */ for (field = fields; field->name; field++) { WriteField2(f, field, (byte *) ent); } } /* * ============== WriteLevelLocals * * All pointer variables (except function pointers) must be handled specially. * ============== */ void WriteLevelLocals(FILE * f) { field_t *field; level_locals_t temp; /* all of the ints, floats, and vectors stay as they are */ temp = level; /* change the pointers to lengths or indexes */ for (field = levelfields; field->name; field++) { WriteField1(f, field, (byte *) & temp); } /* write the block */ fwrite(&temp, sizeof(temp), 1, f); /* now write any allocated data following the edict */ for (field = levelfields; field->name; field++) { WriteField2(f, field, (byte *) & level); } } /* * ============== ReadEdict * * All pointer variables (except function pointers) must be handled specially. * ============== */ void ReadEdict(FILE * f, edict_t * ent) { field_t *field; fread(ent, sizeof(*ent), 1, f); for (field = fields; field->name; field++) { ReadField(f, field, (byte *) ent); } } /* * ============== ReadLevelLocals * * All pointer variables (except function pointers) must be handled specially. * ============== */ void ReadLevelLocals(FILE * f) { field_t *field; fread(&level, sizeof(level), 1, f); for (field = levelfields; field->name; field++) { ReadField(f, field, (byte *) & level); } } /* * ================= WriteLevel * * ================= */ void WriteLevel(char *filename) { int i; edict_t *ent; FILE *f; void *base; f = fopen(filename, "wb"); if (!f) gi.error("Couldn't open %s", filename); /* write out edict size for checking */ i = sizeof(edict_t); fwrite(&i, sizeof(i), 1, f); /* write out a function pointer for checking */ /* base = (void *)InitGame; */ base = (void *)0x20011cb0; fwrite(&base, sizeof(base), 1, f); /* write out level_locals_t */ WriteLevelLocals(f); /* write out all the entities */ for (i = 0; i < globals.num_edicts; i++) { ent = &g_edicts[i]; if (!ent->inuse) continue; fwrite(&i, sizeof(i), 1, f); WriteEdict(f, ent); } i = -1; fwrite(&i, sizeof(i), 1, f); fclose(f); } /* * ================= ReadLevel * * SpawnEntities will allready have been called on the level the same way it was * when the level was saved. * * That is necessary to get the baselines set up identically. * * The server will have cleared all of the world links before calling ReadLevel. * * No clients are connected yet. ================= */ void ReadLevel(char *filename) { int entnum; FILE *f; int i; void *base; edict_t *ent; f = fopen(filename, "rb"); if (!f) gi.error("Couldn't open %s", filename); /* free any dynamic memory allocated by loading the level */ /* base state */ gi.FreeTags(TAG_LEVEL); /* wipe all the entities */ memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0])); globals.num_edicts = maxclients->value + 1; /* check edict size */ fread(&i, sizeof(i), 1, f); if (i != sizeof(edict_t)) { fclose(f); gi.error("ReadLevel: mismatched edict size"); } /* check function pointer base address */ fread(&base, sizeof(base), 1, f); /* load the level locals */ ReadLevelLocals(f); /* load all the entities */ while (1) { if (fread(&entnum, sizeof(entnum), 1, f) != 1) { fclose(f); gi.error("ReadLevel: failed to read entnum"); } if (entnum == -1) break; if (entnum >= globals.num_edicts) globals.num_edicts = entnum + 1; ent = &g_edicts[entnum]; ReadEdict(f, ent); /* let the server rebuild world links for this ent */ memset(&ent->area, 0, sizeof(ent->area)); gi.linkentity(ent); } fclose(f); /* mark all clients as unconnected */ for (i = 0; i < maxclients->value; i++) { ent = &g_edicts[i + 1]; ent->client = game.clients + i; ent->client->pers.connected = false; } /* do any load time things at this point */ for (i = 0; i < globals.num_edicts; i++) { ent = &g_edicts[i]; if (!ent->inuse) continue; /* fire any cross-level triggers */ if (ent->classname) if (strcmp(ent->classname, "target_crosslevel_target") == 0) ent->nextthink = level.time + ent->delay; } }