Compare commits

...

32 Commits

Author SHA1 Message Date
Cameron Murphy Reikes dcdeaf4cc1 Handle mouse inputs while flag modal open 2 years ago
Cameron Murphy Reikes fe2c9d07c5 Squad selection 2 years ago
Cameron Murphy Reikes bacc6e9f03 Serialize last used medbay 2 years ago
Cameron Murphy Reikes a5873b4547 Fix annoying msvc error 2 years ago
Cameron Murphy Reikes 515385a477 Only calculate length on ser of names 2 years ago
Cameron Murphy Reikes b942e0afd9 VS format 2 years ago
Cameron Murphy Reikes 0752ac8989 Fix digits serialization bug 2 years ago
Cameron Murphy Reikes ac1928d2cd Stop server service before compiling game 2 years ago
Cameron Murphy Reikes 541d08468e Massive bump in proximity voice chat travel dist 2 years ago
Cameron Murphy Reikes 0b41df7ff7 Merge branch 'main' of https://github.com/creikey/flight 2 years ago
Cameron Murphy Reikes a9f06b36c0 Release build visual studio 2 years ago
root a40a1bf491 Get linux build working 2 years ago
Cameron Murphy Reikes 71ad6c7bbd Update server to have mutex 2 years ago
Cameron Murphy Reikes c374c7113b Implement the proximity voice chat 2 years ago
Cameron Murphy Reikes 9eec314031 Add profiling on server, fix timing bug 2 years ago
Cameron Murphy Reikes 7c2798747a Basic voice chat to self, mute button.Lag+no linux 2 years ago
Cameron Murphy Reikes 9295c95121 Fix pound define 2 years ago
Cameron Murphy Reikes 4a17bbd6b2 Tune vision radius 2 years ago
Cameron Murphy Reikes a58ac048ab Only send part of large grids in vision radius 2 years ago
Cameron Murphy Reikes 4ed024a025 Fix crash on spacebar 2 years ago
Cameron Murphy Reikes bb849cd026 Clamp server processing time 2 years ago
Cameron Murphy Reikes 5fdad86b64 Escape exit fullscreen 2 years ago
Cameron Murphy Reikes 337a61596e Respawn at last medbay and tune save timing 2 years ago
Cameron Murphy Reikes 557cd25630 Fix entities not using free list 2 years ago
Cameron Murphy Reikes 2cee8fc7d3 Automated build tooling 2 years ago
Cameron Murphy Reikes 2dd01b1f34 Lock mouse frozen behind debug flag 2 years ago
Cameron Murphy Reikes 5b5036ff5b Bump max entities and remove bad perf memset 2 years ago
Cameron Murphy Reikes 8a91469cf8 Increase gamestate limit 2 years ago
Cameron Murphy Reikes 2b8a2016d4 fopen_s but for linux 2 years ago
Cameron Murphy Reikes ce0ca17976 Set velocity of player to match that of seat 2 years ago
Cameron Murphy Reikes de4de50aac Save world and red vignette on damage 2 years ago
Cameron Murphy Reikes dd5731eb45 Only send player what's near them, other fixes
- When zoom out players get big
2 years ago

7
.gitignore vendored

@ -1,12 +1,17 @@
*.spall # profiling
*.bin # world files
debug_world.bin
flight*.zip
flight_server
flight.zip
ipsettings.h
releases/
.vs/
x64/
*.exe
*.obj
*.pdb
*.exp
*.lib
*.ilk
*.gen.h

3
.gitmodules vendored

@ -4,3 +4,6 @@
[submodule "thirdparty/Chipmunk2D"]
path = thirdparty/Chipmunk2D
url = https://github.com/slembcke/Chipmunk2D.git
[submodule "thirdparty/opus"]
path = thirdparty/opus
url = https://github.com/xiph/opus.git

@ -142,13 +142,13 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);DEBUG_RENDERING;SERVER_ADDRESS="127.0.0.1"</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Users\Cameron\Documents\flight\thirdparty\enet\include;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include\chipmunk;C:\Users\Cameron\Documents\flight\thirdparty;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include;C:\Users\Cameron\Documents\flight\thirdparty\minilzo</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)\thirdparty\enet\include;$(ProjectDir)\thirdparty\Chipmunk2D\include\chipmunk;$(ProjectDir)\thirdparty;$(ProjectDir)\thirdparty\Chipmunk2D\include;$(ProjectDir)\thirdparty\minilzo;$(ProjectDir)\thirdparty\opus\include</AdditionalIncludeDirectories>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies);Ws2_32.lib;winmm.lib</AdditionalDependencies>
<AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies);Ws2_32.lib;winmm.lib;$(ProjectDir)\thirdparty\opus\win32\VS2015\x64\Release\opus.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug No Host|x64'">
@ -174,14 +174,14 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Users\Cameron\Documents\flight\thirdparty\enet\include;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include\chipmunk;C:\Users\Cameron\Documents\flight\thirdparty;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include;C:\Users\Cameron\Documents\flight\thirdparty\minilzo</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)\thirdparty\enet\include;$(ProjectDir)\thirdparty\Chipmunk2D\include\chipmunk;$(ProjectDir)\thirdparty;$(ProjectDir)\thirdparty\Chipmunk2D\include;$(ProjectDir)\thirdparty\minilzo;$(ProjectDir)\thirdparty\opus\include</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies);Ws2_32.lib;winmm.lib</AdditionalDependencies>
<AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies);Ws2_32.lib;winmm.lib;$(ProjectDir)\thirdparty\opus\win32\VS2015\x64\Release\opus.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
@ -223,7 +223,9 @@
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSpatialIndex.c" />
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSweep1D.c" />
<ClCompile Include="thirdparty\enet\callbacks.c" />
<ClCompile Include="thirdparty\enet\compress.c" />
<ClCompile Include="thirdparty\enet\compress.c">
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)\thirdparty\enet\include;$(ProjectDir)\thirdparty\Chipmunk2D\include\chipmunk;$(ProjectDir)\thirdparty;$(ProjectDir)\thirdparty\Chipmunk2D\include;$(ProjectDir)\thirdparty\minilzo;$(ProjectDir)\thirdparty\opus\include</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile Include="thirdparty\enet\host.c" />
<ClCompile Include="thirdparty\enet\list.c" />
<ClCompile Include="thirdparty\enet\packet.c" />
@ -233,6 +235,7 @@
<ClCompile Include="thirdparty\minilzo\minilzo.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="hueshift.gen.h" />
<ClInclude Include="ipsettings.h" />
<ClInclude Include="thirdparty\minilzo\lzoconf.h" />
<ClInclude Include="thirdparty\minilzo\lzodefs.h" />

@ -191,6 +191,9 @@
<ClInclude Include="thirdparty\minilzo\minilzo.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="hueshift.gen.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="thirdparty\enet\enet_dll.cbp" />

@ -12,4 +12,9 @@
</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LocalDebuggerCommandArguments>
</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

@ -1,23 +1,3 @@
@echo off
@REM what all the compile flags mean: https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170
WHERE sokol-shdc.exe
IF %ERRORLEVEL% NEQ 0 ECHO ERROR download sokol-shdc from https://github.com/floooh/sokol-tools-bin/blob/master/bin/win32/sokol-shdc.exe and put it in this folder
@REM example of how to compile shaders: sokol-shdc.exe --input triangle.glsl --output triangle.gen.h --slang glsl330:hlsl5:metal_macos
setlocal enabledelayedexpansion enableextensions
pushd thirdparty\Chipmunk2D\src
set MUNKSRC=
for %%x in (*.c) do set MUNKSRC=!MUNKSRC! thirdparty\Chipmunk2D\src\%%x
popd
@REM /DENET_DEBUG=1^
cl /MP /Zi /FS /Fd"flight.pdb" /Fe"flight"^
/I"thirdparty" /I"thirdparty\minilzo" /I"thirdparty\enet\include" /I"thirdparty\Chipmunk2D\include\chipmunk" /I"thirdparty\Chipmunk2D\include"^
/DSERVER_ADDRESS="\"127.0.0.1\""^
main.c gamestate.c server.c debugdraw.c^
thirdparty\minilzo\minilzo.c^
thirdparty\enet\callbacks.c thirdparty\enet\compress.c thirdparty\enet\host.c thirdparty\enet\list.c thirdparty\enet\packet.c thirdparty\enet\peer.c thirdparty\enet\protocol.c thirdparty\enet\win32.c Ws2_32.lib winmm.lib^
%MUNKSRC%
call shadergen.bat
set compileopts=/Fe"flight_debug" /Zi /FS /Fd"flight.pdb" /DSERVER_ADDRESS="\"127.0.0.1\"" /DDEBUG_RENDERING
call build_msvc.bat

