Compare commits

...

2 Commits

@ -105,6 +105,7 @@
<PreprocessorDefinitions>_DEBUG;_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</AdditionalIncludeDirectories>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -192,4 +193,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

@ -15,174 +15,172 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\debugdraw.c">
<ClCompile Include="debugdraw.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\gamestate.c">
<ClCompile Include="gamestate.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\main.c">
<ClCompile Include="main.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\server.c">
<ClCompile Include="server.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\sokol_impl.c">
<ClCompile Include="sokol_impl.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\chipmunk.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\chipmunk.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpArbiter.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpArbiter.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpArray.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpArray.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpBBTree.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpBBTree.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpBody.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpBody.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpCollision.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpCollision.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpConstraint.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpConstraint.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpDampedRotarySpring.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpDampedRotarySpring.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpDampedSpring.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpDampedSpring.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpGearJoint.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpGearJoint.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpGrooveJoint.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpGrooveJoint.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpHashSet.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpHashSet.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpHastySpace.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpHastySpace.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpMarch.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpMarch.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpPinJoint.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpPinJoint.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpPivotJoint.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpPivotJoint.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpPolyline.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpPolyline.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpPolyShape.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpPolyShape.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpRatchetJoint.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpRatchetJoint.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpRobust.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpRobust.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpRotaryLimitJoint.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpRotaryLimitJoint.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpShape.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpShape.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSimpleMotor.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSimpleMotor.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSlideJoint.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSlideJoint.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSpace.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSpace.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSpaceComponent.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSpaceComponent.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSpaceDebug.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSpaceDebug.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSpaceHash.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSpaceHash.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSpaceQuery.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSpaceQuery.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSpaceStep.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSpaceStep.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSpatialIndex.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSpatialIndex.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\Chipmunk2D\src\cpSweep1D.c">
<ClCompile Include="thirdparty\Chipmunk2D\src\cpSweep1D.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\enet\callbacks.c">
<ClCompile Include="thirdparty\enet\callbacks.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\enet\compress.c">
<ClCompile Include="thirdparty\enet\compress.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\enet\host.c">
<ClCompile Include="thirdparty\enet\host.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\enet\list.c">
<ClCompile Include="thirdparty\enet\list.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\enet\packet.c">
<ClCompile Include="thirdparty\enet\packet.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\enet\peer.c">
<ClCompile Include="thirdparty\enet\peer.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\enet\protocol.c">
<ClCompile Include="thirdparty\enet\protocol.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\thirdparty\enet\win32.c">
<ClCompile Include="thirdparty\enet\win32.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\ipsettings.h">
<ClInclude Include="ipsettings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\triangle.gen.h">
<ClInclude Include="thirdparty\sokol_app.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\types.h">
<ClInclude Include="thirdparty\sokol_gfx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\thirdparty\sokol_app.h">
<ClInclude Include="thirdparty\sokol_glue.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\thirdparty\sokol_gfx.h">
<ClInclude Include="thirdparty\sokol_gp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\thirdparty\sokol_glue.h">
<ClInclude Include="thirdparty\sokol_time.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\thirdparty\sokol_gp.h">
<ClInclude Include="thirdparty\stb_image.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\thirdparty\sokol_time.h">
<ClInclude Include="triangle.gen.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\thirdparty\stb_image.h">
<ClInclude Include="types.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\thirdparty\enet\enet_dll.cbp">
<Filter>Source Files</Filter>
</None>
<None Include="thirdparty\enet\enet_dll.cbp" />
</ItemGroup>
</Project>

