Basic voice chat to self, mute button.Lag+no linux

main
Cameron Murphy Reikes 2 years ago
parent 9295c95121
commit 7c2798747a

1
.gitignore vendored

@ -1,3 +1,4 @@
*.bin # world files
debug_world.bin debug_world.bin
flight*.zip flight*.zip
flight_server flight_server

3
.gitmodules vendored

@ -4,3 +4,6 @@
[submodule "thirdparty/Chipmunk2D"] [submodule "thirdparty/Chipmunk2D"]
path = thirdparty/Chipmunk2D path = thirdparty/Chipmunk2D
url = https://github.com/slembcke/Chipmunk2D.git 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> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);DEBUG_RENDERING;SERVER_ADDRESS="127.0.0.1"</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);DEBUG_RENDERING;SERVER_ADDRESS="127.0.0.1"</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <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> <TreatWarningAsError>true</TreatWarningAsError>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <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> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug No Host|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug No Host|x64'">
@ -174,7 +174,7 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <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)\flight\thirdparty\Chipmunk2D\include\chipmunk;$(ProjectDir)\thirdparty;$(ProjectDir)\thirdparty\Chipmunk2D\include;$(ProjectDir)\thirdparty\minilzo;$(ProjectDir)\thirdparty\opus\include</AdditionalIncludeDirectories>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>

@ -5,6 +5,11 @@
WHERE sokol-shdc.exe 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 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
set OPUSLIB=thirdparty\opus\win32\VS2015\x64\Release\opus.lib
WHERE %OPUSLIB%
IF %ERRORLEVEL% NEQ 0 ECHO ERROR compile opus by opening the visual studio project in win32\VS2015 and building the release setting
@REM example of how to compile shaders: sokol-shdc.exe --input triangle.glsl --output triangle.gen.h --slang glsl330:hlsl5:metal_macos @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 setlocal enabledelayedexpansion enableextensions
@ -16,8 +21,9 @@ popd
@REM /DENET_DEBUG=1^ @REM /DENET_DEBUG=1^
cl /MP^ cl /MP^
%compileopts%^ %compileopts%^
/I"thirdparty" /I"thirdparty\minilzo" /I"thirdparty\enet\include" /I"thirdparty\Chipmunk2D\include\chipmunk" /I"thirdparty\Chipmunk2D\include"^ /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^ main.c gamestate.c server.c debugdraw.c^
thirdparty\minilzo\minilzo.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^ 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% %MUNKSRC%^
%OPUSLIB%

