author | DeaDDooMER <deaddoomer@deadsoftware.ru> | |
Thu, 30 Mar 2017 20:05:35 +0000 (23:05 +0300) | ||
committer | DeaDDooMER <deaddoomer@deadsoftware.ru> | |
Thu, 30 Mar 2017 20:05:35 +0000 (23:05 +0300) |
15 files changed:
.gitignore | [new file with mode: 0644] | patch | blob |
bsdmake.sh | [new file with mode: 0755] | patch | blob |
client.c | [new file with mode: 0644] | patch | blob |
client.h | [new file with mode: 0644] | patch | blob |
dedicated.c | [new file with mode: 0644] | patch | blob |
game.c | [new file with mode: 0644] | patch | blob |
game.h | [new file with mode: 0644] | patch | blob |
makefile | [new file with mode: 0644] | patch | blob |
netwar.c | [new file with mode: 0644] | patch | blob |
network.c | [new file with mode: 0644] | patch | blob |
network.h | [new file with mode: 0644] | patch | blob |
protocol.h | [new file with mode: 0644] | patch | blob |
server.c | [new file with mode: 0644] | patch | blob |
server.h | [new file with mode: 0644] | patch | blob |
start.bat | [new file with mode: 0644] | patch | blob |
diff --git a/.gitignore b/.gitignore
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.exe
+*.dll
+netwar
+dedicated
diff --git a/bsdmake.sh b/bsdmake.sh
--- /dev/null
+++ b/bsdmake.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec cc -o dedicated -lSDL2 -lSDL2_net -lm dedicated.c game.c server.c network.c -I/usr/local/include -L/usr/local/lib
diff --git a/client.c b/client.c
--- /dev/null
+++ b/client.c
@@ -0,0 +1,101 @@
+#include <assert.h>
+#include <SDL2/SDL_net.h>
+
+#include "game.h"
+#include "client.h"
+#include "network.h"
+#include "protocol.h"
+
+int cl_playerid;
+
+static bool run;
+static IPaddress addr;
+static UDPsocket sock;
+static char nickname[32] = "Anonymous";
+
+static void cl_kill_client(ProtocolMessage m) {
+ SDL_Log("Connection refused by server: %.*s\n", (int) sizeof(m.sv.kill.message), m.sv.kill.message);
+ cl_disconnect(true);
+}
+
+static void cl_update_svinfo(ProtocolMessage m) {
+ SDL_Log("Connected to server %.*s\n", (int) sizeof(m.sv.info.name), m.sv.info.name);
+ cl_playerid = m.sv.info.clientid;
+ assert(cl_playerid < MAX_PLAYERS);
+ SDL_Log("Player id%i\n", cl_playerid);
+}
+
+static void cl_update_svplayer(ProtocolMessage m) {
+ g_player_set(
+ m.sv.splr.clid,
+ m.sv.splr.live,
+ b2f(m.sv.splr.x),
+ b2f(m.sv.splr.y),
+ b2f(m.sv.splr.r),
+ b2f(m.sv.splr.vx),
+ b2f(m.sv.splr.vy),
+ b2f(m.sv.splr.vr)
+ );
+}
+
+void cl_connect(const char * host, uint16_t port) {
+ if(SDLNet_ResolveHost(&addr, host, (port) ? (port) : (DEFAULT_PORT)) != 0) {
+ SDL_Log("Unable to resolve host: %s\n", SDLNet_GetError());
+ exit(1);
+ }
+
+ sock = OpenPort(0);
+ SendMessage(sock, addr, cl_info(nickname));
+
+ ProtocolMessage m;
+ bool received = WaitMessage(sock, NULL, &m, 10000);
+ if(!received) {
+ SDL_Log("Connection timeout");
+ exit(1);
+ }
+
+ run = true;
+ if(m.type == SV_KILL) {
+ cl_kill_client(m);
+ } else if(m.type == SV_INFO) {
+ cl_update_svinfo(m);
+ } else {
+ SDL_Log("Invalid first message %i\n", m.type);
+ run = false;
+ }
+}
+
+void cl_move(DoesCode code) {
+ g_player_does(cl_playerid, code);
+ SendMessage(sock, addr, cl_does(code));
+}
+
+void cl_recv() {
+ IPaddress address;
+ ProtocolMessage m;
+ if(!RecvMessage(sock, &address, &m))
+ return;
+
+ switch(m.type) {
+ case SV_INFO: cl_update_svinfo(m); break;
+ case SV_KILL: cl_kill_client(m); break;
+ case SV_SPLR: cl_update_svplayer(m); break;
+ default: SDL_Log("invalid message %i", m.type);
+ }
+}
+
+void cl_disconnect(bool force) {
+ if(!run)
+ return;
+
+ if(!force)
+ SendMessage(sock, addr, cl_kill());
+
+ ClosePort(sock);
+
+ run = false;
+}
+
+bool cl_isrun() {
+ return run;
+}
diff --git a/client.h b/client.h
--- /dev/null
+++ b/client.h
@@ -0,0 +1,9 @@
+#include <stdbool.h>
+
+extern int cl_playerid;
+
+void cl_connect(const char * host, uint16_t port);
+void cl_disconnect(bool force);
+void cl_move(DoesCode code);
+void cl_recv();
+bool cl_isrun();
diff --git a/dedicated.c b/dedicated.c
--- /dev/null
+++ b/dedicated.c
@@ -0,0 +1,66 @@
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_net.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "server.h"
+#include "protocol.h"
+
+static uint16_t port = DEFAULT_PORT;
+
+static void argverr() {
+ SDL_Log("Invalid argument\n");
+ exit(1);
+}
+
+static void useargs(char ** argv) {
+ int i = 1;
+ while(argv[i]) {
+ if(strcmp(argv[i], "-p") == 0) {
+ if(argv[++i])
+ port = atoi(argv[i++]);
+ else
+ argverr();
+ } else {
+ argverr();
+ }
+ }
+}
+
+static void sysinit() {
+ if(SDL_Init(SDL_INIT_EVENTS) != 0) {
+ SDL_Log("Unable to initialize SDL: %s\n", SDL_GetError());
+ exit(1);
+ }
+ atexit(SDL_Quit);
+
+ if(SDLNet_Init() != 0) {
+ SDL_Log("Unable to initialize SDLNet: %s\n", SDLNet_GetError());
+ exit(1);
+ }
+ atexit(SDLNet_Quit);
+}
+
+int main(int argc, char ** argv) {
+ sysinit();
+ useargs(argv);
+
+ sv_start(port);
+ sv_setclientlimit(32);
+
+ bool run = true;
+ while(run) {
+ SDL_Event event;
+ while(SDL_PollEvent(&event))
+ if(event.type == SDL_QUIT)
+ run = false;
+
+ sv_handle();
+
+ SDL_Delay(1);
+ }
+
+ sv_stop();
+
+ return 0;
+}
diff --git a/game.c b/game.c
--- /dev/null
+++ b/game.c
@@ -0,0 +1,65 @@
+#include <SDL2/SDL.h>
+
+#include <assert.h>
+#include <stdbool.h>
+#include <math.h>
+
+#include "game.h"
+
+#define SPEED 0.00006
+#define ROTATE 0.00004
+
+Player g_player[MAX_PLAYERS];
+
+static void moveplayer(int id, float speed) {
+ g_player[id].vx += speed * cos(g_player[id].r * 2 * M_PI);
+ g_player[id].vy += speed * sin(g_player[id].r * 2 * M_PI);
+}
+
+static void checkspacebound(float * a) {
+ float x = *a;
+ if(x < -1)
+ x = abs(x);
+ else if(x > 1)
+ x = -x;
+ *a = x;
+}
+
+void g_player_set(unsigned int id, bool live, float x, float y, float r, float vx, float vy, float vr) {
+ assert(id < MAX_PLAYERS);
+ g_player[id] = (Player) {
+ .updated = true,
+ .live = live,
+ .x = x,
+ .y = y,
+ .r = r,
+ .vx = vx,
+ .vy = vy,
+ .vr = vr,
+ };
+}
+
+void g_player_does(unsigned int id, DoesCode code) {
+ assert(id < MAX_PLAYERS);
+ g_player[id].updated = true;
+ switch(code) {
+ case DOES_UP: moveplayer(id, SPEED); break;
+ case DOES_DOWN: moveplayer(id, -SPEED); break;
+ case DOES_LEFT: g_player[id].vr -= ROTATE; break;
+ case DOES_RIGHT: g_player[id].vr += ROTATE; break;
+ case DOES_FIRE: break;
+ }
+}
+
+void g_update() {
+ for(int i = 0; i < MAX_PLAYERS; i++) {
+ if(g_player[i].live) {
+ g_player[i].updated = true;
+ g_player[i].x += g_player[i].vx;
+ g_player[i].y += g_player[i].vy;
+ g_player[i].r += g_player[i].vr;
+ checkspacebound(&g_player[i].x);
+ checkspacebound(&g_player[i].y);
+ }
+ }
+}
diff --git a/game.h b/game.h
--- /dev/null
+++ b/game.h
@@ -0,0 +1,23 @@
+#include <stdbool.h>
+
+#include "protocol.h"
+
+#define MAX_PLAYERS 64
+#define TICK_DELAY (1000 / 60)
+#define PLAYER_SIZE 0.01785
+
+typedef struct Player {
+ /* public */
+ bool live;
+ float x, y, r;
+ float vx, vy, vr;
+
+ /* internal */
+ bool updated;
+} Player;
+
+extern Player g_player[MAX_PLAYERS];
+
+void g_player_set(unsigned int id, bool live, float x, float y, float r, float vx, float vy, float vr);
+void g_player_does(unsigned int id, DoesCode code);
+void g_update();
diff --git a/makefile b/makefile
--- /dev/null
+++ b/makefile
@@ -0,0 +1,18 @@
+ifdef WIN32
+ CC := x86_64-w64-mingw32-gcc
+ CFLAGS := -g -std=gnu11 -Wall
+ LDFLAGS := -lmingw32 -lSDL2main -lSDL2 -lSDL2_net -lm
+ OUTPOSTFIX := .exe
+else
+ CC := gcc
+ CFLAGS := -g -std=gnu11 -Wall
+ LDFLAGS := -lSDL2_net -lSDL2 -lm
+endif
+
+all: dedicated.o netwar.o
+
+netwar.o:
+ $(CC) $(CFLAGS) -o netwar$(OUTPOSTFIX) netwar.c client.c network.c game.c $(LDFLAGS)
+
+dedicated.o:
+ $(CC) $(CFLAGS) -o dedicated$(OUTPOSTFIX) dedicated.c server.c network.c game.c $(LDFLAGS)
diff --git a/netwar.c b/netwar.c
--- /dev/null
+++ b/netwar.c
@@ -0,0 +1,165 @@
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_net.h>
+#include <string.h>
+#include <stdbool.h>
+#include <math.h>
+
+#include "game.h"
+#include "client.h"
+#include "protocol.h"
+
+#define WIDTH 640
+#define HEIGHT 480
+
+static const float ship[] = {
+ +1.0, +0.0, -1.0, -1.0,
+ -1.0, -1.0, +0.0, +0.0,
+ +0.0, +0.0, -1.0, +1.0,
+ -1.0, +1.0, +1.0, +0.0,
+};
+
+static char * host = "localhost";
+static char * nick = "Anonymous";
+static uint16_t port = DEFAULT_PORT;
+
+static SDL_Window * window;
+static SDL_Renderer * renderer;
+
+static void argverr() {
+ SDL_Log("Invalid argument\n");
+ exit(1);
+}
+
+static void useargs(char ** argv) {
+ int i = 1;
+ while(argv[i]) {
+ if(strcmp(argv[i], "-h") == 0) {
+ if(argv[++i])
+ host = argv[i++];
+ else
+ argverr();
+ } else if(strcmp(argv[i], "-p") == 0) {
+ if(argv[++i])
+ port = atoi(argv[i++]);
+ else
+ argverr();
+ } else if(strcmp(argv[i], "-l") == 0) {
+ if(argv[++i])
+ nick = argv[i++];
+ else
+ argverr();
+ } else {
+ argverr();
+ }
+ }
+}
+
+static void sysinit() {
+ if(SDL_Init(SDL_INIT_EVERYTHING) != 0) {
+ SDL_Log("Unable to initialize SDL: %s\n", SDL_GetError());
+ exit(1);
+ }
+ atexit(SDL_Quit);
+
+ if(SDLNet_Init() != 0) {
+ SDL_Log("Unable to initialize SDLNet: %s\n", SDLNet_GetError());
+ exit(1);
+ }
+ atexit(SDLNet_Quit);
+}
+
+static void openwindow() {
+ SDL_CreateWindowAndRenderer(WIDTH, HEIGHT, 0, &window, &renderer);
+ SDL_RenderPresent(renderer);
+}
+
+static void closewindow() {
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+}
+
+static void paintwindow() {
+ SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
+ SDL_RenderClear(renderer);
+
+ int cx = WIDTH / 2;
+ int cy = HEIGHT / 2;
+ int shipsz = (WIDTH + HEIGHT) / 2 * PLAYER_SIZE;
+
+ for(int i = 0; i < MAX_PLAYERS; i++) {
+ if(i == cl_playerid)
+ SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0x00);
+ else
+ SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0x00);
+
+ int count = sizeof(ship) / sizeof(ship[0]) / 2;
+ int x = g_player[i].x * WIDTH / 2;
+ int y = g_player[i].y * HEIGHT / 2;
+
+ SDL_Point pixship[count];
+ for(int j = 0; j < count * 2; j += 2) {
+ float c = cos(g_player[i].r * 2 * M_PI);
+ float s = sin(g_player[i].r * 2 * M_PI);
+ float rx = ship[j + 0] * shipsz;
+ float ry = ship[j + 1] * shipsz;
+ pixship[j / 2].x = cx + x + (c * rx - s * ry);
+ pixship[j / 2].y = cy + y + (s * rx + c * ry);
+ }
+
+ if(g_player[i].live)
+ SDL_RenderDrawLines(renderer, pixship, count);
+ }
+
+ SDL_RenderPresent(renderer);
+}
+
+static void keyboardhandle() {
+ SDL_PumpEvents();
+ const Uint8 * key = SDL_GetKeyboardState(NULL);
+ if(key[SDL_SCANCODE_UP])
+ cl_move(DOES_UP);
+ if(key[SDL_SCANCODE_DOWN])
+ cl_move(DOES_DOWN);
+ if(key[SDL_SCANCODE_LEFT])
+ cl_move(DOES_LEFT);
+ if(key[SDL_SCANCODE_RIGHT])
+ cl_move(DOES_RIGHT);
+ if(key[SDL_SCANCODE_LCTRL])
+ cl_move(DOES_FIRE);
+}
+
+int main(int argc, char ** argv) {
+ sysinit();
+ useargs(argv);
+ openwindow();
+
+ cl_connect(host, port);
+
+ Uint32 lastTime = SDL_GetTicks();
+ Uint32 currTime = SDL_GetTicks();
+
+ while(cl_isrun()) {
+ SDL_Event event;
+ while(SDL_PollEvent(&event))
+ if(event.type == SDL_QUIT)
+ goto cleanup;
+
+ if(currTime - lastTime >= TICK_DELAY) {
+ keyboardhandle();
+ g_update();
+ lastTime = SDL_GetTicks();
+ }
+ currTime = SDL_GetTicks();
+
+ cl_recv();
+
+ paintwindow();
+
+ SDL_Delay(1);
+ }
+
+cleanup:
+ closewindow();
+ cl_disconnect(false);
+ return 0;
+}
diff --git a/network.c b/network.c
--- /dev/null
+++ b/network.c
@@ -0,0 +1,56 @@
+#include "network.h"
+
+void SendMessage(UDPsocket sock, IPaddress address, ProtocolMessage m) {
+ UDPpacket packet = {
+ .channel = -1,
+ .data = (void *) &m,
+ .len = MessageSize(m),
+ .maxlen = sizeof(ProtocolMessage),
+ .status = 0,
+ .address = address,
+ };
+
+ if(!SDLNet_UDP_Send(sock, -1, &packet)) {
+ SDL_Log("Messge not sended: %s\n", SDLNet_GetError());
+ exit(1);
+ }
+}
+
+bool RecvMessage(UDPsocket sock, IPaddress * address, ProtocolMessage * m) {
+ UDPpacket packet = {
+ .channel = -1,
+ .data = (void *) m,
+ .len = sizeof(ProtocolMessage),
+ .maxlen = sizeof(ProtocolMessage),
+ .status = 0,
+ .address = (IPaddress) { 0, 0 },
+ };
+
+ int status;
+ status = SDLNet_UDP_Recv(sock, &packet);
+ if(status < 0)
+ goto error_recv;
+ else if(status == 0)
+ goto error_norecv;
+
+ if(address)
+ *address = packet.address;
+
+ return true;
+
+error_recv:
+ SDL_Log("Messge not received: %s\n", SDLNet_GetError());
+ exit(1);
+error_norecv:
+ return false;
+}
+
+bool WaitMessage(UDPsocket sock, IPaddress * address, ProtocolMessage * m, uint32_t timeout) {
+ uint32_t lastTime = SDL_GetTicks();
+ while(SDL_GetTicks() - lastTime < timeout) {
+ if(RecvMessage(sock, address, m))
+ return true;
+ SDL_Delay(1);
+ }
+ return false;
+}
diff --git a/network.h b/network.h
--- /dev/null
+++ b/network.h
@@ -0,0 +1,26 @@
+#ifndef NETWORK_H_INCLUDED
+#define NETWORK_H_INCLUDED
+
+#include <SDL2/SDL_net.h>
+#include <stdbool.h>
+
+#include "protocol.h"
+
+static inline UDPsocket OpenPort(uint16_t port) {
+ UDPsocket sock = SDLNet_UDP_Open(SDL_SwapLE16(port));
+ if(!sock) {
+ SDL_Log("SDLNet_UDP_Open: %s\n", SDLNet_GetError());
+ exit(1);
+ }
+ return sock;
+}
+
+static inline void ClosePort(UDPsocket sock) {
+ SDLNet_UDP_Close(sock);
+}
+
+void SendMessage(UDPsocket sock, IPaddress address, ProtocolMessage m);
+bool RecvMessage(UDPsocket sock, IPaddress * address, ProtocolMessage * m);
+bool WaitMessage(UDPsocket sock, IPaddress * address, ProtocolMessage * m, uint32_t timeout);
+
+#endif /* NETWORK_H_INCLUDED */
diff --git a/protocol.h b/protocol.h
--- /dev/null
+++ b/protocol.h
@@ -0,0 +1,167 @@
+#ifndef PROTOCOL_H_INCLUDED
+#define PROTOCOL_H_INCLUDED
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#define DEFAULT_PORT 29386
+#define PROTOCOL_VERSION 0
+#define PROTOCOL_F8FRAC (1 << 7)
+
+#define PACKED __attribute__((__packed__))
+
+typedef enum {
+ INVALID = 0,
+
+ CL_INFO = 1,
+ CL_KILL = 2,
+ CL_DOES = 3,
+
+ SV_INFO = 128,
+ SV_KILL = 139,
+ SV_SPLR = 130,
+} MessageType;
+
+typedef enum {
+ DOES_UP = 0,
+ DOES_DOWN = 1,
+ DOES_LEFT = 2,
+ DOES_RIGHT = 3,
+ DOES_FIRE = 4,
+} DoesCode;
+
+typedef struct PACKED ClInfo {
+ uint8_t type;
+ uint8_t version;
+ uint8_t name[32];
+} ClInfo;
+
+typedef struct PACKED ClKill {
+ uint8_t type;
+} ClKill;
+
+typedef struct PACKED ClDoes {
+ uint8_t type;
+ uint8_t code;
+} ClDoes;
+
+typedef struct PACKED SvInfo {
+ uint8_t type;
+ uint8_t version;
+ uint8_t clientid;
+ uint8_t maxclients;
+ uint8_t name[32];
+} SvInfo;
+
+typedef struct PACKED SvKill {
+ uint8_t type;
+ uint8_t message[256];
+} SvKill;
+
+typedef struct PACKED SvSplr {
+ uint8_t type;
+ uint8_t clid;
+ uint8_t live;
+ uint8_t x, y, r;
+ uint8_t vx, vy, vr;
+} SvSplr;
+
+typedef union ClMessage {
+ uint8_t type;
+ ClInfo info;
+ ClKill kill;
+ ClDoes does;
+} ClMessage;
+
+typedef union SvMessage {
+ uint8_t type;
+ SvInfo info;
+ SvKill kill;
+ SvSplr splr;
+} SvMessage;
+
+typedef union ProtocolMessage {
+ uint8_t type;
+ ClMessage cl;
+ SvMessage sv;
+} ProtocolMessage;
+
+static inline int8_t f2b(float x) { return x * PROTOCOL_F8FRAC; }
+
+static inline float b2f(int8_t x) { return (float) x / PROTOCOL_F8FRAC; }
+
+static inline int MessageTypeSize(MessageType type) {
+ switch(type) {
+ case CL_INFO: return sizeof(ClInfo);
+ case CL_KILL: return sizeof(ClKill);
+ case CL_DOES: return sizeof(ClDoes);
+ case SV_INFO: return sizeof(SvInfo);
+ case SV_KILL: return sizeof(SvKill);
+ case SV_SPLR: return sizeof(SvSplr);
+ default: return sizeof(ProtocolMessage);
+ }
+}
+
+static inline int MessageSize(ProtocolMessage m) {
+ return MessageTypeSize(m.type);
+}
+
+static inline ProtocolMessage cl_info(const char * name) {
+ ClInfo m = {
+ .type = CL_INFO,
+ .version = PROTOCOL_VERSION,
+ };
+ strncpy((char*)m.name, name, sizeof(m.name));
+ return (ProtocolMessage) (ClMessage) m;
+}
+
+static inline ProtocolMessage cl_kill() {
+ return (ProtocolMessage) (ClMessage) (ClKill) {
+ .type = CL_KILL,
+ };
+}
+
+static inline ProtocolMessage cl_does(DoesCode code) {
+ return (ProtocolMessage) (ClMessage) (ClDoes) {
+ .type = CL_DOES,
+ .code = code,
+ };
+}
+
+static inline ProtocolMessage sv_info(const char * name, int clientid, int maxclients) {
+ SvInfo m = {
+ .type = SV_INFO,
+ .version = PROTOCOL_VERSION,
+ .clientid = clientid,
+ .maxclients = maxclients,
+ };
+ strncpy((char*) m.name, name, sizeof(m.name));
+ return (ProtocolMessage) (SvMessage) m;
+}
+
+static inline ProtocolMessage sv_kill(const char * msg) {
+ SvKill m = {
+ .type = SV_KILL,
+ };
+ strncpy((char*) m.message, msg, sizeof(m.message));
+ return (ProtocolMessage) (SvMessage) m;
+}
+
+static inline ProtocolMessage sv_splr(int clid, bool live, float x, float y, float r, float vx, float vy, float vr) {
+ return (ProtocolMessage) (SvMessage) (SvSplr) {
+ .type = SV_SPLR,
+ .clid = clid,
+ .live = live,
+ .x = f2b(x),
+ .y = f2b(y),
+ .r = f2b(r),
+ .vx = f2b(vx),
+ .vy = f2b(vy),
+ .vr = f2b(vr),
+ };
+}
+
+#undef PACKED
+
+#endif /* PROTOCOL_H_INCLUDED */
diff --git a/server.c b/server.c
--- /dev/null
+++ b/server.c
@@ -0,0 +1,148 @@
+#include <assert.h>
+#include <SDL2/SDL_net.h>
+
+#include "game.h"
+#include "server.h"
+#include "network.h"
+#include "protocol.h"
+
+#define MAX_NAMESIZE 32
+
+typedef struct Client {
+ bool connected;
+ IPaddress address;
+ char name[MAX_NAMESIZE];
+} Client;
+
+static Uint32 lastTime;
+static Uint32 currTime;
+
+static UDPsocket sock;
+static Client client[MAX_PLAYERS];
+static char name[MAX_NAMESIZE] = "netwar! server";
+static int clientlimit = 0;
+
+static inline float randf() {
+ return (float) rand() / (float) RAND_MAX;
+}
+
+static inline float randfm() {
+ return (rand() % 2) ? (-randf()) : (+randf());
+}
+
+static inline bool cmpaddr(IPaddress a, IPaddress b) {
+ return (a.host == b.host) && (a.port == b.port);
+}
+
+static int sv_find_client(IPaddress address) {
+ for(int i = 0; i < clientlimit; i++)
+ if(client[i].connected && cmpaddr(client[i].address, address))
+ return i;
+ return -1;
+}
+
+static void sv_sync_client(int id, bool full) {
+ assert(id >= 0);
+ assert(client[id].connected);
+
+ for(int i = 0; i < clientlimit; i++)
+ if(full || g_player[i].updated)
+ SendMessage(sock, client[id].address, sv_splr(i, g_player[i].live, g_player[i].x, g_player[i].y, g_player[i].r, g_player[i].vx, g_player[i].vy, g_player[i].vr));
+}
+
+static void sv_kill_player(int id, bool send, const char * msg) {
+ assert(id >= 0);
+ assert(client[id].connected);
+
+ if(send)
+ SendMessage(sock, client[id].address, sv_kill(msg));
+
+ client[id].connected = false;
+ g_player_set(id, false, 0, 0, 0, 0, 0, 0);
+ SDL_Log("Player %s (%i) disconnected: %s", client[id].name, id, msg);
+}
+
+static void sv_move_player(int id, ProtocolMessage m) {
+ assert(id >= 0);
+ assert(client[id].connected);
+
+ g_player_does(id, m.cl.does.code);
+}
+
+static void sv_register_player(IPaddress address, ProtocolMessage m) {
+ if(m.cl.info.version != PROTOCOL_VERSION)
+ SendMessage(sock, address, sv_kill("Uncompatible version"));
+
+ int i = 0;
+ while((i < clientlimit) && (client[i].connected))
+ ++i;
+
+ int namesize = sizeof(m.cl.info.name);
+ if(i >= clientlimit) {
+ SDL_Log("Too may clints, player %.*s disconnected", namesize, m.cl.info.name);
+ SendMessage(sock, address, sv_kill("Too many clients"));
+ return;
+ }
+
+ client[i].connected = true;
+ client[i].address = address;
+ strncpy(client[i].name, (char*) m.cl.info.name, MAX_NAMESIZE);
+ client[i].name[MAX_NAMESIZE - 1] = 0;
+
+ SDL_Log("Player %s (%i) connected", client[i].name, i);
+ SendMessage(sock, address, sv_info(name, i, clientlimit));
+
+ g_player_set(i, true, randfm(), randfm(), randfm(), 0, 0, 0);
+ sv_sync_client(i, true);
+}
+
+static void sv_recv() {
+ IPaddress address;
+ ProtocolMessage m;
+ if(!RecvMessage(sock, &address, &m))
+ return;
+
+ switch(m.type) {
+ case CL_INFO: sv_register_player(address, m); break;
+ case CL_KILL: sv_kill_player(sv_find_client(address), false, ""); break;
+ case CL_DOES: sv_move_player(sv_find_client(address), m); break;
+ default: SDL_Log("invalid message %i", m.type);
+ }
+}
+
+static void sv_send() {
+ for(int i = 0; i < clientlimit; i++)
+ if(client[i].connected)
+ sv_sync_client(i, false);
+
+ for(int i = 0; i < clientlimit; i++)
+ g_player[i].updated = false;
+}
+
+void sv_start(uint16_t port) {
+ sock = OpenPort(port);
+ lastTime = SDL_GetTicks();
+ currTime = SDL_GetTicks();
+}
+
+void sv_stop() {
+ for(int i = 0; i < clientlimit; i++)
+ if(client[i].connected)
+ sv_kill_player(i, true, "Server shutdown");
+ ClosePort(sock);
+}
+
+void sv_setclientlimit(unsigned int maxclients) {
+ // TODO disconnect clients
+ clientlimit = (maxclients > MAX_PLAYERS) ? (MAX_PLAYERS) : (maxclients);
+}
+
+void sv_handle() {
+ if(currTime - lastTime >= TICK_DELAY) {
+ sv_recv();
+ g_update();
+ sv_send();
+ lastTime = SDL_GetTicks();
+ }
+ currTime = SDL_GetTicks();
+}
diff --git a/server.h b/server.h
--- /dev/null
+++ b/server.h
@@ -0,0 +1,7 @@
+#include <stdint.h>
+
+void sv_start (uint16_t port);
+void sv_stop ();
+void sv_handle ();
+
+void sv_setclientlimit(unsigned int maxclients);
diff --git a/start.bat b/start.bat
--- /dev/null
+++ b/start.bat
@@ -0,0 +1 @@
+netwar.exe -h succubus.deadsoftware.ru