This commit is contained in:
2025-06-08 14:38:22 -05:00
parent 8411c2981b
commit 0b6b33721b
69 changed files with 210 additions and 3384 deletions

View File

@ -4,11 +4,3 @@
# https://opensource.org/licenses/MIT
include(FetchContent)
# RayLib
FetchContent_Declare(
raylib
URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.tar.gz
URL_HASH MD5=61638c4c2c097fbca1d6a71e4da36c16
)
FetchContent_MakeAvailable(raylib)

View File

@ -7,7 +7,6 @@
target_link_libraries(${DUSK_TARGET_NAME}
PUBLIC
m
raylib
)
# Includes
@ -20,14 +19,10 @@ target_include_directories(${DUSK_TARGET_NAME}
target_sources(${DUSK_TARGET_NAME}
PRIVATE
main.c
input.c
)
# Subdirs
add_subdirectory(assert)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(error)
add_subdirectory(game)
add_subdirectory(network)
add_subdirectory(rpg)
add_subdirectory(util)

View File

@ -7,12 +7,6 @@
#include "assert.h"
pthread_t assertMainThread = 0;
void assertInit() {
assertMainThread = pthread_self();
}
void assertTrueImpl(
const char *file,
const int32_t line,

View File

@ -8,13 +8,6 @@
#pragma once
#include "dusk.h"
extern pthread_t assertMainThread;
/**
* Initialises the assert system.
*/
void assertInit();
/**
* Assert a given value to be true.
*
@ -135,10 +128,4 @@ void assertMemoryRangeMatchesImpl(
#define assertStrLenMin(str, len, message) \
assertTrue(strlen(str) >= len, message)
#define assertIsMainThread(message) \
assertTrue(pthread_self() == assertMainThread, message)
#define assertNotMainThread(message) \
assertFalse(pthread_self() == assertMainThread, message)
// EOF

View File

@ -1,12 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
console.c
consolecmd.c
consolevar.c
)

View File

@ -1,395 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "console.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "input.h"
console_t CONSOLE;
void consoleInit() {
memoryZero(&CONSOLE, sizeof(console_t));
pthread_mutex_init(&CONSOLE.lock, NULL); // Initialize the mutex
// Register the get and set command.
CONSOLE.cmdGet = consoleRegCmd("get", cmdGet);
CONSOLE.cmdSet = consoleRegCmd("set", cmdSet);
consoleRegCmd("echo", cmdEcho);
consolePrint(" = Dawn Console = ");
}
consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function) {
pthread_mutex_lock(&CONSOLE.lock); // Lock
consolecmd_t *cmd = &CONSOLE.commands[CONSOLE.commandCount++];
consoleCmdInit(cmd, name, function);
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
return cmd;
}
consolevar_t * consoleRegVar(
const char_t *name,
const char_t *value,
consolevarchanged_t event
) {
pthread_mutex_lock(&CONSOLE.lock); // Lock
consolevar_t *var = &CONSOLE.variables[CONSOLE.variableCount++];
consoleVarInitListener(var, name, value, event);
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
return var;
}
void consolePrint(const char_t *message, ...) {
char_t buffer[CONSOLE_LINE_MAX];
va_list args;
va_start(args, message);
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
va_end(args);
// Move all lines back
memoryMove(
CONSOLE.line[0],
CONSOLE.line[1],
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
);
// Copy the new line
memoryCopy(
CONSOLE.line[CONSOLE_HISTORY_MAX - 1],
buffer,
len + 1
);
printf("%s\n", buffer);
}
void consoleExec(const char_t *line) {
pthread_mutex_lock(&CONSOLE.lock); // Lock
assertNotNull(line, "line must not be NULL");
assertTrue(
CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX,
"Too many commands in the buffer."
);
char_t buffer[CONSOLE_LINE_MAX];
size_t i = 0, j = 0;
char_t c;
consoleexecstate_t state = CONSOLE_EXEC_STATE_INITIAL;
consolecmdexec_t *exec = NULL;
while(state != CONSOLE_EXEC_STATE_FULLY_PARSED) {
c = line[i];
switch(state) {
case CONSOLE_EXEC_STATE_INITIAL:
assertTrue(j == 0, "Buffer not empty?");
if(c == '\0') {
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
break;
}
if(stringIsWhitespace(c) || c == ';') {
i++;
continue;
}
state = CONSOLE_EXEC_STATE_PARSE_CMD;
break;
case CONSOLE_EXEC_STATE_PARSE_CMD:
if(stringIsWhitespace(c) || c == '\0' || c == ';') {
state = CONSOLE_EXEC_STATE_CMD_PARSED;
continue;
}
if(c == '"') {
// Can't handle quotes within the command.
consolePrint("Invalid command");
while(c != '\0' && c != ';') c = line[++i];
continue;
}
buffer[j++] = c;
i++;
if(j >= CONSOLE_LINE_MAX) {
consolePrint("Command too long");
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
continue;
}
break;
case CONSOLE_EXEC_STATE_CMD_PARSED:
if(j == 0) {
state = CONSOLE_EXEC_STATE_INITIAL;
continue;
}
// Create exec
assertNull(exec, "Existing command parsing?");
exec = &CONSOLE.execBuffer[CONSOLE.execBufferCount];
memoryZero(exec, sizeof(consolecmdexec_t));
buffer[j] = '\0';
stringCopy(exec->command, buffer, CONSOLE_LINE_MAX);
state = CONSOLE_EXEC_STATE_FIND_ARG;
j = 0;// Free up buffer
break;
case CONSOLE_EXEC_STATE_FIND_ARG:
if(c == '\0' || c == ';') {
state = CONSOLE_EXEC_STATE_CMD_FINISHED;
continue;
}
if(stringIsWhitespace(c)) {
i++;
continue;
}
if(c == '"') {
state = CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED;
i++;
} else {
state = CONSOLE_EXEC_STATE_PARSE_ARG;
}
break;
case CONSOLE_EXEC_STATE_PARSE_ARG:
if(stringIsWhitespace(c) || c == '\0' || c == ';') {
state = CONSOLE_EXEC_STATE_ARG_PARSED;
continue;
}
buffer[j++] = c;
i++;
if(j >= CONSOLE_LINE_MAX) {
consolePrint("Arg too long");
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
continue;
}
break;
case CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED:
if(c == '"') {
state = CONSOLE_EXEC_STATE_ARG_PARSED;
i++;
continue;
}
if(c == '\0' || c == ';') {
consolePrint("Unterminated quote");
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
continue;
}
if(c == '\\') {
c = line[++i];
if(c == '\0' || c == ';') {
consolePrint("Unterminated quote");
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
continue;
}
}
buffer[j++] = c;
i++;
if(j >= CONSOLE_LINE_MAX) {
consolePrint("Arg too long");
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
continue;
}
break;
case CONSOLE_EXEC_STATE_ARG_PARSED:
buffer[j] = '\0';
stringCopy(exec->argv[exec->argc++], buffer, CONSOLE_LINE_MAX);
state = CONSOLE_EXEC_STATE_FIND_ARG;
j = 0;// Free up buffer
break;
case CONSOLE_EXEC_STATE_CMD_FINISHED:
assertNotNull(exec, "No command found?");
// Now, is there a command that matches?
for(uint32_t k = 0; k < CONSOLE.commandCount; k++) {
consolecmd_t *cmd = &CONSOLE.commands[k];
if(stringCompare(cmd->name, exec->command) != 0) continue;
exec->cmd = cmd;
break;
}
if(exec->cmd == NULL) {
// Command wasn't found, is there a variable that matches?
for(uint32_t k = 0; k < CONSOLE.variableCount; k++) {
consolevar_t *var = &CONSOLE.variables[k];
if(stringCompare(var->name, exec->command) != 0) continue;
// Matching variable found, is this a GET or a SET?
if(exec->argc == 0) {
exec->cmd = CONSOLE.cmdGet;
stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX);
exec->argc = 1;
} else {
exec->cmd = CONSOLE.cmdSet;
stringCopy(exec->argv[1], exec->argv[0], CONSOLE_LINE_MAX);
stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX);
exec->argc = 2;
}
break;
}
if(exec->cmd == NULL) {
consolePrint("Command not found", exec->command);
exec = NULL;
state = CONSOLE_EXEC_STATE_INITIAL;
break;
}
}
// Prep for next command.
exec = NULL;
state = CONSOLE_EXEC_STATE_INITIAL;
CONSOLE.execBufferCount++;
break;
default:
assertUnreachable("Invalid state.");
break;
}
}
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
}
void consoleProcess() {
pthread_mutex_lock(&CONSOLE.lock); // Lock
for(uint32_t i = 0; i < CONSOLE.execBufferCount; i++) {
consolecmdexec_t *exec = &CONSOLE.execBuffer[i];
assertNotNull(exec->cmd, "Command execution has no command.");
exec->cmd->function(exec);
}
// Clear the exec buffer
CONSOLE.execBufferCount = 0;
pthread_mutex_unlock(&CONSOLE.lock); // Unlock
}
void cmdGet(const consolecmdexec_t *exec) {
assertTrue(
exec->argc >= 1,
"Get command requires 1 argument."
);
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
consolevar_t *var = &CONSOLE.variables[i];
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
consolePrint("%s", var->value);
return;
}
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
}
void cmdSet(const consolecmdexec_t *exec) {
assertTrue(exec->argc >= 2, "set command requires 2 arguments.");
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
consolevar_t *var = &CONSOLE.variables[i];
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
consoleVarSetValue(var, exec->argv[1]);
consolePrint("%s %s", var->name, var->value);
for(i = 0; i < var->eventCount; i++) {
assertNotNull(var->events[i], "Event is NULL");
var->events[i](var);
}
return;
}
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
}
void cmdEcho(const consolecmdexec_t *exec) {
assertTrue(
exec->argc >= 1,
"echo command requires 1 argument."
);
consolePrint("%s", exec->argv[0]);
}
// May move these later
void consoleUpdate() {
if(inputIsPressed(INPUT_TOGGLE_CONSOLE)) {
CONSOLE.open = !CONSOLE.open;
} else if(CONSOLE.open) {
switch(INPUT.keyPressed) {
case 0:
break;
case KEY_ENTER:
consoleExec(CONSOLE.inputBuffer);
CONSOLE.inputIndex = 0;
CONSOLE.inputBuffer[0] = '\0';
break;
case KEY_BACKSPACE:
if(CONSOLE.inputIndex > 0) {
CONSOLE.inputIndex--;
CONSOLE.inputBuffer[CONSOLE.inputIndex] = '\0';
}
break;
default:
if(
INPUT.keyPressed >= 32 &&
INPUT.keyPressed <= 126 &&
CONSOLE.inputIndex < CONSOLE_LINE_MAX - 1
) {
CONSOLE.inputBuffer[CONSOLE.inputIndex++] = INPUT.charPressed;
CONSOLE.inputBuffer[CONSOLE.inputIndex] = '\0';
}
break;
}
}
consoleProcess();
}
void consoleDraw() {
if(!CONSOLE.open) return;
size_t i = 0;
char_t *line;
int32_t fontSize = 10;
do {
line = CONSOLE.line[i];
if(line[0] == '\0') {
i++;
continue;
}
DrawText(line, 0, i*fontSize, fontSize, YELLOW);
i++;
} while(i < CONSOLE_HISTORY_MAX);
DrawText(
CONSOLE.inputBuffer, 0,
(CONSOLE_HISTORY_MAX + 1) * fontSize,
fontSize,
PINK
);
}

View File

@ -1,114 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "consolevar.h"
#include "consolecmd.h"
typedef enum {
CONSOLE_EXEC_STATE_INITIAL,
CONSOLE_EXEC_STATE_PARSE_CMD,
CONSOLE_EXEC_STATE_CMD_PARSED,
CONSOLE_EXEC_STATE_FIND_ARG,
CONSOLE_EXEC_STATE_PARSE_ARG,
CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED,
CONSOLE_EXEC_STATE_ARG_PARSED,
CONSOLE_EXEC_STATE_CMD_FINISHED,
CONSOLE_EXEC_STATE_FULLY_PARSED
} consoleexecstate_t;
typedef struct {
consolecmd_t commands[CONSOLE_COMMANDS_MAX];
uint32_t commandCount;
consolevar_t variables[CONSOLE_VARIABLES_MAX];
uint32_t variableCount;
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
consolecmdexec_t execBuffer[CONSOLE_EXEC_BUFFER_MAX];
uint32_t execBufferCount;
consolecmd_t *cmdGet;
consolecmd_t *cmdSet;
pthread_mutex_t lock; // Mutex for thread safety
// May move these later
char_t inputBuffer[CONSOLE_LINE_MAX];
int32_t inputIndex;
bool_t open;
} console_t;
extern console_t CONSOLE;
/**
* Initializes the console.
*/
void consoleInit();
/**
* Registers a console command.
*
* @param name The name of the command.
* @param function The function to execute when the command is called.
* @return The registered command.
*/
consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function);
/**
* Registers a console variable.
*
* @param name The name of the variable.
* @param value The initial value of the variable.
* @param event The event to register.
* @return The registered variable.
*/
consolevar_t * consoleRegVar(
const char_t *name,
const char_t *value,
consolevarchanged_t event
);
/**
* Sets the value of a console variable.
*
* @param name The name of the variable.
* @param value The new value of the variable.
*/
void consolePrint(
const char_t *message,
...
);
/**
* Executes a console command.
*
* @param line The line to execute.
*/
void consoleExec(const char_t *line);
/**
* Processes the console's pending commands.
*/
void consoleProcess();
/**
* Updates the console's input buffer and handles user input.
*/
void consoleUpdate();
/**
* Draws the console's output.
*/
void consoleDraw();
void cmdGet(const consolecmdexec_t *exec);
void cmdSet(const consolecmdexec_t *exec);
void cmdEcho(const consolecmdexec_t *exec);

View File

@ -1,27 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "consolecmd.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
void consoleCmdInit(
consolecmd_t *cmd,
const char_t *name,
consolecmdfunc_t function
) {
assertNotNull(cmd, "Command is NULL.");
assertNotNull(name, "Name is NULL.");
assertNotNull(function, "Function is NULL.");
assertStrLenMin(name, 1, "Name is empty.");
assertStrLenMax(name, CONSOLE_CMD_NAME_MAX, "Name is too long.");
memoryZero(cmd, sizeof(consolecmd_t));
stringCopy(cmd->name, name, CONSOLE_CMD_NAME_MAX);
cmd->function = function;
}

View File

@ -1,39 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "consoledefs.h"
typedef struct consolecmd_s consolecmd_t;
typedef struct {
consolecmd_t *cmd;
char_t command[CONSOLE_LINE_MAX];
char_t argv[CONSOLE_CMD_ARGC_MAX][CONSOLE_LINE_MAX];
uint32_t argc;
} consolecmdexec_t;
typedef void (*consolecmdfunc_t)(const consolecmdexec_t *exec);
typedef struct consolecmd_s {
char_t name[CONSOLE_CMD_NAME_MAX];
consolecmdfunc_t function;
} consolecmd_t;
/**
* Initializes a console command.
*
* @param cmd Pointer to the console command.
* @param name The name of the command.
* @param function The function to execute when the command is called.
*/
void consoleCmdInit(
consolecmd_t *cmd,
const char_t *name,
consolecmdfunc_t function
);

View File

@ -1,21 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#define CONSOLE_CMD_NAME_MAX 32
#define CONSOLE_CMD_ARGC_MAX 16
#define CONSOLE_COMMANDS_MAX 128
#define CONSOLE_VARIABLES_MAX 128
#define CONSOLE_LINE_MAX 256
#define CONSOLE_HISTORY_MAX 32
#define CONSOLE_EXEC_BUFFER_MAX 16
#define CONSOLE_VAR_NAME_MAX 32
#define CONSOLE_VAR_VALUE_MAX 128
#define CONSOLE_VAR_EVENTS_MAX 8

View File

@ -1,64 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "consolevar.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
void consoleVarInit(
consolevar_t *var,
const char_t *name,
const char_t *value
) {
assertNotNull(var, "var must not be NULL");
assertNotNull(name, "name must not be NULL");
assertNotNull(value, "value must not be NULL");
assertStrLenMin(name, 1, "name must not be empty");
assertStrLenMax(name, CONSOLE_VAR_NAME_MAX, "name is too long");
assertStrLenMax(value, CONSOLE_VAR_VALUE_MAX, "value is too long");
memoryZero(var, sizeof(consolevar_t));
stringCopy(var->name, name, CONSOLE_VAR_NAME_MAX);
stringCopy(var->value, value, CONSOLE_VAR_VALUE_MAX);
}
void consoleVarInitListener(
consolevar_t *var,
const char_t *name,
const char_t *value,
consolevarchanged_t event
) {
consoleVarInit(var, name, value);
if(event) consoleVarListen(var, event);
}
void consoleVarSetValue(consolevar_t *var, const char_t *value) {
assertNotNull(var, "var must not be NULL");
assertNotNull(value, "value must not be NULL");
assertStrLenMax(value, CONSOLE_VAR_VALUE_MAX, "value is too long");
stringCopy(var->value, value, CONSOLE_VAR_VALUE_MAX);
uint8_t i = 0;
while (i < var->eventCount) {
var->events[i](var);
i++;
}
}
void consoleVarListen(consolevar_t *var, consolevarchanged_t event) {
assertNotNull(var, "var must not be NULL");
assertNotNull(event, "event must not be NULL");
assertTrue(
var->eventCount < CONSOLE_VAR_EVENTS_MAX,
"Event count is too high"
);
var->events[var->eventCount++] = event;
}

View File

@ -1,65 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "consoledefs.h"
typedef struct consolevar_s consolevar_t;
typedef void (*consolevarchanged_t)(const consolevar_t *var);
typedef struct consolevar_s {
char_t name[CONSOLE_VAR_NAME_MAX];
char_t value[CONSOLE_VAR_VALUE_MAX];
consolevarchanged_t events[CONSOLE_VAR_EVENTS_MAX];
uint8_t eventCount;
} consolevar_t;
/**
* Initializes a console variable.
*
* @param var Pointer to the console variable.
* @param name The name of the variable.
* @param value The initial value of the variable.
*/
void consoleVarInit(
consolevar_t *var,
const char_t *name,
const char_t *value
);
/**
* Initializes a console variable with a listener.
*
* @param var Pointer to the console variable.
* @param name The name of the variable.
* @param value The initial value of the variable.
* @param event The event to register.
*/
void consoleVarInitListener(
consolevar_t *var,
const char_t *name,
const char_t *value,
consolevarchanged_t event
);
/**
* Sets the value of a console variable.
*
* @param var Pointer to the console variable.
* @param value The new value of the variable.
*/
void consoleVarSetValue(consolevar_t *var, const char_t *value);
/**
* Registers an event to be called when the value of a console variable changes.
*
* @param var Pointer to the console variable.
* @param event The event to register.
*/
void consoleVarListen(consolevar_t *var, consolevarchanged_t event);

View File

@ -8,5 +8,3 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE
render.c
)
# Subdirs