@ -37,7 +37,7 @@ static cpVect v2_to_cp(V2 v)
return cpv(v.x, v.y);
}
bool was_entity_deleted(struct 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
Entity* the_entity = &gs->entities[id.index];
@ -45,7 +45,7 @@ bool was_entity_deleted(struct GameState* gs, EntityID id)
}
// may return null if it doesn't exist anymore
Entity* get_entity(struct GameState* gs, EntityID id)
Entity* get_entity(GameState* gs, EntityID id)
{
if (id.generation == 0)
{
@ -59,18 +59,18 @@ Entity* get_entity(struct GameState* gs, EntityID id)
return to_return;
}
EntityID get_id(struct GameState* gs, Entity* e)
EntityID get_id(GameState* gs, Entity* e)
{
if (e == NULL)
return (EntityID) { 0 };
size_t index = e - gs->entities;
size_t index = (e - gs->entities);
assert(index >= 0);
assert(index < gs->cur_next_entity);
return (EntityID) {
.generation = e->generation,
.index = index,
.index = (unsigned int)index,
};
}
@ -84,12 +84,12 @@ static Entity* cp_body_entity(cpBody* body)
return (Entity*)cpBodyGetUserData(body);
}
static struct GameState* cp_space_gs(cpSpace* space)
static GameState* cp_space_gs(cpSpace* space)
{
return (struct GameState*)cpSpaceGetUserData(space);
return (GameState*)cpSpaceGetUserData(space);
}
int grid_num_boxes(struct GameState* gs, Entity* e)
int grid_num_boxes(GameState* gs, Entity* e)
{
assert(e->is_grid);
int to_return = 0;
@ -122,6 +122,20 @@ void box_remove_from_boxes(GameState* gs, Entity* box)
}
void on_entity_child_shape(cpBody* body, cpShape* shape, void* data);
// gs is for iterating over all child shapes and destroying those, too
static void destroy_body(GameState* gs, cpBody** body)
{
if (*body != NULL)
{
cpBodyEachShape(*body, on_entity_child_shape, (void*)gs);
cpSpaceRemoveBody(gs->space, *body);
cpBodyFree(*body);
*body = NULL;
}
*body = NULL;
}
void entity_destroy(GameState* gs, Entity* e)
{
assert(e->exists);
@ -142,15 +156,7 @@ void entity_destroy(GameState* gs, Entity* e)
cpShapeFree(e->shape);
e->shape = NULL;
}
if (e->body != NULL)
{
cpBodyEachShape(e->body, on_entity_child_shape, (void*)gs);
cpSpaceRemoveBody(gs->space, e->body);
cpBodyFree(e->body);
e->body = NULL;
}
e->body = NULL;
e->shape = NULL;
destroy_body(gs, &e->body);
Entity* front_of_free_list = get_entity(gs, gs->free_list);
if (front_of_free_list != NULL)
@ -167,7 +173,7 @@ void on_entity_child_shape(cpBody* body, cpShape* shape, void* data)
entity_destroy((GameState*)data, cp_shape_entity(shape));
}
Entity* new_entity(struct GameState* gs)
Entity* new_entity(GameState* gs)
{
Entity* to_return = NULL;
if (get_entity(gs, gs->free_list) != NULL)
@ -188,7 +194,7 @@ Entity* new_entity(struct GameState* gs)
return to_return;
}
void create_body(struct GameState* gs, Entity* e)
void create_body(GameState* gs, Entity* e)
{
assert(gs->space != NULL);
@ -204,7 +210,20 @@ void create_body(struct GameState* gs, Entity* e)
cpBodySetUserData(e->body, (void*)e);
}
void grid_create(struct GameState* gs, Entity* e)
V2 player_vel(GameState* gs, Entity* player)
{
assert(player->is_player);
Entity* potential_seat = get_entity(gs, player->currently_piloting_seat);
if (potential_seat != NULL)
{
return cp_to_v2(cpBodyGetVelocity(get_entity(gs, potential_seat->shape_parent_entity)->body));
}
else {
return cp_to_v2(cpBodyGetVelocity(player->body));
}
}
void grid_create(GameState* gs, Entity* e)
{
e->is_grid = true;
create_body(gs, e);
@ -243,7 +262,7 @@ void create_rectangle_shape(GameState* gs, Entity* e, Entity* parent, V2 pos, V2
cpSpaceAddShape(gs->space, e->shape);
}
void create_player(struct GameState* gs, Entity* e)
void create_player(GameState* gs, Entity* e)
{
e->is_player = true;
create_body(gs, e);
@ -253,7 +272,7 @@ void create_player(struct GameState* gs, Entity* e)
// box must be passed as a parameter as the box added to chipmunk uses this pointer in its
// user data. pos is in local coordinates. Adds the box to the grid's chain of boxes
void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos)
void box_create(GameState* gs, Entity* new_box, Entity* grid, V2 pos)
{
new_box->is_box = true;
assert(gs->space != NULL);
@ -276,7 +295,7 @@ void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos)
// removes boxes from grid, then ensures that the rule that grids must not have
// holes in them is applied.
static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct Entity* box)
static void grid_remove_box(GameState* gs, struct Entity* grid, struct Entity* box)
{
assert(grid->is_grid);
assert(box->is_box);
@ -341,17 +360,17 @@ static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct En
V2 cur_local_pos = entity_shape_pos(N);
const V2 dirs[] = {
(V2) {
.x = -1.0f, .y = 0.0f
},
(V2) {
.x = 1.0f, .y = 0.0f
},
(V2) {
.x = 0.0f, .y = 1.0f
},
(V2) {
.x = 0.0f, .y = -1.0f
},
.x = -1.0f, .y = 0.0f
},
(V2) {
.x = 1.0f, .y = 0.0f
},
(V2) {
.x = 0.0f, .y = 1.0f
},
(V2) {
.x = 0.0f, .y = -1.0f
},
};
int num_dirs = sizeof(dirs) / sizeof(*dirs);
@ -364,7 +383,7 @@ static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct En
EntityID box_in_direction = (EntityID){ 0 };
BOXES_ITER(gs, cur, grid)
{
if (V2cmp(entity_shape_pos(cur), wanted_local_pos, 0.01f))
if (V2equal(entity_shape_pos(cur), wanted_local_pos, 0.01f))
{
box_in_direction = get_id(gs, cur);
break;
@ -428,18 +447,6 @@ static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct En
}
}
static void postStepRemove(cpSpace* space, void* key, void* data)
{
cpShape* b = (cpShape*)key;
Entity* e = cp_shape_entity(b);
// @Robust why not just do these deletions in the update loop? save on a lot of complexity
if (e->damage > 1.0f)
{
if (e->is_box)
grid_remove_box(cp_space_gs(space), cp_body_entity(cpShapeGetBody(b)), e);
}
}
static cpBool on_damage(cpArbiter* arb, cpSpace* space, cpDataPointer userData)
{
cpShape* a, * b;
@ -458,28 +465,28 @@ static cpBool on_damage(cpArbiter* arb, cpSpace* space, cpDataPointer userData)
}
// 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
}
void initialize(struct GameState* gs, void* entity_arena, int entity_arena_size)
void initialize(GameState* gs, void* entity_arena, size_t entity_arena_size)
{
*gs = (struct GameState){ 0 };
*gs = (GameState){ 0 };
memset(entity_arena, 0, entity_arena_size); // SUPER critical. Random vals in the entity data causes big problem
gs->entities = (Entity*)entity_arena;
gs->max_entities = entity_arena_size / sizeof(Entity);
gs->max_entities = (unsigned int)(entity_arena_size / sizeof(Entity));
gs->space = cpSpaceNew();
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
handler->postSolveFunc = on_damage;
}
void destroy(struct GameState* gs)
void destroy(GameState* gs)
{
// can't zero out gs data because the entity memory arena is reused
// on deserialization
for (int i = 0; i < gs->max_entities; i++)
for (size_t i = 0; i < gs->max_entities; i++)
{
if (gs->entities[i].exists)
entity_destroy(gs, &gs->entities[i]);
@ -494,9 +501,11 @@ V2 grid_com(Entity* grid)
return cp_to_v2(cpBodyLocalToWorld(grid->body, cpBodyGetCenterOfGravity(grid->body)));
}
V2 entity_pos(Entity* grid)
V2 entity_pos(Entity* e)
{
return cp_to_v2(cpBodyGetPosition(grid->body));
assert(!e->is_box);
// @Robust merge entity_pos with box_pos
return cp_to_v2(cpBodyGetPosition(e->body));
}
V2 grid_vel(Entity* grid)
{
@ -579,7 +588,7 @@ void update_from(cpBody* body, struct BodyData* data)
cpBodySetAngularVelocity(body, data->angular_velocity);
}
#define WRITE_VARNAMES // debugging feature horrible for network
//#define WRITE_VARNAMES // debugging feature horrible for network
typedef struct SerState
{
char* bytes;
@ -587,10 +596,11 @@ typedef struct SerState
size_t cursor; // points to next available byte, is the size of current message after serializing something
size_t max_size;
} SerState;
void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name)
void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name, const char* file, int line)
{
const char* var_name = name;
#ifdef WRITE_VARNAMES
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
size_t var_name_len = strlen(var_name);
#endif
if (ser->serializing)
@ -611,8 +621,8 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name
{
#ifdef WRITE_VARNAMES
{
char read_name[1024] = { 0 };
char read_name[512] = { 0 };
for (int i = 0; i < var_name_len; i++)
{
read_name[i] = ser->bytes[ser->cursor];
@ -620,11 +630,18 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name
assert(ser->cursor < ser->max_size);
}
read_name[var_name_len] = '\0';
if (strcmp(read_name, var_name) != 0)
// advance past digits
char* read = read_name;
char* var = var_name;
while (*read >= '0' && *read <= '9')
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);
printf("%s:%d | Expected variable %s but got %sn\n", file, line, var_name, read_name);
*(char*)NULL = 0;
}
}
}
#endif
for (int b = 0; b < var_size; b++)
@ -636,7 +653,7 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name
}
}
#define SER_VAR_NAME(var_pointer, name) ser_var(ser, (char*)var_pointer, sizeof(var_pointer), name)
#define SER_VAR_NAME(var_pointer, name) ser_var(ser, (char*)var_pointer, sizeof(var_pointer), name, __FILE__, __LINE__)
#define SER_VAR(var_pointer) SER_VAR_NAME(var_pointer, #var_pointer)
void ser_V2(SerState* ser, V2* var)
@ -661,16 +678,20 @@ void ser_entityid(SerState* ser, EntityID* id)
void ser_inputframe(SerState* ser, struct InputFrame* i)
{
SER_VAR(&i->tick);
SER_VAR(&i->movement);
SER_VAR(&i->inhabit);
SER_VAR(&i->build);
SER_VAR(&i->seat_action);
ser_entityid(ser, &i->seat_to_inhabit);
SER_VAR(&i->hand_pos);
SER_VAR(&i->dobuild);
SER_VAR(&i->build_type);
SER_VAR(&i->build_rotation);
ser_entityid(ser, &i->grid_to_build_on);
}
void ser_player(SerState* ser, struct Player* p)
void ser_player(SerState* ser, Player* p)
{
SER_VAR(&p->connected);
if (p->connected)
@ -680,7 +701,7 @@ void ser_player(SerState* ser, struct Player* p)
}
}
void ser_entity(SerState* ser, struct GameState* gs, Entity* e)
void ser_entity(SerState* ser, GameState* gs, Entity* e)
{
SER_VAR(&e->generation);
SER_VAR(&e->damage);
@ -754,13 +775,15 @@ void ser_entity(SerState* ser, struct GameState* gs, Entity* e)
ser_entityid(ser, &e->prev_box);
SER_VAR(&e->compass_rotation);
SER_VAR(&e->thrust);
SER_VAR(&e->wanted_thrust);
SER_VAR(&e->energy_used);
ser_entityid(ser, &e->piloted_by);
}
}
void ser_server_to_client(SerState* ser, ServerToClient* s)
{
struct GameState* gs = s->cur_gs;
GameState* gs = s->cur_gs;
int cur_next_entity = 0;
if (ser->serializing)
@ -780,20 +803,22 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
ser_V2(ser, &gs->goldpos);
for (int i = 0; i < MAX_PLAYERS; i++)
for (size_t i = 0; i < MAX_PLAYERS; i++)
{
ser_player(ser, &gs->players[i]);
}
if (ser->serializing)
{
for (int i = 0; i < gs->cur_next_entity; i++)
bool entities_done = false;
for (size_t i = 0; i < gs->cur_next_entity; i++)
{
Entity* e = &gs->entities[i];
if (e->exists)
{
if (e->is_player)
{
SER_VAR(&entities_done);
SER_VAR(&i);
ser_entity(ser, gs, e);
}
@ -801,35 +826,41 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
{
// serialize boxes always after bodies, so that by the time the boxes
// are loaded in the parent body is loaded in and can be referenced.
SER_VAR(&entities_done);
SER_VAR(&i);
ser_entity(ser, gs, e);
BOXES_ITER(gs, cur, e)
{
EntityID cur_id = get_id(gs, cur);
SER_VAR_NAME(&cur_id.index, "&i");
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);
}
}
}
}
int end_of_entities = -1;
SER_VAR_NAME(&end_of_entities, "&i");
entities_done = true;
SER_VAR(&entities_done);
}
else
{
while (true)
{
int next_index;
SER_VAR_NAME(&next_index, "&i");
if (next_index == -1)
bool entities_done = false;
SER_VAR(&entities_done);
if (entities_done)
break;
size_t next_index;
SER_VAR_NAME(&next_index, "&i");
assert(next_index < gs->max_entities);
Entity* e = &gs->entities[next_index];
e->exists = true;
ser_entity(ser, gs, e);
gs->cur_next_entity = max(gs->cur_next_entity, next_index + 1);
gs->cur_next_entity = (unsigned int)max(gs->cur_next_entity, next_index + 1);
}
for (int 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)
@ -841,7 +872,7 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
}
}
void into_bytes(struct ServerToClient* msg, char* bytes, size_t * out_len, size_t max_len)
void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len)
{
assert(msg->cur_gs != NULL);
assert(msg != NULL);
@ -889,7 +920,7 @@ static void closest_point_callback_func(cpShape* shape, cpContactPointSet* point
}
}
Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius)
Entity* closest_to_point_in_radius(GameState* gs, V2 point, float radius)
{
closest_to_point_in_radius_result = NULL;
closest_to_point_in_radius_result_largest_dist = 0.0f;
@ -926,12 +957,12 @@ V2 thruster_force(Entity* box)
return V2scale(thruster_direction(box), -box->thrust * THRUSTER_FORCE);
}
uint64_t tick(struct GameState* gs)
uint64_t tick(GameState* gs)
{
return (uint64_t)floor(gs->time / ((double)TIMESTEP));
}
void process(struct GameState* gs, float dt)
void process(GameState* gs, float dt)
{
assert(gs->space != NULL);
@ -956,70 +987,42 @@ void process(struct GameState* gs, float dt)
// update gold win condition
if (V2length(V2sub(cp_to_v2(cpBodyGetPosition(p->body)), gs->goldpos)) < GOLD_COLLECT_RADIUS)
{
p->goldness += 0.1;
p->goldness += 0.1f;
p->spice_taken_away = 0.0f;
gs->goldpos = (V2){ .x = hash11(gs->time) * 20.0f, .y = hash11(gs->time - 13.6f) * 20.0f };
}
if (get_entity(gs, p->currently_piloting_seat) == NULL)
{
p->currently_piloting_seat = (EntityID){ 0 };
gs->goldpos = (V2){ .x = hash11((float)gs->time) * 20.0f, .y = hash11((float)gs->time - 13.6f) * 20.0f };
}
// @Todo do getting inside pilot seat
#if 0
if (p->input.inhabit)
#if 1
if (player->input.seat_action)
{
p->input.inhabit = false; // "handle" the input
if (p->currently_inhabiting_index == -1)
player->input.seat_action = false; // "handle" the input
Entity* the_seat = get_entity(gs, p->currently_piloting_seat);
if (the_seat == NULL) // not piloting any seat
{
// @Robust mask to only ship boxes of things the player can inhabit
cpPointQueryInfo query_info = { 0 };
cpShape* result = cpSpacePointQueryNearest(gs->space, v2_to_cp(p->pos), 0.1f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, CP_ALL_CATEGORIES), &query_info);
cpShape* result = cpSpacePointQueryNearest(gs->space, v2_to_cp(player->input.hand_pos), 0.1f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &query_info);
if (result != NULL)
{
// result is assumed to be a box shape
struct Grid* g = (struct Grid*)cpBodyGetUserData(cpShapeGetBody(result));
int ship_to_inhabit = -1;
for (int ii = 0; ii < MAX_GRIDS; ii++)
{
SKIPNULL(gs->grids[ii].body);
if (&gs->grids[ii] == g)
{
ship_to_inhabit = ii;
break;
}
}
// don't allow inhabiting a grid that's already inhabited
for (int ii = 0; ii < MAX_PLAYERS; ii++)
Entity* potential_seat = cp_shape_entity(result);
assert(potential_seat->is_box);
if (potential_seat->box_type == BoxCockpit)
{
if (gs->players[ii].currently_inhabiting_index == ship_to_inhabit)
{
Log("Attempted to inhabit already taken ship\n");
ship_to_inhabit = -1;
}
}
if (ship_to_inhabit == -1)
{
Log("Couldn't find ship to inhabit even though point collision returned something\n");
}
else
{
p->currently_inhabiting_index = ship_to_inhabit;
p->currently_piloting_seat = get_id(gs, potential_seat);
potential_seat->piloted_by = get_id(gs, p);
}
}
else
{
Log("No ship above player at point %f %f\n", p->pos.x, p->pos.y);
Log("No ship above player at point %f %f\n", player->input.hand_pos.x, player->input.hand_pos.y);
}
}
else
{
p->vel = grid_vel(&gs->grids[p->currently_inhabiting_index]);
p->currently_inhabiting_index = -1;
V2 pilot_seat_exit_spot = V2add(box_pos(the_seat), V2rotate((V2) { .x = BOX_SIZE }, rotangle(the_seat->compass_rotation)));
cpBodySetPosition(p->body, v2_to_cp(pilot_seat_exit_spot));
cpBodySetVelocity(p->body, v2_to_cp(player_vel(gs, p)));
the_seat->piloted_by = (EntityID){ 0 };
p->currently_piloting_seat = (EntityID){ 0 };
}
}
#endif
@ -1032,46 +1035,38 @@ void process(struct GameState* gs, float dt)
{
player->input.movement = V2scale(V2normalize(player->input.movement), clamp(V2length(player->input.movement), 0.0f, 1.0f));
}
cpBodyApplyForceAtWorldPoint(p->body, v2_to_cp(V2scale(player->input.movement, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body));
p->spice_taken_away += movement_strength * dt * PLAYER_JETPACK_SPICE_PER_SECOND;
// @Todo do pilot seat
#if 0
Entity* piloting_seat = get_entity(gs, p->currently_piloting_seat);
if (piloting_seat == NULL)
{
struct Grid* g = &gs->grids[p->currently_inhabiting_index];
V2 target_new_pos = V2lerp(p->pos, grid_com(g), dt * 20.0f);
p->vel = V2scale(V2sub(target_new_pos, p->pos), 1.0f / dt); // set vel correctly so newly built grids have the correct velocity copied from it
cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_ALL);
cpBodyApplyForceAtWorldPoint(p->body, v2_to_cp(V2scale(player->input.movement, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body));
p->spice_taken_away += movement_strength * dt * PLAYER_JETPACK_SPICE_PER_SECOND;
}
else
{
cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_NONE); // no collisions while going to pilot seat
cpBodySetPosition(p->body, v2_to_cp(box_pos(piloting_seat)));
// set thruster thrust from movement
{
float energy_available = g->total_energy_capacity;
Entity* g = get_entity(gs, piloting_seat->shape_parent_entity);
V2 target_direction = { 0 };
if (V2length(p->input.movement) > 0.0f)
if (V2length(player->input.movement) > 0.0f)
{
target_direction = V2normalize(p->input.movement);
target_direction = V2normalize(player->input.movement);
}
for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++)
BOXES_ITER(gs, cur, g)
{
SKIPNULL(g->boxes[ii].shape);
if (g->boxes[ii].type != BoxThruster)
if (cur->box_type != BoxThruster)
continue;
float wanted_thrust = -V2dot(target_direction, thruster_direction(&g->boxes[ii]));
float wanted_thrust = -V2dot(target_direction, thruster_direction(cur));
wanted_thrust = clamp01(wanted_thrust);
float needed_energy = wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt;
energy_available -= needed_energy;
if (energy_available > 0.0f)
g->boxes[ii].thrust = wanted_thrust;
else
g->boxes[ii].thrust = 0.0f;
cur->wanted_thrust = wanted_thrust;
}
}
// cpBodyApplyForceAtWorldPoint(g->body, v2_to_cp(V2scale(p->input.movement, 5.0f)), v2_to_cp(grid_com(g)));
// bigger the ship, the more efficient the spice usage
}
#endif
}
#if 1 // building
@ -1080,14 +1075,10 @@ void process(struct GameState* gs, float dt)
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...
cpPointQueryInfo info = { 0 };
V2 world_build = player->input.build;
V2 world_build = player->input.hand_pos;
// @Robust sanitize this input so player can't build on any grid in the world
Entity* target_grid = get_entity(gs, player->input.grid_to_build_on);
if (target_grid != NULL)
{
world_build = grid_local_to_world(target_grid, player->input.build);
}
cpShape* nearest = cpSpacePointQueryNearest(gs->space, v2_to_cp(world_build), 0.01f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &info);
if (nearest != NULL)
{
@ -1107,7 +1098,7 @@ void process(struct GameState* gs, float dt)
box_create(gs, new_box, new_grid, (V2) { 0 });
new_box->box_type = player->input.build_type;
new_box->compass_rotation = player->input.build_rotation;
cpBodySetVelocity(new_grid->body, cpBodyGetVelocity(p->body));
cpBodySetVelocity(new_grid->body, v2_to_cp(player_vel(gs, p)));
}
else
{
@ -1128,59 +1119,42 @@ void process(struct GameState* gs, float dt)
p->spice_taken_away = clamp01(p->spice_taken_away);
}
// @Todo add thrust from thruster blocks
#if 0
for (int i = 0; i < MAX_GRIDS; i++)
{
SKIPNULL(gs->grids[i].body);
// process grids
for (size_t i = 0; i < gs->cur_next_entity; i++) {
Entity* e = &gs->entities[i];
if (!e->exists)
continue;
struct Box* batteries[MAX_BOXES_PER_GRID] = { 0 };
int cur_battery = 0;
for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++)
if (e->is_box)
{
SKIPNULL(gs->grids[i].boxes[ii].shape);
if (gs->grids[i].boxes[ii].type == BoxBattery)
if (e->damage >= 1.0f)
{
assert(cur_battery < MAX_BOXES_PER_GRID);
batteries[cur_battery] = &gs->grids[i].boxes[ii];
cur_battery++;
grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e);
}
}
int batteries_len = cur_battery;
float thruster_energy_consumption_per_second = 0.0f;
for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++)
if (e->is_grid)
{
SKIPNULL(gs->grids[i].boxes[ii].shape);
if (gs->grids[i].boxes[ii].type == BoxThruster)
float thruster_energy_consumption_per_second = 0.0f;
BOXES_ITER(gs, cur, e)
{
float energy_to_consume = gs->grids[i].boxes[ii].thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt;
struct Box* max_capacity_battery = NULL;
float max_capacity_battery_energy_used = 1.0f;
for (int iii = 0; iii < batteries_len; iii++)
if (cur->box_type == BoxThruster)
{
if (batteries[iii]->energy_used < max_capacity_battery_energy_used)
float energy_to_consume = cur->wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt;
cur->thrust = 0.0f;
BOXES_ITER(gs, possible_battery, e)
{
max_capacity_battery = batteries[iii];
max_capacity_battery_energy_used = batteries[iii]->energy_used;
if (possible_battery->box_type == BoxBattery && (1.0f - possible_battery->energy_used) > energy_to_consume)
{
possible_battery->energy_used += energy_to_consume;
cur->thrust = cur->wanted_thrust;
cpBodyApplyForceAtWorldPoint(e->body, v2_to_cp(thruster_force(cur)), v2_to_cp(box_pos(cur)));
}
}
}
if (max_capacity_battery != NULL && (1.0f - max_capacity_battery->energy_used) > energy_to_consume)
{
max_capacity_battery->energy_used += energy_to_consume;
cpBodyApplyForceAtWorldPoint(gs->grids[i].body, v2_to_cp(thruster_force(&gs->grids[i].boxes[ii])), v2_to_cp(box_pos(&gs->grids[i].boxes[ii])));
}
}
}
gs->grids[i].total_energy_capacity = 0.0f;
for (int ii = 0; ii < batteries_len; ii++)
{
gs->grids[i].total_energy_capacity += 1.0f - batteries[ii]->energy_used;
}
}
#endif
cpSpaceStep(gs->space, dt);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

