/*
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.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.util.Calendar;
import com.google.gwt.user.client.Command;
import com.googlecode.gwtquake.shared.common.Buffer;
import com.googlecode.gwtquake.shared.common.Buffers;
import com.googlecode.gwtquake.shared.common.CM;
import com.googlecode.gwtquake.shared.common.Com;
import com.googlecode.gwtquake.shared.common.Compatibility;
import com.googlecode.gwtquake.shared.common.ConsoleVariables;
import com.googlecode.gwtquake.shared.common.Constants;
import com.googlecode.gwtquake.shared.common.ExecutableCommand;
import com.googlecode.gwtquake.shared.common.Globals;
import com.googlecode.gwtquake.shared.common.NetworkAddress;
import com.googlecode.gwtquake.shared.common.NetworkChannel;
import com.googlecode.gwtquake.shared.common.QuakeFileSystem;
import com.googlecode.gwtquake.shared.game.Commands;
import com.googlecode.gwtquake.shared.game.ConsoleVariable;
import com.googlecode.gwtquake.shared.game.EndianHandler;
import com.googlecode.gwtquake.shared.game.GameSVCmds;
import com.googlecode.gwtquake.shared.game.GameSave;
import com.googlecode.gwtquake.shared.game.Info;
import com.googlecode.gwtquake.shared.sys.NET;
import com.googlecode.gwtquake.shared.sys.Sys;
import com.googlecode.gwtquake.shared.util.Lib;
import com.googlecode.gwtquake.shared.util.QuakeFile;
import com.googlecode.gwtquake.shared.util.Vargs;
public class ServerCommands {
/*
===============================================================================
OPERATOR CONSOLE ONLY COMMANDS
These commands can only be entered from stdin or by a remote operator datagram
===============================================================================
*/
private static final Command EMPTY_COMMAND = new Command() {
public void execute() {
}
};
/*
====================
SV_SetMaster_f
Specify a list of master servers
====================
*/
public static void SV_SetMaster_f() {
int i, slot;
// only dedicated servers send heartbeats
if (Globals.dedicated.value == 0) {
Com.Printf("Only dedicated servers use masters.\n");
return;
}
// make sure the server is listed public
ConsoleVariables.Set("public", "1");
for (i = 1; i < Constants.MAX_MASTERS; i++)
ServerMain.master_adr[i] = new NetworkAddress();
slot = 1; // slot 0 will always contain the id master
for (i = 1; i < Commands.Argc(); i++) {
if (slot == Constants.MAX_MASTERS)
break;
if (!NET.StringToAdr(Commands.Argv(i), ServerMain.master_adr[i])) {
Com.Printf("Bad address: " + Commands.Argv(i) + "\n");
continue;
}
if (ServerMain.master_adr[slot].port == 0)
ServerMain.master_adr[slot].port = Constants.PORT_MASTER;
Com.Printf("Master server at " + NET.AdrToString(ServerMain.master_adr[slot]) + "\n");
Com.Printf("Sending a ping.\n");
NetworkChannel.OutOfBandPrint(Constants.NS_SERVER, ServerMain.master_adr[slot], "ping");
slot++;
}
ServerInit.svs.last_heartbeat = -9999999;
}
/*
==================
SV_SetPlayer
Sets sv_client and sv_player to the player with idnum Cmd.Argv(1)
==================
*/
public static boolean SV_SetPlayer() {
ClientData cl;
int i;
int idnum;
String s;
if (Commands.Argc() < 2)
return false;
s = Commands.Argv(1);
// numeric values are just slot numbers
if (s.charAt(0) >= '0' && s.charAt(0) <= '9') {
idnum = Lib.atoi(Commands.Argv(1));
if (idnum < 0 || idnum >= ServerMain.maxclients.value) {
Com.Printf("Bad client slot: " + idnum + "\n");
return false;
}
ServerMain.sv_client = ServerInit.svs.clients[idnum];
User.sv_player = ServerMain.sv_client.edict;
if (0 == ServerMain.sv_client.state) {
Com.Printf("Client " + idnum + " is not active\n");
return false;
}
return true;
}
// check for a name match
for (i = 0; i < ServerMain.maxclients.value; i++) {
cl = ServerInit.svs.clients[i];
if (0 == cl.state)
continue;
if (0 == Lib.strcmp(cl.name, s)) {
ServerMain.sv_client = cl;
User.sv_player = ServerMain.sv_client.edict;
return true;
}
}
Com.Printf("Userid " + s + " is not on the server\n");
return false;
}
/*
===============================================================================
SAVEGAME FILES
===============================================================================
*/
public static void remove(String name) {
try {
new File(name).delete();
}
catch (Exception e) {
Compatibility.printStackTrace(e);
}
}
/** Delete save files save/(number)/. */
public static void SV_WipeSavegame(String savename) {
String name;
Com.DPrintf("SV_WipeSaveGame(" + savename + ")\n");
name = QuakeFileSystem.Gamedir() + "/save/" + savename + "/server.ssv";
remove(name);
name = QuakeFileSystem.Gamedir() + "/save/" + savename + "/game.ssv";
remove(name);
name = QuakeFileSystem.Gamedir() + "/save/" + savename + "/*.sav";
File f = Sys.FindFirst(name, 0, 0);
while (f != null) {
f.delete();
f = Sys.FindNext();
}
Sys.FindClose();
name = QuakeFileSystem.Gamedir() + "/save/" + savename + "/*.sv2";
f = Sys.FindFirst(name, 0, 0);
while (f != null) {
f.delete();
f = Sys.FindNext();
}
Sys.FindClose();
}
/*
================
CopyFile
================
*/
public static void CopyFile(String src, String dst) {
RandomAccessFile f1, f2;
int l = -1;
byte buffer[] = new byte[65536];
//Com.DPrintf("CopyFile (" + src + ", " + dst + ")\n");
try {
f1 = new RandomAccessFile(src, "r");
}
catch (Exception e) {
Compatibility.printStackTrace(e);
return;
}
try {
f2 = new RandomAccessFile(dst, "rw");
}
catch (Exception e) {
try {
f1.close();
}
catch (IOException e1) {
Compatibility.printStackTrace(e1);
}
return;
}
while (true) {
try {
l = f1.read(buffer, 0, 65536);
}
catch (IOException e1) {
Compatibility.printStackTrace(e1);
}
if (l == -1)
break;
try {
f2.write(buffer, 0, l);
}
catch (IOException e2) {
Compatibility.printStackTrace(e2);
}
}
try {
f1.close();
}
catch (IOException e1) {
Compatibility.printStackTrace(e1);
}
try {
f2.close();
}
catch (IOException e2) {
Compatibility.printStackTrace(e2);
}
}
/*
================
SV_CopySaveGame
================
*/
public static void SV_CopySaveGame(String src, String dst) {
//char name[MAX_OSPATH], name2[MAX_OSPATH];
int l, len;
File found;
String name, name2;
Com.DPrintf("SV_CopySaveGame(" + src + "," + dst + ")\n");
SV_WipeSavegame(dst);
// copy the savegame over
name = QuakeFileSystem.Gamedir() + "/save/" + src + "/server.ssv";
name2 = QuakeFileSystem.Gamedir() + "/save/" + dst + "/server.ssv";
QuakeFileSystem.CreatePath(name2);
CopyFile(name, name2);
name = QuakeFileSystem.Gamedir() + "/save/" + src + "/game.ssv";
name2 = QuakeFileSystem.Gamedir() + "/save/" + dst + "/game.ssv";
CopyFile(name, name2);
String name1 = QuakeFileSystem.Gamedir() + "/save/" + src + "/";
len = name1.length();
name = QuakeFileSystem.Gamedir() + "/save/" + src + "/*.sav";
found = Sys.FindFirst(name, 0, 0);
while (found != null) {
name = name1 + found.getName();
name2 = QuakeFileSystem.Gamedir() + "/save/" + dst + "/" + found.getName();
CopyFile(name, name2);
// change sav to sv2
name = name.substring(0, name.length() - 3) + "sv2";
name2 = name2.substring(0, name2.length() - 3) + "sv2";
CopyFile(name, name2);
found = Sys.FindNext();
}
Sys.FindClose();
}
/*
==============
SV_WriteLevelFile
==============
*/
public static void SV_WriteLevelFile() {
String name;
QuakeFile f;
Com.DPrintf("SV_WriteLevelFile()\n");
name = QuakeFileSystem.Gamedir() + "/save/current/" + ServerInit.sv.name + ".sv2";
try {
f = new QuakeFile(name, "rw");
for (int i = 0; i < Constants.MAX_CONFIGSTRINGS; i++)
f.writeString(ServerInit.sv.configstrings[i]);
CM.CM_WritePortalState(f);
f.close();
}
catch (Exception e) {
Com.Printf("Failed to open " + name + "\n");
Compatibility.printStackTrace(e);
}
name = QuakeFileSystem.Gamedir() + "/save/current/" + ServerInit.sv.name + ".sav";
GameSave.WriteLevel(name);
}
/*
==============
SV_ReadLevelFile
==============
*/
public static void SV_ReadLevelFile() {
//char name[MAX_OSPATH];
String name;
QuakeFile f;
Com.DPrintf("SV_ReadLevelFile()\n");
name = QuakeFileSystem.Gamedir() + "/save/current/" + ServerInit.sv.name + ".sv2";
try {
f = new QuakeFile(name, "r");
for (int n = 0; n < Constants.MAX_CONFIGSTRINGS; n++)
ServerInit.sv.configstrings[n] = f.readString();
CM.CM_ReadPortalState(f);
f.close();
}
catch (IOException e1) {
Com.Printf("Failed to open " + name + "\n");
Compatibility.printStackTrace(e1);
}
name = QuakeFileSystem.Gamedir() + "/save/current/" + ServerInit.sv.name + ".sav";
GameSave.ReadLevel(name);
}
/*
==============
SV_WriteServerFile
==============
*/
public static void SV_WriteServerFile(boolean autosave) {
QuakeFile f;
ConsoleVariable var;
String filename, name, string, comment;
Com.DPrintf("SV_WriteServerFile(" + (autosave ? "true" : "false") + ")\n");
filename = QuakeFileSystem.Gamedir() + "/save/current/server.ssv";
try {
f = new QuakeFile(filename, "rw");
if (!autosave) {
Calendar c = Calendar.getInstance();
comment =
Com.sprintf(
"%2i:%2i %2i/%2i ",
new Vargs().add(c.get(Calendar.HOUR_OF_DAY)).add(c.get(Calendar.MINUTE)).add(
c.get(Calendar.MONTH) + 1).add(
c.get(Calendar.DAY_OF_MONTH)));
comment += ServerInit.sv.configstrings[Constants.CS_NAME];
}
else {
// autosaved
comment = "ENTERING " + ServerInit.sv.configstrings[Constants.CS_NAME];
}
f.writeString(comment);
f.writeString(ServerInit.svs.mapcmd);
// write the mapcmd
// write all CVAR_LATCH cvars
// these will be things like coop, skill, deathmatch, etc
for (var = Globals.cvar_vars; var != null; var = var.next) {
if (0 == (var.flags & Constants.CVAR_LATCH))
continue;
if (var.name.length() >= Constants.MAX_OSPATH - 1 || var.string.length() >= 128 - 1) {
Com.Printf("Cvar too long: " + var.name + " = " + var.string + "\n");
continue;
}
name = var.name;
string = var.string;
try {
f.writeString(name);
f.writeString(string);
}
catch (IOException e2) {
}
}
// rst: for termination.
f.writeString(null);
f.close();
}
catch (Exception e) {
Compatibility.printStackTrace(e);
Com.Printf("Couldn't write " + filename + "\n");
}
// write game state
filename = QuakeFileSystem.Gamedir() + "/save/current/game.ssv";
GameSave.WriteGame(filename, autosave);
}
/*
==============
SV_ReadServerFile
==============
*/
public static void SV_ReadServerFile() {
String filename="", name = "", string, comment, mapcmd;
try {
QuakeFile f;
mapcmd = "";
Com.DPrintf("SV_ReadServerFile()\n");
filename = QuakeFileSystem.Gamedir() + "/save/current/server.ssv";
f = new QuakeFile(filename, "r");
// read the comment field
comment = f.readString();
// read the mapcmd
mapcmd = f.readString();
// read all CVAR_LATCH cvars
// these will be things like coop, skill, deathmatch, etc
while (true) {
name = f.readString();
if (name == null)
break;
string = f.readString();
Com.DPrintf("Set " + name + " = " + string + "\n");
ConsoleVariables.ForceSet(name, string);
}
f.close();
// start a new game fresh with new cvars
ServerInit.SV_InitGame();
ServerInit.svs.mapcmd = mapcmd;
// read game state
filename = QuakeFileSystem.Gamedir() + "/save/current/game.ssv";
GameSave.ReadGame(filename);
}
catch (Exception e) {
Com.Printf("Couldn't read file " + filename + "\n");
Compatibility.printStackTrace(e);
}
}
//=========================================================
/*
==================
SV_DemoMap_f
Puts the server in demo mode on a specific map/cinematic
==================
*/
public static void SV_DemoMap_f() {
ServerInit.SV_Map(true, Commands.Argv(1), false, EMPTY_COMMAND);
}
/*
==================
SV_GameMap_f
Saves the state of the map just being exited and goes to a new map.
If the initial character of the map string is '*', the next map is
in a new unit, so the current savegame directory is cleared of
map files.
Example:
*inter.cin+jail
Clears the archived maps, plays the inter.cin cinematic, then
goes to map jail.bsp.
==================
*/
public static void SV_GameMap_f() {
String map;
int i;
ClientData cl;
boolean savedInuse[];
if (Commands.Argc() != 2) {
Com.Printf("USAGE: gamemap <map>\n");
return;
}
Com.DPrintf("SV_GameMap(" + Commands.Argv(1) + ")\n");
QuakeFileSystem.CreatePath(QuakeFileSystem.Gamedir() + "/save/current/");
// check for clearing the current savegame
map = Commands.Argv(1);
if (map.charAt(0) == '*') {
// wipe all the *.sav files
SV_WipeSavegame("current");
}
else { // save the map just exited
if (ServerInit.sv.state == Constants.ss_game) {
// clear all the client inuse flags before saving so that
// when the level is re-entered, the clients will spawn
// at spawn points instead of occupying body shells
savedInuse = new boolean[(int) ServerMain.maxclients.value];
for (i = 0; i < ServerMain.maxclients.value; i++) {
cl = ServerInit.svs.clients[i];
savedInuse[i] = cl.edict.inuse;
cl.edict.inuse = false;
}
SV_WriteLevelFile();
// we must restore these for clients to transfer over correctly
for (i = 0; i < ServerMain.maxclients.value; i++) {
cl = ServerInit.svs.clients[i];
cl.edict.inuse = savedInuse[i];
}
savedInuse = null;
}
}
Command continueCmd = new Command() {
public void execute() {
// archive server state
ServerInit.svs.mapcmd = Commands.Argv(1);
// copy off the level to the autosave slot
if (0 == Globals.dedicated.value) {
SV_WriteServerFile(true);
SV_CopySaveGame("current", "save0");
}
}
};
// start up the next map
ServerInit.SV_Map(false, Commands.Argv(1), false, continueCmd);
}
/*
==================
SV_Map_f
Goes directly to a given map without any savegame archiving.
For development work
==================
*/
public static void SV_Map_f() {
String map;
//char expanded[MAX_QPATH];
String expanded;
// if not a pcx, demo, or cinematic, check to make sure the level exists
map = Commands.Argv(1);
// if (map.indexOf(".") < 0) {
// expanded = "maps/" + map + ".bsp";
// if (FS.LoadFile(expanded) == null) {
//
// Com.Printf("Can't find " + expanded + "\n");
// return;
// }
// }
ServerInit.sv.state = Constants.ss_dead; // don't save current level when changing
SV_WipeSavegame("current");
SV_GameMap_f();
}
/*
=====================================================================
SAVEGAMES
=====================================================================
*/
/*
==============
SV_Loadgame_f
==============
*/
public static void SV_Loadgame_f() {
String name;
RandomAccessFile f;
String dir;
if (Commands.Argc() != 2) {
Com.Printf("USAGE: loadgame <directory>\n");
return;
}
Com.Printf("Loading game...\n");
dir = Commands.Argv(1);
if ( (dir.indexOf("..") > -1) || (dir.indexOf("/") > -1) || (dir.indexOf("\\") > -1)) {
Com.Printf("Bad savedir.\n");
}
// make sure the server.ssv file exists
name = QuakeFileSystem.Gamedir() + "/save/" + Commands.Argv(1) + "/server.ssv";
try {
f = new RandomAccessFile(name, "r");
}
catch (FileNotFoundException e) {
Com.Printf("No such savegame: " + name + "\n");
Compatibility.printStackTrace(e);
return;
}
try {
f.close();
}
catch (IOException e1) {
Compatibility.printStackTrace(e1);
}
SV_CopySaveGame(Commands.Argv(1), "current");
SV_ReadServerFile();
// go to the map
ServerInit.sv.state = Constants.ss_dead; // don't save current level when changing
ServerInit.SV_Map(false, ServerInit.svs.mapcmd, true, EMPTY_COMMAND);
}
/*
==============
SV_Savegame_f
==============
*/
public static void SV_Savegame_f() {
String dir;
if (ServerInit.sv.state != Constants.ss_game) {
Com.Printf("You must be in a game to save.\n");
return;
}
if (Commands.Argc() != 2) {
Com.Printf("USAGE: savegame <directory>\n");
return;
}
if (ConsoleVariables.VariableValue("deathmatch") != 0) {
Com.Printf("Can't savegame in a deathmatch\n");
return;
}
if (0 == Lib.strcmp(Commands.Argv(1), "current")) {
Com.Printf("Can't save to 'current'\n");
return;
}
if (ServerMain.maxclients.value == 1 && ServerInit.svs.clients[0].edict.client.ps.stats[Constants.STAT_HEALTH] <= 0) {
Com.Printf("\nCan't savegame while dead!\n");
return;
}
dir = Commands.Argv(1);
if ( (dir.indexOf("..") > -1) || (dir.indexOf("/") > -1) || (dir.indexOf("\\") > -1)) {
Com.Printf("Bad savedir.\n");
}
Com.Printf("Saving game...\n");
// archive current level, including all client edicts.
// when the level is reloaded, they will be shells awaiting
// a connecting client
SV_WriteLevelFile();
// save server state
try {
SV_WriteServerFile(false);
}
catch (Exception e) {
Com.Printf("IOError in SV_WriteServerFile: " + e);
Compatibility.printStackTrace(e);
}
// copy it off
SV_CopySaveGame("current", dir);
Com.Printf("Done.\n");
}
//===============================================================
/*
==================
SV_Kick_f
Kick a user off of the server
==================
*/
public static void SV_Kick_f() {
if (!ServerInit.svs.initialized) {
Com.Printf("No server running.\n");
return;
}
if (Commands.Argc() != 2) {
Com.Printf("Usage: kick <userid>\n");
return;
}
if (!SV_SetPlayer())
return;
ServerSend.SV_BroadcastPrintf(Constants.PRINT_HIGH, ServerMain.sv_client.name + " was kicked\n");
// print directly, because the dropped client won't get the
// SV_BroadcastPrintf message
ServerSend.SV_ClientPrintf(ServerMain.sv_client, Constants.PRINT_HIGH, "You were kicked from the game\n");
ServerMain.SV_DropClient(ServerMain.sv_client);
ServerMain.sv_client.lastmessage = ServerInit.svs.realtime; // min case there is a funny zombie
}
/*
================
SV_Status_f
================
*/
public static void SV_Status_f() {
int i, j, l;
ClientData cl;
String s;
int ping;
if (ServerInit.svs.clients == null) {
Com.Printf("No server running.\n");
return;
}
Com.Printf("map : " + ServerInit.sv.name + "\n");
Com.Printf("num score ping name lastmsg address qport \n");
Com.Printf("--- ----- ---- --------------- ------- --------------------- ------\n");
for (i = 0; i < ServerMain.maxclients.value; i++) {
cl = ServerInit.svs.clients[i];
if (0 == cl.state)
continue;
Com.Printf("%3i ", new Vargs().add(i));
Com.Printf("%5i ", new Vargs().add(cl.edict.client.ps.stats[Constants.STAT_FRAGS]));
if (cl.state == Constants.cs_connected)
Com.Printf("CNCT ");
else if (cl.state == Constants.cs_zombie)
Com.Printf("ZMBI ");
else {
ping = cl.ping < 9999 ? cl.ping : 9999;
Com.Printf("%4i ", new Vargs().add(ping));
}
Com.Printf("%s", new Vargs().add(cl.name));
l = 16 - cl.name.length();
for (j = 0; j < l; j++)
Com.Printf(" ");
Com.Printf("%7i ", new Vargs().add(ServerInit.svs.realtime - cl.lastmessage));
s = NET.AdrToString(cl.netchan.remote_address);
Com.Printf(s);
l = 22 - s.length();
for (j = 0; j < l; j++)
Com.Printf(" ");
Com.Printf("%5i", new Vargs().add(cl.netchan.qport));
Com.Printf("\n");
}
Com.Printf("\n");
}
/*
==================
SV_ConSay_f
==================
*/
public static void SV_ConSay_f() {
ClientData client;
int j;
String p;
String text; // char[1024];
if (Commands.Argc() < 2)
return;
text = "console: ";
p = Commands.Args();
if (p.charAt(0) == '"') {
p = p.substring(1, p.length() - 1);
}
text += p;
for (j = 0; j < ServerMain.maxclients.value; j++) {
client = ServerInit.svs.clients[j];
if (client.state != Constants.cs_spawned)
continue;
ServerSend.SV_ClientPrintf(client, Constants.PRINT_CHAT, text + "\n");
}
}
/*
==================
SV_Heartbeat_f
==================
*/
public static void SV_Heartbeat_f() {
ServerInit.svs.last_heartbeat = -9999999;
}
/*
===========
SV_Serverinfo_f
Examine or change the serverinfo string
===========
*/
public static void SV_Serverinfo_f() {
Com.Printf("Server info settings:\n");
Info.Print(ConsoleVariables.Serverinfo());
}
/*
===========
SV_DumpUser_f
Examine all a users info strings
===========
*/
public static void SV_DumpUser_f() {
if (Commands.Argc() != 2) {
Com.Printf("Usage: info <userid>\n");
return;
}
if (!SV_SetPlayer())
return;
Com.Printf("userinfo\n");
Com.Printf("--------\n");
Info.Print(ServerMain.sv_client.userinfo);
}
/*
==============
SV_ServerRecord_f
Begins server demo recording. Every entity and every message will be
recorded, but no playerinfo will be stored. Primarily for demo merging.
==============
*/
public static void SV_ServerRecord_f() {
//char name[MAX_OSPATH];
String name;
int len;
int i;
if (Commands.Argc() != 2) {
Com.Printf("serverrecord <demoname>\n");
return;
}
if (ServerInit.svs.demofile != null) {
Com.Printf("Already recording.\n");
return;
}
if (ServerInit.sv.state != Constants.ss_game) {
Com.Printf("You must be in a level to record.\n");
return;
}
//
// open the demo file
//
name = QuakeFileSystem.Gamedir() + "/demos/" + Commands.Argv(1) + ".dm2";
Com.Printf("recording to " + name + ".\n");
QuakeFileSystem.CreatePath(name);
try {
ServerInit.svs.demofile = new RandomAccessFile(name, "rw");
}
catch (Exception e) {
Com.Printf("ERROR: couldn't open.\n");
Compatibility.printStackTrace(e);
return;
}
// setup a buffer to catch all multicasts
ServerInit.svs.demo_multicast = Buffer.wrap(ServerInit.svs.demo_multicast_buf).order(ByteOrder.LITTLE_ENDIAN);
//
// write a single giant fake message with all the startup info
//
Buffer buf = Buffer.allocate(32768).order(ByteOrder.LITTLE_ENDIAN);
//
// serverdata needs to go over for all types of servers
// to make sure the protocol is right, and to set the gamedir
//
// send the serverdata
Buffers.writeByte(buf, Constants.svc_serverdata);
buf.putInt(Constants.PROTOCOL_VERSION);
buf.putInt(ServerInit.svs.spawncount);
// 2 means server demo
Buffers.writeByte(buf, 2); // demos are always attract loops
Buffers.WriteString(buf, ConsoleVariables.VariableString("gamedir"));
buf.WriteShort(-1);
// send full levelname
Buffers.WriteString(buf, ServerInit.sv.configstrings[Constants.CS_NAME]);
for (i = 0; i < Constants.MAX_CONFIGSTRINGS; i++)
if (ServerInit.sv.configstrings[i].length() == 0) {
Buffers.writeByte(buf, Constants.svc_configstring);
buf.WriteShort(i);
Buffers.WriteString(buf, ServerInit.sv.configstrings[i]);
}
// write it to the demo file
Com.DPrintf("signon message length: " + buf.cursize + "\n");
len = EndianHandler.swapInt(buf.cursize);
//fwrite(len, 4, 1, svs.demofile);
//fwrite(buf.data, buf.cursize, 1, svs.demofile);
try {
ServerInit.svs.demofile.writeInt(len);
ServerInit.svs.demofile.write(buf.data, 0, buf.cursize);
}
catch (IOException e1) {
// TODO: do quake2 error handling!
Compatibility.printStackTrace(e1);
}
// the rest of the demo file will be individual frames
}
/*
==============
SV_ServerStop_f
Ends server demo recording
==============
*/
public static void SV_ServerStop_f() {
if (ServerInit.svs.demofile == null) {
Com.Printf("Not doing a serverrecord.\n");
return;
}
try {
ServerInit.svs.demofile.close();
}
catch (IOException e) {
Compatibility.printStackTrace(e);
}
ServerInit.svs.demofile = null;
Com.Printf("Recording completed.\n");
}
/*
===============
SV_KillServer_f
Kick everyone off, possibly in preparation for a new game
===============
*/
public static void SV_KillServer_f() {
if (!ServerInit.svs.initialized)
return;
ServerMain.SV_Shutdown("Server was killed.\n", false);
NET.Config(false); // close network sockets
}
/*
===============
SV_ServerCommand_f
Let the game dll handle a command
===============
*/
public static void SV_ServerCommand_f() {
GameSVCmds.ServerCommand();
}
//===========================================================
/*
==================
SV_InitOperatorCommands
==================
*/
public static void SV_InitOperatorCommands() {
Commands.addCommand("heartbeat", new ExecutableCommand() {
public void execute() {
SV_Heartbeat_f();
}
});
Commands.addCommand("kick", new ExecutableCommand() {
public void execute() {
SV_Kick_f();
}
});
Commands.addCommand("status", new ExecutableCommand() {
public void execute() {
SV_Status_f();
}
});
Commands.addCommand("serverinfo", new ExecutableCommand() {
public void execute() {
SV_Serverinfo_f();
}
});
Commands.addCommand("dumpuser", new ExecutableCommand() {
public void execute() {
SV_DumpUser_f();
}
});
Commands.addCommand("map", new ExecutableCommand() {
public void execute() {
SV_Map_f();
}
});
Commands.addCommand("demomap", new ExecutableCommand() {
public void execute() {
SV_DemoMap_f();
}
});
Commands.addCommand("gamemap", new ExecutableCommand() {
public void execute() {
SV_GameMap_f();
}
});
Commands.addCommand("setmaster", new ExecutableCommand() {
public void execute() {
SV_SetMaster_f();
}
});
if (Globals.dedicated.value != 0)
Commands.addCommand("say", new ExecutableCommand() {
public void execute() {
SV_ConSay_f();
}
});
Commands.addCommand("serverrecord", new ExecutableCommand() {
public void execute() {
SV_ServerRecord_f();
}
});
Commands.addCommand("serverstop", new ExecutableCommand() {
public void execute() {
SV_ServerStop_f();
}
});
Commands.addCommand("save", new ExecutableCommand() {
public void execute() {
SV_Savegame_f();
}
});
Commands.addCommand("load", new ExecutableCommand() {
public void execute() {
SV_Loadgame_f();
}
});
Commands.addCommand("killserver", new ExecutableCommand() {
public void execute() {
SV_KillServer_f();
}
});
Commands.addCommand("sv", new ExecutableCommand() {
public void execute() {
SV_ServerCommand_f();
}
});
}
}