View File

@ -6,31 +6,7 @@
*/
#include "render.h"
#include "game/game.h"
#include "assert/assert.h"
#include "console/console.h"
void renderInit() {
// Initialize the rendering system
InitWindow(800, 600, "Dusk");
}
renderresult_t renderDraw() {
BeginDrawing();
ClearBackground(BLACK);
for(uint32_t i = 0; i < GAME.entityCount; i++) {
entityRender(&GAME.entities[i]);
}
consoleDraw();
EndDrawing();
if(WindowShouldClose()) return RENDER_EXIT;
return RENDER_OK;
}
void renderDispose() {
CloseWindow();
}

View File

@ -6,33 +6,8 @@
*/
#pragma once
#include "dusk.h"
#define RENDER_FONT_SIZE 20
typedef enum {
RENDER_OK,
RENDER_EXIT,
RENDER_ERROR
} renderresult_t;
/**
* Initializes the rendering system.
*
* @return The result of the initialization.
* Init the render system.
*/
void renderInit();
/**
* Initializes the rendering system.
*
* @return The result of the initialization.
*/
renderresult_t renderDraw();
/**
* Renders the game.
*
* @return The result of the render operation.
*/
void renderDispose();

View File

@ -16,10 +16,8 @@
#include <stdarg.h>
#include <ctype.h>
#include <pthread.h>
#include <raylib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
typedef bool bool_t;
typedef char char_t;

View File

@ -1,56 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "error.h"
errorstack_t ERROR_STACK;
errorret_t error(const char_t *message, ...) {
return errorCode(1, message);
}
errorret_t errorCode(const errorret_t code, const char_t *message, ...) {
if(ERROR_STACK.code != ERROR_OK) {
snprintf(
ERROR_STACK.message,
ERROR_STACK_SIZE,
"Multiple errors encountered."
);
errorPrint();
return code;
}
va_list args;
va_start(args, message);
vsnprintf(ERROR_STACK.message, ERROR_STACK_SIZE, message, args);
va_end(args);
return ERROR_STACK.code = code;
}
bool_t errorCheck() {
return ERROR_STACK.code != ERROR_OK;
}
const char_t * errorString() {
if(!errorCheck()) return NULL;
return ERROR_STACK.message;
}
void errorFlush() {
if(!errorCheck()) return;
ERROR_STACK.code = ERROR_OK;
ERROR_STACK.message[0] = '\0';
}
errorret_t errorPrint() {
if(!errorCheck()) return ERROR_OK;
printf("Error: %s\n", errorString());
errorret_t code = ERROR_STACK.code;
errorFlush();
return code;
}

