Moved code and assets for the game to a seperate folder. Moved jansson and raylib to third_party.

This commit is contained in:
2025-12-16 11:47:55 +01:00
parent fd2dbf232d
commit 8cdbd5b162
63 changed files with 14 additions and 14 deletions

View File

@@ -0,0 +1,155 @@
#include "game_session.h"
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
void game_session_init(Game_Session *session, Game_Session_Settings settings) {
assert(session != NULL);
session->settings = settings;
session->simulation_world = simulation_create_world(
session->settings.seed,
session->settings.online,
session->settings.max_players,
session->settings.level_width,
session->settings.level_height
);
session->players = (Game_Session_Player *)calloc(session->settings.max_players, sizeof(Game_Session_Player));
session->players_prev = (Game_Session_Player *)calloc(session->settings.max_players, sizeof(Game_Session_Player));
}
void game_session_dispose(Game_Session *session) {
if(session == NULL) {
return;
}
simulation_destroy_world(&session->simulation_world);
free(session->players);
session->players = NULL;
free(session->players_prev);
session->players_prev = NULL;
memset(session, 0, sizeof(Game_Session));
}
void game_session_init_default_settings(bool online, Game_Session_Settings *out_settings) {
out_settings->seed = 1337; // TODO: SS - Randomize.
out_settings->online = online;
out_settings->tickrate = 10.0;
out_settings->level_width = 64;
out_settings->level_height = 32;
out_settings->max_players = online ? 8 : 2;
}
void game_session_tick(Game_Session *session) {
for(uint16_t i = 0; i < session->settings.max_players; i++) {
Game_Session_Player *session_player = &session->players[i];
{ // Check if any players have joined/disconnected by comparing against the previous session_player.
Game_Session_Player *session_player_prev = &session->players_prev[i];
bool was_active = session_player_prev->active;
bool is_active = session_player->active;
if (!was_active && is_active) {
simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) {
.type = Simulation_Command_Type_Add_Player,
.player_id = i
});
} else if (was_active && !is_active) {
simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) {
.type = Simulation_Command_Type_Remove_Player,
.player_id = i
});
}
}
if(!session_player->active) {
continue;
}
// Update input.
simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) {
.type = Simulation_Command_Type_Set_Player_Input,
.player_id = i,
.player_input = session_player->input,
});
}
// Tick.
simulation_world_tick(&session->simulation_world);
// Copy 'players' from session to 'players_prev'.
memcpy(session->players_prev, session->players, sizeof(Game_Session_Player) * session->settings.max_players);
}
static Game_Session_Player *game_session_get_player_from_id(Game_Session *session, uint16_t player_index) {
assert(session != NULL);
if(player_index >= session->settings.max_players) {
return NULL;
}
return &session->players[player_index];
}
uint16_t game_session_get_amount_of_active_players(Game_Session *session) {
assert(session != NULL);
uint16_t amount = 0;
for(uint16_t i = 0; i < session->settings.max_players; i++) {
Game_Session_Player *player = &session->players[i];
if(player->active) {
amount += 1;
}
}
return amount;
}
bool game_session_create_player(Game_Session *session, uint16_t *out_player_index) {
assert(session != NULL);
int32_t found_index = -1;
for(uint16_t i = 0; i < session->settings.max_players; i++) {
Game_Session_Player *session_player = &session->players[i];
assert(session_player != NULL);
if(!session_player->active) {
found_index = i;
session_player->active = true;
break;
}
}
if(found_index < 0) {
return false;
}
*out_player_index = (uint16_t)found_index;
return true;
}
void game_session_destroy_player(Game_Session *session, uint16_t player_index) {
assert(session != NULL);
assert(player_index < session->settings.max_players);
Game_Session_Player *player = &session->players[player_index];
assert(player != NULL);
assert(player->active);
memset(player, 0, sizeof(Game_Session_Player));
}
bool game_session_set_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input) {
Game_Session_Player *player = game_session_get_player_from_id(session, player_index);
assert(player != NULL);
player->input = input;
return true;
}

View File

