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>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Users\Cameron\Documents\flight\thirdparty\enet\include;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include\chipmunk;C:\Users\Cameron\Documents\flight\thirdparty;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include</AdditionalIncludeDirectories>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

513
main.c

@ -4,27 +4,30 @@
#define SOKOL_IMPL
#define SOKOL_D3D11
#include "sokol_gfx.h"
#include "sokol_gp.h"
#include <enet/enet.h>
#include <process.h> // starting server thread
#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert()
#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert()
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_gp.h"
#include "sokol_time.h"
#include <enet/enet.h>
#include <process.h> // starting server thread
#pragma warning ( default: 33010 )
#pragma warning ( disable: 6262 ) // warning about using a lot of stack, lol that's how stb image is
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "types.h"
#include <string.h> // errno error message on file open
#include <inttypes.h>
#include <string.h> // errno error message on file open
static struct GameState gs = { 0 };
static int myplayer = -1;
static bool right_mouse_down = false;
static bool keydown[SAPP_KEYCODE_MENU] = { 0 };
typedef struct KeyPressed
{
typedef struct KeyPressed {
bool pressed;
uint64_t frame;
} KeyPressed;
@ -33,7 +36,8 @@ static V2 mouse_pos = { 0 };
static bool mouse_pressed = false;
static uint64_t mouse_pressed_frame = 0;
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 ENetHost* client;
static ENetPeer* peer;
@ -43,15 +47,16 @@ static sg_image image_itemframe;
static sg_image image_itemframe_selected;
static sg_image image_thrusterburn;
static sg_image image_player;
static sg_image image_cockpit_used;
static int cur_editing_boxtype = -1;
static int cur_editing_rotation = 0;
static struct BoxInfo
{
static struct BoxInfo {
enum BoxType type;
const char* image_path;
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,
.image_path = "loaded/hullpiece.png",
@ -63,13 +68,18 @@ static struct BoxInfo
{
.type = BoxBattery,
.image_path = "loaded/battery.png",
} };
},
{
.type = BoxCockpit,
.image_path = "loaded/cockpit.png",
},
};
const int boxes_len = sizeof(boxes) / sizeof(*boxes);
struct BoxInfo boxinfo(enum BoxType type)
{
for (int i = 0; i < boxes_len; i++)
struct BoxInfo
boxinfo(enum BoxType type)
{
for (int i = 0; i < boxes_len; i++) {
if (boxes[i].type == type)
return boxes[i];
}
@ -77,7 +87,8 @@ struct BoxInfo boxinfo(enum BoxType type)
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();
@ -87,13 +98,12 @@ static sg_image load_image(const char* path)
const int desired_channels = 4;
stbi_set_flip_vertically_on_load(true);
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());
exit(-1);
}
sg_init_image(to_return, &(sg_image_desc){
.width = x,
sg_init_image(to_return,
&(sg_image_desc) {.width = x,
.height = y,
.pixel_format = SG_PIXELFORMAT_RGBA8,
.min_filter = SG_FILTER_NEAREST,
@ -107,45 +117,47 @@ static sg_image load_image(const char* path)
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);
initialize(&gs, entity_data, sizeof * entity_data * MAX_ENTITIES);
sg_desc sgdesc = { .context = sapp_sgcontext() };
sg_setup(&sgdesc);
if (!sg_isvalid())
{
if (!sg_isvalid()) {
fprintf(stderr, "Failed to create Sokol GFX context!\n");
exit(-1);
}
sgp_desc sgpdesc = { 0 };
sgp_setup(&sgpdesc);
if (!sgp_is_valid())
{
fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error()));
if (!sgp_is_valid()) {
fprintf(stderr,
"Failed to create Sokol GP context: %s\n",
sgp_get_error_message(sgp_get_last_error()));
exit(-1);
}
// 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);
}
image_thrusterburn = load_image("loaded/thrusterburn.png");
image_itemframe = load_image("loaded/itemframe.png");
image_itemframe_selected = load_image("loaded/itemframe_selected.png");
image_player = load_image("loaded/player.png");
image_cockpit_used = load_image("loaded/cockpit_used.png");
}
// socket initialization
{
if (enet_initialize() != 0)
{
if (enet_initialize() != 0) {
fprintf(stderr, "An error occurred while initializing ENet.\n");
exit(-1);
}
@ -154,9 +166,9 @@ static void init(void)
2 /* allow up 2 channels to be used, 0 and 1 */,
0 /* assume any amount of incoming bandwidth */,
0 /* assume any amount of outgoing bandwidth */);
if (client == NULL)
{
fprintf(stderr,
if (client == NULL) {
fprintf(
stderr,
"An error occurred while trying to create an ENet client host.\n");
exit(-1);
}
@ -166,20 +178,16 @@ static void init(void)
enet_address_set_host(&address, SERVER_ADDRESS);
address.port = SERVER_PORT;
peer = enet_host_connect(client, &address, 2, 0);
if (peer == NULL)
{
if (peer == NULL) {
fprintf(stderr,
"No available peers for initiating an ENet connection.\n");
exit(-1);
}
// the timeout is the third parameter here
if (enet_host_service(client, &event, 5000) > 0 &&
event.type == ENET_EVENT_TYPE_CONNECT)
{
if (enet_host_service(client, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
Log("Connected\n");
}
else
{
else {
/* Either the 5 seconds are up or a disconnect event was */
/* received. Reset the peer in the event the 5 seconds */
/* 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())
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;
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);
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 });
}
static void draw_circle(V2 point, float radius)
static void
draw_circle(V2 point, float radius)
{
#define POINTS 64
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 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].b = (V2){ .x = cos(next_progress * 2.0f * PI) * radius, .y = sin(next_progress * 2.0f * PI) * radius };
lines[i].a = (V2){ .x = cosf(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].b = V2add(lines[i].b, point);
}
sgp_draw_lines(lines, POINTS);
}
static Entity* myentity()
static Entity*
myentity()
{
if (myplayer == -1)
return NULL;
@ -237,100 +255,107 @@ static Entity* myentity()
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;
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;
}
// draw spice bar
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;
if (myentity() != NULL)
{
if (myentity() != NULL) {
spice_taken_away = myentity()->spice_taken_away;
}
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;
sgp_draw_filled_rect(margin, 80.0f, bar_width, 30.0f);
sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity);
sgp_draw_filled_rect(margin, 80.0f, bar_width * (1.0f - spice_taken_away), 30.0f);
sgp_draw_filled_rect(
margin, 80.0f, bar_width * (1.0f - spice_taken_away), 30.0f);
}
// draw item toolbar
{
int itemframe_width = sg_query_image_info(image_itemframe).width * 2.0f;
int itemframe_height = sg_query_image_info(image_itemframe).height * 2.0f;
int total_width = itemframe_width * boxes_len;
float item_width = itemframe_width * 0.75;
float item_height = itemframe_height * 0.75;
float itemframe_width = (float)sg_query_image_info(image_itemframe).width * 2.0f;
float itemframe_height = (float)sg_query_image_info(image_itemframe).height * 2.0f;
float total_width = itemframe_width * boxes_len;
float item_width = itemframe_width * 0.75f;
float item_height = itemframe_height * 0.75f;
float item_offset_x = (itemframe_width - item_width) / 2.0f;
float item_offset_y = (itemframe_height - item_height) / 2.0f;
float x = width / 2.0 - total_width / 2.0;
float y = height - itemframe_height * 1.5;
for (int i = 0; i < boxes_len; i++)
{
if (has_point((AABB) {
float x = width / 2.0f - total_width / 2.0f;
float y = height - itemframe_height * 1.5f;
for (int i = 0; i < boxes_len; i++) {
if (has_point(
(AABB) {
.x = x,
.y = y,
.width = itemframe_width,
.height = itemframe_height,
},
mouse_pos) &&
mouse_pressed)
{
mouse_pos)
&& mouse_pressed) {
// "handle" mouse pressed
mouse_pressed = false;
cur_editing_boxtype = i;
}
if (draw)
{
if (draw) {
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);
}
else
{
else {
sgp_set_image(0, image_itemframe);
}
sgp_draw_textured_rect(x, y, itemframe_width, itemframe_height);
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);
}
x += itemframe_width;
}
}
if (draw)
sgp_pop_transform();
}
static void frame(void)
static void
frame(void)
{
int width = sapp_width(), height = sapp_height();
float ratio = width / (float)height;
float width = (float)sapp_width(), height = (float)sapp_height();
float ratio = width / height;
double time = sapp_frame_count() * sapp_frame_duration();
float dt = sapp_frame_duration();
float dt = (float)sapp_frame_duration();
// pressed input management
{
for (int i = 0; i < SAPP_KEYCODE_MENU; i++)
{
if (keypressed[i].frame < sapp_frame_count())
{
for (int i = 0; i < SAPP_KEYCODE_MENU; i++) {
if (keypressed[i].frame < sapp_frame_count()) {
keypressed[i].pressed = false;
}
}
if (mouse_pressed_frame < sapp_frame_count())
{
if (mouse_pressed_frame < sapp_frame_count()) {
mouse_pressed = false;
}
}
@ -338,25 +363,24 @@ static void frame(void)
// networking
{
ENetEvent event;
while (true)
{
while (true) {
int enet_status = enet_host_service(client, &event, 0);
if (enet_status > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
{
if (enet_status > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT: {
Log("New client from host %x\n", event.peer->address.host);
break;
}
case ENET_EVENT_TYPE_RECEIVE:
{
// @Robust @BeforeShip use some kind of serialization strategy that checks for out of bounds
// and other validation instead of just casting to a struct
// "Alignment of structure members can be different even among different compilers on the same platform, let alone different platforms."
// ^^ need serialization strategy that accounts for this if multiple platforms is happening https://stackoverflow.com/questions/28455163/how-can-i-portably-send-a-c-struct-through-a-network-socket
case ENET_EVENT_TYPE_RECEIVE: {
// @Robust @BeforeShip use some kind of serialization strategy that
// checks for out of bounds and other validation instead of just
// casting to a struct "Alignment of structure members can be
// different even among different compilers on the same platform,
// let alone different platforms."
// ^^ need serialization strategy that accounts for this if multiple
// platforms is happening
// https://stackoverflow.com/questions/28455163/how-can-i-portably-send-a-c-struct-through-a-network-socket
ServerToClient msg = (ServerToClient){
.cur_gs = &gs,
};
@ -367,21 +391,17 @@ static void frame(void)
break;
}
case ENET_EVENT_TYPE_DISCONNECT:
{
case ENET_EVENT_TYPE_DISCONNECT: {
fprintf(stderr, "Disconnected from server\n");
exit(-1);
break;
}
}
}
else if (enet_status == 0)
{
else if (enet_status == 0) {
break;
}
else if (enet_status < 0)
{
else if (enet_status < 0) {
fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
break;
}
@ -392,14 +412,15 @@ static void frame(void)
ui(false, dt, width, height); // handle events
V2 build_target_pos = { 0 };
float build_target_rotation = 0.0f;
static V2 camera_pos = { 0 }; // keeps camera at same position after player death
V2 world_mouse_pos = mouse_pos;
struct BuildPreviewInfo
{
static V2 camera_pos = {
0
}; // keeps camera at same position after player death
V2 world_mouse_pos = mouse_pos; // processed later in scope
struct BuildPreviewInfo {
V2 grid_pos;
float grid_rotation;
V2 pos;
} build_preview = { 0 };
V2 hand_pos = { 0 };
bool hand_at_arms_length = false;
{
// interpolate zoom
@ -407,9 +428,7 @@ static void frame(void)
// calculate world position and camera
{
if (myentity() != NULL)
{
if (myentity() != NULL) {
camera_pos = entity_pos(myentity());
}
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
EntityID grid_to_build_on = (EntityID){ 0 };
if (myentity() != NULL)
{
V2 hand_pos = V2sub(world_mouse_pos, entity_pos(myentity()));
if (myentity() != NULL) {
hand_pos = V2sub(world_mouse_pos, entity_pos(myentity()));
float hand_len = V2length(hand_pos);
if (hand_len > MAX_HAND_REACH)
{
if (hand_len > MAX_HAND_REACH) {
hand_at_arms_length = true;
hand_len = MAX_HAND_REACH;
}
else
{
else {
hand_at_arms_length = false;
}
hand_pos = V2scale(V2normalize(hand_pos), hand_len);
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);
if (placing_grid == NULL)
{
if (placing_grid == NULL) {
build_preview = (struct BuildPreviewInfo){
.grid_pos = hand_pos,
.grid_rotation = 0.0f,
.pos = hand_pos,
};
}
else
{
else {
grid_to_build_on = get_id(&gs, placing_grid);
V2 pos = grid_snapped_box_pos(placing_grid, hand_pos);
build_preview = (struct BuildPreviewInfo){
.grid_pos = entity_pos(placing_grid),
hand_pos = grid_snapped_box_pos(placing_grid, hand_pos);
build_preview = (struct BuildPreviewInfo){ .grid_pos = entity_pos(placing_grid),
.grid_rotation = entity_rotation(placing_grid),
.pos = pos };
};
}
}
// 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 };
cur_input_frame.id = last_frame_id;
V2 input = (V2){
.x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A],
.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.inhabit = keypressed[SAPP_KEYCODE_G].pressed;
if (mouse_pressed && cur_editing_boxtype != -1)
{
cur_input_frame.seat_action = keypressed[SAPP_KEYCODE_G].pressed;
cur_input_frame.hand_pos = hand_pos;
if (mouse_pressed && cur_editing_boxtype != -1) {
cur_input_frame.dobuild = mouse_pressed;
cur_input_frame.build_type = cur_editing_boxtype;
cur_input_frame.build_rotation = cur_editing_rotation;
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];
// if they're not the same
if (
!V2cmp(cur_input_frame.movement, latest.movement, 0.01f) ||
cur_input_frame.inhabit != latest.inhabit ||
cur_input_frame.dobuild != latest.dobuild ||
cur_input_frame.grid_to_build_on.generation != latest.grid_to_build_on.generation ||
cur_input_frame.grid_to_build_on.index != latest.grid_to_build_on.index ||
!V2cmp(cur_input_frame.build, latest.build, 0.01f))
{
for (int i = 0; i < INPUT_BUFFER - 1; i++)
{
client_to_server.inputs[i + 1] = client_to_server.inputs[i];
// @Robust split this into separate lines and be very careful about testing for inequality
bool input_differs = false;
input_differs = input_differs || !V2equal(cur_input_frame.movement, latest.movement, 0.01f);
input_differs = input_differs || cur_input_frame.seat_action != latest.seat_action;
input_differs = input_differs || !entityids_same(cur_input_frame.seat_to_inhabit, latest.seat_to_inhabit);
input_differs = input_differs || !V2equal(cur_input_frame.hand_pos, latest.hand_pos, 0.01f);
input_differs = input_differs || cur_input_frame.dobuild != latest.dobuild;
input_differs = input_differs || cur_input_frame.build_type != latest.build_type;
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);
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;
if (fabs(last_input_sent_time - time) > TIME_BETWEEN_INPUT_PACKETS)
{
ENetPacket* packet = enet_packet_create((void*)&client_to_server, sizeof(client_to_server), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
if (fabs(last_input_sent_time - time) > TIME_BETWEEN_INPUT_PACKETS) {
ENetPacket* packet = enet_packet_create((void*)&client_to_server,
sizeof(client_to_server),
ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(peer, 0, packet);
last_input_sent_time = time;
}
}
// @BeforeShip client side prediction and rollback to previous server authoritative state, then replay inputs
// 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!
// process(&gs, (float)sapp_frame_duration());
// @BeforeShip client side prediction and rollback to previous server
// authoritative state, then replay inputs 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! process(&gs,
// (float)sapp_frame_duration());
}
// drawing
{
sgp_begin(width, height);
sgp_viewport(0, 0, width, height);
sgp_begin((int)width, (int)height);
sgp_viewport(0, 0, (int)width, (int)height);
sgp_project(0.0f, width, 0.0f, height);
sgp_set_blend_mode(SGP_BLENDMODE_BLEND);
@ -529,7 +550,8 @@ static void frame(void)
// sokol drawing library draw in world space
// world space coordinates are +Y up, -Y down. Like normal cartesian coords
transform_scope{
transform_scope
{
sgp_translate(width / 2, height / 2);
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);
// hand reached limit circle
if (myentity() != NULL)
{
if (myentity() != NULL) {
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);
draw_circle(entity_pos(myentity()), MAX_HAND_REACH);
}
@ -548,10 +569,8 @@ static void frame(void)
// stars
sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
const int num = 30;
for (int x = -num; x < num; x++)
{
for (int y = -num; y < num; y++)
{
for (int x = -num; x < num; x++) {
for (int y = -num; y < num; y++) {
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;
// mouse
if (mouse_frozen)
{
if (mouse_frozen) {
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);
}
// building preview
if (cur_editing_boxtype != -1)
{
sgp_set_color(0.5f, 0.5f, 0.5f, (sin(time * 9.0f) + 1.0) / 3.0f + 0.2);
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);
transform_scope
{
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);
draw_texture_centered(build_preview.pos, BOX_SIZE);
// drawbox(build_preview.pos, build_preview.grid_rotation, 0.0f, cur_editing_boxtype, cur_editing_rotation);
sgp_rotate_at(build_preview.grid_rotation + rotangle(cur_editing_rotation),
hand_pos.x,
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);
}
}
for (int i = 0; i < gs.cur_next_entity; i++)
{
for (size_t i = 0; i < gs.cur_next_entity; i++) {
Entity* e = &gs.entities[i];
if (!e->exists)
continue;
@ -602,37 +620,43 @@ static void frame(void)
}
}
#endif
if (b->box_type == BoxBattery)
{
if (b->box_type == BoxBattery) {
float cur_alpha = sgp_get_color().a;
Color from = WHITE;
Color to = colhex(255, 0, 0);
Color result = Collerp(from, to, b->energy_used);
sgp_set_color(result.r, result.g, result.b, cur_alpha);
}
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)
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) {
transform_scope
{
sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
sgp_set_image(0, image_thrusterburn);
// float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0, 0.07, b->thrust);
// printf("%f\n", b->thrust);
float scaling = 0.95 + lerp(0.0, 0.3, b->thrust);
// float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0,
// 0.07, b->thrust); printf("%f\n", b->thrust);
float scaling = 0.95f + lerp(0.0f, 0.3f, b->thrust);
// float scaling = 1.1;
// sgp_translate(-(scaling*BOX_SIZE - BOX_SIZE), 0.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);
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);
sgp_reset_image(0);
@ -645,61 +669,33 @@ static void frame(void)
V2 to = V2add(grid_com(g), vel);
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
{
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);
draw_texture_rectangle_centered(entity_pos(e), PLAYER_SIZE);
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
set_color(GOLD);
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);
dbg_drawall();
} // world space transform end
}
// UI drawn in screen space
ui(true, dt, width, height);
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_end();
sg_end_pass();
@ -717,38 +713,31 @@ void cleanup(void)
void event(const sapp_event* e)
{
switch (e->type)
{
switch (e->type) {
case SAPP_EVENTTYPE_KEY_DOWN:
if (e->key_code == SAPP_KEYCODE_T)
{
if (e->key_code == SAPP_KEYCODE_T) {
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 %= RotationLast;
}
int key_num = e->key_code - SAPP_KEYCODE_0;
int target_box = key_num - 1;
if (target_box < BoxLast)
{
if (target_box < BoxLast) {
cur_editing_boxtype = target_box;
}
if (!mouse_frozen)
{
if (!mouse_frozen) {
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].frame = e->frame_count;
}
}
break;
case SAPP_EVENTTYPE_KEY_UP:
if (!mouse_frozen)
{
if (!mouse_frozen) {
keydown[e->key_code] = 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);
break;
case SAPP_EVENTTYPE_MOUSE_DOWN:
if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT)
{
if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
mouse_pressed = true;
mouse_pressed_frame = e->frame_count;
}
if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT)
{
if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) {
right_mouse_down = true;
}
break;
case SAPP_EVENTTYPE_MOUSE_UP:
if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT)
{
if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
mouse_pressed = false;
mouse_pressed_frame = 0;
}
if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT)
{
if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) {
right_mouse_down = false;
}
break;
case SAPP_EVENTTYPE_MOUSE_MOVE:
if (!mouse_frozen)
{
if (!mouse_frozen) {
mouse_pos = (V2){ .x = e->mouse_x, .y = e->mouse_y };
}
if (right_mouse_down)
{
if (right_mouse_down) {
funval += e->mouse_dx;
Log("Funval %f\n", funval);
}
@ -795,10 +778,10 @@ void event(const sapp_event* e)
}
}
sapp_desc sokol_main(int argc, char* argv[])
{
if (argc > 1)
sapp_desc
sokol_main(int argc, char* argv[])
{
if (argc > 1) {
_beginthread(server, 0, NULL);
}
(void)argv;

@ -60,7 +60,7 @@ void server(void* data)
ENetEvent event;
uint64_t last_processed_time = stm_now();
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)
{
// @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 };
memcpy(&received, event.packet->data, length);
int64_t player_slot = (int64_t)event.peer->data;
uint64_t latest_tick = player_to_latest_tick_processed[player_slot];
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--)
{
if (received.inputs[i].tick == 0) // empty input
continue;
if (received.inputs[i].tick <= latest_tick)
if (received.inputs[i].id <= latest_id)
continue; // don't reprocess inputs already processed
struct InputFrame cur_input = received.inputs[i];
gs.players[player_slot].input.movement = cur_input.movement;
gs.players[player_slot].input.hand_pos = cur_input.hand_pos;
// for these "event" inputs, only modify the current input if the event is true.
// while processing the gamestate, will mark it as false once processed. This
// prevents setting the event input to false before it's been processed.
if (cur_input.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)
{
gs.players[player_slot].input.grid_to_build_on = cur_input.grid_to_build_on;
gs.players[player_slot].input.build = cur_input.build;
gs.players[player_slot].input.dobuild = cur_input.dobuild;
gs.players[player_slot].input.build_type = cur_input.build_type;
gs.players[player_slot].input.build_rotation = cur_input.build_rotation;
}
}
player_to_latest_tick_processed[player_slot] = received.inputs[0].tick;
player_to_latest_id_processed[player_slot] = received.inputs[0].id;
}
}
@ -165,7 +165,7 @@ void server(void* data)
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);
gs.players[player_index].connected = false;
// box_destroy(&gs.players[player_index].box);
@ -203,7 +203,7 @@ void server(void* data)
to_send.cur_gs = &gs;
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);
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 TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
#define SERVER_PORT 2551
#define INPUT_BUFFER 4
#define INPUT_BUFFER 6
// must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1"
#include "ipsettings.h" // don't leak IP!
@ -91,14 +91,23 @@ typedef struct
unsigned int index; // index into the entity arena
} EntityID;
static bool entityids_same(EntityID a, EntityID b)
{
return (a.generation == b.generation) && (a.index == b.index);
}
// when updated, must update serialization, AND comparison
// function in main.c
struct InputFrame
{
uint64_t tick;
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;
bool inhabit;
// if grid_index != -1, this is in local coordinates to the grid
V2 build;
bool seat_action;
EntityID seat_to_inhabit;
V2 hand_pos; // world coords, world star!
bool dobuild;
enum BoxType build_type;
enum CompassRotation build_rotation;
@ -115,7 +124,10 @@ typedef struct Entity
cpBody* body; // used by grid, player, and box
cpShape* shape; // must be a box so shape_size can be set appropriately, and serialized
// for serializing the shape
// @Robust remove shape_parent_entity from this struct, use the shape's body to figure out
// what the shape's parent entity is
EntityID shape_parent_entity; // can't be zero if shape is nonzero
V2 shape_size;
@ -136,10 +148,18 @@ typedef struct Entity
EntityID next_box;
EntityID prev_box; // doubly linked so can remove in middle of chain
enum CompassRotation compass_rotation;
float thrust; // must be between 0 and 1
float wanted_thrust; // the thrust command applied to the thruster
float thrust; // the actual thrust it can provide based on energy sources in the grid
float energy_used; // battery
EntityID piloted_by;
} Entity;
typedef struct Player
{
bool connected;
EntityID entity;
struct InputFrame input;
} Player;
// gotta update the serialization functions when this changes
typedef struct GameState
{
@ -149,12 +169,7 @@ typedef struct GameState
V2 goldpos;
struct Player
{
bool connected;
EntityID entity;
struct InputFrame input;
} players[MAX_PLAYERS];
Player players[MAX_PLAYERS];
// Entity arena
// ent:ity pointers can't move around because of how the physics engine handles user data.
@ -207,7 +222,7 @@ struct ClientToServer
void server(void* data); // data parameter required from thread api...
// 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 process(struct GameState* gs, float dt); // does in place
Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius);
@ -239,7 +254,7 @@ V2 grid_world_to_local(Entity *grid, V2 world);
V2 grid_snapped_box_pos(Entity* grid, V2 world); // returns the snapped pos in world coords
float entity_angular_velocity(Entity* grid);
V2 entity_shape_pos(Entity* box);
V2 box_pos(Entity *box);
V2 box_pos(Entity* box); // returns in world coords
float box_rotation(Entity* box);
// thruster
@ -332,7 +347,7 @@ static V2 V2sub(V2 a, V2 b)
};
}
static bool V2cmp(V2 a, V2 b, float eps)
static bool V2equal(V2 a, V2 b, float eps)
{
return V2length(V2sub(a, b)) < eps;
}

Loading…
Cancel
Save