View File

@ -1,60 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef int32_t errorret_t;
#define ERROR_OK 0
#define ERROR_STACK_SIZE 256
typedef struct {
char_t message[ERROR_STACK_SIZE + 1];
errorret_t code;
} errorstack_t;
extern errorstack_t ERROR_STACK;
/**
* Pushes an error message to the error stack.
*
* @param message Message to push to the error stack.
* @param ... Arguments to format the message with.
*/
errorret_t error(const char_t *message, ...);
/**
* Pushes an error message to the error stack with a given error code.
*
* @param code Error code to push to the error stack.
* @param message Message to push to the error stack.
* @param ... Arguments to format the message with.
*/
errorret_t errorCode(const errorret_t code, const char_t *message, ...);
/**
* Checks if an error has been pushed to the error stack.
*
* @return True if an error has been pushed to the error stack.
*/
bool_t errorCheck();
/**
* Retrieves the error message from the error stack.
*/
const char_t * errorString();
/**
* Clears the error stack.
*/
void errorFlush();
/**
* Prints the error stack to the console. This also clears the error stack.
*/
errorret_t errorPrint();

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
typedef enum {
DIRECTION_SOUTH = 0,
DIRECTION_NORTH = 1,
DIRECTION_EAST = 2,
DIRECTION_WEST = 3,
DIRECTION_DOWN = 0,
DIRECTION_UP = 1,
DIRECTION_RIGHT = 2,
DIRECTION_LEFT = 3,
} direction_t;

View File

@ -1,78 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "game/game.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "display/render.h"
#include "input.h"
void entityInit(entity_t *entity, const entitytype_t type) {
memoryZero(entity, sizeof(entity_t));
entity->type = type;
switch(type) {
case ENTITY_TYPE_PLAYER:
break;
default:
assertUnreachable("Unknown entity type.");
break;
}
}
void entityTest(entity_t *ent) {
if(inputIsPressed(INPUT_DOWN)) {
ent->direction = DIRECTION_DOWN;
if(ent->y < GAME.map.height - 1) ent->y++;
} else if(inputIsPressed(INPUT_UP)) {
ent->direction = DIRECTION_UP;
if(ent->y > 0) ent->y--;
} else if(inputIsPressed(INPUT_LEFT)) {
ent->direction = DIRECTION_LEFT;
if(ent->x > 0) ent->x--;
} else if(inputIsPressed(INPUT_RIGHT)) {
ent->direction = DIRECTION_RIGHT;
if(ent->x < GAME.map.width - 1) ent->x++;
}
}
void entityRender(const entity_t *ent) {
// Draw the entity
int32_t fontSize = 20;
char_t str[1];
switch(ent->direction) {
case DIRECTION_NORTH:
str[0] = '^';
break;
case DIRECTION_EAST:
str[0] = '>';
break;
case DIRECTION_SOUTH:
str[0] = 'v';
break;
case DIRECTION_WEST:
str[0] = '<';
break;
default:
assertUnreachable("Invalid entity direction.");
break;
}
DrawTextEx(
GetFontDefault(),
str,
(struct Vector2){
ent->x * RENDER_FONT_SIZE,
ent->y * RENDER_FONT_SIZE
},
RENDER_FONT_SIZE,
(float)(RENDER_FONT_SIZE / 10),
WHITE
);
}

View File

@ -1,39 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "game/direction.h"
typedef enum {
ENTITY_TYPE_NULL = 0,
ENTITY_TYPE_PLAYER,
} entitytype_t;
typedef struct {
entitytype_t type;
uint8_t x, y;
direction_t direction;
} entity_t;
/**
* Initializes a given entity.
*
* @param entity The entity to initialize.
* @param type The type of the entity.
*/
void entityInit(entity_t *entity, const entitytype_t type);
void entityTest(entity_t *entity);
/**
* Renders a given entity.
*
* @param entity The entity to render.
*/
void entityRender(const entity_t *entity);

View File

@ -1,18 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "game.h"
#include "util/memory.h"
game_t GAME;
void gameInit(void) {
memoryZero(&GAME, sizeof(game_t));
GAME.map.width = 10;
GAME.map.height = 10;
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "game/entity/entity.h"
#include "game/map/map.h"
#define GAME_ENTITIES_MAX 256
typedef struct {
entity_t entities[GAME_ENTITIES_MAX];
uint32_t entityCount;
map_t map;
} game_t;
extern game_t GAME;
/**
* Initializes the game.
*/
void gameInit(void);

View File

@ -9,20 +9,6 @@
#include "util/memory.h"
#include "assert/assert.h"
inputbindmap_t INPUT_BIND_MAPS[] = {
{ KEY_W, INPUT_UP },
{ KEY_S, INPUT_DOWN },
{ KEY_A, INPUT_LEFT },
{ KEY_D, INPUT_RIGHT },
{ KEY_ENTER, INPUT_ACCEPT },
{ KEY_ESCAPE, INPUT_CANCEL },
{ KEY_UP, INPUT_UP },
{ KEY_DOWN, INPUT_DOWN },
{ KEY_LEFT, INPUT_LEFT },
{ KEY_RIGHT, INPUT_RIGHT },
{ KEY_GRAVE, INPUT_TOGGLE_CONSOLE }
};
#define INPUT_BIND_MAP_COUNT (sizeof(INPUT_BIND_MAPS) / sizeof(inputbindmap_t))
input_t INPUT;
@ -32,48 +18,4 @@ void inputInit(void) {
}
void inputUpdate(void) {
memoryCopy(
INPUT.previous,
INPUT.current,
sizeof(INPUT.previous)
);
memoryZero(
INPUT.current,
sizeof(INPUT.current)
);
for(uint32_t i = 0; i < INPUT_BIND_MAP_COUNT; i++) {
const inputbindmap_t *map = &INPUT_BIND_MAPS[i];
if(IsKeyDown(map->key)) INPUT.current[map->bind] = true;
}
INPUT.keyPressed = GetKeyPressed();
INPUT.charPressed = GetCharPressed();
}
bool_t inputIsDown(const inputbind_t input) {
assertTrue(input < INPUT_BIND_COUNT, "Input out of range");
return INPUT.current[input];
}
bool_t inputIsUp(const inputbind_t input) {
return !inputIsDown(input);
}
bool_t inputWasDown(const inputbind_t input) {
assertTrue(input < INPUT_BIND_COUNT, "Input out of range");
return INPUT.previous[input];
}
bool_t inputWasUp(const inputbind_t input) {
return !inputWasDown(input);
}
bool_t inputIsPressed(const inputbind_t input) {
return inputIsDown(input) && inputWasUp(input);
}
bool_t inputIsReleased(const inputbind_t input) {
return inputIsUp(input) && inputWasDown(input);
}

View File

@ -8,28 +8,8 @@
#pragma once
#include "dusk.h"
typedef enum {
INPUT_UP,
INPUT_DOWN,
INPUT_LEFT,
INPUT_RIGHT,
INPUT_ACCEPT,
INPUT_CANCEL,
INPUT_TOGGLE_CONSOLE,
} inputbind_t;
#define INPUT_BIND_COUNT INPUT_TOGGLE_CONSOLE + 1
typedef struct {
const int32_t key;
const inputbind_t bind;
} inputbindmap_t;
typedef struct {
bool_t current[INPUT_BIND_COUNT];
bool_t previous[INPUT_BIND_COUNT];
int32_t keyPressed;
char_t charPressed;
char_t input;
} input_t;
extern input_t INPUT;
@ -43,51 +23,3 @@ void inputInit(void);
* Updates the input system.
*/
void inputUpdate(void);
/**
* Returns whether a given input is currently pressed.
*
* @param input The input to check.
* @return True if the input is pressed, false otherwise.
*/
bool_t inputIsDown(const inputbind_t input);
/**
* Returns whether a given input is currently up.
*
* @param input The input to check.
* @return True if the input is up, false otherwise.
*/
bool_t inputIsUp(const inputbind_t input);
/**
* Returns whether a given input was just pressed.
*
* @param input The input to check.
* @return True if the input was just pressed, false otherwise.
*/
bool_t inputWasDown(const inputbind_t input);
/**
* Returns whether a given input was just released.
*
* @param input The input to check.
* @return True if the input was just released, false otherwise.
*/
bool_t inputWasUp(const inputbind_t input);
/**
* Returns whether a given input was just pressed.
*
* @param input The input to check.
* @return True if the input was just pressed, false otherwise.
*/
bool_t inputIsPressed(const inputbind_t input);
/**
* Returns whether a given input was just released.
*
* @param input The input to check.
* @return True if the input was just released, false otherwise.
*/
bool_t inputIsReleased(const inputbind_t input);

View File

@ -5,70 +5,17 @@
* https://opensource.org/licenses/MIT
*/
#include "assert/assert.h"
#include "game/game.h"
#include "display/render.h"
#include "input.h"
#include "console/console.h"
#include "network/client/client.h"
#include "network/server/server.h"
#include "util/random.h"
bool_t exitRequested = false;
void cmdExit(const consolecmdexec_t *exec) {
exitRequested = true;
}
#include "display/render.h"
int32_t main(const int32_t argc, const char **argv) {
assertInit();
randomInit();
gameInit();
inputInit();
renderInit();
consoleInit();
clientInit();
serverInit();
consoleRegCmd("exit", cmdExit);
while(1) {
entityInit(&GAME.entities[GAME.entityCount++], ENTITY_TYPE_PLAYER);
float_t lastPing = -1;
float_t time = 0;
while(true) {
inputUpdate();
consoleUpdate();
serverUpdate();
clientUpdate();
if(!CONSOLE.open) {
for(uint32_t i = 0; i < GAME.entityCount; i++) {
entity_t *ent = &GAME.entities[i];
entityTest(ent);
usleep(16 * 1000); // Sleep for 16 milliseconds (60 FPS)
}
}
renderresult_t result = renderDraw();
if(result != RENDER_OK) break;
if(exitRequested) break;
time += GetFrameTime();
if(time - lastPing > 1.0f) {
lastPing = time;
if(CLIENT.state != CLIENT_STATE_CONNECTED) continue;
packet_t packet;
packetPingCreate(&packet);
packetQueuePushOut(&CLIENT.packetQueue, &packet);
lastPing = time;
}
}
serverDispose();
clientDispose();
renderDispose();
return EXIT_SUCCESS;
}

View File

@ -1,11 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
# Subdirs
add_subdirectory(client)
add_subdirectory(packet)
add_subdirectory(server)

View File

@ -1,12 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
client.c
)
# Subdirs

View File

@ -1,96 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "client.h"
#include "util/memory.h"
#include "assert/assert.h"
client_t CLIENT;
void clientInit() {
memoryZero(&CLIENT, sizeof(client_t));
}
void clientConnect(const clientconnect_t connect) {
int32_t error;
assertTrue(
CLIENT.state == CLIENT_STATE_DISCONNECTED,
"Client is not in a disconnected state."
);
// Reset state
packetQueueInit(&CLIENT.queue);
CLIENT.error = CLIENT_ERROR_NO_ERROR;
CLIENT.state = CLIENT_STATE_CONNECTING;
CLIENT.type = connect.type;
// Init the properties based on the type of client.
switch(connect.type) {
case CLIENT_TYPE_NETWORKED:
networkClientConnect(&CLIENT, connect);
break;
case CLIENT_TYPE_SINGLE_PLAYER:
break;
default:
assertUnreachable("Invalid client connection type.");
break;
}
// Spawn the connect thread which will be responsible from here on out.
error = pthread_create(
&CLIENT.connectThread,
NULL,
(void *(*)(void *))clientThreadConnect,
&CLIENT
);
if(error != 0) {
CLIENT.state = CLIENT_STATE_DISCONNECTED;
CLIENT.error = CLIENT_ERROR_CONNECT_THREAD_SPAWN_FAILED;
}
}
void clientDisconnect() {
if(CLIENT.state == CLIENT_STATE_DISCONNECTED) return;
if(CLIENT.state == CLIENT_STATE_DISCONNECTING) return;
}
void clientUpdate() {
assertIsMainThread("Client update called from non-main thread.");
}
void clientDispose() {
if(CLIENT.state != CLIENT_STATE_DISCONNECTED) {
clientDisconnect();
}
}
void * clientThreadConnect(void *arg) {
assertNotNull(arg, "Client thread connect argument is null.");
assertNotMainThread("Client thread connect called from main thread.");
client_t *client = (client_t *)arg;
assertTrue(client == &CLIENT, "Invalid client thread arg.");
if(client->state) return NULL;
switch(client->type) {
case CLIENT_TYPE_NETWORKED:
break;
case CLIENT_TYPE_SINGLE_PLAYER:
break;
default:
assertUnreachable("Invalid client type.");
break;
}
//
}

View File

@ -1,117 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "network/packet/packetqueue.h"
#include "networkclient.h"
/**
* Type of connection used for a client.
*/
typedef enum {
CLIENT_TYPE_SINGLE_PLAYER = 0,
CLIENT_TYPE_NETWORKED = 1,
} clienttype_t;
/**
* The state of the client and what it is doing.
*/
typedef enum {
CLIENT_STATE_DISCONNECTED = 0,
CLIENT_STATE_CONNECTING,
CLIENT_STATE_CONNECTED,
CLIENT_STATE_DISCONNECTING,
} clientstate_t;
/**
* Information used to connect to a server.
*/
typedef struct clientconnect_s {
clienttype_t type;
} clientconnect_t;
/**
* Error codes for the client.
*/
typedef enum {
CLIENT_ERROR_NO_ERROR = 0,
CLIENT_ERROR_CONNECT_THREAD_SPAWN_FAILED,
} clienterror_t;
/**
* The clients state and information.
*/
typedef struct client_s {
clientstate_t state;
clienttype_t type;
packetqueue_t queue;
clienterror_t error;
pthread_t connectThread;
union {
networkclient_t network;
};
} client_t;
extern client_t CLIENT;
/**
* Initializes the client object, does not perform any connection.
*/
void clientInit();
/**
* Initiates a client connection, this function will not block and will return
* immediately even if a connection issue occurs. It is up to the caller to
* handle the connection state and any errors that occur.
*
* Client must be already disconnected before calling this function.
*
* @param connect The connection information to use.
*/
void clientConnect(const clientconnect_t connect);
/**
* Requests the client to disconnect from the server. This will not block and
* will return immediately. The client will handle the disconnection in the
* background.
*/
void clientDisconnect();
/**
* Internal method that writes a packet to the client immediately.
*/
clienterror_t clientWritePacket(const packet_t *packet);
/**
* Internal method that reads a packet from the client immediately.
*
* @param packet The packet to read into.
* @return Error code indicating success or failure.
*/
clienterror_t clientReadPacket(const packet_t *packet);
/**
* Main update loop for the client, this will handle all incoming packets and
* update the client state.
*/
void clientUpdate();
/**
* Disposes of the client, cleans up any resources and will force disconnect if
* not already disconnected.
*/
void clientDispose();
/**
* Thread function responsible for initiating and handshaking the connection.
*
* @param arg The client connection information.
* @return Returns NULL, unless an error occurs.
*/
void * clientThreadConnect(void *arg);

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct client_s client_t;
typedef struct clientconnect_s clientconnect_t;
typedef struct {
void *nothing;
} networkclient_t;
/**
* Initialize the client.
*
* @param client The client to initialize.
* @param connect The connection information.
*/
void networkClientConnect(client_t *client, const clientconnect_t connect);

View File

@ -1,14 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
packet.c
packetwelcome.c
packetdisconnect.c
packetqueue.c
packetping.c
)