1464
main.c

File diff suppressed because it is too large Load Diff

@ -6,204 +6,214 @@
#include <stdlib.h>
// started in a thread from host
void server(void *data)
void server(void* data)
{
(void)data;
stm_setup();
struct GameState gs = {0};
size_t entities_size = (sizeof(Entity) * MAX_ENTITIES);
Entity *entity_data = malloc(entities_size);
initialize(&gs, entity_data, entities_size);
Log("Allocated %zu bytes for entities\n", entities_size);
// one box policy
if (true)
{
Entity * grid = new_entity(&gs);
grid_create(&gs, grid);
entity_set_pos(grid, (V2){-BOX_SIZE*2, 0.0f});
Entity * box = new_entity(&gs);
box_create(&gs, box, grid, (V2){0});
}
if (enet_initialize() != 0)
{
fprintf(stderr, "An error occurred while initializing ENet.\n");
exit(-1);
}
ENetAddress address;
ENetHost *server;
int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS);
if (sethost != 0)
{
Log("Fishy return value from set host: %d\n", sethost);
}
/* Bind the server to port 1234. */
address.port = SERVER_PORT;
server = enet_host_create(&address /* the address to bind the server host to */,
32 /* allow up to 32 clients and/or outgoing connections */,
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)
{
fprintf(stderr,
"An error occurred while trying to create an ENet server host.\n");
exit(-1);
}
Log("Serving on port %d...\n", SERVER_PORT);
ENetEvent event;
uint64_t last_processed_time = stm_now();
float total_time = 0.0f;
uint64_t player_to_latest_tick_processed[MAX_PLAYERS] = {0};
while (true)
{
// @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);
if (ret == 0)
break;
if (ret < 0)
{
fprintf(stderr, "Enet host service error %d\n", ret);
}
if (ret > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
Log("A new client connected from %x:%u.\n",
event.peer->address.host,
event.peer->address.port);
int64_t player_slot = -1;
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (!gs.players[i].connected)
{
player_slot = i;
break;
}
}
if (player_slot == -1)
{
enet_peer_disconnect_now(event.peer, 69);
}
else
{
event.peer->data = (void *)player_slot;
gs.players[player_slot] = (struct Player){0};
gs.players[player_slot].connected = true;
}
break;
case ENET_EVENT_TYPE_RECEIVE:
// Log("A packet of length %zu was received on channel %u.\n",
// event.packet->dataLength,
// event.channelID);
size_t length = event.packet->dataLength;
if (length != sizeof(struct ClientToServer))
{
Log("Length did not match up...\n");
}
else
{
struct ClientToServer received = {0};
memcpy(&received, event.packet->data, length);
int64_t player_slot = (int64_t)event.peer->data;
uint64_t latest_tick = player_to_latest_tick_processed[player_slot];
if (received.inputs[0].tick > latest_tick)
{
for (int i = INPUT_BUFFER - 1; i >= 0; i--)
{
if (received.inputs[i].tick == 0) // empty input
continue;
if (received.inputs[i].tick <= latest_tick)
continue; // don't reprocess inputs already processed
struct InputFrame cur_input = received.inputs[i];
gs.players[player_slot].input.movement = cur_input.movement;
// 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.inhabit)
{
gs.players[player_slot].input.inhabit = cur_input.inhabit;
}
if (cur_input.dobuild)
{
gs.players[player_slot].input.grid_to_build_on = cur_input.grid_to_build_on;
gs.players[player_slot].input.build = cur_input.build;
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_tick_processed[player_slot] = received.inputs[0].tick;
}
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
int player_index = (int64_t)event.peer->data;
Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
gs.players[player_index].connected = false;
// box_destroy(&gs.players[player_index].box);
event.peer->data = NULL;
}
}
}
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...
bool processed = false;
while (total_time > TIMESTEP)
{
processed = true;
process(&gs, TIMESTEP);
total_time -= TIMESTEP;
}
if (processed)
{
(void)data;
stm_setup();
struct GameState gs = { 0 };
size_t entities_size = (sizeof(Entity) * MAX_ENTITIES);
Entity* entity_data = malloc(entities_size);
initialize(&gs, entity_data, entities_size);
Log("Allocated %zu bytes for entities\n", entities_size);
// one box policy
if (true)
{
Entity* grid = new_entity(&gs);
grid_create(&gs, grid);
entity_set_pos(grid, (V2) { -BOX_SIZE * 2, 0.0f });
Entity* box = new_entity(&gs);
box_create(&gs, box, grid, (V2) { 0 });
}
if (enet_initialize() != 0)
{
fprintf(stderr, "An error occurred while initializing ENet.\n");
exit(-1);
}
ENetAddress address;
ENetHost* server;
int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS);
if (sethost != 0)
{
Log("Fishy return value from set host: %d\n", sethost);
}
/* Bind the server to port 1234. */
address.port = SERVER_PORT;
server = enet_host_create(&address /* the address to bind the server host to */,
32 /* allow up to 32 clients and/or outgoing connections */,
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)
{
fprintf(stderr,
"An error occurred while trying to create an ENet server host.\n");
exit(-1);
}
Log("Serving on port %d...\n", SERVER_PORT);
ENetEvent event;
uint64_t last_processed_time = stm_now();
float total_time = 0.0f;
size_t player_to_latest_id_processed[MAX_PLAYERS] = { 0 };
while (true)
{
// @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);
if (ret == 0)
break;
if (ret < 0)
{
fprintf(stderr, "Enet host service error %d\n", ret);
}
if (ret > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
{
Log("A new client connected from %x:%u.\n",
event.peer->address.host,
event.peer->address.port);
int64_t player_slot = -1;
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (!gs.players[i].connected)
{
player_slot = i;
break;
}
}
if (player_slot == -1)
{
enet_peer_disconnect_now(event.peer, 69);
}
else
{
event.peer->data = (void*)player_slot;
gs.players[player_slot] = (struct Player){ 0 };
gs.players[player_slot].connected = true;
}
break;
}
case ENET_EVENT_TYPE_RECEIVE:
{
// Log("A packet of length %zu was received on channel %u.\n",
// event.packet->dataLength,
// event.channelID);
size_t length = event.packet->dataLength;
if (length != sizeof(struct ClientToServer))
{
Log("Length did not match up...\n");
}
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)
{
for (int i = INPUT_BUFFER - 1; i >= 0; i--)
{
if (received.inputs[i].tick == 0) // empty input
continue;
if (received.inputs[i].id <= latest_id)
continue; // don't reprocess inputs already processed
struct 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;
}
if (cur_input.dobuild)
{
gs.players[player_slot].input.grid_to_build_on = cur_input.grid_to_build_on;
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;
}
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet);
break;
}
case ENET_EVENT_TYPE_DISCONNECT:
{
int player_index = (int)(int64_t)event.peer->data;
Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
gs.players[player_index].connected = false;
// box_destroy(&gs.players[player_index].box);
event.peer->data = NULL;
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...
bool processed = false;
while (total_time > TIMESTEP)
{
processed = true;
process(&gs, TIMESTEP);
total_time -= TIMESTEP;
}
if (processed)
{
#define MAX_BYTES_SIZE 2048 * 2
static char bytes_buffer[MAX_BYTES_SIZE] = {0};
for (int i = 0; i < server->peerCount; i++)
{
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
if (server->peers[i].state != ENET_PEER_STATE_CONNECTED)
{
continue;
}
struct ServerToClient to_send;
to_send.cur_gs = &gs;
to_send.your_player = (int)(int64_t)server->peers[i].data;
int len = 0;
into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE);
ENetPacket *gamestate_packet = enet_packet_create((void *)bytes_buffer, len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(&server->peers[i], 0, gamestate_packet);
}
}
}
destroy(&gs);
free(entity_data);
enet_host_destroy(server);
enet_deinitialize();
static char bytes_buffer[MAX_BYTES_SIZE] = { 0 };
for (int i = 0; i < server->peerCount; i++)
{
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
if (server->peers[i].state != ENET_PEER_STATE_CONNECTED)
{
continue;
}
struct ServerToClient to_send;
to_send.cur_gs = &gs;
to_send.your_player = (int)(int64_t)server->peers[i].data;
size_t len = 0;
into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE);
ENetPacket* gamestate_packet = enet_packet_create((void*)bytes_buffer, len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(&server->peers[i], 0, gamestate_packet);
}
}
}
destroy(&gs);
free(entity_data);
enet_host_destroy(server);
enet_deinitialize();
}

