// client processing of the incoming network stream

#include "cube.h"
#include "bot/bot.h"

VARP(networkdebug, 0, 0, 1);
#define DEBUGCOND (networkdebug==1)

extern bool watchingdemo;
extern string clientpassword;
extern int ispaused;

void *downloaddemomenu = NULL;
static vector<mline> demo_mlines;

packetqueue pktlogger;

void neterr(const char *s)
{
    conoutf("\f3illegal network message (%s)", s);

    // might indicate a client/server communication bug, create error report
    pktlogger.flushtolog("packetlog.txt");
    conoutf("\f3wrote a network error report to packetlog.txt, please post this file to the bugtracker now!");

    disconnect();
}

VARP(autogetmap, 0, 1, 1); // only if the client doesn't have that map
VARP(autogetnewmaprevisions, 0, 1, 1);
VAR(disconnectonmaperror, 0, 1, 1);

extern bool incompatiblemap;
extern void trydisconnect();

bool localwrongmap = false;

bool changemapserv(char *name, int mode, int download, int revision)        // forced map change from the server
{
    gamemode = mode;
    if(m_demo) return true;
    if(m_coop)
    {
        if(!name[0] || load_world(name) < 0) empty_world(0, true);
        return true;
    }
    else if(player1->state==CS_EDITING) { /*conoutf("SANITY drop from EDITING");*/ toggleedit(true); } // fix stuck-in-editmode bug
    bool loaded = load_world(name) >= 0;
    if(disconnectonmaperror && incompatiblemap && multiplayer(NULL)) trydisconnect();
    if(download > 0)
    {
        bool revmatch = hdr.maprevision == revision || revision == 0;
        if(watchingdemo)
        {
            if(loaded && !revmatch) conoutf("\f3demo was recorded on map revision %d, you have map revision %d", revision, hdr.maprevision);
        }
        else
        {
            if(securemapcheck(name, false)) return true;
            bool sizematch = maploaded == download || download < 10;
            if(loaded && sizematch && revmatch) return true;
            bool getnewrev = autogetnewmaprevisions && revision > hdr.maprevision;
            if(autogetmap || getnewrev)
            {
                if(!loaded || getnewrev) getmap(); // no need to ask
                else
                {
                    defformatstring(msg)("map '%s' revision: local %d, provided by server %d", name, hdr.maprevision, revision);
                    alias("__getmaprevisions", msg, true);
                    showmenu("getmap");
                }
            }
            else
            {
                if(!loaded || download < 10) conoutf("\"getmap\" to download the current map from the server");
                else conoutf("\"getmap\" to download a %s version of the current map from the server",
                         revision == 0 ? "different" : (revision > hdr.maprevision ? "newer" : "older"));
            }
        }
    }
    else return true;
    return false;
}

// update the position of other clients in the game in our world
// don't care if he's in the scenery or other players,
// just don't overlap with our client

void updateplayerpos(playerent *d)
{
    const float r = player1->radius+d->radius;
    const float dx = player1->o.x-d->o.x;
    const float dy = player1->o.y-d->o.y;
    const float dz = player1->o.z-d->o.z;
    const float rz = player1->aboveeye+d->eyeheight;
    const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz);
    if(fx<r && fy<r && fz<rz && d->state!=CS_DEAD)
    {
        if(fx<fy) d->o.y += dy<0 ? r-fy : -(r-fy);  // push aside
        else      d->o.x += dx<0 ? r-fx : -(r-fx);
    }
}

void updatelagtime(playerent *d)
{
    int lagtime = totalmillis-d->lastupdate;
    if(lagtime)
    {
        if(d->state!=CS_SPAWNING && d->lastupdate) d->plag = (d->plag*5+lagtime)/6;
        d->lastupdate = totalmillis;
    }
}

void parsepositions(ucharbuf &p)
{
    int type;
    while(p.remaining()) switch(type = getint(p))
    {
        case SV_POS:                        // position of another client
        case SV_POSC:
        case SV_POSC2:
        case SV_POSC3:
        case SV_POSC4:
        {
            int cn, f, yaw, pitch, z;
            vec o, vel;
            if(type != SV_POS)
            {
                bitbuf<ucharbuf> q(p);
                cn = q.getbits(5);
                int usefactor = type - SV_POSC + 7;
                o.x = q.getbits(usefactor + 4) / DMF;
                o.y = q.getbits(usefactor + 4) / DMF;
                yaw = q.getbits(YAWBITS);
                pitch = q.getbits(PITCHBITS);
                f = q.getbits(FLAGBITS);
                if(f & (1 << 9))
                {
                    vel.x = (q.getbits(4) - 8) / DVELF;
                    vel.y = (q.getbits(4) - 8) / DVELF;
                    vel.z = (q.getbits(4) - 8) / DVELF;
                }
                else vel.x = vel.y = vel.z = 0.0f;
                int full = q.getbits(1);
                int s = q.rembits();
                if(s < 3) s += 8;
                if(full) s = 11;
                z = q.getbits(s);
            }
            else
            {
                cn = getint(p);
                o.x = getuint(p)/DMF;
                o.y = getuint(p)/DMF;
                z   = getuint(p);
                uint64_t ff = getuintn(p, (YAWBITS + PITCHBITS + FLAGBITS + 7) / 8);
                yaw = (ff >> (FLAGBITS + PITCHBITS)) & ((1 << YAWBITS) - 1);
                pitch = (ff >> FLAGBITS) & ((1 << PITCHBITS) - 1);
                f = ff & ((1 << FLAGBITS) - 1);
                if(f & (1 << 9))
                {
                    vel.x = getint(p)/DVELF;
                    vel.y = getint(p)/DVELF;
                    vel.z = getint(p)/DVELF;
                }
                else vel.x = vel.y = vel.z = 0.0f;
            }
            if(f & (1 << 10)) z = -z;
            o.z = z / DMF;
            int seqcolor = (f>>6)&1;
            playerent *d = getclient(cn);
            if(!d || seqcolor!=(d->lifesequence&1)) continue;
            vec oldpos(d->o);
            float oldyaw = d->yaw, oldpitch = d->pitch;
            d->o = o;
            d->o.z += d->eyeheight;
            d->yaw = decodeyaw(yaw);
            d->pitch = decodepitch(pitch);
            if(d->weaponsel->type == GUN_SNIPER)
            {
                sniperrifle *sr = (sniperrifle *)d->weaponsel;
                sr->scoped = d->scoping = (f & (1 << 5)) ? true : false;
            }
            else d->scoping = false;

            d->roll = 0;
            d->vel = vel;
            int ft = f & 0x1f;
            d->strafe = (ft % 3) - 1;
            ft /= 3;
            d->move = (ft % 3) - 1;
            ft /= 3;
            d->jumpd = (f & (1 << 8)) ? true : false; // FIXME
            d->onfloor = f & (1 << 7) ? true : false;
            d->onladder = ft == 1;
            d->last_pos = totalmillis;
            updatecrouch(d, ft == 2);
            updateplayerpos(d);
            updatelagtime(d);
            extern int smoothmove, smoothdist;
            if(d->state==CS_DEAD)
            {
                d->resetinterp();
                d->smoothmillis = 0;
            }
            else if(smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < smoothdist)
            {
                d->newpos = d->o;
                d->newpos.z -= d->eyeheight;
                d->newyaw = d->yaw;
                d->newpitch = d->pitch;
                d->o = oldpos;
                d->yaw = oldyaw;
                d->pitch = oldpitch;
                oldpos.z -= d->eyeheight;
                (d->deltapos = oldpos).sub(d->newpos);
                d->deltayaw = oldyaw - d->newyaw;
                if(d->deltayaw > 180) d->deltayaw -= 360;
                else if(d->deltayaw < -180) d->deltayaw += 360;
                d->deltapitch = oldpitch - d->newpitch;
                d->smoothmillis = lastmillis;
            }
            else d->smoothmillis = 0;
            if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE;
            // when playing a demo spectate first player we know about
            if(player1->isspectating() && player1->spectatemode==SM_NONE) togglespect();
            break;
        }

        default:
            neterr("type");
            return;
    }
}