@ -1,3 +1,9 @@
#!/usr/bin/env bash
gcc -o flight_server -Wall -O2 -Ithirdparty -Ithirdparty/enet/include -Ithirdparty/minilzo -Ithirdparty/Chipmunk2D/include -Ithirdparty/Chipmunk2D/include/chipmunk server_main.c server.c debugdraw.c gamestate.c sokol_impl.c thirdparty/minilzo/minilzo.c thirdparty/enet/*.c thirdparty/Chipmunk2D/src/*.c -lm -lpthread
mkdir thirdparty/opus/build
cd thirdparty/opus/build
cmake ..
cmake --build .
cd -
gcc -o flight_server -Wall -O2 -Ithirdparty -Ithirdparty/opus/include -Ithirdparty/enet/include -Ithirdparty/minilzo -Ithirdparty/Chipmunk2D/include -Ithirdparty/Chipmunk2D/include/chipmunk server_main.c server.c debugdraw.c gamestate.c sokol_impl.c thirdparty/minilzo/minilzo.c thirdparty/enet/*.c thirdparty/Chipmunk2D/src/*.c -lm -lpthread -ldl thirdparty/opus/build/libopus.a

@ -0,0 +1,25 @@
@echo off
@REM what all the compile flags mean: https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170
set OPUSLIB=%~dp0\thirdparty\opus\win32\VS2015\x64\Release\opus.lib
WHERE %OPUSLIB%
IF %ERRORLEVEL% NEQ 0 ECHO ERROR Couldn't find %OPUSLIB% compile opus by opening the visual studio project in win32\VS2015 and building the release setting
setlocal enabledelayedexpansion enableextensions
pushd thirdparty\Chipmunk2D\src
set MUNKSRC=
for %%x in (*.c) do set MUNKSRC=!MUNKSRC! thirdparty\Chipmunk2D\src\%%x
popd
@REM /DENET_DEBUG=1^
cl /MP^
%compileopts%^
/I"thirdparty" /I"thirdparty\minilzo" /I"thirdparty\enet\include" /I"thirdparty\Chipmunk2D\include\chipmunk" /I"thirdparty\Chipmunk2D\include" /I"thirdparty\opus\include" /I"thirdparty\opus\src"^
main.c gamestate.c server.c debugdraw.c^
thirdparty\minilzo\minilzo.c^
thirdparty\enet\callbacks.c thirdparty\enet\compress.c thirdparty\enet\host.c thirdparty\enet\list.c thirdparty\enet\packet.c thirdparty\enet\peer.c thirdparty\enet\protocol.c thirdparty\enet\win32.c Ws2_32.lib winmm.lib^
%MUNKSRC%^
%OPUSLIB%

@ -0,0 +1,2 @@
set compileopts=/Fe"flight_release" /O2
call build_msvc.bat

@ -3,6 +3,7 @@ Description=Flight
[Service]
ExecStart=/root/flight/flight_server
WorkingDirectory=/root/flight
Restart=always
[Install]

File diff suppressed because it is too large Load Diff

@ -0,0 +1,53 @@
@module hueshift
@vs vs
in vec4 coord;
out vec2 texUV;
void main() {
gl_Position = vec4(coord.xy, 0.0, 1.0);
texUV = coord.zw;
}
@end
@fs fs
uniform sampler2D iChannel0;
uniform uniforms {
int is_colorless; // if greater than zero, no color
float target_hue;
};
in vec2 texUV;
out vec4 fragColor;
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec4 outColor = texture(iChannel0, texUV);
vec3 hsv = rgb2hsv(outColor.rgb);
if(is_colorless > 0)
{
hsv.y = 0.0f;
} else if(hsv.y > 0.5) {
hsv.x = target_hue;
}
fragColor = vec4(hsv2rgb(hsv), outColor.a);
}
@end
@program program vs fs

@ -1,5 +1,6 @@
#!/usr/bin/env bash
systemctl stop flight
./build_linux_server_release.sh
cp flight.service /etc/systemd/system/
systemctl enable flight

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

1112
main.c

File diff suppressed because it is too large Load Diff

@ -0,0 +1,3 @@
call build_release.bat
call update_server.bat
tar.exe -a -c -f releases\flight-nonumber.zip flight_release.exe loaded

@ -5,15 +5,71 @@
#include <stdio.h>
#include <inttypes.h> // int64 printing
#include <stdlib.h>
#include <string.h> // error string
#include <errno.h>
#include "minilzo.h"
#include "opus.h"
#ifdef __unix
#define fopen_s(pFile, filename, mode) ((*(pFile)) = fopen((filename), (mode))) == NULL
#endif
#define CONNECTED_PEERS(host, cur) \
for (ENetPeer *cur = host->peers; cur < host->peers + host->peerCount; cur++) \
if (cur->state == ENET_PEER_STATE_CONNECTED)
#ifdef PROFILING
#define SPALL_IMPLEMENTATION
#pragma warning(disable:4996) // spall uses fopen
#include "spall.h"
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define NOMINMAX
#include <Windows.h>
// This is slow, if you can use RDTSC and set the multiplier in SpallInit, you'll have far better timing accuracy
double get_time_in_micros() {
static double invfreq;
if (!invfreq) {
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
invfreq = 1000000.0 / frequency.QuadPart;
}
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return counter.QuadPart * invfreq;
}
static SpallProfile spall_ctx;
static SpallBuffer spall_buffer;
#define PROFILE_SCOPE(name) DeferLoop(SpallTraceBeginLenTidPid(&spall_ctx, &spall_buffer, name, sizeof(name) - 1, 0, 0, get_time_in_micros()), SpallTraceEndTidPid(&spall_ctx, &spall_buffer, 0, 0, get_time_in_micros()))
#else // PROFILING
#define PROFILE_SCOPE(name)
#endif
// started in a thread from host
void server(void* data)
void server(void* info_raw)
{
(void)data;
ServerThreadInfo* info = (ServerThreadInfo*)info_raw;
const char* world_save_name = info->world_save;
#ifdef PROFILING
#define BUFFER_SIZE (1 * 1024 * 1024)
spall_ctx = SpallInit("server.spall", 1);
unsigned char* buffer = malloc(BUFFER_SIZE);
spall_buffer = (SpallBuffer){
.length = BUFFER_SIZE,
.data = buffer,
};
SpallBufferInit(&spall_ctx, &spall_buffer);
#endif
stm_setup();
struct GameState gs = { 0 };
size_t entities_size = (sizeof(Entity) * MAX_ENTITIES);
@ -21,7 +77,56 @@ void server(void* data)
initialize(&gs, entity_data, entities_size);
Log("Allocated %zu bytes for entities\n", entities_size);
#define BOX_AT_TYPE(grid, pos, type) { Entity* box = new_entity(&gs); box_create(&gs, box, grid, pos); box->box_type = type; }
OpusBuffer* player_voip_buffers[MAX_PLAYERS] = { 0 };
for (int i = 0; i < MAX_PLAYERS; i++) player_voip_buffers[i] = calloc(1, sizeof * player_voip_buffers[i]);
OpusEncoder* player_encoders[MAX_PLAYERS] = { 0 };
OpusDecoder* player_decoders[MAX_PLAYERS] = { 0 };
// for (int i = 0; i < MAX_PLAYERS; i++)
//{
// int error = 0;
// player_encoders[i] = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
// if (error != OPUS_OK) Log("Failed to create encoder\n");
// player_decoders[i] = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
// if (error != OPUS_OK) Log("Failed to create decoder\n");
// }
if (world_save_name != NULL)
{
size_t read_game_data_buffer_size = entities_size;
char* read_game_data = malloc(read_game_data_buffer_size);
FILE* file = NULL;
fopen_s(&file, (const char*)world_save_name, "rb");
if (file == NULL)
{
Log("Could not read from data file %s: errno %d\n", (const char*)world_save_name, errno);
}
else
{
size_t actual_length = fread(read_game_data, sizeof(char), entities_size, file);
if (actual_length <= 1)
{
Log("Could only read %zu bytes, error: errno %d\n", actual_length, errno);
exit(-1);
}
Log("Read %zu bytes from save file\n", actual_length);
ServerToClient msg = (ServerToClient){
.cur_gs = &gs,
};
server_to_client_deserialize(&msg, read_game_data, actual_length, true);
fclose(file);
}
free(read_game_data);
}
#define BOX_AT_TYPE(grid, pos, type) \
{ \
Entity *box = new_entity(&gs); \
box_create(&gs, box, grid, pos); \
box->box_type = type; \
}
#define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece)
// one box policy
@ -43,12 +148,12 @@ void server(void* data)
entity_set_rotation(grid, PI / 1.7f);
cpBodySetVelocity(grid->body, cpv(-0.1, 0.0));
cpBodySetAngularVelocity(grid->body, 1.0f);
BOX_AT(grid,((V2) { 0 }));
BOX_AT(grid,((V2) { BOX_SIZE, 0 }));
BOX_AT(grid,((V2) { 2.0*BOX_SIZE, 0 }));
BOX_AT(grid,((V2) { 2.0*BOX_SIZE, BOX_SIZE }));
BOX_AT(grid,((V2) { 0.0*BOX_SIZE, -BOX_SIZE }));
BOX_AT(grid, ((V2){0}));
BOX_AT(grid, ((V2){BOX_SIZE, 0}));
BOX_AT(grid, ((V2){2.0 * BOX_SIZE, 0}));
BOX_AT(grid, ((V2){2.0 * BOX_SIZE, BOX_SIZE}));
BOX_AT(grid, ((V2){0.0 * BOX_SIZE, -BOX_SIZE}));
}
if (enet_initialize() != 0)
@ -58,7 +163,7 @@ void server(void* data)
}
ENetAddress address;
ENetHost* server;
ENetHost* enet_host;
int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS);
if (sethost != 0)
{
@ -66,12 +171,12 @@ void server(void* data)
}
/* Bind the server to port 1234. */
address.port = SERVER_PORT;
server = enet_host_create(&address /* the address to bind the server host to */,
32 /* allow up to 32 clients and/or outgoing connections */,
enet_host = enet_host_create(&address /* the address to bind the server host to */,
MAX_PLAYERS /* allow up to MAX_PLAYERS clients and/or outgoing connections */,
2 /* allow up to 2 channels to be used, 0 and 1 */,
0 /* assume any amount of incoming bandwidth */,
0 /* assume any amount of outgoing bandwidth */);
if (server == NULL)
if (enet_host == NULL)
{
fprintf(stderr,
"An error occurred while trying to create an ENet server host.\n");
@ -81,14 +186,25 @@ void server(void* data)
Log("Serving on port %d...\n", SERVER_PORT);
ENetEvent event;
uint64_t last_processed_time = stm_now();
uint64_t last_saved_world_time = stm_now();
uint64_t last_sent_audio_time = stm_now();
float audio_time_to_send = 0.0f;
float total_time = 0.0f;
size_t player_to_latest_id_processed[MAX_PLAYERS] = { 0 };
char* world_save_buffer = malloc(entities_size);
while (true)
{
ma_mutex_lock(&info->info_mutex);
if (info->should_quit) {
ma_mutex_unlock(&info->info_mutex);
break;
}
ma_mutex_unlock(&info->info_mutex);
// @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
while (true)
{
int ret = enet_host_service(server, &event, 0);
int ret = enet_host_service(enet_host, &event, 0);
if (ret == 0)
break;
if (ret < 0)
@ -125,144 +241,313 @@ void server(void* data)
gs.players[player_slot] = (struct Player){ 0 };
gs.players[player_slot].connected = true;
player_to_latest_id_processed[player_slot] = 0;
int error;
player_encoders[player_slot] = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
if (error != OPUS_OK)
Log("Failed to create encoder: %d\n", error);
player_decoders[player_slot] = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
if (error != OPUS_OK)
Log("Failed to create decoder: %d\n", error);
#ifdef UNLOCK_ALL
gs.players[player_slot].unlocked_bombs = true;
#endif
gs.players[player_slot].squad = SquadPurple;
}
break;
}
break;
case ENET_EVENT_TYPE_RECEIVE:
{
//Log("A packet of length %zu was received on channel %u.\n",
// event.packet->dataLength,
//event.channelID);
size_t length = event.packet->dataLength;
if (length != sizeof(struct ClientToServer))
// Log("A packet of length %zu was received on channel %u.\n",
// event.packet->dataLength,
// event.channelID);
if (event.packet->dataLength == 0)
{
Log("Length did not match up...\n");
Log("Wtf an empty packet from enet?\n");
}
else
{
struct ClientToServer received = { 0 };
memcpy(&received, event.packet->data, length);
else {
int64_t player_slot = (int64_t)event.peer->data;
size_t latest_id = player_to_latest_id_processed[player_slot];
if (received.inputs[0].id > latest_id)
size_t length = event.packet->dataLength;
OpusBuffer throwaway_buffer = { 0 };
OpusBuffer* buffer_to_fill = player_voip_buffers[player_slot];
if (get_entity(&gs, gs.players[player_slot].entity) == NULL) buffer_to_fill = &throwaway_buffer;
struct ClientToServer received = { .mic_data = buffer_to_fill };
if (!client_to_server_deserialize(&gs, &received, event.packet->data, event.packet->dataLength))
{
for (int i = INPUT_BUFFER - 1; i >= 0; i--)
Log("Bad packet from client %d\n", (int)player_slot);
}
else
{
size_t latest_id = player_to_latest_id_processed[player_slot];
if (received.inputs[0].id > latest_id)
{
if (received.inputs[i].tick == 0) // empty input
continue;
if (received.inputs[i].id <= latest_id)
continue; // don't reprocess inputs already processed
InputFrame cur_input = received.inputs[i];
gs.players[player_slot].input.movement = cur_input.movement;
gs.players[player_slot].input.hand_pos = cur_input.hand_pos;
// for these "event" inputs, only modify the current input if the event is true.
// while processing the gamestate, will mark it as false once processed. This
// prevents setting the event input to false before it's been processed.
if (cur_input.seat_action)
{
gs.players[player_slot].input.seat_action = cur_input.seat_action;
gs.players[player_slot].input.grid_hand_pos_local_to = cur_input.grid_hand_pos_local_to;
}
if (cur_input.dobuild)
for (int i = INPUT_BUFFER - 1; i >= 0; i--)
{
gs.players[player_slot].input.grid_hand_pos_local_to = cur_input.grid_hand_pos_local_to;
gs.players[player_slot].input.dobuild = cur_input.dobuild;
gs.players[player_slot].input.build_type = cur_input.build_type;
gs.players[player_slot].input.build_rotation = cur_input.build_rotation;
if (received.inputs[i].tick == 0) // empty input
continue;
if (received.inputs[i].id <= latest_id)
continue; // don't reprocess inputs already processed
InputFrame cur_input = received.inputs[i];
gs.players[player_slot].input.movement = cur_input.movement;
gs.players[player_slot].input.hand_pos = cur_input.hand_pos;
// for these "event" inputs, only modify the current input if the event is true.
// while processing the gamestate, will mark it as false once processed. This
// prevents setting the event input to false before it's been processed.
if (cur_input.take_over_squad >= 0)
gs.players[player_slot].input.take_over_squad = cur_input.take_over_squad;
if (cur_input.seat_action)
{
gs.players[player_slot].input.seat_action = cur_input.seat_action;
gs.players[player_slot].input.grid_hand_pos_local_to = cur_input.grid_hand_pos_local_to;
}
if (cur_input.dobuild)
{
gs.players[player_slot].input.grid_hand_pos_local_to = cur_input.grid_hand_pos_local_to;
gs.players[player_slot].input.dobuild = cur_input.dobuild;
gs.players[player_slot].input.build_type = cur_input.build_type;
gs.players[player_slot].input.build_rotation = cur_input.build_rotation;
}
}
player_to_latest_id_processed[player_slot] = received.inputs[0].id;
}
player_to_latest_id_processed[player_slot] = received.inputs[0].id;
}
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet);
break;
}
break;
case ENET_EVENT_TYPE_DISCONNECT:
{
int player_index = (int)(int64_t)event.peer->data;
Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
Entity* player_body = get_entity(&gs, gs.players[player_index].entity);
if(player_body != NULL)
if (player_body != NULL)
{
entity_destroy(&gs, player_body);
}
opus_encoder_destroy(player_encoders[player_index]);
player_encoders[player_index] = NULL;
opus_decoder_destroy(player_decoders[player_index]);
player_decoders[player_index] = NULL;
gs.players[player_index].connected = false;
// box_destroy(&gs.players[player_index].box);
clear_buffer(player_voip_buffers[player_index]);
event.peer->data = NULL;
break;
}
break;
case ENET_EVENT_TYPE_NONE:
{
}
break;
}
}
}
}
total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time));
last_processed_time = stm_now();
// @Robost @BeforeShip if can't process quick enough will be stuck being lagged behind, think of a solution for this...
const float max_time = 5.0f * TIMESTEP;
if (total_time > max_time)
{
Log("Abnormally large total time %f, clamping\n", total_time);
total_time = max_time;
}
bool processed = false;
while (total_time > TIMESTEP)
PROFILE_SCOPE("World Processing")
{
processed = true;
process(&gs, TIMESTEP);
total_time -= TIMESTEP;
while (total_time > TIMESTEP)
{
processed = true;
process(&gs, TIMESTEP);
total_time -= TIMESTEP;
}
}
if (world_save_name != NULL && (stm_sec(stm_diff(stm_now(), last_saved_world_time))) > TIME_BETWEEN_WORLD_SAVE)
{
PROFILE_SCOPE("Save World") {
last_saved_world_time = stm_now();
ServerToClient msg = (ServerToClient){
.cur_gs = &gs,
};
size_t out_len = 0;
if (server_to_client_serialize(&msg, world_save_buffer, &out_len, entities_size, NULL, true))
{
FILE* save_file = NULL;
fopen_s(&save_file, (const char*)world_save_name, "wb");
if (save_file == NULL)
{
Log("Could not open save file: errno %d\n", errno);
}
else
{
size_t data_written = fwrite(world_save_buffer, sizeof(*world_save_buffer), out_len, save_file);
if (data_written != out_len)
{
Log("Failed to save world data, wanted to write %zu but could only write %zu\n", out_len, data_written);
}
else
{
Log("Saved game world to %s\n", (const char*)world_save_name);
}
fclose(save_file);
}
}
else
{
Log("URGENT: FAILED TO SAVE WORLD FILE!\n");
}
}
}
if (processed)
{
static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = { 0 };
for (int i = 0; i < server->peerCount; i++)
{
if (server->peers[i].state != ENET_PEER_STATE_CONNECTED)
PROFILE_SCOPE("send_data") {
static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = { 0 };
audio_time_to_send += (float)stm_sec(stm_diff(stm_now(), last_sent_audio_time));
last_sent_audio_time = stm_now();
int num_audio_packets = (int)floor(1.0f / (VOIP_TIME_PER_PACKET / audio_time_to_send));
#define MAX_AUDIO_PACKETS_TO_SEND 6
if (num_audio_packets > MAX_AUDIO_PACKETS_TO_SEND)
{
continue;
Log("Wants %d, this is too many packets. Greater than the maximum %d\n", num_audio_packets, MAX_AUDIO_PACKETS_TO_SEND);
num_audio_packets = MAX_AUDIO_PACKETS_TO_SEND;
}
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
char* bytes_buffer = malloc(sizeof *bytes_buffer * MAX_BYTES_SIZE);
char* compressed_buffer = malloc(sizeof * compressed_buffer * MAX_BYTES_SIZE);
struct ServerToClient to_send;
to_send.cur_gs = &gs;
to_send.your_player = (int)(int64_t)server->peers[i].data;
size_t len = 0;
into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE);
opus_int16 decoded_audio_packets[MAX_PLAYERS][MAX_AUDIO_PACKETS_TO_SEND][VOIP_EXPECTED_FRAME_COUNT] = { 0 };
audio_time_to_send -= num_audio_packets * VOIP_TIME_PER_PACKET;
size_t compressed_len = 0;
lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void*)lzo_working_mem);
// decode what everybody said
CONNECTED_PEERS(enet_host, cur)
{
int this_player_index = (int)(int64_t)cur->data;
for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
{
opus_int16* to_dump_to = decoded_audio_packets[this_player_index][packet_i];
OpusPacket* cur_packet = pop_packet(player_voip_buffers[this_player_index]);
if (cur_packet == NULL)
opus_decode(player_decoders[this_player_index], NULL, 0, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
else
opus_decode(player_decoders[this_player_index], cur_packet->data, cur_packet->length, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
}
}
// send gamestate to each player
CONNECTED_PEERS(enet_host, cur)
{
int this_player_index = (int)(int64_t)cur->data;
Entity* this_player_entity = get_entity(&gs, gs.players[this_player_index].entity);
if (this_player_entity == NULL)
continue;
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
char* bytes_buffer = malloc(sizeof * bytes_buffer * MAX_SERVER_TO_CLIENT);
char* compressed_buffer = malloc(sizeof * compressed_buffer * MAX_SERVER_TO_CLIENT);
// mix audio to be sent
OpusBuffer* buffer_to_play = calloc(1, sizeof * buffer_to_play); // @Robust no malloc, also in all other places no malloc
{
for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
{
opus_int16 to_send_to_cur[VOIP_EXPECTED_FRAME_COUNT] = { 0 }; // mix what other players said into this buffer
CONNECTED_PEERS(enet_host, other_player)
{
if (other_player != cur)
{
int other_player_index = (int)(int64_t)other_player->data;
Entity* other_player_entity = get_entity(&gs, gs.players[other_player_index].entity);
if (other_player_entity != NULL)
{
float dist = V2dist(entity_pos(this_player_entity), entity_pos(other_player_entity));
float volume = lerp(1.0f, 0.0f, clamp01(dist / VOIP_DISTANCE_WHEN_CANT_HEAR));
if (volume > 0.01f)
{
for (int frame_i = 0; frame_i < VOIP_EXPECTED_FRAME_COUNT; frame_i++)
{
to_send_to_cur[frame_i] += (opus_int16)((float)decoded_audio_packets[other_player_index][packet_i][frame_i]*volume);
}
}
}
}
}
OpusPacket* this_packet = push_packet(buffer_to_play);
opus_int32 ret = opus_encode(player_encoders[this_player_index], to_send_to_cur, VOIP_EXPECTED_FRAME_COUNT, this_packet->data, VOIP_PACKET_MAX_SIZE);
if (ret < 0)
{
Log("Failed to encode audio packet for player %d: opus error code %d\n", this_player_index, ret);
}
else {
this_packet->length = ret;
}
}
}
ServerToClient to_send = (ServerToClient){
.cur_gs = &gs,
.your_player = this_player_index,
.playback_buffer = buffer_to_play,
};
size_t len = 0;
if (server_to_client_serialize(&to_send, bytes_buffer, &len, MAX_SERVER_TO_CLIENT, this_player_entity, false))
{
if (len > MAX_SERVER_TO_CLIENT - 8)
{
Log("Too much data quitting!\n");
exit(-1);
}
size_t compressed_len = 0;
lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void*)lzo_working_mem);
#ifdef LOG_GAMESTATE_SIZE
Log("Size of gamestate packet before comrpession: %zu | After: %zu\n", len, compressed_len);
Log("Size of gamestate packet before comrpession: %zu | After: %zu\n", len, compressed_len);
#endif
//ENetPacket* gamestate_packet = enet_packet_create((void*)bytes_buffer, len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
ENetPacket* gamestate_packet = enet_packet_create((void*)compressed_buffer, compressed_len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
// @Robust error check this
int err = enet_peer_send(&server->peers[i], 0, gamestate_packet);
if (err < 0)
{
Log("Enet failed to send packet error %d\n", err);
enet_packet_destroy(gamestate_packet);
ENetPacket* gamestate_packet = enet_packet_create((void*)compressed_buffer, compressed_len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
int err = enet_peer_send(cur, 0, gamestate_packet);
if (err < 0)
{
Log("Enet failed to send packet error %d\n", err);
enet_packet_destroy(gamestate_packet);
}
}
else
{
Log("Failed to serialize data for client %d\n", this_player_index);
}
free(buffer_to_play);
free(bytes_buffer);
free(compressed_buffer);
}
free(bytes_buffer);
free(compressed_buffer);
}
}
}
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (player_encoders[i] != NULL)
opus_encoder_destroy(player_encoders[i]);
if (player_decoders[i] != NULL)
opus_decoder_destroy(player_decoders[i]);
}
for (int i = 0; i < MAX_PLAYERS; i++) free(player_voip_buffers[i]);
free(world_save_buffer);
destroy(&gs);
free(entity_data);
enet_host_destroy(server);
enet_host_destroy(enet_host);
enet_deinitialize();
printf("Cleanup\n");
#ifdef PROFILING
SpallBufferQuit(&spall_ctx, &spall_buffer);
SpallQuit(&spall_ctx);
#endif
}