View File

@ -1,84 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "packet.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "network/client/client.h"
#include "network/server/server.h"
packethandler_t PACKET_HANDLERS[] = {
{ NULL, NULL },
{ packetWelcomeClientProcess, NULL },
{ packetDisconnectClientProcess, NULL },
{ packetPingClientProcess, packetPingServerProcess },
};
void packetInit(
packet_t *packet,
const packettype_t type,
const uint32_t length
) {
assertNotNull(packet, "Packet is NULL");
memoryZero(packet, sizeof(packet_t));
assertTrue(length > 0, "Packet length is 0");
assertTrue(
length <= sizeof(packetdata_t),
"Packet length is too large"
);
packet->type = type;
packet->length = length;
}
errorret_t packetClientProcess(
const packet_t *packet,
client_t *client
) {
assertNotNull(packet, "Packet is NULL");
assertNotNull(client, "Client is NULL");
assertTrue(
client->type == CLIENT_TYPE_NETWORKED,
"Client is not networked"
);
assertIsMainThread("Client process must be on main thread");
if(packet->type >= PACKET_TYPE_COUNT) {
return error("Unknown packet type %d", packet->type);
}
if(PACKET_HANDLERS[packet->type].client == NULL) {
return error("Packet type %d has no client handler", packet->type);
}
return PACKET_HANDLERS[packet->type].client(packet, client);
}
errorret_t packetServerProcess(
const packet_t *packet,
serverclient_t *client
) {
assertNotNull(packet, "Packet is NULL");
assertNotNull(client, "Client is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertIsMainThread("Server client process must be on main thread");
if(packet->type >= PACKET_TYPE_COUNT) {
return error("Unknown packet type %d", packet->type);
}
if(PACKET_HANDLERS[packet->type].server == NULL) {
return error("Packet type %d has no server handler", packet->type);
}
return PACKET_HANDLERS[packet->type].server(packet, client);
}

View File

@ -1,80 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "packetwelcome.h"
#include "packetdisconnect.h"
#include "packetping.h"
typedef enum {
PACKET_TYPE_INVALID = 0,
PACKET_TYPE_WELCOME = 1,
PACKET_TYPE_DISCONNECT = 2,
PACKET_TYPE_PING = 3,
} packettype_t;
#define PACKET_TYPE_COUNT 4
typedef union {
packetwelcome_t welcome;
packetdisconnect_t disconnect;
packetping_t ping;
} packetdata_t;
typedef struct packet_s {
packettype_t type;
uint32_t length;
packetdata_t data;
} packet_t;
typedef struct {
errorret_t (*client)(const packet_t *packet, client_t *client);
errorret_t (*server)(const packet_t *packet, serverclient_t *client);
} packethandler_t;
extern packethandler_t PACKET_HANDLERS[];
/**
* Initializes a packet with the given type. This is only to be used by sub
* initializers, such as packetWelcomeCreate for example.
*
* @param packet Pointer to the packet structure to initialize.
* @param type The type of the packet.
* @param length The length of the packet data.
*/
void packetInit(
packet_t *packet,
const packettype_t type,
const uint32_t length
);
/**
* Processes a packet for a given client. Will auto-decide the correct method to
* handle the process
*
* @param packet Pointer to the packet structure to process.
* @param client Pointer to the client structure.
* @return ERROR_OK on success, or an error code on failure.
*/
errorret_t packetClientProcess(
const packet_t *packet,
client_t *client
);
/**
* Processes a packet for a given server client. Will auto-decide the correct
* method to handle the process
*
* @param packet Pointer to the packet structure to process.
* @param client Pointer to the server client structure.
* @return ERROR_OK on success, or an error code on failure.
*/
errorret_t packetServerProcess(
const packet_t *packet,
serverclient_t *client
);

View File

@ -1,52 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "packet.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "network/client/client.h"
void packetDisconnectCreate(
packet_t *packet,
const packetdisconnectreason_t reason
) {
packetInit(packet, PACKET_TYPE_DISCONNECT, sizeof(packetdisconnect_t));
packet->data.disconnect.reason = reason;
}
errorret_t packetDisconnectClientProcess(
const packet_t *packet,
client_t *client
) {
assertTrue(
packet->type == PACKET_TYPE_DISCONNECT,
"Packet type is not DISCONNECT"
);
if(packet->length != sizeof(packetdisconnect_t)) {
return error("Disconnect packet length is not correct");
}
// TODO: Handle disconnect reasons better.
packetdisconnect_t *data = (packetdisconnect_t *)&packet->data;
// switch(data->reason) {
// case PACKET_DISCONNECT_REASON_UNKNOWN:
// return error("Server disconnected: Unknown reason");
// case PACKET_DISCONNECT_REASON_INVALID_VERSION:
// return error("Server disconnected: Invalid version");
// case PACKET_DISCONNECT_REASON_MALFORMED_PACKET:
// return error("Server disconnected: Malformed packet");
// case PACKET_DISCONNECT_REASON_SERVER_FULL:
// return error("Server disconnected: Server full");
// case PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN:
// return error("Server disconnected: Server shutdown");
// default:
// return error("Server disconnected: Unknown reason");
// }
return ERROR_OK;
}

View File

@ -1,43 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "packetbase.h"
typedef enum {
PACKET_DISCONNECT_REASON_UNKNOWN = 0,
PACKET_DISCONNECT_REASON_INVALID_VERSION = 1,
PACKET_DISCONNECT_REASON_MALFORMED_PACKET = 2,
PACKET_DISCONNECT_REASON_SERVER_FULL = 3,
PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN = 4,
} packetdisconnectreason_t;
typedef struct {
packetdisconnectreason_t reason;
} packetdisconnect_t;
/**
* Creates a disconnect packet.
*
* @param packet Pointer to the packet structure to initialize.
* @param reason The reason for the disconnect.
*/
void packetDisconnectCreate(
packet_t *packet,
const packetdisconnectreason_t reason
);
/**
* Handles disconnect packet client side.
*
* @param packet Pointer to the packet structure to handle.
* @param client Pointer to the client structure.
* @return ERROR_OK on success, or an error code on failure.
*/
errorret_t packetDisconnectClientProcess(
const packet_t *packet, client_t *client
);

View File

@ -1,53 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "packet.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "network/server/server.h"
void packetPingCreate(packet_t *packet) {
packetInit(packet, PACKET_TYPE_PING, sizeof(packetping_t));
packet->data.ping.number = GetRandomValue(0, INT32_MAX);
}
errorret_t packetPingClientProcess(
const packet_t *packet,
client_t *client
) {
assertTrue(
packet->type == PACKET_TYPE_PING,
"Packet type is not PING"
);
printf("Client got Pong!\n");
return ERROR_OK;
}
errorret_t packetPingServerProcess(
const packet_t *packet,
serverclient_t *client
) {
assertTrue(
packet->type == PACKET_TYPE_PING,
"Packet type is not PING"
);
if(packet->length != sizeof(packetping_t)) {
return error("Ping packet length is not %d", sizeof(packetping_t));
}
printf("Server got Ping!\n");
// Send ping back to the client.
packet_t pong;
packetPingCreate(&pong);
packetQueuePushOut(&client->packetQueue, &pong);
return ERROR_OK;
}

View File

@ -1,44 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "packetbase.h"
typedef struct {
int32_t number;
} packetping_t;
/**
* Creates a ping packet.
*
* @param packet Pointer to the packet structure to initialize.
*/
void packetPingCreate(packet_t *packet);
/**
* Validates a ping packet received FROM a client INTO a server.
*
* @param packet Pointer to the packet structure to validate.
* @param client Pointer to the server client structure.
* @return ERROR_OK on success, or an error code on failure.
*/
errorret_t packetPingClientProcess(
const packet_t *packet,
client_t *client
);
/**
* Handles a ping packet received FROM a client INTO a server.
*
* @param packet Pointer to the packet structure to handle.
* @param client Pointer to the server client structure.
* @return ERROR_OK on success, or an error code on failure.
*/
errorret_t packetPingServerProcess(
const packet_t *packet,
serverclient_t *client
);

View File

@ -1,86 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "packetqueue.h"
#include "assert/assert.h"
#include "util/memory.h"
void packetQueueInit(packetqueue_t *queue) {
assertNotNull(queue, "Packet queue is NULL");
memoryZero(queue, sizeof(packetqueue_t));
pthread_mutex_init(&queue->lock, NULL);
}
void packetQueuePushIn(packetqueue_t *queue, const packet_t *packet) {
assertNotNull(queue, "Packet queue is NULL");
assertNotNull(packet, "Packet is NULL");
pthread_mutex_lock(&queue->lock);
assertTrue(
queue->packetsInCount < PACKET_QUEUE_MAX_SIZE, "Inbound packet queue is full"
);
queue->packetsIn[queue->packetsInCount++] = *packet;
pthread_mutex_unlock(&queue->lock);
}
void packetQueuePushOut(packetqueue_t *queue, const packet_t *packet) {
assertNotNull(queue, "Packet queue is NULL");
assertNotNull(packet, "Packet is NULL");
pthread_mutex_lock(&queue->lock);
assertTrue(
queue->packetsOutCount < PACKET_QUEUE_MAX_SIZE, "Outbound packet queue is full"
);
queue->packetsOut[queue->packetsOutCount++] = *packet;
pthread_mutex_unlock(&queue->lock);
}
int32_t packetQueuePopIn(packetqueue_t *queue, packet_t *packet) {
assertNotNull(queue, "Packet queue is NULL");
assertNotNull(packet, "Packet is NULL");
pthread_mutex_lock(&queue->lock);
if(queue->packetsInCount == 0) {
pthread_mutex_unlock(&queue->lock);
return 0;
}
*packet = queue->packetsIn[0];
if(queue->packetsInCount > 1) {
memoryCopy(
&queue->packetsIn[0],
&queue->packetsIn[1],
(queue->packetsInCount - 1) * sizeof(packet_t)
);
}
queue->packetsInCount--;
pthread_mutex_unlock(&queue->lock);
return 1;
}
int32_t packetQueuePopOut(packetqueue_t *queue, packet_t *packet) {
assertNotNull(queue, "Packet queue is NULL");
assertNotNull(packet, "Packet is NULL");
pthread_mutex_lock(&queue->lock);
if(queue->packetsOutCount == 0) {
pthread_mutex_unlock(&queue->lock);
return 0;
}
*packet = queue->packetsOut[0];
if(queue->packetsOutCount > 1) {
memoryCopy(
&queue->packetsOut[0],
&queue->packetsOut[1],
(queue->packetsOutCount - 1) * sizeof(packet_t)
);
}
queue->packetsOutCount--;
pthread_mutex_unlock(&queue->lock);
return 1;
}

View File

@ -1,60 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "network/packet/packet.h"
#define PACKET_QUEUE_MAX_SIZE 512
typedef struct {
packet_t packetsIn[PACKET_QUEUE_MAX_SIZE];
uint32_t packetsInCount;
packet_t packetsOut[PACKET_QUEUE_MAX_SIZE];
uint32_t packetsOutCount;
pthread_mutex_t lock;
} packetqueue_t;
/**
* Initializes the packet queue.
*
* @param queue Pointer to the packet queue structure.
*/
void packetQueueInit(packetqueue_t *queue);
/**
* Pushes a packet into the inbound packet queue.
*
* @param queue Pointer to the packet queue structure.
* @param packet Pointer to the packet to be pushed.
*/
void packetQueuePushIn(packetqueue_t *queue, const packet_t *packet);
/**
* Pushes a packet into the outbound packet queue.
*
* @param queue Pointer to the packet queue structure.
* @param packet Pointer to the packet to be pushed.
*/
void packetQueuePushOut(packetqueue_t *queue, const packet_t *packet);
/**
* Pops a packet from the inbound packet queue.
*
* @param queue Pointer to the packet queue structure.
* @param packet Pointer to the packet to store the popped packet.
* @return 1 if a packet was popped, 0 otherwise.
*/
int32_t packetQueuePopIn(packetqueue_t *queue, packet_t *packet);
/**
* Pops a packet from the outbound packet queue.
*
* @param queue Pointer to the packet queue structure.
* @param packet Pointer to the packet to store the popped packet.
* @return 1 if a packet was popped, 0 otherwise.
*/
int32_t packetQueuePopOut(packetqueue_t *queue, packet_t *packet);

View File

@ -1,40 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "packet.h"
#include "util/memory.h"
#include "assert/assert.h"
void packetWelcomeCreate(packet_t *packet) {
packetInit(packet, PACKET_TYPE_WELCOME, PACKET_WELCOME_SIZE);
memoryCopy(
packet->data.welcome.dusk, PACKET_WELCOME_STRING, PACKET_WELCOME_SIZE
);
}
errorret_t packetWelcomeClientProcess(
const packet_t *packet,
client_t *client
) {
assertNotNull(packet, "Packet is NULL");
assertNotNull(client, "Client is NULL");
assertTrue(packet->type == PACKET_TYPE_WELCOME, "Packet type is not WELCOME");
if(packet->length != PACKET_WELCOME_SIZE) {
return error("Welcome packet length is not %d", PACKET_WELCOME_SIZE);
}
if(
memoryCompare(
packet->data.welcome.dusk, PACKET_WELCOME_STRING, PACKET_WELCOME_SIZE
) != 0
) {
return error("Welcome packet data is not %s", PACKET_WELCOME_STRING);
}
return ERROR_OK;
}

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "packetbase.h"
#define PACKET_WELCOME_STRING "DUSK"
#define PACKET_WELCOME_SIZE 4
typedef struct {
char_t dusk[PACKET_WELCOME_SIZE];
} packetwelcome_t;
/**
* Creates a welcome packet.
*
* @param packet Pointer to the packet structure to initialize.
*/
void packetWelcomeCreate(packet_t *packet);
/**
* Handles a welcome packet received FROM a server INTO a client.
*
* @param packet Pointer to the packet structure to handle.
* @param client Pointer to the client structure.
* @return ERROR_OK on success, or an error code on failure.
*/
errorret_t packetWelcomeClientProcess(
const packet_t *packet,
client_t *client
);

View File

@ -1,15 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
server.c
serverclient.c
)
# Subdirs
add_subdirectory(networked)
add_subdirectory(singleplayer)