int lastspawn = 0;

void onCallVote(int type, int vcn, char *text, char *a)
{
    exechook(HOOK_SP_MP, "onCallVote", "%d %d [%s] [%s]", type, vcn, text, a);
}

void onChangeVote(int mod, int id)
{
    exechook(HOOK_SP_MP, "onChangeVote", "%d %d", mod, id);
}

VARP(voicecomsounds, 0, 1, 2);

struct session_s
{
    enet_uint32 serverip, clientip, clientipcensored, curpeerip;
    int curpeerport, serverclock, clientclock, cn, clientsalt, datecodes;
    uchar serverpubkey[32], clientsignature[64];
    string clan, publiccomment;
} session;

int srvgamesalt;

void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false)
{
    static char text[MAXTRANS];
    int type, joining = 0;

    while(p.remaining())
    {
        type = getint(p);

        #ifdef _DEBUG
        if(type!=SV_POS && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_PONG && type!=SV_CLIENT)
        {
            DEBUGVAR(cn);
            ASSERT(type>=0 && type<SV_NUM);
            DEBUGVAR(messagenames[type]);
            protocoldebug(DEBUGCOND);
        }
        else protocoldebug(false);
        #endif

        switch(type)
        {
            case SV_SERVINFO:  // welcome message from the server
            {
                string tmp1, tmp2;
                session_s *s = &session;
                memset(s, 0, sizeof(session_s));

                s->cn = getint(p);
                int prot = getint(p);
                if(prot!=CUR_PROTOCOL_VERSION && !(watchingdemo && prot == -PROTOCOL_VERSION))
                {
                    conoutf("\f3incompatible game protocol (local protocol: %d :: server protocol: %d)", CUR_PROTOCOL_VERSION, prot);
                    conoutf("\f3if this occurs a lot, obtain an upgrade from \f1http://assault.cubers.net");
                    if(watchingdemo) conoutf("breaking loop : \f3this demo is using a different protocol\f5 : end it now!"); // SVN-WiP-bug: causes endless retry loop else!
                    else disconnect();
                    return;
                }
                int req_auth = getint(p);
                if(!sk && req_auth)
                {
                    conoutf("\f3server requires a valid game account to connect");
                    conoutf("\f3please authenticate or create a new game account");
                    disconnect();
                    return;
                }
                if(getint(p) > 0) conoutf("INFO: this server is password protected");
                sessionid = getint(p); // salt
                s->serverip = getip4(p);
                s->clientip = getip4(p);
                s->clientipcensored = getip4(p);
                s->serverclock = getint(p);
                char cc[3] = { (char)getint(p), (char)getint(p), 0 };
                filtercountrycode(cc, cc);
                p.get(s->serverpubkey, 32);
                entropy_get((uchar *)&(s->clientsalt), sizeof(int));
                s->clientclock = (int) (time(NULL) / (time_t) 60);
                int clockoffset = s->serverclock - s->clientclock;
                if(curpeer && iabs(clockoffset) > 60 * 24) conoutf("\f3warning: server and client clock are offset more than one day (%d minutes)", clockoffset);

                clientlogf("own IP: %s, censored own IP: %s, %s, clock offset %d hours %d minutes", iptoa(s->clientip, tmp1), iptoa(s->clientipcensored, tmp2), cc, clockoffset / 60, clockoffset % 60);
                if(curpeer && s->serverip)
                {
                    s->curpeerip = ENET_NET_TO_HOST_32(curpeer->address.host);
                    s->curpeerport = curpeer->address.port;
                }
                if(s->curpeerip && s->curpeerip != s->serverip) conoutf("\f3warning: server IP mismatch (true: %s, reported: %s)", iptoa(s->curpeerip, tmp1), iptoa(s->serverip, tmp2));

                defformatstring(challenge)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", sessionid, s->cn, iptoa(s->clientipcensored, tmp1), cc, iptoa(s->serverip, tmp2), s->curpeerport);
                concatformatstring(challenge, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, s->serverpubkey, 32), s->serverclock, s->clientclock, s->clientsalt);
                clientlogf("auth challenge: %s", challenge);
                int chlen = (int)strlen(challenge);
                if(chlen >= MAXSTRLEN - 65)
                {
                    conoutf("\f3ERROR: oversized auth challenge (%d), can't connect", chlen);
                    disconnect();
                    return;
                }
                if(sk) ed25519_sign((uchar*)challenge, NULL, (uchar*)challenge, chlen, sk);

                packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
                putint(pr, SV_SERVINFO_RESPONSE);
                putint(pr, s->clientsalt);
                putint(pr, s->clientclock);
                putint(pr, s->curpeerport);
                putip4(pr, s->curpeerip);
                int wantauth = sk && curpeer;
                putint(pr, wantauth);
                if(wantauth)
                {
                    memcpy(s->clientsignature, challenge, 64);
                    pr.put(sk + 32, 32);
                    pr.put(s->clientsignature, 64);
                }
                sendpackettoserv(1, pr.finalize());
                break;
            }

            case SV_SERVINFO_CONTD:  // 2nd welcome message from the server
            {
                session_s *s = &session;
                string tmp, servauth;
                int auth = getint(p);
                if(auth)
                {
                    formatstring(servauth)("SERVINFOSIGNED<%s>", bin2hex(tmp, (uchar*)s->clientsignature, 64));
                    memmove(servauth + 64, servauth, (auth = (int)strlen(servauth)) + 1);
                    p.get((uchar*)servauth, 64);
                    s->datecodes = getint(p);
                    getstring(text, p);
                    filtertext(s->clan, text, FTXT__VITACLAN);
                    getstring(text, p);
                    filtertext(s->publiccomment, text, FTXT__VITACOMMENT);
#ifdef _DEBUG
                    formatstring(text)("server-side%s", s->datecodes ? " timed flags:" : "");
                    loopi(VS_NUMCOUNTERS) if(s->datecodes & (1 << i)) concatformatstring(text, " %s", vskeywords[i]);
                    if(s->clan[0]) concatformatstring(text, " clan tag: \"%s\"", s->clan);
                    if(s->publiccomment[0]) concatformatstring(text, " comment: \"%s\"", s->publiccomment);
                    if(strlen(text) > 12) conoutf("%s", text);
#endif
                }
                if(curpeer && sk && !auth) conoutf("\f3server refuses to authenticate");
                if(sk && auth)
                {
                    if(!ed25519_sign_check((uchar*)servauth, auth + 64, s->serverpubkey)) conoutf("\f3server authentication failed");
                    else clientlogf("server authenticated as %s", bin2hex(tmp, s->serverpubkey, 32));
                }
                player1->clientnum = s->cn;
                sendintro();
                break;
            }

            case SV_WELCOME:
                joining = getint(p);
                player1->resetspec();
                resetcamera();
                break;

            case SV_CLIENT:
            {
                int cn = getint(p), len = getuint(p);
                ucharbuf q = p.subbuf(len);
                parsemessages(cn, getclient(cn), q, demo);
                break;
            }

            case SV_SOUND:
                audiomgr.playsound(getint(p), d);
                break;

            case SV_VOICECOMTEAM:
            {
                playerent *d = getclient(getint(p));
                if(d) d->lastvoicecom = lastmillis;
                int t = getint(p);
                if(!d || !(d->muted || d->ignored))
                {
                    if ( voicecomsounds == 1 || (voicecomsounds == 2 && m_teammode) ) audiomgr.playsound(t, SP_HIGH);
                }
                break;
            }
            case SV_VOICECOM:
            {
                int t = getint(p);
                if(!d || !(d->muted || d->ignored))
                {
                    if ( voicecomsounds == 1 ) audiomgr.playsound(t, SP_HIGH);
                }
                if(d) d->lastvoicecom = lastmillis;
                break;
            }

            case SV_TEAMTEXTME:
            case SV_TEAMTEXT:
            {
                int cn = getint(p);
                getstring(text, p);
                filtertext(text, text, FTXT__CHAT);
                playerent *d = getclient(cn);
                if(!d) break;
                if(d->ignored) clientlogf("ignored: %s%s %s", colorname(d), type == SV_TEAMTEXT ? ":" : "", text);
                else
                {
                    if(m_teammode || team_isspect(player1->team)) conoutf(type == SV_TEAMTEXTME ? "\f1%s %s" : "%s:\f1 %s", colorname(d), highlight(text));
                    else conoutf(type == SV_TEAMTEXTME ? "\f0%s %s" : "%s:\f0 %s", colorname(d), highlight(text));
                }
                break;
            }

            case SV_TEXTME:
            case SV_TEXT:
                if(cn == -1)
                {
                    getstring(text, p);
                    conoutf("MOTD:");
                    conoutf("\f4%s", text);
                }
                else if(d)
                {
                    getstring(text, p);
                    filtertext(text, text, FTXT__CHAT);
                    if(d->ignored && d->clientrole != CR_ADMIN) clientlogf("ignored: %s%s %s", colorname(d), type == SV_TEXT ? ":" : "", text);
                    else conoutf(type == SV_TEXTME ? "\f0%s %s" : "%s:\f0 %s", colorname(d), highlight(text));
                }
                else return;
                break;

            case SV_TEXTPRIVATE:
            {
                int src = getint(p), dst = getint(p);
                getstring(text, p);
                filtertext(text, text, FTXT__CHAT);
                playerent *s = getclient(src), *d = getclient(dst);
                if(!s) break;
                if(s == player1 && d) conoutf("to %s (PM):\f9 %s", colorname(d), highlight(text));
                else if(s->ignored) clientlogf("ignored: pm %s %s", colorname(s), text);
                else
                {
                    conoutf("%s (PM):\f9 %s", colorname(s), highlight(text));
                    lastpm = s->clientnum;
                    exechook(HOOK_SP_MP, "onPM", "%d [%s]", s->clientnum, text);
                }
                break;
            }

            case SV_MAPCHANGE:
            {
                extern int spawnpermission;
                spawnpermission = SP_SPECT;
                getstring(text, p);
                int mode = getint(p);
                int downloadable = getint(p);
                int revision = getint(p);
                srvgamesalt = getint(p);
                localwrongmap = !changemapserv(text, mode, downloadable, revision);
                if(m_arena && joining > 1 && !watchingdemo) deathstate(player1);
                break;
            }

            case SV_ITEMLIST:
            {
                int n;
                resetpickups();
                while((n = getint(p))!=-1) setpickupspawn(n, true);
                break;
            }

            case SV_MAPIDENT:
            {
                loopi(2) getint(p);
                break;
            }

            case SV_SWITCHNAME:
                getstring(text, p);
                filtertext(text, text, FTXT__PLAYERNAME, MAXNAMELEN);
                if(!text[0]) copystring(text, "unarmed");
                if(d)
                {
                    if(strcmp(d->name, text)) conoutf("%s is now known as %s", colorname(d), colorname(d, text));
                    exechook(HOOK_SP, "onNameChange", "%d \"%s\"", d->clientnum, text);
                    copystring(d->name, text, MAXNAMELEN+1);
                    updateclientname(d);
                }
                break;

            case SV_SWITCHTEAM:
                getint(p);
                break;

            case SV_SWITCHSKIN:
                loopi(2)
                {
                    int skin = getint(p);
                    if(d) d->setskin(i, skin);
                }
                break;

            case SV_INITCLIENT:            // another client either connected or changed name/team
            {
                int cn = getint(p);
                playerent *d = newclient(cn);
                if(!d)
                {
                    getstring(text, p);
                    loopi(2) getint(p);
                    getint(p);
                    loopi(4) getint(p);
                    getint(p);
                    break;
                }
                getstring(text, p);
                filtertext(text, text, FTXT__PLAYERNAME, MAXNAMELEN);
                if(!text[0]) copystring(text, "unarmed");
                if(d->name[0])          // already connected
                {
                    if(strcmp(d->name, text))
                        conoutf("%s is now known as %s", colorname(d), colorname(d, text));
                }
                else                    // new client
                {
                    conoutf("connected: %s", colorname(d, text));
                }
                copystring(d->name, text, MAXNAMELEN+1);
                exechook(HOOK_SP_MP, "onConnect", "%d", d->clientnum);
                loopi(2) d->setskin(i, getint(p));
                d->team = getint(p);

                d->maxroll = (float)clamp(getint(p), 0, ROLLMOVMAX);
                d->maxrolleffect = (float)clamp(getint(p), 0, ROLLEFFMAX);
                d->ffov = (float)clamp(getint(p), 75, 120);
                d->scopefov = (float)clamp(getint(p), 5, 60);
                d->address = getint(p); // partial IP address

                if(m_flags_) loopi(2)
                {
                    flaginfo &f = flaginfos[i];
                    if(!f.actor) f.actor = getclient(f.actor_cn);
                }
                updateclientname(d);
                break;
            }

            case SV_CDIS:
            {
                int cn = getint(p);
                playerent *d = getclient(cn);
                if(!d) break;
                if(d->name[0]) conoutf("player %s disconnected", colorname(d));
                zapplayer(players[cn]);
                exechook(HOOK_SP_MP, "onDisconnect", "%d", d->clientnum);
                break;
            }

            case SV_EDITMODE:
            {
                int val = getint(p);
                if(!d) break;
                if(val) d->state = CS_EDITING;
                else d->state = CS_ALIVE;
                break;
            }

            case SV_SPAWN:
            {
                playerent *s = d;
                if(!s) { static playerent dummy; s = &dummy; }
                s->respawn();
                s->lifesequence = getint(p);
                s->health = getint(p);
                s->armour = getint(p);
                int gunselect = getint(p);
                s->setprimary(gunselect);
                s->selectweapon(gunselect);
                loopi(NUMGUNS) s->ammo[i] = getint(p);
                loopi(NUMGUNS) s->mag[i] = getint(p);
                s->state = CS_SPAWNING;
                arenaintermission = 0;
                if(s->lifesequence==0) s->resetstats(); //NEW
                break;
            }

            case SV_SPAWNSTATE:
            {
                if(editmode) toggleedit(true);
                showscores(false);
                setscope(false);
                player1->respawn();
                player1->lifesequence = getint(p);
                player1->health = getint(p);
                player1->armour = getint(p);
                player1->setprimary(getint(p));
                player1->selectweapon(getint(p));
                int arenaspawn = getint(p);
                loopi(NUMGUNS) player1->ammo[i] = getint(p);
                loopi(NUMGUNS) player1->mag[i] = getint(p);
                player1->state = CS_ALIVE;
                lastspawn = lastmillis;
                findplayerstart(player1, false, arenaspawn);
                arenaintermission = 0;
                if(m_arena && !localwrongmap)
                {
                    if(curpeer) closemenu(NULL);
                    conoutf("new round starting... fight!");
                    hudeditf(HUDMSG_TIMER, "FIGHT!");
                    if(m_botmode) BotManager.RespawnBots();
                }
                addmsg(SV_SPAWN, "rii", player1->lifesequence, player1->weaponsel->type);
                player1->weaponswitch(player1->primweap, false);
                player1->weaponchanging -= weapon::weaponchangetime/2;
                if(player1->lifesequence==0) player1->resetstats(); //NEW
                break;
            }

            case SV_SPECTCN:
                getint(p);
                break;

            case SV_SHOTFX:
            {
                int scn = getint(p), gun = getint(p);
                vec from, to;
                loopk(3) to[k] = getint(p)/DMF;
                playerent *s = getclient(scn);
                if(!s || !valid_weapon(gun)) break;
                loopk(3) from[k] = s->o.v[k];
                if(gun==GUN_SHOTGUN) createrays(from, to);
                s->lastaction = lastmillis;
                s->weaponchanging = 0;
                s->mag[gun]--;
                if(s->weapons[gun])
                {
                    s->lastattackweapon = s->weapons[gun];
                    s->weapons[gun]->gunwait = s->weapons[gun]->info.attackdelay;
                    s->weapons[gun]->attackfx(from, to, -1);
                    s->weapons[gun]->reloading = 0;
                }
                s->pstatshots[gun]++; //NEW
                break;
            }

            case SV_THROWNADE:
            {
                vec from, vel;
                loopk(3) from[k] = getint(p)/DNF;
                loopk(3) vel[k] = getint(p)/DNF;
                int nademillis = getint(p);
                if(!d) break;
                d->lastaction = lastmillis;
                d->weaponchanging = 0;
                d->lastattackweapon = d->weapons[GUN_GRENADE];
                if(d->weapons[GUN_GRENADE])
                {
                    d->weapons[GUN_GRENADE]->attackfx(from, vel, nademillis);
                    d->weapons[GUN_GRENADE]->reloading = 0;
                }
                if(d!=player1) d->pstatshots[GUN_GRENADE]++; //NEW
                break;
            }

            case SV_RELOAD:
            {
                int cn = getint(p), gun = getint(p);
                if(!valid_weapon(gun)) break;

                playerent *p = getclient(cn);
                if(p && p!=player1) p->weapons[gun]->reload(false);
                break;
            }

            case SV_GIBDAMAGE:
            case SV_DAMAGE:
            {
                int tcn = getint(p),
                    acn = getint(p),
                    gun = getint(p),
                    damage = getint(p),
                    armour = getint(p),
                    health = getint(p);
                playerent *target = getclient(tcn), *actor = getclient(acn);
                if(!target || !actor || !valid_weapon(gun)) break;
                target->armour = armour;
                target->health = health;
                dodamage(damage, target, actor, -1, type==SV_GIBDAMAGE, false);
                actor->pstatdamage[gun]+=damage; //NEW
                break;
            }

            case SV_HITPUSH:
            {
                int gun = getint(p), damage = getint(p);
                vec dir;
                loopk(3) dir[k] = getint(p)/DNF;
                player1->hitpush(damage, dir, NULL, gun);
                break;
            }

            case SV_GIBDIED:
            case SV_DIED:
            {
                int vcn = getint(p), acn = getint(p), frags = getint(p), gun = getint(p);
                playerent *victim = getclient(vcn), *actor = getclient(acn);
                if(!actor) break;
                if ( m_mp(gamemode) ) actor->frags = frags;
                if(!victim) break;
                dokill(victim, actor, type==SV_GIBDIED, gun);
                break;
            }

            case SV_RESUME:
            {
                loopi(MAXCLIENTS)
                {
                    int cn = getint(p);
                    if(p.overread() || cn<0) break;
                    int state = getint(p),
                        lifesequence = getint(p),
                        primary = getint(p),
                        gunselect = getint(p),
                        flagscore = getint(p),
                        frags = getint(p),
                        deaths = getint(p),
                        health = getint(p),
                        armour = getint(p),
                        teamkills = getint(p);
                    int ammo[NUMGUNS], mag[NUMGUNS];
                    loopi(NUMGUNS) ammo[i] = getint(p);
                    loopi(NUMGUNS) mag[i] = getint(p);
                    playerent *d = (cn == getclientnum() ? player1 : newclient(cn));
                    if(!d) continue;
                    if(d!=player1) d->state = state;
                    d->lifesequence = lifesequence;
                    d->flagscore = flagscore;
                    d->frags = frags;
                    d->deaths = deaths;
                    d->tks = teamkills;
                    if(d!=player1)
                    {
                        d->setprimary(primary);
                        d->selectweapon(gunselect);
                        d->health = health;
                        d->armour = armour;
                        memcpy(d->ammo, ammo, sizeof(ammo));
                        memcpy(d->mag, mag, sizeof(mag));
                        if(d->lifesequence==0) d->resetstats(); //NEW
                    }
                }
                break;
            }

            case SV_DISCSCORES:
            {
                discscores.shrink(0);
                int team;
                while((team = getint(p)) >= 0)
                {
                    discscore &ds = discscores.add();
                    ds.team = team;
                    getstring(text, p);
                    filtertext(ds.name, text, FTXT__PLAYERNAME, MAXNAMELEN);
                    ds.flags = getint(p);
                    ds.frags = getint(p);
                    ds.deaths = getint(p);
                }
                break;
            }
            case SV_ITEMSPAWN:
            {
                int i = getint(p);
                setpickupspawn(i, true);
                break;
            }

            case SV_ITEMACC:
            {
                int i = getint(p), cn = getint(p);
                playerent *d = getclient(cn);
                pickupeffects(i, d);
                break;
            }

            case SV_EDITXY:              // coop editing messages, should be extended to include all possible editing ops
            {
                int cmd = getint(p);
                int x  = getint(p);
                int y  = getint(p);
                int xs = getint(p);
                int ys = getint(p);
                int a1 = getint(p);
                int a2 = getint(p);
                if(m_coop && !OUTBORD(x,y)  && xs > 0 && ys > 0 && !OUTBORD(x + xs-1, y + ys - 1))
                {
                    block b = { x, y, xs, ys };
                    switch(cmd)
                    {
                        case EDITXY_HEIGHT:   editheightxy(a1 != 0, a2, b); break;
                        case EDITXY_TEX:      edittexxy(a1, a2, b);         break;
                        case EDITXY_TYPE:     edittypexy(a1, b);            break;
                        case EDITXY_VDELTA:   setvdeltaxy(a1, b);           break;
                        case EDITXY_EQUALISE: editequalisexy(a1 != 0, b);   break;
                        case EDITXY_TAG:      edittagxy(a1, a2, b);         break;
                        case EDITXY_SLOPE:    slopexy(a1, a2, b);           break;
                        case EDITXY_STAIRS:   stairsxy(a1, a2, b);          break;
                        case EDITXY_FLIPROT:  selfliprotate(b, a1);         break;
                    }
                }
                break;
            }

            case SV_EDITARCH:
            {
                int av[50];  // MAXARCHVERT, hardcoded
                int x  = getint(p);
                int y  = getint(p);
                int xs = getint(p);
                int ys = getint(p);
                int a1 = getint(p);
                loopi(50) av[i] = getint(p);
                if(m_coop && !OUTBORD(x,y)  && xs > 0 && ys > 0 && !OUTBORD(x + xs-1, y + ys - 1))
                {
                    block b = { x, y, xs, ys };
                    archxy(a1, av, b);
                }
                break;
            }

            case SV_EDITBLOCK:
            {
                int bx = getuint(p), by = getuint(p), bxs = getuint(p), bys = getuint(p), light = getuint(p);
                ucharbuf *pp = getgzbuf(p);
                if(m_coop) netblockpastexy(pp, bx, by, bxs, bys, light);
                freegzbuf(pp);
                break;
            }

            case SV_NEWMAP:
            {
                int size = getint(p);
                if(!m_coop) break;
                if(size < 0 && sfactor > 9) break; // don't enlarge over 10 in MP
                empty_world(size, true);
                if(d && d!=player1)
                    conoutf(size>=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", colorname(d), sfactor);
                break;
            }

            case SV_EDITENT:            // coop edit of ent
            {
                if(!m_coop)
                {
                    loopi(12) getint(p);
                    break;
                }
                uint i = getint(p);
                while((uint)ents.length()<=i) ents.add().type = NOTUSED;
                int to = ents[i].type;
                if(ents[i].type==SOUND)
                {
                    entity &e = ents[i];

                    entityreference entref(&e);
                    location *loc = audiomgr.locations.find(e.attr1, &entref, mapsounds);

                    if(loc)
                        loc->drop();
                }

                ents[i].type = getint(p);
                ents[i].x = getint(p);
                ents[i].y = getint(p);
                ents[i].z = getint(p);
                ents[i].attr1 = getint(p);
                ents[i].attr2 = getint(p);
                ents[i].attr3 = getint(p);
                ents[i].attr4 = getint(p);
                ents[i].attr5 = getint(p);
                ents[i].attr6 = getint(p);
                ents[i].attr7 = getint(p);
                ents[i].spawned = false;
                if(ents[i].type==LIGHT || to==LIGHT) calclight();
                if(ents[i].type==SOUND) audiomgr.preloadmapsound(ents[i]);
                break;
            }

            case SV_PONG:
            {
                int millis = getint(p);
                addmsg(SV_CLIENTPING, "i", player1->ping = max(0, (player1->ping*5+totalmillis-millis)/6));
                break;
            }

            case SV_CLIENTPING:
                if(!d) return;
                d->ping = getint(p);
                break;

            case SV_TIMEUP:
            {
                int curgamemillis = getint(p);
                int curgamelimit = getint(p);
                timeupdate(curgamemillis, curgamelimit);
                break;
            }

            case SV_WEAPCHANGE:
            {
                int gun = getint(p);
                if(d) d->selectweapon(gun,d!=player1);
                break;
            }

            case SV_SERVMSGVERB:  // FIXME
            case SV_SERVMSG:
                getstring(text, p);
                conoutf("%s", text);
                break;

            case SV_FLAGINFO:
            {
                int flag = getint(p);
                if(flag<0 || flag>1) return;
                flaginfo &f = flaginfos[flag];
                f.state = getint(p);
                switch(f.state)
                {
                    case CTFF_STOLEN:
                        flagstolen(flag, getint(p));
                        break;
                    case CTFF_DROPPED:
                    {
                        float x = getuint(p)/DMF;
                        float y = getuint(p)/DMF;
                        float z = getuint(p)/DMF;
                        flagdropped(flag, x, y, z);
                        break;
                    }
                    case CTFF_INBASE:
                        flaginbase(flag);
                        break;
                    case CTFF_IDLE:
                        flagidle(flag);
                        break;
                }
                break;
            }

            case SV_FLAGMSG:
            {
                int flag = getint(p);
                int message = getint(p);
                int actor = getint(p);
                int flagtime = message == FM_KTFSCORE ? getint(p) : -1;
                flagmsg(flag, message, actor, flagtime);
                break;
            }

            case SV_FLAGCNT:
            {
                int fcn = getint(p);
                int flags = getint(p);
                playerent *p = getclient(fcn);
                if(p) p->flagscore = flags;
                break;
            }

            case SV_ARENAWIN:
            {
                int acn = getint(p);
                playerent *alive = getclient(acn);
                conoutf("the round is over! next round in 5 seconds...");
                if(m_botmode && acn==-2) hudoutf("the bots have won the round!");
                else if(acn==-1) hudoutf("everyone died!");
                else if(m_teammode) hudoutf("team %s has won the round!", team_string(alive->team));
                else if(alive==player1) hudoutf("you are the survivor!");
                else hudoutf("%s is the survivor!", colorname(alive));
                arenaintermission = lastmillis;
                break;
            }

            case SV_SPAWNDENY:
            {
                extern int spawnpermission;
                spawnpermission = getint(p);
                if(spawnpermission == SP_REFILLMATCH) hudoutf("\f3you can spawn now to refill your team");
                break;
            }
            case SV_FORCEDEATH:
            {
                int cn = getint(p);
                playerent *d = cn==getclientnum() ? player1 : newclient(cn);
                if(!d) break;
                deathstate(d);
                break;
            }

            case SV_SERVOPINFO:
            {
                loopv(players) { if(players[i]) players[i]->clientrole = CR_DEFAULT; }
                player1->clientrole = CR_DEFAULT;

                int cl = getint(p), r = getint(p);
                if(cl >= 0 && r >= 0)
                {
                    playerent *pl = (cl == getclientnum() ? player1 : newclient(cl));
                    if(pl)
                    {
                        pl->clientrole = r;
                        if(pl->name[0])
                        {
                            // two messages required to allow for proper german translation - is there a better way to do it?
                            if(pl==player1) conoutf("you claimed %s status", r == CR_ADMIN ? "admin" : "master");
                            else conoutf("%s claimed %s status", colorname(pl), r == CR_ADMIN ? "admin" : "master");
                        }
                    }
                }
                break;
            }

            case SV_TEAMDENY:
            {
                int t = getint(p);
                if(m_teammode)
                {
                    if(team_isvalid(t)) conoutf("you can't change to team %s", team_string(t));
                }
                else
                {
                    if(team_isspect(t)) conoutf("you can't change to spectate mode");
                    else if (player1->state!=CS_ALIVE) conoutf("you can't change to active mode");
                    else conoutf("you can't switch teams while being alive");
                }
                break;
            }

            case SV_SETTEAM:
            {
                int fpl = getint(p), fnt = getint(p), ftr = fnt >> 4;
                fnt &= 0x0f;
                playerent *d = (fpl == getclientnum() ? player1 : newclient(fpl));
                if(d)
                {
                    const char *nts = team_string(fnt);
                    bool you = fpl == player1->clientnum;
                    if(m_teammode || team_isspect(fnt))
                    {
                        if(d->team == fnt)
                        {
                            if(you && ftr == FTR_AUTOTEAM) hudoutf("you stay in team %s", nts);
                        }
                        else
                        {
                            if(you && !watchingdemo)
                            {
                                switch(ftr)
                                {
                                    case FTR_PLAYERWISH:
                                        conoutf("you're now in team %s", nts);
                                        break;
                                    case FTR_AUTOTEAM:
                                        hudoutf("the server forced you to team %s", nts);
                                        break;
                                }
                            }
                            else
                            {
                                const char *pls = colorname(d);
                                bool et = team_base(player1->team) != team_base(fnt);
                                switch(ftr)
                                {
                                    case FTR_PLAYERWISH:
                                        conoutf("player %s switched to team %s", pls, nts); // new message
                                        break;
                                    case FTR_AUTOTEAM:
                                        if(watchingdemo || team_isspect(player1->team)) conoutf("the server forced %s to team %s", colorname(d), nts);
                                        else hudoutf("the server forced %s to %s team", colorname(d), et ? "the enemy" : "your");
                                        break;
                                }
                            }
                            if(you && !team_isspect(d->team) && team_isspect(fnt) && d->state == CS_DEAD) spectatemode(SM_FLY);
                        }
                    }
                    else if(d->team != fnt && ftr == FTR_PLAYERWISH && !team_isactive(d->team)) conoutf("%s changed to active play", you ? "you" : colorname(d));
                    d->team = fnt;
                    if(team_isspect(d->team)) d->state = CS_SPECTATE;
                }
                break;
            }

            case SV_SERVERMODE:
            {
                int sm = getint(p);
                servstate.autoteam = sm & 1;
                servstate.mastermode = (sm >> 2) & MM_MASK;
                servstate.matchteamsize = sm >> 4;
                //if(sm & AT_SHUFFLE) playsound(TEAMSHUFFLE);    // TODO
                break;
            }

            case SV_PAUSEMODE:
            {
                ispaused = getint(p);
                if (player1->state == CS_ALIVE) player1->attacking = false;
                break;
            }

            case SV_CALLVOTE:
            {
                int type = getint(p);
                int vcn = -1, n_yes = 0, n_no = 0;
                if ( type == -1 )
                {
                    d = getclient(vcn = getint(p));
                    n_yes = getint(p);
                    n_no = getint(p);
                    type = getint(p);
                }
                if (type == SA_MAP && d == NULL) d = player1;      // gonext uses this
                if( type < 0 || type >= SA_NUM || !d ) return;
                votedisplayinfo *v = NULL;
                string a1, a2;
                switch(type)
                {
                    case SA_MAP:
                    {
                        getstring(text, p);
                        int mode = getint(p);
                        if(m_isdemo(mode)) filtertext(text, text, FTXT__DEMONAME);
                        else filtertext(text, behindpath(text), FTXT__MAPNAME);
                        itoa(a1, mode);
                        defformatstring(t)("%d", getint(p));
                        v = newvotedisplayinfo(d, type, text, a1, t);
                        break;
                    }
                    case SA_KICK:
                    case SA_BAN:
                    {
                        itoa(a1, getint(p));
                        getstring(text, p);
                        filtertext(text, text, FTXT__CHAT);
                        v = newvotedisplayinfo(d, type, a1, text);
                        break;
                    }
                    case SA_SERVERDESC:
                        getstring(text, p);
                        filtertext(text, text, FTXT__SERVDESC);
                        v = newvotedisplayinfo(d, type, text, NULL);
                        break;
                    case SA_STOPDEMO:
                        // compatibility
                        break;
                    case SA_REMBANS:
                    case SA_SHUFFLETEAMS:
                        v = newvotedisplayinfo(d, type, NULL, NULL);
                        break;
                    case SA_FORCETEAM:
                        itoa(a1, getint(p));
                        itoa(a2, getint(p));
                        v = newvotedisplayinfo(d, type, a1, a2);
                        break;
                    case SA_PAUSE:
                        itoa(a1, getint(p));
                        v = newvotedisplayinfo(d, type, a1, NULL);
                        break;
                    default:
                        itoa(a1, getint(p));
                        v = newvotedisplayinfo(d, type, a1, NULL);
                        break;
                }
                displayvote(v);
                onCallVote(type, v->owner->clientnum, text, a1);
                if (vcn >= 0)
                {
                    loopi(n_yes) votecount(VOTE_YES);
                    loopi(n_no) votecount(VOTE_NO);
                }
                extern int vote(int);
                if (d == player1) vote(VOTE_YES);
                break;
            }

            case SV_CALLVOTESUC:
            {
                callvotesuc();
                onChangeVote( 0, -1);
                break;
            }

            case SV_CALLVOTEERR:
            {
                int errn = getint(p);
                callvoteerr(errn);
                onChangeVote( 1, errn);
                break;
            }

            case SV_VOTE:
            {
                int vote = getint(p);
                votecount(vote);
                onChangeVote( 2, vote);
                break;
            }

            case SV_VOTERESULT:
            {
                int vres = getint(p);
                voteresult(vres);
                onChangeVote( 3, vres);
                break;
            }

            case SV_IPLIST:
            {
                int cn;
                while((cn = getint(p)) >= 0 && !p.overread())
                {
                    playerent *pl = getclient(cn);
                    int ip = getint(p);
                    if(!pl) continue;
                    else pl->address = ip;
                }
                break;
            }

            case SV_SENDDEMOLIST:
            {
                int demos = getint(p);
                menureset(downloaddemomenu);
                demo_mlines.shrink(0);
                if(!demos)
                {
                    conoutf("no demos available");
                    mline &m = demo_mlines.add();
                    copystring(m.name, "no demos available");
                    menuitemmanual(downloaddemomenu,m.name);
                }
                else
                {
                    demo_mlines.reserve(demos);
                    loopi(demos)
                    {
                        int seq = getint(p);
                        getstring(text, p); // info
                        filtertext(text, text, FTXT__DEMOINFO, MAXMAPNAMELEN + 64);
                        int len = getint(p); // len
                        concatformatstring(text, ", %.2f%s", len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
                        conoutf("%d. %s", seq, text);
                        mline &m = demo_mlines.add();
                        formatstring(m.name)("%d. %s", seq, text);
                        formatstring(m.cmd)("getdemo %d", seq);
                        menuitemmanual(downloaddemomenu, m.name, m.cmd);
                        getstring(text, p); // mapname
                        filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN);
                        getint(p); // gmode
                        getint(p); // timeplayed
                        getint(p); // timeremain
                        getint(p); // timestamp
                    }
                }
                break;
            }

            case SV_DEMOPLAYBACK:
            {
                bool demoplayback = false;
                string demofile;
                extern char *curdemofile;
                getstring(demofile, p, MAXSTRLEN);
                watchingdemo = demoplayback = demofile[0] != '\0';
                DELETEA(curdemofile);
                if(demoplayback)
                {
                    curdemofile = newstring(demofile);
                    player1->resetspec();
                    player1->state = CS_SPECTATE;
                    player1->team = TEAM_SPECT;
                }
                else
                {
                    // cleanups
                    curdemofile = newstring("n/a");
                    loopv(players) zapplayer(players[i]);
                    clearvote();
                    player1->state = CS_ALIVE;
                    player1->resetspec();
                }
                player1->clientnum = getint(p);
                break;
            }

            case SV_DEMOCHECKSUM: // raw data checksum of demo file
            {
                uchar dtiger[TIGERHASHSIZE];
                if(p.get(dtiger, TIGERHASHSIZE) == TIGERHASHSIZE)
                {
                    bin2hex(text, dtiger, TIGERHASHSIZE);
                    defformatstring(msg)("CLIENTDEMOEND<%s %d>", iptoa(session.curpeerip, text + 100), session.curpeerport);
                    int msglen = (int)strlen(msg);
                    ASSERT(msglen + 64 < MAXSTRLEN);
                    if(sk) ed25519_sign((uchar*)msg, NULL, (uchar*)msg, msglen, sk);
                    else memset(msg, 0, 64);

                    packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
                    putint(pr, SV_DEMOSIGNATURE);
                    putint(pr, session.curpeerport);
                    putip4(pr, session.curpeerip);
                    pr.put((uchar*)msg, 64);
                    sendpackettoserv(1, pr.finalize());
                }
                break;
            }
            case SV_DEMOSIGNATURE: // client acknowledging a demo checksum
                getint(p);
                getip4(p);
                p.get((uchar*)text, 64);
                break;
            
            case SV_VITADATA: { // Server sending vita data of a client. Possibly expose this to cubescript in the future
                uchar pubkey[32];
                char pubkeyhex[68];
                string privatecomment;
                string publiccomment;
                int vs[VS_NUM];
                
                int targetcn = getint(p);
                playerent *target = getclient(targetcn);
                
                if (target && p.get(pubkey, 32) == 32) {
                    getstring(text, p);
                    filtertext(privatecomment, text, FTXT__VITACOMMENT);
                    getstring(text, p);
                    filtertext(publiccomment, text, FTXT__VITACOMMENT);
                    
                    loopi(VS_NUM) vs[i] = getint(p);
                    
                    bin2hex(pubkeyhex, pubkey, 32);
                    
                    conoutf("\f0Vita Info for \f5%s \f0(\f5%d\f0) \f2pubkey: \f1%s", target->name, targetcn, pubkeyhex);
                    if(privatecomment[0]) conoutf("\f0Private Comment: \f1%s", privatecomment);
                    if(publiccomment[0]) conoutf("\f0Public Comment: \f1%s", publiccomment);
                    
                    string msg = "\f0Flags:";
                    for (int i = 0; i < VS_NUM; i++) {
                        concatformatstring(msg, " \f2%s=\f1%d", vskeywords[i], vs[i]);
                    }
                    conoutf("%s", msg);
                }
                break;
            }

            default:
                neterr("type");
                return;
        }
    }

    #ifdef _DEBUG
    protocoldebug(false);
    #endif
}

