automapconfig
Enables the "automatic embedded map config data" feature, which means
with the next map save the map config file gets renamed and the map
config data stored inside the map file.
mapsoundslotusage <map sound slot number>
Returns a list of map entity indices which use a certain map sound slot.
If the sound is unused, it returns an empty string.
deletemapsoundslot <map sound slot number> <"purge">
Deletes an unused map sound slot. If "purge" is specified as second
argument, it also deletes an used slot (including all map entities
which use that sound). Also enables automapconfig.
editmapsoundslot <map sound slot number> <map sound path> <maxuses>
Edits path/name and maxuses parameters of a mapsound slot. Only
non-empty parameters actually change something. The command returns
the resulting data of the map sound slot. If only the map sound slot
number is specified, no changes are made, but the current data is
returned. Therefore the command can be used to get and set parameters.
If anything is changed, automapconfig is enabled.
mapmodelslotusage <mapmodel slot number>
Returns a list of map entity indices which use a certain mapmodel slot.
If the mapmodel is unused, it returns an empty string.
deletemapmodelslot <mapmodel slot number> <"purge">
Deletes an unused mapmodel slot. If "purge" is specified as second
argument, it also deletes an used slot (including all map entities
which use that mapmodel). Also enables automapconfig.
editmapmodelslot <mapmodel slot #> <radius> <height> <z-offset> 0 <path>
Edits the parameters of a mapmodel slot. Only non-empty parameters
actually change something. The command returns the resulting data of
the mapmodel slot. If only the mapmodel slot number is specified, no
changes are made, but the current data is returned. Therefore the
command can be used to get and set parameters.
examples:
editmapmodelslot 33 "" "" -5
sets the z-offset of slot #33 to -5 without affecting other params
echo (editmapmodelslot 33)
print the current parameters of mapmodel slot #33
for example: 0 0 0 0 "makke/lightbulb"
If anything is changed, automapconfig is enabled.
gettexturelist <includes> <excludes> <extensions>
Returns a table of all texture files fitting a certain description.
includes
Optional list of path prefixes to include in the table.
excludes
Optional list of path prefixes to exclude from the table.
extensions
Optional list of filename extensions to include in the table.
All files found under packages/textures are examined. By default, the
command returns a list with two columns: path and filename. If "excludes"
is exactly one path prefix to exclude, the table gets a third column
with path names without that prefix. If "extensions" is exactly one
extension which does not start with the character '.', the table gets
an additional (third or fourth) column containing file names without
that extension.
examples:
echo (gettexturelist)
outputs two columns like "noctua/wall" "wall02.jpg"
echo (gettexturelist "" "map_editor skymaps")
same, but omits skymaps and special textures for the editor
echo (gettexturelist makke)
outputs only textures by makke and adds a third column
like "makke/rattrap" "rb_box_03.jpg" "/rattrap"
echo (gettexturelist "skymaps/" "" "_ft.jpg")
outputs four columns like
"skymaps/egypt" "egypt_ft.jpg" "egypt" "egypt"
"skymaps/humus" "meadow_ft.jpg" "humus" "meadow"
textureslotusage <texture slot number>
Returns a list of mapmodels who use that texture as skin. If no mapmodel
uses the texture, but world geometry does, a string of whitespace is
returned. If the mapmodel is unused, an empty string is returned.
deletetextureslot <texture slot number> <"purge">
Deletes an unused texture slot. If "purge" is specified as second
argument, it also deletes an used slot. All mapmodels that use that
texture as skin are changed to default skin. All world geometry, which
uses the texture, is instead set to use slot #255. Slots below #5 always
require "purge", no matter if they are in use (default slots).
Also enables automapconfig.
edittextureslot <texture slot #> <scale> <path>
Edits the parameters of a texture slot. Only non-empty parameters
actually change something. The command returns the resulting data of
the texture slot. If only the texture slot number is specified, no
changes are made, but the current data is returned. Therefore the
command can be used to get and set parameters.
examples:
edittextureslot 33 "" "arcitool/rohbaubims.jpg"
sets texture name of slot #33
echo (edittextureslot 33)
print the current parameters of texture slot #33
for example: 0 "zastrow/3wood_crate_10.jpg"
If anything is changed, automapconfig is enabled.
deleteentity <entity index/number>
Deletes a map entity. (Note: deleting an entity only marks it as unused.
It will be completely removed after saving and loading the map.)
editentity <entity index/number> <x> <y> <z> <attr1> <attr2> <attr3> <attr4>
Edits a map entity. Edits only parameters given a non-empty value.
Returns the values of all attributes (type, x, y, z, attr1-attr4).
examples:
echo (editentity 33)
prints all attributes of map entity number 33
for example "light 174 172 12 20 255 200 200"
editentity 33 "" 173
changes the y-position of entity 33 to 173
rereadtexturelists
Variable, which is set to "1" when a texture is downloaded. Used to
trigger menu rebuilds.
rereadsoundlists
Variable, which is set to "1" when a sound is downloaded. Used to
trigger menu rebuilds.
DOCREF mapsoundslotusage deletemapsoundslot editmapsoundslot
DOCREF mapmodelslotusage deletemapmodelslot editmapmodelslot
DOCREF textureslotusage deletetextureslot edittextureslot
DOCREF gettexturelist deleteentity editentity
DOCREF automapconfig rereadtexturelists rereadsoundlists
1184 lines
35 KiB
C++
1184 lines
35 KiB
C++
// client.cpp, mostly network related client game code
|
|
|
|
#include "cube.h"
|
|
#include "bot/bot.h"
|
|
|
|
VAR(connected, 1, 0, 0);
|
|
|
|
ENetHost *clienthost = NULL;
|
|
ENetPeer *curpeer = NULL, *connpeer = NULL;
|
|
int connmillis = 0, connattempts = 0, discmillis = 0;
|
|
SVAR(curdemofile, "n/a");
|
|
extern bool clfail, cllock;
|
|
extern int searchlan;
|
|
|
|
int getclientnum() { return player1 ? player1->clientnum : -1; }
|
|
|
|
bool multiplayer(bool msg)
|
|
{
|
|
// check not correct on listen server?
|
|
if(curpeer && msg) conoutf(_("operation not available in multiplayer"));
|
|
return curpeer!=NULL;
|
|
}
|
|
|
|
bool allowedittoggle()
|
|
{
|
|
bool allow = !curpeer || gamemode==1;
|
|
if(!allow) conoutf(_("editing in multiplayer requires coopedit mode (1)"));
|
|
return allow;
|
|
}
|
|
|
|
void throttle();
|
|
|
|
VARF(throttle_interval, 0, 5, 30, throttle());
|
|
VARF(throttle_accel, 0, 2, 32, throttle());
|
|
VARF(throttle_decel, 0, 2, 32, throttle());
|
|
|
|
void throttle()
|
|
{
|
|
if(!curpeer) return;
|
|
ASSERT(ENET_PEER_PACKET_THROTTLE_SCALE==32);
|
|
enet_peer_throttle_configure(curpeer, throttle_interval*1000, throttle_accel, throttle_decel);
|
|
}
|
|
|
|
string clientpassword = "";
|
|
int connectrole = CR_DEFAULT;
|
|
bool modprotocol = false;
|
|
|
|
void abortconnect()
|
|
{
|
|
if(!connpeer) return;
|
|
clientpassword[0] = '\0';
|
|
connectrole = CR_DEFAULT;
|
|
if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer);
|
|
connpeer = NULL;
|
|
#if 0
|
|
if(!curpeer)
|
|
{
|
|
enet_host_destroy(clienthost);
|
|
clienthost = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void connectserv_(const char *servername, int serverport = 0, const char *password = NULL, int role = CR_DEFAULT)
|
|
{
|
|
if(serverport <= 0) serverport = CUBE_DEFAULT_SERVER_PORT;
|
|
if(watchingdemo) enddemoplayback();
|
|
if(!clfail && cllock && searchlan<2) return;
|
|
|
|
if(connpeer)
|
|
{
|
|
conoutf(_("aborting connection attempt"));
|
|
abortconnect();
|
|
}
|
|
connectrole = role;
|
|
copystring(clientpassword, password ? password : "");
|
|
ENetAddress address;
|
|
address.port = serverport;
|
|
|
|
if(servername)
|
|
{
|
|
addserver(servername, serverport, 0);
|
|
conoutf(_("%c2attempting to %sconnect to %c5%s%c4:%d%c2"), CC, role==CR_DEFAULT?"":"\f8admin\f2", CC, servername, CC, serverport, CC);
|
|
if(!resolverwait(servername, &address))
|
|
{
|
|
conoutf(_("%c2could %c3not resolve%c2 server %c5%s%c2"), CC, CC, CC, CC, servername, CC);
|
|
clientpassword[0] = '\0';
|
|
connectrole = CR_DEFAULT;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
conoutf(_("%c2attempting to connect over %c1LAN%c2"), CC, CC, CC);
|
|
address.host = ENET_HOST_BROADCAST;
|
|
}
|
|
|
|
if(!clienthost)
|
|
clienthost = enet_host_create(NULL, 2, 3, 0, 0);
|
|
|
|
if(clienthost)
|
|
{
|
|
connpeer = enet_host_connect(clienthost, &address, 3, 0);
|
|
enet_host_flush(clienthost);
|
|
connmillis = totalmillis;
|
|
connattempts = 0;
|
|
if(!m_mp(gamemode)) gamemode = GMODE_TEAMDEATHMATCH;
|
|
}
|
|
else
|
|
{
|
|
conoutf(_("%c2could %c3not connect%c2 to server"),CC,CC,CC);
|
|
clientpassword[0] = '\0';
|
|
connectrole = CR_DEFAULT;
|
|
}
|
|
}
|
|
|
|
void connectserv(char *servername, int *serverport, char *password)
|
|
{
|
|
modprotocol = false;
|
|
connectserv_(servername, *serverport, password);
|
|
}
|
|
|
|
void connectadmin(char *servername, int *serverport, char *password)
|
|
{
|
|
modprotocol = false;
|
|
if(!password[0]) return;
|
|
connectserv_(servername, *serverport, password, CR_ADMIN);
|
|
}
|
|
|
|
void lanconnect()
|
|
{
|
|
modprotocol = false;
|
|
connectserv_(NULL);
|
|
}
|
|
|
|
void modconnectserv(char *servername, int *serverport, char *password)
|
|
{
|
|
modprotocol = true;
|
|
connectserv_(servername, *serverport, password);
|
|
}
|
|
|
|
void modconnectadmin(char *servername, int *serverport, char *password)
|
|
{
|
|
modprotocol = true;
|
|
if(!password[0]) return;
|
|
connectserv_(servername, *serverport, password, CR_ADMIN);
|
|
}
|
|
|
|
void modlanconnect()
|
|
{
|
|
modprotocol = true;
|
|
connectserv_(NULL);
|
|
}
|
|
|
|
void whereami()
|
|
{
|
|
conoutf("you are at (%.2f,%.2f)", player1->o.x, player1->o.y);
|
|
}
|
|
|
|
void go_to(float *x, float *y, char *showmsg)
|
|
{
|
|
if(player1->state != CS_EDITING) return;
|
|
player1->newpos.x = *x;
|
|
player1->newpos.y = *y;
|
|
if(!showmsg || !*showmsg || strcmp(showmsg, "0")!=0)
|
|
conoutf("you are going to (%.2f; %.2f)", *x, *y);
|
|
}
|
|
|
|
void disconnect(int onlyclean, int async)
|
|
{
|
|
bool cleanup = onlyclean!=0;
|
|
if(curpeer)
|
|
{
|
|
if(!discmillis)
|
|
{
|
|
enet_peer_disconnect(curpeer, DISC_NONE);
|
|
enet_host_flush(clienthost);
|
|
discmillis = totalmillis;
|
|
}
|
|
if(curpeer->state!=ENET_PEER_STATE_DISCONNECTED)
|
|
{
|
|
if(async) return;
|
|
enet_peer_reset(curpeer);
|
|
}
|
|
curpeer = NULL;
|
|
discmillis = 0;
|
|
connected = 0;
|
|
conoutf(_("disconnected"));
|
|
cleanup = true;
|
|
}
|
|
|
|
if(cleanup)
|
|
{
|
|
player1->clientnum = -1;
|
|
player1->lifesequence = 0;
|
|
player1->clientrole = CR_DEFAULT;
|
|
kickallbots();
|
|
loopv(players) zapplayer(players[i]);
|
|
clearvote();
|
|
audiomgr.clearworldsounds(false);
|
|
localdisconnect();
|
|
}
|
|
#if 0
|
|
if(!connpeer && clienthost)
|
|
{
|
|
enet_host_destroy(clienthost);
|
|
clienthost = NULL;
|
|
}
|
|
#endif
|
|
if(!onlyclean) localconnect();
|
|
exechook(HOOK_SP_MP, "onDisconnect", "%d", -1);
|
|
}
|
|
|
|
void trydisconnect()
|
|
{
|
|
if(connpeer)
|
|
{
|
|
conoutf(_("aborting connection attempt"));
|
|
abortconnect();
|
|
return;
|
|
}
|
|
if(!curpeer)
|
|
{
|
|
conoutf(_("not connected"));
|
|
return;
|
|
}
|
|
conoutf(_("attempting to disconnect..."));
|
|
disconnect(0, !discmillis);
|
|
}
|
|
|
|
void _toserver(char *text, int msg, int msgt)
|
|
{
|
|
bool toteam = text && text[0] == '%' && (m_teammode || team_isspect(player1->team));
|
|
if(!toteam && text[0] == '%' && strlen(text) > 1) text++; // convert team-text to normal-text if no team-mode is active
|
|
if(toteam) text++;
|
|
filtertext(text, text, FTXT__CHAT);
|
|
trimtrailingwhitespace(text);
|
|
if(servstate.mastermode == MM_MATCH && servstate.matchteamsize && !team_isactive(player1->team) && !(player1->team == TEAM_SPECT && player1->clientrole == CR_ADMIN)) toteam = true; // spect chat
|
|
if(*text)
|
|
{
|
|
if(msg == SV_TEXTME) conoutf("\f%d%s %s", toteam ? 1 : 0, colorname(player1), highlight(text));
|
|
else conoutf("%s:\f%d %s", colorname(player1), toteam ? 1 : 0, highlight(text));
|
|
addmsg(toteam ? msgt : msg, "rs", text);
|
|
}
|
|
}
|
|
|
|
void toserver(char *text)
|
|
{
|
|
_toserver(text, SV_TEXT, SV_TEAMTEXT);
|
|
}
|
|
|
|
void toserverme(char *text)
|
|
{
|
|
_toserver(text, SV_TEXTME, SV_TEAMTEXTME);
|
|
}
|
|
|
|
void echo(char *text)
|
|
{
|
|
const char *s = strtok(text, "\n");
|
|
do
|
|
{
|
|
conoutf("%s", s ? s : "");
|
|
s = strtok(NULL, "\n");
|
|
}
|
|
while(s);
|
|
}
|
|
|
|
VARP(allowhudechos, 0, 1, 1);
|
|
void hudecho(char *text)
|
|
{
|
|
const char *s = strtok(text, "\n");
|
|
void (*outf)(const char *s, ...) = allowhudechos ? hudoutf : conoutf;
|
|
do
|
|
{
|
|
outf("%s", s ? s : "");
|
|
s = strtok(NULL, "\n");
|
|
}
|
|
while(s);
|
|
}
|
|
|
|
void pm(char *text)
|
|
{
|
|
if(!text || !text[0]) return;
|
|
int cn = -1;
|
|
char digit;
|
|
while ((digit = *text++) != '\0')
|
|
{
|
|
if (digit < '0' || digit > '9') break;
|
|
if(cn < 0) cn = 0;
|
|
else cn *= 10;
|
|
cn += digit - '0';
|
|
}
|
|
playerent *to = getclient(cn);
|
|
if(!to)
|
|
{
|
|
conoutf("invalid client number specified");
|
|
return;
|
|
}
|
|
|
|
if(!isspace(digit)) { --text; }
|
|
|
|
// FIXME:
|
|
/*if(!text || !text[0] || !isdigit(text[0])) return;
|
|
int cn = -1;
|
|
char *numend = strpbrk(text, " \t");
|
|
if(!numend) return;
|
|
string cnbuf;
|
|
copystring(cnbuf, text, min(numend-text+1, MAXSTRLEN));
|
|
cn = atoi(cnbuf);
|
|
playerent *to = getclient(cn);
|
|
if(!to)
|
|
{
|
|
conoutf("invalid client number specified");
|
|
return;
|
|
}
|
|
|
|
if(*numend) numend++;*/
|
|
// :FIXME
|
|
|
|
filtertext(text, text, FTXT__CHAT);
|
|
trimtrailingwhitespace(text);
|
|
|
|
addmsg(SV_TEXTPRIVATE, "ris", cn, text);
|
|
conoutf("to %s:\f9 %s", colorname(to), highlight(text));
|
|
}
|
|
COMMAND(pm, "c");
|
|
|
|
COMMAND(echo, "c");
|
|
COMMAND(hudecho, "c");
|
|
COMMANDN(say, toserver, "c");
|
|
COMMANDN(me, toserverme, "c");
|
|
COMMANDN(connect, connectserv, "sis");
|
|
COMMAND(connectadmin, "sis");
|
|
COMMAND(lanconnect, "");
|
|
COMMANDN(modconnect, modconnectserv, "sis");
|
|
COMMAND(modconnectadmin, "sis");
|
|
COMMAND(modlanconnect, "");
|
|
COMMANDN(disconnect, trydisconnect, "");
|
|
COMMAND(whereami, "");
|
|
COMMAND(go_to, "ffs");
|
|
|
|
void current_version(char *text)
|
|
{
|
|
int version = atoi(text);
|
|
if (version && AC_VERSION<version)
|
|
{
|
|
hudoutf("\f3YOUR VERSION OF ASSAULTCUBE IS OUTDATED!");
|
|
conoutf("\f3YOU MUST UPDATE ASSAULTCUBE\nplease visit \f2http://assault.cubers.net \f3for more information");
|
|
}
|
|
}
|
|
COMMAND(current_version, "s");
|
|
|
|
void cleanupclient()
|
|
{
|
|
abortconnect();
|
|
disconnect(1);
|
|
if(clienthost)
|
|
{
|
|
enet_host_destroy(clienthost);
|
|
clienthost = NULL;
|
|
}
|
|
}
|
|
|
|
// collect c2s messages conveniently
|
|
|
|
vector<uchar> messages;
|
|
|
|
void addmsg(int type, const char *fmt, ...)
|
|
{
|
|
static uchar buf[MAXTRANS];
|
|
ucharbuf p(buf, MAXTRANS);
|
|
putint(p, type);
|
|
int numi = 1, nums = 0;
|
|
bool reliable = false;
|
|
if(fmt)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
while(*fmt) switch(*fmt++)
|
|
{
|
|
case 'r': reliable = true; break;
|
|
case 'v':
|
|
{
|
|
int n = va_arg(args, int);
|
|
int *v = va_arg(args, int *);
|
|
loopi(n) putint(p, v[i]);
|
|
numi += n;
|
|
break;
|
|
}
|
|
|
|
case 'i':
|
|
{
|
|
int n = isdigit(*fmt) ? *fmt++-'0' : 1;
|
|
loopi(n) putint(p, va_arg(args, int));
|
|
numi += n;
|
|
break;
|
|
}
|
|
case 's':
|
|
{
|
|
const char *t = va_arg(args, const char *);
|
|
if(t) sendstring(t, p); nums++; break;
|
|
}
|
|
}
|
|
va_end(args);
|
|
}
|
|
int num = nums?0:numi, msgsize = msgsizelookup(type);
|
|
if(msgsize && num!=msgsize) { fatal("inconsistent msg size for %d (%d != %d)", type, num, msgsize); }
|
|
int len = p.length();
|
|
messages.add(len&0xFF);
|
|
messages.add((len>>8)|(reliable ? 0x80 : 0));
|
|
loopi(len) messages.add(buf[i]);
|
|
}
|
|
|
|
static int lastupdate = -1000, lastping = 0;
|
|
bool sendmapidenttoserver = false;
|
|
|
|
void sendpackettoserv(int chan, ENetPacket *packet)
|
|
{
|
|
if(curpeer) enet_peer_send(curpeer, chan, packet);
|
|
else localclienttoserver(chan, packet);
|
|
}
|
|
|
|
void c2skeepalive()
|
|
{
|
|
if(clienthost && (curpeer || connpeer)) enet_host_service(clienthost, NULL, 0);
|
|
}
|
|
|
|
extern string masterpwd;
|
|
bool sv_pos = true;
|
|
|
|
void c2sinfo(playerent *d) // send update to the server
|
|
{
|
|
if(d->clientnum<0) return; // we haven't had a welcome message from the server yet
|
|
if(totalmillis-lastupdate<40) return; // don't update faster than 25fps
|
|
|
|
if(d->state==CS_ALIVE || d->state==CS_EDITING)
|
|
{
|
|
packetbuf q(100);
|
|
int cn = d->clientnum,
|
|
x = (int)(d->o.x*DMF), // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes
|
|
y = (int)(d->o.y*DMF),
|
|
z = (int)((d->o.z - d->eyeheight)*DMF),
|
|
ya = (int)((512 * d->yaw) / 360.0f),
|
|
pi = (int)((127 * d->pitch) / 90.0f),
|
|
r = (int)(31*d->roll/20),
|
|
dxt = (int)(d->vel.x*DVELF),
|
|
dyt = (int)(d->vel.y*DVELF),
|
|
dzt = (int)(d->vel.z*DVELF),
|
|
dx = dxt - d->vel_t.i[0],
|
|
dy = dyt - d->vel_t.i[1],
|
|
dz = dzt - d->vel_t.i[2],
|
|
// pack rest in 1 int: strafe:2, move:2, onfloor:1, onladder: 1
|
|
f = (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | (((int)d->onladder)<<5) | ((d->lifesequence&1)<<6) | (((int)d->crouching)<<7),
|
|
g = (dx?1:0) | ((dy?1:0)<<1) | ((dz?1:0)<<2) | ((r?1:0)<<3) | (((int)d->scoping)<<4) | (((int)d->shoot)<<5);
|
|
d->vel_t.i[0] = dxt;
|
|
d->vel_t.i[1] = dyt;
|
|
d->vel_t.i[2] = dzt;
|
|
int usefactor = sfactor < 7 ? 7 : sfactor, sizexy = 1 << (usefactor + 4);
|
|
if(cn >= 0 && cn < 32 &&
|
|
usefactor <= 7 + 3 && // map size 7..10
|
|
x >= 0 && x < sizexy &&
|
|
y >= 0 && y < sizexy &&
|
|
z >= -2047 && z <= 2047 &&
|
|
ya >= 0 && ya < 512 &&
|
|
pi >= -128 && pi <= 127 &&
|
|
r >= -32 && r <= 31 &&
|
|
dx >= -8 && dx <= 7 && // FIXME
|
|
dy >= -8 && dy <= 7 &&
|
|
dz >= -8 && dz <= 7)
|
|
{ // compact POS packet
|
|
bool noroll = !r, novel = !dx && !dy && !dz;
|
|
bitbuf<packetbuf> b(q);
|
|
putint(q, SV_POSC);
|
|
b.putbits(5, cn);
|
|
b.putbits(2, usefactor - 7);
|
|
b.putbits(usefactor + 4, x);
|
|
b.putbits(usefactor + 4, y);
|
|
b.putbits(9, ya);
|
|
b.putbits(8, pi + 128);
|
|
b.putbits(1, noroll ? 1 : 0);
|
|
if(!noroll) b.putbits(6, r + 32);
|
|
b.putbits(1, novel ? 1 : 0);
|
|
if(!novel)
|
|
{
|
|
b.putbits(4, dx + 8);
|
|
b.putbits(4, dy + 8);
|
|
b.putbits(4, dz + 8);
|
|
}
|
|
b.putbits(8, f);
|
|
b.putbits(1, z < 0 ? 1 : 0);
|
|
if(z < 0) z = -z; // z is encoded with 3..10 bits minimum (fitted to byte boundaries), or full 11 bits if necessary
|
|
int s = (b.rembits() - 1 + 8) % 8;
|
|
if(s < 3) s += 8;
|
|
if(z >= (1 << s)) s = 11;
|
|
b.putbits(1, s == 11 ? 1 : 0);
|
|
b.putbits(s, z);
|
|
b.putbits(1, d->scoping ? 1 : 0);
|
|
b.putbits(1, d->shoot ? 1 : 0);
|
|
}
|
|
else
|
|
{ // classic POS packet
|
|
putint(q, SV_POS);
|
|
putint(q, d->clientnum);
|
|
putuint(q, x);
|
|
putuint(q, y);
|
|
putuint(q, z);
|
|
putuint(q, (int)d->yaw);
|
|
putint(q, (int)d->pitch);
|
|
putuint(q, g);
|
|
if (r) putint(q, (int)(125*d->roll/20));
|
|
if (dx) putint(q, dx);
|
|
if (dy) putint(q, dy);
|
|
if (dz) putint(q, dz);
|
|
putuint(q, f);
|
|
}
|
|
sendpackettoserv(0, q.finalize());
|
|
d->shoot = false;
|
|
}
|
|
|
|
if(sendmapidenttoserver || messages.length() || totalmillis-lastping>250)
|
|
{
|
|
packetbuf p(MAXTRANS);
|
|
|
|
if(sendmapidenttoserver) // new map
|
|
{
|
|
p.reliable();
|
|
putint(p, SV_MAPIDENT);
|
|
putint(p, maploaded);
|
|
putint(p, hdr.maprevision);
|
|
sendmapidenttoserver = false;
|
|
}
|
|
int i = 0;
|
|
while(i < messages.length()) // send messages collected during the previous frames
|
|
{
|
|
int len = messages[i] | ((messages[i+1]&0x7F)<<8);
|
|
if(p.remaining() < len) break;
|
|
if(messages[i+1]&0x80) p.reliable();
|
|
p.put(&messages[i+2], len);
|
|
i += 2 + len;
|
|
}
|
|
messages.remove(0, i);
|
|
if(totalmillis-lastping>250)
|
|
{
|
|
putint(p, SV_PING);
|
|
putint(p, totalmillis);
|
|
lastping = totalmillis;
|
|
}
|
|
if(p.length()) sendpackettoserv(1, p.finalize());
|
|
}
|
|
|
|
if(clienthost) enet_host_flush(clienthost);
|
|
lastupdate = totalmillis;
|
|
}
|
|
|
|
int getbuildtype()
|
|
{
|
|
return (isbigendian() ? 0x80 : 0 )|(adler((unsigned char *)guns, sizeof(guns)) % 31 << 8)|
|
|
#ifdef WIN32
|
|
0x40 |
|
|
#endif
|
|
#ifdef __APPLE__
|
|
0x20 |
|
|
#endif
|
|
#ifdef _DEBUG
|
|
0x08 |
|
|
#endif
|
|
#ifdef __GNUC__
|
|
0x04 |
|
|
#endif
|
|
0;
|
|
}
|
|
|
|
void sendintro()
|
|
{
|
|
packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
|
|
putint(p, SV_CONNECT);
|
|
putint(p, AC_VERSION);
|
|
putint(p, getbuildtype());
|
|
sendstring(player1->name, p);
|
|
sendstring(genpwdhash(player1->name, clientpassword, sessionid), p);
|
|
sendstring(!lang || strlen(lang) != 2 ? "" : lang, p);
|
|
putint(p, connectrole);
|
|
clientpassword[0] = '\0';
|
|
connectrole = CR_DEFAULT;
|
|
putint(p, player1->nextprimweap->type);
|
|
loopi(2) putint(p, player1->skin(i));
|
|
sendpackettoserv(1, p.finalize());
|
|
}
|
|
|
|
void gets2c() // get updates from the server
|
|
{
|
|
ENetEvent event;
|
|
if(!clienthost || (!curpeer && !connpeer)) return;
|
|
if(connpeer && totalmillis/3000 > connmillis/3000)
|
|
{
|
|
conoutf(_("attempting to connect..."));
|
|
connmillis = totalmillis;
|
|
++connattempts;
|
|
if(connattempts > 3)
|
|
{
|
|
conoutf(_("%c3could not connect to server"), CC);
|
|
abortconnect();
|
|
return;
|
|
}
|
|
}
|
|
while(clienthost!=NULL && enet_host_service(clienthost, &event, 0)>0)
|
|
switch(event.type)
|
|
{
|
|
case ENET_EVENT_TYPE_CONNECT:
|
|
disconnect(1);
|
|
curpeer = connpeer;
|
|
connpeer = NULL;
|
|
connected = 1;
|
|
conoutf(_("connected to server"));
|
|
exechook(HOOK_SP_MP, "onConnect", "%d", -1);
|
|
throttle();
|
|
if(editmode) toggleedit(true);
|
|
break;
|
|
|
|
case ENET_EVENT_TYPE_RECEIVE:
|
|
{
|
|
extern packetqueue pktlogger;
|
|
pktlogger.queue(event.packet);
|
|
|
|
if(discmillis) conoutf(_("attempting to disconnect..."));
|
|
else servertoclient(event.channelID, event.packet->data, (int)event.packet->dataLength);
|
|
// destroyed in logger
|
|
//enet_packet_destroy(event.packet);
|
|
break;
|
|
}
|
|
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
{
|
|
extern const char *disc_reason(int reason);
|
|
if(event.data>=DISC_NUM) event.data = DISC_NONE;
|
|
if(event.peer==connpeer)
|
|
{
|
|
conoutf(_("%c3could not connect to server"), CC);
|
|
abortconnect();
|
|
}
|
|
else
|
|
{
|
|
if(!discmillis || event.data) conoutf(_("%c3server network error, disconnecting (%s) ..."), CC, disc_reason(event.data));
|
|
disconnect();
|
|
}
|
|
return;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// for AUTH:
|
|
|
|
vector<authkey *> authkeys;
|
|
|
|
VARP(autoauth, 0, 1, 1);
|
|
|
|
authkey *findauthkey(const char *desc)
|
|
{
|
|
loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, player1->name)) return authkeys[i];
|
|
loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc)) return authkeys[i];
|
|
return NULL;
|
|
}
|
|
|
|
void addauthkey(const char *name, const char *key, const char *desc)
|
|
{
|
|
loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) delete authkeys.remove(i);
|
|
if(name[0] && key[0]) authkeys.add(new authkey(name, key, desc));
|
|
}
|
|
|
|
bool _hasauthkey(const char *name, const char *desc)
|
|
{
|
|
if(!name[0] && !desc[0]) return authkeys.length() > 0;
|
|
loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) return true;
|
|
return false;
|
|
}
|
|
|
|
void genauthkey(const char *secret)
|
|
{
|
|
if(!secret[0]) { conoutf("you must specify a secret password"); return; }
|
|
vector<char> privkey, pubkey;
|
|
genprivkey(secret, privkey, pubkey);
|
|
conoutf("private key: %s", privkey.getbuf());
|
|
conoutf("public key: %s", pubkey.getbuf());
|
|
}
|
|
|
|
void saveauthkeys()
|
|
{
|
|
if(authkeys.length())
|
|
{
|
|
stream *f = openfile("config/auth.cfg", "w");
|
|
if(!f) { conoutf("failed to open config/auth.cfg for writing"); return; }
|
|
loopv(authkeys)
|
|
{
|
|
authkey *a = authkeys[i];
|
|
f->printf("authkey \"%s\" \"%s\" \"%s\"\n", a->name, a->key, a->desc);
|
|
}
|
|
conoutf("saved authkeys to config/auth.cfg");
|
|
delete f;
|
|
}
|
|
else conoutf("you need to use 'addauthkey USER KEY DESC' first; one DESC 'public', one DESC empty(=private)");
|
|
}
|
|
|
|
bool tryauth(const char *desc)
|
|
{
|
|
authkey *a = findauthkey(desc);
|
|
if(!a) return false;
|
|
a->lastauth = lastmillis;
|
|
addmsg(SV_AUTHTRY, "rss", a->desc, a->name);
|
|
return true;
|
|
}
|
|
|
|
COMMANDN(authkey, addauthkey, "sss");
|
|
COMMANDF(hasauthkey, "ss", (char *name, char *desc) { intret(_hasauthkey(name, desc) ? 1 : 0); });
|
|
COMMAND(genauthkey, "s");
|
|
COMMAND(saveauthkeys, "");
|
|
COMMANDF(auth, "s", (char *desc) { intret(tryauth(desc)); });
|
|
|
|
// :for AUTH
|
|
|
|
// sendmap/getmap commands, should be replaced by more intuitive map downloading
|
|
|
|
vector<char *> securemaps;
|
|
|
|
void resetsecuremaps() { securemaps.deletearrays(); }
|
|
void securemap(char *map) { if(map) securemaps.add(newstring(map)); }
|
|
bool securemapcheck(const char *map, bool msg)
|
|
{
|
|
if(strstr(map, "maps/")==map || strstr(map, "maps\\")==map) map += strlen("maps/");
|
|
loopv(securemaps) if(!strcmp(securemaps[i], map))
|
|
{
|
|
if(msg)
|
|
{
|
|
conoutf(_("%c3Map %c4%s%c3 is secured. This means you CAN'T send, receive or overwrite it."), CC, CC, map, CC);
|
|
if(connected)
|
|
{
|
|
conoutf(_("%c3If you get this error often, you (or the server) may be running an outdated game."), CC);
|
|
conoutf(_("%c3You can check for updates at %c1http://assault.cubers.net/download.html"), CC, CC);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void sendmap(char *mapname)
|
|
{
|
|
if(!*mapname) mapname = getclientmap();
|
|
if(securemapcheck(mapname)) return;
|
|
if(gamemode == GMODE_COOPEDIT && !strcmp(getclientmap(), mapname)) save_world(mapname, true, false); // skip optimisations, don't add undos
|
|
|
|
int mapsize, cfgsize, cfgsizegz, revision;
|
|
uchar *mapdata = readmap(path(mapname), &mapsize, &revision);
|
|
if(!mapdata) return;
|
|
uchar *cfgdata = readmcfggz(path(mapname), &cfgsize, &cfgsizegz);
|
|
if(!cfgdata) { cfgsize = 0; cfgsizegz = 0; }
|
|
|
|
packetbuf p(MAXTRANS + mapsize + cfgsizegz, ENET_PACKET_FLAG_RELIABLE);
|
|
|
|
putint(p, SV_SENDMAP);
|
|
sendstring(mapname, p);
|
|
putint(p, mapsize);
|
|
putint(p, cfgsize);
|
|
putint(p, cfgsizegz);
|
|
putint(p, revision);
|
|
if(MAXMAPSENDSIZE - p.length() < mapsize + cfgsizegz || cfgsize > MAXCFGFILESIZE)
|
|
{
|
|
conoutf(_("map %s is too large to send"), mapname);
|
|
delete[] mapdata;
|
|
if(cfgsize) delete[] cfgdata;
|
|
return;
|
|
}
|
|
p.put(mapdata, mapsize);
|
|
delete[] mapdata;
|
|
if(cfgsizegz)
|
|
{
|
|
p.put(cfgdata, cfgsizegz);
|
|
delete[] cfgdata;
|
|
}
|
|
|
|
sendpackettoserv(2, p.finalize());
|
|
conoutf(_("sending map %s to server..."), mapname);
|
|
}
|
|
|
|
void getmap(char *name, char *callback)
|
|
{
|
|
if((!name || !*name)
|
|
|| (connected && !strcmp(name, getclientmap())) )
|
|
{
|
|
conoutf(_("requesting map from server..."));
|
|
packetbuf p(10, ENET_PACKET_FLAG_RELIABLE);
|
|
putint(p, SV_RECVMAP);
|
|
sendpackettoserv(2, p.finalize());
|
|
}
|
|
else
|
|
{
|
|
defformatstring(package)("packages/maps/%s.cgz", name);
|
|
requirepackage(PCK_MAP, package);
|
|
if(downloadpackages())
|
|
{
|
|
if(callback && *callback) execute(callback);
|
|
conoutf("map %s installed successfully", name);
|
|
}
|
|
else conoutf("\f3map download failed.");
|
|
}
|
|
}
|
|
|
|
void deleteservermap(char *mapname)
|
|
{
|
|
const char *name = behindpath(mapname);
|
|
if(!*name || securemapcheck(name)) return;
|
|
addmsg(SV_REMOVEMAP, "rs", name);
|
|
}
|
|
|
|
string demosubpath;
|
|
void getdemo(int *idx, char *dsp)
|
|
{
|
|
if(!multiplayer(false))
|
|
{
|
|
conoutf("%c3Getting demo from server is not available in singleplayer", CC);
|
|
return;
|
|
}
|
|
if(dsp && dsp[0]) formatstring(demosubpath)("%s/", dsp);
|
|
else copystring(demosubpath, "");
|
|
if(*idx<=0) conoutf(_("getting demo..."));
|
|
else conoutf(_("getting demo %d..."), *idx);
|
|
addmsg(SV_GETDEMO, "ri", *idx);
|
|
}
|
|
|
|
void listdemos()
|
|
{
|
|
if(!multiplayer(false))
|
|
{
|
|
conoutf("%c3Listing demos from server is not available in singleplayer", CC);
|
|
return;
|
|
}
|
|
conoutf(_("listing demos..."));
|
|
addmsg(SV_LISTDEMOS, "r");
|
|
}
|
|
|
|
void shiftgametime(int newmillis)
|
|
{
|
|
newmillis = max(0, newmillis);
|
|
int gamemillis = gametimecurrent + (lastmillis - lastgametimeupdate);
|
|
|
|
if(!watchingdemo) { conoutf("You have to be watching a demo to change game time"); return; }
|
|
if(newmillis > gametimemaximum) { conoutf("Invalid time specified"); return; }
|
|
|
|
if(newmillis < gamemillis)
|
|
{
|
|
// if rewinding
|
|
if(!curdemofile || !curdemofile[0]) return;
|
|
watchingdemo = false;
|
|
callvote(SA_MAP, curdemofile, "-1", "0");
|
|
nextmillis = newmillis;
|
|
}
|
|
else
|
|
{
|
|
nextmillis = newmillis - gamemillis;
|
|
}
|
|
}
|
|
|
|
void setminutesremaining(int *minutes)
|
|
{
|
|
shiftgametime(gametimemaximum - (*minutes)*60000);
|
|
}
|
|
|
|
void rewinddemo(int *seconds)
|
|
{
|
|
int gamemillis = gametimecurrent+(lastmillis-lastgametimeupdate);
|
|
shiftgametime(gamemillis - (*seconds)*1000);
|
|
}
|
|
|
|
COMMAND(sendmap, "s");
|
|
COMMAND(getmap, "ss");
|
|
COMMAND(deleteservermap, "s");
|
|
COMMAND(resetsecuremaps, "");
|
|
COMMAND(securemap, "s");
|
|
COMMAND(getdemo, "is");
|
|
COMMAND(listdemos, "");
|
|
COMMANDN(setmr, setminutesremaining, "i");
|
|
COMMANDN(rewind, rewinddemo, "i");
|
|
|
|
// packages auto - downloader
|
|
|
|
// arrays
|
|
vector<pckserver *> pckservers;
|
|
hashtable<const char *, package *> pendingpackages;
|
|
|
|
// cubescript
|
|
VARP(autodownload, 0, 1, 1);
|
|
|
|
void resetpckservers()
|
|
{
|
|
pckservers.deletecontents();
|
|
delfile(findfile("config" PATHDIVS "pcksources.cfg", "w"));
|
|
conoutf("%c3Restart AssaultCube to take effect after resetting the list of packages source servers", CC);
|
|
}
|
|
|
|
void addpckserver(char *addr)
|
|
{
|
|
pckserver *srcserver = new pckserver();
|
|
srcserver->addr = newstring(addr);
|
|
srcserver->pending = true;
|
|
pckservers.add(srcserver);
|
|
}
|
|
|
|
COMMAND(resetpckservers, "");
|
|
COMMAND(addpckserver, "s");
|
|
|
|
// cURL / Network
|
|
|
|
bool havecurl = false, canceldownloads = false;
|
|
|
|
void setupcurl()
|
|
{
|
|
if(curl_global_init(CURL_GLOBAL_NOTHING)) conoutf(_("\f3could not init cURL, content downloads not available"));
|
|
else
|
|
{
|
|
havecurl = true;
|
|
execfile("config/pcksources.cfg");
|
|
}
|
|
}
|
|
|
|
// detect the "nearest" server
|
|
int pckserversort(pckserver **a, pckserver **b)
|
|
{
|
|
if((*a)->ping < 0) return ((*b)->ping < 0) ? 0 : 1;
|
|
if((*b)->ping < 0) return -1;
|
|
|
|
return (*a)->ping == (*b)->ping ? 0 : ((*a)->ping < (*b)->ping ? -1 : 1);
|
|
}
|
|
|
|
SDL_Thread* pingpcksrvthread = NULL;
|
|
SDL_mutex *pingpcksrvlock = NULL;
|
|
int pingpckservers(void *data)
|
|
{
|
|
SDL_mutexP(pingpcksrvlock);
|
|
vector<pckserver> serverstoping;
|
|
loopv(pckservers) serverstoping.add(*pckservers[i]);
|
|
SDL_mutexV(pingpcksrvlock);
|
|
// measure the time it took to receive each's server response
|
|
// not very accurate but it should do the trick
|
|
loopv(serverstoping)
|
|
{
|
|
double ping = 0, namelookup = 0;
|
|
pckserver *serv = &serverstoping[i];
|
|
CURL *cu = curl_easy_init();
|
|
curl_easy_setopt(cu, CURLOPT_URL, serv->addr);
|
|
curl_easy_setopt(cu, CURLOPT_NOSIGNAL, 1); // Fixes crashbug for some buggy libcurl versions (Linux)
|
|
curl_easy_setopt(cu, CURLOPT_NOPROGRESS, 1);
|
|
curl_easy_setopt(cu, CURLOPT_NOBODY, 1); // don't download response body (as its size may vary a lot from one server to another)
|
|
curl_easy_setopt(cu, CURLOPT_CONNECTTIMEOUT, 10); // the timeout should be large here
|
|
int result = curl_easy_perform(cu);
|
|
curl_easy_getinfo(cu, CURLINFO_TOTAL_TIME, &ping);
|
|
curl_easy_getinfo(cu, CURLINFO_NAMELOOKUP_TIME, &namelookup);
|
|
ping -= namelookup; // ignore DNS lookup time as it should be performed only once
|
|
curl_easy_cleanup(cu);
|
|
cu = NULL;
|
|
if(result == CURLE_OPERATION_TIMEDOUT || result == CURLE_COULDNT_RESOLVE_HOST)
|
|
serv->responsive = false;
|
|
else
|
|
serv->ping = (int)(ping*1000);
|
|
}
|
|
|
|
SDL_mutexP(pingpcksrvlock);
|
|
loopv(serverstoping) loopvj(pckservers) if(!strcmp(serverstoping[i].addr, pckservers[j]->addr)) *pckservers[j] = serverstoping[i];
|
|
pckservers.sort(pckserversort);
|
|
// print the results. we should make it silent once tested enough
|
|
loopv(pckservers)
|
|
{
|
|
pckserver *serv = pckservers[i];
|
|
if(serv->ping > 0) clientlogf("%d. %s (%d ms)", i+1, serv->addr, serv->ping);
|
|
else clientlogf("%d. %s (did not reply)", i+1, serv->addr);
|
|
}
|
|
SDL_mutexV(pingpcksrvlock);
|
|
|
|
SDL_DestroyMutex(pingpcksrvlock);
|
|
pingpcksrvthread = NULL;
|
|
pingpcksrvlock = NULL;
|
|
return 0;
|
|
}
|
|
|
|
void sortpckservers()
|
|
{
|
|
if(pingpcksrvthread || !havecurl) return;
|
|
pingpcksrvlock = SDL_CreateMutex();
|
|
pingpcksrvthread = SDL_CreateThread(pingpckservers, NULL);
|
|
}
|
|
COMMAND(sortpckservers, "");
|
|
|
|
size_t write_callback(void *ptr, size_t size, size_t nmemb, FILE *stream)
|
|
{
|
|
return fwrite(ptr, size, nmemb, stream);
|
|
}
|
|
|
|
int progress_callback_dlpackage(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
|
|
{
|
|
package *pck = (package *)clientp;
|
|
loadingscreen(_("downloading package %d out of %d...\n%s %.0f/%.0f KB (%.1f%%)\n(ESC to cancel)"), pck->number + 1, pck->number + pendingpackages.numelems,
|
|
pck->name, dlnow/double(1024.0), dltotal/double(1024.0), dltotal == 0 ? 0 : (dlnow/dltotal * double(100.0)));
|
|
if(interceptkey(SDLK_ESCAPE))
|
|
{
|
|
canceldownloads = true;
|
|
loadingscreen();
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
VAR(rereadtexturelists, 0, 1, 1); // flag to indicate additional texture files to trigger a menu rebuild (defaults to "1" to build the menus after startup)
|
|
VAR(rereadsoundlists, 0, 1, 1); // same for map sounds
|
|
|
|
int processdownload(package *pck)
|
|
{
|
|
string tmpname = "";
|
|
copystring(tmpname, findfile(path("tmp", true), "rb"));
|
|
if(!pck->pending)
|
|
{
|
|
switch(pck->type)
|
|
{
|
|
case PCK_TEXTURE: case PCK_AUDIO:
|
|
{
|
|
const char *pckname = findfile(path(pck->name, true), "w+");
|
|
// with textures/sounds, the image/audio file itself is sent. Just need to copy it from the temporary file
|
|
if(rename(tmpname, pckname)) conoutf(_("\f3failed to install"), pckname);
|
|
if(pck->type == PCK_TEXTURE) rereadtexturelists = 1;
|
|
else rereadsoundlists = 1;
|
|
break;
|
|
}
|
|
|
|
case PCK_MAP: case PCK_MAPMODEL:
|
|
{
|
|
addzip(tmpname, pck->name, NULL, true, pck->type);
|
|
break;
|
|
}
|
|
|
|
case PCK_SKYBOX:
|
|
{
|
|
char *fname = newstring(pck->name), *ls = strrchr(fname, '/');
|
|
if(ls) *ls = '\0';
|
|
addzip(tmpname, fname, NULL, true, pck->type);
|
|
rereadtexturelists = 1;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
conoutf(_("could not install package %s"), pck->name);
|
|
break;
|
|
}
|
|
delfile(tmpname);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// download a package
|
|
double dlpackage(package *pck)
|
|
{
|
|
if(!pck || !pck->source) return false;
|
|
const char *tmpname = findfile(path("tmp", true), "wb");
|
|
FILE *outfile = fopen(tmpname, "wb");
|
|
string req, pckname = "";
|
|
sprintf(req, "%s/%s%s", pck->source->addr, strreplace(pckname, pck->name, " ", "%20"), (pck->type==PCK_MAP || pck->type==PCK_MAPMODEL || pck->type==PCK_SKYBOX) ? ".zip" : "");
|
|
conoutf(_("downloading %s from %s ..."), pck->name, pck->source->addr);
|
|
if(!outfile)
|
|
{
|
|
pck->pending = false;
|
|
conoutf("\f3failed to write temporary file \"%s\"", tmpname);
|
|
return 0;
|
|
}
|
|
|
|
int result, httpresult = 0;
|
|
double dlsize;
|
|
pck->curl = curl_easy_init();
|
|
curl_easy_setopt(pck->curl, CURLOPT_URL, req);
|
|
curl_easy_setopt(pck->curl, CURLOPT_NOSIGNAL, 1); // Fixes crashbug for some buggy libcurl versions (Linux)
|
|
curl_easy_setopt(pck->curl, CURLOPT_WRITEFUNCTION, write_callback);
|
|
curl_easy_setopt(pck->curl, CURLOPT_WRITEDATA, outfile);
|
|
curl_easy_setopt(pck->curl, CURLOPT_NOPROGRESS, 0);
|
|
curl_easy_setopt(pck->curl, CURLOPT_PROGRESSFUNCTION, progress_callback_dlpackage);
|
|
curl_easy_setopt(pck->curl, CURLOPT_PROGRESSDATA, pck);
|
|
curl_easy_setopt(pck->curl, CURLOPT_CONNECTTIMEOUT, 10); // generous timeout for Bukz ;)
|
|
result = curl_easy_perform(pck->curl);
|
|
curl_easy_getinfo(pck->curl, CURLINFO_RESPONSE_CODE, &httpresult);
|
|
curl_easy_getinfo(pck->curl, CURLINFO_SIZE_DOWNLOAD, &dlsize);
|
|
curl_easy_cleanup(pck->curl);
|
|
pck->curl = NULL;
|
|
fclose(outfile);
|
|
|
|
pck->pending = false;
|
|
if(result == CURLE_OPERATION_TIMEDOUT || result == CURLE_COULDNT_RESOLVE_HOST)
|
|
{
|
|
// mark source unresponsive
|
|
pck->source->responsive = false;
|
|
conoutf(_("\f3could not connect to %s"), pck->source->addr);
|
|
|
|
// try to find another source
|
|
pckserver *source = NULL;
|
|
loopv(pckservers) if(pckservers[i]->responsive) { source = pckservers[i]; break; }
|
|
if(!source)
|
|
{
|
|
conoutf(_("\f3no more servers to connect to"));
|
|
canceldownloads = true;
|
|
return 0;
|
|
}
|
|
|
|
// update all packages
|
|
enumerate(pendingpackages, package *, pack,
|
|
{
|
|
pack->source = source;
|
|
});
|
|
|
|
pck->pending = true;
|
|
|
|
return dlpackage(pck); // retry current
|
|
}
|
|
if(!result && httpresult == 200) processdownload(pck);
|
|
else if(result == CURLE_ABORTED_BY_CALLBACK) conoutf(_("\f3download cancelled"));
|
|
else conoutf(_("\f2request for %s failed (cURL %d, HTTP %d)"), req, result, httpresult);
|
|
return (!result && httpresult == 200) ? dlsize : 0;
|
|
}
|
|
|
|
int lastpackagesdownload = 0;
|
|
int downloadpackages()
|
|
{
|
|
if(!pendingpackages.numelems) return 0;
|
|
else if(totalmillis - lastpackagesdownload < 2000)
|
|
{
|
|
conoutf("\f3quick download attempt aborted");
|
|
return 0;
|
|
}
|
|
|
|
double total = 0;
|
|
int downloaded = 0;
|
|
enumerate(pendingpackages, package *, pck,
|
|
{
|
|
if(!canceldownloads)
|
|
{
|
|
if(connected) c2skeepalive(); // try to avoid time out
|
|
pck->number = downloaded++;
|
|
total += dlpackage(pck);
|
|
}
|
|
pendingpackages.remove(pck->name);
|
|
delete pck;
|
|
});
|
|
canceldownloads = false;
|
|
lastpackagesdownload = lastmillis;
|
|
return (int)total;
|
|
}
|
|
|
|
bool requirepackage(int type, const char *path)
|
|
{
|
|
pckserver *source = NULL;
|
|
loopv(pckservers) if(pckservers[i]->responsive) { source = pckservers[i]; break; }
|
|
if(!source) { conoutf(_("\f3no responsive source server found, can't download")); return false; }
|
|
|
|
char *cleanpath = unixpath(newstring(path));
|
|
filtertext(cleanpath, cleanpath, FTXT__MEDIAFILEPATH);
|
|
if(strcmp(path, cleanpath)) { conoutf("refuse to download \"%s\": illegal characters", path); delstring(cleanpath); return false; }
|
|
if(!havecurl || canceldownloads || type < 0 || type >= PCK_NUM || pendingpackages.access(cleanpath)) { delstring(cleanpath); return false; }
|
|
|
|
package *pck = new package;
|
|
pck->name = cleanpath;
|
|
pck->type = type;
|
|
pck->source = source;
|
|
pck->pending = true;
|
|
pendingpackages.access(pck->name, pck);
|
|
return true;
|
|
}
|
|
|
|
void writepcksourcecfg()
|
|
{
|
|
if(pckservers.length())
|
|
{
|
|
stream *f = openfile("config" PATHDIVS "pcksources.cfg", "w");
|
|
if(!f) return;
|
|
f->printf("// list of package source servers (only add servers you trust!)\n\n");
|
|
loopv(pckservers) f->printf("addpckserver %s\n", pckservers[i]->addr);
|
|
delete f;
|
|
}
|
|
}
|