@ -3,7 +3,7 @@
#include "ipsettings.h" // debug/developer settings #include "ipsettings.h" // debug/developer settings
#include <stdio.h> // assert logging #include <stdio.h> // assert logging
#include <string.h> // memset #include <string.h> // memset
// do not use any global variables to process gamestate // do not use any global variables to process gamestate
@ -41,7 +41,8 @@ static cpVect v2_to_cp(V2 v)
bool was_entity_deleted(GameState* gs, EntityID id) bool was_entity_deleted(GameState* gs, EntityID id)
{ {
if (id.generation == 0) return false; // generation 0 means null entity ID, not a deleted entity if (id.generation == 0)
return false; // generation 0 means null entity ID, not a deleted entity
Entity* the_entity = &gs->entities[id.index]; Entity* the_entity = &gs->entities[id.index];
return (!the_entity->exists || the_entity->generation != id.generation); return (!the_entity->exists || the_entity->generation != id.generation);
} }
@ -55,7 +56,7 @@ Entity* get_entity_even_if_dead(GameState* gs, EntityID id)
assert(id.index < gs->cur_next_entity || gs->cur_next_entity == 0); assert(id.index < gs->cur_next_entity || gs->cur_next_entity == 0);
assert(id.index < gs->max_entities); assert(id.index < gs->max_entities);
Entity* to_return = &gs->entities[id.index]; Entity* to_return = &gs->entities[id.index];
// don't validate the generation either // don't validate the generation either
return to_return; return to_return;
} }
@ -230,7 +231,8 @@ V2 player_vel(GameState* gs, Entity* player)
{ {
return cp_to_v2(cpBodyGetVelocity(get_entity(gs, potential_seat->shape_parent_entity)->body)); return cp_to_v2(cpBodyGetVelocity(get_entity(gs, potential_seat->shape_parent_entity)->body));
} }
else { else
{
return cp_to_v2(cpBodyGetVelocity(player->body)); return cp_to_v2(cpBodyGetVelocity(player->body));
} }
} }
@ -383,16 +385,16 @@ static void grid_correct_for_holes(GameState* gs, struct Entity* grid)
const V2 dirs[] = { const V2 dirs[] = {
(V2) { (V2) {
.x = -1.0f, .y = 0.0f .x = -1.0f, .y = 0.0f
}, },
(V2) { (V2) {
.x = 1.0f, .y = 0.0f .x = 1.0f, .y = 0.0f
}, },
(V2) { (V2) {
.x = 0.0f, .y = 1.0f .x = 0.0f, .y = 1.0f
}, },
(V2) { (V2) {
.x = 0.0f, .y = -1.0f .x = 0.0f, .y = -1.0f
}, },
}; };
int num_dirs = sizeof(dirs) / sizeof(*dirs); int num_dirs = sizeof(dirs) / sizeof(*dirs);
@ -498,12 +500,11 @@ static cpBool on_damage(cpArbiter* arb, cpSpace* space, cpDataPointer userData)
// Log("Collision with damage %f\n", damage); // Log("Collision with damage %f\n", damage);
entity_a->damage += damage; entity_a->damage += damage;
entity_b->damage += damage; entity_b->damage += damage;
} }
// b must be the key passed into the post step removed, the key is cast into its shape // b must be the key passed into the post step removed, the key is cast into its shape
//cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL); // cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL);
//cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL); // cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL);
return true; // keep colliding return true; // keep colliding
} }
@ -515,7 +516,7 @@ void initialize(GameState* gs, void* entity_arena, size_t entity_arena_size)
gs->entities = (Entity*)entity_arena; gs->entities = (Entity*)entity_arena;
gs->max_entities = (unsigned int)(entity_arena_size / sizeof(Entity)); gs->max_entities = (unsigned int)(entity_arena_size / sizeof(Entity));
gs->space = cpSpaceNew(); gs->space = cpSpaceNew();
cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler
cpCollisionHandler* handler = cpSpaceAddCollisionHandler(gs->space, 0, 0); // @Robust limit collision type to just blocks that can be damaged cpCollisionHandler* handler = cpSpaceAddCollisionHandler(gs->space, 0, 0); // @Robust limit collision type to just blocks that can be damaged
handler->postSolveFunc = on_damage; handler->postSolveFunc = on_damage;
} }
@ -533,7 +534,8 @@ void destroy(GameState* gs)
for (size_t i = 0; i < gs->cur_next_entity; i++) for (size_t i = 0; i < gs->cur_next_entity; i++)
{ {
if (gs->entities[i].exists) gs->entities[i] = (Entity){ 0 }; if (gs->entities[i].exists)
gs->entities[i] = (Entity){ 0 };
} }
gs->cur_next_entity = 0; gs->cur_next_entity = 0;
} }
@ -596,13 +598,16 @@ float box_rotation(Entity* box)
} }
V2 entity_pos(Entity* e) V2 entity_pos(Entity* e)
{ {
if (e->is_box) { if (e->is_box)
{
return V2add(entity_pos(box_grid(e)), V2rotate(entity_shape_pos(e), entity_rotation(box_grid(e)))); return V2add(entity_pos(box_grid(e)), V2rotate(entity_shape_pos(e), entity_rotation(box_grid(e))));
} }
else if (e->is_explosion) { else if (e->is_explosion)
{
return e->explosion_pos; return e->explosion_pos;
} }
else { else
{
assert(e->body != NULL); assert(e->body != NULL);
return cp_to_v2(cpBodyGetPosition(e->body)); return cp_to_v2(cpBodyGetPosition(e->body));
} }
@ -639,13 +644,41 @@ typedef struct SerState
size_t cursor; // points to next available byte, is the size of current message after serializing something size_t cursor; // points to next available byte, is the size of current message after serializing something
size_t max_size; size_t max_size;
Entity* for_player; Entity* for_player;
size_t max_entity_index; // for error checking
bool write_varnames; bool write_varnames;
bool save_or_load_from_disk; bool save_or_load_from_disk;
// output // output
uint32_t version; uint32_t version;
} SerState; } SerState;
void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name, const char* file, int line)
typedef struct SerMaybeFailure
{
bool failed;
int line;
const char* expression;
} SerMaybeFailure;
const static SerMaybeFailure ser_ok = { 0 };
#define SER_ASSERT(cond) \
if (!(cond)) \
{ \
__assert(false, __FILE__, __LINE__, #cond); \
if (ser->save_or_load_from_disk) \
{ \
Log("While saving/loading, serialization assertion failed %s on line %d\n", #cond, __LINE__); \
} \
else \
{ \
return (SerMaybeFailure){.failed = true, .line = __LINE__, .expression = #cond}; \
} \
}
#define SER_MAYBE_RETURN(maybe_failure) \
{ \
SerMaybeFailure result = maybe_failure; \
if (result.failed) \
return result; \
}
SerMaybeFailure ser_data(SerState* ser, char* data, size_t data_len, const char* name, const char* file, int line)
{ {
char var_name[512] = { 0 }; char var_name[512] = { 0 };
snprintf(var_name, 512, "%d%s", line, name); // can't have separator before the name, when comparing names skips past the digit snprintf(var_name, 512, "%d%s", line, name); // can't have separator before the name, when comparing names skips past the digit
@ -657,23 +690,24 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name
memcpy(ser->bytes + ser->cursor, var_name, var_name_len); memcpy(ser->bytes + ser->cursor, var_name, var_name_len);
ser->cursor += var_name_len; ser->cursor += var_name_len;
} }
for (int b = 0; b < var_size; b++) for (int b = 0; b < data_len; b++)
{ {
ser->bytes[ser->cursor] = var_pointer[b]; ser->bytes[ser->cursor] = data[b];
ser->cursor += 1; ser->cursor += 1;
assert(ser->cursor < ser->max_size); SER_ASSERT(ser->cursor < ser->max_size);
} }
} }
else else
{ {
if (ser->write_varnames) { if (ser->write_varnames)
{
char read_name[512] = { 0 }; char read_name[512] = { 0 };
for (int i = 0; i < var_name_len; i++) for (int i = 0; i < var_name_len; i++)
{ {
read_name[i] = ser->bytes[ser->cursor]; read_name[i] = ser->bytes[ser->cursor];
ser->cursor += 1; ser->cursor += 1;
assert(ser->cursor <= ser->max_size); SER_ASSERT(ser->cursor <= ser->max_size);
} }
read_name[var_name_len] = '\0'; read_name[var_name_len] = '\0';
// advance past digits // advance past digits
@ -683,90 +717,106 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name
read++; read++;
while (*var >= '0' && *var <= '9') while (*var >= '0' && *var <= '9')
var++; var++;
if (strcmp(read, var) != 0) SER_ASSERT(strcmp(read, var) == 0);
{
printf("%s:%d | Expected variable %s but got %sn\n", file, line, var_name, read_name);
*(char*)NULL = 0;
}
} }
for (int b = 0; b < var_size; b++) for (int b = 0; b < data_len; b++)
{ {
var_pointer[b] = ser->bytes[ser->cursor]; data[b] = ser->bytes[ser->cursor];
ser->cursor += 1; ser->cursor += 1;
assert(ser->cursor <= ser->max_size); SER_ASSERT(ser->cursor <= ser->max_size);
} }
} }
return ser_ok;
} }
#define SER_VAR_NAME(var_pointer, name) ser_var(ser, (char*)var_pointer, sizeof(*var_pointer), name, __FILE__, __LINE__) SerMaybeFailure ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name, const char* file, int line)
{
return ser_data(ser, var_pointer, var_size, name, file, line);
}
#define SER_DATA(data_pointer, data_length) SER_MAYBE_RETURN(ser_data(ser, data_pointer, data_length, #data_pointer, __FILE__, __LINE__))
#define SER_VAR_NAME(var_pointer, name) SER_MAYBE_RETURN(ser_var(ser, (char *)var_pointer, sizeof(*var_pointer), name, __FILE__, __LINE__))
#define SER_VAR(var_pointer) SER_VAR_NAME(var_pointer, #var_pointer) #define SER_VAR(var_pointer) SER_VAR_NAME(var_pointer, #var_pointer)
enum GameVersion { enum GameVersion
{
VInitial, VInitial,
VAddedTest, VAddedTest,
VAddedSerToDisk, VAddedSerToDisk,
VRemovedTest,
VMax, // this minus one will be the version used VMax, // this minus one will be the version used
}; };
// @Robust probably get rid of this as separate function, just use SER_VAR // @Robust probably get rid of this as separate function, just use SER_VAR
void ser_V2(SerState* ser, V2* var) SerMaybeFailure ser_V2(SerState* ser, V2* var)
{ {
SER_VAR(&var->x); SER_VAR(&var->x);
SER_VAR(&var->y); SER_VAR(&var->y);
SER_ASSERT(!isnan(var->x));
SER_ASSERT(!isnan(var->y));
return ser_ok;
} }
void ser_bodydata(SerState* ser, struct BodyData* data) SerMaybeFailure ser_bodydata(SerState* ser, struct BodyData* data)
{ {
ser_V2(ser, &data->pos); SER_MAYBE_RETURN(ser_V2(ser, &data->pos));
ser_V2(ser, &data->vel); SER_MAYBE_RETURN(ser_V2(ser, &data->vel));
SER_VAR(&data->rotation); SER_VAR(&data->rotation);
SER_VAR(&data->angular_velocity); SER_VAR(&data->angular_velocity);
SER_ASSERT(!isnan(data->rotation));
SER_ASSERT(!isnan(data->angular_velocity));
return ser_ok;
} }
void ser_entityid(SerState* ser, EntityID* id) SerMaybeFailure ser_entityid(SerState* ser, EntityID* id)
{ {
SER_VAR(&id->generation); SER_VAR(&id->generation);
SER_VAR(&id->index); SER_VAR(&id->index);
if (id->generation > 0) SER_ASSERT(id->index < ser->max_entity_index);
return ser_ok;
} }
void ser_inputframe(SerState* ser, InputFrame* i) SerMaybeFailure ser_inputframe(SerState* ser, InputFrame* i)
{ {
SER_VAR(&i->tick); SER_VAR(&i->tick);
SER_VAR(&i->movement); SER_VAR(&i->id);
SER_MAYBE_RETURN(ser_V2(ser, &i->movement));
SER_VAR(&i->seat_action); SER_VAR(&i->seat_action);
ser_entityid(ser, &i->seat_to_inhabit); SER_MAYBE_RETURN(ser_entityid(ser, &i->seat_to_inhabit));
SER_VAR(&i->hand_pos); SER_MAYBE_RETURN(ser_V2(ser, &i->hand_pos));
ser_entityid(ser, &i->grid_hand_pos_local_to); SER_MAYBE_RETURN(ser_entityid(ser, &i->grid_hand_pos_local_to));
SER_VAR(&i->dobuild); SER_VAR(&i->dobuild);
SER_VAR(&i->build_type); SER_VAR(&i->build_type);
SER_ASSERT(i->build_type >= 0);
SER_ASSERT(i->build_type < BoxLast);
SER_VAR(&i->build_rotation); SER_VAR(&i->build_rotation);
SER_ASSERT(!isnan(i->build_rotation));
return ser_ok;
} }
void ser_player(SerState* ser, Player* p) SerMaybeFailure ser_player(SerState* ser, Player* p)
{ {
SER_VAR(&p->connected); SER_VAR(&p->connected);
if (p->connected) if (p->connected)
{ {
SER_VAR(&p->unlocked_bombs); SER_VAR(&p->unlocked_bombs);
ser_entityid(ser, &p->entity); SER_MAYBE_RETURN(ser_entityid(ser, &p->entity));
ser_inputframe(ser, &p->input); SER_MAYBE_RETURN(ser_inputframe(ser, &p->input));
} }
return ser_ok;
} }
void ser_entity(SerState* ser, GameState* gs, Entity* e) SerMaybeFailure ser_entity(SerState* ser, GameState* gs, Entity* e)
{ {
SER_VAR(&e->no_save_to_disk); SER_VAR(&e->no_save_to_disk); // this is always false when saving to disk?
SER_VAR(&e->generation); SER_VAR(&e->generation);
SER_VAR(&e->damage); SER_VAR(&e->damage);
int test; int test;
if (ser->serializing) test = 27; if (ser->version < VRemovedTest && ser->version >= VAddedTest)
if (ser->version >= VAddedTest)
SER_VAR(&test); SER_VAR(&test);
else test = 27;
assert(test == 27);
bool has_body = ser->serializing && e->body != NULL; bool has_body = ser->serializing && e->body != NULL;
SER_VAR(&has_body); SER_VAR(&has_body);
@ -776,7 +826,7 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
struct BodyData body_data; struct BodyData body_data;
if (ser->serializing) if (ser->serializing)
populate(e->body, &body_data); populate(e->body, &body_data);
ser_bodydata(ser, &body_data); SER_MAYBE_RETURN(ser_bodydata(ser, &body_data));
if (!ser->serializing) if (!ser->serializing)
{ {
create_body(gs, e); create_body(gs, e);
@ -789,20 +839,21 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
if (has_shape) if (has_shape)
{ {
ser_V2(ser, &e->shape_size); SER_MAYBE_RETURN(ser_V2(ser, &e->shape_size));
ser_entityid(ser, &e->shape_parent_entity); SER_MAYBE_RETURN(ser_entityid(ser, &e->shape_parent_entity));
Entity* parent = get_entity(gs, e->shape_parent_entity); Entity* parent = get_entity(gs, e->shape_parent_entity);
assert(parent != NULL); SER_ASSERT(parent != NULL);
V2 shape_pos; V2 shape_pos;
if (ser->serializing) if (ser->serializing)
shape_pos = entity_shape_pos(e); shape_pos = entity_shape_pos(e);
SER_VAR(&shape_pos); SER_MAYBE_RETURN(ser_V2(ser, &shape_pos));
float shape_mass; float shape_mass;
if (ser->serializing) if (ser->serializing)
shape_mass = entity_shape_mass(e); shape_mass = entity_shape_mass(e);
SER_VAR(&shape_mass); SER_VAR(&shape_mass);
SER_ASSERT(!isnan(shape_mass));
cpShapeFilter filter; cpShapeFilter filter;
if (ser->serializing) if (ser->serializing)
@ -822,16 +873,16 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
SER_VAR(&e->is_player); SER_VAR(&e->is_player);
if (e->is_player) if (e->is_player)
{ {
assert(e->no_save_to_disk); SER_ASSERT(e->no_save_to_disk);
ser_entityid(ser, &e->currently_inside_of_box); SER_MAYBE_RETURN(ser_entityid(ser, &e->currently_inside_of_box));
SER_VAR(&e->goldness); SER_VAR(&e->goldness);
} }
SER_VAR(&e->is_explosion); SER_VAR(&e->is_explosion);
if (e->is_explosion) if (e->is_explosion)
{ {
ser_V2(ser, &e->explosion_pos); SER_MAYBE_RETURN(ser_V2(ser, &e->explosion_pos));
ser_V2(ser, &e->explosion_vel); SER_MAYBE_RETURN(ser_V2(ser, &e->explosion_vel));
SER_VAR(&e->explosion_progresss); SER_VAR(&e->explosion_progresss);
} }
@ -839,7 +890,7 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
if (e->is_grid) if (e->is_grid)
{ {
SER_VAR(&e->total_energy_capacity); SER_VAR(&e->total_energy_capacity);
ser_entityid(ser, &e->boxes); SER_MAYBE_RETURN(ser_entityid(ser, &e->boxes));
} }
SER_VAR(&e->is_box); SER_VAR(&e->is_box);
@ -848,21 +899,72 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
SER_VAR(&e->box_type); SER_VAR(&e->box_type);
SER_VAR(&e->always_visible); SER_VAR(&e->always_visible);
SER_VAR(&e->is_explosion_unlock); SER_VAR(&e->is_explosion_unlock);
ser_entityid(ser, &e->next_box); SER_MAYBE_RETURN(ser_entityid(ser, &e->next_box));
ser_entityid(ser, &e->prev_box); SER_MAYBE_RETURN(ser_entityid(ser, &e->prev_box));
SER_VAR(&e->compass_rotation); SER_VAR(&e->compass_rotation);
SER_VAR(&e->indestructible); SER_VAR(&e->indestructible);
SER_VAR(&e->thrust); SER_VAR(&e->thrust);
SER_VAR(&e->wanted_thrust); SER_VAR(&e->wanted_thrust);
SER_VAR(&e->energy_used); SER_VAR(&e->energy_used);
SER_VAR(&e->sun_amount); SER_VAR(&e->sun_amount);
ser_entityid(ser, &e->player_who_is_inside_of_me); SER_MAYBE_RETURN(ser_entityid(ser, &e->player_who_is_inside_of_me));
}
return ser_ok;
}
SerMaybeFailure ser_opus_packets(SerState* ser, OpusBuffer* mic_or_speaker_data)
{
bool no_more_packets = false;
if (ser->serializing)
{
int queued = num_queued_packets(mic_or_speaker_data);
for (int i = 0; i < queued; i++)
{
SER_VAR(&no_more_packets);
OpusPacket* cur = pop_packet(mic_or_speaker_data);
bool isnull = cur == NULL;
SER_VAR(&isnull);
if (!isnull)
{
SER_VAR(&cur->length);
SER_DATA(cur->data, cur->length);
}
}
no_more_packets = true;
SER_VAR(&no_more_packets);
} }
else
{
while (true)
{
SER_VAR(&no_more_packets);
if (no_more_packets) break;
OpusPacket* cur = push_packet(mic_or_speaker_data);
OpusPacket dummy;
if (cur == NULL) cur = &dummy; // throw away this packet
bool isnull = false;
SER_VAR(&isnull);
if (!isnull)
{
SER_VAR(&cur->length);
SER_ASSERT(cur->length < VOIP_PACKET_MAX_SIZE);
SER_ASSERT(cur->length >= 0);
SER_DATA(cur->data, cur->length);
}
}
}
return ser_ok;
} }
void ser_server_to_client(SerState* ser, ServerToClient* s) SerMaybeFailure ser_server_to_client(SerState* ser, ServerToClient* s)
{ {
SER_VAR(&ser->version); SER_VAR(&ser->version);
SER_ASSERT(ser->version >= 0);
SER_ASSERT(ser->version < VMax);
if (!ser->save_or_load_from_disk)
SER_MAYBE_RETURN(ser_opus_packets(ser, s->playback_buffer));
GameState* gs = s->cur_gs; GameState* gs = s->cur_gs;
@ -870,10 +972,11 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
if (ser->serializing) if (ser->serializing)
cur_next_entity = gs->cur_next_entity; cur_next_entity = gs->cur_next_entity;
SER_VAR(&cur_next_entity); SER_VAR(&cur_next_entity);
SER_ASSERT(cur_next_entity <= ser->max_entity_index);
if (!ser->serializing) if (!ser->serializing)
{ {
// avoid a memset here very expensive // avoid a memset here very expensive. que rico!
destroy(gs); destroy(gs);
initialize(gs, gs->entities, gs->max_entities * sizeof(*gs->entities)); initialize(gs, gs->entities, gs->max_entities * sizeof(*gs->entities));
gs->cur_next_entity = 0; // updated on deserialization gs->cur_next_entity = 0; // updated on deserialization
@ -882,14 +985,14 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
SER_VAR(&s->your_player); SER_VAR(&s->your_player);
SER_VAR(&gs->time); SER_VAR(&gs->time);
ser_V2(ser, &gs->goldpos); SER_MAYBE_RETURN(ser_V2(ser, &gs->goldpos));
if (!ser->save_or_load_from_disk) if (!ser->save_or_load_from_disk)
{ {
// @Robust save player data with their ID or something somehow. Like local backup of their account // @Robust save player data with their ID or something somehow. Like local backup of their account
for (size_t i = 0; i < MAX_PLAYERS; i++) for (size_t i = 0; i < MAX_PLAYERS; i++)
{ {
ser_player(ser, &gs->players[i]); SER_MAYBE_RETURN(ser_player(ser, &gs->players[i]));
} }
} }
if (ser->serializing) if (ser->serializing)
@ -898,7 +1001,10 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
for (size_t i = 0; i < gs->cur_next_entity; i++) for (size_t i = 0; i < gs->cur_next_entity; i++)
{ {
Entity* e = &gs->entities[i]; Entity* e = &gs->entities[i];
#define SER_ENTITY() SER_VAR(&entities_done); SER_VAR(&i); ser_entity(ser, gs, e) #define SER_ENTITY() \
SER_VAR(&entities_done); \
SER_VAR(&i); \
SER_MAYBE_RETURN(ser_entity(ser, gs, e))
if (e->exists && !(ser->save_or_load_from_disk && e->no_save_to_disk)) if (e->exists && !(ser->save_or_load_from_disk && e->no_save_to_disk))
{ {
if (!e->is_box && !e->is_grid) if (!e->is_box && !e->is_grid)
@ -913,7 +1019,8 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
BOXES_ITER(gs, cur, e) BOXES_ITER(gs, cur, e)
{ {
bool this_box_in_range = (ser->for_player == NULL || (ser->for_player != NULL && V2distsqr(entity_pos(ser->for_player), entity_pos(cur)) < VISION_RADIUS * VISION_RADIUS)); bool this_box_in_range = (ser->for_player == NULL || (ser->for_player != NULL && V2distsqr(entity_pos(ser->for_player), entity_pos(cur)) < VISION_RADIUS * VISION_RADIUS));
if (cur->always_visible) this_box_in_range = true; if (cur->always_visible)
this_box_in_range = true;
if (this_box_in_range) if (this_box_in_range)
{ {
if (!serialized_grid_yet) if (!serialized_grid_yet)
@ -924,14 +1031,13 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
// serialize this box // serialize this box
EntityID cur_id = get_id(gs, cur); EntityID cur_id = get_id(gs, cur);
assert(cur_id.index < gs->max_entities); SER_ASSERT(cur_id.index < gs->max_entities);
SER_VAR(&entities_done); SER_VAR(&entities_done);
size_t the_index = (size_t)cur_id.index; // super critical. Type of &i is size_t. @Robust add debug info in serialization for what size the expected type is, maybe string nameof the type size_t the_index = (size_t)cur_id.index; // super critical. Type of &i is size_t. @Robust add debug info in serialization for what size the expected type is, maybe string nameof the type
SER_VAR_NAME(&the_index, "&i"); SER_VAR_NAME(&the_index, "&i");
ser_entity(ser, gs, cur); SER_MAYBE_RETURN(ser_entity(ser, gs, cur));
} }
} }
} }
} }
#undef SER_ENTITY #undef SER_ENTITY
@ -950,24 +1056,27 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
break; break;
size_t next_index; size_t next_index;
SER_VAR_NAME(&next_index, "&i"); SER_VAR_NAME(&next_index, "&i");
assert(next_index < gs->max_entities); SER_ASSERT(next_index < gs->max_entities);
SER_ASSERT(next_index >= 0);
Entity* e = &gs->entities[next_index]; Entity* e = &gs->entities[next_index];
e->exists = true; e->exists = true;
//unsigned int possible_next_index = (unsigned int)(next_index + 2); // plus two because player entity refers to itself on deserialization // unsigned int possible_next_index = (unsigned int)(next_index + 2); // plus two because player entity refers to itself on deserialization
unsigned int possible_next_index = (unsigned int)(next_index + 1); unsigned int possible_next_index = (unsigned int)(next_index + 1);
gs->cur_next_entity = gs->cur_next_entity < possible_next_index ? possible_next_index : gs->cur_next_entity; gs->cur_next_entity = gs->cur_next_entity < possible_next_index ? possible_next_index : gs->cur_next_entity;
ser_entity(ser, gs, e); SER_MAYBE_RETURN(ser_entity(ser, gs, e));
if (e->is_box) if (e->is_box)
{ {
assert(last_grid != NULL); SER_ASSERT(last_grid != NULL);
assert(last_grid == get_entity(gs, e->shape_parent_entity)); SER_ASSERT(get_entity(gs, e->shape_parent_entity) != NULL);
SER_ASSERT(last_grid == get_entity(gs, e->shape_parent_entity));
e->prev_box = (EntityID){ 0 }; e->prev_box = (EntityID){ 0 };
e->next_box = (EntityID){ 0 }; e->next_box = (EntityID){ 0 };
box_add_to_boxes(gs, last_grid, e); box_add_to_boxes(gs, last_grid, e);
} }
if (e->is_grid) { if (e->is_grid)
{
e->boxes = (EntityID){ 0 }; e->boxes = (EntityID){ 0 };
last_grid = e; last_grid = e;
} }
@ -984,10 +1093,11 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
} }
} }
} }
return ser_ok;
} }
// for_this_player can be null then the entire world will be sent // for_this_player can be null then the entire world will be sent
void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player, bool write_varnames) 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)
{ {
assert(msg->cur_gs != NULL); assert(msg->cur_gs != NULL);
assert(msg != NULL); assert(msg != NULL);
@ -998,6 +1108,7 @@ void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t
.cursor = 0, .cursor = 0,
.max_size = max_len, .max_size = max_len,
.for_player = for_this_player, .for_player = for_this_player,
.max_entity_index = msg->cur_gs->cur_next_entity,
.version = VMax - 1, .version = VMax - 1,
}; };
@ -1006,35 +1117,122 @@ void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t
ser.save_or_load_from_disk = true; ser.save_or_load_from_disk = true;
} }
ser.write_varnames = write_varnames; ser.write_varnames = to_disk;
#ifdef WRITE_VARNAMES #ifdef WRITE_VARNAMES
ser.write_varnames = true; ser.write_varnames = true;
#endif #endif
ser_server_to_client(&ser, msg); SerMaybeFailure result = ser_server_to_client(&ser, msg);
*out_len = ser.cursor + 1; // @Robust not sure why I need to add one to cursor, ser.cursor should be the length.. *out_len = ser.cursor + 1; // @Robust not sure why I need to add one to cursor, ser.cursor should be the length..
if (result.failed)
{
Log("Failed to serialize on line %d because of %s\n", result.line, result.expression);
return false;
}
else
{
return true;
}
} }
void from_bytes(struct ServerToClient* msg, char* bytes, size_t max_len, bool write_varnames, bool from_disk) bool server_to_client_deserialize(struct ServerToClient* msg, char* bytes, size_t max_len, bool from_disk)
{ {
assert(msg->cur_gs != NULL); assert(msg->cur_gs != NULL);
assert(msg != NULL); assert(msg != NULL);
SerState ser = (SerState){ SerState servar = (SerState){
.bytes = bytes, .bytes = bytes,
.serializing = false, .serializing = false,
.cursor = 0, .cursor = 0,
.max_size = max_len, .max_size = max_len,
.max_entity_index = msg->cur_gs->max_entities,
.save_or_load_from_disk = from_disk, .save_or_load_from_disk = from_disk,
}; };
ser.write_varnames = write_varnames; if (from_disk)
servar.write_varnames = true;
#ifdef WRITE_VARNAMES
servar.write_varnames = true;
#endif
SerState* ser = &servar;
SerMaybeFailure result = ser_server_to_client(ser, msg);
if (result.failed)
{
Log("Failed to deserialize server to client on line %d because of %s\n", result.line, result.expression);
return false;
}
else
{
return true;
}
}
SerMaybeFailure ser_client_to_server(SerState* ser, ClientToServer* msg)
{
SER_VAR(&ser->version);
SER_MAYBE_RETURN(ser_opus_packets(ser, msg->mic_data));
for (int i = 0; i < INPUT_BUFFER; i++)
{
SER_MAYBE_RETURN(ser_inputframe(ser, &msg->inputs[i]));
}
return ser_ok;
}
bool client_to_server_serialize(GameState* gs, struct ClientToServer* msg, char* bytes, size_t* out_len, size_t max_len)
{
SerState ser = (SerState){
.bytes = bytes,
.serializing = true,
.cursor = 0,
.max_size = max_len,
.for_player = NULL,
.max_entity_index = gs->cur_next_entity,
.version = VMax - 1,
};
#ifdef WRITE_VARNAMES #ifdef WRITE_VARNAMES
ser.write_varnames = true; ser.write_varnames = true;
#endif #endif
ser_server_to_client(&ser, msg); SerMaybeFailure result = ser_client_to_server(&ser, msg);
*out_len = ser.cursor + 1; // see other comment for server to client
if (result.failed)
{
Log("Failed to serialize client to server because %s was false, line %d\n", result.expression, result.line);
return false;
}
else
{
return true;
}
}
bool client_to_server_deserialize(GameState* gs, struct ClientToServer* msg, char* bytes, size_t max_len)
{
SerState servar = (SerState){
.bytes = bytes,
.serializing = false,
.cursor = 0,
.max_size = max_len,
.max_entity_index = gs->cur_next_entity,
.save_or_load_from_disk = false,
};
#ifdef WRITE_VARNAMES
servar.write_varnames = true;
#endif
SerState* ser = &servar;
SerMaybeFailure result = ser_client_to_server(ser, msg);
if (result.failed)
{
Log("Failed to deserialize client to server on line %d because of %s\n", result.line, result.expression);
return false;
}
else
{
return true;
}
} }
// has to be global var because can only get this information // has to be global var because can only get this information
@ -1086,7 +1284,6 @@ static void explosion_callback_func(cpShape* shape, cpContactPointSet* points, v
V2 impulse = V2scale(V2normalize(V2sub(from_pos, explosion_origin)), EXPLOSION_PUSH_STRENGTH); V2 impulse = V2scale(V2normalize(V2sub(from_pos, explosion_origin)), EXPLOSION_PUSH_STRENGTH);
assert(parent->body != NULL); assert(parent->body != NULL);
cpBodyApplyImpulseAtWorldPoint(parent->body, v2_to_cp(impulse), v2_to_cp(from_pos)); cpBodyApplyImpulseAtWorldPoint(parent->body, v2_to_cp(impulse), v2_to_cp(from_pos));
} }
static void do_explosion(GameState* gs, Entity* explosion, float dt) static void do_explosion(GameState* gs, Entity* explosion, float dt)
@ -1131,7 +1328,8 @@ V2 get_world_hand_pos(GameState* gs, InputFrame* input, Entity* player)
{ {
return grid_local_to_world(potential_grid, input->hand_pos); return grid_local_to_world(potential_grid, input->hand_pos);
} }
else { else
{
return V2add(entity_pos(player), input->hand_pos); return V2add(entity_pos(player), input->hand_pos);
} }
} }
@ -1167,7 +1365,15 @@ V2 box_vel(Entity* box)
EntityID create_spacestation(GameState* gs) EntityID create_spacestation(GameState* gs)
{ {
#define BOX_AT_TYPE(grid, pos, type) { Entity* box = new_entity(gs); box_create(gs, box, grid, pos); box->box_type = type; box->indestructible = indestructible; box->always_visible = true; box->no_save_to_disk = true; } #define BOX_AT_TYPE(grid, pos, type) \
{ \
Entity *box = new_entity(gs); \
box_create(gs, box, grid, pos); \
box->box_type = type; \
box->indestructible = indestructible; \
box->always_visible = true; \
box->no_save_to_disk = true; \
}
#define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece) #define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece)
bool indestructible = false; bool indestructible = false;
@ -1180,28 +1386,28 @@ EntityID create_spacestation(GameState* gs)
box_create(gs, explosion_box, grid, (V2) { 0 }); box_create(gs, explosion_box, grid, (V2) { 0 });
explosion_box->is_explosion_unlock = true; explosion_box->is_explosion_unlock = true;
explosion_box->no_save_to_disk = true; explosion_box->no_save_to_disk = true;
BOX_AT_TYPE(grid, ((V2) { BOX_SIZE, 0 }), BoxExplosive); BOX_AT_TYPE(grid, ((V2){BOX_SIZE, 0}), BoxExplosive);
BOX_AT_TYPE(grid, ((V2) { BOX_SIZE * 2, 0 }), BoxHullpiece); BOX_AT_TYPE(grid, ((V2){BOX_SIZE * 2, 0}), BoxHullpiece);
BOX_AT_TYPE(grid, ((V2) { BOX_SIZE * 3, 0 }), BoxHullpiece); BOX_AT_TYPE(grid, ((V2){BOX_SIZE * 3, 0}), BoxHullpiece);
BOX_AT_TYPE(grid, ((V2) { BOX_SIZE * 4, 0 }), BoxHullpiece); BOX_AT_TYPE(grid, ((V2){BOX_SIZE * 4, 0}), BoxHullpiece);
indestructible = true; indestructible = true;
for (float y = -BOX_SIZE * 5.0; y <= BOX_SIZE * 5.0; y += BOX_SIZE) for (float y = -BOX_SIZE * 5.0; y <= BOX_SIZE * 5.0; y += BOX_SIZE)
{ {
BOX_AT_TYPE(grid, ((V2) { BOX_SIZE * 5.0, y }), BoxHullpiece); BOX_AT_TYPE(grid, ((V2){BOX_SIZE * 5.0, y}), BoxHullpiece);
} }
for (float x = -BOX_SIZE * 5.0; x <= BOX_SIZE * 5.0; x += BOX_SIZE) for (float x = -BOX_SIZE * 5.0; x <= BOX_SIZE * 5.0; x += BOX_SIZE)
{ {
BOX_AT_TYPE(grid, ((V2) { x, BOX_SIZE * 5.0 }), BoxHullpiece); BOX_AT_TYPE(grid, ((V2){x, BOX_SIZE * 5.0}), BoxHullpiece);
BOX_AT_TYPE(grid, ((V2) { x, -BOX_SIZE * 5.0 }), BoxHullpiece); BOX_AT_TYPE(grid, ((V2){x, -BOX_SIZE * 5.0}), BoxHullpiece);
} }
indestructible = false; indestructible = false;
BOX_AT_TYPE(grid, ((V2) { -BOX_SIZE * 6.0, BOX_SIZE * 5.0 }), BoxExplosive); BOX_AT_TYPE(grid, ((V2){-BOX_SIZE * 6.0, BOX_SIZE * 5.0}), BoxExplosive);
BOX_AT_TYPE(grid, ((V2) { -BOX_SIZE * 6.0, BOX_SIZE * 3.0 }), BoxExplosive); BOX_AT_TYPE(grid, ((V2){-BOX_SIZE * 6.0, BOX_SIZE * 3.0}), BoxExplosive);
BOX_AT_TYPE(grid, ((V2) { -BOX_SIZE * 6.0, BOX_SIZE * 1.0 }), BoxExplosive); BOX_AT_TYPE(grid, ((V2){-BOX_SIZE * 6.0, BOX_SIZE * 1.0}), BoxExplosive);
BOX_AT_TYPE(grid, ((V2) { -BOX_SIZE * 6.0, -BOX_SIZE * 2.0 }), BoxExplosive); BOX_AT_TYPE(grid, ((V2){-BOX_SIZE * 6.0, -BOX_SIZE * 2.0}), BoxExplosive);
BOX_AT_TYPE(grid, ((V2) { -BOX_SIZE * 6.0, -BOX_SIZE * 3.0 }), BoxExplosive); BOX_AT_TYPE(grid, ((V2){-BOX_SIZE * 6.0, -BOX_SIZE * 3.0}), BoxExplosive);
BOX_AT_TYPE(grid, ((V2) { -BOX_SIZE * 6.0, -BOX_SIZE * 5.0 }), BoxExplosive); BOX_AT_TYPE(grid, ((V2){-BOX_SIZE * 6.0, -BOX_SIZE * 5.0}), BoxExplosive);
return get_id(gs, grid); return get_id(gs, grid);
} }
@ -1311,7 +1517,8 @@ void process(GameState* gs, float dt)
cpBodySetVelocity(p->body, v2_to_cp(box_vel(seat_inside_of))); cpBodySetVelocity(p->body, v2_to_cp(box_vel(seat_inside_of)));
// set thruster thrust from movement // set thruster thrust from movement
if (seat_inside_of->box_type == BoxCockpit) { if (seat_inside_of->box_type == BoxCockpit)
{
Entity* g = get_entity(gs, seat_inside_of->shape_parent_entity); Entity* g = get_entity(gs, seat_inside_of->shape_parent_entity);
V2 target_direction = { 0 }; V2 target_direction = { 0 };
@ -1331,7 +1538,7 @@ void process(GameState* gs, float dt)
} }
} }
#if 1 // building #if 1 // building
if (player->input.dobuild) if (player->input.dobuild)
{ {
player->input.dobuild = false; // handle the input. if didn't do this, after destruction of hovered box, would try to build on its grid with grid_index... player->input.dobuild = false; // handle the input. if didn't do this, after destruction of hovered box, would try to build on its grid with grid_index...
@ -1391,7 +1598,8 @@ void process(GameState* gs, float dt)
} }
// process entities // process entities
for (size_t i = 0; i < gs->cur_next_entity; i++) { for (size_t i = 0; i < gs->cur_next_entity; i++)
{
Entity* e = &gs->entities[i]; Entity* e = &gs->entities[i];
if (!e->exists) if (!e->exists)
continue; continue;
@ -1458,7 +1666,8 @@ void process(GameState* gs, float dt)
float energy_to_add = 0.0f; float energy_to_add = 0.0f;
BOXES_ITER(gs, cur, e) BOXES_ITER(gs, cur, e)
{ {
if (cur->box_type == BoxSolarPanel) { if (cur->box_type == BoxSolarPanel)
{
cur->sun_amount = clamp01(V2dot(box_facing_vector(cur), V2normalize(V2sub(SUN_POS, entity_pos(cur))))); cur->sun_amount = clamp01(V2dot(box_facing_vector(cur), V2normalize(V2sub(SUN_POS, entity_pos(cur)))));
energy_to_add += cur->sun_amount * SOLAR_ENERGY_PER_SECOND * dt; energy_to_add += cur->sun_amount * SOLAR_ENERGY_PER_SECOND * dt;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

235
main.c

@ -7,7 +7,6 @@
#include <enet/enet.h> #include <enet/enet.h>
#include <process.h> // starting server thread #include <process.h> // starting server thread
#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert()
#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert() #pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert()
#include "sokol_app.h" #include "sokol_app.h"
#include "sokol_gfx.h" #include "sokol_gfx.h"
@ -20,11 +19,16 @@
#include "stb_image.h" #include "stb_image.h"
#include "types.h" #include "types.h"
#include "opus.h"
#include <inttypes.h> #include <inttypes.h>
#include <string.h> // errno error message on file open #include <string.h> // errno error message on file open
#include "minilzo.h" #include "minilzo.h"
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
static struct GameState gs = { 0 }; static struct GameState gs = { 0 };
static int myplayer = -1; static int myplayer = -1;
static bool right_mouse_down = false; static bool right_mouse_down = false;
@ -58,9 +62,21 @@ static sg_image image_medbay_used;
static sg_image image_mystery; static sg_image image_mystery;
static sg_image image_explosion; static sg_image image_explosion;
static sg_image image_low_health; static sg_image image_low_health;
static sg_image image_mic_muted;
static int cur_editing_boxtype = -1; static int cur_editing_boxtype = -1;
static int cur_editing_rotation = 0; static int cur_editing_rotation = 0;
// audio
static bool muted = false;
static ma_device microphone_device;
static ma_device speaker_device;
OpusEncoder* enc;
OpusDecoder* dec;
OpusBuffer packets_to_send = { 0 };
OpusBuffer packets_to_play = { 0 };
ma_mutex send_packets_mutex = { 0 };
ma_mutex play_packets_mutex = { 0 };
static struct BoxInfo { static struct BoxInfo {
enum BoxType type; enum BoxType type;
const char* image_path; const char* image_path;
@ -141,9 +157,112 @@ load_image(const char* path)
return to_return; return to_return;
} }
void microphone_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
if (peer != NULL)
{
ma_mutex_lock(&send_packets_mutex);
OpusPacket* packet = push_packet(&packets_to_send);
if (packet != NULL)
{
opus_int16 muted_audio[VOIP_EXPECTED_FRAME_COUNT] = { 0 };
const opus_int16* audio_buffer = (const opus_int16*)pInput;
if (muted)
audio_buffer = muted_audio;
opus_int32 written = opus_encode(enc, audio_buffer, VOIP_EXPECTED_FRAME_COUNT, packet->data, VOIP_PACKET_MAX_SIZE);
packet->length = written;
}
ma_mutex_unlock(&send_packets_mutex);
}
(void)pOutput;
}
void speaker_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
{
assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
ma_mutex_lock(&play_packets_mutex);
OpusPacket* cur_packet = pop_packet(&packets_to_play);
if (cur_packet != NULL && cur_packet->length > 0) // length of 0 means skipped packet
{
opus_decode(dec, cur_packet->data, cur_packet->length, (opus_int16*)pOutput, frameCount, 0);
}
else
{
opus_decode(dec, NULL, 0, (opus_int16*)pOutput, frameCount, 0); // I think opus makes it sound good if packets are skipped with null
}
ma_mutex_unlock(&play_packets_mutex);
(void)pInput;
}
static void static void
init(void) init(void)
{ {
// audio
{
// opus
{
int error;
enc = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
assert(error == OPUS_OK);
dec = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
assert(error == OPUS_OK);
}
ma_device_config microphone_config = ma_device_config_init(ma_device_type_capture);
microphone_config.capture.format = ma_format_s16;
microphone_config.capture.channels = 1;
microphone_config.sampleRate = VOIP_SAMPLE_RATE;
microphone_config.dataCallback = microphone_data_callback;
ma_device_config speaker_config = ma_device_config_init(ma_device_type_playback);
speaker_config.playback.format = ma_format_s16;
speaker_config.playback.channels = 1;
speaker_config.sampleRate = VOIP_SAMPLE_RATE;
speaker_config.dataCallback = speaker_data_callback;
ma_result result;
result = ma_device_init(NULL, &microphone_config, &microphone_device);
if (result != MA_SUCCESS) {
Log("Failed to initialize capture device.\n");
exit(-1);
}
result = ma_device_init(NULL, &speaker_config, &speaker_device);
if (result != MA_SUCCESS)
{
ma_device_uninit(&microphone_device);
Log("Failed to init speaker\n");
exit(-1);
}
if (ma_mutex_init(&send_packets_mutex) != MA_SUCCESS) Log("Failed to init send mutex\n");
if (ma_mutex_init(&play_packets_mutex) != MA_SUCCESS) Log("Failed to init play mutex\n");
result = ma_device_start(&microphone_device);
if (result != MA_SUCCESS) {
ma_device_uninit(&microphone_device);
Log("Failed to start device.\n");
exit(-1);
}
result = ma_device_start(&speaker_device);
if (result != MA_SUCCESS)
{
ma_device_uninit(&microphone_device);
ma_device_uninit(&speaker_device);
Log("Failed to start speaker\n");
exit(-1);
}
Log("Initialized audio\n");
}
// @BeforeShip make all fprintf into logging to file, warning dialog grids on // @BeforeShip make all fprintf into logging to file, warning dialog grids on
// failure instead of exit(-1), replace the macros in sokol with this as well, // failure instead of exit(-1), replace the macros in sokol with this as well,
// like assert // like assert
@ -184,6 +303,7 @@ init(void)
image_mystery = load_image("loaded/mystery.png"); image_mystery = load_image("loaded/mystery.png");
image_explosion = load_image("loaded/explosion.png"); image_explosion = load_image("loaded/explosion.png");
image_low_health = load_image("loaded/low_health.png"); image_low_health = load_image("loaded/low_health.png");
image_mic_muted = load_image("loaded/mic_muted.png");
} }
// socket initialization // socket initialization
@ -314,7 +434,6 @@ ui(bool draw, float dt, float width, float height)
if (draw) if (draw)
sgp_push_transform(); sgp_push_transform();
// if(draw) sgp_scale(1.0f, -1.0f);
// draw spice bar // draw spice bar
if (draw) { if (draw) {
static float damage = 0.5f; static float damage = 0.5f;
@ -324,12 +443,31 @@ ui(bool draw, float dt, float width, float height)
} }
sgp_set_color(0.5f, 0.5f, 0.5f, cur_opacity); sgp_set_color(0.5f, 0.5f, 0.5f, cur_opacity);
float margin = width * 0.1f; float margin = width * 0.2f;
float bar_width = width - margin * 2.0f; float bar_width = width - margin * 2.0f;
sgp_draw_filled_rect(margin, 80.0f, bar_width, 30.0f); float y = height - 150.0f;
sgp_draw_filled_rect(margin, y, bar_width, 30.0f);
sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity); sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity);
sgp_draw_filled_rect( sgp_draw_filled_rect(
margin, 80.0f, bar_width * (1.0f - damage), 30.0f); margin, y, bar_width * (1.0f - damage), 30.0f);
}
// draw muted
static float muted_opacity = 0.0f;
if (draw) {
muted_opacity = lerp(muted_opacity, muted ? 1.0f : 0.0f, 8.0f * dt);
sgp_set_color(1.0f, 1.0f, 1.0f, muted_opacity);
float size_x = 150.0f;
float size_y = 150.0f;
sgp_set_image(0, image_mic_muted);
float x = width - size_x - 40.0f;
float y = height - size_y - 40.0f;
transform_scope {
sgp_scale_at(1.0f, -1.0f, x + size_x / 2.0f, y + size_y / 2.0f);
sgp_draw_textured_rect(x, y, size_x, size_y);
sgp_reset_image(0);
}
} }
// draw item toolbar // draw item toolbar
@ -401,7 +539,7 @@ static void draw_dots(V2 camera_pos, float gap)
for (int x = -num; x < num; x++) { for (int x = -num; x < num; x++) {
for (int y = -num; y < num; y++) { for (int y = -num; y < num; y++) {
V2 star = (V2){ (float)x * gap, (float)y * gap }; V2 star = (V2){ (float)x * gap, (float)y * gap };
if(V2lengthsqr(V2sub(star, camera_pos)) > VISION_RADIUS*VISION_RADIUS) if (V2lengthsqr(V2sub(star, camera_pos)) > VISION_RADIUS * VISION_RADIUS)
continue; continue;
star.x += hash11(star.x * 100.0f + star.y * 67.0f) * gap; star.x += hash11(star.x * 100.0f + star.y * 67.0f) * gap;
@ -444,33 +582,27 @@ frame(void)
} }
case ENET_EVENT_TYPE_RECEIVE: { case ENET_EVENT_TYPE_RECEIVE: {
// @Robust @BeforeShip use some kind of serialization strategy that char* decompressed = malloc(sizeof * decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
// checks for out of bounds and other validation instead of just size_t decompressed_max_len = MAX_SERVER_TO_CLIENT;
// casting to a struct "Alignment of structure members can be assert(LZO1X_MEM_DECOMPRESS == 0);
// different even among different compilers on the same platform,
// let alone different platforms." ma_mutex_lock(&play_packets_mutex);
// ^^ need serialization strategy that accounts for this if multiple
// platforms is happening
// https://stackoverflow.com/questions/28455163/how-can-i-portably-send-a-c-struct-through-a-network-socket
ServerToClient msg = (ServerToClient){ ServerToClient msg = (ServerToClient){
.cur_gs = &gs, .cur_gs = &gs,
.playback_buffer = &packets_to_play,
}; };
// @Robust @BeforeShip maximum acceptable message size?
char* decompressed = malloc(sizeof * decompressed * MAX_BYTES_SIZE);
size_t decompressed_max_len = MAX_BYTES_SIZE;
assert(LZO1X_MEM_DECOMPRESS == 0);
int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL); int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL);
// @Robust not sure what return_value is, error test on it somehow
if (return_value == LZO_E_OK) if (return_value == LZO_E_OK)
{ {
from_bytes(&msg, decompressed, decompressed_max_len, false, false); server_to_client_deserialize(&msg, decompressed, decompressed_max_len, false);
myplayer = msg.your_player; myplayer = msg.your_player;
} }
else { else {
Log("Couldn't decompress gamestate packet, error code %d from lzo\n", return_value); Log("Couldn't decompress gamestate packet, error code %d from lzo\n", return_value);
} }
enet_packet_destroy(event.packet); ma_mutex_unlock(&play_packets_mutex);
free(decompressed); free(decompressed);
enet_packet_destroy(event.packet);
break; break;
} }
@ -556,8 +688,6 @@ frame(void)
// Create and send input packet // Create and send input packet
{ {
// @Robust accumulate total time and send input at rate like 20 hz, not
// every frame
static size_t last_frame_id = 0; static size_t last_frame_id = 0;
InputFrame cur_input_frame = { 0 }; InputFrame cur_input_frame = { 0 };
@ -600,19 +730,37 @@ frame(void)
InputFrame last_last_frame = last_frame; InputFrame last_last_frame = last_frame;
last_frame = client_to_server.inputs[i + 1]; last_frame = client_to_server.inputs[i + 1];
client_to_server.inputs[i + 1] = last_last_frame; client_to_server.inputs[i + 1] = last_last_frame;
// these references, in old input frames, may have been deleted by the time we
// want to send them.
client_to_server.inputs[i + 1].seat_to_inhabit = cur_input_frame.seat_to_inhabit;
client_to_server.inputs[i + 1].grid_hand_pos_local_to = cur_input_frame.grid_hand_pos_local_to;
} }
cur_input_frame.tick = tick(&gs); cur_input_frame.tick = tick(&gs);
client_to_server.inputs[0] = cur_input_frame; client_to_server.inputs[0] = cur_input_frame;
last_frame_id += 1; last_frame_id += 1;
} }
static double last_input_sent_time = 0.0; static int64_t last_sent_input_time = 0;
if (fabs(last_input_sent_time - time) > TIME_BETWEEN_INPUT_PACKETS) { if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) > TIME_BETWEEN_INPUT_PACKETS) {
ENetPacket* packet = enet_packet_create((void*)&client_to_server, ma_mutex_lock(&send_packets_mutex);
sizeof(client_to_server), client_to_server.mic_data = &packets_to_send;
ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); char serialized[MAX_CLIENT_TO_SERVER] = { 0 };
enet_peer_send(peer, 0, packet); // @Robust error check this size_t out_len = 0;
last_input_sent_time = time; if (client_to_server_serialize(&gs, &client_to_server, serialized, &out_len, MAX_CLIENT_TO_SERVER))
{
ENetPacket* packet = enet_packet_create((void*)serialized,
out_len,
ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(peer, 0, packet); // @Robust error check this
last_sent_input_time = stm_now();
}
else {
Log("Failed to serialize client to server!\n");
}
client_to_server.mic_data = NULL;
ma_mutex_unlock(&send_packets_mutex);
} }
} }
@ -873,13 +1021,13 @@ frame(void)
} // world space transform end } // world space transform end
// low health // low health
if (myentity() != NULL) if (myentity() != NULL)
{ {
sgp_set_color(1.0f, 1.0f, 1.0f, myentity()->damage); sgp_set_color(1.0f, 1.0f, 1.0f, myentity()->damage);
sgp_set_image(0, image_low_health); sgp_set_image(0, image_low_health);
draw_texture_rectangle_centered((V2) { width / 2.0f, height / 2.0f }, (V2) { width, height }); draw_texture_rectangle_centered((V2) { width / 2.0f, height / 2.0f }, (V2) { width, height });
sgp_reset_image(0); sgp_reset_image(0);
} }
// UI drawn in screen space // UI drawn in screen space
ui(true, dt, width, height); ui(true, dt, width, height);
@ -895,6 +1043,15 @@ frame(void)
void cleanup(void) void cleanup(void)
{ {
ma_mutex_uninit(&send_packets_mutex);
ma_mutex_uninit(&play_packets_mutex);
ma_device_uninit(&microphone_device);
ma_device_uninit(&speaker_device);
opus_encoder_destroy(enc);
opus_decoder_destroy(dec);
destroy(&gs); destroy(&gs);
free(gs.entities); free(gs.entities);
sgp_shutdown(); sgp_shutdown();
@ -915,6 +1072,10 @@ void event(const sapp_event* e)
cur_editing_rotation += 1; cur_editing_rotation += 1;
cur_editing_rotation %= RotationLast; cur_editing_rotation %= RotationLast;
} }
if (e->key_code == SAPP_KEYCODE_M)
{
muted = !muted;
}
if (e->key_code == SAPP_KEYCODE_F11) if (e->key_code == SAPP_KEYCODE_F11)
{ {
fullscreened = !fullscreened; fullscreened = !fullscreened;

@ -10,10 +10,16 @@
#include "minilzo.h" #include "minilzo.h"
#include "opus.h"
#ifdef __unix #ifdef __unix
#define fopen_s(pFile,filename,mode) ((*(pFile))=fopen((filename),(mode)))==NULL #define fopen_s(pFile, filename, mode) ((*(pFile)) = fopen((filename), (mode))) == NULL
#endif #endif
#define CONNECTED_PEERS(host, cur) \
for (ENetPeer *cur = host->peers; cur < host->peers + host->peerCount; cur++) \
if (cur->state == ENET_PEER_STATE_CONNECTED)
// started in a thread from host // started in a thread from host
void server(void* world_save_name) void server(void* world_save_name)
{ {
@ -26,6 +32,20 @@ void server(void* world_save_name)
initialize(&gs, entity_data, entities_size); initialize(&gs, entity_data, entities_size);
Log("Allocated %zu bytes for entities\n", entities_size); Log("Allocated %zu bytes for entities\n", entities_size);
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) if (world_save_name != NULL)
{ {
size_t read_game_data_buffer_size = entities_size; size_t read_game_data_buffer_size = entities_size;
@ -49,14 +69,19 @@ void server(void* world_save_name)
ServerToClient msg = (ServerToClient){ ServerToClient msg = (ServerToClient){
.cur_gs = &gs, .cur_gs = &gs,
}; };
from_bytes(&msg, read_game_data, actual_length, true, true); server_to_client_deserialize(&msg, read_game_data, actual_length, true);
fclose(file); fclose(file);
} }
free(read_game_data); 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_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) #define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece)
// one box policy // one box policy
@ -79,11 +104,11 @@ void server(void* world_save_name)
cpBodySetVelocity(grid->body, cpv(-0.1, 0.0)); cpBodySetVelocity(grid->body, cpv(-0.1, 0.0));
cpBodySetAngularVelocity(grid->body, 1.0f); cpBodySetAngularVelocity(grid->body, 1.0f);
BOX_AT(grid, ((V2) { 0 })); BOX_AT(grid, ((V2){0}));
BOX_AT(grid, ((V2) { BOX_SIZE, 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, 0}));
BOX_AT(grid, ((V2) { 2.0 * BOX_SIZE, BOX_SIZE })); 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.0 * BOX_SIZE, -BOX_SIZE}));
} }
if (enet_initialize() != 0) if (enet_initialize() != 0)
@ -93,7 +118,7 @@ void server(void* world_save_name)
} }
ENetAddress address; ENetAddress address;
ENetHost* server; ENetHost* enet_host;
int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS); int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS);
if (sethost != 0) if (sethost != 0)
{ {
@ -101,12 +126,12 @@ void server(void* world_save_name)
} }
/* Bind the server to port 1234. */ /* Bind the server to port 1234. */
address.port = SERVER_PORT; address.port = SERVER_PORT;
server = enet_host_create(&address /* the address to bind the server host to */, 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 */, MAX_PLAYERS /* allow up to MAX_PLAYERS clients and/or outgoing connections */,
2 /* allow up to 2 channels to be used, 0 and 1 */, 2 /* allow up to 2 channels to be used, 0 and 1 */,
0 /* assume any amount of incoming bandwidth */, 0 /* assume any amount of incoming bandwidth */,
0 /* assume any amount of outgoing bandwidth */); 0 /* assume any amount of outgoing bandwidth */);
if (server == NULL) if (enet_host == NULL)
{ {
fprintf(stderr, fprintf(stderr,
"An error occurred while trying to create an ENet server host.\n"); "An error occurred while trying to create an ENet server host.\n");
@ -125,7 +150,7 @@ void server(void* world_save_name)
// @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
while (true) while (true)
{ {
int ret = enet_host_service(server, &event, 0); int ret = enet_host_service(enet_host, &event, 0);
if (ret == 0) if (ret == 0)
break; break;
if (ret < 0) if (ret < 0)
@ -162,70 +187,79 @@ void server(void* world_save_name)
gs.players[player_slot] = (struct Player){ 0 }; gs.players[player_slot] = (struct Player){ 0 };
gs.players[player_slot].connected = true; gs.players[player_slot].connected = true;
player_to_latest_id_processed[player_slot] = 0; 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 #ifdef UNLOCK_ALL
gs.players[player_slot].unlocked_bombs = true; gs.players[player_slot].unlocked_bombs = true;
#endif #endif
} }
break;
} }
break;
case ENET_EVENT_TYPE_RECEIVE: case ENET_EVENT_TYPE_RECEIVE:
{ {
//Log("A packet of length %zu was received on channel %u.\n", // Log("A packet of length %zu was received on channel %u.\n",
// event.packet->dataLength, // event.packet->dataLength,
//event.channelID); // event.channelID);
if (event.packet->dataLength == 0)
size_t length = event.packet->dataLength;
if (length != sizeof(struct ClientToServer))
{ {
Log("Length did not match up...\n"); Log("Wtf an empty packet from enet?\n");
} }
else else {
{
struct ClientToServer received = { 0 };
memcpy(&received, event.packet->data, length);
int64_t player_slot = (int64_t)event.peer->data; int64_t player_slot = (int64_t)event.peer->data;
size_t latest_id = player_to_latest_id_processed[player_slot]; size_t length = event.packet->dataLength;
struct ClientToServer received = { .mic_data = player_voip_buffers[player_slot] };
if (received.inputs[0].id > latest_id) 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 for (int i = INPUT_BUFFER - 1; i >= 0; i--)
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)
{ {
gs.players[player_slot].input.grid_hand_pos_local_to = cur_input.grid_hand_pos_local_to; if (received.inputs[i].tick == 0) // empty input
gs.players[player_slot].input.dobuild = cur_input.dobuild; continue;
gs.players[player_slot].input.build_type = cur_input.build_type; if (received.inputs[i].id <= latest_id)
gs.players[player_slot].input.build_rotation = cur_input.build_rotation; 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)
{
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. */ /* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet); enet_packet_destroy(event.packet);
break;
} }
break;
case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT:
{ {
@ -236,111 +270,135 @@ void server(void* world_save_name)
{ {
entity_destroy(&gs, player_body); entity_destroy(&gs, player_body);
} }
opus_encoder_destroy(player_encoders[player_index]);
opus_decoder_destroy(player_decoders[player_index]);
gs.players[player_index].connected = false; gs.players[player_index].connected = false;
// box_destroy(&gs.players[player_index].box); clear_buffer(player_voip_buffers[player_index]);
event.peer->data = NULL; 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)
{
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) total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time));
{ last_processed_time = stm_now();
last_saved_world_time = stm_now(); // @Robost @BeforeShip if can't process quick enough will be stuck being lagged behind, think of a solution for this...
ServerToClient msg = (ServerToClient){ const float max_time = 5.0f * TIMESTEP;
.cur_gs = &gs, if (total_time > max_time)
};
size_t out_len = 0;
into_bytes(&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); Log("Abnormally large total time %f, clamping\n", total_time);
total_time = max_time;
} }
else { bool processed = false;
size_t data_written = fwrite(world_save_buffer, sizeof(*world_save_buffer), out_len, save_file); while (total_time > TIMESTEP)
if (data_written != out_len) {
{ processed = true;
Log("Failed to save world data, wanted to write %zu but could only write %zu\n", out_len, data_written); process(&gs, TIMESTEP);
} total_time -= TIMESTEP;
else {
Log("Saved game world to %s\n", (const char*)world_save_name);
}
fclose(save_file);
} }
}
if (processed) if (world_save_name != NULL && (stm_sec(stm_diff(stm_now(), last_saved_world_time))) > TIME_BETWEEN_WORLD_SAVE)
{
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) 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))
{ {
continue; 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);
}
} }
int this_player_index = (int)(int64_t)server->peers[i].data; else
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_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 = this_player_index;
size_t len = 0;
into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE, this_player_entity, false);
if (len > MAX_BYTES_SIZE - 8)
{ {
Log("Too much data quitting!\n"); Log("URGENT: FAILED TO SAVE WORLD FILE!\n");
exit(-1);
} }
}
size_t compressed_len = 0; if (processed)
lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void*)lzo_working_mem); {
static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = { 0 };
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);
ServerToClient to_send = (ServerToClient){
.cur_gs = &gs,
.your_player = this_player_index,
.playback_buffer = player_voip_buffers[this_player_index],
};
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 #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 #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);
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);
// @Robust error check this if (err < 0)
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);
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(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); free(world_save_buffer);
destroy(&gs); destroy(&gs);
free(entity_data); free(entity_data);
enet_host_destroy(server); enet_host_destroy(enet_host);
enet_deinitialize(); enet_deinitialize();
} }

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

@ -16,7 +16,8 @@
#define THRUSTER_FORCE 12.0f #define THRUSTER_FORCE 12.0f
#define THRUSTER_ENERGY_USED_PER_SECOND 0.005f #define THRUSTER_ENERGY_USED_PER_SECOND 0.005f
#define VISION_RADIUS 12.0f #define VISION_RADIUS 12.0f
#define MAX_BYTES_SIZE 1024 * 512 // maximum size of serialized gamestate buffer #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 SUN_RADIUS 10.0f
#define INSTANT_DEATH_DISTANCE_FROM_SUN 2000.0f #define INSTANT_DEATH_DISTANCE_FROM_SUN 2000.0f
#define SUN_POS ((V2){50.0f,0.0f}) #define SUN_POS ((V2){50.0f,0.0f})
@ -33,6 +34,13 @@
#define GOLD_UNLOCK_RADIUS 1.0f #define GOLD_UNLOCK_RADIUS 1.0f
#define TIME_BETWEEN_WORLD_SAVE 30.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_EXCPECTED_FRAME_COUNT)) // in seconds
#define VOIP_PACKET_MAX_SIZE 4000
#define TIMESTEP (1.0f / 60.0f) // not required to simulate at this, but this defines what tick the game is on #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) #define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
#define SERVER_PORT 2551 #define SERVER_PORT 2551
@ -70,6 +78,10 @@ typedef void cpShape;
#include <stdbool.h> #include <stdbool.h>
#ifndef OPUS_TYPES_H
typedef __int32 opus_int32;
#endif
#ifndef _STDBOOL #ifndef _STDBOOL
#define bool _Bool #define bool _Bool
@ -81,9 +93,9 @@ typedef void cpShape;
typedef sgp_vec2 V2; typedef sgp_vec2 V2;
typedef sgp_point P2; typedef sgp_point P2;
#define Log(...) \ #define Log(...){ \
fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \ fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \
fprintf(stdout, __VA_ARGS__) fprintf(stdout, __VA_ARGS__);}
enum BoxType enum BoxType
{ {
@ -109,7 +121,7 @@ enum CompassRotation
// when generation is 0, invalid ID // when generation is 0, invalid ID
typedef struct EntityID 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 unsigned int index; // index into the entity arena
} EntityID; } EntityID;
@ -246,16 +258,32 @@ 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 typedef struct ServerToClient
{ {
struct GameState* cur_gs; struct GameState* cur_gs;
OpusBuffer* playback_buffer;
int your_player; int your_player;
} ServerToClient; } ServerToClient;
struct ClientToServer
typedef struct ClientToServer
{ {
OpusBuffer* mic_data; // on serialize, flushes this of packets. On deserialize, fills it
InputFrame inputs[INPUT_BUFFER]; InputFrame inputs[INPUT_BUFFER];
}; } ClientToServer;
// server // server
void server(void* data); // data parameter required from thread api... void server(void* data); // data parameter required from thread api...
@ -267,8 +295,12 @@ void destroy(struct GameState* gs);
void process(struct GameState* gs, float dt); // does in place void process(struct GameState* gs, float dt); // does in place
Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius); Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius);
uint64_t tick(struct GameState* gs); uint64_t tick(struct GameState* gs);
void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player, bool write_varnames);
void from_bytes(struct ServerToClient* msg, char* bytes, size_t max_len, bool write_varnames, bool from_disk); // 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 // entities
Entity* get_entity(struct GameState* gs, EntityID id); Entity* get_entity(struct GameState* gs, EntityID id);
@ -283,10 +315,6 @@ 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 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) #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 // grid
void grid_create(struct GameState* gs, Entity* e); void grid_create(struct GameState* gs, Entity* e);
void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos); void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos);
@ -309,10 +337,78 @@ void dbg_drawall();
void dbg_line(V2 from, V2 to); void dbg_line(V2 from, V2 to);
void dbg_rect(V2 center); void dbg_rect(V2 center);
// helper
#define SKIPNULL(thing) \ static void clear_buffer(OpusBuffer* buff)
if (thing == NULL) \ {
continue *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;
}
// how many unpopped packets there are, can't check for null on pop_packet because
// could be a skipped packet. This is used in a for loop to flush a packet buffer
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;
}
// 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;
}
// all the math is static so that it can be defined in each compilation unit its included in // all the math is static so that it can be defined in each compilation unit its included in

Binary file not shown.
Loading…
Cancel
Save