View File

@ -1,11 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
networkedserverclient.c
networkedserver.c
)

View File

@ -1,224 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network/server/server.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
errorret_t networkedServerStart(
server_t *server,
const serverstart_t start
) {
assertNotNull(server, "Server is null");
assertTrue(start.type == SERVER_TYPE_NETWORKED, "Invalid server type");
assertTrue(server->state == SERVER_STATE_STOPPED,"Server is already running");
assertIsMainThread("Server start must be on main thread");
// Initialize the networked server.
consolePrint("Starting server on port %d...", start.networked.port);
memoryZero(&server->networked, sizeof(server->networked));
// Initialize the server socket
server->networked.socket = socket(AF_INET, SOCK_STREAM, 0);
if(server->networked.socket < 0) {
return error("Failed to create socket");
}
// Setup the socket options
server->networked.address.sin_addr.s_addr = INADDR_ANY;
server->networked.address.sin_family = AF_INET;
server->networked.address.sin_port = htons(start.networked.port);
// Set the socket to reuse the address
int opt = 1;
setsockopt(
server->networked.socket,
SOL_SOCKET,
SO_REUSEADDR,
&opt,
sizeof(opt)
);
// Set the socket to non-blocking
int flags = fcntl(server->networked.socket, F_GETFL, 0);
if(
flags < 0 ||
fcntl(server->networked.socket, F_SETFL, flags | O_NONBLOCK) < 0
) {
close(server->networked.socket);
return error("Failed to set socket to non-blocking %s", strerror(errno));
}
// Bind the socket to the address and port
int32_t res = bind(
server->networked.socket,
(struct sockaddr *)&server->networked.address,
sizeof(server->networked.address)
);
if(res != 0) {
close(server->networked.socket);
return error(
"Socket bind to port %d failed: %s",
start.networked.port,
strerror(errno)
);
}
// Set the socket to listen for incoming connections
res = listen(server->networked.socket, SERVER_MAX_CLIENTS);
if(res != 0) {
close(server->networked.socket);
return error(
"Failed to listen on port %d: %s",
start.networked.port,
strerror(errno)
);
}
// Start the server thread.
pthread_mutex_init(&server->networked.lock, NULL);
server->state = SERVER_STATE_STARTING;
res = pthread_create(&server->thread, NULL, networkedServerThread, server);
if(res != 0) {
close(server->networked.socket);
pthread_mutex_destroy(&server->networked.lock);
server->state = SERVER_STATE_STOPPED;
return error("Failed to create server thread");
}
// Server started, hand back.
consolePrint("Server started.");
return ERROR_OK;
}
void networkedServerStop(server_t *server) {
assertNotNull(server, "Server is null");
assertTrue(server->state != SERVER_STATE_STOPPED,"Server is already stopped");
assertTrue(server->state != SERVER_STATE_STARTING, "Server is starting");
assertIsMainThread("Server stop must be on main thread");
// Notify thread to stop
pthread_mutex_lock(&server->networked.lock);
server->state = SERVER_STATE_STOPPING;
pthread_mutex_unlock(&server->networked.lock);
// Disconnect clients
uint8_t i = 0;
do {
serverclient_t *client = &server->clients[i++];
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) continue;
serverClientClose(client);
} while(i < SERVER_MAX_CLIENTS);
// Wait for the server thread to finish
pthread_join(server->thread, NULL);
pthread_mutex_destroy(&server->networked.lock);
close(server->networked.socket);
server->networked.socket = -1;
server->state = SERVER_STATE_STOPPED;
consolePrint("Server stopped.");
}
void * networkedServerThread(void *arg) {
assertNotNull(arg, "Server is null");
assertNotMainThread("Server thread must not be main thread");
server_t *server = (server_t *)arg;
// Set server state to running
pthread_mutex_lock(&server->networked.lock);
server->state = SERVER_STATE_RUNNING;
pthread_mutex_unlock(&server->networked.lock);
fd_set readfds;
struct timeval timeout;
// Main server loop
while (1) {
// Check if the server should continue running
pthread_mutex_lock(&server->networked.lock);
if (server->state != SERVER_STATE_RUNNING) {
pthread_mutex_unlock(&server->networked.lock);
break;
}
pthread_mutex_unlock(&server->networked.lock);
// Prepare the select call to wait for new connections
FD_ZERO(&readfds);
FD_SET(server->networked.socket, &readfds);
timeout.tv_sec = 1;
timeout.tv_usec = 0;
// Wait for incoming connections
int activity = select(
server->networked.socket + 1,
&readfds,
NULL,
NULL,
&timeout
);
if (activity <= 0) continue;
if (!FD_ISSET(server->networked.socket, &readfds)) continue;
// Accept incoming connection
int32_t clientSocket = accept(server->networked.socket, NULL, NULL);
if (clientSocket < 0) {
consolePrint("Failed to accept connection");
continue;
}
// Find an available client slot
serverclient_t *client = NULL;
for (uint8_t i = 0; i < SERVER_MAX_CLIENTS; i++) {
if(server->clients[i].state != SERVER_CLIENT_STATE_DISCONNECTED) {
continue;
}
client = &server->clients[i];
break;
}
// Server full, send disconnect packet
if (!client) {
packet_t packet;
serverclient_t tempClient = {
.networked = { .socket = clientSocket },
.server = server,
.state = SERVER_CLIENT_STATE_ACCEPTING
};
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_SERVER_FULL);
networkedServerClientWritePacket(&tempClient, &packet);
if (errorCheck()) errorPrint();
consolePrint("Client disconnected: Server full.");
close(clientSocket);
continue;
}
// Accept the client into the server
errorret_t ret = serverClientAccept(client, (serverclientaccept_t){
.server = server,
.networked = { .socket = clientSocket }
});
if (ret != ERROR_OK) {
consolePrint("Failed to accept client connection: %s", errorString());
errorFlush();
close(clientSocket);
}
}
// Cleanup and signal server stop
pthread_mutex_lock(&server->networked.lock);
server->state = SERVER_STATE_STOPPED;
pthread_mutex_unlock(&server->networked.lock);
consolePrint("Server thread exiting.");
return NULL;
}

