Complete cockpit implementation, VS format

main
Cameron Murphy Reikes 2 years ago
parent 30321fd068
commit cb891afae7

@ -105,6 +105,7 @@
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Users\Cameron\Documents\flight\thirdparty\enet\include;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include\chipmunk;C:\Users\Cameron\Documents\flight\thirdparty;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include</AdditionalIncludeDirectories> <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> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>

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

@ -37,7 +37,7 @@ static cpVect v2_to_cp(V2 v)
return cpv(v.x, v.y); 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 if (id.generation == 0) return false; // generation 0 means null entity ID, not a deleted entity
Entity* the_entity = &gs->entities[id.index]; Entity* the_entity = &gs->entities[id.index];
@ -45,7 +45,7 @@ bool was_entity_deleted(struct GameState* gs, EntityID id)
} }
// may return null if it doesn't exist anymore // 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) if (id.generation == 0)
{ {
@ -59,18 +59,18 @@ Entity* get_entity(struct GameState* gs, EntityID id)
return to_return; return to_return;
} }
EntityID get_id(struct GameState* gs, Entity* e) EntityID get_id(GameState* gs, Entity* e)
{ {
if (e == NULL) if (e == NULL)
return (EntityID) { 0 }; return (EntityID) { 0 };
size_t index = e - gs->entities; size_t index = (e - gs->entities);
assert(index >= 0); assert(index >= 0);
assert(index < gs->cur_next_entity); assert(index < gs->cur_next_entity);
return (EntityID) { return (EntityID) {
.generation = e->generation, .generation = e->generation,
.index = index, .index = (unsigned int)index,
}; };
} }
@ -84,12 +84,12 @@ static Entity* cp_body_entity(cpBody* body)
return (Entity*)cpBodyGetUserData(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); assert(e->is_grid);
int to_return = 0; 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); 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) void entity_destroy(GameState* gs, Entity* e)
{ {
assert(e->exists); assert(e->exists);
@ -142,15 +156,7 @@ void entity_destroy(GameState* gs, Entity* e)
cpShapeFree(e->shape); cpShapeFree(e->shape);
e->shape = NULL; e->shape = NULL;
} }
if (e->body != NULL) destroy_body(gs, &e->body);
{
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;
Entity* front_of_free_list = get_entity(gs, gs->free_list); Entity* front_of_free_list = get_entity(gs, gs->free_list);
if (front_of_free_list != NULL) 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_destroy((GameState*)data, cp_shape_entity(shape));
} }
Entity* new_entity(struct GameState* gs) Entity* new_entity(GameState* gs)
{ {
Entity* to_return = NULL; Entity* to_return = NULL;
if (get_entity(gs, gs->free_list) != NULL) if (get_entity(gs, gs->free_list) != NULL)
@ -188,7 +194,7 @@ Entity* new_entity(struct GameState* gs)
return to_return; return to_return;
} }
void create_body(struct GameState* gs, Entity* e) void create_body(GameState* gs, Entity* e)
{ {
assert(gs->space != NULL); assert(gs->space != NULL);
@ -204,7 +210,20 @@ void create_body(struct GameState* gs, Entity* e)
cpBodySetUserData(e->body, (void*)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; e->is_grid = true;
create_body(gs, e); 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); cpSpaceAddShape(gs->space, e->shape);
} }
void create_player(struct GameState* gs, Entity* e) void create_player(GameState* gs, Entity* e)
{ {
e->is_player = true; e->is_player = true;
create_body(gs, e); 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 // 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 // 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; new_box->is_box = true;
assert(gs->space != NULL); 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 // removes boxes from grid, then ensures that the rule that grids must not have
// holes in them is applied. // 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(grid->is_grid);
assert(box->is_box); 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); V2 cur_local_pos = entity_shape_pos(N);
const V2 dirs[] = { const V2 dirs[] = {
(V2) { (V2) {
.x = -1.0f, .y = 0.0f .x = -1.0f, .y = 0.0f
}, },
(V2) { (V2) {
.x = 1.0f, .y = 0.0f .x = 1.0f, .y = 0.0f
}, },
(V2) { (V2) {
.x = 0.0f, .y = 1.0f .x = 0.0f, .y = 1.0f
}, },
(V2) { (V2) {
.x = 0.0f, .y = -1.0f .x = 0.0f, .y = -1.0f
}, },
}; };
int num_dirs = sizeof(dirs) / sizeof(*dirs); int num_dirs = sizeof(dirs) / sizeof(*dirs);
@ -364,7 +383,7 @@ static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct En
EntityID box_in_direction = (EntityID){ 0 }; EntityID box_in_direction = (EntityID){ 0 };
BOXES_ITER(gs, cur, grid) 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); box_in_direction = get_id(gs, cur);
break; 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) static cpBool on_damage(cpArbiter* arb, cpSpace* space, cpDataPointer userData)
{ {
cpShape* a, * b; 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 // b must be the key passed into the post step removed, the key is cast into its shape
cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL); //cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL);
cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL); //cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL);
return true; // keep colliding return true; // keep colliding
} }
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 memset(entity_arena, 0, entity_arena_size); // SUPER critical. Random vals in the entity data causes big problem
gs->entities = (Entity*)entity_arena; 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(); gs->space = cpSpaceNew();
cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler
cpCollisionHandler* handler = cpSpaceAddCollisionHandler(gs->space, 0, 0); // @Robust limit collision type to just blocks that can be damaged cpCollisionHandler* handler = cpSpaceAddCollisionHandler(gs->space, 0, 0); // @Robust limit collision type to just blocks that can be damaged
handler->postSolveFunc = on_damage; handler->postSolveFunc = on_damage;
} }
void destroy(struct GameState* gs) void destroy(GameState* gs)
{ {
// can't zero out gs data because the entity memory arena is reused // can't zero out gs data because the entity memory arena is reused
// on deserialization // 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) if (gs->entities[i].exists)
entity_destroy(gs, &gs->entities[i]); 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))); 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) V2 grid_vel(Entity* grid)
{ {
@ -579,7 +588,7 @@ void update_from(cpBody* body, struct BodyData* data)
cpBodySetAngularVelocity(body, data->angular_velocity); cpBodySetAngularVelocity(body, data->angular_velocity);
} }
#define WRITE_VARNAMES // debugging feature horrible for network //#define WRITE_VARNAMES // debugging feature horrible for network
typedef struct SerState typedef struct SerState
{ {
char* bytes; 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 cursor; // points to next available byte, is the size of current message after serializing something
size_t max_size; size_t max_size;
} SerState; } 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 #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); size_t var_name_len = strlen(var_name);
#endif #endif
if (ser->serializing) if (ser->serializing)
@ -611,7 +621,7 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name
{ {
#ifdef WRITE_VARNAMES #ifdef WRITE_VARNAMES
{ {
char read_name[1024] = { 0 }; char read_name[512] = { 0 };
for (int i = 0; i < var_name_len; i++) for (int i = 0; i < var_name_len; i++)
{ {
@ -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); assert(ser->cursor < ser->max_size);
} }
read_name[var_name_len] = '\0'; 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; *(char*)NULL = 0;
} }
} }
#endif #endif
for (int b = 0; b < var_size; b++) 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) #define SER_VAR(var_pointer) SER_VAR_NAME(var_pointer, #var_pointer)
void ser_V2(SerState* ser, V2* var) 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) void ser_inputframe(SerState* ser, struct InputFrame* i)
{ {
SER_VAR(&i->tick);
SER_VAR(&i->movement); 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->dobuild);
SER_VAR(&i->build_type); SER_VAR(&i->build_type);
SER_VAR(&i->build_rotation); SER_VAR(&i->build_rotation);
ser_entityid(ser, &i->grid_to_build_on); 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); SER_VAR(&p->connected);
if (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->generation);
SER_VAR(&e->damage); 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_entityid(ser, &e->prev_box);
SER_VAR(&e->compass_rotation); SER_VAR(&e->compass_rotation);
SER_VAR(&e->thrust); SER_VAR(&e->thrust);
SER_VAR(&e->wanted_thrust);
SER_VAR(&e->energy_used); SER_VAR(&e->energy_used);
ser_entityid(ser, &e->piloted_by);
} }
} }
void ser_server_to_client(SerState* ser, ServerToClient* s) 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; int cur_next_entity = 0;
if (ser->serializing) if (ser->serializing)
@ -780,20 +803,22 @@ void ser_server_to_client(SerState* ser, ServerToClient* s)
ser_V2(ser, &gs->goldpos); 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]); ser_player(ser, &gs->players[i]);
} }
if (ser->serializing) 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]; Entity* e = &gs->entities[i];
if (e->exists) if (e->exists)
{ {
if (e->is_player) if (e->is_player)
{ {
SER_VAR(&entities_done);
SER_VAR(&i); SER_VAR(&i);
ser_entity(ser, gs, e); 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 // 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. // are loaded in the parent body is loaded in and can be referenced.
SER_VAR(&entities_done);
SER_VAR(&i); SER_VAR(&i);
ser_entity(ser, gs, e); ser_entity(ser, gs, e);
BOXES_ITER(gs, cur, e) BOXES_ITER(gs, cur, e)
{ {
EntityID cur_id = get_id(gs, cur); 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); ser_entity(ser, gs, cur);
} }
} }
} }
} }
int end_of_entities = -1; entities_done = true;
SER_VAR_NAME(&end_of_entities, "&i"); SER_VAR(&entities_done);
} }
else else
{ {
while (true) while (true)
{ {
int next_index; bool entities_done = false;
SER_VAR_NAME(&next_index, "&i"); SER_VAR(&entities_done);
if (next_index == -1) if (entities_done)
break; break;
size_t next_index;
SER_VAR_NAME(&next_index, "&i");
assert(next_index < gs->max_entities); assert(next_index < gs->max_entities);
Entity* e = &gs->entities[next_index]; Entity* e = &gs->entities[next_index];
e->exists = true; e->exists = true;
ser_entity(ser, gs, e); 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]; Entity* e = &gs->entities[i];
if (!e->exists) 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->cur_gs != NULL);
assert(msg != 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 = NULL;
closest_to_point_in_radius_result_largest_dist = 0.0f; 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); 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)); 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); assert(gs->space != NULL);
@ -956,70 +987,42 @@ void process(struct GameState* gs, float dt)
// update gold win condition // update gold win condition
if (V2length(V2sub(cp_to_v2(cpBodyGetPosition(p->body)), gs->goldpos)) < GOLD_COLLECT_RADIUS) 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; p->spice_taken_away = 0.0f;
gs->goldpos = (V2){ .x = hash11(gs->time) * 20.0f, .y = hash11(gs->time - 13.6f) * 20.0f }; gs->goldpos = (V2){ .x = hash11((float)gs->time) * 20.0f, .y = hash11((float)gs->time - 13.6f) * 20.0f };
} }
if (get_entity(gs, p->currently_piloting_seat) == NULL) #if 1
if (player->input.seat_action)
{ {
p->currently_piloting_seat = (EntityID){ 0 }; 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
// @Todo do getting inside pilot seat
#if 0
if (p->input.inhabit)
{
p->input.inhabit = false; // "handle" the input
if (p->currently_inhabiting_index == -1)
{ {
// @Robust mask to only ship boxes of things the player can inhabit
cpPointQueryInfo query_info = { 0 }; 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) if (result != NULL)
{ {
// result is assumed to be a box shape Entity* potential_seat = cp_shape_entity(result);
struct Grid* g = (struct Grid*)cpBodyGetUserData(cpShapeGetBody(result)); assert(potential_seat->is_box);
int ship_to_inhabit = -1; if (potential_seat->box_type == BoxCockpit)
for (int ii = 0; ii < MAX_GRIDS; ii++)
{ {
SKIPNULL(gs->grids[ii].body); p->currently_piloting_seat = get_id(gs, potential_seat);
if (&gs->grids[ii] == g) potential_seat->piloted_by = get_id(gs, p);
{
ship_to_inhabit = ii;
break;
}
}
// don't allow inhabiting a grid that's already inhabited
for (int ii = 0; ii < MAX_PLAYERS; ii++)
{
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;
} }
} }
else 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 else
{ {
p->vel = grid_vel(&gs->grids[p->currently_inhabiting_index]); V2 pilot_seat_exit_spot = V2add(box_pos(the_seat), V2rotate((V2) { .x = BOX_SIZE }, rotangle(the_seat->compass_rotation)));
p->currently_inhabiting_index = -1; 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 #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)); 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)); Entity* piloting_seat = get_entity(gs, p->currently_piloting_seat);
p->spice_taken_away += movement_strength * dt * PLAYER_JETPACK_SPICE_PER_SECOND;
// @Todo do pilot seat if (piloting_seat == NULL)
#if 0
{ {
struct Grid* g = &gs->grids[p->currently_inhabiting_index]; cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_ALL);
V2 target_new_pos = V2lerp(p->pos, grid_com(g), dt * 20.0f); cpBodyApplyForceAtWorldPoint(p->body, v2_to_cp(V2scale(player->input.movement, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body));
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 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 // 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 }; 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 (cur->box_type != BoxThruster)
if (g->boxes[ii].type != BoxThruster)
continue; continue;
float wanted_thrust = -V2dot(target_direction, thruster_direction(cur));
float wanted_thrust = -V2dot(target_direction, thruster_direction(&g->boxes[ii]));
wanted_thrust = clamp01(wanted_thrust); wanted_thrust = clamp01(wanted_thrust);
cur->wanted_thrust = 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;
} }
} }
// 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 #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... 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 }; 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 // @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); 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); cpShape* nearest = cpSpacePointQueryNearest(gs->space, v2_to_cp(world_build), 0.01f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &info);
if (nearest != NULL) if (nearest != NULL)
{ {
@ -1107,7 +1098,7 @@ void process(struct GameState* gs, float dt)
box_create(gs, new_box, new_grid, (V2) { 0 }); box_create(gs, new_box, new_grid, (V2) { 0 });
new_box->box_type = player->input.build_type; new_box->box_type = player->input.build_type;
new_box->compass_rotation = player->input.build_rotation; 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 else
{ {
@ -1128,59 +1119,42 @@ void process(struct GameState* gs, float dt)
p->spice_taken_away = clamp01(p->spice_taken_away); p->spice_taken_away = clamp01(p->spice_taken_away);
} }
// @Todo add thrust from thruster blocks // process grids
#if 0 for (size_t i = 0; i < gs->cur_next_entity; i++) {
for (int i = 0; i < MAX_GRIDS; i++) Entity* e = &gs->entities[i];
{ if (!e->exists)
SKIPNULL(gs->grids[i].body); continue;
struct Box* batteries[MAX_BOXES_PER_GRID] = { 0 }; if (e->is_box)
int cur_battery = 0;
for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++)
{ {
SKIPNULL(gs->grids[i].boxes[ii].shape); if (e->damage >= 1.0f)
if (gs->grids[i].boxes[ii].type == BoxBattery)
{ {
assert(cur_battery < MAX_BOXES_PER_GRID); grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e);
batteries[cur_battery] = &gs->grids[i].boxes[ii];
cur_battery++;
} }
} }
int batteries_len = cur_battery; if (e->is_grid)
float thruster_energy_consumption_per_second = 0.0f;
for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++)
{ {
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; if (cur->box_type == BoxThruster)
struct Box* max_capacity_battery = NULL;
float max_capacity_battery_energy_used = 1.0f;
for (int iii = 0; iii < batteries_len; iii++)
{ {
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]; if (possible_battery->box_type == BoxBattery && (1.0f - possible_battery->energy_used) > energy_to_consume)
max_capacity_battery_energy_used = batteries[iii]->energy_used; {
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); cpSpaceStep(gs->space, dt);
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

583
main.c

File diff suppressed because it is too large Load Diff

@ -60,7 +60,7 @@ void server(void* data)
ENetEvent event; ENetEvent event;
uint64_t last_processed_time = stm_now(); uint64_t last_processed_time = stm_now();
float total_time = 0.0f; float total_time = 0.0f;
uint64_t player_to_latest_tick_processed[MAX_PLAYERS] = { 0 }; size_t player_to_latest_id_processed[MAX_PLAYERS] = { 0 };
while (true) while (true)
{ {
// @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
@ -123,36 +123,36 @@ void server(void* data)
struct ClientToServer received = { 0 }; struct ClientToServer received = { 0 };
memcpy(&received, event.packet->data, length); memcpy(&received, event.packet->data, length);
int64_t player_slot = (int64_t)event.peer->data; int64_t player_slot = (int64_t)event.peer->data;
uint64_t latest_tick = player_to_latest_tick_processed[player_slot]; size_t latest_id = player_to_latest_id_processed[player_slot];
if (received.inputs[0].tick > latest_tick) if (received.inputs[0].id > latest_id)
{ {
for (int i = INPUT_BUFFER - 1; i >= 0; i--) for (int i = INPUT_BUFFER - 1; i >= 0; i--)
{ {
if (received.inputs[i].tick == 0) // empty input if (received.inputs[i].tick == 0) // empty input
continue; continue;
if (received.inputs[i].tick <= latest_tick) if (received.inputs[i].id <= latest_id)
continue; // don't reprocess inputs already processed continue; // don't reprocess inputs already processed
struct InputFrame cur_input = received.inputs[i]; struct InputFrame cur_input = received.inputs[i];
gs.players[player_slot].input.movement = cur_input.movement; 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. // 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 // while processing the gamestate, will mark it as false once processed. This
// prevents setting the event input to false before it's been processed. // prevents setting the event input to false before it's been processed.
if (cur_input.inhabit) if (cur_input.seat_action)
{ {
gs.players[player_slot].input.inhabit = cur_input.inhabit; gs.players[player_slot].input.seat_action = cur_input.seat_action;
} }
if (cur_input.dobuild) if (cur_input.dobuild)
{ {
gs.players[player_slot].input.grid_to_build_on = cur_input.grid_to_build_on; 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.dobuild = cur_input.dobuild;
gs.players[player_slot].input.build_type = cur_input.build_type; gs.players[player_slot].input.build_type = cur_input.build_type;
gs.players[player_slot].input.build_rotation = cur_input.build_rotation; gs.players[player_slot].input.build_rotation = cur_input.build_rotation;
} }
} }
player_to_latest_tick_processed[player_slot] = received.inputs[0].tick; player_to_latest_id_processed[player_slot] = received.inputs[0].id;
} }
} }
@ -165,7 +165,7 @@ void server(void* data)
case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT:
{ {
int player_index = (int64_t)event.peer->data; int player_index = (int)(int64_t)event.peer->data;
Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index); Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
gs.players[player_index].connected = false; gs.players[player_index].connected = false;
// box_destroy(&gs.players[player_index].box); // box_destroy(&gs.players[player_index].box);
@ -203,7 +203,7 @@ void server(void* data)
to_send.cur_gs = &gs; to_send.cur_gs = &gs;
to_send.your_player = (int)(int64_t)server->peers[i].data; to_send.your_player = (int)(int64_t)server->peers[i].data;
int len = 0; size_t len = 0;
into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE); 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); ENetPacket* gamestate_packet = enet_packet_create((void*)bytes_buffer, len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);

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

Loading…
Cancel
Save