DEADSOFTWARE

Первая версия протестированная на сервере
authorDeaDDooMER <deaddoomer@deadsoftware.ru>
Thu, 30 Mar 2017 20:05:35 +0000 (23:05 +0300)
committerDeaDDooMER <deaddoomer@deadsoftware.ru>
Thu, 30 Mar 2017 20:05:35 +0000 (23:05 +0300)
15 files changed:
.gitignore [new file with mode: 0644]
bsdmake.sh [new file with mode: 0755]
client.c [new file with mode: 0644]
client.h [new file with mode: 0644]
dedicated.c [new file with mode: 0644]
game.c [new file with mode: 0644]
game.h [new file with mode: 0644]
makefile [new file with mode: 0644]
netwar.c [new file with mode: 0644]
network.c [new file with mode: 0644]
network.h [new file with mode: 0644]
protocol.h [new file with mode: 0644]
server.c [new file with mode: 0644]
server.h [new file with mode: 0644]
start.bat [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..cbac3ed
--- /dev/null
@@ -0,0 +1,4 @@
+*.exe
+*.dll
+netwar
+dedicated
diff --git a/bsdmake.sh b/bsdmake.sh
new file mode 100755 (executable)
index 0000000..ad58d29
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..defe5ec
--- /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
new file mode 100644 (file)
index 0000000..1a85a23
--- /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
new file mode 100644 (file)
index 0000000..bef813c
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..cbc8e15
--- /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
new file mode 100644 (file)
index 0000000..17c2b68
--- /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
new file mode 100644 (file)
index 0000000..973189b
--- /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
new file mode 100644 (file)
index 0000000..2419456
--- /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
new file mode 100644 (file)
index 0000000..45adb04
--- /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
new file mode 100644 (file)
index 0000000..3dbcc65
--- /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
new file mode 100644 (file)
index 0000000..2681553
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..ea28583
--- /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
new file mode 100644 (file)
index 0000000..895062c
--- /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
new file mode 100644 (file)
index 0000000..3689d29
--- /dev/null
+++ b/start.bat
@@ -0,0 +1 @@
+netwar.exe -h succubus.deadsoftware.ru