View File

@ -1,56 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct {
uint16_t port;
} networkedserverstart_t;
typedef struct {
int socket;
struct sockaddr_in address;
pthread_mutex_t lock;
} networkedserver_t;
typedef struct server_s server_t;
typedef struct serverstart_s serverstart_t;
/**
* Initialize the networked server
*
* This function initializes the networked server by setting up the ENet library
* and creating a server host.
*
* @param server Pointer to the server structure.
* @param start Information about how to start the server.
* @return Returns an error code if the server fails to start.
*/
errorret_t networkedServerStart(server_t *server, const serverstart_t start);
/**
* Stop the networked server
*
* This function stops the networked server by shutting down the ENet library
* and closing the server host.
*
* @param server Pointer to the server structure.
*/
void networkedServerStop(server_t *server);
/**
* Server thread function
*
* This function runs in a separate thread and handles incoming connections,
* messages, and disconnections.
*
* @param arg Pointer to the argument passed to the thread.
*/
void * networkedServerThread(void *arg);

View File

@ -1,382 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network/server/server.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
#include <sys/socket.h>
errorret_t networkedServerClientAccept(
serverclient_t *client,
const serverclientaccept_t accept
) {
assertNotNull(client, "Client is NULL");
assertNotNull(accept.server, "Server is NULL");
assertTrue(
accept.server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
client->state = SERVER_CLIENT_STATE_ACCEPTING;
client->networked.socket = accept.networked.socket;
// Set timeout to 8 seconds
client->networked.timeout.tv_sec = 8;
client->networked.timeout.tv_usec = 0;
// Initialize mutexs
pthread_mutex_init(&client->networked.readLock, NULL);
pthread_mutex_init(&client->networked.writeLock, NULL);
// Create a read thread for the client
int32_t ret = pthread_create(
&client->networked.readThread,
NULL,
networkedServerClientReadThread,
client
);
if(ret != 0) {
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
return error("Failed to create client read thread");
}
// Set socket timeout
if(setsockopt(
client->networked.socket,
SOL_SOCKET,
SO_RCVTIMEO,
&client->networked.timeout,
sizeof(client->networked.timeout)
) < 0) {
networkedServerClientCloseOnThread(client, "Failed to set socket timeout");
return error("Failed to set socket timeout");
}
return ERROR_OK;
}
void networkedServerClientClose(serverclient_t *client) {
assertIsMainThread("Server client close must be on main thread.");
assertNotNull(client, "Client is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
// Mark client as disconnecting
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
// Wait for the read thread to finish
pthread_mutex_lock(&client->networked.readLock);
pthread_mutex_unlock(&client->networked.readLock);
pthread_join(client->networked.readThread, NULL);
client->networked.readThread = 0;
pthread_mutex_destroy(&client->networked.readLock);
// Signal and wait for the write thread to finish
pthread_mutex_lock(&client->networked.writeLock);
pthread_mutex_unlock(&client->networked.writeLock);
pthread_join(client->networked.writeThread, NULL);
client->networked.writeThread = 0;
pthread_mutex_destroy(&client->networked.writeLock);
// Close the socket
if(client->networked.socket != -1) {
close(client->networked.socket);
client->networked.socket = -1;
}
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
consolePrint("Client %d disconnected.", client->networked.socket);
}
void networkedServerClientCloseOnThread(
serverclient_t *client,
const char_t *reason
) {
assertNotNull(client, "Client is NULL");
assertNotNull(reason, "Reason is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertNotMainThread("Client close must not be main thread");
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
// Terminate the socket
close(client->networked.socket);
client->networked.socket = -1;
client->networked.readThread = 0;
consolePrint("Client %d disconnected: %s", client->networked.socket, reason);
// Mark this client as disconnected so it can be used again.
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
pthread_exit(NULL);
}
ssize_t networkedServerClientRead(
const serverclient_t * client,
uint8_t *buffer,
const size_t len
) {
assertNotNull(client, "Client is NULL");
assertNotNull(buffer, "Buffer is NULL");
assertNotNull(client->server, "Server is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertTrue(len > 0, "Buffer length is 0");
assertTrue(
client->state == SERVER_CLIENT_STATE_CONNECTED ||
client->state == SERVER_CLIENT_STATE_ACCEPTING ||
client->state == SERVER_CLIENT_STATE_DISCONNECTING,
"Client is not connected, accepting or disconnecting"
);
return recv(client->networked.socket, buffer, len, 0);
}
errorret_t networkedServerClientReadPacket(
const serverclient_t * client,
packet_t *packet
) {
uint8_t buffer[sizeof(packet_t)];
ssize_t read;
assertNotNull(client, "Client is NULL");
assertNotNull(packet, "Packet is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
// Read packet ID
read = networkedServerClientRead(client, buffer, sizeof(packettype_t));
if(read != sizeof(packettype_t)) {
return error("Failed to read packet ID");
}
packet->type = *(packettype_t *)buffer;
if(packet->type == PACKET_TYPE_INVALID) {
return error("Invalid packet type");
}
// Read length
read = networkedServerClientRead(
client,
buffer,
sizeof(uint32_t)
);
if(read != sizeof(uint32_t)) {
return error("Failed to read packet length");
}
packet->length = *(uint32_t *)buffer;
if(packet->length > sizeof(packetdata_t)) {
return error("Packet length is too large");
}
// Read data
read = networkedServerClientRead(
client,
(uint8_t *)&packet->data,
packet->length
);
if(read != packet->length) {
return error("Failed to read packet data");
}
return ERROR_OK;
}
errorret_t networkedServerClientWrite(
serverclient_t * client,
const uint8_t *data,
const size_t len
) {
assertNotNull(client, "Client is NULL");
assertNotNull(data, "Data is NULL");
assertTrue(len > 0, "Data length is 0");
assertNotNull(client->server, "Server is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) {
return error("Client is disconnected");
}
ssize_t sent = send(client->networked.socket, data, len, 0);
if(sent < 0) return error("Failed to send data");
return ERROR_OK;
}
errorret_t networkedServerClientWritePacket(
serverclient_t * client,
const packet_t *packet
) {
assertNotNull(packet, "Packet is NULL");
assertTrue(packet->type != PACKET_TYPE_INVALID, "Packet type is INVALID");
assertTrue(packet->length > 0, "Packet length is 0");
assertTrue(
packet->length <= sizeof(packetdata_t),
"Packet length is too large (1)"
);
size_t fullSize = sizeof(packet_t) - sizeof(packet->data) + packet->length;
assertTrue(fullSize <= sizeof(packet_t), "Packet size is too large (2)");
return networkedServerClientWrite(client, (const uint8_t *)packet, fullSize);
}
void * networkedServerClientReadThread(void *arg) {
assertNotNull(arg, "Client is NULL");
assertNotMainThread("Client thread must not be main thread");
serverclient_t *client = (serverclient_t *)arg;
char_t buffer[1024];
ssize_t read;
errorret_t err;
packet_t packet;
assertNotNull(client->server, "Server is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertTrue(
client->state == SERVER_CLIENT_STATE_ACCEPTING,
"Client is not accepting?"
);
// First message from the client should always be "DUSK|VERSION" to match
// the server version.
{
const char_t *expecting = "DUSK|"DUSK_VERSION;
read = networkedServerClientRead(client, buffer, sizeof(buffer));
if(read <= 0) {
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
err = networkedServerClientWritePacket(client, &packet);
networkedServerClientCloseOnThread(client, "Failed to receive version");
return NULL;
}
buffer[read] = '\0'; // Null-terminate the string
if(strncmp(buffer, expecting, strlen(expecting)) != 0) {
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
err = networkedServerClientWritePacket(client, &packet);
networkedServerClientCloseOnThread(client, "Invalid version");
return NULL;
}
}
// Send DUSK back!
packetWelcomeCreate(&packet);
err = networkedServerClientWritePacket(client, &packet);
if(err != ERROR_OK) {
networkedServerClientCloseOnThread(
client, "Failed to send welcome message"
);
return NULL;
}
// Client is connected.
client->state = SERVER_CLIENT_STATE_CONNECTED;
// Start the write thread after the handshake
int32_t ret = pthread_create(
&client->networked.writeThread,
NULL,
networkedServerClientWriteThread,
client
);
if(ret != 0) {
networkedServerClientCloseOnThread(client, "Failed to create write thread");
return NULL;
}
// Start listening for packets.
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
pthread_mutex_lock(&client->networked.readLock);
err = networkedServerClientReadPacket(client, &packet);
if(err != ERROR_OK) {
consolePrint("Failed to read packet %s", errorString());
errorFlush();
pthread_mutex_unlock(&client->networked.readLock);
break;
}
packetQueuePushIn(&client->packetQueue, &packet);
pthread_mutex_unlock(&client->networked.readLock);
}
pthread_mutex_lock(&client->networked.readLock);
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
pthread_mutex_unlock(&client->networked.readLock);
return NULL;
}
void * networkedServerClientWriteThread(void *arg) {
packet_t packet;
int32_t ret;
errorret_t err;
assertNotNull(arg, "Client is NULL");
assertNotMainThread("Client thread must not be main thread");
assertTrue(
((serverclient_t *)arg)->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertTrue(
((serverclient_t *)arg)->state == SERVER_CLIENT_STATE_CONNECTED,
"Client is not connected"
);
serverclient_t *client = (serverclient_t *)arg;
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
pthread_mutex_lock(&client->networked.writeLock);
ret = packetQueuePopOut(
&client->packetQueue,
&packet
);
if(ret == 0) {
pthread_mutex_unlock(&client->networked.writeLock);
continue;
}
if(ret < 0) {
consolePrint("Failed to pop packet %s", errorString());
errorFlush();
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
pthread_mutex_unlock(&client->networked.writeLock);
break;
}
err = networkedServerClientWritePacket(client, &packet);
if(err != ERROR_OK) {
consolePrint("Failed to write packet %s", errorString());
errorFlush();
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
pthread_mutex_unlock(&client->networked.writeLock);
break;
}
pthread_mutex_unlock(&client->networked.writeLock);
}
return NULL;
}

View File

@ -1,111 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef struct serverclient_s serverclient_t;
typedef struct serverclientaccept_s serverclientaccept_t;
typedef struct {
int32_t socket;
} networkedserverclientaccept_t;
typedef struct {
int32_t socket;
pthread_t readThread;
pthread_t writeThread;
struct timeval timeout;
pthread_mutex_t readLock;
pthread_mutex_t writeLock;
} networkedserverclient_t;
/**
* Accepts an incoming connection from a networked server client.
*
* @param client Pointer to the server client structure.
* @param accept Accept structure containing client information.
* @return Error code indicating success or failure.
*/
errorret_t networkedServerClientAccept(
serverclient_t *client,
const serverclientaccept_t accept
);
/**
* Closes the connection to a networked server client. Waits for the thread to finish.
*
* @param client Pointer to the server client structure.
*/
void networkedServerClientClose(serverclient_t *client);
/**
* Closes the connection to a networked server client on the thread.
*
* @param client Pointer to the server client structure.
* @param reason Reason for closing the connection.
*/
void networkedServerClientCloseOnThread(
serverclient_t *client,
const char_t *reason
);
/**
* Receives data from a server client.
*
* @param client Pointer to the server client structure.
* @param buffer Pointer to the buffer to store received data.
* @param len Max length of the buffer.
* @return Number of bytes received. 0 or less indicates an error.
*/
ssize_t networkedServerClientRead(
const serverclient_t * client,
uint8_t *buffer,
const size_t len
);
/**
* Writes data to a server client.
*
* @param client Pointer to the server client structure.
* @param data Pointer to the data to send.
* @param len Length of the data to send.
* @return Error code indicating success or failure.
*/
errorret_t networkedServerClientWrite(
serverclient_t * client,
const uint8_t *data,
const size_t len
);
/**
* Writes a packet to a server client.
*
* @param client Pointer to the server client structure.
* @param packet Pointer to the packet to send.
* @return Error code indicating success or failure.
*/
errorret_t networkedServerClientWritePacket(
serverclient_t * client,
const packet_t *packet
);
/**
* Thread function for handling reading from a networked server client.
*
* @param arg Pointer to the server client structure.
* @return NULL.
*/
void * networkedServerClientReadThread(void *arg);
/**
* Thread function for handling writing to a networked server client.
*
* @param arg Pointer to the server client structure.
* @return NULL.
*/
void * networkedServerClientWriteThread(void *arg);

View File

@ -1,127 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "server.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "console/console.h"
#include "util/string.h"
server_t SERVER;
void cmdStart(const consolecmdexec_t *exec) {
serverstart_t start;
if(exec->argc > 0) {
if(stringCompare(exec->argv[0], "0") == 0) {
start.type = SERVER_TYPE_SINGLE_PLAYER;
} else if(stringCompare(exec->argv[0], "1") == 0) {
start.type = SERVER_TYPE_NETWORKED;
} else {
consolePrint("Invalid server type: %s", exec->argv[0]);
return;
}
} else {
start.type = SERVER_TYPE_NETWORKED;
}
if(exec->argc > 1) {
if(start.type == SERVER_TYPE_NETWORKED) {
if(!stringToU16(exec->argv[1], &start.networked.port)) {
consolePrint("Invalid port number: %s", exec->argv[0]);
return;
}
}
} else {
if(start.type == SERVER_TYPE_NETWORKED) {
start.networked.port = SERVER_DEFAULT_PORT;
}
}
errorret_t ret = serverStart(start);
if(ret != ERROR_OK) {
consolePrint("Failed to start server: %s", errorString());
errorFlush();
return;
}
}
void cmdClose(const consolecmdexec_t *exec) {
serverStop();
}
void serverInit() {
memoryZero(&SERVER, sizeof(server_t));
SERVER.state = SERVER_STATE_STOPPED;
consoleRegCmd("start", cmdStart);
consoleRegCmd("close", cmdClose);
}
errorret_t serverStart(const serverstart_t start) {
errorret_t ret;
assertIsMainThread("Server start must be on main thread");
// Do not start a running server.
if(SERVER.state != SERVER_STATE_STOPPED) {
return error("Server is already running");
}
SERVER.type = start.type;
// Hand off to relevant server type to start.
switch(start.type) {
case SERVER_TYPE_NETWORKED:
ret = networkedServerStart(&SERVER, start);
break;
default:
assertUnreachable("Invalid server type");
}
if(ret != ERROR_OK) SERVER.state = SERVER_STATE_STOPPED;
return ret;
}
uint8_t serverGetClientCount() {
uint8_t i = 0, clientCount = 0;
do {
if(SERVER.clients[i++].state == SERVER_CLIENT_STATE_DISCONNECTED) continue;
clientCount++;
} while(i < SERVER_MAX_CLIENTS);
return clientCount;
}
void serverUpdate() {
assertIsMainThread("Server update must be on main thread");
if(SERVER.state != SERVER_STATE_RUNNING) return;
// Update all clients
for(uint8_t i = 0; i < SERVER_MAX_CLIENTS; i++) {
serverClientUpdate(&SERVER.clients[i]);
}
}
void serverStop() {
assertIsMainThread("Server stop must be on main thread");
if(SERVER.state == SERVER_STATE_STOPPED) return;
consolePrint("Stopping server...");
switch(SERVER.type) {
case SERVER_TYPE_NETWORKED:
networkedServerStop(&SERVER);
break;
default:
assertUnreachable("Invalid server type");
}
consolePrint("Server stopped.");
}
void serverDispose() {
serverStop();
}

View File

@ -1,93 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "serverclient.h"
#include "network/server/networked/networkedserver.h"
#define SERVER_MAX_CLIENTS 32
#define SERVER_DEFAULT_PORT 3030
typedef enum {
SERVER_STATE_STOPPED,
SERVER_STATE_STARTING,
SERVER_STATE_RUNNING,
SERVER_STATE_STOPPING
} serverstate_t;
typedef enum {
SERVER_TYPE_SINGLE_PLAYER,
SERVER_TYPE_NETWORKED
} servertype_t;
typedef struct serverstart_s {
servertype_t type;
union {
networkedserverstart_t networked;
};
} serverstart_t;
typedef struct server_s {
serverstate_t state;
servertype_t type;
pthread_t thread;
serverclient_t clients[SERVER_MAX_CLIENTS];
union {
networkedserver_t networked;
};
} server_t;
extern server_t SERVER;
/**
* Initialize the server
*
* This function initializes the server by setting up the ENet library and
* creating a server host.
*/
void serverInit();
/**
* Start the server
*
* This function starts the server by creating a host, binding it to the
* specified address and port, and starting the server thread.
*
* @param port The port number to bind the server to.
* @return Returns an error code if the server fails to start.
*/
errorret_t serverStart(const serverstart_t start);
/**
* Get the client count
*
* This function returns the number of connected clients to the server.
*
* @return The number of connected clients.
*/
uint8_t serverGetClientCount();
/**
* Performs a (main thread) update to process clients on the server.
*/
void serverUpdate();
/**
* Stop the server
*
* This function stops the server by destroying the host and cleaning up
* resources.
*/
void serverStop();
/**
* Dispose of the server
*
* This function disposes of the server by deinitializing the ENet library.
*/
void serverDispose();

View File

@ -1,79 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "server.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
errorret_t serverClientAccept(
serverclient_t *client,
const serverclientaccept_t accept
) {
errorret_t ret;
memoryZero(client, sizeof(serverclient_t));
assertNotNull(accept.server, "Server is NULL");
assertNotMainThread("Server client accept must not be main thread");
client->server = accept.server;
packetQueueInit(&client->packetQueue);
switch(accept.server->type) {
case SERVER_TYPE_NETWORKED:
ret = networkedServerClientAccept(client, accept);
break;
default:
assertUnreachable("Unknown server type");
}
if(ret != ERROR_OK) memoryZero(client, sizeof(serverclient_t));
return ret;
}
void serverClientUpdate(serverclient_t *client) {
assertNotNull(client, "Client is NULL");
assertIsMainThread("Server client update must be on main thread");
if(client->state != SERVER_CLIENT_STATE_CONNECTED) return;
packet_t packet;
int32_t ret = 1;
// Process packets
do {
ret = packetQueuePopIn(
&client->packetQueue,
&packet
);
if(ret == 0) break;
if(ret < 0) {
serverClientClose(client);
consolePrint("Failed to pop packet");
return;
}
packetServerProcess(&packet, client);
} while(true);
}
void serverClientClose(serverclient_t *client) {
assertNotNull(client, "Client is NULL");
assertIsMainThread("Server client close must be on main thread");
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) return;
switch(client->server->type) {
case SERVER_TYPE_NETWORKED:
networkedServerClientClose(client);
break;
default:
assertUnreachable("Unknown server type");
}
}

View File

@ -1,64 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "network/packet/packet.h"
#include "network/server/networked/networkedserverclient.h"
#include "network/packet/packetqueue.h"
typedef struct server_s server_t;
typedef enum {
SERVER_CLIENT_STATE_DISCONNECTED,
SERVER_CLIENT_STATE_ACCEPTING,
SERVER_CLIENT_STATE_CONNECTED,
SERVER_CLIENT_STATE_DISCONNECTING,
} serverclientstate_t;
typedef struct serverclientaccept_s {
server_t *server;
union {
networkedserverclientaccept_t networked;
};
} serverclientaccept_t;
typedef struct serverclient_s {
server_t *server;
serverclientstate_t state;
packetqueue_t packetQueue;
union {
networkedserverclient_t networked;
};
} serverclient_t;
/**
* Accepts an incoming connection from a server client.
*
* @param client Pointer to the server client structure.
* @param accept Accept structure containing client information.
* @return Error code indicating success or failure.
*/
errorret_t serverClientAccept(
serverclient_t *client,
const serverclientaccept_t accept
);
/**
* Handles the main thread updating of this server client. This is where all
* the packets will be handled and processed.
*
* @param client Pointer to the server client structure.
*/
void serverClientUpdate(serverclient_t *client);
/**
* Closes the connection to a server client. Waits for the thread to finish.
*
* @param client Pointer to the server client structure.
*/
void serverClientClose(serverclient_t *client);

View File

@ -6,8 +6,9 @@
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
game.c
)
# Subdirs
add_subdirectory(entity)
add_subdirectory(item)
add_subdirectory(quest)

View File

@ -7,4 +7,5 @@
target_sources(${DUSK_TARGET_NAME}
PRIVATE
entity.c
player.c
)

34
src/rpg/entity/entity.c Normal file
View File

@ -0,0 +1,34 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assert/assert.h"
#include "util/memory.h"
#include "entity.h"
entity_t ENTITIES[ENTITY_COUNT];
entitycallbacks_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
[ENTITY_TYPE_NULL] = {
.init = NULL
},
[ENTITY_TYPE_PLAYER] = {
.init = playerInit
},
};
void entityInit(entity_t *entity, const entitytype_t type) {
assertNotNull(entity, "Entity is NULL");
assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type");
assertNotNull(ENTITY_CALLBACKS[type].init, "Entity type has no init");
memoryZero(entity, sizeof(entity_t));
entity->type = type;
ENTITY_CALLBACKS[type].init(entity);
}

54
src/rpg/entity/entity.h Normal file
View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "player.h"
typedef enum {
ENTITY_DIR_UP = 0,
ENTITY_DIR_DOWN = 1,
ENTITY_DIR_LEFT = 2,
ENTITY_DIR_RIGHT = 3,
ENTITY_DIR_NORTH = ENTITY_DIR_UP,
ENTITY_DIR_SOUTH = ENTITY_DIR_DOWN,
ENTITY_DIR_WEST = ENTITY_DIR_LEFT,
ENTITY_DIR_EAST = ENTITY_DIR_RIGHT
} entitydir_t;
typedef enum {
ENTITY_TYPE_NULL = 0,
ENTITY_TYPE_PLAYER = 1,
} entitytype_t;
#define ENTITY_TYPE_COUNT (ENTITY_TYPE_PLAYER + 1)
typedef struct _entity_t {
entitytype_t type;
uint8_t x, y;
entitydir_t dir;
// Per type data
union {
player_t player;
};
} entity_t;
#define ENTITY_COUNT 16
extern entity_t ENTITIES[ENTITY_COUNT];
typedef struct {
void (*init)(entity_t *entity);
} entitycallbacks_t;
extern entitycallbacks_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT];
/**
* Initializes an entity with the given type.
*
* @param entity Pointer to the entity to initialize.
* @param type The type of the entity to initialize.
*/
void entityInit(entity_t *entity, const entitytype_t type);

