/* * Copyright (C) 1999 Peter Amstutz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of *the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ #include #include #include #include #include #include #include #include #include "player.h" #include "relay.h" #include "game.h" #include "terrain.h" #include "packets.h" #include "ballistics.h" #include "kserver.h" #include "log.h" #include "svrhandlers.h" char colorcounter = 0; char gm_stuff_happening; /* Creates a player structure for a new login, called from relay. */ void shNewPlayer(Relay_rl * rl, int id, char *pkt, int pktlen) { Player_pl *pcur; char buf[512]; struct NewPlayer_pkt nppkt; struct SetGameMode_pkt gmpkt; struct PlayerID_pkt urpkt; struct PlayerID_pkt nrpkt; struct SetTank_pkt stpkt; struct TerrainInfo_pkt ti; Player_pl *pl; int x; urpkt.type[0] = 'P'; urpkt.type[1] = 'V'; urpkt.id = PROTOCOL_VERSION; rlSend(rl, id, buf, pktPackPlayerID(buf, &urpkt)); pl = plCreatePlayer(); pl->id = id; pl->money = gm_capital; pl->ready = OBSERVER; pl->name = NULL; pl->score = 0; pl->roundScore = 0; pl->tankcolor = observer_color; /* tell the new player about the other players */ nppkt.type[0] = 'N'; nppkt.type[1] = 'P'; for(pcur = pl_begin; pcur; pcur = pcur->next) { if(pcur != pl) { nppkt.id = pcur->id; nppkt.ready = pcur->ready; nppkt.color = pcur->tankcolor; if(pcur->name) strcpy(nppkt.name, pcur->name); else nppkt.name[0] = 0; rlSend(rl, id, buf, pktPackNewPlayer(buf, &nppkt)); logPrintf(SPAM, "Sending info from id %i to new player id %i\n", pcur->id, id); } } /* now that it is inserted into the local player list, tell the clients */ nppkt.id = pl->id; nppkt.ready = pl->ready; nppkt.color = pl->tankcolor; if(pl_end->name) strcpy(nppkt.name, pl_end->name); else nppkt.name[0] = 0; x = 0; rlBroadcast(rl, &x, buf, pktPackNewPlayer(buf, &nppkt)); logPrintf(SPAM, "Sending info from new player id %i to all\n", id); /* tell the new player what game type we are playing */ gmpkt.type[0]='G'; gmpkt.type[1]='T'; gmpkt.gamemode=gm_gametype; rlSend(rl, id, buf, pktPackSetGameMode(buf, &gmpkt)); /* tell the new player the if we are in game or in pregame */ gmpkt.type[0] = 'G'; gmpkt.type[1] = 'M'; gmpkt.gamemode = gm_gamemode; rlSend(rl, id, buf, pktPackSetGameMode(buf, &gmpkt)); /* give the client it's id# */ urpkt.type[0] = 'U'; urpkt.type[1] = 'R'; urpkt.id = id; rlSend(rl, id, buf, pktPackPlayerID(buf, &urpkt)); /* reuse this to send the client's money */ urpkt.type[0] = 'S'; urpkt.type[1] = 'M'; urpkt.id = pl->money; rlSend(rl, id, buf, pktPackPlayerID(buf, &urpkt)); /* send the current and total round #'s to the client */ nrpkt.type[0] = 'T'; nrpkt.type[1] = 'R'; nrpkt.id = gm_totalRounds; rlSend(rl, id, buf, pktPackPlayerID(buf, &nrpkt)); nrpkt.type[0] = 'N'; nrpkt.type[1] = 'R'; nrpkt.id = gm_currentRound; rlSend(rl, id, buf, pktPackPlayerID(buf, &nrpkt)); /* if we're in game, the client can observe, so send terrain, tank position data, etc */ if(gm_gamemode == INGAME) { int who[2]; ti.type[0] = 'N'; ti.type[1] = 'T'; ti.sizex = ter_sizex; ti.sizey = ter_sizey; ti.lerp_tweak = bal_lerp_tweak * 0xFFFF; ti.grav = bal_grav * 0xFFFF; rlSend(rl, id, buf, pktPackTerrainInfo(buf, &ti)); /* Send wall type */ urpkt.type[0] = 'W'; urpkt.type[1] = 'T'; urpkt.id = (int) bal_wall; rlSend(rl, id, buf, pktPackWallType(buf, &urpkt)); who[0] = 1; who[1] = id; svSendWind(who); for(pcur = pl_begin; pcur; pcur = pcur->next) { if(pcur->ready == READY) { stpkt.type[0] = 'S'; stpkt.type[1] = 'T'; stpkt.id = pcur->id; stpkt.x = pcur->x; stpkt.y = pcur->y; stpkt.a = pcur->fire_angle; stpkt.v = pcur->fire_velocity; stpkt.armor = pcur->armor; rlSend(rl, id, buf, pktPackSetTank(buf, &stpkt)); } } } } /* client chatting */ void shMessage(Relay_rl * rl, int id, char *pkt, int pktlen) { int x = 0; char buf[512]; struct Message_pkt incoming; struct ColoredMessage_pkt outgoing; Player_pl *p; pktUnpackMessage(&incoming, pkt); outgoing.type[0] = 'M'; outgoing.type[1] = 'C'; p = plLookupPlayer(id); assert(p); /* IRC rules! */ if(!strncmp("/me ", incoming.message, 4) && strlen(incoming.message) > 4) sprintf(outgoing.message, "* %s %s", p->name, incoming.message + 4); else sprintf(outgoing.message, "<%s> %s", p->name, incoming.message); outgoing.color = p->tankcolor; rlBroadcast(rl, &x, buf, pktPackColoredMessage(buf, &outgoing)); logPrintf(INTERESTING, "%s\n", outgoing.message); } /* transfers terrain data to the client */ void shSendTerrain(Relay_rl * rl, int id, char *pkt, int pktlen) { short int q; TerrainSpans_ter *tmp; int i, x, s; struct UpdateTerrain_pkt outpkt; char buf[768]; outpkt.type[0] = 'U'; outpkt.type[1] = 'T'; for(tmp = &ter_data[0], x = 0; x < ter_sizex;) { /* basically we're serializing the linked list that we actually * hold in memory - start/height pairs are sent. A 0 start * indicates the start of a new column */ s = x; for(i = 0; i < 250 && x < ter_sizex; i += 2) { q = tmp->start; outpkt.ter[i] = q; q = tmp->height; outpkt.ter[i + 1] = q; if(tmp->nexthigher) tmp = tmp->nexthigher; else tmp = &ter_data[++x]; } if(x < ter_sizex && tmp != &ter_data[x]) for(; tmp->start != 0; i -= 2, tmp = tmp->nextlower) ; outpkt.startpos = s; outpkt.length = i; rlSend(rl, id, buf, pktPackUpdateTerrain(buf, &outpkt)); } } /* in pregame, toggles clients' readiness */ void shSetReady(Relay_rl * rl, int id, char *pkt, int pktlen) { struct ChangeReady_pkt cr; Player_pl *pl = plLookupPlayer(id); int toall = 0; char str[256]; Ready_pl oldrd; int oltc; assert(pl); if(gm_gamemode == INGAME) return; pktUnpackChangeReady(&cr, pkt); oldrd = pl->ready; oltc = pl->tankcolor; if(cr.r == OBSERVER) { if(pl->ready == OBSERVER) { pl->ready = NOTREADY; pl->tankcolor = shGetTankColor(); if(pl->tankcolor == observer_color) { pl->ready = OBSERVER; logPrintf(INTERESTING, "%s is trying to change from observer to notready, but server is full\n", pl->name); rlSend(rl, id, str, sprintf(str, "MSServer: Server is full, observing...") + 1); } } else { pl->ready = OBSERVER; pl->tankcolor = observer_color; } } else if(cr.r == READY) { if(pl->ready == OBSERVER) { pl->ready = READY; pl->tankcolor = shGetTankColor(); if(pl->tankcolor == observer_color) { pl->ready = OBSERVER; logPrintf(INTERESTING, "%s is trying to change from observer to ready, but server is full\n", pl->name); rlSend(rl, id, str, sprintf(str, "MSServer: Server is full, observing...") + 1); } } else if(pl->ready == NOTREADY) { pl->ready = READY; } else { pl->ready = NOTREADY; } } else { if(pl->ready == OBSERVER) { pl->ready = NOTREADY; pl->tankcolor = shGetTankColor(); if(pl->tankcolor == observer_color) { pl->ready = OBSERVER; logPrintf(INTERESTING, "%s is trying to change from observer to notready, but server is full\n", pl->name); rlSend(rl, id, str, sprintf(str, "MSServer: Server is full, observing...") + 1); } } else if(pl->ready == NOTREADY) { pl->ready = READY; } else { pl->ready = NOTREADY; } } if(oldrd != pl->ready) { cr.type[0] = 'C'; cr.type[1] = 'R'; cr.id = id; cr.r = (ubyte_pkt)pl->ready; rlBroadcast(rl, &toall, str, pktPackChangeReady(str, &cr)); if(gm_gamemode == PREGAME) { if(pl->ready == NOTREADY) { rlBroadcast(rl, &toall, str, sprintf(str, "MSServer: %s is not ready to play.", pl->name) + 1); } else if(pl->ready == READY) { rlBroadcast(rl, &toall, str, sprintf(str, "MSServer: %s is ready to play!", pl->name) + 1); } else if(pl->ready == OBSERVER) { rlBroadcast(rl, &toall, str, sprintf(str, "MSServer: %s is now Observer", pl->name) + 1); } } } if(oltc != pl->tankcolor) { struct TankColor_pkt tc; tc.type[0] = 'T'; tc.type[1] = 'C'; tc.color = pl->tankcolor; tc.id = id; rlBroadcast(rl, &toall, str, pktPackTankColor(str, &tc)); } } /* Sets client name. Clients aren't allowed to play with blank names, although they can observe */ void shSetName(Relay_rl * rl, int id, char *pkt, int pktlen) { Player_pl *pcur = plLookupPlayer(id); int bcast = 0; struct Message_pkt inpkt; struct SetName_pkt snpkt; char str[256]; Ready_pl oldready; assert(pcur); oldready = pcur->ready; pktUnpackMessage(&inpkt, pkt); if(pcur->name) { char *n = inpkt.message; if(!n[0]) n = "Observer"; logPrintf(INTERESTING, "%s has changed their name to %s\n", pcur->name, n); rlBroadcast(rl, &bcast, str, sprintf(str, "MSServer: %s has changed their name to %s", pcur->name, n) + 1); free(pcur->name); } else { if(inpkt.message[0]) { logPrintf(INTERESTING, "%s has joined the game\n", inpkt.message); rlBroadcast(rl, &bcast, str, sprintf(str, "MSServer: %s has joined the game", inpkt.message) + 1); } else { logPrintf(INTERESTING, "An observer has joined the game\n"); rlBroadcast(rl, &bcast, str, sprintf(str, "MSServer: An observer has joined the game") + 1); } } if(inpkt.message[0]) { pcur->name = strdup(inpkt.message); if(pcur->ready != OBSERVER && !strcmp(pcur->name, "Observer")) { pcur->ready = OBSERVER; } /*else if(gm_gamemode != INGAME) { pcur->ready = NOTREADY; }*/ } else { pcur->name = strdup("Observer"); pcur->ready = OBSERVER; } snpkt.type[0] = 'S'; snpkt.type[1] = 'N'; snpkt.id = id; strcpy(snpkt.name, pcur->name); rlBroadcast(rl, &bcast, str, pktPackSetName(str, &snpkt)); if(oldready != pcur->ready) { struct ChangeReady_pkt cr; struct TankColor_pkt tc; if(pcur->ready == OBSERVER) { tc.color = observer_color; } else { tc.color = shGetTankColor(); if(tc.color == observer_color) { logPrintf(INTERESTING, "%s is trying to change from observer to notready, but server is full\n", pcur->name); rlSend(rl, id, str, sprintf(str, "MSServer: Server is full, observing...") + 1); pcur->ready = OBSERVER; return; } } pcur->tankcolor = tc.color; tc.type[0] = 'T'; tc.type[1] = 'C'; tc.id = id; rlBroadcast(rl, &bcast, str, pktPackTankColor(str, &tc)); /* from OBSERVER to NOTREADY, and vice-versa */ cr.type[0] = 'C'; cr.type[1] = 'R'; cr.id = id; cr.r = pcur->ready; rlBroadcast(rl, &bcast, str, pktPackChangeReady(str, &cr)); } } /* gets a fire command from a client, determines if it is valid, and dispatches it to the other clients */ void shFireCommand(Relay_rl * rl, int id, char *pkt, int pktlen) { int toall = 0; struct FireCmd_pkt fc, sf; char buf[512]; struct Projectilelist_bal *prj; char m; Player_pl *pl; Weapon_wep *wp; pl = plLookupPlayer(id); assert(pl); if(pl->ready != READY || (gm_gametype == TAKETURNS && pl != gm_firing_order[gm_current_attacker])) return; for(prj = bal_Projectiles; prj; prj = prj->next) { if(prj->prjpos.id == id) return; } pktUnpackFireCmd(&fc, pkt); if(fc.a > 180) { logPrintf(INTERESTING, "Bogus weapon angle received: %us\n", fc.a); return; } if(fc.v > 1000) { logPrintf(INTERESTING, "Bogus weapon velocity received: %us\n", fc.v); return; } wp = wepLookupWeapon(fc.shottype); if(wp && plUseWeaponInStock(plLookupPlayer(id), wp, 1) > 0) { pl->fire_angle = fc.a; pl->barreloff_x = pl->fire_angle < 90 ? pl->barreloff_right : pl->barreloff_left; pl->fire_velocity = fc.v; logPrintf(DEBUG, "Fire command received for angle %i velocity %i\n", fc.a, fc.v); sf.type[0] = 'S'; sf.type[1] = 'F'; sf.id = id; sf.gen = gm_shotgeneration; sf.a = fc.a; sf.v = fc.v; strcpy(sf.shottype, fc.shottype); logPrintf(DEBUG, "Calling balNewShotAV: pl->x=%i; pl->barreloff_x=%i; pl->y=%i; pl->barreloff_y=%i, pl_barrelen=%i\n", pl->x, pl->barreloff_x, pl->y, pl->barreloff_y, pl_barrelen); balNewShotAV(id, gm_shotgeneration, pl->x + pl->barreloff_x + pl_barrelen * cos((pl->fire_angle / 180.0) * M_PI), pl->y + pl->barreloff_y + pl_barrelen * sin((pl->fire_angle / 180.0) * M_PI), pl->fire_angle, pl->fire_velocity, wp); rlBroadcast(rl, &toall, buf, pktPackFireCmd(buf, &sf)); if(gm_gametype == SIMULTANEOUS) { for(pl = pl_begin; pl; pl = pl->next) { if(pl->ready == READY) { for(m = 0, prj = bal_Projectiles; prj; prj = prj->next) { if(pl->id == prj->prjpos.id) { m = 1; break; } } if(!m) return; } } } gm_activate_shots = 1; } else { logPrintf(INTERESTING, "Received bogus weapon %.255s\n", fc.shottype); } } void shBuyWeapon(Relay_rl * rl, int id, char *pkt, int pktlen) { struct BuyWeapon_pkt bw; struct PlayerID_pkt money; char *buf = (char *) malloc(512); int r; pktUnpackBuyWeapon(&bw, pkt); if((r = plBuyWeapon(id, bw.weapontype, bw.count, NULL)) == 0) { rlSend(rl, id, pkt, pktlen); money.type[0] = 'S'; money.type[1] = 'M'; money.id = plLookupPlayer(id)->money; rlSend(rl, id, buf, pktPackPlayerID(buf, &money)); } else { bw.count = 0; pktPackBuyWeapon(pkt, &bw); rlSend(rl, id, pkt, pktlen); switch (r) { case 1: rlSend(rl, id, buf, sprintf(buf, "MSServer: Can't afford %s", bw.weapontype) + 1); break; case 2: rlSend(rl, id, buf, sprintf(buf, "MSServer: Maxed out on %s", bw.weapontype) + 1); break; case 3: rlSend(rl, id, buf, sprintf(buf, "MSServer: Can't buy %s", bw.weapontype) + 1); break; } } free(buf); } void shSellWeapon(Relay_rl * rl, int id, char *pkt, int pktlen) { struct BuyWeapon_pkt bw; struct PlayerID_pkt money; char *buf = (char *) malloc(512); pktUnpackBuyWeapon(&bw, pkt); if(plSellWeapon(id, bw.weapontype, bw.count) == 0) { memcpy(buf, pkt, pktlen); rlSend(rl, id, buf, pktlen); money.type[0] = 'S'; money.type[1] = 'M'; money.id = plLookupPlayer(id)->money; rlSend(rl, id, buf, pktPackPlayerID(buf, &money)); } else { bw.count = 0; pktPackBuyWeapon(pkt, &bw); rlSend(rl, id, pkt, pktlen); rlSend(rl, id, buf, sprintf(buf, "MSServer: You don't have enough %ss!", bw.weapontype) + 1); } free(buf); } void shResync(Relay_rl * rl, int id, char *pkt, int pktlen) { struct SetTank_pkt stpkt; struct TerrainInfo_pkt ti; char buf[512]; Player_pl *pcur; if(gm_gamemode == INGAME) { ti.type[0] = 'N'; ti.type[1] = 'T'; ti.sizex = ter_sizex; ti.sizey = ter_sizey; ti.lerp_tweak = bal_lerp_tweak * 0xFFFF; ti.grav = bal_grav * 0xFFFF; rlSend(rl, id, buf, pktPackTerrainInfo(buf, &ti)); for(pcur = pl_begin; pcur; pcur = pcur->next) { if(pcur->ready == READY) { stpkt.type[0] = 'S'; stpkt.type[1] = 'T'; stpkt.id = pcur->id; stpkt.x = pcur->x; stpkt.y = pcur->y; stpkt.a = pcur->fire_angle; stpkt.v = pcur->fire_velocity; stpkt.armor = pcur->armor; rlSend(rl, id, buf, pktPackSetTank(buf, &stpkt)); } } } } /* called when a client disconnects */ void shTheyLeft(Relay_rl * rl, int id) { int toall = 0; Player_pl *pcur = plLookupPlayer(id); char buf[256]; struct PlayerID_pkt pid; if(pcur == NULL) return; if(pcur->name) { logPrintf(INTERESTING, "%s has left the game\n", pcur->name); rlBroadcast(rl, &toall, buf, sprintf(buf, "MSServer: %s has left the game", pcur->name) + 1); free(pcur->name); } if(pcur->ready != OBSERVER) { --gm_numberPlayers; if(pcur->ready == READY) { gm_activeplayers--; if(gm_gamemode == INGAME && !gm_stuff_happening) { register struct Projectilelist_bal *prj; for(prj = bal_Projectiles; prj; prj = prj->next) { if(prj->prjpos.id == pid.id) { prj->stat = FREEING; } } } } } if(pl_begin == pcur) pl_begin = pcur->next; if(pl_end == pcur) pl_end = pcur->prev; if(pcur->prev) pcur->prev->next = pcur->next; if(pcur->next) pcur->next->prev = pcur->prev; free(pcur); pid.type[0] = 'R'; pid.type[1] = 'P'; pid.id = id; rlBroadcast(rl, &toall, buf, pktPackPlayerID(buf, &pid)); } /* Return a unused color, if the server is full, returns observer_color */ int shGetTankColor() { Player_pl *pcur; int tankcolor; int firstcolor = colorcounter; do { tankcolor = colorcounter++; colorcounter %= MAX_TANKS; if(colorcounter == firstcolor) return observer_color; for(pcur = pl_begin; pcur; pcur = pcur->next) { if(pcur->tankcolor == tankcolor) break; } } while(pcur != NULL); return tankcolor; }