Binary file not shown.

@ -1,10 +1,32 @@
#include "types.h"
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#define SOKOL_IMPL
#include "sokol_time.h"
#include <signal.h>
#include <unistd.h>
ServerThreadInfo server_info = {
.world_save = "world.bin",
};
void term(int signum)
{
server_info.should_quit = true;
}
int main(int argc, char **argv)
{
server(0);
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = term;
sigaction(SIGTERM, &action, NULL);
stm_setup();
ma_mutex_init(&server_info.info_mutex);
server(&server_info);
ma_mutex_uninit(&server_info.info_mutex);
return 0;
}

@ -0,0 +1,6 @@
WHERE sokol-shdc.exe
IF %ERRORLEVEL% NEQ 0 ECHO ERROR download sokol-shdc from https://github.com/floooh/sokol-tools-bin/blob/master/bin/win32/sokol-shdc.exe and put it in this folder
@REM example of how to compile shaders: sokol-shdc.exe --input triangle.glsl --output triangle.gen.h --slang glsl330:hlsl5:metal_macos
sokol-shdc.exe --format sokol --input hueshift.glsl --output hueshift.gen.h --slang glsl330:hlsl5:metal_macos

91289
thirdparty/miniaudio.h vendored

File diff suppressed because it is too large Load Diff