14
src/rpg/entity/player.c Normal file
View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "assert/assert.h"
void playerInit(entity_t *player) {
assertNotNull(player, "Player entity is NULL");
assertTrue(player->type == ENTITY_TYPE_PLAYER, "Entity is not a player");
}

22
src/rpg/entity/player.h Normal file
View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct _entity_t entity_t;
typedef struct {
uint8_t nothing;
} player_t;
/**
* Initializes a player entity.
*
* @param ent Pointer to the player entity to initialize.
*/
void playerInit(entity_t *ent);

View File

@ -9,6 +9,5 @@
#include "dusk.h"
typedef struct {
void *nothing;
uint8_t width, height;
} map_t;
} event_t;

View File

@ -6,5 +6,5 @@
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
error.c
itemtype.c
)

9
src/rpg/item/item.h Normal file
View File

@ -0,0 +1,9 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "itemtype.h"

22
src/rpg/item/itemtype.c Normal file
View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "itemtype.h"
iteminfo_t ITEM_INFO[ITEM_TYPE_COUNT] = {
[ITEM_TYPE_NULL] = { 0 },
// Consumables
[ITEM_TYPE_POTION] = { .stackable = true },
// Ingredients
[ITEM_TYPE_ONION] = { .stackable = true },
[ITEM_TYPE_SWEET_POTATO] = { .stackable = true },
// Cooked Items
[ITEM_TYPE_BAKED_SWEET_POTATO] = { .stackable = true },
};

