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
flight*.zip
flight_server

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,7 +174,7 @@
<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)\flight\thirdparty\Chipmunk2D\include\chipmunk;$(ProjectDir)\thirdparty;$(ProjectDir)\thirdparty\Chipmunk2D\include;$(ProjectDir)\thirdparty\minilzo;$(ProjectDir)\thirdparty\opus\include</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>

@ -5,6 +5,11 @@
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
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
setlocal enabledelayedexpansion enableextensions
@ -16,8 +21,9 @@ 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" /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%
%MUNKSRC%^
%OPUSLIB%

@ -41,7 +41,8 @@ static cpVect v2_to_cp(V2 v)
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];
return (!the_entity->exists || the_entity->generation != id.generation);
}
@ -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));
}
else {
else
{
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[] = {
(V2) {
.x = -1.0f, .y = 0.0f
},
(V2) {
},
(V2) {
.x = 1.0f, .y = 0.0f
},
(V2) {
},
(V2) {
.x = 0.0f, .y = 1.0f
},
(V2) {
},
(V2) {
.x = 0.0f, .y = -1.0f
},
},
};
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);
entity_a->damage += damage;
entity_b->damage += damage;
}
// 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, a, NULL);
// cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL);
// cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL);
return true; // keep colliding
}
@ -533,7 +534,8 @@ void destroy(GameState* gs)
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;
}
@ -596,13 +598,16 @@ float box_rotation(Entity* box)
}
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))));
}
else if (e->is_explosion) {
else if (e->is_explosion)
{
return e->explosion_pos;
}
else {
else
{
assert(e->body != NULL);
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 max_size;
Entity* for_player;
size_t max_entity_index; // for error checking
bool write_varnames;
bool save_or_load_from_disk;
// output
uint32_t version;
} 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 };
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);
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;
assert(ser->cursor < ser->max_size);
SER_ASSERT(ser->cursor < ser->max_size);
}
}
else
{
if (ser->write_varnames) {
if (ser->write_varnames)
{
char read_name[512] = { 0 };
for (int i = 0; i < var_name_len; i++)
{
read_name[i] = ser->bytes[ser->cursor];
ser->cursor += 1;
assert(ser->cursor <= ser->max_size);
SER_ASSERT(ser->cursor <= ser->max_size);
}
read_name[var_name_len] = '\0';
// advance past digits
@ -683,90 +717,106 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name
read++;
while (*var >= '0' && *var <= '9')
var++;
if (strcmp(read, var) != 0)
{
printf("%s:%d | Expected variable %s but got %sn\n", file, line, var_name, read_name);
*(char*)NULL = 0;
SER_ASSERT(strcmp(read, var) == 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;
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)
enum GameVersion {
enum GameVersion
{
VInitial,
VAddedTest,
VAddedSerToDisk,
VRemovedTest,
VMax, // this minus one will be the version used
};
// @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->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_V2(ser, &data->vel);
SER_MAYBE_RETURN(ser_V2(ser, &data->pos));
SER_MAYBE_RETURN(ser_V2(ser, &data->vel));
SER_VAR(&data->rotation);
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->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->movement);
SER_VAR(&i->id);
SER_MAYBE_RETURN(ser_V2(ser, &i->movement));
SER_VAR(&i->seat_action);
ser_entityid(ser, &i->seat_to_inhabit);
SER_VAR(&i->hand_pos);
ser_entityid(ser, &i->grid_hand_pos_local_to);
SER_MAYBE_RETURN(ser_entityid(ser, &i->seat_to_inhabit));
SER_MAYBE_RETURN(ser_V2(ser, &i->hand_pos));
SER_MAYBE_RETURN(ser_entityid(ser, &i->grid_hand_pos_local_to));
SER_VAR(&i->dobuild);
SER_VAR(&i->build_type);
SER_ASSERT(i->build_type >= 0);
SER_ASSERT(i->build_type < BoxLast);
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);
if (p->connected)
{
SER_VAR(&p->unlocked_bombs);
ser_entityid(ser, &p->entity);
ser_inputframe(ser, &p->input);
SER_MAYBE_RETURN(ser_entityid(ser, &p->entity));
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->damage);
int test;
if (ser->serializing) test = 27;
if (ser->version >= VAddedTest)
if (ser->version < VRemovedTest && ser->version >= VAddedTest)
SER_VAR(&test);
else test = 27;
assert(test == 27);
bool has_body = ser->serializing && e->body != NULL;
SER_VAR(&has_body);
@ -776,7 +826,7 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
struct BodyData body_data;
if (ser->serializing)
populate(e->body, &body_data);
ser_bodydata(ser, &body_data);
SER_MAYBE_RETURN(ser_bodydata(ser, &body_data));
if (!ser->serializing)
{
create_body(gs, e);
@ -789,20 +839,21 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
if (has_shape)
{
ser_V2(ser, &e->shape_size);
ser_entityid(ser, &e->shape_parent_entity);
SER_MAYBE_RETURN(ser_V2(ser, &e->shape_size));
SER_MAYBE_RETURN(ser_entityid(ser, &e->shape_parent_entity));
Entity* parent = get_entity(gs, e->shape_parent_entity);
assert(parent != NULL);
SER_ASSERT(parent != NULL);
V2 shape_pos;
if (ser->serializing)
shape_pos = entity_shape_pos(e);
SER_VAR(&shape_pos);
SER_MAYBE_RETURN(ser_V2(ser, &shape_pos));
float shape_mass;
if (ser->serializing)
shape_mass = entity_shape_mass(e);
SER_VAR(&shape_mass);
SER_ASSERT(!isnan(shape_mass));
cpShapeFilter filter;
if (ser->serializing)
@ -822,16 +873,16 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
SER_VAR(&e->is_player);
if (e->is_player)
{
assert(e->no_save_to_disk);
ser_entityid(ser, &e->currently_inside_of_box);
SER_ASSERT(e->no_save_to_disk);
SER_MAYBE_RETURN(ser_entityid(ser, &e->currently_inside_of_box));
SER_VAR(&e->goldness);
}
SER_VAR(&e->is_explosion);
if (e->is_explosion)
{
ser_V2(ser, &e->explosion_pos);
ser_V2(ser, &e->explosion_vel);
SER_MAYBE_RETURN(ser_V2(ser, &e->explosion_pos));
SER_MAYBE_RETURN(ser_V2(ser, &e->explosion_vel));
SER_VAR(&e->explosion_progresss);
}
@ -839,7 +890,7 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e)
if (e->is_grid)
{
SER_VAR(&e->total_energy_capacity);
ser_entityid(ser, &e->boxes);
SER_MAYBE_RETURN(ser_entityid(ser, &e->boxes));
}
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->always_visible);
SER_VAR(&e->is_explosion_unlock);
ser_entityid(ser, &e->next_box);
ser_entityid(ser, &e->prev_box);
SER_MAYBE_RETURN(ser_entityid(ser, &e->next_box));
SER_MAYBE_RETURN(ser_entityid(ser, &e->prev_box));
SER_VAR(&e->compass_rotation);
SER_VAR(&e->indestructible);
SER_VAR(&e->thrust);
SER_VAR(&e->wanted_thrust);
SER_VAR(&e->energy_used);
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_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;
@ -870,10 +972,11 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
if (ser->serializing)
cur_next_entity = gs->cur_next_entity;
SER_VAR(&cur_next_entity);
SER_ASSERT(cur_next_entity <= ser->max_entity_index);
if (!ser->serializing)
{
// avoid a memset here very expensive
// avoid a memset here very expensive. que rico!
destroy(gs);
initialize(gs, gs->entities, gs->max_entities * sizeof(*gs->entities));
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(&gs->time);
ser_V2(ser, &gs->goldpos);
SER_MAYBE_RETURN(ser_V2(ser, &gs->goldpos));
if (!ser->save_or_load_from_disk)
{
// @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++)
{
ser_player(ser, &gs->players[i]);
SER_MAYBE_RETURN(ser_player(ser, &gs->players[i]));
}
}
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++)
{
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->is_box && !e->is_grid)
@ -913,7 +1019,8 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
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));
if (cur->always_visible) this_box_in_range = true;
if (cur->always_visible)
this_box_in_range = true;
if (this_box_in_range)
{
if (!serialized_grid_yet)
@ -924,14 +1031,13 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
// serialize this box
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);
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_entity(ser, gs, cur);
SER_MAYBE_RETURN(ser_entity(ser, gs, cur));
}
}
}
}
#undef SER_ENTITY
@ -950,24 +1056,27 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
break;
size_t next_index;
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];
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);
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)
{
assert(last_grid != NULL);
assert(last_grid == get_entity(gs, e->shape_parent_entity));
SER_ASSERT(last_grid != NULL);
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->next_box = (EntityID){ 0 };
box_add_to_boxes(gs, last_grid, e);
}
if (e->is_grid) {
if (e->is_grid)
{
e->boxes = (EntityID){ 0 };
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
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 != NULL);
@ -998,6 +1108,7 @@ void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t
.cursor = 0,
.max_size = max_len,
.for_player = for_this_player,
.max_entity_index = msg->cur_gs->cur_next_entity,
.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.write_varnames = write_varnames;
ser.write_varnames = to_disk;
#ifdef WRITE_VARNAMES
ser.write_varnames = true;
#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..
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 != NULL);
SerState ser = (SerState){
SerState servar = (SerState){
.bytes = bytes,
.serializing = false,
.cursor = 0,
.max_size = max_len,
.max_entity_index = msg->cur_gs->max_entities,
.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
ser.write_varnames = true;
#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
@ -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);
assert(parent->body != NULL);
cpBodyApplyImpulseAtWorldPoint(parent->body, v2_to_cp(impulse), v2_to_cp(from_pos));
}
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);
}
else {
else
{
return V2add(entity_pos(player), input->hand_pos);
}
}
@ -1167,7 +1365,15 @@ V2 box_vel(Entity* box)
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)
bool indestructible = false;
@ -1180,28 +1386,28 @@ EntityID create_spacestation(GameState* gs)
box_create(gs, explosion_box, grid, (V2) { 0 });
explosion_box->is_explosion_unlock = true;
explosion_box->no_save_to_disk = true;
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 * 3, 0 }), BoxHullpiece);
BOX_AT_TYPE(grid, ((V2) { BOX_SIZE * 4, 0 }), BoxHullpiece);
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 * 3, 0}), BoxHullpiece);
BOX_AT_TYPE(grid, ((V2){BOX_SIZE * 4, 0}), BoxHullpiece);
indestructible = true;
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)
{
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;
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 * 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 * 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);
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 * 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 * 5.0}), BoxExplosive);
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)));
// 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);
V2 target_direction = { 0 };
@ -1391,7 +1598,8 @@ void process(GameState* gs, float dt)
}
// 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];
if (!e->exists)
continue;
@ -1458,7 +1666,8 @@ void process(GameState* gs, float dt)
float energy_to_add = 0.0f;
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)))));
energy_to_add += cur->sun_amount * SOLAR_ENERGY_PER_SECOND * dt;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