1
thirdparty/opus vendored

@ -0,0 +1 @@
Subproject commit bce1f392353d72d77d543bb2069a044ae1045e9d

429
thirdparty/spall.h vendored

@ -0,0 +1,429 @@
// SPDX-FileCopyrightText: © 2022 Phillip Trudeau-Tavara <pmttavara@protonmail.com>
// SPDX-License-Identifier: 0BSD
/*
TODO: Core API:
- Completely contextless; you pass in params to begin()/end(), get a packed begin/end struct
- Simple, handmade, user has full control and full responsibility
TODO: Optional Helper APIs:
- Buffered-writing API
- Caller allocates and stores a buffer for multiple events
- begin()/end() writes chunks to the buffer
- Function invokes a callback when the buffer is full and needs flushing
- Can a callback be avoided? The function indicates when the buffer must be flushed?
- Compression API: would require a mutexed lockable context (yuck...)
- Either using a ZIP library, a name cache + TIDPID cache, or both (but ZIP is likely more than enough!!!)
- begin()/end() writes compressed chunks to a caller-determined destination
- The destination can be the buffered-writing API or a custom user destination
- Ultimately need to take a lock with some granularity... can that be the caller's responsibility?
- fopen()/fwrite() API: requires a context (no mutex needed, since fwrite() takes a lock)
- begin()/end() writes chunks to a FILE*
- before writing them to disk, the chunks can optionally be sent through the compression API
- is this opt-in or opt-out?
- the write to disk can optionally use the buffered writing API
*/
#ifndef SPALL_H
#define SPALL_H
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define SPALL_MIN(a, b) (((a) < (b)) ? (a) : (b))
#pragma pack(push, 1)
typedef struct SpallHeader {
uint64_t magic_header; // = 0x0BADF00D
uint64_t version; // = 1
double timestamp_unit;
uint64_t must_be_0;
} SpallHeader;
enum {
SpallEventType_Invalid = 0,
SpallEventType_Custom_Data = 1, // Basic readers can skip this.
SpallEventType_StreamOver = 2,
SpallEventType_Begin = 3,
SpallEventType_End = 4,
SpallEventType_Instant = 5,
SpallEventType_Overwrite_Timestamp = 6, // Retroactively change timestamp units - useful for incrementally improving RDTSC frequency.
};
typedef struct SpallBeginEvent {
uint8_t type; // = SpallEventType_Begin
uint8_t category;
uint32_t pid;
uint32_t tid;
double when;
uint8_t name_length;
uint8_t args_length;
// char name[1];
// char args[1];
} SpallBeginEvent;
typedef struct SpallBeginEventMax {
SpallBeginEvent event;
char name_bytes[255];
char args_bytes[255];
} SpallBeginEventMax;
typedef struct SpallEndEvent {
uint8_t type; // = SpallEventType_End
uint32_t pid;
uint32_t tid;
double when;
} SpallEndEvent;
#pragma pack(pop)
typedef struct SpallProfile {
double timestamp_unit;
bool (*write)(struct SpallProfile *self, const void *data, size_t length);
bool (*flush)(struct SpallProfile *self);
void (*close)(struct SpallProfile *self);
union {
FILE *file;
void *userdata;
};
} SpallProfile;
// Important!: If you are writing Begin/End events, then do NOT write
// events for the same PID + TID pair on different buffers!!!
typedef struct SpallBuffer {
void *data;
size_t length;
// Internal data - don't assign this
size_t head;
SpallProfile *ctx;
} SpallBuffer;
#ifdef __cplusplus
extern "C" {
#endif
// Profile context
SpallProfile SpallInit (const char *filename, double timestamp_unit);
void SpallQuit (SpallProfile *ctx);
bool SpallFlush(SpallProfile *ctx);
// Buffer API
extern SpallBuffer SpallSingleThreadedBuffer;
bool SpallBufferInit (SpallProfile *ctx, SpallBuffer *wb);
bool SpallBufferQuit (SpallProfile *ctx, SpallBuffer *wb);
bool SpallBufferFlush(SpallProfile *ctx, SpallBuffer *wb);
// Begin events
bool SpallTraceBeginLen (SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when);
bool SpallTraceBeginLenTid (SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, uint32_t tid, double when);
bool SpallTraceBeginLenTidPid(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, uint32_t tid, uint32_t pid, double when);
// End events
bool SpallTraceEnd (SpallProfile *ctx, SpallBuffer *wb, double when);
bool SpallTraceEndTid (SpallProfile *ctx, SpallBuffer *wb, uint32_t tid, double when);
bool SpallTraceEndTidPid(SpallProfile *ctx, SpallBuffer *wb, uint32_t tid, uint32_t pid, double when);
#ifdef __cplusplus
}
#endif
#endif // SPALL_H
#ifdef SPALL_IMPLEMENTATION
#ifndef SPALL_IMPLEMENTED
#define SPALL_IMPLEMENTED
#ifdef __cplusplus
extern "C" {
#endif
#if defined(SPALL_BUFFER_PROFILING) && !defined(SPALL_BUFFER_PROFILING_GET_TIME)
#error "You must #define SPALL_BUFFER_PROFILING_GET_TIME() to profile buffer flushes."
#endif
#ifdef SPALL_BUFFER_PROFILING
static void Spall__BufferProfile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len) {
// precon: ctx
// precon: ctx->write
char temp_buffer_data[2048];
SpallBuffer temp_buffer = { temp_buffer_data, sizeof(temp_buffer_data) };
if (!SpallTraceBeginLenTidPid(ctx, &temp_buffer, name, sizeof(name) - 1, (uint32_t)(uintptr_t)wb->data, 4222222222, spall_time_begin)) return;
if (!SpallTraceEndTidPid(ctx, &temp_buffer, (uint32_t)(uintptr_t)wb->data, 4222222222, spall_time_end)) return;
if (ctx->write) ctx->write(ctx, temp_buffer_data, temp_buffer.head);
}
#define SPALL_BUFFER_PROFILE_BEGIN() double spall_time_begin = (SPALL_BUFFER_PROFILING_GET_TIME())
// Don't call this with anything other than a string literal
#define SPALL_BUFFER_PROFILE_END(name) Spall__BufferProfile(ctx, wb, spall_time_begin, (SPALL_BUFFER_PROFILING_GET_TIME()), "" name "", sizeof("" name "") - 1)
#else
#define SPALL_BUFFER_PROFILE_BEGIN()
#define SPALL_BUFFER_PROFILE_END(name)
#endif
extern char SpallSingleThreadedBufferData[];
char SpallSingleThreadedBufferData[1 << 16];
SpallBuffer SpallSingleThreadedBuffer = {SpallSingleThreadedBufferData, sizeof(SpallSingleThreadedBufferData)};
static bool Spall__FileWrite(SpallProfile *ctx, const void *p, size_t n) {
if (!ctx->file) return false;
#ifdef SPALL_DEBUG
if (feof(ctx->file)) return false;
if (ferror(ctx->file)) return false;
#endif
if (fwrite(p, n, 1, ctx->file) != 1) return false;
return true;
}
static bool Spall__FileFlush(SpallProfile *ctx) {
if (!ctx->file) return false;
if (fflush(ctx->file)) return false;
return true;
}
static void Spall__FileClose(SpallProfile *ctx) {
if (!ctx->file) return;
#ifdef SPALL_JSON
#ifdef SPALL_DEBUG
if (!feof(ctx->file) && !ferror(ctx->file))
#endif
{
fseek(ctx->file, -2, SEEK_CUR); // seek back to overwrite trailing comma
fwrite("\n]}\n", sizeof("\n]}\n") - 1, 1, ctx->file);
}
#endif
fflush(ctx->file);
fclose(ctx->file);
ctx->file = NULL;
}
static bool Spall__BufferFlush(SpallProfile *ctx, SpallBuffer *wb) {
// precon: wb
// precon: wb->data
// precon: wb->head <= wb->length
// precon: !ctx || ctx->write
#ifdef SPALL_DEBUG
if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL)
#endif
if (wb->head && ctx) {
SPALL_BUFFER_PROFILE_BEGIN();
if (!ctx->write || !ctx->write(ctx, wb->data, wb->head)) return false;
SPALL_BUFFER_PROFILE_END("Buffer Flush");
}
wb->head = 0;
return true;
}
static bool Spall__BufferWrite(SpallProfile *ctx, SpallBuffer *wb, void *p, size_t n) {
// precon: !wb || wb->head < wb->length
// precon: !ctx || ctx->write
if (!wb) return ctx->write && ctx->write(ctx, p, n);
#ifdef SPALL_DEBUG
if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL)
#endif
if (wb->head + n > wb->length && !Spall__BufferFlush(ctx, wb)) return false;
if (n > wb->length) {
SPALL_BUFFER_PROFILE_BEGIN();
if (!ctx->write || !ctx->write(ctx, p, n)) return false;
SPALL_BUFFER_PROFILE_END("Unbuffered Write");
return true;
}
memcpy((char *)wb->data + wb->head, p, n);
wb->head += n;
return true;
}
bool SpallBufferFlush(SpallProfile *ctx, SpallBuffer *wb) {
#ifdef SPALL_DEBUG
if (!wb) return false;
if (!wb->data) return false;
#endif
if (!Spall__BufferFlush(ctx, wb)) return false;
return true;
}
bool SpallBufferInit(SpallProfile *ctx, SpallBuffer *wb) {
if (!SpallBufferFlush(NULL, wb)) return false;
wb->ctx = ctx;
return true;
}
bool SpallBufferQuit(SpallProfile *ctx, SpallBuffer *wb) {
if (!SpallBufferFlush(ctx, wb)) return false;
wb->ctx = NULL;
return true;
}
bool SpallBufferAbort(SpallBuffer *wb) {
if (!wb) return false;
wb->ctx = NULL;
if (!Spall__BufferFlush(NULL, wb)) return false;
return true;
}
static SpallProfile Spall__Init(const char *filename, double timestamp_unit) {
SpallProfile ctx;
memset(&ctx, 0, sizeof(ctx));
if (timestamp_unit < 0) return ctx;
if (!filename) return ctx;
ctx.file = fopen(filename, "wb"); // TODO: handle utf8 and long paths on windows
ctx.write = Spall__FileWrite;
ctx.flush = Spall__FileFlush;
ctx.close = Spall__FileClose;
if (!ctx.file) { SpallQuit(&ctx); return ctx; }
ctx.timestamp_unit = timestamp_unit;
#ifdef SPALL_JSON
if (!ctx.write(&ctx, "{\"traceEvents\":[\n", sizeof("{\"traceEvents\":[\n") - 1)) { SpallQuit(&ctx); return ctx; }
#else
SpallHeader header;
header.magic_header = 0x0BADF00D;
header.version = 1;
header.timestamp_unit = timestamp_unit;
header.must_be_0 = 0;
if (!ctx.write(&ctx, &header, sizeof(header))) { SpallQuit(&ctx); return ctx; }
#endif
return ctx;
}
SpallProfile SpallInit (const char *filename, double timestamp_unit) { return Spall__Init(filename, timestamp_unit); }
void SpallQuit(SpallProfile *ctx) {
if (!ctx) return;
if (ctx->close) ctx->close(ctx);
memset(ctx, 0, sizeof(*ctx));
}
bool SpallFlush(SpallProfile *ctx) {
#ifdef SPALL_DEBUG
if (!ctx) return false;
if (!ctx->file) return false;
#endif
if (!ctx->flush || !ctx->flush(ctx)) return false;
return true;
}
bool SpallTraceBeginLenArgsTidPid(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, const char *args, signed long args_len, uint32_t tid, uint32_t pid, double when) {
SpallBeginEventMax ev;
#ifdef SPALL_DEBUG
if (!ctx) return false;
if (!name) return false;
if (!ctx->file) return false;
if (name_len <= 0) return false;
#endif
name_len = SPALL_MIN(name_len, 255); // will be interpreted as truncated in the app (?)
args_len = SPALL_MIN(args_len, 255); // will be interpreted as truncated in the app (?)
char *args_bytes = ev.name_bytes + name_len;
ev.event.type = SpallEventType_Begin;
ev.event.category = 0;
ev.event.pid = pid;
ev.event.tid = tid;
ev.event.when = when;
ev.event.name_length = (uint8_t)name_len;
ev.event.args_length = (uint8_t)args_len;
memcpy(ev.name_bytes, name, (uint8_t)name_len);
memcpy(args_bytes, args, (uint8_t)args_len);
#ifdef SPALL_JSON
char buf[1024];
int buf_len = snprintf(buf, sizeof(buf),
"{\"args\":\"%.*s\",\"name\":\"%.*s\",\"ph\":\"B\",\"pid\":%u,\"tid\":%u,\"ts\":%f},\n",
(int)ev.event.args_length, args_bytes,
(int)ev.event.name_length, ev.name_bytes,
ev.event.pid,
ev.event.tid,
ev.event.when * ctx->timestamp_unit);
if (buf_len <= 0) return false;
if (buf_len >= sizeof(buf)) return false;
if (!Spall__BufferWrite(ctx, wb, buf, buf_len)) return false;
#else
if (!Spall__BufferWrite(ctx, wb, &ev, sizeof(SpallBeginEvent) + (uint8_t)name_len + (uint8_t)args_len)) return false;
#endif
return true;
}
bool SpallTraceBeginLenTidPid(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, uint32_t tid, uint32_t pid, double when) { return SpallTraceBeginLenArgsTidPid(ctx, wb, name, name_len, "", 0, tid, 0, when); }
bool SpallTraceBeginLenTid(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, uint32_t tid, double when) { return SpallTraceBeginLenArgsTidPid(ctx, wb, name, name_len, "", 0, tid, 0, when); }
bool SpallTraceBeginLen (SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when) { return SpallTraceBeginLenArgsTidPid(ctx, wb, name, name_len, "", 0, 0, 0, when); }
bool SpallTraceEndTidPid(SpallProfile *ctx, SpallBuffer *wb, uint32_t tid, uint32_t pid, double when) {
SpallEndEvent ev;
#ifdef SPALL_DEBUG
if (!ctx) return false;
if (!ctx->file) return false;
#endif
ev.type = SpallEventType_End;
ev.pid = pid;
ev.tid = tid;
ev.when = when;
#ifdef SPALL_JSON
char buf[512];
int buf_len = snprintf(buf, sizeof(buf),
"{\"ph\":\"E\",\"pid\":%u,\"tid\":%u,\"ts\":%f},\n",
ev.pid,
ev.tid,
ev.when * ctx->timestamp_unit);
if (buf_len <= 0) return false;
if (buf_len >= sizeof(buf)) return false;
if (!Spall__BufferWrite(ctx, wb, buf, buf_len)) return false;
#else
if (!Spall__BufferWrite(ctx, wb, &ev, sizeof(ev))) return false;
#endif
return true;
}
bool SpallTraceEndTid(SpallProfile *ctx, SpallBuffer *wb, uint32_t tid, double when) { return SpallTraceEndTidPid(ctx, wb, tid, 0, when); }
bool SpallTraceEnd (SpallProfile *ctx, SpallBuffer *wb, double when) { return SpallTraceEndTidPid(ctx, wb, 0, 0, when); }
#ifdef __cplusplus
}
#endif
#endif // SPALL_IMPLEMENTED
#endif // SPALL_IMPLEMENTATION
/*
Zero-Clause BSD (0BSD)
Copyright (c) 2022, Phillip Trudeau-Tavara
All rights reserved.
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

@ -13,7 +13,7 @@ Sleep, 20
WinActivate, flightbuild
If WinActive("flightbuild")
{
Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && flight.exe{Enter}
Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && flight_debug.exe{Enter}
}
return
@ -26,6 +26,6 @@ WinKill, Flight
WinActivate, flightbuild
If WinActive("flightbuild")
{
Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && START /B flight.exe && flight.exe --host{Enter}
Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && START /B flight_debug.exe && flight_debug.exe --host{Enter}
}
return

@ -1,7 +1,7 @@
#pragma once
#define MAX_PLAYERS 8
#define MAX_ENTITIES 1024*2
#define MAX_PLAYERS 16
#define MAX_ENTITIES 1024*25
#define BOX_SIZE 0.25f
#define PLAYER_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE})
#define PLAYER_MASS 0.5f
@ -15,10 +15,11 @@
#define COLLISION_DAMAGE_SCALING 0.15f
#define THRUSTER_FORCE 12.0f
#define THRUSTER_ENERGY_USED_PER_SECOND 0.005f
#define VISION_RADIUS 16.0f
#define MAX_BYTES_SIZE 1024 * 128 // maximum size of serialized gamestate buffer
#define VISION_RADIUS 12.0f
#define MAX_SERVER_TO_CLIENT 1024 * 512 // maximum size of serialized gamestate buffer
#define MAX_CLIENT_TO_SERVER 1024*10 // maximum size of serialized inputs and mic data
#define SUN_RADIUS 10.0f
#define INSTANT_DEATH_DISTANCE_FROM_SUN 500.0f
#define INSTANT_DEATH_DISTANCE_FROM_SUN 2000.0f
#define SUN_POS ((V2){50.0f,0.0f})
#define SUN_GRAVITY_STRENGTH (9.0e2f)
#define SOLAR_ENERGY_PER_SECOND 0.02f
@ -31,6 +32,15 @@
#define EXPLOSION_RADIUS 1.0f
#define EXPLOSION_DAMAGE_THRESHOLD 0.2f // how much damage until it explodes
#define GOLD_UNLOCK_RADIUS 1.0f
#define TIME_BETWEEN_WORLD_SAVE 30.0f
// VOIP
#define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2
#define VOIP_EXPECTED_FRAME_COUNT 480
#define VOIP_SAMPLE_RATE 48000
#define VOIP_TIME_PER_PACKET 1.0f / ((float)(VOIP_SAMPLE_RATE/VOIP_EXPECTED_FRAME_COUNT)) // in seconds
#define VOIP_PACKET_MAX_SIZE 4000
#define VOIP_DISTANCE_WHEN_CANT_HEAR (VISION_RADIUS*0.8f)
#define TIMESTEP (1.0f / 60.0f) // not required to simulate at this, but this defines what tick the game is on
#define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
@ -40,6 +50,8 @@
// must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1"
#include "ipsettings.h" // don't leak IP!
#include "miniaudio.h" // @Robust BAD. using miniaudio mutex construct for server thread synchronization. AWFUL!
// @Robust remove this include somehow, needed for sqrt and cos
#include <math.h>
#include <stdint.h> // tick is unsigned integer
@ -69,6 +81,10 @@ typedef void cpShape;
#include <stdbool.h>
#ifndef OPUS_TYPES_H
typedef int opus_int32;
#endif
#ifndef _STDBOOL
#define bool _Bool
@ -80,9 +96,9 @@ typedef void cpShape;
typedef sgp_vec2 V2;
typedef sgp_point P2;
#define Log(...) \
#define Log(...){ \
fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \
fprintf(stdout, __VA_ARGS__)
fprintf(stdout, __VA_ARGS__);}
enum BoxType
{
@ -105,10 +121,20 @@ enum CompassRotation
RotationLast,
};
enum Squad
{
SquadNone,
SquadRed,
SquadGreen,
SquadBlue,
SquadPurple,
SquadLast,
};
// when generation is 0, invalid ID
typedef struct EntityID
{
unsigned int generation; // if 0 then EntityID points to nothing, generation >= 1
unsigned int generation; // VERY IMPORTANT if 0 then EntityID points to nothing, generation >= 1
unsigned int index; // index into the entity arena
} EntityID;
@ -125,6 +151,8 @@ typedef struct InputFrame
size_t id; // each input has unique, incrementing, I.D, so server doesn't double process inputs. Inputs to server should be ordered from 0-max like biggest id-smallest. This is done so if packet loss server still processes input
V2 movement;
int take_over_squad; // -1 means not taking over any squad
bool seat_action;
EntityID seat_to_inhabit;
V2 hand_pos; // local to player transationally but not rotationally unless field below is not null, then it's local to that grid
@ -142,6 +170,8 @@ typedef struct Entity
EntityID next_free_entity;
unsigned int generation;
bool no_save_to_disk; // stuff generated later on, like player's bodies or space stations that respawn.
float damage; // used by box and player
cpBody* body; // used by grid, player, and box
cpShape* shape; // must be a box so shape_size can be set appropriately, and serialized
@ -154,6 +184,7 @@ typedef struct Entity
// player
bool is_player;
enum Squad presenting_squad;
EntityID currently_inside_of_box;
float goldness; // how much the player is a winner
@ -170,6 +201,7 @@ typedef struct Entity
// boxes
bool is_box;
bool always_visible; // always serialized to the player
enum BoxType box_type;
bool is_explosion_unlock;
EntityID next_box;
@ -187,7 +219,9 @@ typedef struct Player
{
bool connected;
bool unlocked_bombs;
enum Squad squad;
EntityID entity;
EntityID last_used_medbay;
InputFrame input;
} Player;
// gotta update the serialization functions when this changes
@ -216,6 +250,7 @@ typedef struct GameState
#define PLAYERS_ITER(players, cur) for(Player * cur = players; cur < players+MAX_PLAYERS; cur++) if(cur->connected)
#define PI 3.14159f
#define TAU (PI*2.0f)
// returns in radians
static float rotangle(enum CompassRotation rot)
@ -241,19 +276,35 @@ static float rotangle(enum CompassRotation rot)
}
}
typedef struct OpusPacket {
bool exists;
struct OpusPacket* next;
char data[VOIP_PACKET_MAX_SIZE];
opus_int32 length;
} OpusPacket;
typedef struct OpusBuffer {
OpusPacket packets[VOIP_PACKET_BUFFER_SIZE];
OpusPacket* next;
} OpusBuffer;
typedef struct ServerToClient
{
struct GameState* cur_gs;
OpusBuffer* playback_buffer;
int your_player;
} ServerToClient;
struct ClientToServer
typedef struct ClientToServer
{
OpusBuffer* mic_data; // on serialize, flushes this of packets. On deserialize, fills it
InputFrame inputs[INPUT_BUFFER];
};
} ClientToServer;
// server
void server(void* data); // data parameter required from thread api...
void server(void* info); // data parameter required from thread api...
// gamestate
EntityID create_spacestation(GameState* gs);
@ -262,8 +313,12 @@ void destroy(struct GameState* gs);
void process(struct GameState* gs, float dt); // does in place
Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius);
uint64_t tick(struct GameState* gs);
void into_bytes(struct ServerToClient* gs, char* out_bytes, size_t* out_len, size_t max_len);
void from_bytes(struct ServerToClient* gs, char* bytes, size_t max_len);
// all of these return if successful or not
bool server_to_client_serialize(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player, bool to_disk);
bool server_to_client_deserialize(struct ServerToClient* msg, char* bytes, size_t max_len, bool from_disk);
bool client_to_server_deserialize(GameState* gs, struct ClientToServer* msg, char* bytes, size_t max_len);
bool client_to_server_serialize(GameState* gs, struct ClientToServer* msg, char* bytes, size_t* out_len, size_t max_len);
// entities
Entity* get_entity(struct GameState* gs, EntityID id);
@ -278,15 +333,12 @@ void entity_destroy(GameState* gs, Entity* e);
#define BOX_CHAIN_ITER(gs, cur, starting_box) for (Entity *cur = get_entity(gs, starting_box); cur != NULL; cur = get_entity(gs, cur->next_box))
#define BOXES_ITER(gs, cur, grid_entity_ptr) BOX_CHAIN_ITER(gs, cur, (grid_entity_ptr)->boxes)
// player
void player_destroy(struct Player* p);
void player_new(struct Player* p);
// grid
void grid_create(struct GameState* gs, Entity* e);
void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos);
V2 grid_com(Entity* grid);
V2 grid_vel(Entity* grid);
V2 box_vel(Entity* box);
V2 grid_local_to_world(Entity* grid, V2 local);
V2 grid_world_to_local(Entity* grid, V2 world);
V2 grid_snapped_box_pos(Entity* grid, V2 world); // returns the snapped pos in world coords
@ -303,10 +355,100 @@ void dbg_drawall();
void dbg_line(V2 from, V2 to);
void dbg_rect(V2 center);
// helper
#define SKIPNULL(thing) \
if (thing == NULL) \
continue
typedef struct ServerThreadInfo {
ma_mutex info_mutex;
const char* world_save;
bool should_quit;
} ServerThreadInfo;
static void clear_buffer(OpusBuffer* buff)
{
*buff = (OpusBuffer){ 0 };
}
// you push a packet, get the return value, and fill it with data. It's that easy!
static OpusPacket* push_packet(OpusBuffer* buff)
{
OpusPacket* to_return = NULL;
for (size_t i = 0; i < VOIP_PACKET_BUFFER_SIZE; i++)
if (!buff->packets[i].exists)
{
to_return = &buff->packets[i];
break;
}
// no free packet found in the buffer
if (to_return == NULL)
{
Log("Opus Buffer Full\n");
clear_buffer(buff);
to_return = &buff->packets[0];
#if 0
to_return = buff->next;
buff->next = buff->next->next;
#endif
}
*to_return = (OpusPacket){ 0 };
to_return->exists = true;
// add to the end of the linked list chain
if (buff->next != NULL)
{
OpusPacket* cur = buff->next;
while (cur->next != NULL) cur = cur->next;
cur->next = to_return;
}
else {
buff->next = to_return;
}
return to_return;
}
static int num_queued_packets(OpusBuffer* buff)
{
int to_return = 0;
for (size_t i = 0; i < VOIP_PACKET_BUFFER_SIZE; i++)
if (buff->packets[i].exists) to_return++;
return to_return;
}
static OpusPacket* get_packet_at_index(OpusBuffer* buff, int i)
{
OpusPacket* to_return = buff->next;
int index_at = 0;
while (index_at < i)
{
if (to_return->next == NULL)
{
Log("FAILED TO GET TO INDEX %d\n", i);
return to_return;
}
to_return = to_return->next;
index_at++;
}
return to_return;
}
// returns null if the packet was dropped, like if the buffer was too full
static OpusPacket* pop_packet(OpusBuffer* buff)
{
#if 0
if (buff->skipped_packets > 0) {
buff->skipped_packets--;
return NULL;
}
#endif
OpusPacket* to_return = buff->next;
if (buff->next != NULL) buff->next = buff->next->next;
if (to_return != NULL) to_return->exists = false; // feels janky to do this
return to_return;
}
#define DeferLoop(start, end) \
for (int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end))
// all the math is static so that it can be defined in each compilation unit its included in
@ -336,9 +478,14 @@ static V2 V2scale(V2 a, float f)
};
}
static float V2lengthsqr(V2 v)
{
return v.x * v.x + v.y * v.y;
}
static float V2length(V2 v)
{
return sqrtf(v.x * v.x + v.y * v.y);
return sqrtf(V2lengthsqr(v));
}
static V2 V2normalize(V2 v)
@ -346,6 +493,7 @@ static V2 V2normalize(V2 v)
return V2scale(v, 1.0f / V2length(v));
}
static float V2dot(V2 a, V2 b)
{
return a.x * b.x + a.y * b.y;
@ -394,6 +542,16 @@ static inline float clamp01(float f)
return fmaxf(0.0f, fminf(f, 1.0f));
}
static float V2distsqr(V2 from, V2 to)
{
return V2lengthsqr(V2sub(to, from));
}
static float V2dist(V2 from, V2 to)
{
return sqrtf(V2distsqr(from, to));
}
static inline float clamp(float f, float minimum, float maximum)
{
if (f < minimum)
@ -413,6 +571,12 @@ static float lerp(float a, float b, float f)
return a * (1.0f - f) + (b * f);
}
static float lerp_angle(float p_from, float p_to, float p_weight) {
float difference = fmodf(p_to - p_from, (float)TAU);
float distance = fmodf(2.0f * difference, (float)TAU) - difference;
return p_from + distance * p_weight;
}
static V2 V2floor(V2 p)
{
return (V2) { floorf(p.x), floorf(p.y) };
@ -518,4 +682,4 @@ static void set_color(Color c)
(Color) { .r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f }
#define RED \
(Color) { .r = 1.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f }
#define GOLD colhex(255, 215, 0)
#define GOLD colhex(255, 215, 0)

@ -0,0 +1 @@
ssh astris "cd flight; git pull; ./linux_server_install.sh"

Binary file not shown.
Loading…
Cancel
Save