30
src/rpg/item/itemtype.h Normal file
View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
ITEM_TYPE_NULL,
// Consumables
ITEM_TYPE_POTION,
// Ingredients
ITEM_TYPE_ONION,
ITEM_TYPE_SWEET_POTATO,
// Cooked Items
ITEM_TYPE_BAKED_SWEET_POTATO,
} itemtype_t;
#define ITEM_TYPE_COUNT (ITEM_TYPE_BAKED_SWEET_POTATO + 1)
typedef struct {
bool_t stackable;
} iteminfo_t;
extern iteminfo_t ITEM_INFO[ITEM_TYPE_COUNT];

View File

@ -6,4 +6,5 @@
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
quest.c
)

View File

@ -5,4 +5,4 @@
* https://opensource.org/licenses/MIT
*/
#include "networkclient.h"
#include "quest.h"

View File

@ -6,8 +6,8 @@
*/
#pragma once
#include "error/error.h"
#include "dusk.h"
typedef struct client_s client_t;
typedef struct serverclient_s serverclient_t;
typedef struct packet_s packet_t;
typedef struct {
} quest_t;

View File

@ -8,17 +8,6 @@
#include "memory.h"
#include "assert/assert.h"
void * memoryAllocate(const size_t size) {
void *ptr = malloc(size);
assertNotNull(ptr, "Failed to allocate memory.");
return ptr;
}
void memoryFree(void *ptr) {
assertNotNull(ptr, "Cannot free NULL memory.");
free(ptr);
}
void memoryCopy(void *dest, const void *src, const size_t size) {
assertNotNull(dest, "Cannot copy to NULL memory.");
assertNotNull(src, "Cannot copy from NULL memory.");

View File

@ -9,19 +9,4 @@
#include "assert/assert.h"
void randomInit() {
randomSeed(time(NULL));
}
void randomSeed(const uint32_t seed) {
srand(seed);
}
int32_t randomI32(const int32_t min, const int32_t max) {
assertTrue(min < max, "Min is not less than max");
return (rand() % (max - min)) + min;
}
float_t randomF32(const float_t min, const float_t max) {
assertTrue(min < max, "Min is not less than max");
return ((float_t)rand() / (float_t)RAND_MAX) * (max - min) + min;
}

View File

@ -16,28 +16,3 @@
* the current time to generate a seed.
*/
void randomInit();
/**
* Sets the random seed for the random number generator.
*
* @param seed The seed to set.
*/
void randomSeed(const uint32_t seed);
/**
* Generates a random integer between min and max.
*
* @param min The minimum value (inclusive).
* @param max The maximum value (exclusive).
* @return A random integer between min and max.
*/
int32_t randomI32(const int32_t min, const int32_t max);
/**
* Generates a random float between min and max.
*
* @param min The minimum value (inclusive).
* @param max The maximum value (exclusive).
* @return A random float between min and max.
*/
float_t randomF32(const float_t min, const float_t max);