217
main.c

@ -7,7 +7,6 @@
#include <enet/enet.h>
#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()
#include "sokol_app.h"
#include "sokol_gfx.h"
@ -20,11 +19,16 @@
#include "stb_image.h"
#include "types.h"
#include "opus.h"
#include <inttypes.h>
#include <string.h> // errno error message on file open
#include "minilzo.h"
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
static struct GameState gs = { 0 };
static int myplayer = -1;
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_explosion;
static sg_image image_low_health;
static sg_image image_mic_muted;
static int cur_editing_boxtype = -1;
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 {
enum BoxType type;
const char* image_path;
@ -141,9 +157,112 @@ load_image(const char* path)
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
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
// failure instead of exit(-1), replace the macros in sokol with this as well,
// like assert
@ -184,6 +303,7 @@ init(void)
image_mystery = load_image("loaded/mystery.png");
image_explosion = load_image("loaded/explosion.png");
image_low_health = load_image("loaded/low_health.png");
image_mic_muted = load_image("loaded/mic_muted.png");
}
// socket initialization
@ -314,7 +434,6 @@ ui(bool draw, float dt, float width, float height)
if (draw)
sgp_push_transform();
// if(draw) sgp_scale(1.0f, -1.0f);
// draw spice bar
if (draw) {
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);
float margin = width * 0.1f;
float margin = width * 0.2f;
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_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
@ -401,7 +539,7 @@ static void draw_dots(V2 camera_pos, float gap)
for (int x = -num; x < num; x++) {
for (int y = -num; y < num; y++) {
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;
star.x += hash11(star.x * 100.0f + star.y * 67.0f) * gap;
@ -444,33 +582,27 @@ frame(void)
}
case ENET_EVENT_TYPE_RECEIVE: {
// @Robust @BeforeShip use some kind of serialization strategy that
// checks for out of bounds and other validation instead of just
// casting to a struct "Alignment of structure members can be
// different even among different compilers on the same platform,
// let alone different platforms."
// ^^ 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
char* decompressed = malloc(sizeof * decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
size_t decompressed_max_len = MAX_SERVER_TO_CLIENT;
assert(LZO1X_MEM_DECOMPRESS == 0);
ma_mutex_lock(&play_packets_mutex);
ServerToClient msg = (ServerToClient){
.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);
// @Robust not sure what return_value is, error test on it somehow
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;
}
else {
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);
enet_packet_destroy(event.packet);
break;
}
@ -556,8 +688,6 @@ frame(void)
// 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;
InputFrame cur_input_frame = { 0 };
@ -600,19 +730,37 @@ frame(void)
InputFrame last_last_frame = last_frame;
last_frame = client_to_server.inputs[i + 1];
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);
client_to_server.inputs[0] = cur_input_frame;
last_frame_id += 1;
}
static double last_input_sent_time = 0.0;
if (fabs(last_input_sent_time - time) > TIME_BETWEEN_INPUT_PACKETS) {
ENetPacket* packet = enet_packet_create((void*)&client_to_server,
sizeof(client_to_server),
static int64_t last_sent_input_time = 0;
if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) > TIME_BETWEEN_INPUT_PACKETS) {
ma_mutex_lock(&send_packets_mutex);
client_to_server.mic_data = &packets_to_send;
char serialized[MAX_CLIENT_TO_SERVER] = { 0 };
size_t out_len = 0;
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_input_sent_time = time;
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);
}
}
@ -895,6 +1043,15 @@ frame(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);
free(gs.entities);
sgp_shutdown();
@ -915,6 +1072,10 @@ void event(const sapp_event* e)
cur_editing_rotation += 1;
cur_editing_rotation %= RotationLast;
}
if (e->key_code == SAPP_KEYCODE_M)
{
muted = !muted;
}
if (e->key_code == SAPP_KEYCODE_F11)
{
fullscreened = !fullscreened;

@ -10,10 +10,16 @@
#include "minilzo.h"
#include "opus.h"
#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
#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
void server(void* world_save_name)
{
@ -26,6 +32,20 @@ void server(void* world_save_name)
initialize(&gs, entity_data, 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)
{
size_t read_game_data_buffer_size = entities_size;
@ -49,14 +69,19 @@ void server(void* world_save_name)
ServerToClient msg = (ServerToClient){
.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);
}
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)
// one box policy
@ -79,11 +104,11 @@ void server(void* world_save_name)
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)
@ -93,7 +118,7 @@ void server(void* world_save_name)
}
ENetAddress address;
ENetHost* server;
ENetHost* enet_host;
int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS);
if (sethost != 0)
{
@ -101,12 +126,12 @@ void server(void* world_save_name)
}
/* Bind the server to port 1234. */
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 */,
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");
@ -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
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)
@ -162,30 +187,41 @@ void server(void* world_save_name)
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
}
break;
}
break;
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.channelID);
// event.channelID);
if (event.packet->dataLength == 0)
{
Log("Wtf an empty packet from enet?\n");
}
else {
int64_t player_slot = (int64_t)event.peer->data;
size_t length = event.packet->dataLength;
if (length != sizeof(struct ClientToServer))
struct ClientToServer received = { .mic_data = player_voip_buffers[player_slot] };
if (!client_to_server_deserialize(&gs, &received, event.packet->data, event.packet->dataLength))
{
Log("Length did not match up...\n");
Log("Bad packet from client %d\n", (int)player_slot);
}
else
{
struct ClientToServer received = { 0 };
memcpy(&received, event.packet->data, length);
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)
@ -219,13 +255,11 @@ void server(void* world_save_name)
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:
{
@ -236,13 +270,18 @@ void server(void* world_save_name)
{
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;
// 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;
}
}
@ -270,48 +309,56 @@ void server(void* world_save_name)
.cur_gs = &gs,
};
size_t out_len = 0;
into_bytes(&msg, world_save_buffer, &out_len, entities_size, NULL, true);
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 {
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 {
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++)
CONNECTED_PEERS(enet_host, cur)
{
if (server->peers[i].state != ENET_PEER_STATE_CONNECTED)
{
continue;
}
int this_player_index = (int)(int64_t)server->peers[i].data;
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;
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;
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;
into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE, this_player_entity, false);
if (len > MAX_BYTES_SIZE - 8)
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);
@ -323,24 +370,35 @@ void server(void* world_save_name)
#ifdef LOG_GAMESTATE_SIZE
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);
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(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();
}

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_ENERGY_USED_PER_SECOND 0.005f
#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 INSTANT_DEATH_DISTANCE_FROM_SUN 2000.0f
#define SUN_POS ((V2){50.0f,0.0f})
@ -33,6 +34,13 @@
#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_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 TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
#define SERVER_PORT 2551
@ -70,6 +78,10 @@ typedef void cpShape;
#include <stdbool.h>
#ifndef OPUS_TYPES_H
typedef __int32 opus_int32;
#endif
#ifndef _STDBOOL
#define bool _Bool
@ -81,9 +93,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
{
@ -109,7 +121,7 @@ enum CompassRotation
// 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;
@ -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
{
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...
@ -267,8 +295,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* 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
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 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);
@ -309,10 +337,78 @@ void dbg_drawall();
void dbg_line(V2 from, V2 to);
void dbg_rect(V2 center);
// helper
#define SKIPNULL(thing) \
if (thing == NULL) \
continue
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;
}
// 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

Binary file not shown.
Loading…
Cancel
Save