@@ -0,0 +1,58 @@
#ifndef GAME_SESSION_H
#define GAME_SESSION_H
#include <stdint.h>
#include <stdbool.h>
#include "shared/squeue.h"
#include "simulation/simulation_world.h"
#include "simulation/input.h"
#include "simulation/player.h"
typedef struct {
bool active;
Simulation_Game_Input input;
} Game_Session_Player;
typedef struct {
uint32_t seed;
bool online;
uint16_t level_width;
uint16_t level_height;
double tickrate;
uint8_t max_players;
// ..
} Game_Session_Settings;
typedef struct {
Game_Session_Settings settings;
Game_Session_Player *players;
Game_Session_Player *players_prev;
Simulation_World simulation_world;
} Game_Session;
void game_session_init(Game_Session *session, Game_Session_Settings settings);
void game_session_dispose(Game_Session *session);
void game_session_init_default_settings(bool online, Game_Session_Settings *out_settings);
void game_session_tick(Game_Session *session);
uint16_t game_session_get_amount_of_active_players(Game_Session *session);
bool game_session_create_player(Game_Session *session, uint16_t *out_player_index);
void game_session_destroy_player(Game_Session *session, uint16_t player_index);
bool game_session_set_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input);
#endif

View File

@@ -0,0 +1,758 @@
#include "multiplayer_api.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
typedef struct ListenerNode {
int id;
MultiplayerListener cb;
void *context;
struct ListenerNode *next;
} ListenerNode;
typedef struct ListenerSnapshot {
MultiplayerListener cb;
void *context;
} ListenerSnapshot;
struct MultiplayerApi {
char *server_host;
uint16_t server_port;
char identifier[37];
int sockfd;
char *session_id;
pthread_t recv_thread;
int recv_thread_started;
int running;
pthread_mutex_t lock;
ListenerNode *listeners;
int next_listener_id;
};
static int connect_to_server(const char *host, uint16_t port);
static int ensure_connected(MultiplayerApi *api);
static int send_all(int fd, const char *buf, size_t len);
static int send_json_line(MultiplayerApi *api, json_t *obj); /* tar över ägarskap */
static int read_line(int fd, char **out_line);
static void *recv_thread_main(void *arg);
static void process_line(MultiplayerApi *api, const char *line);
static int start_recv_thread(MultiplayerApi *api);
MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port, const char *identifier)
{
int len = strlen(identifier);
if (len != 36) {
return NULL;
}
MultiplayerApi *api = (MultiplayerApi *)calloc(1, sizeof(MultiplayerApi));
if (!api) {
return NULL;
}
if (server_host) {
api->server_host = strdup(server_host);
} else {
api->server_host = strdup("127.0.0.1");
}
if (!api->server_host) {
free(api);
return NULL;
}
api->server_port = server_port;
strncpy(api->identifier, identifier, 37);
api->sockfd = -1;
api->session_id = NULL;
api->recv_thread_started = 0;
api->running = 0;
api->listeners = NULL;
api->next_listener_id = 1;
if (pthread_mutex_init(&api->lock, NULL) != 0) {
free(api->server_host);
free(api);
return NULL;
}
return api;
}
void mp_api_destroy(MultiplayerApi *api) {
if (!api) return;
if (api->recv_thread_started && api->sockfd >= 0) {
shutdown(api->sockfd, SHUT_RDWR);
pthread_join(api->recv_thread, NULL);
}
if (api->sockfd >= 0) {
close(api->sockfd);
}
pthread_mutex_lock(&api->lock);
ListenerNode *node = api->listeners;
api->listeners = NULL;
pthread_mutex_unlock(&api->lock);
while (node) {
ListenerNode *next = node->next;
free(node);
node = next;
}
if (api->session_id) {
free(api->session_id);
}
if (api->server_host) {
free(api->server_host);
}
pthread_mutex_destroy(&api->lock);
free(api);
}
int mp_api_host(MultiplayerApi *api,
json_t *data,
char **out_session,
char **out_clientId,
json_t **out_data) {
if (!api) return MP_API_ERR_ARGUMENT;
if (api->session_id) return MP_API_ERR_STATE;
int rc = ensure_connected(api);
if (rc != MP_API_OK) return rc;
json_t *root = json_object();
if (!root) return MP_API_ERR_IO;
json_object_set_new(root, "identifier", json_string(api->identifier));
json_object_set_new(root, "cmd", json_string("host"));
json_t *data_copy;
if (data && json_is_object(data)) {
data_copy = json_deep_copy(data);
} else {
data_copy = json_object();
}
json_object_set_new(root, "data", data_copy);
rc = send_json_line(api, root);
if (rc != MP_API_OK) {
return rc;
}
char *line = NULL;
rc = read_line(api->sockfd, &line);
if (rc != MP_API_OK) {
return rc;
}
json_error_t jerr;
json_t *resp = json_loads(line, 0, &jerr);
free(line);
if (!resp || !json_is_object(resp)) {
if (resp) json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
json_t *cmd_val = json_object_get(resp, "cmd");
if (!json_is_string(cmd_val) || strcmp(json_string_value(cmd_val), "host") != 0) {
json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
json_t *sess_val = json_object_get(resp, "session");
if (!json_is_string(sess_val)) {
json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
const char *session = json_string_value(sess_val);
json_t *cid_val = json_object_get(resp, "clientId");
const char *clientId = json_is_string(cid_val) ? json_string_value(cid_val) : NULL;
json_t *data_val = json_object_get(resp, "data");
json_t *data_obj = NULL;
if (json_is_object(data_val)) {
data_obj = data_val;
json_incref(data_obj);
}
api->session_id = strdup(session);
if (!api->session_id) {
if (data_obj) json_decref(data_obj);
json_decref(resp);
return MP_API_ERR_IO;
}
if (out_session) {
*out_session = strdup(session);
}
if (out_clientId && clientId) {
*out_clientId = strdup(clientId);
}
if (out_data) {
*out_data = data_obj;
} else if (data_obj) {
json_decref(data_obj);
}
json_decref(resp);
rc = start_recv_thread(api);
if (rc != MP_API_OK) {
return rc;
}
return MP_API_OK;
}
int mp_api_list(MultiplayerApi *api, json_t **out_list)
{
if (!api || !out_list) return MP_API_ERR_ARGUMENT;
int rc = ensure_connected(api);
if (rc != MP_API_OK) return rc;
json_t *root = json_object();
if (!root) return MP_API_ERR_IO;
json_object_set_new(root, "identifier", json_string(api->identifier));
json_object_set_new(root, "cmd", json_string("list"));
rc = send_json_line(api, root);
if (rc != MP_API_OK) {
return rc;
}
char *line = NULL;
rc = read_line(api->sockfd, &line);
if (rc != MP_API_OK) {
return rc;
}
printf("Received line: %s\n", line); // Debug print
json_error_t jerr;
json_t *resp = json_loads(line, 0, &jerr);
free(line);
if (!resp || !json_is_object(resp)) {
if (resp) json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
json_t *cmd_val = json_object_get(resp, "cmd");
if (!json_is_string(cmd_val) || strcmp(json_string_value(cmd_val), "list") != 0) {
json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
json_t *list_val = json_object_get(resp, "data");
if (!json_is_object(list_val)) {
json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
json_t *list_obj = json_object_get(list_val, "list");
if (!json_is_array(list_obj)) {
json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
*out_list = list_obj;
json_incref(*out_list);
json_decref(resp);
return MP_API_OK;
}
int mp_api_join(MultiplayerApi *api,
const char *sessionId,
json_t *data,
char **out_session,
char **out_clientId,
json_t **out_data) {
if (!api || !sessionId) return MP_API_ERR_ARGUMENT;
if (api->session_id) return MP_API_ERR_STATE;
int rc = ensure_connected(api);
if (rc != MP_API_OK) return rc;
json_t *root = json_object();
if (!root) return MP_API_ERR_IO;
json_object_set_new(root, "identifier", json_string(api->identifier));
json_object_set_new(root, "session", json_string(sessionId));
json_object_set_new(root, "cmd", json_string("join"));
json_t *data_copy;
if (data && json_is_object(data)) {
data_copy = json_deep_copy(data);
} else {
data_copy = json_object();
}
json_object_set_new(root, "data", data_copy);
rc = send_json_line(api, root);
if (rc != MP_API_OK) {
return rc;
}
char *line = NULL;
rc = read_line(api->sockfd, &line);
if (rc != MP_API_OK) {
return rc;
}
json_error_t jerr;
json_t *resp = json_loads(line, 0, &jerr);
free(line);
if (!resp || !json_is_object(resp)) {
if (resp) json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
json_t *cmd_val = json_object_get(resp, "cmd");
if (!json_is_string(cmd_val) || strcmp(json_string_value(cmd_val), "join") != 0) {
json_decref(resp);
return MP_API_ERR_PROTOCOL;
}
json_t *sess_val = json_object_get(resp, "session");
const char *session = NULL;
if (json_is_string(sess_val)) {
session = json_string_value(sess_val);
}
json_t *cid_val = json_object_get(resp, "clientId");
const char *clientId = json_is_string(cid_val) ? json_string_value(cid_val) : NULL;
json_t *data_val = json_object_get(resp, "data");
json_t *data_obj = NULL;
if (json_is_object(data_val)) {
data_obj = data_val;
json_incref(data_obj);
}
int joinAccepted = 1;
if (data_obj) {
json_t *status_val = json_object_get(data_obj, "status");
if (json_is_string(status_val) &&
strcmp(json_string_value(status_val), "error") == 0) {
joinAccepted = 0;
}
}
if (joinAccepted && session) {
api->session_id = strdup(session);
if (!api->session_id) {
if (data_obj) json_decref(data_obj);
json_decref(resp);
return MP_API_ERR_IO;
}
}
if (out_session && session) {
*out_session = strdup(session);
}
if (out_clientId && clientId) {
*out_clientId = strdup(clientId);
}
if (out_data) {
*out_data = data_obj;
} else if (data_obj) {
json_decref(data_obj);
}
json_decref(resp);
if (joinAccepted && api->session_id) {
rc = start_recv_thread(api);
if (rc != MP_API_OK) {
return rc;
}
}
return joinAccepted ? MP_API_OK : MP_API_ERR_REJECTED;
}
int mp_api_game(MultiplayerApi *api, json_t *data, const char* destination) {
if (!api || !data) return MP_API_ERR_ARGUMENT;
if (api->sockfd < 0 || !api->session_id) return MP_API_ERR_STATE;
json_t *root = json_object();
if (!root) return MP_API_ERR_IO;
json_object_set_new(root, "identifier", json_string(api->identifier));
json_object_set_new(root, "session", json_string(api->session_id));
json_object_set_new(root, "cmd", json_string("game"));
if(destination)
json_object_set_new(root, "destination", json_string(destination));
json_t *data_copy;
if (json_is_object(data)) {
data_copy = json_deep_copy(data);
} else {
data_copy = json_object();
}
json_object_set_new(root, "data", data_copy);
return send_json_line(api, root);
}
int mp_api_listen(MultiplayerApi *api,
MultiplayerListener cb,
void *context) {
if (!api || !cb) return -1;
ListenerNode *node = (ListenerNode *)malloc(sizeof(ListenerNode));
if (!node) return -1;
node->cb = cb;
node->context = context;
pthread_mutex_lock(&api->lock);
node->id = api->next_listener_id++;
node->next = api->listeners;
api->listeners = node;
pthread_mutex_unlock(&api->lock);
return node->id;
}
void mp_api_unlisten(MultiplayerApi *api, int listener_id) {
if (!api || listener_id <= 0) return;
pthread_mutex_lock(&api->lock);
ListenerNode *prev = NULL;
ListenerNode *cur = api->listeners;
while (cur) {
if (cur->id == listener_id) {
if (prev) {
prev->next = cur->next;
} else {
api->listeners = cur->next;
}
free(cur);
break;
}
prev = cur;
cur = cur->next;
}
pthread_mutex_unlock(&api->lock);
}
/* --- Interna hjälpfunktioner --- */
static int connect_to_server(const char *host, uint16_t port) {
if (!host) host = "127.0.0.1";
char port_str[16];
snprintf(port_str, sizeof(port_str), "%u", (unsigned int)port);
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* IPv4 eller IPv6 */
hints.ai_socktype = SOCK_STREAM;
struct addrinfo *res = NULL;
int err = getaddrinfo(host, port_str, &hints, &res);
if (err != 0) {
return -1;
}
int fd = -1;
for (struct addrinfo *rp = res; rp != NULL; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == -1) continue;
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
break;
}
close(fd);
fd = -1;
}
freeaddrinfo(res);
return fd;
}
static int ensure_connected(MultiplayerApi *api) {
if (!api) return MP_API_ERR_ARGUMENT;
if (api->sockfd >= 0) return MP_API_OK;
int fd = connect_to_server(api->server_host, api->server_port);
if (fd < 0) {
return MP_API_ERR_CONNECT;
}
api->sockfd = fd;
return MP_API_OK;
}
static int send_all(int fd, const char *buf, size_t len) {
size_t sent = 0;
while (sent < len) {
ssize_t n = send(fd, buf + sent, len - sent, 0);
if (n < 0) {
if (errno == EINTR) continue;
return -1;
}
if (n == 0) {
return -1;
}
sent += (size_t)n;
}
return 0;
}
static int send_json_line(MultiplayerApi *api, json_t *obj) {
if (!api || api->sockfd < 0 || !obj) return MP_API_ERR_ARGUMENT;
char *text = json_dumps(obj, JSON_COMPACT);
if (!text) {
json_decref(obj);
return MP_API_ERR_IO;
}
printf("Sending JSON: %s\n", text); // Debug print
size_t len = strlen(text);
int fd = api->sockfd;
int rc = 0;
if (send_all(fd, text, len) != 0 || send_all(fd, "\n", 1) != 0) {
rc = MP_API_ERR_IO;
}
free(text);
json_decref(obj);
return rc;
}
static int read_line(int fd, char **out_line) {
if (!out_line) return MP_API_ERR_ARGUMENT;
size_t cap = 256;
size_t len = 0;
char *buf = (char *)malloc(cap);
if (!buf) return MP_API_ERR_IO;
for (;;) {
char c;
ssize_t n = recv(fd, &c, 1, 0);
if (n < 0) {
if (errno == EINTR) continue;
free(buf);
return MP_API_ERR_IO;
}
if (n == 0) {
free(buf);
return MP_API_ERR_IO;
}
if (c == '\n') {
break;
}
if (len + 1 >= cap) {
cap *= 2;
char *tmp = (char *)realloc(buf, cap);
if (!tmp) {
free(buf);
return MP_API_ERR_IO;
}
buf = tmp;
}
buf[len++] = c;
}
buf[len] = '\0';
*out_line = buf;
return MP_API_OK;
}
static void process_line(MultiplayerApi *api, const char *line) {
if (!api || !line || !*line) return;
json_error_t jerr;
json_t *root = json_loads(line, 0, &jerr);
if (!root || !json_is_object(root)) {
if (root) json_decref(root);
return;
}
json_t *cmd_val = json_object_get(root, "cmd");
if (!json_is_string(cmd_val)) {
json_decref(root);
return;
}
const char *cmd = json_string_value(cmd_val);
if (!cmd) {
json_decref(root);
return;
}
if (strcmp(cmd, "joined") != 0 &&
strcmp(cmd, "leaved") != 0 &&
strcmp(cmd, "game") != 0) {
json_decref(root);
return;
}
json_int_t msgId = 0;
json_t *mid_val = json_object_get(root, "messageId");
if (json_is_integer(mid_val)) {
msgId = json_integer_value(mid_val);
}
const char *clientId = NULL;
json_t *cid_val = json_object_get(root, "clientId");
if (json_is_string(cid_val)) {
clientId = json_string_value(cid_val);
}
json_t *data_val = json_object_get(root, "data");
json_t *data_obj;
if (json_is_object(data_val)) {
data_obj = data_val;
json_incref(data_obj);
} else {
data_obj = json_object();
}
pthread_mutex_lock(&api->lock);
int count = 0;
ListenerNode *node = api->listeners;
while (node) {
if (node->cb) count++;
node = node->next;
}
if (count == 0) {
pthread_mutex_unlock(&api->lock);
json_decref(data_obj);
json_decref(root);
return;
}
ListenerSnapshot *snapshot = (ListenerSnapshot *)malloc(sizeof(ListenerSnapshot) * count);
if (!snapshot) {
pthread_mutex_unlock(&api->lock);
json_decref(data_obj);
json_decref(root);
return;
}
int idx = 0;
node = api->listeners;
while (node) {
if (node->cb) {
snapshot[idx].cb = node->cb;
snapshot[idx].context = node->context;
idx++;
}
node = node->next;
}
pthread_mutex_unlock(&api->lock);
for (int i = 0; i < count; ++i) {
snapshot[i].cb(cmd, (int64_t)msgId, clientId, data_obj, snapshot[i].context);
}
free(snapshot);
json_decref(data_obj);
json_decref(root);
}
static void *recv_thread_main(void *arg) {
MultiplayerApi *api = (MultiplayerApi *)arg;
char buffer[1024];
char *acc = NULL;
size_t acc_len = 0;
size_t acc_cap = 0;
while (1) {
ssize_t n = recv(api->sockfd, buffer, sizeof(buffer), 0);
if (n <= 0) {
break;
}
for (ssize_t i = 0; i < n; ++i) {
char ch = buffer[i];
if (ch == '\n') {
if (acc_len > 0) {
char *line = (char *)malloc(acc_len + 1);
if (!line) {
acc_len = 0;
continue;
}
memcpy(line, acc, acc_len);
line[acc_len] = '\0';
acc_len = 0;
process_line(api, line);
free(line);
} else {
acc_len = 0;
}
} else {
if (acc_len + 1 >= acc_cap) {
size_t new_cap = acc_cap == 0 ? 256 : acc_cap * 2;
char *tmp = (char *)realloc(acc, new_cap);
if (!tmp) {
free(acc);
acc = NULL;
acc_len = 0;
acc_cap = 0;
break;
}
acc = tmp;
acc_cap = new_cap;
}
acc[acc_len++] = ch;
}
}
}
if (acc) {
free(acc);
}
return NULL;
}
static int start_recv_thread(MultiplayerApi *api) {
if (!api) return MP_API_ERR_ARGUMENT;
if (api->recv_thread_started) {
return MP_API_OK;
}
api->running = 1;
int rc = pthread_create(&api->recv_thread, NULL, recv_thread_main, api);
if (rc != 0) {
api->running = 0;
return MP_API_ERR_IO;
}
api->recv_thread_started = 1;
return MP_API_OK;
}

View File

@@ -0,0 +1,81 @@
#ifndef MULTIPLAYER_API_H
#define MULTIPLAYER_API_H
#include <stdint.h>
#include "third_party/jansson/jansson.h"
typedef struct MultiplayerApi MultiplayerApi;
/* Callbacktyp för inkommande events från servern. */
typedef void (*MultiplayerListener)(
const char *event, /* "joined", "leaved", "game" */
int64_t messageId, /* sekventiellt meddelandeID (från host) */
const char *clientId, /* avsändarens klientID (eller NULL) */
json_t *data, /* JSONobjekt med godtycklig speldata */
void *context /* godtycklig pekare som skickas vidare */
);
/* Returkoder */
enum {
MP_API_OK = 0,
MP_API_ERR_ARGUMENT = 1,
MP_API_ERR_STATE = 2,
MP_API_ERR_CONNECT = 3,
MP_API_ERR_PROTOCOL = 4,
MP_API_ERR_IO = 5,
MP_API_ERR_REJECTED = 6 /* t.ex. ogiltigt sessionsID vid join */
};
/* Skapar en ny APIinstans. Returnerar NULL vid fel. */
MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port, const char *identifier);
/* Stänger ner anslutning, stoppar mottagartråd och frigör minne. */
void mp_api_destroy(MultiplayerApi *api);
/* Hostar en ny session. Blockerar tills svar erhållits eller fel uppstår.
out_session / out_clientId pekar på nyallokerade strängar (malloc) som
anroparen ansvarar för att free:a. out_data (om ej NULL) får ett json_t*
med extra data från servern (anroparen ska json_decref när klart). */
int mp_api_host(MultiplayerApi *api,
json_t *data,
char **out_session,
char **out_clientId,
json_t **out_data);
/*
Hämtar en lista över tillgängliga publika sessioner.
Returnerar MP_API_OK vid framgång, annan felkod vid fel.
Anroparen ansvarar för att json_decref:a out_list när klar. */
int mp_api_list(MultiplayerApi *api,
json_t **out_list);
/* Går med i befintlig session.
sessionId: sessionskod (t.ex. "ABC123").
data: valfri JSONpayload med spelarinformation (kan vara NULL).
out_* fungerar som i mp_api_host.
Returnerar:
- MP_API_OK vid lyckad join
- MP_API_ERR_REJECTED om servern svarar med status:error (t.ex. ogiltigt ID)
- annan felkod vid nätverks/protokollfel.
*/
int mp_api_join(MultiplayerApi *api,
const char *sessionId,
json_t *data,
char **out_session,
char **out_clientId,
json_t **out_data);
/* Skickar ett "game"meddelande med godtycklig JSONdata till sessionen. */
int mp_api_game(MultiplayerApi *api, json_t *data, const char* destination);
/* Registrerar en lyssnare för inkommande events.
Returnerar ett positivt listenerID, eller 1 vid fel. */
int mp_api_listen(MultiplayerApi *api,
MultiplayerListener cb,
void *context);
/* Avregistrerar lyssnare. ListenerID är värdet från mp_api_listen. */
void mp_api_unlisten(MultiplayerApi *api, int listener_id);
#endif /* MULTIPLAYER_API_H */

View File

@@ -0,0 +1,257 @@
#include "networking.h"
#include <assert.h>
#define ONVO_IDENTIFIER "c2438167-831b-4bf7-8bdc-abcdefabcd00"
static void listen_callback(
const char *event, /* "joined", "leaved", "game" */
int64_t messsage_id, /* sekventiellt meddelandeID (från host) */
const char *client_id, /* avsändarens klientID (eller NULL) */
json_t *data, /* JSONobjekt med godtycklig speldata */
void *context /* godtycklig pekare som skickas vidare */
) {
Game_Networking *networking = (Game_Networking *)context;
assert(networking != NULL);
assert(networking->game_session != NULL);
(void)data;
printf("#%li :: Event: '%s' from '%s'.\n", messsage_id, event, client_id);
assert(networking != NULL);
if(strcmp(event, "joined") == 0) {
// Create a new player in the session, get the id and store it as the value in the
// 'client_id_to_player_index' hashmap using 'client_id' as the key.
uint32_t *existing_id = map_get(&networking->client_id_to_player_index, client_id);
assert(existing_id == NULL); // Something has gone terribly wrong if we get a 'joined' event for the same client-id with them still being in the map.
uint16_t player_index = 0xFFFF;
assert(game_session_create_player(networking->game_session, &player_index));
assert(map_set(&networking->client_id_to_player_index, client_id, player_index) == 0); // 0 = OK.
// TODO: SS - Send a 'game' message to the joinee with information about the session?
printf("Added client-id '%s' (player-index: %u) to the map.\n", client_id, player_index);
}
else if(strcmp(event, "leaved") == 0) {
// TODO: SS - Index the hashmap using 'client_id' as the key and attempt to get the
// player-index for that client. Run 'game_session_destroy_player()'.
uint32_t *existing_id = map_get(&networking->client_id_to_player_index, client_id);
assert(existing_id != NULL); // Something has gone terribly wrong if we get a 'leaved' event for the a client-id that isn't in the map.
uint16_t player_id_to_remove = *existing_id;
game_session_destroy_player(networking->game_session, player_id_to_remove); // Returns void but crashes internally if id isn't OK.
map_remove(&networking->client_id_to_player_index, client_id);
printf("Removed client-id '%s' (player-index: %u) from the map.\n", client_id, player_id_to_remove);
}
else if(strcmp(event, "game") == 0) {
// TODO: SS - Check the 'data' to see what type of game-event it is.
// Possible game-events I can think of right now:
// - Player-input.
// - Match-state changed.
// - Chat-message?
// - Session-state?
// - Maybe Player-Joined/Left. (seems like mp_api only sends 'joined'/'leaved' events to the host.)
}
else {
printf("UNKNOWN EVENT-TYPE: '%s'.\n", event);
}
}
static inline bool setup_api(Game_Networking *networking) {
assert(networking != NULL);
if(networking->api != NULL) {
printf("Failed to set up API. Already active.\n");
return false;
}
MultiplayerApi *mp_api = mp_api_create("kontoret.onvo.se", 9001, ONVO_IDENTIFIER);
if(mp_api == NULL) {
printf("Failed to set up API. Could not initialize.\n");
return false;
}
networking->api = mp_api;
return true;
}
static bool start_listening(Game_Networking *networking) {
assert(networking != NULL);
assert(networking->api != NULL);
int listener_id = mp_api_listen(networking->api, listen_callback, (void *)networking); // TODO: SS - Change context.
if(listener_id < 0) {
return false;
}
networking->listener_id = (uint32_t)listener_id;
return true;
}
static bool stop_listening(Game_Networking *networking) {
assert(networking != NULL);
assert(networking->api != NULL);
mp_api_unlisten(networking->api, (int)networking->listener_id);
return true;
}
bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings, uint16_t *out_host_player_id) {
assert(networking != NULL);
assert(session != NULL);
if(!settings.online) {
printf("Failed to host; Game_Session_Settings.online == false, expected it to be true.\n");
return false;
}
if(!setup_api(networking)) {
printf("Failed to host; API is already set up.\n");
return false;
}
char *session_id = NULL;
char *local_client_id = NULL;
json_t *response_data = NULL;
int host_result = mp_api_host(
networking->api,
NULL, // TODO: SS - Send data to server that contains the game-session's settings.
&session_id,
&local_client_id,
&response_data
);
if(host_result != MP_API_OK) {
printf("Failed to host; Got result: %i.\n", host_result);
// TODO: SS - Shutdown API.
return false;
}
printf("Started hosting session '%s', local_client_id is '%s'.\n", session_id, local_client_id);
networking->is_host = true;
networking->game_session = session;
networking->session_id = session_id;
networking->local_client_id = local_client_id;
map_init(&networking->client_id_to_player_index);
assert(start_listening(networking));
// Set up session.
game_session_init(
session,
settings
);
// Add the host to the game.
uint16_t host_player_index = 0xFFFF;
assert(game_session_create_player(networking->game_session, &host_player_index));
map_set(&networking->client_id_to_player_index, local_client_id, host_player_index);
*out_host_player_id = host_player_index;
printf("Host-player created (client-id: '%s'). Player-id: %u.\n", local_client_id, host_player_index);
if(response_data != NULL) {
// TODO: SS - Handle JSON-response.
json_decref(response_data);
}
return true;
}
bool networking_try_join(Game_Networking *networking, Game_Session *session, const char *session_id) {
assert(networking != NULL);
assert(session != NULL);
if(strlen(session_id) == 0) {
return false;
}
if(!setup_api(networking)) {
printf("Failed to join; API is already set up.\n");
return false;
}
printf("Trying to join session using code: '%s' ...\n", session_id);
char *result_session_id;
char *local_client_id;
json_t *received_json;
int join_result = mp_api_join(
networking->api, // API.
session_id, // Session-code.
NULL, // JSON-data to send. // TODO: SS - Send information about you as a player (username, ..).
&result_session_id, // Received Session-id. I assume it should be the same as 'session_id'. // TODO: SS - Ask Robin why mp_api_join() returns a session_id. Is it to support 'abcdef' => 'ABCDEF' and/or proxies?
&local_client_id, // Received client-id.
&received_json // Received JSON-data.
);
if(join_result != MP_API_OK) {
// TODO: SS - Shutdown API.
printf("Failed to join; Got result: %i.\n", join_result);
return false;
}
assert(strcmp(result_session_id, session_id) == 0);
printf("Joined session '%s', local_client_id is '%s'.\n", result_session_id, local_client_id);
networking->is_host = true;
networking->game_session = session;
networking->session_id = result_session_id;
networking->local_client_id = local_client_id;
map_init(&networking->client_id_to_player_index);
// TODO: SS - Decide how to network this..
// Should we just receive the session's settings as well as a long list of commands of what has happened throughout the match (so far) - at least one per tick?
// Or, should this client receive the current state of the session?
if(received_json != NULL) {
// TODO: SS - Read this json because it might contain valuable information.
json_decref(received_json);
}
// TODO: SS - Stop listening.
assert(start_listening(networking));
// TODO: SS - Set up session based on what we got in the json.
// game_session_init(
// session,
// settings
// );
return true;
}
bool networking_stop_hosting(Game_Networking *networking) {
assert(networking != NULL);
if(networking->api == NULL) {
printf("Failed to stop hosting; API is not set up.\n");
return false;
}
map_deinit(&networking->client_id_to_player_index);
assert(stop_listening(networking));
mp_api_destroy(networking->api);
networking->api = NULL;
networking->game_session = NULL;
networking->is_host = false;
if(networking->session_id != NULL) {
free(networking->session_id);
networking->session_id = NULL;
}
if(networking->local_client_id != NULL) {
free(networking->local_client_id);
networking->local_client_id = NULL;
}
printf("Stopped hosting.\n");
return true;
}

View File

@@ -0,0 +1,28 @@
#ifndef NETWORKING_H
#define NETWORKING_H
#include "multiplayer_api.h"
#include "game_session.h"
#include "shared/map.h"
typedef struct {
MultiplayerApi *api;
bool is_host;
char *session_id;
char *local_client_id;
uint32_t listener_id;
Game_Session *game_session; // Comes from Game_Instance.
map_uint_t client_id_to_player_index;
} Game_Networking;
bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings, uint16_t *out_host_player_id);
bool networking_try_join(Game_Networking *networking, Game_Session *session, const char *session_id);
bool networking_stop_hosting(Game_Networking *networking);
#endif