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;
printf("%s:%d | Expected variable %s but got %sn\n", __FILE__, __LINE__, var_name, 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);
*(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)
{
p->currently_piloting_seat = (EntityID){ 0 };
} }
// @Todo do getting inside pilot seat #if 1
#if 0 if (player->input.seat_action)
if (p->input.inhabit)
{ {
p->input.inhabit = false; // "handle" the input player->input.seat_action = false; // "handle" the input
if (p->currently_inhabiting_index == -1) 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 }; 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);
if (&gs->grids[ii] == g)
{ {
ship_to_inhabit = ii; p->currently_piloting_seat = get_id(gs, potential_seat);
break; potential_seat->piloted_by = get_id(gs, p);
}
}
// 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));
} }
Entity* piloting_seat = get_entity(gs, p->currently_piloting_seat);
if (piloting_seat == NULL)
{
cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_ALL);
cpBodyApplyForceAtWorldPoint(p->body, v2_to_cp(V2scale(player->input.movement, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body)); 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; p->spice_taken_away += movement_strength * dt * PLAYER_JETPACK_SPICE_PER_SECOND;
// @Todo do pilot seat }
#if 0 else
{ {
struct Grid* g = &gs->grids[p->currently_inhabiting_index]; cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_NONE); // no collisions while going to pilot seat
V2 target_new_pos = V2lerp(p->pos, grid_com(g), dt * 20.0f); cpBodySetPosition(p->body, v2_to_cp(box_pos(piloting_seat)));
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
// 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; float thruster_energy_consumption_per_second = 0.0f;
for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++) BOXES_ITER(gs, cur, e)
{ {
SKIPNULL(gs->grids[i].boxes[ii].shape); if (cur->box_type == BoxThruster)
if (gs->grids[i].boxes[ii].type == BoxThruster)
{ {
float energy_to_consume = gs->grids[i].boxes[ii].thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt; float energy_to_consume = cur->wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt;
struct Box* max_capacity_battery = NULL; cur->thrust = 0.0f;
float max_capacity_battery_energy_used = 1.0f; BOXES_ITER(gs, possible_battery, e)
for (int iii = 0; iii < batteries_len; iii++)
{ {
if (batteries[iii]->energy_used < max_capacity_battery_energy_used) if (possible_battery->box_type == BoxBattery && (1.0f - possible_battery->energy_used) > energy_to_consume)
{ {
max_capacity_battery = batteries[iii]; possible_battery->energy_used += energy_to_consume;
max_capacity_battery_energy_used = batteries[iii]->energy_used; 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

515
main.c

@ -4,27 +4,30 @@
#define SOKOL_IMPL #define SOKOL_IMPL
#define SOKOL_D3D11 #define SOKOL_D3D11
#include "sokol_gfx.h" #include <enet/enet.h>
#include "sokol_gp.h" #include <process.h> // starting server thread
#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert()
#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert()
#include "sokol_app.h" #include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h" #include "sokol_glue.h"
#include "sokol_gp.h"
#include "sokol_time.h" #include "sokol_time.h"
#include <enet/enet.h> #pragma warning ( default: 33010 )
#include <process.h> // starting server thread #pragma warning ( disable: 6262 ) // warning about using a lot of stack, lol that's how stb image is
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h" #include "stb_image.h"
#include "types.h" #include "types.h"
#include <string.h> // errno error message on file open
#include <inttypes.h> #include <inttypes.h>
#include <string.h> // errno error message on file open
static struct GameState gs = { 0 }; static struct GameState gs = { 0 };
static int myplayer = -1; static int myplayer = -1;
static bool right_mouse_down = false; static bool right_mouse_down = false;
static bool keydown[SAPP_KEYCODE_MENU] = { 0 }; static bool keydown[SAPP_KEYCODE_MENU] = { 0 };
typedef struct KeyPressed typedef struct KeyPressed {
{
bool pressed; bool pressed;
uint64_t frame; uint64_t frame;
} KeyPressed; } KeyPressed;
@ -33,7 +36,8 @@ static V2 mouse_pos = { 0 };
static bool mouse_pressed = false; static bool mouse_pressed = false;
static uint64_t mouse_pressed_frame = 0; static uint64_t mouse_pressed_frame = 0;
static bool mouse_frozen = false; // @BeforeShip make this debug only thing static bool mouse_frozen = false; // @BeforeShip make this debug only thing
static float funval = 0.0f; // easy to play with value controlled by left mouse button when held down @BeforeShip remove on release builds static float funval = 0.0f; // easy to play with value controlled by left mouse button when held
// down @BeforeShip remove on release builds
static struct ClientToServer client_to_server = { 0 }; // buffer of inputs static struct ClientToServer client_to_server = { 0 }; // buffer of inputs
static ENetHost* client; static ENetHost* client;
static ENetPeer* peer; static ENetPeer* peer;
@ -43,15 +47,16 @@ static sg_image image_itemframe;
static sg_image image_itemframe_selected; static sg_image image_itemframe_selected;
static sg_image image_thrusterburn; static sg_image image_thrusterburn;
static sg_image image_player; static sg_image image_player;
static sg_image image_cockpit_used;
static int cur_editing_boxtype = -1; static int cur_editing_boxtype = -1;
static int cur_editing_rotation = 0; static int cur_editing_rotation = 0;
static struct BoxInfo static struct BoxInfo {
{
enum BoxType type; enum BoxType type;
const char* image_path; const char* image_path;
sg_image image; sg_image image;
} boxes[] = { // if added to here will show up in toolbar, is placeable } boxes[] = {
// if added to here will show up in toolbar, is placeable
{ {
.type = BoxHullpiece, .type = BoxHullpiece,
.image_path = "loaded/hullpiece.png", .image_path = "loaded/hullpiece.png",
@ -63,13 +68,18 @@ static struct BoxInfo
{ {
.type = BoxBattery, .type = BoxBattery,
.image_path = "loaded/battery.png", .image_path = "loaded/battery.png",
} }; },
{
.type = BoxCockpit,
.image_path = "loaded/cockpit.png",
},
};
const int boxes_len = sizeof(boxes) / sizeof(*boxes); const int boxes_len = sizeof(boxes) / sizeof(*boxes);
struct BoxInfo boxinfo(enum BoxType type) struct BoxInfo
boxinfo(enum BoxType type)
{ {
for (int i = 0; i < boxes_len; i++) for (int i = 0; i < boxes_len; i++) {
{
if (boxes[i].type == type) if (boxes[i].type == type)
return boxes[i]; return boxes[i];
} }
@ -77,7 +87,8 @@ struct BoxInfo boxinfo(enum BoxType type)
return (struct BoxInfo) { 0 }; return (struct BoxInfo) { 0 };
} }
static sg_image load_image(const char* path) static sg_image
load_image(const char* path)
{ {
sg_image to_return = sg_alloc_image(); sg_image to_return = sg_alloc_image();
@ -87,13 +98,12 @@ static sg_image load_image(const char* path)
const int desired_channels = 4; const int desired_channels = 4;
stbi_set_flip_vertically_on_load(true); stbi_set_flip_vertically_on_load(true);
stbi_uc* image_data = stbi_load(path, &x, &y, &comp, desired_channels); stbi_uc* image_data = stbi_load(path, &x, &y, &comp, desired_channels);
if (!image_data) if (!image_data) {
{
fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason()); fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
exit(-1); exit(-1);
} }
sg_init_image(to_return, &(sg_image_desc){ sg_init_image(to_return,
.width = x, &(sg_image_desc) {.width = x,
.height = y, .height = y,
.pixel_format = SG_PIXELFORMAT_RGBA8, .pixel_format = SG_PIXELFORMAT_RGBA8,
.min_filter = SG_FILTER_NEAREST, .min_filter = SG_FILTER_NEAREST,
@ -101,51 +111,53 @@ static sg_image load_image(const char* path)
.data.subimage[0][0] = { .data.subimage[0][0] = {
.ptr = image_data, .ptr = image_data,
.size = (size_t)(x * y * desired_channels), .size = (size_t)(x * y * desired_channels),
}}); } });
stbi_image_free(image_data); stbi_image_free(image_data);
return to_return; return to_return;
} }
static void init(void) static void
init(void)
{ {
// @BeforeShip make all fprintf into logging to file, warning dialog grids on failure instead of exit(-1), replace the macros in sokol with this as well, like assert // @BeforeShip make all fprintf into logging to file, warning dialog grids on
// failure instead of exit(-1), replace the macros in sokol with this as well,
// like assert
Entity* entity_data = malloc(sizeof * entity_data * MAX_ENTITIES); Entity* entity_data = malloc(sizeof * entity_data * MAX_ENTITIES);
initialize(&gs, entity_data, sizeof * entity_data * MAX_ENTITIES); initialize(&gs, entity_data, sizeof * entity_data * MAX_ENTITIES);
sg_desc sgdesc = { .context = sapp_sgcontext() }; sg_desc sgdesc = { .context = sapp_sgcontext() };
sg_setup(&sgdesc); sg_setup(&sgdesc);
if (!sg_isvalid()) if (!sg_isvalid()) {
{
fprintf(stderr, "Failed to create Sokol GFX context!\n"); fprintf(stderr, "Failed to create Sokol GFX context!\n");
exit(-1); exit(-1);
} }
sgp_desc sgpdesc = { 0 }; sgp_desc sgpdesc = { 0 };
sgp_setup(&sgpdesc); sgp_setup(&sgpdesc);
if (!sgp_is_valid()) if (!sgp_is_valid()) {
{ fprintf(stderr,
fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error())); "Failed to create Sokol GP context: %s\n",
sgp_get_error_message(sgp_get_last_error()));
exit(-1); exit(-1);
} }
// image loading // image loading
{ {
for (int i = 0; i < boxes_len; i++) for (int i = 0; i < boxes_len; i++) {
{
boxes[i].image = load_image(boxes[i].image_path); boxes[i].image = load_image(boxes[i].image_path);
} }
image_thrusterburn = load_image("loaded/thrusterburn.png"); image_thrusterburn = load_image("loaded/thrusterburn.png");
image_itemframe = load_image("loaded/itemframe.png"); image_itemframe = load_image("loaded/itemframe.png");
image_itemframe_selected = load_image("loaded/itemframe_selected.png"); image_itemframe_selected = load_image("loaded/itemframe_selected.png");
image_player = load_image("loaded/player.png"); image_player = load_image("loaded/player.png");
image_cockpit_used = load_image("loaded/cockpit_used.png");
} }
// socket initialization // socket initialization
{ {
if (enet_initialize() != 0) if (enet_initialize() != 0) {
{
fprintf(stderr, "An error occurred while initializing ENet.\n"); fprintf(stderr, "An error occurred while initializing ENet.\n");
exit(-1); exit(-1);
} }
@ -154,9 +166,9 @@ static void init(void)
2 /* allow up 2 channels to be used, 0 and 1 */, 2 /* allow up 2 channels to be used, 0 and 1 */,
0 /* assume any amount of incoming bandwidth */, 0 /* assume any amount of incoming bandwidth */,
0 /* assume any amount of outgoing bandwidth */); 0 /* assume any amount of outgoing bandwidth */);
if (client == NULL) if (client == NULL) {
{ fprintf(
fprintf(stderr, stderr,
"An error occurred while trying to create an ENet client host.\n"); "An error occurred while trying to create an ENet client host.\n");
exit(-1); exit(-1);
} }
@ -166,20 +178,16 @@ static void init(void)
enet_address_set_host(&address, SERVER_ADDRESS); enet_address_set_host(&address, SERVER_ADDRESS);
address.port = SERVER_PORT; address.port = SERVER_PORT;
peer = enet_host_connect(client, &address, 2, 0); peer = enet_host_connect(client, &address, 2, 0);
if (peer == NULL) if (peer == NULL) {
{
fprintf(stderr, fprintf(stderr,
"No available peers for initiating an ENet connection.\n"); "No available peers for initiating an ENet connection.\n");
exit(-1); exit(-1);
} }
// the timeout is the third parameter here // the timeout is the third parameter here
if (enet_host_service(client, &event, 5000) > 0 && if (enet_host_service(client, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
event.type == ENET_EVENT_TYPE_CONNECT)
{
Log("Connected\n"); Log("Connected\n");
} }
else else {
{
/* Either the 5 seconds are up or a disconnect event was */ /* Either the 5 seconds are up or a disconnect event was */
/* received. Reset the peer in the event the 5 seconds */ /* received. Reset the peer in the event the 5 seconds */
/* had run out without any significant event. */ /* had run out without any significant event. */
@ -190,44 +198,54 @@ static void init(void)
} }
} }
#define DeferLoop(start, end) for (int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end)) #define DeferLoop(start, end) \
for (int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end))
#define transform_scope DeferLoop(sgp_push_transform(), sgp_pop_transform()) #define transform_scope DeferLoop(sgp_push_transform(), sgp_pop_transform())
static void draw_color_rect_centered(V2 center, float size) static void
draw_color_rect_centered(V2 center, float size)
{ {
float halfbox = size / 2.0f; float halfbox = size / 2.0f;
sgp_draw_filled_rect(center.x - halfbox, center.y - halfbox, size, size); sgp_draw_filled_rect(center.x - halfbox, center.y - halfbox, size, size);
} }
static void draw_texture_rectangle_centered(V2 center, V2 width_height) static void
draw_texture_rectangle_centered(V2 center, V2 width_height)
{ {
V2 halfsize = V2scale(width_height, 0.5f); V2 halfsize = V2scale(width_height, 0.5f);
sgp_draw_textured_rect(center.x - halfsize.x, center.y - halfsize.y, width_height.x, width_height.y); sgp_draw_textured_rect(center.x - halfsize.x,
center.y - halfsize.y,
width_height.x,
width_height.y);
} }
static void draw_texture_centered(V2 center, float size) static void
draw_texture_centered(V2 center, float size)
{ {
draw_texture_rectangle_centered(center, (V2) { size, size }); draw_texture_rectangle_centered(center, (V2) { size, size });
} }
static void draw_circle(V2 point, float radius) static void
draw_circle(V2 point, float radius)
{ {
#define POINTS 64 #define POINTS 64
sgp_line lines[POINTS]; sgp_line lines[POINTS];
for (int i = 0; i < POINTS; i++) for (int i = 0; i < POINTS; i++) {
{
float progress = (float)i / (float)POINTS; float progress = (float)i / (float)POINTS;
float next_progress = (float)(i + 1) / (float)POINTS; float next_progress = (float)(i + 1) / (float)POINTS;
lines[i].a = (V2){ .x = cos(progress * 2.0f * PI) * radius, .y = sin(progress * 2.0f * PI) * radius }; lines[i].a = (V2){ .x = cosf(progress * 2.0f * PI) * radius,
lines[i].b = (V2){ .x = cos(next_progress * 2.0f * PI) * radius, .y = sin(next_progress * 2.0f * PI) * radius }; .y = sinf(progress * 2.0f * PI) * radius };
lines[i].b = (V2){ .x = cosf(next_progress * 2.0f * PI) * radius,
.y = sinf(next_progress * 2.0f * PI) * radius };
lines[i].a = V2add(lines[i].a, point); lines[i].a = V2add(lines[i].a, point);
lines[i].b = V2add(lines[i].b, point); lines[i].b = V2add(lines[i].b, point);
} }
sgp_draw_lines(lines, POINTS); sgp_draw_lines(lines, POINTS);
} }
static Entity* myentity() static Entity*
myentity()
{ {
if (myplayer == -1) if (myplayer == -1)
return NULL; return NULL;
@ -237,100 +255,107 @@ static Entity* myentity()
return to_return; return to_return;
} }
static void ui(bool draw, float dt, float width, float height) static void
ui(bool draw, float dt, float width, float height)
{ {
static float cur_opacity = 1.0f; static float cur_opacity = 1.0f;
cur_opacity = lerp(cur_opacity, myentity() != NULL ? 1.0f : 0.0f, dt * 5.0f); cur_opacity = lerp(cur_opacity, myentity() != NULL ? 1.0f : 0.0f, dt * 5.0f);
if (cur_opacity <= 0.01f) if (cur_opacity <= 0.01f) {
{
return; return;
} }
// draw spice bar
if (draw) if (draw)
{ sgp_push_transform();
// if(draw) sgp_scale(1.0f, -1.0f);
// draw spice bar
if (draw) {
static float spice_taken_away = 0.5f; static float spice_taken_away = 0.5f;
if (myentity() != NULL) if (myentity() != NULL) {
{
spice_taken_away = myentity()->spice_taken_away; spice_taken_away = myentity()->spice_taken_away;
} }
sgp_set_color(0.5f, 0.5f, 0.5f, cur_opacity); sgp_set_color(0.5f, 0.5f, 0.5f, cur_opacity);
float margin = width * 0.1; float margin = width * 0.1f;
float bar_width = width - margin * 2.0f; float bar_width = width - margin * 2.0f;
sgp_draw_filled_rect(margin, 80.0f, bar_width, 30.0f); sgp_draw_filled_rect(margin, 80.0f, bar_width, 30.0f);
sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity); sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity);
sgp_draw_filled_rect(margin, 80.0f, bar_width * (1.0f - spice_taken_away), 30.0f); sgp_draw_filled_rect(
margin, 80.0f, bar_width * (1.0f - spice_taken_away), 30.0f);
} }
// draw item toolbar // draw item toolbar
{ {
int itemframe_width = sg_query_image_info(image_itemframe).width * 2.0f; float itemframe_width = (float)sg_query_image_info(image_itemframe).width * 2.0f;
int itemframe_height = sg_query_image_info(image_itemframe).height * 2.0f; float itemframe_height = (float)sg_query_image_info(image_itemframe).height * 2.0f;
int total_width = itemframe_width * boxes_len; float total_width = itemframe_width * boxes_len;
float item_width = itemframe_width * 0.75; float item_width = itemframe_width * 0.75f;
float item_height = itemframe_height * 0.75; float item_height = itemframe_height * 0.75f;
float item_offset_x = (itemframe_width - item_width) / 2.0f; float item_offset_x = (itemframe_width - item_width) / 2.0f;
float item_offset_y = (itemframe_height - item_height) / 2.0f; float item_offset_y = (itemframe_height - item_height) / 2.0f;
float x = width / 2.0 - total_width / 2.0; float x = width / 2.0f - total_width / 2.0f;
float y = height - itemframe_height * 1.5; float y = height - itemframe_height * 1.5f;
for (int i = 0; i < boxes_len; i++) for (int i = 0; i < boxes_len; i++) {
{ if (has_point(
if (has_point((AABB) { (AABB) {
.x = x, .x = x,
.y = y, .y = y,
.width = itemframe_width, .width = itemframe_width,
.height = itemframe_height, .height = itemframe_height,
}, },
mouse_pos) && mouse_pos)
mouse_pressed) && mouse_pressed) {
{
// "handle" mouse pressed // "handle" mouse pressed
mouse_pressed = false; mouse_pressed = false;
cur_editing_boxtype = i; cur_editing_boxtype = i;
} }
if (draw)
{ if (draw) {
sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity); sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity);
if (cur_editing_boxtype == i) if (cur_editing_boxtype == i) {
{
sgp_set_image(0, image_itemframe_selected); sgp_set_image(0, image_itemframe_selected);
} }
else else {
{
sgp_set_image(0, image_itemframe); sgp_set_image(0, image_itemframe);
} }
sgp_draw_textured_rect(x, y, itemframe_width, itemframe_height); sgp_draw_textured_rect(x, y, itemframe_width, itemframe_height);
sgp_set_image(0, boxinfo((enum BoxType)i).image); sgp_set_image(0, boxinfo((enum BoxType)i).image);
sgp_draw_textured_rect(x + item_offset_x, y + item_offset_y, item_width, item_height); transform_scope
{
float item_x = x + item_offset_x;
float item_y = y + item_offset_y;
sgp_scale_at(1.0f, -1.0f, item_x + item_width / 2.0f, item_y + item_height / 2.0f);
//sgp_scale(1.0f, -1.0f);
sgp_draw_textured_rect(item_x, item_y, item_width, item_height);
}
sgp_reset_image(0); sgp_reset_image(0);
} }
x += itemframe_width; x += itemframe_width;
} }
} }
if (draw)
sgp_pop_transform();
} }
static void frame(void) static void
frame(void)
{ {
int width = sapp_width(), height = sapp_height(); float width = (float)sapp_width(), height = (float)sapp_height();
float ratio = width / (float)height; float ratio = width / height;
double time = sapp_frame_count() * sapp_frame_duration(); double time = sapp_frame_count() * sapp_frame_duration();
float dt = sapp_frame_duration(); float dt = (float)sapp_frame_duration();
// pressed input management // pressed input management
{ {
for (int i = 0; i < SAPP_KEYCODE_MENU; i++) for (int i = 0; i < SAPP_KEYCODE_MENU; i++) {
{ if (keypressed[i].frame < sapp_frame_count()) {
if (keypressed[i].frame < sapp_frame_count())
{
keypressed[i].pressed = false; keypressed[i].pressed = false;
} }
} }
if (mouse_pressed_frame < sapp_frame_count()) if (mouse_pressed_frame < sapp_frame_count()) {
{
mouse_pressed = false; mouse_pressed = false;
} }
} }
@ -338,25 +363,24 @@ static void frame(void)
// networking // networking
{ {
ENetEvent event; ENetEvent event;
while (true) while (true) {
{
int enet_status = enet_host_service(client, &event, 0); int enet_status = enet_host_service(client, &event, 0);
if (enet_status > 0) if (enet_status > 0) {
{ switch (event.type) {
switch (event.type) case ENET_EVENT_TYPE_CONNECT: {
{
case ENET_EVENT_TYPE_CONNECT:
{
Log("New client from host %x\n", event.peer->address.host); Log("New client from host %x\n", event.peer->address.host);
break; break;
} }
case ENET_EVENT_TYPE_RECEIVE: case ENET_EVENT_TYPE_RECEIVE: {
{ // @Robust @BeforeShip use some kind of serialization strategy that
// @Robust @BeforeShip use some kind of serialization strategy that checks for out of bounds // checks for out of bounds and other validation instead of just
// and other validation instead of just casting to a struct // casting to a struct "Alignment of structure members can be
// "Alignment of structure members can be different even among different compilers on the same platform, let alone different platforms." // different even among different compilers on the same platform,
// ^^ need serialization strategy that accounts for this if multiple platforms is happening https://stackoverflow.com/questions/28455163/how-can-i-portably-send-a-c-struct-through-a-network-socket // let alone different platforms."
// ^^ need serialization strategy that accounts for this if multiple
// platforms is happening
// https://stackoverflow.com/questions/28455163/how-can-i-portably-send-a-c-struct-through-a-network-socket
ServerToClient msg = (ServerToClient){ ServerToClient msg = (ServerToClient){
.cur_gs = &gs, .cur_gs = &gs,
}; };
@ -367,21 +391,17 @@ static void frame(void)
break; break;
} }
case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT: {
{
fprintf(stderr, "Disconnected from server\n"); fprintf(stderr, "Disconnected from server\n");
exit(-1); exit(-1);
break; break;
} }
} }
} }
else if (enet_status == 0) else if (enet_status == 0) {
{
break; break;
} }
else if (enet_status < 0) else if (enet_status < 0) {
{
fprintf(stderr, "Error receiving enet events: %d\n", enet_status); fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
break; break;
} }
@ -392,14 +412,15 @@ static void frame(void)
ui(false, dt, width, height); // handle events ui(false, dt, width, height); // handle events
V2 build_target_pos = { 0 }; V2 build_target_pos = { 0 };
float build_target_rotation = 0.0f; float build_target_rotation = 0.0f;
static V2 camera_pos = { 0 }; // keeps camera at same position after player death static V2 camera_pos = {
V2 world_mouse_pos = mouse_pos; 0
struct BuildPreviewInfo }; // keeps camera at same position after player death
{ V2 world_mouse_pos = mouse_pos; // processed later in scope
struct BuildPreviewInfo {
V2 grid_pos; V2 grid_pos;
float grid_rotation; float grid_rotation;
V2 pos;
} build_preview = { 0 }; } build_preview = { 0 };
V2 hand_pos = { 0 };
bool hand_at_arms_length = false; bool hand_at_arms_length = false;
{ {
// interpolate zoom // interpolate zoom
@ -407,9 +428,7 @@ static void frame(void)
// calculate world position and camera // calculate world position and camera
{ {
if (myentity() != NULL) if (myentity() != NULL) {
{
camera_pos = entity_pos(myentity()); camera_pos = entity_pos(myentity());
} }
world_mouse_pos = V2sub(world_mouse_pos, (V2) { .x = width / 2.0f, .y = height / 2.0f }); world_mouse_pos = V2sub(world_mouse_pos, (V2) { .x = width / 2.0f, .y = height / 2.0f });
@ -420,106 +439,108 @@ static void frame(void)
// calculate build preview stuff // calculate build preview stuff
EntityID grid_to_build_on = (EntityID){ 0 }; EntityID grid_to_build_on = (EntityID){ 0 };
if (myentity() != NULL) if (myentity() != NULL) {
{ hand_pos = V2sub(world_mouse_pos, entity_pos(myentity()));
V2 hand_pos = V2sub(world_mouse_pos, entity_pos(myentity()));
float hand_len = V2length(hand_pos); float hand_len = V2length(hand_pos);
if (hand_len > MAX_HAND_REACH) if (hand_len > MAX_HAND_REACH) {
{
hand_at_arms_length = true; hand_at_arms_length = true;
hand_len = MAX_HAND_REACH; hand_len = MAX_HAND_REACH;
} }
else else {
{
hand_at_arms_length = false; hand_at_arms_length = false;
} }
hand_pos = V2scale(V2normalize(hand_pos), hand_len); hand_pos = V2scale(V2normalize(hand_pos), hand_len);
hand_pos = V2add(hand_pos, entity_pos(myentity())); hand_pos = V2add(hand_pos, entity_pos(myentity()));
Entity* placing_grid = closest_to_point_in_radius(&gs, hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP); Entity* placing_grid = closest_to_point_in_radius(&gs, hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP);
if (placing_grid == NULL) if (placing_grid == NULL) {
{
build_preview = (struct BuildPreviewInfo){ build_preview = (struct BuildPreviewInfo){
.grid_pos = hand_pos, .grid_pos = hand_pos,
.grid_rotation = 0.0f, .grid_rotation = 0.0f,
.pos = hand_pos,
}; };
} }
else else {
{
grid_to_build_on = get_id(&gs, placing_grid); grid_to_build_on = get_id(&gs, placing_grid);
V2 pos = grid_snapped_box_pos(placing_grid, hand_pos); hand_pos = grid_snapped_box_pos(placing_grid, hand_pos);
build_preview = (struct BuildPreviewInfo){ build_preview = (struct BuildPreviewInfo){ .grid_pos = entity_pos(placing_grid),
.grid_pos = entity_pos(placing_grid),
.grid_rotation = entity_rotation(placing_grid), .grid_rotation = entity_rotation(placing_grid),
.pos = pos }; };
} }
} }
// Create and send input packet // Create and send input packet
{ {
// @Robust accumulate total time and send input at rate like 20 hz, not every frame // @Robust accumulate total time and send input at rate like 20 hz, not
// every frame
static size_t last_frame_id = 0;
struct InputFrame cur_input_frame = { 0 }; struct InputFrame cur_input_frame = { 0 };
cur_input_frame.id = last_frame_id;
V2 input = (V2){ V2 input = (V2){
.x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A], .x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A],
.y = (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S], .y = (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S],
}; };
if (V2length(input) > 0.0)
input = V2normalize(input);
cur_input_frame.movement = input; cur_input_frame.movement = input;
cur_input_frame.inhabit = keypressed[SAPP_KEYCODE_G].pressed; cur_input_frame.seat_action = keypressed[SAPP_KEYCODE_G].pressed;
if (mouse_pressed && cur_editing_boxtype != -1) cur_input_frame.hand_pos = hand_pos;
{ if (mouse_pressed && cur_editing_boxtype != -1) {
cur_input_frame.dobuild = mouse_pressed; cur_input_frame.dobuild = mouse_pressed;
cur_input_frame.build_type = cur_editing_boxtype; cur_input_frame.build_type = cur_editing_boxtype;
cur_input_frame.build_rotation = cur_editing_rotation; cur_input_frame.build_rotation = cur_editing_rotation;
cur_input_frame.grid_to_build_on = grid_to_build_on; cur_input_frame.grid_to_build_on = grid_to_build_on;
Entity* grid = get_entity(&gs, grid_to_build_on);
if (grid != NULL)
{
cur_input_frame.build = grid_world_to_local(grid, build_preview.pos);
}
else
{
cur_input_frame.build = build_preview.pos;
}
} }
struct InputFrame latest = client_to_server.inputs[0]; struct InputFrame latest = client_to_server.inputs[0];
// if they're not the same // @Robust split this into separate lines and be very careful about testing for inequality
if (
!V2cmp(cur_input_frame.movement, latest.movement, 0.01f) || bool input_differs = false;
cur_input_frame.inhabit != latest.inhabit || input_differs = input_differs || !V2equal(cur_input_frame.movement, latest.movement, 0.01f);
cur_input_frame.dobuild != latest.dobuild ||
cur_input_frame.grid_to_build_on.generation != latest.grid_to_build_on.generation || input_differs = input_differs || cur_input_frame.seat_action != latest.seat_action;
cur_input_frame.grid_to_build_on.index != latest.grid_to_build_on.index || input_differs = input_differs || !entityids_same(cur_input_frame.seat_to_inhabit, latest.seat_to_inhabit);
!V2cmp(cur_input_frame.build, latest.build, 0.01f)) input_differs = input_differs || !V2equal(cur_input_frame.hand_pos, latest.hand_pos, 0.01f);
{
for (int i = 0; i < INPUT_BUFFER - 1; i++) input_differs = input_differs || cur_input_frame.dobuild != latest.dobuild;
{ input_differs = input_differs || cur_input_frame.build_type != latest.build_type;
client_to_server.inputs[i + 1] = client_to_server.inputs[i]; input_differs = input_differs || cur_input_frame.build_rotation != latest.build_rotation;
input_differs = input_differs || !entityids_same(cur_input_frame.grid_to_build_on, latest.grid_to_build_on);
if (input_differs) {
struct InputFrame last_frame = client_to_server.inputs[0];
for (int i = 0; i < INPUT_BUFFER - 1; i++) {
struct InputFrame last_last_frame = last_frame;
last_frame = client_to_server.inputs[i + 1];
client_to_server.inputs[i + 1] = last_last_frame;
} }
cur_input_frame.tick = tick(&gs); cur_input_frame.tick = tick(&gs);
client_to_server.inputs[0] = cur_input_frame; client_to_server.inputs[0] = cur_input_frame;
last_frame_id += 1;
} }
dbg_rect(client_to_server.inputs[0].hand_pos);
static double last_input_sent_time = 0.0; static double last_input_sent_time = 0.0;
if (fabs(last_input_sent_time - time) > TIME_BETWEEN_INPUT_PACKETS) if (fabs(last_input_sent_time - time) > TIME_BETWEEN_INPUT_PACKETS) {
{ ENetPacket* packet = enet_packet_create((void*)&client_to_server,
ENetPacket* packet = enet_packet_create((void*)&client_to_server, sizeof(client_to_server), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); sizeof(client_to_server),
ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(peer, 0, packet); enet_peer_send(peer, 0, packet);
last_input_sent_time = time; last_input_sent_time = time;
} }
} }
// @BeforeShip client side prediction and rollback to previous server authoritative state, then replay inputs // @BeforeShip client side prediction and rollback to previous server
// no need to store copies of game state, just player input frame to frame. Then know how many frames ago the server game state arrived, it's that easy! // authoritative state, then replay inputs no need to store copies of game
// process(&gs, (float)sapp_frame_duration()); // state, just player input frame to frame. Then know how many frames ago
// the server game state arrived, it's that easy! process(&gs,
// (float)sapp_frame_duration());
} }
// drawing // drawing
{ {
sgp_begin(width, height); sgp_begin((int)width, (int)height);
sgp_viewport(0, 0, width, height); sgp_viewport(0, 0, (int)width, (int)height);
sgp_project(0.0f, width, 0.0f, height); sgp_project(0.0f, width, 0.0f, height);
sgp_set_blend_mode(SGP_BLENDMODE_BLEND); sgp_set_blend_mode(SGP_BLENDMODE_BLEND);
@ -529,7 +550,8 @@ static void frame(void)
// sokol drawing library draw in world space // sokol drawing library draw in world space
// world space coordinates are +Y up, -Y down. Like normal cartesian coords // world space coordinates are +Y up, -Y down. Like normal cartesian coords
transform_scope{ transform_scope
{
sgp_translate(width / 2, height / 2); sgp_translate(width / 2, height / 2);
sgp_scale_at(zoom, -zoom, 0.0f, 0.0f); sgp_scale_at(zoom, -zoom, 0.0f, 0.0f);
@ -537,10 +559,9 @@ static void frame(void)
sgp_translate(-camera_pos.x, -camera_pos.y); sgp_translate(-camera_pos.x, -camera_pos.y);
// hand reached limit circle // hand reached limit circle
if (myentity() != NULL) if (myentity() != NULL) {
{
static float hand_reach_alpha = 1.0f; static float hand_reach_alpha = 1.0f;
hand_reach_alpha = lerp(hand_reach_alpha, hand_at_arms_length ? 1.0f : 0.0f, dt * 5.0); hand_reach_alpha = lerp(hand_reach_alpha, hand_at_arms_length ? 1.0f : 0.0f, dt * 5.0f);
sgp_set_color(1.0f, 1.0f, 1.0f, hand_reach_alpha); sgp_set_color(1.0f, 1.0f, 1.0f, hand_reach_alpha);
draw_circle(entity_pos(myentity()), MAX_HAND_REACH); draw_circle(entity_pos(myentity()), MAX_HAND_REACH);
} }
@ -548,10 +569,8 @@ static void frame(void)
// stars // stars
sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
const int num = 30; const int num = 30;
for (int x = -num; x < num; x++) for (int x = -num; x < num; x++) {
{ for (int y = -num; y < num; y++) {
for (int y = -num; y < num; y++)
{
sgp_draw_point((float)x * 0.1f, (float)y * 0.1f); sgp_draw_point((float)x * 0.1f, (float)y * 0.1f);
} }
} }
@ -559,30 +578,29 @@ static void frame(void)
float halfbox = BOX_SIZE / 2.0f; float halfbox = BOX_SIZE / 2.0f;
// mouse // mouse
if (mouse_frozen) if (mouse_frozen) {
{
sgp_set_color(1.0f, 0.0f, 0.0f, 0.5f); sgp_set_color(1.0f, 0.0f, 0.0f, 0.5f);
sgp_draw_filled_rect(world_mouse_pos.x, world_mouse_pos.y, 0.1f, 0.1f); sgp_draw_filled_rect(world_mouse_pos.x, world_mouse_pos.y, 0.1f, 0.1f);
} }
// building preview // building preview
if (cur_editing_boxtype != -1) if (cur_editing_boxtype != -1) {
{ sgp_set_color(0.5f, 0.5f, 0.5f, (sinf((float)time * 9.0f) + 1.0f) / 3.0f + 0.2f);
sgp_set_color(0.5f, 0.5f, 0.5f, (sin(time * 9.0f) + 1.0) / 3.0f + 0.2);
transform_scope transform_scope
{ {
sgp_set_image(0, boxinfo(cur_editing_boxtype).image); sgp_set_image(0, boxinfo(cur_editing_boxtype).image);
sgp_rotate_at(build_preview.grid_rotation + rotangle(cur_editing_rotation), build_preview.pos.x, build_preview.pos.y); sgp_rotate_at(build_preview.grid_rotation + rotangle(cur_editing_rotation),
draw_texture_centered(build_preview.pos, BOX_SIZE); hand_pos.x,
// drawbox(build_preview.pos, build_preview.grid_rotation, 0.0f, cur_editing_boxtype, cur_editing_rotation); hand_pos.y);
draw_texture_centered(hand_pos, BOX_SIZE);
// drawbox(hand_pos, build_preview.grid_rotation, 0.0f,
// cur_editing_boxtype, cur_editing_rotation);
sgp_reset_image(0); sgp_reset_image(0);
} }
} }
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)
continue; continue;
@ -602,37 +620,43 @@ static void frame(void)
} }
} }
#endif #endif
if (b->box_type == BoxBattery) if (b->box_type == BoxBattery) {
{
float cur_alpha = sgp_get_color().a; float cur_alpha = sgp_get_color().a;
Color from = WHITE; Color from = WHITE;
Color to = colhex(255, 0, 0); Color to = colhex(255, 0, 0);
Color result = Collerp(from, to, b->energy_used); Color result = Collerp(from, to, b->energy_used);
sgp_set_color(result.r, result.g, result.b, cur_alpha); sgp_set_color(result.r, result.g, result.b, cur_alpha);
} }
transform_scope { transform_scope
sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation), box_pos(b).x, box_pos(b).y);
if (b->box_type == BoxThruster)
{ {
sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation),
box_pos(b).x,
box_pos(b).y);
if (b->box_type == BoxThruster) {
transform_scope transform_scope
{ {
sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
sgp_set_image(0, image_thrusterburn); sgp_set_image(0, image_thrusterburn);
// float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0, 0.07, b->thrust); // float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0,
// printf("%f\n", b->thrust); // 0.07, b->thrust); printf("%f\n", b->thrust);
float scaling = 0.95 + lerp(0.0, 0.3, b->thrust); float scaling = 0.95f + lerp(0.0f, 0.3f, b->thrust);
// float scaling = 1.1; // float scaling = 1.1;
// sgp_translate(-(scaling*BOX_SIZE - BOX_SIZE), 0.0); // sgp_translate(-(scaling*BOX_SIZE - BOX_SIZE), 0.0);
// sgp_scale(scaling, 1.0); // sgp_scale(scaling, 1.0);
sgp_scale_at(scaling, 1.0, box_pos(b).x, box_pos(b).y); sgp_scale_at(scaling, 1.0f, box_pos(b).x, box_pos(b).y);
draw_texture_centered(box_pos(b), BOX_SIZE); draw_texture_centered(box_pos(b), BOX_SIZE);
sgp_reset_image(0); sgp_reset_image(0);
} }
} }
sg_image img = boxinfo(b->box_type).image;
if (b->box_type == BoxCockpit)
{
if (get_entity(&gs, b->piloted_by) != NULL)
img = image_cockpit_used;
}
sgp_set_image(0, boxinfo(b->box_type).image); sgp_set_image(0, img);
draw_texture_centered(box_pos(b), BOX_SIZE); draw_texture_centered(box_pos(b), BOX_SIZE);
sgp_reset_image(0); sgp_reset_image(0);
@ -645,61 +669,33 @@ static void frame(void)
V2 to = V2add(grid_com(g), vel); V2 to = V2add(grid_com(g), vel);
sgp_draw_line(grid_com(g).x, grid_com(g).y, to.x, to.y); sgp_draw_line(grid_com(g).x, grid_com(g).y, to.x, to.y);
} }
if (e->is_player) if (e->is_player && get_entity(&gs, e->currently_piloting_seat) == NULL) {
{
transform_scope transform_scope
{ {
sgp_rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y); sgp_rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
sgp_set_color(1.0f, 0.5f, 0.5f, 1.0f); sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
sgp_set_image(0, image_player); sgp_set_image(0, image_player);
draw_texture_rectangle_centered(entity_pos(e), PLAYER_SIZE); draw_texture_rectangle_centered(entity_pos(e), PLAYER_SIZE);
sgp_reset_image(0); sgp_reset_image(0);
} }
} }
} }
}
// player
for (int i = 0; i < MAX_PLAYERS; i++)
{
struct Player* player = &gs.players[i];
if (!player->connected)
continue;
Entity* p = get_entity(&gs, player->entity);
if (p == NULL)
continue;
static float opacities[MAX_PLAYERS] = { 1.0f };
static V2 positions[MAX_PLAYERS] = { 0 };
opacities[i] = lerp(opacities[i], p != NULL ? 1.0f : 0.1f, dt * 7.0f);
Color col_to_draw = Collerp(WHITE, GOLD, p->goldness);
col_to_draw.a = opacities[i];
if (p != NULL)
{
positions[i] = entity_pos(p);
}
set_color(col_to_draw);
transform_scope
{
float psize = 0.1f;
sgp_draw_filled_rect(positions[i].x - psize / 2.0f, positions[i].y - psize / 2.0f, psize, psize);
}
}
// gold target // gold target
set_color(GOLD); set_color(GOLD);
sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f); sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f);
sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
dbg_drawall(); dbg_drawall();
} // world space transform end
} }
// UI drawn in screen space // UI drawn in screen space
ui(true, dt, width, height); ui(true, dt, width, height);
sg_pass_action pass_action = { 0 }; sg_pass_action pass_action = { 0 };
sg_begin_default_pass(&pass_action, width, height); sg_begin_default_pass(&pass_action, (int)width, (int)height);
sgp_flush(); sgp_flush();
sgp_end(); sgp_end();
sg_end_pass(); sg_end_pass();
@ -717,38 +713,31 @@ void cleanup(void)
void event(const sapp_event* e) void event(const sapp_event* e)
{ {
switch (e->type) switch (e->type) {
{
case SAPP_EVENTTYPE_KEY_DOWN: case SAPP_EVENTTYPE_KEY_DOWN:
if (e->key_code == SAPP_KEYCODE_T) if (e->key_code == SAPP_KEYCODE_T) {
{
mouse_frozen = !mouse_frozen; mouse_frozen = !mouse_frozen;
} }
if (e->key_code == SAPP_KEYCODE_R) if (e->key_code == SAPP_KEYCODE_R) {
{
cur_editing_rotation += 1; cur_editing_rotation += 1;
cur_editing_rotation %= RotationLast; cur_editing_rotation %= RotationLast;
} }
int key_num = e->key_code - SAPP_KEYCODE_0; int key_num = e->key_code - SAPP_KEYCODE_0;
int target_box = key_num - 1; int target_box = key_num - 1;
if (target_box < BoxLast) if (target_box < BoxLast) {
{
cur_editing_boxtype = target_box; cur_editing_boxtype = target_box;
} }
if (!mouse_frozen) if (!mouse_frozen) {
{
keydown[e->key_code] = true; keydown[e->key_code] = true;
if (keypressed[e->key_code].frame == 0) if (keypressed[e->key_code].frame == 0) {
{
keypressed[e->key_code].pressed = true; keypressed[e->key_code].pressed = true;
keypressed[e->key_code].frame = e->frame_count; keypressed[e->key_code].frame = e->frame_count;
} }
} }
break; break;
case SAPP_EVENTTYPE_KEY_UP: case SAPP_EVENTTYPE_KEY_UP:
if (!mouse_frozen) if (!mouse_frozen) {
{
keydown[e->key_code] = false; keydown[e->key_code] = false;
keypressed[e->key_code].pressed = false; keypressed[e->key_code].pressed = false;
@ -760,34 +749,28 @@ void event(const sapp_event* e)
zoom_target = clamp(zoom_target, 0.5f, 900.0f); zoom_target = clamp(zoom_target, 0.5f, 900.0f);
break; break;
case SAPP_EVENTTYPE_MOUSE_DOWN: case SAPP_EVENTTYPE_MOUSE_DOWN:
if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
{
mouse_pressed = true; mouse_pressed = true;
mouse_pressed_frame = e->frame_count; mouse_pressed_frame = e->frame_count;
} }
if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) {
{
right_mouse_down = true; right_mouse_down = true;
} }
break; break;
case SAPP_EVENTTYPE_MOUSE_UP: case SAPP_EVENTTYPE_MOUSE_UP:
if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
{
mouse_pressed = false; mouse_pressed = false;
mouse_pressed_frame = 0; mouse_pressed_frame = 0;
} }
if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) {
{
right_mouse_down = false; right_mouse_down = false;
} }
break; break;
case SAPP_EVENTTYPE_MOUSE_MOVE: case SAPP_EVENTTYPE_MOUSE_MOVE:
if (!mouse_frozen) if (!mouse_frozen) {
{
mouse_pos = (V2){ .x = e->mouse_x, .y = e->mouse_y }; mouse_pos = (V2){ .x = e->mouse_x, .y = e->mouse_y };
} }
if (right_mouse_down) if (right_mouse_down) {
{
funval += e->mouse_dx; funval += e->mouse_dx;
Log("Funval %f\n", funval); Log("Funval %f\n", funval);
} }
@ -795,10 +778,10 @@ void event(const sapp_event* e)
} }
} }
sapp_desc sokol_main(int argc, char* argv[]) sapp_desc
sokol_main(int argc, char* argv[])
{ {
if (argc > 1) if (argc > 1) {
{
_beginthread(server, 0, NULL); _beginthread(server, 0, NULL);
} }
(void)argv; (void)argv;

@ -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!
@ -91,14 +91,23 @@ typedef struct
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;
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; V2 movement;
bool inhabit;
// if grid_index != -1, this is in local coordinates to the grid bool seat_action;
V2 build; EntityID seat_to_inhabit;
V2 hand_pos; // world coords, world star!
bool dobuild; bool dobuild;
enum BoxType build_type; enum BoxType build_type;
enum CompassRotation build_rotation; enum CompassRotation build_rotation;
@ -112,10 +121,13 @@ typedef struct 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 // 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 EntityID shape_parent_entity; // can't be zero if shape is nonzero
V2 shape_size; V2 shape_size;
@ -136,31 +148,34 @@ typedef struct Entity
EntityID next_box; EntityID next_box;
EntityID prev_box; // doubly linked so can remove in middle of chain EntityID prev_box; // doubly linked so can remove in middle of chain
enum CompassRotation compass_rotation; enum CompassRotation compass_rotation;
float thrust; // must be between 0 and 1 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 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;
@ -194,7 +209,7 @@ static float rotangle(enum CompassRotation rot)
typedef struct ServerToClient typedef struct ServerToClient
{ {
struct GameState *cur_gs; struct GameState* cur_gs;
int your_player; int your_player;
} ServerToClient; } ServerToClient;
@ -204,47 +219,47 @@ struct ClientToServer
}; };
// 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();
@ -270,7 +285,7 @@ static bool has_point(AABB aabb, V2 point)
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,
}; };
@ -278,7 +293,7 @@ static V2 V2add(V2 a, V2 b)
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,
}; };
@ -312,7 +327,7 @@ static V2 V2project(V2 vec, V2 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),
}; };
@ -326,13 +341,13 @@ static float V2angle(V2 vec)
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;
} }
@ -363,7 +378,7 @@ static float lerp(float a, float b, float 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);
@ -386,7 +401,7 @@ typedef struct 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,
@ -396,7 +411,7 @@ static Color colhex(int r, int g, int b)
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);

Loading…
Cancel
Save