@ -17,7 +17,7 @@
#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
#define INPUT_BUFFER 4
#define INPUT_BUFFER 6
// must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1"
#include "ipsettings.h" // don't leak IP!
@ -36,7 +36,7 @@ void sgp_set_color(float, float, float, float);
// somehow automatically or easily cast to floats
typedef struct sgp_vec2
{
float x, y;
float x, y;
} sgp_vec2;
typedef sgp_vec2 sgp_point;
@ -68,102 +68,117 @@ typedef sgp_point P2;
enum BoxType
{
BoxHullpiece,
BoxThruster,
BoxBattery,
BoxCockpit,
BoxLast,
BoxHullpiece,
BoxThruster,
BoxBattery,
BoxCockpit,
BoxLast,
};
enum CompassRotation
{
Right,
Down,
Left,
Up,
RotationLast,
Right,
Down,
Left,
Up,
RotationLast,
};
// when generation is 0, invalid ID
typedef struct
{
unsigned int generation; // if 0 then EntityID points to nothing, generation >= 1
unsigned int index; // index into the entity arena
unsigned int generation; // if 0 then EntityID points to nothing, generation >= 1
unsigned int index; // index into the entity arena
} EntityID;
static bool entityids_same(EntityID a, EntityID b)
{
return (a.generation == b.generation) && (a.index == b.index);
}
// when updated, must update serialization, AND comparison
// function in main.c
struct InputFrame
{
uint64_t tick;
V2 movement;
bool inhabit;
uint64_t tick;
size_t id; // each input has unique, incrementing, I.D, so server doesn't double process inputs. Inputs to server should be ordered from 0-max like biggest id-smallest. This is done so if packet loss server still processes input
V2 movement;
// if grid_index != -1, this is in local coordinates to the grid
V2 build;
bool dobuild;
enum BoxType build_type;
enum CompassRotation build_rotation;
EntityID grid_to_build_on;
bool seat_action;
EntityID seat_to_inhabit;
V2 hand_pos; // world coords, world star!
bool dobuild;
enum BoxType build_type;
enum CompassRotation build_rotation;
EntityID grid_to_build_on;
};
typedef struct Entity
{
bool exists;
EntityID next_free_entity;
unsigned int generation;
float damage; // used by box and player
cpBody *body; // used by grid, player, and box
cpShape *shape; // must be a box so shape_size can be set appropriately, and serialized
// for serializing the shape
EntityID shape_parent_entity; // can't be zero if shape is nonzero
V2 shape_size;
// player
bool is_player;
EntityID currently_piloting_seat;
float spice_taken_away; // at 1.0, out of spice
float goldness; // how much the player is a winner
// grids
bool is_grid;
float total_energy_capacity;
EntityID boxes;
// boxes
bool is_box;
enum BoxType box_type;
EntityID next_box;
EntityID prev_box; // doubly linked so can remove in middle of chain
enum CompassRotation compass_rotation;
float thrust; // must be between 0 and 1
float energy_used; // battery
bool exists;
EntityID next_free_entity;
unsigned int generation;
float damage; // used by box and player
cpBody* body; // used by grid, player, and box
cpShape* shape; // must be a box so shape_size can be set appropriately, and serialized
// for serializing the shape
// @Robust remove shape_parent_entity from this struct, use the shape's body to figure out
// what the shape's parent entity is
EntityID shape_parent_entity; // can't be zero if shape is nonzero
V2 shape_size;
// player
bool is_player;
EntityID currently_piloting_seat;
float spice_taken_away; // at 1.0, out of spice
float goldness; // how much the player is a winner
// grids
bool is_grid;
float total_energy_capacity;
EntityID boxes;
// boxes
bool is_box;
enum BoxType box_type;
EntityID next_box;
EntityID prev_box; // doubly linked so can remove in middle of chain
enum CompassRotation compass_rotation;
float wanted_thrust; // the thrust command applied to the thruster
float thrust; // the actual thrust it can provide based on energy sources in the grid
float energy_used; // battery
EntityID piloted_by;
} Entity;
typedef struct Player
{
bool connected;
EntityID entity;
struct InputFrame input;
} Player;
// gotta update the serialization functions when this changes
typedef struct GameState
{
cpSpace *space;
cpSpace* space;
double time;
double time;
V2 goldpos;
V2 goldpos;
struct Player
{
bool connected;
EntityID entity;
struct InputFrame input;
} players[MAX_PLAYERS];
Player players[MAX_PLAYERS];
// Entity arena
// ent:ity pointers can't move around because of how the physics engine handles user data.
// if you really need this, potentially refactor to store entity IDs instead of pointers
// in the shapes and bodies of chipmunk. Would require editing the library I think
Entity *entities;
unsigned int max_entities; // maximum number of entities possible in the entities list
unsigned int cur_next_entity; // next entity to pass on request of a new entity if the free list is empty
EntityID free_list;
// Entity arena
// ent:ity pointers can't move around because of how the physics engine handles user data.
// if you really need this, potentially refactor to store entity IDs instead of pointers
// in the shapes and bodies of chipmunk. Would require editing the library I think
Entity* entities;
unsigned int max_entities; // maximum number of entities possible in the entities list
unsigned int cur_next_entity; // next entity to pass on request of a new entity if the free list is empty
EntityID free_list;
} GameState;
#define PI 3.14159f
@ -171,80 +186,80 @@ typedef struct GameState
// returns in radians
static float rotangle(enum CompassRotation rot)
{
switch (rot)
{
case Right:
return 0.0f;
break;
case Down:
return -PI / 2.0f;
break;
case Left:
return -PI;
break;
case Up:
return -3.0f * PI / 2.0f;
break;
default:
Log("Unknown rotation %d\n", rot);
return -0.0f;
break;
}
switch (rot)
{
case Right:
return 0.0f;
break;
case Down:
return -PI / 2.0f;
break;
case Left:
return -PI;
break;
case Up:
return -3.0f * PI / 2.0f;
break;
default:
Log("Unknown rotation %d\n", rot);
return -0.0f;
break;
}
}
typedef struct ServerToClient
{
struct GameState *cur_gs;
int your_player;
struct GameState* cur_gs;
int your_player;
} ServerToClient;
struct ClientToServer
{
struct InputFrame inputs[INPUT_BUFFER];
struct InputFrame inputs[INPUT_BUFFER];
};
// server
void server(void *data); // data parameter required from thread api...
void server(void* data); // data parameter required from thread api...
// gamestate
void initialize(struct GameState *gs, void *entity_arena, int entity_arena_size);
void destroy(struct GameState *gs);
void process(struct GameState *gs, float dt); // does in place
Entity *closest_to_point_in_radius(struct GameState *gs, V2 point, float radius);
uint64_t tick(struct GameState *gs);
void into_bytes(struct ServerToClient *gs, char *out_bytes, size_t * out_len, size_t max_len);
void from_bytes(struct ServerToClient *gs, char *bytes, size_t max_len);
void initialize(struct GameState* gs, void* entity_arena, size_t entity_arena_size);
void destroy(struct GameState* gs);
void process(struct GameState* gs, float dt); // does in place
Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius);
uint64_t tick(struct GameState* gs);
void into_bytes(struct ServerToClient* gs, char* out_bytes, size_t* out_len, size_t max_len);
void from_bytes(struct ServerToClient* gs, char* bytes, size_t max_len);
// entities
Entity *get_entity(struct GameState *gs, EntityID id);
Entity *new_entity(struct GameState *gs);
EntityID get_id(struct GameState *gs, Entity *e);
V2 entity_pos(Entity *e);
void entity_set_pos(Entity *e, V2 pos);
float entity_rotation(Entity *e);
Entity* get_entity(struct GameState* gs, EntityID id);
Entity* new_entity(struct GameState* gs);
EntityID get_id(struct GameState* gs, Entity* e);
V2 entity_pos(Entity* e);
void entity_set_pos(Entity* e, V2 pos);
float entity_rotation(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);
void player_destroy(struct Player* p);
void player_new(struct Player* p);
// grid
void grid_create(struct GameState *gs, Entity *e);
void box_create(struct GameState *gs, Entity *new_box, Entity *grid, V2 pos);
V2 grid_com(Entity *grid);
V2 grid_vel(Entity *grid);
V2 grid_local_to_world(Entity *grid, V2 local);
V2 grid_world_to_local(Entity *grid, V2 world);
V2 grid_snapped_box_pos(Entity *grid, V2 world); // returns the snapped pos in world coords
float entity_angular_velocity(Entity *grid);
V2 entity_shape_pos(Entity *box);
V2 box_pos(Entity *box);
float box_rotation(Entity *box);
void grid_create(struct GameState* gs, Entity* e);
void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos);
V2 grid_com(Entity* grid);
V2 grid_vel(Entity* grid);
V2 grid_local_to_world(Entity* grid, V2 local);
V2 grid_world_to_local(Entity* grid, V2 world);
V2 grid_snapped_box_pos(Entity* grid, V2 world); // returns the snapped pos in world coords
float entity_angular_velocity(Entity* grid);
V2 entity_shape_pos(Entity* box);
V2 box_pos(Entity* box); // returns in world coords
float box_rotation(Entity* box);
// thruster
V2 thruster_direction(Entity *box);
V2 thruster_force(Entity *box);
V2 thruster_direction(Entity* box);
V2 thruster_force(Entity* box);
// debug draw
void dbg_drawall();
@ -260,154 +275,154 @@ void dbg_rect(V2 center);
typedef struct AABB
{
float x, y, width, height;
float x, y, width, height;
} AABB;
static bool has_point(AABB aabb, V2 point)
{
return point.x > aabb.x && point.x < aabb.x + aabb.width && point.y > aabb.y && point.y < aabb.y + aabb.height;
return point.x > aabb.x && point.x < aabb.x + aabb.width && point.y > aabb.y && point.y < aabb.y + aabb.height;
}
static V2 V2add(V2 a, V2 b)
{
return (V2){
.x = a.x + b.x,
.y = a.y + b.y,
};
return (V2) {
.x = a.x + b.x,
.y = a.y + b.y,
};
}
static V2 V2scale(V2 a, float f)
{
return (V2){
.x = a.x * f,
.y = a.y * f,
};
return (V2) {
.x = a.x * f,
.y = a.y * f,
};
}
static float V2length(V2 v)
{
return sqrtf(v.x * v.x + v.y * v.y);
return sqrtf(v.x * v.x + v.y * v.y);
}
static V2 V2normalize(V2 v)
{
return V2scale(v, 1.0f / V2length(v));
return V2scale(v, 1.0f / V2length(v));
}
static float V2dot(V2 a, V2 b)
{
return a.x * b.x + a.y * b.y;
return a.x * b.x + a.y * b.y;
}
static float V2projectvalue(V2 vec, V2 onto)
{
float length_onto = V2length(onto);
return V2dot(vec, onto) / (length_onto * length_onto);
float length_onto = V2length(onto);
return V2dot(vec, onto) / (length_onto * length_onto);
}
static V2 V2project(V2 vec, V2 onto)
{
return V2scale(onto, V2projectvalue(vec, onto));
return V2scale(onto, V2projectvalue(vec, onto));
}
static V2 V2rotate(V2 vec, float theta)
{
return (V2){
.x = vec.x * cosf(theta) - vec.y * sinf(theta),
.y = vec.x * sinf(theta) + vec.y * cosf(theta),
};
return (V2) {
.x = vec.x * cosf(theta) - vec.y * sinf(theta),
.y = vec.x * sinf(theta) + vec.y * cosf(theta),
};
}
// also known as atan2
static float V2angle(V2 vec)
{
return atan2f(vec.y, vec.x);
return atan2f(vec.y, vec.x);
}
static V2 V2sub(V2 a, V2 b)
{
return (V2){
.x = a.x - b.x,
.y = a.y - b.y,
};
return (V2) {
.x = a.x - b.x,
.y = a.y - b.y,
};
}
static bool V2cmp(V2 a, V2 b, float eps)
static bool V2equal(V2 a, V2 b, float eps)
{
return V2length(V2sub(a, b)) < eps;
return V2length(V2sub(a, b)) < eps;
}
static inline float clamp01(float f)
{
return fmaxf(0.0f, fminf(f, 1.0f));
return fmaxf(0.0f, fminf(f, 1.0f));
}
static inline float clamp(float f, float minimum, float maximum)
{
if (f < minimum)
return minimum;
if (f > maximum)
return maximum;
return f;
if (f < minimum)
return minimum;
if (f > maximum)
return maximum;
return f;
}
static float fract(float f)
{
return f - floorf(f);
return f - floorf(f);
}
static float lerp(float a, float b, float f)
{
return a * (1.0f - f) + (b * f);
return a * (1.0f - f) + (b * f);
}
static V2 V2lerp(V2 a, V2 b, float factor)
{
V2 to_return = {0};
to_return.x = lerp(a.x, b.x, factor);
to_return.y = lerp(a.y, b.y, factor);
V2 to_return = { 0 };
to_return.x = lerp(a.x, b.x, factor);
to_return.y = lerp(a.y, b.y, factor);
return to_return;
return to_return;
}
// for random generation
static float hash11(float p)
{
p = fract(p * .1031f);
p *= p + 33.33f;
p *= p + p;
return fract(p);
p = fract(p * .1031f);
p *= p + 33.33f;
p *= p + p;
return fract(p);
}
typedef struct Color
{
float r, g, b, a;
float r, g, b, a;
} Color;
static Color colhex(int r, int g, int b)
{
return (Color){
.r = (float)r / 255.0f,
.g = (float)g / 255.0f,
.b = (float)b / 255.0f,
.a = 1.0f,
};
return (Color) {
.r = (float)r / 255.0f,
.g = (float)g / 255.0f,
.b = (float)b / 255.0f,
.a = 1.0f,
};
}
static Color Collerp(Color a, Color b, float factor)
{
Color to_return = {0};
to_return.r = lerp(a.r, b.r, factor);
to_return.g = lerp(a.g, b.g, factor);
to_return.b = lerp(a.b, b.b, factor);
to_return.a = lerp(a.a, b.a, factor);
Color to_return = { 0 };
to_return.r = lerp(a.r, b.r, factor);
to_return.g = lerp(a.g, b.g, factor);
to_return.b = lerp(a.b, b.b, factor);
to_return.a = lerp(a.a, b.a, factor);
return to_return;
return to_return;
}
static void set_color(Color c)
{
sgp_set_color(c.r, c.g, c.b, c.a);
sgp_set_color(c.r, c.g, c.b, c.a);
}
#define WHITE \

Loading…
Cancel
Save