SVARP(demonameformat, "%w_%h_%n_%Mmin_%G");
SVARP(demotimestampformat, "%Y%m%d_%H%M");

void receivefile(uchar *data, int len)
{
    static char text[MAXTRANS];
    ucharbuf p(data, len);
    int type = getint(p);
    switch(type)
    {
        case SV_SENDDEMO:
        {
            /* int sequence = */ getint(p);
            getstring(text, p);
            filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN);
            int gmode =  getint(p);
            int timeplayed =  getint(p);
            int timeremain =  getint(p);
            int timestamp =  getint(p);
            int demosize = getint(p);
            if(demosize <= MAXDEMOSENDSIZE && demosize <= p.remaining())
            {
                extern string demosubpath;
                string buf;
                enet_uint32 ip = curpeer ? ntohl(curpeer->address.host) : 0;
                defformatstring(fname)("demos/%s%s.dmo", demosubpath, formatdemofilename(demonameformat, demotimestampformat, text, gmode, timestamp, timeplayed, timeremain, ip, buf));
                copystring(demosubpath, "");

                path(fname);
                stream *demo = openfile(fname, "wb");
                if(demo)
                {
                    conoutf("received demo \"%s\"", fname);
                    demo->write(&p.buf[p.len], demosize);
                    delete demo;
                }
                else conoutf("failed writing to \"%s\"", fname);
            }
            break;
        }

        case SV_RECVMAP:
        {
            getstring(text, p);
            filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN);
            conoutf("received map \"%s\" from server, reloading..", text);
            int mapsize = getint(p);
            int cfgsize = getint(p);
            int cfgsizegz = getint(p);
            int size = mapsize + cfgsizegz;
            if(mapsize <= MAXMAPSENDSIZE && cfgsizegz <= MAXMAPSENDSIZE && size <= MAXMAPSENDSIZE && cfgsize <= MAXCFGFILESIZE && size <= p.remaining() && !securemapcheck(behindpath(text)))
            {
                writemap(behindpath(text), mapsize, &p.buf[p.len]);
                p.len += mapsize;
                writecfggz(behindpath(text), cfgsize, cfgsizegz, &p.buf[p.len]);
                p.len += cfgsizegz;
            }
            break;
        }

        default:
            p.len = 0;
            parsemessages(-1, NULL, p);
            break;
    }
}

void servertoclient(int chan, uchar *buf, int len, bool demo)   // processes any updates from the server
{
    ucharbuf p(buf, len);
    switch(chan)
    {
        case 0: parsepositions(p); break;
        case 1: parsemessages(-1, NULL, p, demo); break;
        case 2: receivefile(p.buf, p.maxlen); break;
    }
}

void localservertoclient(int chan, uchar *buf, int len, bool demo)   // processes any updates from the server
{
//    pktlogger.queue(enet_packet_create (buf, len, 0));  // log local & demo packets
    servertoclient(chan, buf, len, demo);
}
