/* * 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. * */ /* Modifications Copyright 2003-2004 Bytonic Software Copyright 2010 Google Inc. */ package com.googlecode.gwtquake.shared.server; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteOrder; import com.google.gwt.user.client.Command; import com.googlecode.gwtquake.shared.client.Client; import com.googlecode.gwtquake.shared.client.Screen; import com.googlecode.gwtquake.shared.common.*; import com.googlecode.gwtquake.shared.game.*; import com.googlecode.gwtquake.shared.sys.NET; import com.googlecode.gwtquake.shared.util.Lib; import com.googlecode.gwtquake.shared.util.Math3D; public class ServerInit { /** * SV_FindIndex. */ public static int SV_FindIndex(String name, int start, int max, boolean create) { int i; if (name == null || name.length() == 0) return 0; for (i = 1; i < max && sv.configstrings[start + i] != null; i++) if (0 == Lib.strcmp(sv.configstrings[start + i], name)) return i; if (!create) return 0; if (i == max) Com.Error(Constants.ERR_DROP, "*Index: overflow"); sv.configstrings[start + i] = name; if (sv.state != Constants.ss_loading) { // send the update to everyone sv.multicast.clear(); Buffers.writeByte(sv.multicast, Constants.svc_configstring); sv.multicast.WriteShort(start + i); Buffers.WriteString(sv.multicast, name); ServerSend.SV_Multicast(Globals.vec3_origin, Constants.MULTICAST_ALL_R); } return i; } public static int SV_ModelIndex(String name) { return SV_FindIndex(name, Constants.CS_MODELS, Constants.MAX_MODELS, true); } public static int SV_SoundIndex(String name) { return SV_FindIndex(name, Constants.CS_SOUNDS, Constants.MAX_SOUNDS, true); } public static int SV_ImageIndex(String name) { return SV_FindIndex(name, Constants.CS_IMAGES, Constants.MAX_IMAGES, true); } /** * SV_CreateBaseline * * Entity baselines are used to compress the update messages to the clients -- * only the fields that differ from the baseline will be transmitted. */ public static void SV_CreateBaseline() { Entity svent; int entnum; for (entnum = 1; entnum < GameBase.num_edicts; entnum++) { svent = GameBase.g_edicts[entnum]; if (!svent.inuse) continue; if (0 == svent.s.modelindex && 0 == svent.s.sound && 0 == svent.s.effects) continue; svent.s.number = entnum; // take current state as baseline Math3D.VectorCopy(svent.s.origin, svent.s.old_origin); sv.baselines[entnum].set(svent.s); } } /** * SV_CheckForSavegame. */ public static void SV_CheckForSavegame() { String name; RandomAccessFile f; int i; if (ServerMain.sv_noreload.value != 0) return; if (ConsoleVariables.VariableValue("deathmatch") != 0) return; name = QuakeFileSystem.Gamedir() + "/save/current/" + sv.name + ".sav"; try { f = new RandomAccessFile(name, "r"); } catch (Exception e) { Compatibility.printStackTrace(e); return; } try { f.close(); } catch (IOException e1) { Compatibility.printStackTrace(e1); } World.SV_ClearWorld(); // get configstrings and areaportals ServerCommands.SV_ReadLevelFile(); if (!sv.loadgame) { // coming back to a level after being in a different // level, so run it for ten seconds // rlava2 was sending too many lightstyles, and overflowing the // reliable data. temporarily changing the server state to loading // prevents these from being passed down. int previousState; // PGM previousState = sv.state; // PGM sv.state = Constants.ss_loading; // PGM for (i = 0; i < 100; i++) GameBase.G_RunFrame(); sv.state = previousState; // PGM } } /** * SV_SpawnServer. * * Change the server to a new map, taking all connected clients along with * it. */ public static void SV_SpawnServer(String server, final String spawnpoint, final int serverstate, boolean attractloop, boolean loadgame, final Command continueCommand) { int i; int checksum = 0; if (attractloop) ConsoleVariables.Set("paused", "0"); Com.Printf("------- Server Initialization -------\n"); Com.DPrintf("SpawnServer: " + server + "\n"); sv.demofile = null; // any partially connected client will be restarted svs.spawncount++; sv.state = Constants.ss_dead; Globals.server_state = sv.state; // wipe the entire per-level structure sv = new ServerState(); svs.realtime = 0; sv.loadgame = loadgame; sv.attractloop = attractloop; // save name for levels that don't set message sv.configstrings[Constants.CS_NAME] = server; if (ConsoleVariables.VariableValue("deathmatch") != 0) { sv.configstrings[Constants.CS_AIRACCEL] = "" + ServerMain.sv_airaccelerate.value; PlayerMovements.pm_airaccelerate = ServerMain.sv_airaccelerate.value; } else { sv.configstrings[Constants.CS_AIRACCEL] = "0"; PlayerMovements.pm_airaccelerate = 0; } sv.multicast = Buffer.wrap(sv.multicast_buf).order(ByteOrder.LITTLE_ENDIAN); sv.name = server; // leave slots at start for clients only for (i = 0; i < ServerMain.maxclients.value; i++) { // needs to reconnect if (svs.clients[i].state > Constants.cs_connected) svs.clients[i].state = Constants.cs_connected; svs.clients[i].lastframe = -1; } sv.time = 1000; sv.name = server; sv.configstrings[Constants.CS_NAME] = server; final int iw[] = {checksum }; CM.ModelCallback onLoad = new CM.ModelCallback() { public void onSuccess(Model response) { sv.models[1] = response; int checksum = iw[0]; sv.configstrings[Constants.CS_MAPCHECKSUM] = "" + checksum; // clear physics interaction links World.SV_ClearWorld(); for (int i = 1; i < CM.CM_NumInlineModels(); i++) { sv.configstrings[Constants.CS_MODELS + 1 + i] = "*" + i; // copy references sv.models[i + 1] = CM.InlineModel(sv.configstrings[Constants.CS_MODELS + 1 + i]); } // spawn the rest of the entities on the map // precache and static commands can be issued during // map initialization sv.state = Constants.ss_loading; Globals.server_state = sv.state; // load and spawn all other entities GameSpawn.SpawnEntities(sv.name, CM.CM_EntityString(), spawnpoint); // run two frames to allow everything to settle GameBase.G_RunFrame(); GameBase.G_RunFrame(); // all precaches are complete sv.state = serverstate; Globals.server_state = sv.state; // create a baseline for more efficient communications SV_CreateBaseline(); // check for a savegame SV_CheckForSavegame(); // set serverinfo variable ConsoleVariables.FullSet("mapname", sv.name, Constants.CVAR_SERVERINFO | Constants.CVAR_NOSET); continueCommand.execute(); } }; if (serverstate != Constants.ss_game) { CM.CM_LoadMap("", false, iw, onLoad); // no real map } else { sv.configstrings[Constants.CS_MODELS + 1] = "maps/" + server + ".bsp"; CM.CM_LoadMap(sv.configstrings[Constants.CS_MODELS + 1], false, iw, onLoad); } } /** * SV_InitGame. * * A brand new game has been started. */ public static void SV_InitGame() { int i; Entity ent; //char idmaster[32]; String idmaster; if (svs.initialized) { // cause any connected clients to reconnect ServerMain.SV_Shutdown("Server restarted\n", true); } else { // make sure the client is down Client.drop(); Screen.BeginLoadingPlaque(); } // get any latched variable changes (maxclients, etc) ConsoleVariables.GetLatchedVars(); svs.initialized = true; if (ConsoleVariables.VariableValue("coop") != 0 && ConsoleVariables.VariableValue("deathmatch") != 0) { Com.Printf("Deathmatch and Coop both set, disabling Coop\n"); ConsoleVariables.FullSet("coop", "0", Constants.CVAR_SERVERINFO | Constants.CVAR_LATCH); } // dedicated servers are can't be single player and are usually DM // so unless they explicity set coop, force it to deathmatch if (Globals.dedicated.value != 0) { if (0 == ConsoleVariables.VariableValue("coop")) ConsoleVariables.FullSet("deathmatch", "1", Constants.CVAR_SERVERINFO | Constants.CVAR_LATCH); } // init clients if (ConsoleVariables.VariableValue("deathmatch") != 0) { if (ServerMain.maxclients.value <= 1) ConsoleVariables.FullSet("maxclients", "8", Constants.CVAR_SERVERINFO | Constants.CVAR_LATCH); else if (ServerMain.maxclients.value > Constants.MAX_CLIENTS) ConsoleVariables.FullSet("maxclients", "" + Constants.MAX_CLIENTS, Constants.CVAR_SERVERINFO | Constants.CVAR_LATCH); } else if (ConsoleVariables.VariableValue("coop") != 0) { if (ServerMain.maxclients.value <= 1 || ServerMain.maxclients.value > 4) ConsoleVariables.FullSet("maxclients", "4", Constants.CVAR_SERVERINFO | Constants.CVAR_LATCH); } else // non-deathmatch, non-coop is one player { ConsoleVariables.FullSet("maxclients", "1", Constants.CVAR_SERVERINFO | Constants.CVAR_LATCH); } svs.spawncount = Lib.rand(); svs.clients = new ClientData[(int) ServerMain.maxclients.value]; for (int n = 0; n < svs.clients.length; n++) { svs.clients[n] = new ClientData(); svs.clients[n].serverindex = n; } svs.num_client_entities = ((int) ServerMain.maxclients.value) * Constants.UPDATE_BACKUP * 64; //ok. svs.client_entities = new EntityState[svs.num_client_entities]; for (int n = 0; n < svs.client_entities.length; n++) svs.client_entities[n] = new EntityState(null); // init network stuff NET.Config((ServerMain.maxclients.value > 1)); // heartbeats will always be sent to the id master svs.last_heartbeat = -99999; // send immediately idmaster = "192.246.40.37:" + Constants.PORT_MASTER; NET.StringToAdr(idmaster, ServerMain.master_adr[0]); // init game ServerGame.SV_InitGameProgs(); for (i = 0; i < ServerMain.maxclients.value; i++) { ent = GameBase.g_edicts[i + 1]; svs.clients[i].edict = ent; svs.clients[i].lastcmd = new UserCommand(); } } private static String firstmap = ""; /** * SV_Map * * the full syntax is: * * map [*] <map>$ <startspot>+ <nextserver> * * command from the console or progs. Map can also be a.cin, .pcx, or .dm2 file. * * Nextserver is used to allow a cinematic to play, then proceed to * another level: * * map tram.cin+jail_e3 */ public static void SV_Map(boolean attractloop, String levelstring, boolean loadgame, final Command continueCmd) { int l; String level, spawnpoint; sv.loadgame = loadgame; sv.attractloop = attractloop; if (sv.state == Constants.ss_dead && !sv.loadgame) SV_InitGame(); // the game is just starting level = levelstring; // bis hier her ok. // if there is a + in the map, set nextserver to the remainder int c = level.indexOf('+'); if (c != -1) { ConsoleVariables.Set("nextserver", "gamemap \"" + level.substring(c + 1) + "\""); level = level.substring(0, c); } else { ConsoleVariables.Set("nextserver", ""); } // rst: base1 works for full, damo1 works for demo, so we need to store first map. if (firstmap.length() == 0) { if (!levelstring.endsWith(".cin") && !levelstring.endsWith(".pcx") && !levelstring.endsWith(".dm2")) { int pos = levelstring.indexOf('+'); firstmap = levelstring.substring(pos + 1); } } // ZOID: special hack for end game screen in coop mode if (ConsoleVariables.VariableValue("coop") != 0 && level.equals("victory.pcx")) ConsoleVariables.Set("nextserver", "gamemap \"*" + firstmap + "\""); // if there is a $, use the remainder as a spawnpoint int pos = level.indexOf('$'); if (pos != -1) { spawnpoint = level.substring(pos + 1); level = level.substring(0, pos); } else spawnpoint = ""; // skip the end-of-unit flag * if necessary if (level.charAt(0) == '*') level = level.substring(1); l = level.length(); Command continueCmd2 = new Command() { public void execute() { ServerSend.SV_BroadcastCommand("reconnect\n"); continueCmd.execute(); } }; if (l > 4 && level.endsWith(".cin")) { Screen.BeginLoadingPlaque(); // for local system ServerSend.SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, Constants.ss_cinematic, attractloop, loadgame, continueCmd2); } else if (l > 4 && level.endsWith(".dm2")) { Screen.BeginLoadingPlaque(); // for local system ServerSend.SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, Constants.ss_demo, attractloop, loadgame, continueCmd2); } else if (l > 4 && level.endsWith(".pcx")) { Screen.BeginLoadingPlaque(); // for local system ServerSend.SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, Constants.ss_pic, attractloop, loadgame, continueCmd2); } else { Screen.BeginLoadingPlaque(); // for local system ServerSend.SV_BroadcastCommand("changing\n"); ServerSend.SV_SendClientMessages(); SV_SpawnServer(level, spawnpoint, Constants.ss_game, attractloop, loadgame, new Command() { public void execute() { CommandBuffer.CopyToDefer(); ServerSend.SV_BroadcastCommand("reconnect\n"); continueCmd.execute(); } }); } } public static ServerStatic svs = new ServerStatic(); // persistant // server info public static ServerState sv = new ServerState(); // local server }